def map_property(type, attrname, notify=None, on_modified=None): def getter(self): return getattr(self, attrname) def setter(self, val): setattr(self, attrname, val) if notify: getattr(self, notify).emit() if on_modified: getattr(self, on_modified)() if notify: return pyqtProperty(type, getter, setter, notify) else: return pyqtProperty(type, getter, setter)
def magic_property(prop_name, prop_type, notify, notify_name): """ Create pyqtProperty object with value captured in the closure and a 'notify' signal attached. You need to pass both: - unbound signal, because pyqtProperty needs it - signal name to allow setter to find the *bound* signal and emit it. """ def getter(self): value = getattr(self, '_' + prop_name) return value def setter(self, value): setattr(self, '_' + prop_name, value) getattr(self, notify_name).emit(value) prop = pyqtProperty( type=prop_type, fget=getter, fset=setter, notify=notify) return prop
def dataPyqtProperty( type, origName ): name = '_' + origName signalName = origName + 'Changed' signal = pyqtSignal( type, name = signalName ) def getter( self ): return getattr( self, name ) def setter( self, value ): if value != getter( self ): getattr( self, signalName ).emit( value ) setattr( self, name, value ) return pyqtProperty( type, getter, setter, notify = signal ), signal
def stylePyqtProperty( type, origName ): name = '_' + origName signalName = origName + 'Changed' signal = pyqtSignal( type, name = signalName ) def getter( self ): return self.lookupProperty( origName ) def setter( self, value ): print( f'setting {origName} of {self.parent().metaObject().className()} to {value}' ) if getattr( self, name ) != value: setattr( self, name, value ) self.notify( origName, value ) return pyqtProperty( type, getter, setter, notify = signal ), signal
def simpleProperty(type, name, notify, readonly=False): _name = "_" + name def getter(obj): return getattr(obj, _name) def setter(obj, value): if getattr(obj, _name) != value: qDebug("set %s %s" % (_name, value)) setattr(obj, _name, value) notify.__get__(obj).emit() fset = setter if readonly: fset = None return pyqtProperty(type, fget=getter, fset=fset, notify=notify)
class HALLabel(QLabel, _HalWidgetBase): def __init__(self, parent=None): super(HALLabel, self).__init__(parent) self._textTemplate = '%f' self.istate = False self._pin_name = '' self._bit_pin_type = True self._s32_pin_type = False self._float_pin_type = False def _hal_init(self): if self._pin_name == '': pname = self.HAL_NAME_ else: pname = self._pin_name if self._bit_pin_type: self.hal_pin = self.HAL_GCOMP_.newpin(pname, hal.HAL_BIT, hal.HAL_IN) self.hal_pin.value_changed.connect( lambda data: self._setText(data)) elif self._float_pin_type: self.hal_pin = self.HAL_GCOMP_.newpin(pname, hal.HAL_FLOAT, hal.HAL_IN) self.hal_pin.value_changed.connect( lambda data: self._setText(data)) elif self._s32_pin_type: self.hal_pin = self.HAL_GCOMP_.newpin(pname, hal.HAL_S32, hal.HAL_IN) self.hal_pin.value_changed.connect( lambda data: self._setText(data)) def _setText(self, data): tmpl = lambda s: str(self._textTemplate) % s self.setText(tmpl(data)) # one can connect signals to this widget to # feed an input that gets scaled by this widget. @pyqtSlot(float) @pyqtSlot(int) @pyqtSlot(bool) def setDisplay(self, data): self._setText(data) ######################################################################### # This is how designer can interact with our widget properties. # designer will show the pyqtProperty properties in the editor # it will use the get set and reset calls to do those actions ######################################################################## def _toggle_properties(self, picked): data = ('bit', 's32', 'float') for i in data: if not i == picked: self[i + '_pin_type'] = False def set_pin_name(self, value): self._pin_name = value def get_pin_name(self): return self._pin_name def reset_pin_name(self): self._pin_name = '' def set_bit_pin_type(self, value): self._bit_pin_type = value if value: self._toggle_properties('bit') def get_bit_pin_type(self): return self._bit_pin_type def reset_bit_pin_type(self): self._bit_pin_type = '' def set_s32_pin_type(self, value): self._s32_pin_type = value if value: self._toggle_properties('s32') def get_s32_pin_type(self): return self._s32_pin_type def reset_s32_pin_type(self): self._s32_pin_type = '' def set_float_pin_type(self, value): self._float_pin_type = value if value: self._toggle_properties('float') def get_float_pin_type(self): return self._float_pin_type def reset_float_pin_type(self): self._float_pin_type = '' def set_textTemplate(self, data): self._textTemplate = data def get_textTemplate(self): return self._textTemplate def reset_textTemplate(self): self._textTemplate = '%d' # designer will show these properties in this order: pin_name = pyqtProperty(str, get_pin_name, set_pin_name, reset_pin_name) bit_pin_type = pyqtProperty(bool, get_bit_pin_type, set_bit_pin_type, reset_bit_pin_type) s32_pin_type = pyqtProperty(bool, get_s32_pin_type, set_s32_pin_type, reset_s32_pin_type) float_pin_type = pyqtProperty(bool, get_float_pin_type, set_float_pin_type, reset_float_pin_type) textTemplate = pyqtProperty(str, get_textTemplate, set_textTemplate, reset_textTemplate) ############################## # required class boiler code # ############################## def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value)
class GCodeGraphics(Lcnc_3dGraphics, _HalWidgetBase): def __init__(self, parent=None): super( GCodeGraphics, self).__init__(parent) self.colors['overlay_background'] = (0.0, 0.0, 0.0) # blue self._overlayColor = QColor(0, 0, 0, 0) self.colors['back'] = (0.0, 0.0, 0.75) # blue self._backgroundColor = QColor(0, 0, 0.75, 150) self.use_gradient_background = False # color1 is the bottom color that blends up to color2 self.gradient_color1 = (0.,0,.5) self.gradient_color2 = (0,.0, 0) self.show_overlay = False # no DRO or DRO overlay self._reload_filename = None self._view_incr = 20 self.inhibit_selection = False def _hal_init(self): STATUS.connect('file-loaded', self.load_program) STATUS.connect('reload-display', self.reloadfile) STATUS.connect('actual-spindle-speed-changed', self.set_spindle_speed) STATUS.connect('metric-mode-changed', lambda w, f: self.set_metric_units(w, f)) STATUS.connect('graphics-view-changed', self.set_view_signal) def set_view_signal(self, w, view, args): v = view.lower() if v == 'clear': self.logger.clear() elif v == 'zoom-in': self.zoomin() elif v == 'zoom-out': self.zoomout() elif v == 'pan-down': self.recordMouse(0,0) self.translateOrRotate(0,self._view_incr) elif v == 'pan-up': self.recordMouse(0,0) self.translateOrRotate(0,-self._view_incr) elif v == 'pan-right': self.recordMouse(0,0) self.translateOrRotate(self._view_incr,0) elif v == 'pan-left': self.recordMouse(0,0) self.translateOrRotate(-self._view_incr,0) elif v == 'rotate-ccw': self.recordMouse(0,0) self.rotateOrTranslate(self._view_incr,0) elif v == 'rotate-cw': self.recordMouse(0,0) self.rotateOrTranslate(-self._view_incr,0) elif v == 'rotate-up': self.recordMouse(0,0) self.rotateOrTranslate(0,self._view_incr) elif v == 'rotate-down': self.recordMouse(0,0) self.rotateOrTranslate(0,-self._view_incr) elif v == 'overlay-offsets-on': self.setShowOffsets(True) elif v == 'overlay-offsets-off': self.setShowOffsets(False) elif v == 'overlay-dro-on': self.setdro(True) elif v == 'overlay-dro-off': self.setdro(False) elif v == 'pan-view': self.panView(args.get('X'),args.get('Y')) elif v == 'rotate-view': self.rotateView(args.get('X'),args.get('Y')) elif v == 'grid-size': self.grid_size = args.get('SIZE') self.updateGL() elif v == 'alpha-mode-on': self.set_alpha_mode(True) elif v == 'alpha-mode-off': self.set_alpha_mode(False) elif v == 'inhibit-selection-on': self.inhibit_selection = True elif v == 'inhibit-selection-off': self.inhibit_selection = False elif v == 'dimensions-on': self.show_extents_option = True self.updateGL() elif v == 'dimensions-off': self.show_extents_option = False self.updateGL() else: self.set_view(v) def load_program(self, g, fname): LOG.debug('load the display: {}'.format(fname)) self._reload_filename = fname self.load(fname) STATUS.emit('graphics-gcode-properties',self.gcode_properties) # reset the current view to standard calculated zoom and position self.set_current_view() def set_metric_units(self, w, state): self.metric_units = state self.updateGL() def set_spindle_speed(self, w, rate): if rate < 1: rate = 1 self.spindle_speed = rate def set_view(self, value): view = str(value).lower() if self.lathe_option: if view not in ['p', 'y', 'y2']: return False elif view not in ['p', 'x', 'y', 'z', 'z2']: return False self.current_view = view if self.initialised: self.set_current_view() def reloadfile(self, w): LOG.debug('reload the display: {}'.format(self._reload_filename)) try: self.load(self._reload_filename) STATUS.emit('graphics-gcode-properties',self.gcode_properties) except: print('error', self._reload_filename) pass #################################################### # functions that override qt5_graphics #################################################### def report_gcode_error(self, result, seq, filename): error_str = gcode.strerror(result) errortext = "G-Code error in " + os.path.basename(filename) + "\n" + "Near line " \ + str(seq) + " of\n" + filename + "\n" + error_str + "\n" STATUS.emit("graphics-gcode-error", errortext) # Override qt5_graphics / glcannon.py function so we can emit a GObject signal def update_highlight_variable(self, line): self.highlight_line = line if line is None: line = -1 STATUS.emit('graphics-line-selected', line) def select_fire(self): if self.inhibit_selection: return if STATUS.is_auto_running(): return if not self.select_primed: return x, y = self.select_primed self.select_primed = None self.select(x, y) # override user plot -One could add gl commands to plot static objects here def user_plot(self): return def emit_percent(self, f): super( GCodeGraphics, self).emit_percent(f) STATUS.emit('graphics-loading-progress',f) ######################################################################### # This is how designer can interact with our widget properties. # property getter/setters ######################################################################### # VIEW def setview(self, view): self.current_view = view self.set_view(view) def getview(self): return self.current_view def resetview(self): self.set_view('p') _view = pyqtProperty(str, getview, setview, resetview) # DRO def setdro(self, state): self.enable_dro = state self.updateGL() def getdro(self): return self.enable_dro _dro = pyqtProperty(bool, getdro, setdro) # DTG def setdtg(self, state): self.show_dtg = state self.updateGL() def getdtg(self): return self.show_dtg _dtg = pyqtProperty(bool, getdtg, setdtg) # METRIC def setmetric(self, state): self.metric_units = state self.updateGL() def getmetric(self): return self.metric_units _metric = pyqtProperty(bool, getmetric, setmetric) # overlay def setoverlay(self, overlay): self.show_overlay = overlay self.updateGL() def getoverlay(self): return self.show_overlay def resetoverlay(self): self.show_overlay(False) _overlay = pyqtProperty(bool, getoverlay, setoverlay, resetoverlay) # show Offsets def setShowOffsets(self, state): self.show_offsets = state self.updateGL() def getShowOffsets(self): return self.show_offsets _offsets = pyqtProperty(bool, getShowOffsets, setShowOffsets) def getOverlayColor(self): return self._overlayColor def setOverlayColor(self, value): self._overlayColor = value self.colors['overlay_background'] = (value.redF(), value.greenF(), value.blueF()) self.updateGL() def resetOverlayColor(self): self._overlayColor = QColor(0, 0, .75, 150) overlay_color = pyqtProperty(QColor, getOverlayColor, setOverlayColor, resetOverlayColor) def getBackgroundColor(self): return self._backgroundColor def setBackgroundColor(self, value): self._backgroundColor = value #print value.getRgbF() self.colors['back'] = (value.redF(), value.greenF(), value.blueF()) self.gradient_color1 = (value.redF(), value.greenF(), value.blueF()) self.updateGL() def resetBackgroundColor(self): self._backgroundColor = QColor(0, 0, 0, 0) self.gradient_color1 = QColor(0, 0, 0, 0) value = QColor(0, 0, 0, 0) self.gradient_color1 = (value.redF(), value.greenF(), value.blueF()) self.colors['back'] = (value.redF(), value.greenF(), value.blueF()) self.updateGL() background_color = pyqtProperty(QColor, getBackgroundColor, setBackgroundColor, resetBackgroundColor) # use gradient background def setGradientBackground(self, state): self.use_gradient_background = state self.updateGL() def getGradientBackground(self): return self.use_gradient_background _use_gradient_background = pyqtProperty(bool, getGradientBackground, setGradientBackground)
class InstanceContainer(QObject, ContainerInterface, PluginObject): Version = 2 ## Constructor # # \param container_id A unique, machine readable/writable ID for this container. def __init__(self, container_id, *args, **kwargs): super().__init__(parent = None, *args, **kwargs) self._id = str(container_id) # type: str self._name = container_id # type: str self._definition = None # type: DefinitionContainerInterface self._metadata = {} self._instances = {} # type: Dict[str, SettingInstance] self._read_only = False self._dirty = False self._path = "" self._postponed_emits = [] self._cached_values = None def __hash__(self): # We need to re-implement the hash, because we defined the __eq__ operator. # According to some, returning the ID is technically not right, as objects with the same value should return # the same hash. The way we use it, it is acceptable for objects with the same value to return a different hash. return id(self) def __deepcopy__(self, memo): new_container = self.__class__(self._id) new_container._name = self._name new_container._definition = self._definition new_container._metadata = copy.deepcopy(self._metadata, memo) new_container._instances = copy.deepcopy(self._instances, memo) for instance in new_container._instances.values(): #Set the back-links of the new instances correctly to the copied container. instance._container = new_container new_container._read_only = self._read_only new_container._dirty = self._dirty new_container._path = copy.deepcopy(self._path, memo) new_container._cached_values = copy.deepcopy(self._cached_values, memo) return new_container def __eq__(self, other): self._instantiateCachedValues() if type(self) != type(other): return False # Type mismatch if self._id != other.getId(): return False # ID mismatch for entry in self._metadata: if other.getMetaDataEntry(entry) != self._metadata[entry]: return False # Meta data entry mismatch for entry in other.getMetaData(): if entry not in self._metadata: return False # Other has a meta data entry that this object does not have. for key in self._instances: if key not in other._instances: return False # This object has an instance that other does not have. if self._instances[key] != other._instances[key]: return False # The objects don't match. for key in other._instances: if key not in self._instances: return False # Other has an instance that this object does not have. return True def __ne__(self, other): return not (self == other) ## For pickle support def __getnewargs__(self): return (self._id,) ## For pickle support def __getstate__(self): return self.__dict__ ## For pickle support def __setstate__(self, state): self.__dict__.update(state) ## \copydoc ContainerInterface::getId # # Reimplemented from ContainerInterface def getId(self) -> str: return self._id id = pyqtProperty(str, fget = getId, constant = True) def setCachedValues(self, cached_values): if not self._instances: self._cached_values = cached_values else: Logger.log("w", "Unable set values to be lazy loaded when values are already loaded ") @classmethod def getLoadingPriority(cls) -> int: return 1 ## \copydoc ContainerInterface::getPath. # # Reimplemented from ContainerInterface def getPath(self): return self._path ## \copydoc ContainerInterface::setPath # # Reimplemented from ContainerInterface def setPath(self, path): self._path = path ## \copydoc ContainerInterface::getName # # Reimplemented from ContainerInterface def getName(self) -> str: return self._name def setName(self, name): if name != self._name: self._name = name self._dirty = True self.nameChanged.emit() self.pyqtNameChanged.emit() # Because we want to expose the properties of InstanceContainer as Qt properties for # CURA-3497, the nameChanged signal should be changed to a pyqtSignal. However, # pyqtSignal throws TypeError when calling disconnect() when there are no connections. # This causes a lot of errors in Cura code when we try to disconnect from nameChanged. # Therefore, rather than change the type of nameChanged, we add an extra signal that # is used as notify for the property. # # TODO: Remove this once the Cura code has been refactored to not use nameChanged anymore. pyqtNameChanged = pyqtSignal() nameChanged = Signal() name = pyqtProperty(str, fget = getName, fset = setName, notify = pyqtNameChanged) ## \copydoc ContainerInterface::isReadOnly # # Reimplemented from ContainerInterface def isReadOnly(self) -> bool: return self._read_only def setReadOnly(self, read_only): if read_only != self._read_only: self._read_only = read_only self.readOnlyChanged.emit() readOnlyChanged = pyqtSignal() readOnly = pyqtProperty(bool, fget = isReadOnly, fset = setReadOnly, notify = readOnlyChanged) ## \copydoc ContainerInterface::getMetaData # # Reimplemented from ContainerInterface def getMetaData(self): return self._metadata def setMetaData(self, metadata): if metadata != self._metadata: self._metadata = metadata self._dirty = True self.metaDataChanged.emit(self) metaDataChanged = pyqtSignal(QObject) metaData = pyqtProperty("QVariantMap", fget = getMetaData, fset = setMetaData, notify = metaDataChanged) ## \copydoc ContainerInterface::getMetaDataEntry # # Reimplemented from ContainerInterface def getMetaDataEntry(self, entry, default = None): return self._metadata.get(entry, default) ## Add a new entry to the metadata of this container. # # \param key \type{str} The key of the new entry. # \param value The value of the new entry. # # \note This does nothing if the key already exists. def addMetaDataEntry(self, key, value): if key not in self._metadata: self._metadata[key] = value self._dirty = True self.metaDataChanged.emit(self) else: Logger.log("w", "Meta data with key %s was already added.", key) ## Set a metadata entry to a certain value. # # \param key The key of the metadata entry to set. # \param value The new value of the metadata. # # \note This does nothing if the key is not already added to the metadata. def setMetaDataEntry(self, key, value): if key in self._metadata: self._metadata[key] = value self._dirty = True self.metaDataChanged.emit(self) else: Logger.log("w", "Meta data with key %s was not found. Unable to change.", key) ## Check if this container is dirty, that is, if it changed from deserialization. def isDirty(self): return self._dirty def setDirty(self, dirty): if self._read_only: Logger.log("w", "Tried to set dirty on read-only object.") else: self._dirty = dirty ## \copydoc ContainerInterface::getProperty # # Reimplemented from ContainerInterface def getProperty(self, key, property_name): self._instantiateCachedValues() if key in self._instances: try: return getattr(self._instances[key], property_name) except AttributeError: pass return None ## \copydoc ContainerInterface::hasProperty # # Reimplemented from ContainerInterface. def hasProperty(self, key, property_name): self._instantiateCachedValues() return key in self._instances and hasattr(self._instances[key], property_name) ## Set the value of a property of a SettingInstance. # # This will set the value of the specified property on the SettingInstance corresponding to key. # If no instance has been created for the specified key, a new one will be created and inserted # into this instance. # # \param key \type{string} The key of the setting to set a property of. # \param property_name \type{string} The name of the property to set. # \param property_value The new value of the property. # \param container The container to use for retrieving values when changing the property triggers property updates. Defaults to None, which means use the current container. # \param set_from_cache Flag to indicate that the property was set from cache. This triggers the behavior that the read_only and setDirty are ignored. # # \note If no definition container is set for this container, new instances cannot be created and this method will do nothing. def setProperty(self, key, property_name, property_value, container = None, set_from_cache = False): if self._read_only and not set_from_cache: Logger.log( "w", "Tried to setProperty [%s] with value [%s] with key [%s] on read-only object [%s]" % ( property_name, property_value, key, self.id)) return if key not in self._instances: if not self._definition: Logger.log("w", "Tried to set value of setting %s that has no instance in container %s and the container has no definition", key, self._name) return setting_definition = self._definition.findDefinitions(key = key) if not setting_definition: Logger.log("w", "Tried to set value of setting %s that has no instance in container %s or its definition %s", key, self._name, self._definition.getName()) return instance = SettingInstance(setting_definition[0], self) instance.propertyChanged.connect(self.propertyChanged) self._instances[instance.definition.key] = instance self._instances[key].setProperty(property_name, property_value, container) if not set_from_cache: self.setDirty(True) propertyChanged = Signal() ## Remove all instances from this container. def clear(self): self._instantiateCachedValues() all_keys = self._instances.copy() for key in all_keys: self.removeInstance(key, postpone_emit=True) self.sendPostponedEmits() ## Get all the keys of the instances of this container # \returns list of keys def getAllKeys(self): if self._cached_values: # If we only want the keys and the actual values are still cached, just get the keys from the cache. return self._cached_values.keys() return [key for key in self._instances] ## Create a new InstanceContainer with the same contents as this container # # \param new_id \type{str} The new ID of the container # \param new_name \type{str} The new name of the container. Defaults to None to indicate the name should not change. # # \return A new InstanceContainer with the same contents as this container. def duplicate(self, new_id: str, new_name: str = None): self._instantiateCachedValues() new_container = self.__class__(new_id) if new_name: new_container.setName(new_name) new_container.setMetaData(copy.deepcopy(self._metadata)) new_container.setDefinition(self._definition) for instance_id in self._instances: instance = self._instances[instance_id] for property_name in instance.definition.getPropertyNames(): if not hasattr(instance, property_name): continue new_container.setProperty(instance.definition.key, property_name, getattr(instance, property_name)) new_container._dirty = True new_container._read_only = False return new_container ## \copydoc ContainerInterface::serialize # # Reimplemented from ContainerInterface def serialize(self) -> str: self._instantiateCachedValues() parser = configparser.ConfigParser(interpolation = None) if not self._definition: Logger.log("w", "Tried to serialize an instance container without definition, this is not supported") return "" parser["general"] = {} parser["general"]["version"] = str(self.Version) parser["general"]["name"] = str(self._name) parser["general"]["definition"] = str(self._definition.getId()) parser["metadata"] = {} for key, value in self._metadata.items(): parser["metadata"][key] = str(value) parser["values"] = {} for key, instance in sorted(self._instances.items()): try: parser["values"][key] = str(instance.value) except AttributeError: pass stream = io.StringIO() parser.write(stream) return stream.getvalue() def _readAndValidateSerialized(self, serialized: str) -> configparser.ConfigParser: parser = configparser.ConfigParser(interpolation=None) parser.read_string(serialized) has_general = "general" in parser has_version = "version" in parser["general"] has_definition = "definition" in parser["general"] if not has_general or not has_version or not has_definition: exception_string = "Missing the required" if not has_general: exception_string += " section 'general'" if not has_definition: exception_string += " property 'definition'" if not has_version: exception_string += " property 'version'" raise InvalidInstanceError(exception_string) return parser def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]: configuration_type = None try: parser = self._readAndValidateSerialized(serialized) configuration_type = parser['metadata'].get('type') except Exception as e: Logger.log("d", "Could not get configuration type: %s", e) return configuration_type def getVersionFromSerialized(self, serialized: str) -> Optional[int]: configuration_type = self.getConfigurationTypeFromSerialized(serialized) # get version version = None try: import UM.VersionUpgradeManager version = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().getFileVersion(configuration_type, serialized) except Exception as e: #Logger.log("d", "Could not get version from serialized: %s", e) pass return version ## \copydoc ContainerInterface::deserialize # # Reimplemented from ContainerInterface def deserialize(self, serialized: str) -> str: # update the serialized data first serialized = super().deserialize(serialized) parser = self._readAndValidateSerialized(serialized) if int(parser["general"]["version"]) != self.Version: raise IncorrectInstanceVersionError("Reported version {0} but expected version {1}".format(int(parser["general"]["version"]), self.Version)) # Reset old data self._metadata = {} self._instances = {} self._name = parser["general"].get("name", self._id) definition_id = parser["general"]["definition"] definitions = _containerRegistry.findDefinitionContainers(id = definition_id) if not definitions: raise DefinitionNotFoundError("Could not find definition {0} required for instance {1}".format(definition_id, self._id)) self._definition = definitions[0] if "metadata" in parser: self._metadata = dict(parser["metadata"]) if "values" in parser: self._cached_values = dict(parser["values"]) self._dirty = False ## Instance containers are lazy loaded. This function ensures that it happened. def _instantiateCachedValues(self): if not self._cached_values: return for key, value in self._cached_values.items(): self.setProperty(key, "value", value, self._definition, set_from_cache=True) self._cached_values = None ## Find instances matching certain criteria. # # \param kwargs \type{dict} A dictionary of keyword arguments with key-value pairs that should match properties of the instances. def findInstances(self, **kwargs) -> List[SettingInstance]: self._instantiateCachedValues() result = [] for setting_key, instance in self._instances.items(): for key, value in kwargs.items(): if not hasattr(instance, key) or getattr(instance, key) != value: break else: result.append(instance) return result ## Get an instance by key # def getInstance(self, key: str) -> SettingInstance: self._instantiateCachedValues() if key in self._instances: return self._instances[key] return None ## Add a new instance to this container. def addInstance(self, instance: SettingInstance) -> None: self._instantiateCachedValues() key = instance.definition.key if key in self._instances: return instance.propertyChanged.connect(self.propertyChanged) instance.propertyChanged.emit(key, "value") self._instances[key] = instance ## Remove an instance from this container. # /param postpone_emit postpone emit until calling sendPostponedEmits def removeInstance(self, key: str, postpone_emit: bool=False) -> None: self._instantiateCachedValues() if key not in self._instances: return instance = self._instances[key] del self._instances[key] if postpone_emit: # postpone, call sendPostponedEmits later. The order matters. self._postponed_emits.append((instance.propertyChanged, (key, "validationState"))) self._postponed_emits.append((instance.propertyChanged, (key, "state"))) self._postponed_emits.append((instance.propertyChanged, (key, "value"))) for property_name in instance.definition.getPropertyNames(): if instance.definition.dependsOnProperty(property_name) == "value": self._postponed_emits.append((instance.propertyChanged, (key, property_name))) else: # Notify listeners of changed properties for all related properties instance.propertyChanged.emit(key, "value") instance.propertyChanged.emit(key, "state") # State is no longer user state, so signal is needed. instance.propertyChanged.emit(key, "validationState") # If the value was invalid, it should now no longer be invalid. for property_name in instance.definition.getPropertyNames(): if instance.definition.dependsOnProperty(property_name) == "value": self.propertyChanged.emit(key, property_name) self._dirty = True instance.updateRelations(self) ## Get the DefinitionContainer used for new instance creation. def getDefinition(self) -> DefinitionContainerInterface: return self._definition ## Set the DefinitionContainer to use for new instance creation. # # Since SettingInstance needs a SettingDefinition to work properly, we need some # way of figuring out what SettingDefinition to use when creating a new SettingInstance. def setDefinition(self, definition: DefinitionContainerInterface): self._definition = definition def __lt__(self, other): own_weight = int(self.getMetaDataEntry("weight", 0)) other_weight = int(other.getMetaDataEntry("weight", 0)) if own_weight and other_weight: return own_weight < other_weight return self._name < other.name ## Send postponed emits # These emits are collected from the option postpone_emit. # Note: the option can be implemented for all functions modifying the container. def sendPostponedEmits(self): while self._postponed_emits: signal, signal_arg = self._postponed_emits.pop(0) signal.emit(*signal_arg)
class MacroTabDialog(QDialog, _HalWidgetBase): def __init__(self, parent=None): super(MacroTabDialog, self).__init__(parent) self.setWindowTitle('Qtvcp Macro Menu') self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'MACROTAB' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(00, 200) self.resize(600, 400) # patch class to call our button methods rather then the # original methods (Gotta do before instantiation) MacroTab.closeChecked = self._close MacroTab.runChecked = self._run MacroTab.setTitle = self._setTitle # ok now instantiate patched class self.tab = MacroTab() self.tab.setObjectName('macroTabInternal_') l = QVBoxLayout() self.setLayout(l) l.addWidget(self.tab) #we need the close button self.tab.closeButton.setVisible(True) def _hal_init(self): x = self.geometry().x() y = self.geometry().y() w = self.geometry().width() h = self.geometry().height() geo = '%s %s %s %s' % (x, y, w, h) self._default_geometry = [x, y, w, h] if self.PREFS_: self._geometry_string = self.PREFS_.getpref( 'MacroTabDialog-geometry', geo, str, 'DIALOG_OPTIONS') else: self._geometry_string = 'default' # gotta call this since we instantiated this out of qtvcp's knowledge self.tab._hal_init() self.topParent = self.QTVCP_INSTANCE_ STATUS.connect('dialog-request', self._external_request) def _external_request(self, w, message): if message['NAME'] == self._request_name: self.load_dialog() # This method is called instead of MacroTab's closeChecked method # we do this so we can use it's buttons to hide our dialog # rather then close the MacroTab widget def _close(self): self.close() # This method is called instead of MacroTab's runChecked() method # we do this so we can use it's buttons to hide our dialog # rather then close the MacroTab widget def _run(self): self.tab.runMacro() self.close() def _setTitle(self, string): self.setWindowTitle(string) def load_dialog(self): STATUS.emit('focus-overlay-changed', True, 'Lathe Macro Dialog', self._color) self.tab.stack.setCurrentIndex(0) self.calculate_placement() self.show() self.exec_() STATUS.emit('focus-overlay-changed', False, None, None) record_geometry(self, 'MacroTabDialog-geometry') def calculate_placement(self): geometry_parsing(self, 'MacroTabDialog-geometry') # ********************** # Designer properties # ********************** @pyqtSlot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) state = pyqtProperty(bool, getState, setState, resetState) overlay_color = pyqtProperty(QColor, getColor, setColor)
class LcncDialog(QMessageBox, _HalWidgetBase): def __init__(self, parent=None): super(LcncDialog, self).__init__(parent) self.setTextFormat(Qt.RichText) self.setText('<b>Sample Text?</b>') self.setStandardButtons(QMessageBox.Yes | QMessageBox.No) self.setIcon(QMessageBox.Critical) self.setDetailedText('') self.OK_TYPE = 1 self.YN_TYPE = 0 self._state = False self._color = QColor(0, 0, 0, 150) self.focus_text = '' self._request_name = 'MESSAGE' self.hide() def _hal_init(self): x = self.geometry().x() y = self.geometry().y() w = self.geometry().width() h = self.geometry().height() geo = '%s %s %s %s' % (x, y, w, h) self._default_geometry = [x, y, w, h] if self.PREFS_: self._geometry_string = self.PREFS_.getpref( 'LcncDialog-geometry', geo, str, 'DIALOG_OPTIONS') else: self._geometry_string = 'default' self.topParent = self.QTVCP_INSTANCE_ STATUS.connect('dialog-request', self._external_request) # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): if message.get('NAME') == self._request_name: t = message.get('TITLE') if t: self.title = t else: self.title = 'Entry' mess = message.get('MESSAGE') or None more = message.get('MORE') or None details = message.get('DETAILS') or None ok_type = message.get('TYPE') if ok_type == None: ok_type = True icon = message.get('ICON') or 'INFO' pin = message.get('PINNAME') or None ftext = message.get('FOCUS_TEXT') or None fcolor = message.get('FOCUS_COLOR') or None alert = message.get('PLAY_ALERT') or None rtrn = self.showdialog(mess, more, details, ok_type, icon, pin, ftext, fcolor, alert) message['RETURN'] = rtrn STATUS.emit('general', message) def showdialog(self, message, more_info=None, details=None, display_type=1, icon=QMessageBox.Information, pinname=None, focus_text=None, focus_color=None, play_alert=None): self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) #Qt.X11BypassWindowManagerHint if focus_text is not None: self.focus_text = focus_text if focus_color is not None: color = focus_color else: color = self._color if icon == 'QUESTION': icon = QMessageBox.Question elif icon == 'INFO' or isinstance(icon, str): icon = QMessageBox.Information elif icon == 'WARNING': icon = QMessageBox.Warning elif icon == 'CRITICAL': icon = QMessageBox.Critical self.setIcon(icon) self.setText('<b>%s</b>' % message) if more_info is not None: self.setInformativeText(more_info) else: self.setInformativeText('') if details is not None: self.setDetailedText(details) else: self.setDetailedText('') if display_type == self.OK_TYPE: self.setStandardButtons(QMessageBox.Ok) else: self.setStandardButtons(QMessageBox.Yes | QMessageBox.No) self.buttonClicked.connect(self.msgbtn) STATUS.emit('focus-overlay-changed', True, self.focus_text, color) if play_alert: STATUS.emit('play-sound', play_alert) retval = self.exec_() STATUS.emit('focus-overlay-changed', False, None, None) LOG.debug("Value of pressed button: {}".format(retval)) if display_type == self.OK_TYPE: if retval == QMessageBox.No: return False else: return True else: return retval def showEvent(self, event): geom = self.frameGeometry() geom.moveCenter(QDesktopWidget().availableGeometry().center()) self.setGeometry(geom) super(LcncDialog, self).showEvent(event) def msgbtn(self, i): LOG.debug("Button pressed is: {}".format(i.text())) return # ********************** # Designer properties # ********************** @pyqtSlot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) overlay_color = pyqtProperty(QColor, getColor, setColor) state = pyqtProperty(bool, getState, setState, resetState)
class CalculatorDialog(Calculator, _HalWidgetBase): def __init__(self, parent=None): super(CalculatorDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self.play_sound = False self._request_name = 'CALCULATOR' self.title = 'Calculator Entry' self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) def _hal_init(self): x = self.geometry().x() y = self.geometry().y() w = self.geometry().width() h = self.geometry().height() geo = '%s %s %s %s' % (x, y, w, h) self._default_geometry = [x, y, w, h] if self.PREFS_: self._geometry_string = self.PREFS_.getpref( 'CalculatorDialog-geometry', geo, str, 'DIALOG_OPTIONS') else: self._geometry_string = 'default' if self.PREFS_: self.play_sound = self.PREFS_.getpref( 'CalculatorDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref( 'CalculatorDialog_sound_type', 'RING', str, 'DIALOG_OPTIONS') else: self.play_sound = False STATUS.connect('dialog-request', self._external_request) # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): if message.get('NAME') == self._request_name: t = message.get('TITLE') if t: self.title = t else: self.title = 'Entry' preload = message.get('PRELOAD') num = self.showdialog(preload) message['RETURN'] = num STATUS.emit('general', message) def showdialog(self, preload=None): STATUS.emit('focus-overlay-changed', True, 'Origin Setting', self._color) self.setWindowTitle(self.title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.calculate_placement() if preload is not None: self.display.setText(str(preload)) retval = self.exec_() STATUS.emit('focus-overlay-changed', False, None, None) record_geometry(self, 'EntryDialog-geometry') LOG.debug("Value of pressed button: {}".format(retval)) if retval: try: return float(self.display.text()) except: pass return None def calculate_placement(self): geometry_parsing(self, 'EntryDialog-geometry') def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) overlay_color = pyqtProperty(QColor, getColor, setColor)
class MDIHistory(QWidget, _HalWidgetBase): def __init__(self, parent=None): super(MDIHistory, self).__init__(parent) self.setMinimumSize(QSize(200, 150)) self.setWindowTitle("PyQt5 editor test example") lay = QVBoxLayout() lay.setContentsMargins(0, 0, 0, 0) self.setLayout(lay) self.list = QListView() self.list.setEditTriggers(QListView.NoEditTriggers) self.list.activated.connect(self.activated) self.list.setAlternatingRowColors(True) self.list.selectionChanged = self.selectionChanged self.model = QStandardItemModel(self.list) self.MDILine = MDILine() self.MDILine.soft_keyboard = False self.MDILine.line_up = self.line_up self.MDILine.line_down = self.line_down STATUS.connect('mdi-history-changed', self.reload) # add widgets lay.addWidget(self.list) lay.addWidget(self.MDILine) self.fp = os.path.expanduser(INFO.MDI_HISTORY_PATH) try: open(self.fp, 'r') except: open(self.fp, 'a+') LOG.debug('MDI History file created: {}'.format(self.fp)) self.reload() self.select_row('last') def _hal_init(self): STATUS.connect('state-off', lambda w: self.setEnabled(False)) STATUS.connect('state-estop', lambda w: self.setEnabled(False)) STATUS.connect( 'interp-idle', lambda w: self.setEnabled(STATUS.machine_is_on( ) and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED))) STATUS.connect('interp-run', lambda w: self.setEnabled(not STATUS.is_auto_mode())) STATUS.connect('all-homed', lambda w: self.setEnabled(STATUS.machine_is_on())) def reload(self, w=None): self.model.clear() try: with open(self.fp, 'r') as inputfile: for line in inputfile: line = line.rstrip('\n') item = QStandardItem(line) self.model.appendRow(item) self.list.setModel(self.model) self.list.scrollToBottom() if self.MDILine.hasFocus(): self.select_row('last') except: LOG.debug('File path is not valid: {}'.format(fp)) def selectionChanged(self, old, new): cmd = self.getSelected() self.MDILine.setText(cmd) selectionModel = self.list.selectionModel() if selectionModel.hasSelection(): self.row = selectionModel.currentIndex().row() def getSelected(self): selected_indexes = self.list.selectedIndexes() selected_rows = [item.row() for item in selected_indexes] # iterates each selected row in descending order for selected_row in sorted(selected_rows, reverse=True): text = self.model.item(selected_row).text() return text def activated(self): cmd = self.getSelected() self.MDILine.setText(cmd) self.MDILine.submit() self.select_row('down') def run_command(self): self.MDILine.submit() self.select_row('last') def select_row(self, style): style = style.lower() selectionModel = self.list.selectionModel() parent = QModelIndex() self.rows = self.model.rowCount(parent) - 1 if style == 'last': self.row = self.rows elif style == 'up': if self.row > 0: self.row -= 1 else: self.row = 0 elif style == 'down': if self.row < self.rows: self.row += 1 else: self.row = self.rows else: return top = self.model.index(self.row, 0, parent) bottom = self.model.index(self.row, 0, parent) selectionModel.setCurrentIndex( top, QItemSelectionModel.Select | QItemSelectionModel.Rows) selection = QItemSelection(top, top) selectionModel.clearSelection() selectionModel.select(selection, QItemSelectionModel.Select) def line_up(self): self.select_row('up') def line_down(self): self.select_row('down') ######################################################################### # This is how designer can interact with our widget properties. # designer will show the pyqtProperty properties in the editor # it will use the get set and reset calls to do those actions ######################################################################### def set_soft_keyboard(self, data): self.MDILine.soft_keyboard = data def get_soft_keyboard(self): return self.MDILine.soft_keyboard def reset_soft_keyboard(self): self.MDILine.soft_keyboard = False # designer will show these properties in this order: soft_keyboard_option = pyqtProperty(bool, get_soft_keyboard, set_soft_keyboard, reset_soft_keyboard)
class GcodeEditor(QWidget, _HalWidgetBase): percentDone = pyqtSignal(int) def __init__(self, parent=None): super(GcodeEditor, self).__init__(parent) self.load_dialog_code = 'LOAD' self.save_dialog_code = 'SAVE' STATUS.connect('general', self.returnFromDialog) self.isCaseSensitive = 0 self.setMinimumSize(QSize(300, 200)) self.setWindowTitle("PyQt5 editor test example") lay = QVBoxLayout() lay.setContentsMargins(0, 0, 0, 0) self.setLayout(lay) # make editor self.editor = GcodeDisplay(self) # class patch editor's function to ours # so we get the lines percent update self.editor.emit_percent = self.emit_percent self.editor.setReadOnly(True) self.editor.setModified(False) ################################ # add menubar actions ################################ # Create new action newAction = QAction(QIcon.fromTheme('document-new'), 'New', self) newAction.setShortcut('Ctrl+N') newAction.setStatusTip('New document') newAction.triggered.connect(self.newCall) # Create open action openAction = QAction(QIcon.fromTheme('document-open'), '&Open', self) openAction.setShortcut('Ctrl+O') openAction.setStatusTip('Open document') openAction.triggered.connect(self.openCall) # Create save action saveAction = QAction(QIcon.fromTheme('document-save'), '&save', self) saveAction.setShortcut('Ctrl+S') saveAction.setStatusTip('save document') saveAction.triggered.connect(self.saveCall) # Create exit action exitAction = QAction(QIcon.fromTheme('application-exit'), '&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit application') exitAction.triggered.connect(self.exitCall) # Create gcode lexer action gCodeLexerAction = QAction(QIcon.fromTheme('lexer.png'), '&Gcode\n lexer', self) gCodeLexerAction.setCheckable(1) gCodeLexerAction.setShortcut('Ctrl+G') gCodeLexerAction.setStatusTip('Set Gcode highlighting') gCodeLexerAction.triggered.connect(self.gcodeLexerCall) # Create gcode lexer action pythonLexerAction = QAction(QIcon.fromTheme('lexer.png'), '&python\n lexer', self) pythonLexerAction.setShortcut('Ctrl+P') pythonLexerAction.setStatusTip('Set Python highlighting') pythonLexerAction.triggered.connect(self.pythonLexerCall) # Create toolbar and add action toolBar = QToolBar('File') toolBar.addAction(newAction) toolBar.addAction(openAction) toolBar.addAction(saveAction) toolBar.addAction(exitAction) toolBar.addSeparator() # add lexer actions toolBar.addAction(gCodeLexerAction) toolBar.addAction(pythonLexerAction) toolBar.addSeparator() toolBar.addWidget( QLabel( '<html><head/><body><p><span style=" font-size:20pt; font-weight:600;">Edit Mode</span></p></body></html>' )) # create a frame for buttons box = QHBoxLayout() box.addWidget(toolBar) self.topMenu = QFrame() self.topMenu.setLayout(box) # add widgets lay.addWidget(self.topMenu) lay.addWidget(self.editor) lay.addWidget(self.createGroup()) self.readOnlyMode() def createGroup(self): self.bottomMenu = QFrame() self.searchText = QLineEdit(self) self.replaceText = QLineEdit(self) toolBar = QToolBar() # Create new action undoAction = QAction(QIcon.fromTheme('edit-undo'), 'Undo', self) undoAction.setStatusTip('Undo') undoAction.triggered.connect(self.undoCall) toolBar.addAction(undoAction) # create redo action redoAction = QAction(QIcon.fromTheme('edit-redo'), 'Redo', self) redoAction.setStatusTip('Undo') redoAction.triggered.connect(self.redoCall) toolBar.addAction(redoAction) toolBar.addSeparator() # create replace action replaceAction = QAction(QIcon.fromTheme('edit-find-replace'), 'Replace', self) replaceAction.triggered.connect(self.replaceCall) toolBar.addAction(replaceAction) # create find action findAction = QAction(QIcon.fromTheme('edit-find'), 'Find', self) findAction.triggered.connect(self.findCall) toolBar.addAction(findAction) # create next action nextAction = QAction(QIcon.fromTheme('go-previous'), 'Find Previous', self) nextAction.triggered.connect(self.nextCall) toolBar.addAction(nextAction) toolBar.addSeparator() # create case action caseAction = QAction(QIcon.fromTheme('edit-case'), 'Aa', self) caseAction.setCheckable(1) caseAction.triggered.connect(self.caseCall) toolBar.addAction(caseAction) box = QHBoxLayout() box.addWidget(toolBar) box.addWidget(self.searchText) box.addWidget(self.replaceText) box.addStretch(1) self.bottomMenu.setLayout(box) return self.bottomMenu # callback functions built for easy class patching ########## # want to refrain from renaming these functions as it will break # any class patch user's use # we split them like this so a user can intercept the callback # but still call the original functionality def caseCall(self): self.case() def case(self): self.isCaseSensitive -= 1 self.isCaseSensitive *= -1 print self.isCaseSensitive def exitCall(self): self.exit() def exit(self): if self.editor.isModified(): result = self.killCheck() if result: self.readOnlyMode() def findCall(self): self.find() def find(self): self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap=False, fwd=True) def gcodeLexerCall(self): self.gcodeLexer() def gcodeLexer(self): self.editor.set_gcode_lexer() def nextCall(self): self.next() def next(self): self.editor.search(str(self.searchText.text()), False) self.editor.search_Next() def newCall(self): self.new() def new(self): if self.editor.isModified(): result = self.killCheck() if result: self.editor.new_text() def openCall(self): self.open() def open(self): self.getFileName() def openReturn(self, f): ACTION.OPEN_PROGRAM(f) self.editor.setModified(False) def pythonLexerCall(self): self.pythonLexer() def pythonLexer(self): self.editor.set_python_lexer() def redoCall(self): self.redo() def redo(self): self.editor.redo() def replaceCall(self): self.replace() def replace(self): self.editor.replace_text(str(self.replaceText.text())) def saveCall(self): self.save() def save(self): self.getSaveFileName() def saveReturn(self, fname): ACTION.SAVE_PROGRAM(self.editor.text(), fname) self.editor.setModified(False) ACTION.OPEN_PROGRAM(fname) def undoCall(self): self.undo() def undo(self): self.editor.undo() # helper functions ############################################ def _hal_init(self): # name the top and bottom frames so it's easier to style self.bottomMenu.setObjectName('%sBottomButtonFrame' % self.objectName()) self.topMenu.setObjectName('%sTopButtonFrame' % self.objectName()) def editMode(self): self.topMenu.show() self.bottomMenu.show() self.editor.setReadOnly(False) def readOnlyMode(self): self.topMenu.hide() self.bottomMenu.hide() self.editor.setReadOnly(True) def getFileName(self): mess = { 'NAME': self.load_dialog_code, 'ID': '%s__' % self.objectName(), 'TITLE': 'Load Editor' } STATUS.emit('dialog-request', mess) def getSaveFileName(self): mess = { 'NAME': self.save_dialog_code, 'ID': '%s__' % self.objectName(), 'TITLE': 'Save Editor' } STATUS.emit('dialog-request', mess) # process the STATUS return message def returnFromDialog(self, w, message): if message.get('NAME') == self.load_dialog_code: path = message.get('RETURN') code = bool(message.get('ID') == '%s__' % self.objectName()) if path and code: self.openReturn(path) elif message.get('NAME') == self.save_dialog_code: path = message.get('RETURN') code = bool(message.get('ID') == '%s__' % self.objectName()) if path and code: self.saveReturn(path) def killCheck(self): choice = QMessageBox.question( self, 'Warning!!', "This file has changed since loading...Still want to proceed?", QMessageBox.Yes | QMessageBox.No) if choice == QMessageBox.Yes: return True else: return False def emit_percent(self, percent): self.percentDone.emit(percent) def select_lineup(self): self.editor.select_lineup(None) def select_linedown(self): self.editor.select_linedown(None) def select_line(self, line): self.editor.highlight_line(None, line) def jump_line(self, jump): self.editor.jump_line(jump) def get_line(self): return self.editor.getCursorPosition()[0] + 1 # designer recognized getter/setters # auto_show_mdi status # These adjust the self.editor instance def set_auto_show_mdi(self, data): self.editor.auto_show_mdi = data def get_auto_show_mdi(self): return self.editor.auto_show_mdi def reset_auto_show_mdi(self): self.editor.auto_show_mdi = True auto_show_mdi_status = pyqtProperty(bool, get_auto_show_mdi, set_auto_show_mdi, reset_auto_show_mdi) # designer recognized getter/setters # auto_show_manual status def set_auto_show_manual(self, data): self.editor.auto_show_manual = data def get_auto_show_manual(self): return self.editor.auto_show_manual def reset_auto_show_manual(self): self.editor.auto_show_manual = True auto_show_manual_status = pyqtProperty(bool, get_auto_show_manual, set_auto_show_manual, reset_auto_show_manual)
class GcodeDisplay(EditorBase, _HalWidgetBase): ARROW_MARKER_NUM = 8 def __init__(self, parent=None): super(GcodeDisplay, self).__init__(parent) # linuxcnc defaults self.idle_line_reset = False self._last_filename = None self.auto_show_mdi = True self.auto_show_manual = False self.auto_show_preference = True self.last_line = None def _hal_init(self): self.cursorPositionChanged.connect(self.line_changed) if self.auto_show_mdi: STATUS.connect('mode-mdi', self.load_mdi) STATUS.connect('mdi-history-changed', self.load_mdi) STATUS.connect('mode-auto', self.reload_last) STATUS.connect('move-text-lineup', self.select_lineup) STATUS.connect('move-text-linedown', self.select_linedown) if self.auto_show_manual: STATUS.connect('mode-manual', self.load_manual) STATUS.connect('machine-log-changed', self.load_manual) if self.auto_show_preference: STATUS.connect('show-preference', self.load_preference) STATUS.connect('file-loaded', self.load_program) STATUS.connect('line-changed', self.highlight_line) STATUS.connect('graphics-line-selected', self.highlight_line) if self.idle_line_reset: STATUS.connect('interp_idle', lambda w: self.set_line_number(None, 0)) def load_program(self, w, filename=None): if filename is None: filename = self._last_filename else: self._last_filename = filename self.load_text(filename) #self.zoomTo(6) self.setCursorPosition(0, 0) self.setModified(False) # when switching from MDI to AUTO we need to reload the # last (linuxcnc loaded) program. def reload_last(self, w): self.load_text(STATUS.old['file']) self.setCursorPosition(0, 0) # With the auto_show__mdi option, MDI history is shown def load_mdi(self, w): self.load_text(INFO.MDI_HISTORY_PATH) self._last_filename = INFO.MDI_HISTORY_PATH #print 'font point size', self.font().pointSize() #self.zoomTo(10) #print 'font point size', self.font().pointSize() self.setCursorPosition(self.lines(), 0) # With the auto_show__mdi option, MDI history is shown def load_manual(self, w): if STATUS.is_man_mode(): self.load_text(INFO.MACHINE_LOG_HISTORY_PATH) self.setCursorPosition(self.lines(), 0) def load_preference(self, w): self.load_text(self.PATHS_.PREFS_FILENAME) self.setCursorPosition(self.lines(), 0) def load_text(self, filename): if filename: try: fp = os.path.expanduser(filename) self.setText(open(fp).read()) self.last_line = None self.ensureCursorVisible() self.SendScintilla(QsciScintilla.SCI_VERTICALCENTRECARET) return except: LOG.error('File path is not valid: {}'.format(filename)) self.setText('') def highlight_line(self, w, line): if STATUS.is_auto_running(): if not STATUS.old['file'] == self._last_filename: LOG.debug('should reload the display') self.load_text(STATUS.old['file']) self._last_filename = STATUS.old['file'] self.emit_percent(line * 100 / self.lines()) self.markerAdd(line, self.ARROW_MARKER_NUM) if self.last_line: self.markerDelete(self.last_line, self.ARROW_MARKER_NUM) self.setCursorPosition(line, 0) self.ensureCursorVisible() self.SendScintilla(QsciScintilla.SCI_VERTICALCENTRECARET) self.last_line = line def emit_percent(self, percent): pass def set_line_number(self, line): STATUS.emit('gcode-line-selected', line) def line_changed(self, line, index): #LOG.debug('Line changed: {}'.format(line)) if STATUS.is_auto_running() is False: self.markerDeleteAll(-1) if STATUS.is_mdi_mode(): line_text = str(self.text(line)).strip() STATUS.emit('mdi-line-selected', line_text, self._last_filename) else: self.set_line_number(line) def select_lineup(self, w): line, col = self.getCursorPosition() LOG.debug(line) self.setCursorPosition(line - 1, 0) self.highlight_line(None, line - 1) def select_linedown(self, w): line, col = self.getCursorPosition() LOG.debug(line) self.setCursorPosition(line + 1, 0) self.highlight_line(None, line + 1) def jump_line(self, jump): line, col = self.getCursorPosition() line = line + jump LOG.debug(line) if line < 0: line = 0 elif line > self.lines(): line = self.lines() self.setCursorPosition(line, 0) self.highlight_line(None, line) # designer recognized getter/setters # auto_show_mdi status def set_auto_show_mdi(self, data): self.auto_show_mdi = data def get_auto_show_mdi(self): return self.auto_show_mdi def reset_auto_show_mdi(self): self.auto_show_mdi = True auto_show_mdi_status = pyqtProperty(bool, get_auto_show_mdi, set_auto_show_mdi, reset_auto_show_mdi) # designer recognized getter/setters # auto_show_manual status def set_auto_show_manual(self, data): self.auto_show_manual = data def get_auto_show_manual(self): return self.auto_show_manual def reset_auto_show_manual(self): self.auto_show_manual = True auto_show_manual_status = pyqtProperty(bool, get_auto_show_manual, set_auto_show_manual, reset_auto_show_manual)
class BatteryWidget(QWidget): chargeChanged = pyqtSignal(float) def __init__(self, parent=None): QWidget.__init__(self, parent) self._charge = 0 self.white = QColor(255, 255, 255) self.black = QColor(0, 0, 0) self.green = QColor(51, 255, 51) self.yellow = QColor(255, 255, 51) self.red = QColor(255, 0, 0) self.setMinimumWidth(110) self.setMinimumHeight(75) self.setMaximumWidth(110) self.setMaximumHeight(75) self.data = None # Defining sizes of battery self.ex_top_left_y = 25 #top left corner, Y position, exterior border (black) self.in_top_left_y = self.ex_top_left_y + 5 #top left corner, Y position, interior border (white, filler) self.ex_top_left_x = 6 #top left corner, X position, exterior border (black) self.in_top_left_x = self.ex_top_left_x + 5 #top left corner, X position, interior border (white, filler) self.exterior_width = self.width( ) - 15 #Width of the drawing, exterior border (black) self.interior_width = self.exterior_width - 10 #Width of the drawing, interior border (white) self.exterior_height = self.height( ) - 40 #Height of the drawing, exterior border (black) self.interior_height = self.exterior_height - 10 #Height of the drawing, interior border (white) self.total_width = self.width( ) - 20 - self.ex_top_left_x #Width of the battery bar drawing (green) def paintEvent(self, ev): """ paint the battery widget""" painter = QPainter() painter.begin(self) painter.setRenderHint(QPainter.Antialiasing) # draw positive terminal painter.setBrush(self.black) rect_p_terminal = QRect(self.width() - 7, self.height() / 2, self.width() / 30, self.height() / 8) painter.drawRect(rect_p_terminal) # draw battery painter.setBrush(self.black) rect = QRect(self.ex_top_left_x, self.ex_top_left_y, self.exterior_width, self.exterior_height) painter.drawRoundedRect(rect, 10.0, 10.0) # draw transparent background painter.setBrush(self.white) rect_transp = QRect(self.in_top_left_x, self.in_top_left_y, self.interior_width, self.interior_height) painter.drawRoundedRect(rect_transp.intersected(rect), 5.0, 5.0) # draw charge charge = self._charge # charge on pixels 0 to 100 if charge > 100: charge = 100 if charge < 0: charge = 0 rect_charge = QRect(self.in_top_left_x, self.in_top_left_y, self.total_width * charge / 100, self.interior_height) if charge > 45: color = self.green elif charge < 20: color = self.red else: color = self.yellow painter.setBrush(color) painter.drawRoundedRect(rect_charge, 5.0, 5.0) painter.setPen(QPen(QBrush(Qt.black), 1, Qt.SolidLine)) painter.drawText(self.width() / 2 - (self.width() / 8), self.height() / 2 + 10, str(int(charge)) + '%') painter.end() def charge(self): """ return charge value""" return self._charge def set_data(self, data): """ set 'data'""" self.data = data if data: self.setCharge(data) @pyqtSlot(float) def setCharge(self, charge): if charge != self._charge: self._charge = charge self.chargeChanged.emit(charge) self.update() charge = pyqtProperty(float, charge, setCharge)
class ContainerStack(QObject, ContainerInterface, PluginObject): """A stack of setting containers to handle setting value retrieval.""" Version = 4 # type: int def __init__(self, stack_id: str) -> None: """Constructor :param stack_id: A unique, machine readable/writable ID. """ super().__init__() QQmlEngine.setObjectOwnership(self, QQmlEngine.CppOwnership) self._metadata = { "id": stack_id, "name": stack_id, "version": self.Version, "container_type": ContainerStack } #type: Dict[str, Any] self._containers = [] # type: List[ContainerInterface] self._next_stack = None # type: Optional[ContainerStack] self._read_only = False # type: bool self._dirty = False # type: bool self._path = "" # type: str self._postponed_emits = [ ] # type: List[Tuple[Signal, ContainerInterface]] # gets filled with 2-tuples: signal, signal_argument(s) self._property_changes = {} # type: Dict[str, Set[str]] self._emit_property_changed_queued = False # type: bool def __getnewargs__(self) -> Tuple[str]: """For pickle support""" return (self.getId(), ) def __getstate__(self) -> Dict[str, Any]: """For pickle support""" return self.__dict__ def __setstate__(self, state: Dict[str, Any]) -> None: """For pickle support""" self.__dict__.update(state) def getId(self) -> str: """:copydoc ContainerInterface::getId Reimplemented from ContainerInterface """ return cast(str, self._metadata["id"]) id = pyqtProperty(str, fget=getId, constant=True) def getName(self) -> str: """:copydoc ContainerInterface::getName Reimplemented from ContainerInterface """ return str(self._metadata["name"]) def setName(self, name: str) -> None: """Set the name of this stack. :param name: The new name of the stack. """ if name != self.getName(): self._metadata["name"] = name self.nameChanged.emit() self.metaDataChanged.emit(self) nameChanged = pyqtSignal() """Emitted whenever the name of this stack changes.""" name = pyqtProperty(str, fget=getName, fset=setName, notify=nameChanged) def isReadOnly(self) -> bool: """:copydoc ContainerInterface::isReadOnly Reimplemented from ContainerInterface """ return self._read_only def setReadOnly(self, read_only: bool) -> None: if read_only != self._read_only: self._read_only = read_only self.readOnlyChanged.emit() readOnlyChanged = pyqtSignal() readOnly = pyqtProperty(bool, fget=isReadOnly, fset=setReadOnly, notify=readOnlyChanged) def getMetaData(self) -> Dict[str, Any]: """:copydoc ContainerInterface::getMetaData Reimplemented from ContainerInterface """ return self._metadata def setMetaData(self, meta_data: Dict[str, Any]) -> None: """Set the complete set of metadata""" if meta_data == self.getMetaData(): return #Unnecessary. #We'll fill a temporary dictionary with all the required metadata and overwrite it with the new metadata. #This way it is ensured that at least the required metadata is still there. self._metadata = { "id": self.getId(), "name": self.getName(), "version": self.getMetaData().get("version", 0), "container_type": ContainerStack } self._metadata.update(meta_data) self.metaDataChanged.emit(self) metaDataChanged = pyqtSignal(QObject) metaData = pyqtProperty("QVariantMap", fget=getMetaData, fset=setMetaData, notify=metaDataChanged) def getMetaDataEntry(self, entry: str, default: Any = None) -> Any: """:copydoc ContainerInterface::getMetaDataEntry Reimplemented from ContainerInterface """ value = self._metadata.get(entry, None) if value is None: for container in self._containers: value = container.getMetaDataEntry(entry, None) if value is not None: break if value is None: return default else: return value def setMetaDataEntry(self, key: str, value: Any) -> None: if key not in self._metadata or self._metadata[key] != value: self._metadata[key] = value self._dirty = True self.metaDataChanged.emit(self) def removeMetaDataEntry(self, key: str) -> None: if key in self._metadata: del self._metadata[key] self.metaDataChanged.emit(self) def isDirty(self) -> bool: return self._dirty def setDirty(self, dirty: bool) -> None: self._dirty = dirty containersChanged = Signal() def getProperty( self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any: """:copydoc ContainerInterface::getProperty Reimplemented from ContainerInterface. getProperty will start at the top of the stack and try to get the property specified. If that container returns no value, the next container on the stack will be tried and so on until the bottom of the stack is reached. If a next stack is defined for this stack it will then try to get the value from that stack. If no next stack is defined, None will be returned. Note that if the property value is a function, this method will return the result of evaluating that property with the current stack. If you need the actual function, use getRawProperty() """ value = self.getRawProperty(key, property_name, context=context) if isinstance(value, SettingFunction): if context is not None: context.pushContainer(self) value = value(self, context) if context is not None: context.popContainer() return value def getRawProperty( self, key: str, property_name: str, *, context: Optional[PropertyEvaluationContext] = None, use_next: bool = True, skip_until_container: Optional[ContainerInterface] = None) -> Any: """Retrieve a property of a setting by key and property name. This method does the same as getProperty() except it does not perform any special handling of the result, instead the raw stored value is returned. :param key: The key to get the property value of. :param property_name: The name of the property to get the value of. :param use_next: True if the value should be retrieved from the next stack if not found in this stack. False if not. :param skip_until_container: A container ID to skip to. If set, it will be as if all containers above the specified container are empty. If the container is not in the stack, it'll try to find it in the next stack. :return: The raw property value of the property, or None if not found. Note that the value might be a SettingFunction instance. """ containers = self._containers if context is not None: # if context is provided, check if there is any container that needs to be skipped. start_index = context.context.get("evaluate_from_container_index", 0) if start_index >= len(self._containers): return None containers = self._containers[start_index:] if property_name not in ["value", "state", "validationState"]: # Value, state & validationState can be changed by instanceContainer, the rest cant. Ask the definition # right away value = containers[-1].getProperty(key, property_name, context) if value is not None: return value else: for container in containers: if skip_until_container and container.getId( ) != skip_until_container: continue #Skip. skip_until_container = None #When we find the container, stop skipping. value = container.getProperty(key, property_name, context) if value is not None: return value if self._next_stack and use_next: return self._next_stack.getRawProperty( key, property_name, context=context, use_next=use_next, skip_until_container=skip_until_container) else: return None def hasProperty(self, key: str, property_name: str) -> bool: """:copydoc ContainerInterface::hasProperty Reimplemented from ContainerInterface. hasProperty will check if any of the containers in the stack has the specified property. If it does, it stops and returns True. If it gets to the end of the stack, it returns False. """ for container in self._containers: if container.hasProperty(key, property_name): return True if self._next_stack: return self._next_stack.hasProperty(key, property_name) return False # NOTE: we make propertyChanged and propertiesChanged as queued signals because otherwise, the emits in # _emitCollectedPropertyChanges() will be direct calls which modify the dict we are iterating over, and then # everything crashes. propertyChanged = Signal(Signal.Queued) propertiesChanged = Signal(Signal.Queued) def serialize(self, ignored_metadata_keys: Optional[Set[str]] = None) -> str: """:copydoc ContainerInterface::serialize Reimplemented from ContainerInterface TODO: Expand documentation here, include the fact that this should _not_ include all containers """ parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False) parser["general"] = {} parser["general"]["version"] = str(self._metadata["version"]) parser["general"]["name"] = str(self.getName()) parser["general"]["id"] = str(self.getId()) if ignored_metadata_keys is None: ignored_metadata_keys = set() ignored_metadata_keys |= {"id", "name", "version", "container_type"} parser["metadata"] = {} for key, value in self._metadata.items(): # only serialize the data that's not in the ignore list if key not in ignored_metadata_keys: parser["metadata"][key] = str(value) parser.add_section("containers") for index in range(len(self._containers)): parser["containers"][str(index)] = str( self._containers[index].getId()) stream = io.StringIO() parser.write(stream) return stream.getvalue() @classmethod def _readAndValidateSerialized( cls, serialized: str) -> configparser.ConfigParser: """Deserializes the given data and checks if the required fields are present. The profile upgrading code depends on information such as "configuration_type" and "version", which come from the serialized data. Due to legacy problem, those data may not be available if it comes from an ancient Cura. """ parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False) parser.read_string(serialized) if "general" not in parser or any(pn not in parser["general"] for pn in ("version", "name", "id")): raise InvalidContainerStackError( "Missing required section 'general' or 'version' property") return parser @classmethod def getConfigurationTypeFromSerialized(cls, serialized: str) -> Optional[str]: configuration_type = None try: parser = cls._readAndValidateSerialized(serialized) configuration_type = parser["metadata"]["type"] except InvalidContainerStackError as icse: raise icse except Exception as e: Logger.log("e", "Could not get configuration type: %s", e) return configuration_type @classmethod def getVersionFromSerialized(cls, serialized: str) -> Optional[int]: configuration_type = cls.getConfigurationTypeFromSerialized(serialized) if not configuration_type: Logger.log("d", "Could not get type from serialized.") return None # Get version version = None try: from UM.VersionUpgradeManager import VersionUpgradeManager version = VersionUpgradeManager.getInstance().getFileVersion( configuration_type, serialized) except Exception as e: Logger.log("d", "Could not get version from serialized: %s", e) return version def deserialize(self, serialized: str, file_name: Optional[str] = None) -> str: """:copydoc ContainerInterface::deserialize Reimplemented from ContainerInterface TODO: Expand documentation here, include the fact that this should _not_ include all containers """ # Update the serialized data first serialized = super().deserialize(serialized, file_name) parser = self._readAndValidateSerialized(serialized) if parser.getint("general", "version") != self.Version: raise IncorrectVersionError() # Clear all data before starting. for container in self._containers: container.propertyChanged.disconnect(self._collectPropertyChanges) self._containers = [] self._metadata = {} if "metadata" in parser: self._metadata = dict(parser["metadata"]) self._metadata["id"] = parser["general"]["id"] self._metadata["name"] = parser["general"].get("name", self.getId()) self._metadata[ "version"] = self.Version # Guaranteed to be equal to what's in the container. See above. self._metadata["container_type"] = ContainerStack if "containers" in parser: for index, container_id in parser.items("containers"): containers = _containerRegistry.findContainers(id=container_id) if containers: containers[0].propertyChanged.connect( self._collectPropertyChanges) self._containers.append(containers[0]) else: self._containers.append( _containerRegistry.getEmptyInstanceContainer()) ConfigurationErrorMessage.getInstance( ).addFaultyContainers(container_id, self.getId()) Logger.log( "e", "When trying to deserialize %s, we received an unknown container ID (%s)" % (self.getId(), container_id)) raise ContainerFormatError( "When trying to deserialize %s, we received an unknown container ID (%s)" % (self.getId(), container_id)) elif parser.has_option("general", "containers"): # Backward compatibility with 2.3.1: The containers used to be saved in a single comma-separated list. container_string = parser["general"].get("containers", "") Logger.log( "d", "While deserializing, we got the following container string: %s", container_string) container_id_list = container_string.split(",") for container_id in container_id_list: if container_id != "": containers = _containerRegistry.findContainers( id=container_id) if containers: containers[0].propertyChanged.connect( self._collectPropertyChanges) self._containers.append(containers[0]) else: self._containers.append( _containerRegistry.getEmptyInstanceContainer()) ConfigurationErrorMessage.getInstance( ).addFaultyContainers(container_id, self.getId()) Logger.log( "e", "When trying to deserialize %s, we received an unknown container ID (%s)" % (self.getId(), container_id)) raise ContainerFormatError( "When trying to deserialize %s, we received an unknown container ID (%s)" % (self.getId(), container_id)) ## TODO; Deserialize the containers. return serialized @classmethod def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]: """Gets the metadata of a container stack from a serialised format. This parses the entire CFG document and only extracts the metadata from it. :param serialized: A CFG document, serialised as a string. :param container_id: The ID of the container that we're getting the metadata of, as obtained from the file name. :return: A dictionary of metadata that was in the CFG document as a singleton list. If anything went wrong, this returns an empty list instead. """ serialized = cls._updateSerialized( serialized) # Update to most recent version. parser = configparser.ConfigParser(interpolation=None) parser.read_string(serialized) metadata = {"id": container_id, "container_type": ContainerStack} try: metadata["name"] = parser["general"]["name"] metadata["version"] = parser["general"]["version"] except KeyError as e: # One of the keys or the General section itself is missing. raise InvalidContainerStackError( "Missing required fields: {error_msg}".format( error_msg=str(e))) if "metadata" in parser: metadata.update(parser["metadata"]) return [metadata] def getAllKeys(self) -> Set[str]: """Get all keys known to this container stack. In combination with getProperty(), you can obtain the current property values of all settings. :return: A set of all setting keys in this container stack. """ keys = set() # type: Set[str] definition_containers = [ container for container in self.getContainers() if container.__class__ == DefinitionContainer ] #To get all keys, get all definitions from all definition containers. for definition_container in cast(List[DefinitionContainer], definition_containers): keys |= definition_container.getAllKeys() if self._next_stack: keys |= self._next_stack.getAllKeys() return keys def getContainers(self) -> List[ContainerInterface]: """Get a list of all containers in this stack. Note that it returns a shallow copy of the container list, as it's only allowed to change the order or entries in this list by the proper functions. :return: A list of all containers in this stack. """ return self._containers[:] def getContainerIndex(self, container: ContainerInterface) -> int: return self._containers.index(container) def getContainer(self, index: int) -> ContainerInterface: """Get a container by index. :param index: The index of the container to get. :return: The container at the specified index. :exception IndexError: Raised when the specified index is out of bounds. """ if index < 0: raise IndexError return self._containers[index] def getTop(self) -> Optional[ContainerInterface]: """Get the container at the top of the stack. This is a convenience method that will always return the top of the stack. :return: The container at the top of the stack, or None if no containers have been added. """ if self._containers: return self._containers[0] return None def getBottom(self) -> Optional[ContainerInterface]: """Get the container at the bottom of the stack. This is a convenience method that will always return the bottom of the stack. :return: The container at the bottom of the stack, or None if no containers have been added. """ if self._containers: return self._containers[-1] return None def getPath(self) -> str: """:copydoc ContainerInterface::getPath. Reimplemented from ContainerInterface """ return self._path def setPath(self, path: str) -> None: """:copydoc ContainerInterface::setPath Reimplemented from ContainerInterface """ self._path = path def getSettingDefinition(self, key: str) -> Optional[SettingDefinition]: """Get the SettingDefinition object for a specified key""" for container in self._containers: if not isinstance(container, DefinitionContainer): continue settings = container.findDefinitions(key=key) if settings: return settings[0] if self._next_stack: return self._next_stack.getSettingDefinition(key) else: return None @UM.FlameProfiler.profile def findContainer(self, criteria: Dict[str, Any] = None, container_type: type = None, **kwargs: Any) -> Optional[ContainerInterface]: """Find a container matching certain criteria. :param criteria: A dictionary containing key and value pairs that need to match the container. Note that the value of "*" can be used as a wild card. This will ensure that any container that has the specified key in the meta data is found. :param container_type: An optional type of container to filter on. :return: The first container that matches the filter criteria or None if not found. """ if not criteria and kwargs: criteria = kwargs elif criteria is None: criteria = {} for container in self._containers: meta_data = container.getMetaData() match = container.__class__ == container_type or container_type is None for key in criteria: if not match: break try: if meta_data[key] == criteria[key] or criteria[key] == "*": continue else: match = False break except KeyError: match = False break if match: return container return None def addContainer(self, container: ContainerInterface) -> None: """Add a container to the top of the stack. :param container: The container to add to the stack. """ self.insertContainer(0, container) def insertContainer(self, index: int, container: ContainerInterface) -> None: """Insert a container into the stack. :param index: The index of to insert the container at. A negative index counts from the bottom :param container: The container to add to the stack. """ if container is self: raise Exception("Unable to add stack to itself.") container.propertyChanged.connect(self._collectPropertyChanges) self._containers.insert(index, container) self.containersChanged.emit(container) self._dirty = True def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None: """Replace a container in the stack. :param index: :type{int} The index of the container to replace. :param container: The container to replace the existing entry with. :param postpone_emit: During stack manipulation you may want to emit later. :exception IndexError: Raised when the specified index is out of bounds. :exception Exception: when trying to replace container ContainerStack. """ if index < 0: raise IndexError if container is self: raise Exception( "Unable to replace container with ContainerStack (self) ") self._containers[index].propertyChanged.disconnect( self._collectPropertyChanges) container.propertyChanged.connect(self._collectPropertyChanges) self._containers[index] = container self._dirty = True if postpone_emit: # send it using sendPostponedEmits self._postponed_emits.append((self.containersChanged, container)) else: self.containersChanged.emit(container) def removeContainer(self, index: int = 0) -> None: """Remove a container from the stack. :param index: :type{int} The index of the container to remove. :exception IndexError: Raised when the specified index is out of bounds. """ if index < 0: raise IndexError try: container = self._containers[index] self._dirty = True container.propertyChanged.disconnect(self._collectPropertyChanges) del self._containers[index] self.containersChanged.emit(container) except TypeError: raise IndexError("Can't delete container with index %s" % index) def getNextStack(self) -> Optional["ContainerStack"]: """Get the next stack The next stack is the stack that is searched for a setting value if the bottom of the stack is reached when searching for a value. :return: :type{ContainerStack} The next stack or None if not set. """ return self._next_stack def setNextStack(self, stack: "ContainerStack", connect_signals: bool = True) -> None: """Set the next stack :param stack: :type{ContainerStack} The next stack to set. Can be None. Raises Exception when trying to set itself as next stack (to prevent infinite loops) :sa getNextStack """ if self is stack: raise Exception("Next stack can not be itself") if self._next_stack == stack: return if self._next_stack: self._next_stack.propertyChanged.disconnect( self._collectPropertyChanges) self.containersChanged.disconnect( self._next_stack.containersChanged) self._next_stack = stack if self._next_stack and connect_signals: self._next_stack.propertyChanged.connect( self._collectPropertyChanges) self.containersChanged.connect(self._next_stack.containersChanged) def sendPostponedEmits(self) -> None: """Send postponed emits These emits are collected from the option postpone_emit. Note: the option can be implemented for all functions modifying the stack. """ while self._postponed_emits: signal, signal_arg = self._postponed_emits.pop(0) signal.emit(signal_arg) @UM.FlameProfiler.profile def hasErrors(self) -> bool: """Check if the container stack has errors""" for key in self.getAllKeys(): enabled = self.getProperty(key, "enabled") if not enabled: continue validation_state = self.getProperty(key, "validationState") if validation_state is None: # Setting is not validated. This can happen if there is only a setting definition. # We do need to validate it, because a setting defintions value can be set by a function, which could # be an invalid setting. definition = cast(SettingDefinition, self.getSettingDefinition(key)) validator_type = SettingDefinition.getValidatorForType( definition.type) if validator_type: validator = validator_type(key) validation_state = validator(self) if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid): return True return False @UM.FlameProfiler.profile def getErrorKeys(self) -> List[str]: """Get all the keys that are in an error state in this stack""" error_keys = [] for key in self.getAllKeys(): validation_state = self.getProperty(key, "validationState") if validation_state is None: # Setting is not validated. This can happen if there is only a setting definition. # We do need to validate it, because a setting defintions value can be set by a function, which could # be an invalid setting. definition = cast(SettingDefinition, self.getSettingDefinition(key)) validator_type = SettingDefinition.getValidatorForType( definition.type) if validator_type: validator = validator_type(key) validation_state = validator(self) if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid): error_keys.append(key) return error_keys # protected: # Gather up all signal emissions and delay their emit until the next time the event # loop can run. This prevents us from sending the same change signal multiple times. # In addition, it allows us to emit a single signal that reports all properties that # have changed. def _collectPropertyChanges(self, key: str, property_name: str) -> None: if key not in self._property_changes: self._property_changes[key] = set() self._property_changes[key].add(property_name) if not self._emit_property_changed_queued: from UM.Application import Application Application.getInstance().callLater( self._emitCollectedPropertyChanges) self._emit_property_changed_queued = True # Perform the emission of the change signals that were collected in a previous step. def _emitCollectedPropertyChanges(self) -> None: for key, property_names in self._property_changes.items(): self.propertiesChanged.emit(key, property_names) for property_name in property_names: self.propertyChanged.emit(key, property_name) self._property_changes = {} self._emit_property_changed_queued = False def __str__(self) -> str: return "<{class_name} '{id}' containers={containers}>".format( class_name=type(self).__name__, id=self.getId(), containers=self._containers) def __repr__(self) -> str: return str(self)
class PyDateEdit(QDateEdit): # # Initialize base class # Force use of the calendar popup # Set default values for calendar properties # def __init__(self, *args): super(PyDateEdit, self).__init__(*args) self.setCalendarPopup(True) self.__cw = None self.__firstDayOfWeek = Qt.Monday self.__gridVisible = False self.__horizontalHeaderFormat = QCalendarWidget.ShortDayNames self.__verticalHeaderFormat = QCalendarWidget.ISOWeekNumbers self.__navigationBarVisible = True # # Call event handler of base class # Get the calendar widget, if not already done # Set the calendar properties # def mousePressEvent(self, event): super(PyDateEdit, self).mousePressEvent(event) if not self.__cw: self.__cw = self.findChild(QCalendarWidget) if self.__cw: self.__cw.setFirstDayOfWeek(self.__firstDayOfWeek) self.__cw.setGridVisible(self.__gridVisible) self.__cw.setHorizontalHeaderFormat( self.__horizontalHeaderFormat) self.__cw.setVerticalHeaderFormat(self.__verticalHeaderFormat) self.__cw.setNavigationBarVisible(self.__navigationBarVisible) # # Make sure, the calendarPopup property is invisible in Designer # def getCalendarPopup(self): return True calendarPopup = pyqtProperty(bool, fget=getCalendarPopup) # # Property firstDayOfWeek: Qt::DayOfWeek # Get: getFirstDayOfWeek() # Set: setFirstDayOfWeek() # Reset: resetFirstDayOfWeek() # def getFirstDayOfWeek(self): return self.__firstDayOfWeek def setFirstDayOfWeek(self, dayOfWeek): if dayOfWeek != self.__firstDayOfWeek: self.__firstDayOfWeek = dayOfWeek if self.__cw: self.__cw.setFirstDayOfWeek(dayOfWeek) def resetFirstDayOfWeek(self): if self.__firstDayOfWeek != Qt.Monday: self.__firstDayOfWeek = Qt.Monday if self.__cw: self.__cw.setFirstDayOfWeek(Qt.Monday) firstDayOfWeek = pyqtProperty( Qt.DayOfWeek, fget=getFirstDayOfWeek, fset=setFirstDayOfWeek, freset=resetFirstDayOfWeek, ) # # Property gridVisible: bool # Get: isGridVisible() # Set: setGridVisible() # Reset: resetGridVisible() # def isGridVisible(self): return self.__gridVisible def setGridVisible(self, show): if show != self.__gridVisible: self.__gridVisible = show if self.__cw: self.__cw.setGridVisible(show) def resetGridVisible(self): if self.__gridVisible != False: self.__gridVisible = False if self.__cw: self.__cw.setGridVisible(False) gridVisible = pyqtProperty(bool, fget=isGridVisible, fset=setGridVisible, freset=resetGridVisible) # # Property horizontalHeaderFormat: QCalendarWidget::HorizontalHeaderFormat # Get: getHorizontalHeaderFormat() # Set: setHorizontalHeaderFormat() # Reset: resetHorizontalHeaderFormat() # def getHorizontalHeaderFormat(self): return self.__horizontalHeaderFormat def setHorizontalHeaderFormat(self, format): if format != self.__horizontalHeaderFormat: self.__horizontalHeaderFormat = format if self.__cw: self.__cw.setHorizontalHeaderFormat(format) def resetHorizontalHeaderFormat(self): if self.__horizontalHeaderFormat != QCalendarWidget.ShortDayNames: self.__horizontalHeaderFormat = QCalendarWidget.ShortDayNames if self.__cw: self.__cw.setHorizontalHeaderFormat( QCalendarWidget.ShortDayNames) horizontalHeaderFormat = pyqtProperty( QCalendarWidget.HorizontalHeaderFormat, fget=getHorizontalHeaderFormat, fset=setHorizontalHeaderFormat, freset=resetHorizontalHeaderFormat, ) # # Property verticalHeaderFormat: QCalendarWidget::VerticalHeaderFormat # Get: getVerticalHeaderFormat() # Set: setVerticalHeaderFormat() # Reset: resetVerticalHeaderFormat() # def getVerticalHeaderFormat(self): return self.__verticalHeaderFormat def setVerticalHeaderFormat(self, format): if format != self.__verticalHeaderFormat: self.__verticalHeaderFormat = format if self.__cw: self.__cw.setVerticalHeaderFormat(format) def resetVerticalHeaderFormat(self): if self.__verticalHeaderFormat != QCalendarWidget.ISOWeekNumbers: self.__verticalHeaderFormat = QCalendarWidget.ISOWeekNumbers if self.__cw: self.__cw.setVerticalHeaderFormat( QCalendarWidget.ISOWeekNumbers) verticalHeaderFormat = pyqtProperty( QCalendarWidget.VerticalHeaderFormat, fget=getVerticalHeaderFormat, fset=setVerticalHeaderFormat, freset=resetVerticalHeaderFormat, ) # # Property navigationBarVisible: bool # Get: isNavigationBarVisible() # Set: setNavigationBarVisible() # Reset: resetNavigationBarVisible() # def isNavigationBarVisible(self): return self.__navigationBarVisible def setNavigationBarVisible(self, visible): if visible != self.__navigationBarVisible: self.__navigationBarVisible = visible if self.__cw: self.__cw.setNavigationBarVisible(visible) def resetNavigationBarVisible(self): if self.__navigationBarVisible != True: self.__navigationBarVisible = True if self.__cw: self.__cw.setNavigationBarVisible(True) navigationBarVisible = pyqtProperty( bool, fget=isNavigationBarVisible, fset=setNavigationBarVisible, freset=resetNavigationBarVisible, )
class MachineLog(QTextEdit, _HalWidgetBase): def __init__(self, parent=None): super(MachineLog, self).__init__(parent) self._delay = 0 self._hash_code = None self._machine_log = True self._integrator_log = False self.integratorPath = os.path.expanduser('~/qtvcp.log') self.machineLogPath = os.path.expanduser(INFO.MACHINE_LOG_HISTORY_PATH) def _hal_init(self): if self._machine_log: STATUS.connect('machine-log-changed',lambda w: self.loadLog()) elif self._integrator_log: STATUS.connect('periodic', self._periodicCheck) def _periodicCheck(self, w): if self._delay < 9: self._delay += 1 return if STATUS.is_status_valid() == False: return self._delay = 0 m1 = self.md5sum(self.integratorPath) if m1 and self._hash_code != m1: self._hash_code = m1 self.loadIntegratorLog() # create a hash code def md5sum(self,filename): try: f = open(filename, "rb") except: return None else: return hashlib.md5(f.read()).hexdigest() def loadLog(self): file = QFile(self.machineLogPath) file.open(QFile.ReadOnly) logText = file.readAll() try: # Python v2. logText = unicode(logText, encoding='utf8') except NameError: # Python v3. logText = str(logText, encoding='utf8') self.setPlainText(logText) def loadIntegratorLog(self): file = QFile(self.integratorPath) file.open(QFile.ReadOnly) logText = file.readAll() try: # Python v2. logText = unicode(logText, encoding='utf8') except NameError: # Python v3. logText = str(logText, encoding='utf8') self.setPlainText(logText) ################## properties ################### def _toggle_properties(self, picked): data = ('machine_log', 'integrator_log') for i in data: if not i == picked: self[i+'_option'] = False def set_machine_log(self, value): self._machine_log = value if value: self._toggle_properties('machine_log') def get_machine_log(self): return self._machine_log def reset_machine_log(self): self._machine_log = True machine_log_option = pyqtProperty(bool, get_machine_log, set_machine_log, reset_machine_log) def set_integrator_log(self, value): self._integrator_log = value if value: self._toggle_properties('integrator_log') def get_integrator_log(self): return self._integrator_log def reset_integrator_log(self): self._integrator_log = False integrator_log_option = pyqtProperty(bool, get_integrator_log, set_integrator_log, reset_integrator_log) ############################## # required class boiler code # ############################## def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value)
class NetworkMJPGImage(QQuickPaintedItem): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._stream_buffer = QByteArray() self._stream_buffer_start_index = -1 self._network_manager = None # type: QNetworkAccessManager self._image_request = None # type: QNetworkRequest self._image_reply = None # type: QNetworkReply self._image = QImage() self._image_rect = QRect() self._source_url = QUrl() self._started = False self._mirror = False self.setAntialiasing(True) ## Ensure that close gets called when object is destroyed def __del__(self) -> None: self.stop() def paint(self, painter: "QPainter") -> None: if self._mirror: painter.drawImage(self.contentsBoundingRect(), self._image.mirrored()) return painter.drawImage(self.contentsBoundingRect(), self._image) def setSourceURL(self, source_url: "QUrl") -> None: self._source_url = source_url self.sourceURLChanged.emit() if self._started: self.start() def getSourceURL(self) -> "QUrl": return self._source_url sourceURLChanged = pyqtSignal() source = pyqtProperty(QUrl, fget=getSourceURL, fset=setSourceURL, notify=sourceURLChanged) def setMirror(self, mirror: bool) -> None: if mirror == self._mirror: return self._mirror = mirror self.mirrorChanged.emit() self.update() def getMirror(self) -> bool: return self._mirror mirrorChanged = pyqtSignal() mirror = pyqtProperty(bool, fget=getMirror, fset=setMirror, notify=mirrorChanged) imageSizeChanged = pyqtSignal() @pyqtProperty(int, notify=imageSizeChanged) def imageWidth(self) -> int: return self._image.width() @pyqtProperty(int, notify=imageSizeChanged) def imageHeight(self) -> int: return self._image.height() @pyqtSlot() def start(self) -> None: self.stop() # Ensure that previous requests (if any) are stopped. if not self._source_url: Logger.log("w", "Unable to start camera stream without target!") return auth_data = "" if self._source_url.userInfo(): # move auth data to basic authorization header auth_data = base64.b64encode( self._source_url.userInfo().encode()).decode("utf-8") authority = self._source_url.authority() self._source_url.setAuthority(authority.rsplit("@", 1)[1]) self._image_request = QNetworkRequest(self._source_url) self._image_request.setAttribute( QNetworkRequest.FollowRedirectsAttribute, True) if auth_data: self._image_request.setRawHeader(b"Authorization", ("basic %s" % auth_data).encode()) if self._source_url.scheme().lower() == "https": # ignore SSL errors (eg for self-signed certificates) ssl_configuration = QSslConfiguration.defaultConfiguration() ssl_configuration.setPeerVerifyMode(QSslSocket.VerifyNone) self._image_request.setSslConfiguration(ssl_configuration) if self._network_manager is None: self._network_manager = QNetworkAccessManager() self._image_reply = self._network_manager.get(self._image_request) self._image_reply.downloadProgress.connect( self._onStreamDownloadProgress) self._started = True @pyqtSlot() def stop(self) -> None: self._stream_buffer = QByteArray() self._stream_buffer_start_index = -1 if self._image_reply: try: try: self._image_reply.downloadProgress.disconnect( self._onStreamDownloadProgress) except Exception: pass if not self._image_reply.isFinished(): self._image_reply.close() except Exception as e: # RuntimeError pass # It can happen that the wrapped c++ object is already deleted. self._image_reply = None self._image_request = None self._network_manager = None self._started = False def _onStreamDownloadProgress(self, bytes_received: int, bytes_total: int) -> None: # An MJPG stream is (for our purpose) a stream of concatenated JPG images. # JPG images start with the marker 0xFFD8, and end with 0xFFD9 if self._image_reply is None: return self._stream_buffer += self._image_reply.readAll() if len(self._stream_buffer ) > 2000000: # No single camera frame should be 2 Mb or larger Logger.log( "w", "MJPEG buffer exceeds reasonable size. Restarting stream...") self.stop() # resets stream buffer and start index self.start() return if self._stream_buffer_start_index == -1: self._stream_buffer_start_index = self._stream_buffer.indexOf( b'\xff\xd8') stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9') # If this happens to be more than a single frame, then so be it; the JPG decoder will # ignore the extra data. We do it like this in order not to get a buildup of frames if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1: jpg_data = self._stream_buffer[ self._stream_buffer_start_index:stream_buffer_end_index + 2] self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:] self._stream_buffer_start_index = -1 self._image.loadFromData(jpg_data) if self._image.rect() != self._image_rect: self.imageSizeChanged.emit() self.update()
class EntryDialog(QDialog, _HalWidgetBase): def __init__(self, parent=None): super(EntryDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self.play_sound = False self._request_name = 'ENTRY' self.title = 'Numerical Entry' self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) l = QVBoxLayout() self.setLayout(l) o = TouchInputWidget() l.addWidget(o) self.Num = QLineEdit() # actiate touch input self.Num.keyboard_type = 'numeric' self.Num.returnPressed.connect(lambda: self.accept()) gl = QVBoxLayout() gl.addWidget(self.Num) self.bBox = QDialogButtonBox() self.bBox.addButton('Apply', QDialogButtonBox.AcceptRole) self.bBox.addButton('Cancel', QDialogButtonBox.RejectRole) self.bBox.rejected.connect(self.reject) self.bBox.accepted.connect(self.accept) gl.addWidget(self.bBox) o.setLayout(gl) def _hal_init(self): x = self.geometry().x() y = self.geometry().y() w = self.geometry().width() h = self.geometry().height() geo = '%s %s %s %s' % (x, y, w, h) self._default_geometry = [x, y, w, h] if self.PREFS_: self._geometry_string = self.PREFS_.getpref( 'EntryDialog-geometry', geo, str, 'DIALOG_OPTIONS') else: self._geometry_string = 'default' if self.PREFS_: self.play_sound = self.PREFS_.getpref('toolDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('toolDialog_sound_type', 'RING', str, 'DIALOG_OPTIONS') else: self.play_sound = False STATUS.connect('dialog-request', self._external_request) # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): if message.get('NAME') == self._request_name: t = message.get('TITLE') if t: self.title = t else: self.title = 'Entry' num = self.showdialog() message['RETURN'] = num STATUS.emit('general', message) def showdialog(self): STATUS.emit('focus-overlay-changed', True, 'Origin Setting', self._color) self.setWindowTitle(self.title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.calculate_placement() retval = self.exec_() STATUS.emit('focus-overlay-changed', False, None, None) record_geometry(self, 'EntryDialog-geometry') LOG.debug("Value of pressed button: {}".format(retval)) if retval: try: return float(self.Num.text()) except Exception as e: print e return None def calculate_placement(self): geometry_parsing(self, 'EntryDialog-geometry') def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) overlay_color = pyqtProperty(QColor, getColor, setColor)
class surfaceCheck(FoamApp): """ surfaceCheck - Utility ====================== Checks geometric and topological quality of a surface. (src: http://www.openfoam.org/docs/user/standard-utilities.php) """ app_name = "surfaceCheck" input_types = ["stl_files"] output_types = ["stl_files"] def __init__(self, parent, instance_name, status): FoamApp.__init__(self, parent, instance_name, status) # Input/Output objects # ==================== self.__stl_input_dict = {} self.__input_files = [] # TreeView model for GUI # ====================== self.__tree_view_model = [] # Visualization objects # ===================== self.__input_vis = [] # the visualization of the input file def load(self): self.update_tree_view() def run(self): for app in self.__stl_input_dict: for stl_file in self.__stl_input_dict[app]: if self.foam_exec(["surfaceCheck", stl_file._filename]) != 0: return False return True def stl_files_in(self, input_dict): self.__input_files = [] self.__stl_input_dict = input_dict for files in input_dict.values(): self.__input_files.extend(files) self.update_tree_view() self.stl_files_out_changed.emit() for iv in self.__input_vis: self.remove_vis_object(iv) self.__input_vis = [] for input_file in self.__input_files: input_vis = STLLoader(input_file) self.__input_vis.append(input_vis) self.add_vis_object(input_vis) ''' TreeView for app ================ ''' tree_view_model_changed = pyqtSignal(name="treeViewModelChanged") @property def tree_view_model(self): return self.__tree_view_model @tree_view_model.setter def tree_view_model(self, tree_view_model): if self.__tree_view_model != tree_view_model: self.__tree_view_model = tree_view_model self.tree_view_model_changed.emit() treeViewModel = pyqtProperty("QVariantList", fget=tree_view_model.fget, fset=tree_view_model.fset, notify=tree_view_model_changed) def __create_tree_view_model(self): stl_model = [{ 'text': stl.filename(), 'isRegion': False, 'isRefinementObject': False, 'type': 'stl_file', 'elements': [{ 'text': region['name'], 'isRegion': True, 'isRefinementObject': False, 'type': 'stl_region' } for region in stl.patchInfo()] } for stl in self.__input_files] return stl_model def update_tree_view(self): self.tree_view_model = self.__create_tree_view_model() ''' Output for other Apps ===================== ''' def stl_files_out(self): return copy.deepcopy(self.__input_files) stl_files_out_changed = pyqtSignal()
class FileDialog(QFileDialog, _HalWidgetBase): def __init__(self, parent=None): super(FileDialog, self).__init__(parent) self._state = False self._load_request_name = 'LOAD' self._save_request_name = 'SAVE' self._color = QColor(0, 0, 0, 150) options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.setOptions(options) self.setWindowModality(Qt.ApplicationModal) exts = INFO.get_qt_filter_extensions() self.setNameFilter(exts) self.default_path = (os.path.join(os.path.expanduser('~'), 'linuxcnc/nc_files/examples')) def _hal_init(self): x = self.geometry().x() y = self.geometry().y() w = self.geometry().width() h = self.geometry().height() geo = '%s %s %s %s' % (x, y, w, h) self._default_geometry = [x, y, w, h] if self.PREFS_: self._geometry_string = self.PREFS_.getpref( 'FileDialog-geometry', geo, str, 'DIALOG_OPTIONS') else: self._geometry_string = 'default' STATUS.connect('dialog-request', self._external_request) if self.PREFS_: self.play_sound = self.PREFS_.getpref('fileDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('fileDialog_sound_type', 'RING', str, 'DIALOG_OPTIONS') last_path = self.PREFS_.getpref('last_file_path', self.default_path, str, 'BOOK_KEEPING') self.setDirectory(last_path) else: self.play_sound = False def _external_request(self, w, message): ext = message.get('EXTENTIONS') pre = message.get('FILENAME') dir = message.get('DIRECTORY') if message.get('NAME') == self._load_request_name: # if there is an ID then a file name response is expected if message.get('ID'): print message.get('ID') message['RETURN'] = self.load_dialog(ext, pre, dir, True) STATUS.emit('general', message) else: self.load_dialog(extentions=ext) elif message.get('NAME') == self._save_request_name: if message.get('ID'): message['RETURN'] = self.save_dialog(ext, pre, dir) STATUS.emit('general', message) def load_dialog(self, extentions=None, preselect=None, directory=None, return_path=False): self.setFileMode(QFileDialog.ExistingFile) self.setAcceptMode(QFileDialog.AcceptOpen) if extentions: self.setNameFilter(extentions) if preselect: self.selectFile(preselect) else: self.selectFile('') if directory: self.setDirectory(directory) self.setWindowTitle('Open') STATUS.emit('focus-overlay-changed', True, 'Open Gcode', self._color) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.calculate_placement() fname = None if (self.exec_()): fname = self.selectedFiles()[0] path = self.directory().absolutePath() self.setDirectory(path) STATUS.emit('focus-overlay-changed', False, None, None) record_geometry(self, 'FileDialog-geometry') if fname and not return_path: if self.PREFS_: self.PREFS_.putpref('last_file_path', path, str, 'BOOK_KEEPING') ACTION.OPEN_PROGRAM(fname) STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME') return fname def save_dialog(self, extentions=None, preselect=None, directory=None): self.setFileMode(QFileDialog.AnyFile) self.setAcceptMode(QFileDialog.AcceptSave) if extentions: self.setNameFilter(extensions) if preselect: self.selectFile(preselect) else: self.selectFile('') if directory: self.setDirectory(directory) self.setWindowTitle('Save') STATUS.emit('focus-overlay-changed', True, 'Save Gcode', self._color) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.calculate_placement() fname = None if (self.exec_()): fname = self.selectedFiles()[0] path = self.directory().absolutePath() self.setDirectory(path) else: fname = None STATUS.emit('focus-overlay-changed', False, None, None) record_geometry(self, 'FileDialog-geometry') if fname: if self.PREFS_: self.PREFS_.putpref('last_file_path', path, str, 'BOOK_KEEPING') return fname def calculate_placement(self): geometry_parsing(self, 'FileDialog-geometry') #********************** # Designer properties #********************** @pyqtSlot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) state = pyqtProperty(bool, getState, setState, resetState) overlay_color = pyqtProperty(QColor, getColor, setColor)
class CPUInfo(QObject): def __init__(self): QObject.__init__(self) self.ctemp, self.gtemp, self.time = [], [], [] self.cpuAvg, self.gpuAvg = 0.0, 0.0 self.carm, self.ccore, self.ch264 = 0, 0, 0 self.cuart, self.cpwm, self.chdmi = 0, 0, 0 self.vcore, self.sdram_c, self.sdram_i, self.sdram_p = 0.0, 0.0, 0.0, 0.0 self.coh264, self.compg2, self.compg4 = False, False, False self.cowvc1, self.comjpg = False, False self.mcpu, self.mgpu = 0, 0 cpuSig = pyqtSignal(list) gpuSig = pyqtSignal(list) timeSig = pyqtSignal(list) cpuAvgSig = pyqtSignal(float) gpuAvgSig = pyqtSignal(float) #************ 1 **************# carmSig = pyqtSignal(int) ccoreSig = pyqtSignal(int) ch264Sig = pyqtSignal(int) cuartSig = pyqtSignal(int) cpwmSig = pyqtSignal(int) chdmiSig = pyqtSignal(int) #************ 2 ****************# vcoreSig = pyqtSignal(float) sdram_cSig = pyqtSignal(float) sdram_iSig = pyqtSignal(float) sdram_pSig = pyqtSignal(float) #*********** 3 ******************# coh264Sig = pyqtSignal(bool) compg2Sig = pyqtSignal(bool) compg4Sig = pyqtSignal(bool) cowvc1Sig = pyqtSignal(bool) comjpgSig = pyqtSignal(bool) #********** 4 *******************# mcpuSig = pyqtSignal(int) mgpuSig = pyqtSignal(int) def setCputemp(self, temp): if self.ctemp != temp: self.ctemp = temp self.cpuSig.emit(self.ctemp) def getCputemp(self): return self.ctemp def setGputemp(self, temp): if self.gtemp != temp: self.gtemp = temp self.gpuSig.emit(self.gtemp) def getGputemp(self): return self.gtemp def setTime(self, t): if self.time != t: self.time = t self.timeSig.emit(self.time) def getTime(self): return self.time def setCpuAvg(self, temp): if self.cpuAvg != temp: self.cpuAvg = temp self.cpuAvgSig.emit(self.cpuAvg) def getCpuAvg(self): return self.cpuAvg def setGpuAvg(self, temp): if self.gpuAvg != temp: self.gpuAvg = temp self.gpuAvgSig.emit(self.gpuAvg) def getGpuAvg(self): return self.gpuAvg def setCarm(self, arm): if self.carm != arm: self.carm = arm self.carmSig.emit(self.carm) def getCarm(self): return self.carm def setCcore(self, core): if self.ccore != core: self.ccore = core self.ccoreSig.emit(self.ccore) def getCcore(self): return self.ccore def setCh264(self, h264): if self.ch264 != h264: self.ch264 = h264 self.ch264Sig.emit(self.ch264) def getCh264(self): return self.ch264 def setCuart(self, uart): if self.cuart != uart: self.cuart = uart self.cuartSig.emit(self.cuart) def getCuart(self): return self.cuart def setCpwm(self, pwm): if self.cpwm != pwm: self.cpwm = pwm self.cpwmSig.emit(self.cpwm) def getCpwm(self): return self.cpwm def setChdmi(self, hdmi): if self.chdmi != hdmi: self.chdmi = hdmi self.chdmiSig.emit(self.chdmi) def getChdmi(self): return self.chdmi def setVcore(self, core): if self.vcore != core: self.vcore = core self.vcoreSig.emit(self.vcore) def getVcore(self): return self.vcore def setSdram_c(self, ram): if self.sdram_c != ram: self.sdram_c = ram self.sdram_cSig.emit(self.sdram_c) def getSdram_c(self): return self.sdram_c def setSdram_i(self, ram): if self.sdram_i != ram: self.sdram_i = ram self.sdram_iSig.emit(self.sdram_i) def getSdram_i(self): return self.sdram_i def setSdram_p(self, ram): if self.sdram_p != ram: self.sdram_p = ram self.sdram_pSig.emit(self.sdram_p) def getSdram_p(self): return self.sdram_p def setCoh264(self, h): if self.coh264 != h: self.coh264 = h self.coh264Sig.emit(self.coh264) def getCoh264(self): return self.coh264 def setCompg2(self, h): if self.compg2 != h: self.compg2 = h self.compg2Sig.emit(self.compg2) def getCompg2(self): return self.compg2 def setCompg4(self, h): if self.compg4 != h: self.compg4 = h self.compg4Sig.emit(self.compg4) def getCompg4(self): return self.compg4 def setCowvc1(self, h): if self.cowvc1 != h: self.cowvc1 = h self.cowvc1Sig.emit(self.cowvc1) def getCowvc1(self): return self.cowvc1 def setComjpg(self, h): if self.comjpg != h: self.comjpg = h self.comjpgSig.emit(self.comjpg) def getComjpg(self): return self.comjpg def setMcpu(self, u): if self.mcpu != u: self.mcpu = u self.mcpuSig.emit(self.mcpu) def getMcpu(self): return self.mcpu def setMgpu(self, u): if self.mgpu != u: self.mgpu = u self.mgpuSig.emit(self.mgpu) def getMgpu(self): return self.mgpu @pyqtSlot() def sig(self): try: data = pd.read_csv("data.csv") except: pass data = pd.read_csv("data.csv") time = np.arange(len(data['cpu'].tolist())) cavg = format(np.mean(data['cpu'].to_numpy()), '.1f') gavg = format(np.mean(data['gpu'].to_numpy()), '.1f') self.setCputemp(data['cpu'].tolist()) self.setGputemp(data['gpu'].tolist()) self.setTime(time.tolist()) self.setCpuAvg(float(cavg)) self.setGpuAvg(float(gavg)) #*********************************# self.setCarm(data['carm'].to_numpy()[-1]) self.setCcore(data['ccore'].to_numpy()[-1]) self.setCh264(data['ch264'].to_numpy()[-1]) self.setCuart(data['cuart'].to_numpy()[-1]) self.setCpwm(data['cpwm'].to_numpy()[-1]) self.setChdmi(data['chdmi'].to_numpy()[-1]) vc = float(format(data['vcore'].to_numpy()[-1],'.3f')) vs = float(format(data['sdram_c'].to_numpy()[-1],'.3f')) vi = float(format(data['sdram_i'].to_numpy()[-1],'.3f')) vp = float(format(data['sdram_p'].to_numpy()[-1],'.3f')) self.setVcore(vc) self.setSdram_c(vs) self.setSdram_i(vi) self.setSdram_p(vp) self.setCoh264(data['coh264'].tolist()[-1]) self.setCompg2(data['compg2'].tolist()[-1]) self.setCompg4(data['compg4'].tolist()[-1]) self.setCowvc1(data['cowvc1'].tolist()[-1]) self.setComjpg(data['comjpg'].tolist()[-1]) self.setMcpu(data['mcpu'].to_numpy()[-1]) self.setMgpu(data['mgpu'].to_numpy()[-1]) sig_cpu = pyqtProperty(list, getCputemp, notify=cpuSig) sig_gpu = pyqtProperty(list, getGputemp, notify=gpuSig) sig_time = pyqtProperty(list, getTime, notify=timeSig) sig_cavg = pyqtProperty(float, getCpuAvg, notify=cpuAvgSig) sig_gavg = pyqtProperty(float, getGpuAvg, notify=gpuAvgSig) sig_carm = pyqtProperty(int, getCarm, notify=carmSig) sig_ccore = pyqtProperty(int, getCcore, notify=ccoreSig) sig_ch264 = pyqtProperty(int, getCh264, notify=ch264Sig) sig_cuart = pyqtProperty(int, getCuart, notify=cuartSig) sig_cpwm = pyqtProperty(int, getCpwm, notify=cpwmSig) sig_chdmi = pyqtProperty(int, getChdmi, notify=chdmiSig) sig_vcore = pyqtProperty(float, getVcore, notify=vcoreSig) sig_sdram_c = pyqtProperty(float, getSdram_c, notify=sdram_cSig) sig_sdram_i = pyqtProperty(float, getSdram_i, notify=sdram_iSig) sig_sdram_p = pyqtProperty(float, getSdram_p, notify=sdram_pSig) sig_coh264 = pyqtProperty(bool, getCoh264, notify=coh264Sig) sig_compg2 = pyqtProperty(bool, getCompg2, notify=compg2Sig) sig_compg4 = pyqtProperty(bool, getCompg4, notify=compg4Sig) sig_comjpg = pyqtProperty(bool, getComjpg, notify=comjpgSig) sig_cowvc1 = pyqtProperty(bool, getCowvc1, notify=cowvc1Sig) sig_mcpu = pyqtProperty(int, getMcpu, notify=mcpuSig) sig_mgpu = pyqtProperty(int, getMgpu, notify=mgpuSig) """
class ToolOffsetDialog(QDialog, _HalWidgetBase): def __init__(self, parent=None): super(ToolOffsetDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'TOOLOFFSET' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(200, 200) buttonBox = QDialogButtonBox() buttonBox.setEnabled(False) STATUS.connect('not-all-homed', lambda w, axis: buttonBox.setEnabled(False)) STATUS.connect('all-homed', lambda w: buttonBox.setEnabled(True)) STATUS.connect('state-estop', lambda w: buttonBox.setEnabled(False)) STATUS.connect( 'state-estop-reset', lambda w: buttonBox.setEnabled( STATUS.machine_is_on() and STATUS.is_all_homed())) for i in ('X', 'Y', 'Z'): b = 'button_%s' % i self[b] = QPushButton('Zero %s' % i) self[b].clicked.connect(self.zeroPress('%s' % i)) buttonBox.addButton(self[b], 3) v = QVBoxLayout() h = QHBoxLayout() self._o = TOOLVIEW_WIDGET() self._o._hal_init() self.setLayout(v) v.addWidget(self._o) b = QPushButton('OK') b.clicked.connect(lambda: self.close()) h.addWidget(b) h.addWidget(buttonBox) v.addLayout(h) self.setModal(True) def _hal_init(self): x = self.geometry().x() y = self.geometry().y() w = self.geometry().width() h = self.geometry().height() geo = '%s %s %s %s' % (x, y, w, h) self._default_geometry = [x, y, w, h] if self.PREFS_: self._geometry_string = self.PREFS_.getpref( 'ToolOffsetDialog-geometry', geo, str, 'DIALOG_OPTIONS') else: self._geometry_string = 'default' self.topParent = self.QTVCP_INSTANCE_ STATUS.connect('dialog-request', self._external_request) def _external_request(self, w, message): if message['NAME'] == self._request_name: self.load_dialog() # This weird code is just so we can get the axis # letter # using clicked.connect() apparently can't easily # add user data def zeroPress(self, data): def calluser(): self.zeroAxis(data) return calluser def zeroAxis(self, index): ACTION.SET_AXIS_ORIGIN(index, 0) def load_dialog(self): STATUS.emit('focus-overlay-changed', True, 'Set Origin Offsets', self._color) self.calculate_placement() self.show() self.exec_() STATUS.emit('focus-overlay-changed', False, None, None) record_geometry(self, 'ToolOffsetDialog-geometry') def calculate_placement(self): geometry_parsing(self, 'ToolOffsetDialog-geometry') # usual boiler code # (used so we can use code such as self[SomeDataName] def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value) # ********************** # Designer properties # ********************** @pyqtSlot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) state = pyqtProperty(bool, getState, setState, resetState) overlay_color = pyqtProperty(QColor, getColor, setColor)
class MainWindow(QQuickWindow): def __init__(self, parent = None): super(MainWindow, self).__init__(parent) self._background_color = QColor(204, 204, 204, 255) self.setClearBeforeRendering(False) self.beforeRendering.connect(self._render, type=Qt.DirectConnection) self._mouse_device = QtMouseDevice(self) self._mouse_device.setPluginId("qt_mouse") self._key_device = QtKeyDevice() self._key_device.setPluginId("qt_key") self._previous_focus = None self._app = QCoreApplication.instance() self._app.getController().addInputDevice(self._mouse_device) self._app.getController().addInputDevice(self._key_device) self._app.getController().getScene().sceneChanged.connect(self._onSceneChanged) self._preferences = Preferences.getInstance() self._preferences.addPreference("general/window_width", 1280) self._preferences.addPreference("general/window_height", 720) self._preferences.addPreference("general/window_left", 50) self._preferences.addPreference("general/window_top", 50) self._preferences.addPreference("general/window_state", Qt.WindowNoState) # Restore window geometry self.setWidth(int(self._preferences.getValue("general/window_width"))) self.setHeight(int(self._preferences.getValue("general/window_height"))) self.setPosition(int(self._preferences.getValue("general/window_left")), int(self._preferences.getValue("general/window_top"))) # Make sure restored geometry is not outside the currently available screens screen_found = False for s in range(0, self._app.desktop().screenCount()): if self.geometry().intersects(self._app.desktop().availableGeometry(s)): screen_found = True break if not screen_found: self.setPosition(50,50) self.setWindowState(int(self._preferences.getValue("general/window_state"))) self._mouse_x = 0 self._mouse_y = 0 self._viewport_rect = QRectF(0, 0, 1.0, 1.0) Application.getInstance().setMainWindow(self) self._fullscreen = False @pyqtSlot() def toggleFullscreen(self): if self._fullscreen: self.setVisibility(QQuickWindow.Windowed) # Switch back to windowed else: self.setVisibility(QQuickWindow.FullScreen) # Go to fullscreen self._fullscreen = not self._fullscreen def getBackgroundColor(self): return self._background_color def setBackgroundColor(self, color): self._background_color = color self._app.getRenderer().setBackgroundColor(color) backgroundColor = pyqtProperty(QColor, fget=getBackgroundColor, fset=setBackgroundColor) mousePositionChanged = pyqtSignal() @pyqtProperty(int, notify = mousePositionChanged) def mouseX(self): return self._mouse_x @pyqtProperty(int, notify = mousePositionChanged) def mouseY(self): return self._mouse_y def setViewportRect(self, rect): if rect != self._viewport_rect: self._viewport_rect = rect self._updateViewportGeometry(self.width() * self.devicePixelRatio(), self.height() * self.devicePixelRatio()) self.viewportRectChanged.emit() viewportRectChanged = pyqtSignal() @pyqtProperty(QRectF, fset = setViewportRect, notify = viewportRectChanged) def viewportRect(self): return self._viewport_rect # Warning! Never reimplemented this as a QExposeEvent can cause a deadlock with QSGThreadedRender due to both trying # to claim the Python GIL. # def event(self, event): def mousePressEvent(self, event): super().mousePressEvent(event) if event.isAccepted(): return if self.activeFocusItem() != None and self.activeFocusItem() != self._previous_focus: self.activeFocusItem().setFocus(False) self._previous_focus = self.activeFocusItem() self._mouse_device.handleEvent(event) def mouseMoveEvent(self, event): self._mouse_x = event.x() self._mouse_y = event.y() self.mousePositionChanged.emit() super().mouseMoveEvent(event) if event.isAccepted(): return self._mouse_device.handleEvent(event) def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) if event.isAccepted(): return self._mouse_device.handleEvent(event) def keyPressEvent(self, event): super().keyPressEvent(event) if event.isAccepted(): return self._key_device.handleEvent(event) def keyReleaseEvent(self, event): super().keyReleaseEvent(event) if event.isAccepted(): return self._key_device.handleEvent(event) def wheelEvent(self, event): super().wheelEvent(event) if event.isAccepted(): return self._mouse_device.handleEvent(event) def moveEvent(self, event): QMetaObject.invokeMethod(self, "_onWindowGeometryChanged", Qt.QueuedConnection); def resizeEvent(self, event): super().resizeEvent(event) win_w = event.size().width() * self.devicePixelRatio() win_h = event.size().height() * self.devicePixelRatio() self._updateViewportGeometry(win_w, win_h) QMetaObject.invokeMethod(self, "_onWindowGeometryChanged", Qt.QueuedConnection); def hideEvent(self, event): Application.getInstance().windowClosed() def _render(self): renderer = self._app.getRenderer() view = self._app.getController().getActiveView() renderer.beginRendering() view.beginRendering() renderer.render() view.endRendering() renderer.endRendering() def _onSceneChanged(self, object): self.update() @pyqtSlot() def _onWindowGeometryChanged(self): if self.windowState() == Qt.WindowNoState: self._preferences.setValue("general/window_width", self.width()) self._preferences.setValue("general/window_height", self.height()) self._preferences.setValue("general/window_left", self.x()) self._preferences.setValue("general/window_top", self.y()) self._preferences.setValue("general/window_state", Qt.WindowNoState) elif self.windowState() == Qt.WindowMaximized: self._preferences.setValue("general/window_state", Qt.WindowMaximized) def _updateViewportGeometry(self, width, height): view_w = width * self._viewport_rect.width() view_h = height * self._viewport_rect.height() for camera in self._app.getController().getScene().getAllCameras(): camera.setViewportSize(view_w, view_h) camera.setWindowSize(width, height) proj = Matrix() if camera.isPerspective(): proj.setPerspective(30, view_w / view_h, 1, 500) else: proj.setOrtho(-view_w / 2, view_w / 2, -view_h / 2, view_h / 2, -500, 500) camera.setProjectionMatrix(proj) self._app.getRenderer().setViewportSize(view_w, view_h) self._app.getRenderer().setWindowSize(width, height)
class VersaProbeDialog(QDialog, _HalWidgetBase): def __init__(self, parent=None): super(VersaProbeDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'VERSAPROBE' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(200, 200) buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) b = buttonBox.button(QDialogButtonBox.Ok) b.clicked.connect(lambda: self.close()) l = QVBoxLayout() self._o = VersaProbe() self.setLayout(l) l.addWidget(self._o) l.addWidget(buttonBox) def _hal_init(self): self._o.hal_init(self.HAL_GCOMP_, self.HAL_NAME_, self.QT_OBJECT_, self.QTVCP_INSTANCE_, self.PATHS_, self.PREFS_) x = self.geometry().x() y = self.geometry().y() w = self.geometry().width() h = self.geometry().height() geo = '%s %s %s %s' % (x, y, w, h) self._default_geometry = [x, y, w, h] if self.PREFS_: self._geometry_string = self.PREFS_.getpref( 'VersaProbeDialog-geometry', geo, str, 'DIALOG_OPTIONS') else: self._geometry_string = 'default' self.topParent = self.QTVCP_INSTANCE_ STATUS.connect('dialog-request', self._external_request) def _external_request(self, w, message): if message['NAME'] == self._request_name: self.load_dialog() def load_dialog(self): STATUS.emit('focus-overlay-changed', True, 'VersaProbe Dialog', self._color) self.calculate_placement() self.show() self.exec_() STATUS.emit('focus-overlay-changed', False, None, None) record_geometry(self, 'VersaProbeDialog-geometry') def calculate_placement(self): geometry_parsing(self, 'VersaProbeDialog-geometry') # ********************** # Designer properties # ********************** @pyqtSlot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) state = pyqtProperty(bool, getState, setState, resetState) overlay_color = pyqtProperty(QColor, getColor, setColor)
class PolygonWidget(QWidget): """PolygonWidget(QWidget) Provides a custom widget to display a polygon with properties and slots that can be used to customize its appearance. """ def __init__(self, parent=None): super(PolygonWidget, self).__init__(parent) self._sides = 5 self._innerRadius = 20 self._outerRadius = 50 self._angle = 0 self.createPath() self._innerColor = QColor(255, 255, 128) self._outerColor = QColor(255, 0, 128) self.createGradient() def paintEvent(self, event): painter = QPainter() painter.begin(self) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QBrush(QColor(192, 192, 255))) painter.draw_rect(event.rect()) painter.translate(self.width() / 2.0, self.height() / 2.0) painter.rotate(self._angle) painter.setBrush(QBrush(self.gradient)) painter.draw_path(self.path) painter.end() def sizeHint(self): return QSize(2 * self._outerRadius + 20, 2 * self._outerRadius + 20) def createPath(self): self.path = QPainterPath() angle = 2 * math.pi / self._sides self.path.moveTo(self._outerRadius, 0) for step in range(1, self._sides + 1): self.path.lineTo( self._innerRadius * math.cos((step - 0.5) * angle), self._innerRadius * math.sin((step - 0.5) * angle)) self.path.lineTo(self._outerRadius * math.cos(step * angle), self._outerRadius * math.sin(step * angle)) self.path.closeSubpath() def createGradient(self): center = QPointF(0, 0) self.gradient = QRadialGradient(center, self._outerRadius, center) self.gradient.setColorAt(0.5, QColor(self._innerColor)) self.gradient.setColorAt(1.0, QColor(self._outerColor)) # The angle property is implemented using the getAngle() and setAngle() # methods. def getAngle(self): return self._angle # The setAngle() setter method is also a slot. @pyqtSlot(int) def setAngle(self, angle): self._angle = min(max(0, angle), 360) self.update() angle = pyqtProperty(int, getAngle, setAngle) # The innerRadius property is implemented using the getInnerRadius() and # setInnerRadius() methods. def getInnerRadius(self): return self._innerRadius # The setInnerRadius() setter method is also a slot. @pyqtSlot(int) def setInnerRadius(self, radius): self._innerRadius = radius self.createPath() self.createGradient() self.update() innerRadius = pyqtProperty(int, getInnerRadius, setInnerRadius) # The outerRadius property is implemented using the getOuterRadius() and # setOuterRadius() methods. def getOuterRadius(self): return self._outerRadius # The setOuterRadius() setter method is also a slot. @pyqtSlot(int) def setOuterRadius(self, radius): self._outerRadius = radius self.createPath() self.createGradient() self.update() outerRadius = pyqtProperty(int, getOuterRadius, setOuterRadius) # The numberOfSides property is implemented using the getNumberOfSides() # and setNumberOfSides() methods. def getNumberOfSides(self): return self._sides # The setNumberOfSides() setter method is also a slot. @pyqtSlot(int) def setNumberOfSides(self, sides): self._sides = max(3, sides) self.createPath() self.update() numberOfSides = pyqtProperty(int, getNumberOfSides, setNumberOfSides) # The innerColor property is implemented using the getInnerColor() and # setInnerColor() methods. def getInnerColor(self): return self._innerColor def setInnerColor(self, color): self._innerColor = max(3, color) self.createGradient() self.update() innerColor = pyqtProperty(QColor, getInnerColor, setInnerColor) # The outerColor property is implemented using the getOuterColor() and # setOuterColor() methods. def getOuterColor(self): return self._outerColor def setOuterColor(self, color): self._outerColor = color self.createGradient() self.update() outerColor = pyqtProperty(QColor, getOuterColor, setOuterColor)
class StateLED(LED): def __init__(self, parent=None): super(StateLED, self).__init__(parent) self.has_hal_pins = False self.setState(False) self.is_estopped = False self.is_on = False self.is_homed = False self.is_idle = False self.is_paused = False self.invert_state = False self.is_flood = False self.is_mist = False self.is_block_delete = False self.is_optional_stop = False self.is_joint_homed = False self.is_limits_overridden = False self.joint_number = 0 def _hal_init(self): def only_false(data): if data: return self._flip_state(False) if self.is_estopped: STATUS.connect('state-estop', lambda w: self._flip_state(True)) STATUS.connect('state-estop-reset', lambda w: self._flip_state(False)) elif self.is_on: STATUS.connect('state-on', lambda w: self._flip_state(True)) STATUS.connect('state-off', lambda w: self._flip_state(False)) elif self.is_homed: STATUS.connect('all-homed', lambda w: self._flip_state(True)) STATUS.connect('not-all-homed', lambda w, axis: self._flip_state(False)) elif self.is_idle: STATUS.connect('interp-idle', lambda w: self._flip_state(True)) STATUS.connect('interp-run', lambda w: self._flip_state(False)) elif self.is_paused: STATUS.connect('program-pause-changed', lambda w, data: self._flip_state(data)) elif self.is_flood: STATUS.connect('flood-changed', lambda w, data: self._flip_state(data)) elif self.is_mist: STATUS.connect('mist-changed', lambda w, data: self._flip_state(data)) elif self.is_block_delete: STATUS.connect('block-delete-changed', lambda w, data: self._flip_state(data)) elif self.is_optional_stop: STATUS.connect('optional-stop-changed', lambda w, data: self._flip_state(data)) elif self.is_joint_homed: STATUS.connect('homed', lambda w, data: self.joint_homed(data)) STATUS.connect('not-all-homed', lambda w, data: self.joints_unhomed(data)) elif self.is_limits_overridden: STATUS.connect('override-limits-changed', self.check_override_limits) STATUS.connect('hard-limits-tripped', lambda w, data: only_false(data)) def _flip_state(self, data): if self.invert_state: data = not data self.change_state(data) def joint_homed(self, joint): if int(joint) == self.joint_number: self._flip_state(True) def joints_unhomed(self, jlist): if str(self.joint_number) in jlist: self._flip_state(False) def check_override_limits(self, w, state, data): for i in data: if i == 1: self._flip_state(True) return self._flip_state(False) ######################################################################### # This is how designer can interact with our widget properties. # designer will show the pyqtProperty properties in the editor # it will use the get set and reset calls to do those actions # # _toggle_properties makes it so we can only select one option ######################################################################## def _toggle_properties(self, picked): data = ('is_paused', 'is_estopped', 'is_on', 'is_idle', 'is_homed', 'is_flood', 'is_mist', 'is_block_delete', 'is_optional_stop', 'is_joint_homed', 'is_limits_overridden') for i in data: if not i == picked: self[i + '_status'] = False # property getter/setters # invert status def set_invert_state(self, data): self.invert_state = data def get_invert_state(self): return self.invert_state def reset_invert_state(self): self.invert_state = False # machine is paused status def set_is_paused(self, data): self.is_paused = data if data: self._toggle_properties('is_paused') def get_is_paused(self): return self.is_paused def reset_is_paused(self): self.is_paused = False # machine is estopped status def set_is_estopped(self, data): self.is_estopped = data if data: self._toggle_properties('is_estopped') def get_is_estopped(self): return self.is_estopped def reset_is_estopped(self): self.is_estopped = False # machine is on status def set_is_on(self, data): self.is_on = data if data: self._toggle_properties('is_on') def get_is_on(self): return self.is_on def reset_is_on(self): self.is_on = False # machine is idle status def set_is_idle(self, data): self.is_idle = data if data: self._toggle_properties('is_idle') def get_is_idle(self): return self.is_idle def reset_is_idle(self): self.is_idle = False # machine_is_homed status def set_is_homed(self, data): self.is_homed = data if data: self._toggle_properties('is_homed') def get_is_homed(self): return self.is_homed def reset_is_homed(self): self.is_homed = False # machine is_flood status def set_is_flood(self, data): self.is_flood = data if data: self._toggle_properties('is_flood') def get_is_flood(self): return self.is_flood def reset_is_flood(self): self.is_flood = False # machine is_mist status def set_is_mist(self, data): self.is_mist = data if data: self._toggle_properties('is_mist') def get_is_mist(self): return self.is_mist def reset_is_mist(self): self.is_mist = False # machine_is_block_delete status def set_is_block_delete(self, data): self.is_block_delete = data if data: self._toggle_properties('is_block_delete') def get_is_block_delete(self): return self.is_block_delete def reset_is_block_delete(self): self.is_block_delete = False # machine_is_optional_stop status def set_is_optional_stop(self, data): self.is_optional_stop = data if data: self._toggle_properties('is_optional_stop') def get_is_optional_stop(self): return self.is_optional_stop def reset_is_optional_stop(self): self.is_optional_stop = False # machine_is_joint_homed status def set_is_joint_homed(self, data): self.is_joint_homed = data if data: self._toggle_properties('is_joint_homed') def get_is_joint_homed(self): return self.is_joint_homed def reset_is_joint_homed(self): self.is_joint_homed = False # machine_is_limits_overridden status def set_is_limits_overridden(self, data): self.is_limits_overridden = data if data: self._toggle_properties('is_limits_overridden') def get_is_limits_overridden(self): return self.is_limits_overridden def reset_is_limits_overridden(self): self.is_limits_overridden = False # Non bool # machine_joint_number status def set_joint_number(self, data): self.joint_number = data def get_joint_number(self): return self.joint_number def reset_joint_number(self): self.joint_number = 0 # designer will show these properties in this order: # BOOL invert_state_status = pyqtProperty(bool, get_invert_state, set_invert_state, reset_invert_state) is_paused_status = pyqtProperty(bool, get_is_paused, set_is_paused, reset_is_paused) is_estopped_status = pyqtProperty(bool, get_is_estopped, set_is_estopped, reset_is_estopped) is_on_status = pyqtProperty(bool, get_is_on, set_is_on, reset_is_on) is_idle_status = pyqtProperty(bool, get_is_idle, set_is_idle, reset_is_idle) is_homed_status = pyqtProperty(bool, get_is_homed, set_is_homed, reset_is_homed) is_flood_status = pyqtProperty(bool, get_is_flood, set_is_flood, reset_is_flood) is_mist_status = pyqtProperty(bool, get_is_mist, set_is_mist, reset_is_mist) is_block_delete_status = pyqtProperty(bool, get_is_block_delete, set_is_block_delete, reset_is_block_delete) is_optional_stop_status = pyqtProperty(bool, get_is_optional_stop, set_is_optional_stop, reset_is_optional_stop) is_joint_homed_status = pyqtProperty(bool, get_is_joint_homed, set_is_joint_homed, reset_is_joint_homed) is_limits_overridden_status = pyqtProperty(bool, get_is_limits_overridden, set_is_limits_overridden, reset_is_limits_overridden) # NON BOOL joint_number_status = pyqtProperty(int, get_joint_number, set_joint_number, reset_joint_number) # boilder code def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value)
class BubbleLabel(QWidget): BackgroundColor = QColor(195, 195, 195) BorderColor = QColor(150, 150, 150) def __init__(self, *args, **kwargs): text = kwargs.pop("text", "") super(BubbleLabel, self).__init__(*args, **kwargs) # 设置无边框置顶 self.setWindowFlags(Qt.Window | Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) # 设置最小宽度和高度 self.setMinimumWidth(200) self.setMinimumHeight(48) self.setAttribute(Qt.WA_TranslucentBackground, True) layout = QVBoxLayout(self) # 左上右下的边距(下方16是因为包括了三角形) layout.setContentsMargins(8, 8, 8, 16) self.label = QLabel(self) layout.addWidget(self.label) self.setText(text) # 获取屏幕高宽 self._desktop = QApplication.instance().desktop() def setText(self, text): self.label.setText(text) def text(self): return self.label.text() def stop(self): self.hide() self.animationGroup.stop() self.close() def show(self): super(BubbleLabel, self).show() # 窗口开始位置 startPos = QPoint( self._desktop.screenGeometry().width() - self.width() - 100, self._desktop.availableGeometry().height() - self.height()) endPos = QPoint( self._desktop.screenGeometry().width() - self.width() - 100, self._desktop.availableGeometry().height() - self.height() * 3 - 5) print(startPos, endPos) self.move(startPos) # 初始化动画 self.initAnimation(startPos, endPos) def initAnimation(self, startPos, endPos): # 透明度动画 opacityAnimation = QPropertyAnimation(self, b"opacity") opacityAnimation.setStartValue(1.0) opacityAnimation.setEndValue(0.0) # 设置动画曲线 opacityAnimation.setEasingCurve(QEasingCurve.InQuad) opacityAnimation.setDuration(4000) # 在4秒的时间内完成 # 往上移动动画 moveAnimation = QPropertyAnimation(self, b"pos") moveAnimation.setStartValue(startPos) moveAnimation.setEndValue(endPos) moveAnimation.setEasingCurve(QEasingCurve.InQuad) moveAnimation.setDuration(5000) # 在5秒的时间内完成 # 并行动画组(目的是让上面的两个动画同时进行) self.animationGroup = QParallelAnimationGroup(self) self.animationGroup.addAnimation(opacityAnimation) self.animationGroup.addAnimation(moveAnimation) self.animationGroup.finished.connect(self.close) # 动画结束时关闭窗口 self.animationGroup.start() def paintEvent(self, event): super(BubbleLabel, self).paintEvent(event) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # 抗锯齿 rectPath = QPainterPath() # 圆角矩形 triPath = QPainterPath() # 底部三角形 height = self.height() - 8 # 往上偏移8 rectPath.addRoundedRect(QRectF(0, 0, self.width(), height), 5, 5) x = self.width() / 5 * 4 triPath.moveTo(x, height) # 移动到底部横线4/5处 # 画三角形 triPath.lineTo(x + 6, height + 8) triPath.lineTo(x + 12, height) rectPath.addPath(triPath) # 添加三角形到之前的矩形上 # 边框画笔 painter.setPen( QPen(self.BorderColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # 背景画刷 painter.setBrush(self.BackgroundColor) # 绘制形状 painter.drawPath(rectPath) # 三角形底边绘制一条线保证颜色与背景一样 painter.setPen( QPen(self.BackgroundColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(x, height, x + 12, height) def windowOpacity(self): return super(BubbleLabel, self).windowOpacity() def setWindowOpacity(self, opacity): super(BubbleLabel, self).setWindowOpacity(opacity) # 由于opacity属性不在QWidget中需要重新定义一个 opacity = pyqtProperty(float, windowOpacity, setWindowOpacity)
class Command(QQuickItem): def __init__(self, parent=None): super(QQuickItem, self).__init__(parent) self._serial = 0 self.c = linuxcnc.command() self.setSerial(self.c.serial) @pyqtSlot() def abort(self): self.c.abort() @pyqtSlot(int, name="auto") def auto1(self, param1): self.c.auto(param1) @pyqtSlot(int, int, name="auto") def auto2(self, param1, param2): self.c.auto(param1, param2) @pyqtSlot(int) def brake(self, param1): self.c.brake(param1) @pyqtSlot(int) def debug(self, param1): self.c.debug(param1) @pyqtSlot(float) def feedrate(self, param1): self.c.feedrate(param1) @pyqtSlot(int) def flood(self, param1): self.c.flood(param1) @pyqtSlot(int) def home(self, param1): self.c.home(param1) @pyqtSlot(int, int, name="jog") def jog1(self, param1, param2): self.c.jog(param1, param2) @pyqtSlot(int, int, int, name="jog") def jog2(self, param1, param2, param3): self.c.jog(param1, param2, param3) @pyqtSlot(int, int, int, int, name="jog") def jog3(self, param1, param2, param3, param4): self.c.jog(param1, param2, param3, param4) @pyqtSlot() def loadToolTable(self): self.c.load_tool_table() @pyqtSlot(float) def maxvel(self, param1): self.c.maxvel(param1) @pyqtSlot('QString') def mdi(self, param1): self.c.mdi(param1) @pyqtSlot(int) def mist(self, param1): self.c.mist(param1) @pyqtSlot() def overrideLimits(self): self.c.override_limits() @pyqtSlot('QString') def programOpen(self, param1): self.c.program_open(param1) @pyqtSlot() def resetInterpreter(self): self.c.reset_interpreter() @pyqtSlot(int) def setAdaptiveFeed(self, param1): self.c.set_adaptive_feed(param1) @pyqtSlot(int, float) def setAnalogOutput(self, param1, param2): self.c.set_analog_output(param1, param2) @pyqtSlot(int) def setBlockDelete(self, param1): self.c.set_block_delete(param1) @pyqtSlot(int, int) def setDigitalOutput(self, param1, param2): self.c.set_digital_output(param1, param2) @pyqtSlot(int) def setFeedHold(self, param1): self.c.set_feed_hold(param1) @pyqtSlot(int) def setFeedOverride(self, param1): self.c.set_feed_override(param1) @pyqtSlot(int, float) def setMaxLimit(self, param1, param2): self.c.set_max_limit(param1, param2) @pyqtSlot(int, float) def setMinLimit(self, param1, param2): self.c.set_min_limit(param1, param2) @pyqtSlot(int) def setOptionalStop(self, param1): self.c.set_option_stop(param1) @pyqtSlot(int) def setSpindleOverride(self, param1): self.c.set_spindle_override(param1) @pyqtSlot(int) def spindle(self, param1): self.c.spindle(param1) @pyqtSlot(float) def spindleoverride(self, param1): self.c.spindleoverride(param1) @pyqtSlot(int) def state(self, param1): self.c.state(param1) @pyqtSlot(int) def teleopEnabled(self, param1): self.c.teleop_enable(param1) @pyqtSlot(float, float, float, name="teleopVector") def teleopVector1(self, param1, param2, param3): self.c.teleop_vector(param1, param2, param3) @pyqtSlot(float, float, float, float, name="teleopVector") def teleopVector2(self, param1, param2, param3, param4): self.c.teleop_vector(param1, param2, param3, param4) @pyqtSlot(float, float, float, float, float, name="teleopVector") def teleopVector3(self, param1, param2, param3, param4, param5): self.c.teleop_vector(param1, param2, param3, param4, param5) @pyqtSlot(float, float, float, float, float, float, name="teleopVector") def teleopVector4(self, param1, param2, param3, param4, param5, param6): self.c.teleop_vector(param1, param2, param3, param4, param5, param6) @pyqtSlot(int, float, float, float, float, float, int) def toolOffset(self, param1, param2, param3, param4, param5, param6, param7): self.c.tool_offset(param1, param2, param3, param4, param5, param6) @pyqtSlot(int) def trajMode(self, param1): self.c.traj_mode(param1) @pyqtSlot(int) def unhome(self, param1): self.c.unhome(param1) @pyqtSlot(name="waitComplete") def waitComplete1(self): self.c.wait_complete() @pyqtSlot(float, name="waitComplete") def waitComplete2(self, param1): self.c.wait_complete(param1) # serial Property serialChanged = pyqtSignal(int) def getSerial(self): return self._serial def setSerial(self, serial): if (self._serial != serial): self._serial = serial self.serialChanged.emit(self._serial) serial = pyqtProperty(int, getSerial, notify=serialChanged)
class PandaPrimitiveWidget(QFrame): # static variables status_label_dict = { pp.PandaPrimitiveStatus.NEUTRAL: '', pp.PandaPrimitiveStatus.READY: pp.PandaPrimitiveStatus.READY.name, pp.PandaPrimitiveStatus.ERROR: pp.PandaPrimitiveStatus.ERROR.name, pp.PandaPrimitiveStatus.EXECUTING: pp.PandaPrimitiveStatus.EXECUTING.name, pp.PandaPrimitiveStatus.REVERTING: pp.PandaPrimitiveStatus.REVERTING.name, pp.PandaPrimitiveStatus.EXECUTED: '' } font = QFont() font.setBold(True) font.setPointSize(10) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) def __init__(self, parent, panda_primitive): super(PandaPrimitiveWidget, self).__init__(parent) self.initUI(panda_primitive) def initUI(self, panda_primitive): # Create widget subcomponents self.primitive = panda_primitive self.primitive_label = QLabel() self.status_label = QLabel( str(PandaPrimitiveWidget.status_label_dict[ panda_primitive.status])) self.status_label.setAlignment(Qt.AlignCenter) # Fetch fitting icon for primitive primitive_icon_path = os.path.join( rospkg.RosPack().get_path('panda_pbd'), 'resources', panda_primitive.__class__.__name__ + '.png') primitive_image = QPixmap(primitive_icon_path) self.primitive_label.setPixmap(primitive_image) # Add vertical layout layout = QVBoxLayout(self) layout.addWidget(self.primitive_label) layout.addWidget(self.status_label) self.setSizePolicy(PandaPrimitiveWidget.sizePolicy) # Beautify QFrame and Color self.setFrameShape(QFrame.Panel) self.setFrameShadow(QFrame.Raised) self.setLineWidth(2) self.status_label.setFont(PandaPrimitiveWidget.font) # Animation self.animation = QPropertyAnimation(self, 'background_color') self.animation.setDuration(2000) # in ms self.animation.setLoopCount(-1) self.animation.setStartValue(QColor('ghostwhite')) self.animation.setEndValue(QColor('ghostwhite')) self.animation.setKeyValueAt(0.5, QColor('cornflowerblue')) self.setAutoFillBackground(True) self.setPalette(gray_palette) def get_background_color(self): return self.palette().color(QPalette.Background) def set_background_color(self, color): palette = QPalette() palette.setColor(QPalette.Background, color) self.setPalette(palette) background_color = pyqtProperty(QColor, get_background_color, set_background_color) def sizeHint(self): return QSize(PRIMITIVE_WIDTH, PRIMITIVE_HEIGHT) def updateWidget(self): if self.primitive.status == pp.PandaPrimitiveStatus.EXECUTING or \ self.primitive.status == pp.PandaPrimitiveStatus.REVERTING: self.animation.start() elif self.primitive.status == pp.PandaPrimitiveStatus.ERROR: self.animation.stop() self.setPalette(error_palette) else: self.animation.stop() if self.primitive.status == pp.PandaPrimitiveStatus.EXECUTED: self.setPalette(executed_primitive_palette) elif self.primitive.status == pp.PandaPrimitiveStatus.READY: self.setPalette(white_palette) else: self.setPalette(gray_palette) self.status_label.setText( str(PandaPrimitiveWidget.status_label_dict[self.primitive.status])) self.update()
class StatusImageSwitcher(ImageSwitcher): def __init__(self, parent=None): super(StatusImageSwitcher, self).__init__(parent) self._imagePath = [os.path.join(self.IMAGEDIR,'applet-critical.png'), os.path.join(self.IMAGEDIR,'spindle_ccw.gif'), os.path.join(self.IMAGEDIR,'spindle_cw.gif')] self.spindle = True self.all_homed = False self.hard_limits = False self._last_limit = [] for i in range(0,len(INFO.AVAILABLE_JOINTS)): self._last_limit.append([0,0]) def _hal_init(self): if self.spindle: STATUS.connect('spindle-control-changed', lambda w, b, d: self.switch_on_spindle(b,d)) elif self.all_homed: STATUS.connect('not-all-homed', lambda w, data: self.switch_on_homed(0)) STATUS.connect('all-homed', lambda w: self.switch_on_homed(1)) elif self.hard_limits: STATUS.connect('hard-limits-tripped', lambda w, data, group: self.switch_on_hard_limits(data, group)) def switch_on_spindle(self, b, data): if data <0: data= 2 self.set_image_number(data) def switch_on_homed(self, data): if not data <0: self.set_image_number(data) def switch_on_hard_limits(self, data, group): if not data: # tripped self.set_image_number(0) elif (len(self._imagePath)) == 2: #print 'bool images' self.set_image_number(1) elif (len(self._imagePath)-1) == (len(INFO.AVAILABLE_JOINTS)): #print 'per joint limts images', self._last_limit, group for i in range(0,len(INFO.AVAILABLE_JOINTS)): if group[i] == self._last_limit[i]: pass elif group[i] == [0,0]: pass else: self.set_image_number(i+1) break elif (len(self._imagePath)-1) == (len(INFO.AVAILABLE_JOINTS) * 2): pass #print 'per joint and per end limts images' self._last_limit = group ######################################################################### # This is how designer can interact with our widget properties. # designer will show the pyqtProperty properties in the editor # it will use the get set and reset calls to do those actions # # _toggle_properties makes it so we can only select one option ######################################################################## def _toggle_properties(self, picked): data = ('spindle','all_homed', 'hard_limits' ) for i in data: if not i == picked: self['watch_'+i] = False # property getter/setters # machine_spindle status def set_spindle(self, data): self.spindle = data if data: self._toggle_properties('spindle') def get_spindle(self): return self.spindle def reset_spindle(self): self.spindle = False watch_spindle = pyqtProperty(bool, get_spindle, set_spindle, reset_spindle) # machine_homed status def set_homed(self, data): self.all_homed = data if data: self._toggle_properties('all_homed') def get_homed(self): return self.all_homed def reset_homed(self): self.all_homed = False watch_all_homed = pyqtProperty(bool, get_homed, set_homed, reset_homed) # machine_limits status def set_limits(self, data): self.hard_limits = data if data: self._toggle_properties('hard_limits') def get_limits(self): return self.hard_limits def reset_limits(self): self.hard_limits = False watch_hard_limits = pyqtProperty(bool, get_limits, set_limits, reset_limits) ############################## # required class boiler code # ############################## def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value)
class StatusAdjustmentBar(HAdjustmentBar, _HalWidgetBase): def __init__(self, parent=None): super(StatusAdjustmentBar, self).__init__(parent) self._block_signal = False self.rapid = True self.feed = False self.spindle = False self.jograte = False self.jograte_angular = False self.maxv = False self.texttemplate = 'Value = %s' # self.PREFS_ # self.HAL_NAME_ # comes from base class def _hal_init(self): # if we build the widget before now, designer options are not # taken account self.buildWidget() # when estopped disable buttons STATUS.connect('state-estop', lambda w: self.setEnabled(False)) STATUS.connect('state-estop-reset', lambda w: self.setEnabled(True)) # set options if self.rapid: STATUS.connect('rapid-override-changed', lambda w, data: self.setValue(data)) elif self.feed: STATUS.connect('feed-override-changed', lambda w, data: self.setValue(data)) self.setMaximum(int(INFO.MAX_FEED_OVERRIDE)) elif self.spindle: STATUS.connect('spindle-override-changed', lambda w, data: self.setValue(data)) self.setMaximum(int(INFO.MAX_SPINDLE_OVERRIDE)) self.setMinimum(int(INFO.MIN_SPINDLE_OVERRIDE)) elif self.jograte: STATUS.connect('jograte-changed', lambda w, data: self.setValue(data)) self.setMaximum(int(INFO.MAX_LINEAR_JOG_VEL)) elif self.jograte_angular: STATUS.connect('jograte-angular-changed', lambda w, data: self.setValue(data)) print int(INFO.MAX_ANGULAR_JOG_VEL) self.setMaximum(int(INFO.MAX_ANGULAR_JOG_VEL)) elif self.maxv: STATUS.connect('max-velocity-override-changed', lambda w, data: self.setValue(data)) self.setMaximum(int(INFO.MAX_TRAJ_VELOCITY)) else: LOG.error('{} : no option recognised'.format(self.HAL_NAME_)) # If there is a preference file object use it to load the hi/low toggle points if self.PREFS_: self.hi_value = self.PREFS_.getpref(self.HAL_NAME_ + '-hi-value', 75, int, 'SCREEN_CONTROL_LAST_SETTING') self.low_value = self.PREFS_.getpref( self.HAL_NAME_ + '-low-value', 25, int, 'SCREEN_CONTROL_LAST_SETTING') # connect a signal and callback function to the button self.valueChanged.connect(self._action) # when qtvcp closes this gets called def closing_cleanup__(self): if self.PREFS_: LOG.debug('Saving {} data to file.'.format(self.HAL_NAME_)) self.PREFS_.putpref(self.HAL_NAME_ + '-hi-value', self.hi_value, int, 'SCREEN_CONTROL_LAST_SETTING') self.PREFS_.putpref(self.HAL_NAME_ + '-low-value', self.low_value, int, 'SCREEN_CONTROL_LAST_SETTING') def _action(self, value): if self.rapid: ACTION.SET_RAPID_RATE(value) elif self.feed: ACTION.SET_FEED_RATE(value) elif self.spindle: ACTION.SET_SPINDLE_RATE(value) elif self.jograte: ACTION.SET_JOG_RATE(value) elif self.jograte_angular: ACTION.SET_JOG_RATE_ANGULAR(value) elif self.maxv: ACTION.SET_MAX_VELOCITY_RATE(value) else: LOG.error('{} no action recognised'.format(self.HAL_NAME_)) ######################################################################### # This is how designer can interact with our widget properties. # designer will show the pyqtProperty properties in the editor # it will use the get set and reset calls to do those actions # # _toggle_properties makes it so we can only select one option ######################################################################## def _toggle_properties(self, picked): data = ('rapid', 'feed', 'spindle', 'jograte', 'jograte_angular', 'maxv') for i in data: if not i == picked: self[i + '_rate'] = False def setrapid(self, data): self.rapid = data if data: self._toggle_properties('rapid') def getrapid(self): return self.rapid def resetrapid(self): self.rapid = False def setfeed(self, data): self.feed = data if data: self._toggle_properties('feed') def getfeed(self): return self.feed def resetfeed(self): self.feed = False def setspindle(self, data): self.spindle = data if data: self._toggle_properties('spindle') def getspindle(self): return self.spindle def resetspindle(self): self.spindle = False def setjograte(self, data): self.jograte = data if data: self._toggle_properties('jograte') def getjograte(self): return self.jograte def resetjograte(self): self.jograte = False def setjograteangular(self, data): self.jograte_angular = data if data: self._toggle_properties('jograte_angular') def getjograteangular(self): return self.jograte_angular def resetjograteangular(self): self.jograte_angular = False def setmaxv(self, data): self.maxv = data if data: self._toggle_properties('maxv') def getmaxv(self): return self.maxv def resetmaxv(self): self.maxv = False def setshowtoggle(self, data): self.showToggleButton = data def getshowtoggle(self): return self.showToggleButton def resetshowtoggle(self): self.showToggleButton = True def setsettingmenu(self, data): self.showSettingMenu = data def getsettingmenu(self): return self.showSettingMenu def resetsettingmenu(self): self.showSettingMenu = True # Designer makes '\n' into '\\n' if added # we put fix it here so we actually get the newline def settexttemplate(self, data): data = data.replace('\\n', '\n') self.texttemplate = data def gettexttemplate(self): return self.texttemplate def resettexttemplate(self): self.texttemplate = 'Value = %s' def setsteprate(self, data): self.step = data def getsteprate(self): return self.step def resetsteprate(self): self.steprate = 1 rapid_rate = pyqtProperty(bool, getrapid, setrapid, resetrapid) feed_rate = pyqtProperty(bool, getfeed, setfeed, resetfeed) spindle_rate = pyqtProperty(bool, getspindle, setspindle, resetspindle) jograte_rate = pyqtProperty(bool, getjograte, setjograte, resetjograte) jograte_angular_rate = pyqtProperty(bool, getjograteangular, setjograteangular, resetjograteangular) max_velocity_rate = pyqtProperty(bool, getmaxv, setmaxv, resetmaxv) show_toggle_button = pyqtProperty(bool, getshowtoggle, setshowtoggle, resetshowtoggle) show_setting_menu = pyqtProperty(bool, getsettingmenu, setsettingmenu, resetsettingmenu) text_template = pyqtProperty(str, gettexttemplate, settexttemplate, resettexttemplate) step_rate = pyqtProperty(int, getsteprate, setsteprate, resetsteprate) ############################## # required class boiler code # ############################## def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value)
class GEditor(QMainWindow, _HalWidgetBase): percentDone = pyqtSignal(int) def __init__(self, parent=None, designer=False): if not designer: parent = None super().__init__(parent) self._show_editor = True self.load_dialog_code = 'LOAD' self.save_dialog_code = 'SAVE' self.dialog_code = 'KEYBOARD' STATUS.connect('general',self.returnFromDialog) self.isCaseSensitive = 0 self.setMinimumSize(QSize(300, 200)) self.setWindowTitle("PyQt5 editor test example") # make editor self.editor = GcodeDisplay(self) self.setCentralWidget(self.editor) # class patch editor's function to ours # so we get the lines percent update self.editor.emit_percent = self.emit_percent self.editor.setReadOnly(True) self.editor.setModified(False) self.createActions() # Create toolbar and add action self.toolBar = QToolBar('File') self.toolBar.setObjectName('{}_toolbarfile'.format( self.objectName())) self.toolBar.addAction(self.newAction) self.toolBar.addAction(self.openAction) self.toolBar.addAction(self.saveAction) self.toolBar.addAction(self.exitAction) self.addToolBar(Qt.TopToolBarArea, self.toolBar) #self.toolBar.addSeparator() self.toolBarLexer = QToolBar('Lexer') self.toolBarLexer.setObjectName('{}_toolbarlexer'.format( self.objectName())) # add lexer actions self.toolBarLexer.addAction(self.gCodeLexerAction) self.toolBarLexer.addAction(self.pythonLexerAction) self.toolBarLexer.addSeparator() self.label = QLabel('''<html><head/><body><p><span style=" font-size:20pt; font-weight:600;">Edit Mode</span></p></body></html>''') self.toolBarLexer.addWidget(self.label) self.addToolBar(Qt.TopToolBarArea, self.toolBarLexer) # Create toolbar and add action self.toolBarEdit = QToolBar('Edit') self.toolBarEdit.setObjectName('{}_toolbaredit'.format( self.objectName())) self.toolBarEdit.addAction(self.undoAction) self.toolBarEdit.addAction(self.redoAction) self.toolBarEdit.addSeparator() self.toolBarEdit.addAction(self.replaceAction) self.toolBarEdit.addAction(self.findAction) self.toolBarEdit.addAction(self.previousAction) self.toolBarEdit.addSeparator() self.toolBarEdit.addAction(self.caseAction) self.addToolBar(Qt.BottomToolBarArea, self.toolBarEdit) self.toolBarEntry = QToolBar('entry') self.toolBarEntry.setObjectName('{}_toolbarentry'.format( self.objectName())) frame = QFrame() box = QHBoxLayout() box.addWidget(self.searchText) box.addWidget(self.replaceText) box.addStretch(1) frame.setLayout(box) self.toolBarEntry.addWidget(frame) self.addToolBar(Qt.BottomToolBarArea, self.toolBarEntry) self.readOnlyMode(save = False) def createActions(self): # Create new action self.newAction = QAction(QIcon.fromTheme('document-new'), 'New', self) self.newAction.setShortcut('Ctrl+N') self.newAction.setStatusTip('New document') self.newAction.triggered.connect(self.newCall) # Create open action self.openAction = QAction(QIcon.fromTheme('document-open'), '&Open', self) self.openAction.setShortcut('Ctrl+O') self.openAction.setStatusTip('Open document') self.openAction.triggered.connect(self.openCall) # Create save action self.saveAction = QAction(QIcon.fromTheme('document-save'), '&Save', self) self.saveAction.setShortcut('Ctrl+S') self.saveAction.setStatusTip('Save document') self.saveAction.triggered.connect(self.saveCall) # Create exit action self.exitAction = QAction(QIcon.fromTheme('application-exit'), '&Exit', self) self.exitAction.setShortcut('Ctrl+Q') self.exitAction.setToolTip('Exit Edit Mode') self.exitAction.setStatusTip('Exit Edit Mode') self.exitAction.triggered.connect(self.exitCall) # Create gcode lexer action self.gCodeLexerAction = QAction(QIcon.fromTheme('lexer.png'), '&Gcode\nLexer', self) self.gCodeLexerAction.setCheckable(1) self.gCodeLexerAction.setShortcut('Ctrl+G') self.gCodeLexerAction.setStatusTip('Set Gcode highlighting') self.gCodeLexerAction.triggered.connect(self.gcodeLexerCall) # Create gcode lexer action self.pythonLexerAction = QAction(QIcon.fromTheme('lexer.png'), '&Python\nLexer', self) self.pythonLexerAction.setShortcut('Ctrl+P') self.pythonLexerAction.setStatusTip('Set Python highlighting') self.pythonLexerAction.triggered.connect(self.pythonLexerCall) self.searchText = QLineEdit(self) self.searchText.setToolTip('Search Text') self.searchText.setStatusTip('Text to search for') self.searchText.installEventFilter(self) self.replaceText = QLineEdit(self) self.replaceText.setToolTip('Replace Text') self.replaceText.setStatusTip('Replace search text with this text') self.replaceText.installEventFilter(self) # Create new action self.undoAction = QAction(QIcon.fromTheme('edit-undo'), 'Undo', self) self.undoAction.setStatusTip('Undo') self.undoAction.triggered.connect(self.undoCall) # create redo action self.redoAction = QAction(QIcon.fromTheme('edit-redo'), 'Redo', self) self.redoAction.setStatusTip('Redo') self.redoAction.triggered.connect(self.redoCall) # create replace action self.replaceAction = QAction(QIcon.fromTheme('edit-find-replace'), 'Replace', self) self.replaceAction.setStatusTip('Replace text') self.replaceAction.triggered.connect(self.replaceCall) # create find action self.findAction = QAction(QIcon.fromTheme('edit-find'), 'Find', self) self.findAction.setStatusTip('Find next occurrence of text') self.findAction.triggered.connect(self.findCall) # create next action self.previousAction = QAction(QIcon.fromTheme('go-previous'), 'Find Previous', self) self.previousAction.setStatusTip('Find previous occurrence of text') self.previousAction.triggered.connect(self.previousCall) # create case action self.caseAction = QAction(QIcon.fromTheme('edit-case'), 'Aa', self) self.caseAction.setToolTip('Toggle Match Case') self.caseAction.setStatusTip('Toggle between any case and match case') self.caseAction.setCheckable(1) self.caseAction.triggered.connect(self.caseCall) # catch focusIn event to pop keyboard dialog def eventFilter(self, obj, event): if event.type() == QEvent.FocusIn: if isinstance(obj, QLineEdit): # only if mouse selected if event.reason () == 0: self.popEntry(obj) return super().eventFilter(obj, event) # callback functions built for easy class patching ########## # want to refrain from renaming these functions as it will break # any class patch user's use # we split them like this so a user can intercept the callback # but still call the original functionality def caseCall(self): self.case() def case(self): self.isCaseSensitive -=1 self.isCaseSensitive *=-1 def exitCall(self): self.exit() def exit(self): if self.editor.isModified(): result = self.killCheck() if result: try: self.editor.reload_last(None) except Exception as e: print (e) self.readOnlyMode() return result return True def findCall(self): self.find() def find(self): self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap= True, fwd=True) def previousCall(self): self.previous() def previous(self): self.editor.setCursorPosition(self.editor.getSelection()[0], self.editor.getSelection()[1]) self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap=True, fwd=False) def gcodeLexerCall(self): self.gcodeLexer() def gcodeLexer(self): self.editor.set_gcode_lexer() def nextCall(self): self.next() def next(self): self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap=True, fwd=False) self.editor.search_Next() def newCall(self): self.new() def new(self): if self.editor.isModified(): result = self.killCheck() if result: self.editor.new_text() else: self.editor.new_text() def openCall(self): self.open() def open(self): self.getFileName() def openReturn(self,f): ACTION.OPEN_PROGRAM(f) self.editor.setModified(False) def pythonLexerCall(self): self.pythonLexer() def pythonLexer(self): self.editor.set_python_lexer() def redoCall(self): self.redo() def redo(self): self.editor.redo() def replaceCall(self): self.replace() def replace(self): self.editor.replace_text(str(self.replaceText.text())) self.editor.search(str(self.searchText.text()), re=False, case=self.isCaseSensitive, word=False, wrap=True, fwd=True) def saveCall(self): self.save() def save(self): self.getSaveFileName() def saveReturn(self, fname): saved = ACTION.SAVE_PROGRAM(self.editor.text(), fname) if saved is not None: self.editor.setModified(False) ACTION.OPEN_PROGRAM(saved) def undoCall(self): self.undo() def undo(self): self.editor.undo() # helper functions ############################################ def saveSettings(self): self.SETTINGS_.beginGroup("geditor-{}".format(self.objectName())) self.SETTINGS_.setValue('state', QVariant(self.saveState().data())) self.SETTINGS_.endGroup() def restoreSettings(self): # set recorded toolbar settings self.SETTINGS_.beginGroup("geditor-{}".format(self.objectName())) state = self.SETTINGS_.value('state') self.SETTINGS_.endGroup() if not state is None: try: self.restoreState(QByteArray(state)) except Exception as e: print(e) else: return True return False def editMode(self): self.editor.setReadOnly(False) result = self.restoreSettings() check = (self.toolBar.toggleViewAction().isChecked() and self.toolBarEdit.toggleViewAction().isChecked() and self.toolBarEntry.toggleViewAction().isChecked() and self.toolBarLexer.toggleViewAction().isChecked()) if not check: self.toolBar.toggleViewAction().setChecked(False) self.toolBar.toggleViewAction().trigger() if not result: self.toolBarEdit.toggleViewAction().setChecked(False) self.toolBarEdit.toggleViewAction().trigger() self.toolBarEntry.toggleViewAction().setChecked(False) self.toolBarEntry.toggleViewAction().trigger() self.toolBarLexer.toggleViewAction().setChecked(False) self.toolBarLexer.toggleViewAction().trigger() def readOnlyMode(self, save = True): if save: self.saveSettings() self.editor.setReadOnly(True) self.toolBar.toggleViewAction().setChecked(True) self.toolBar.toggleViewAction().trigger() self.toolBarEdit.toggleViewAction().setChecked(True) self.toolBarEdit.toggleViewAction().trigger() self.toolBarEntry.toggleViewAction().setChecked(True) self.toolBarEntry.toggleViewAction().trigger() self.toolBarLexer.toggleViewAction().setChecked(True) self.toolBarLexer.toggleViewAction().trigger() def getFileName(self): mess = {'NAME':self.load_dialog_code,'ID':'%s__' % self.objectName(), 'TITLE':'Load Editor'} STATUS.emit('dialog-request', mess) def getSaveFileName(self): mess = {'NAME':self.save_dialog_code,'ID':'%s__' % self.objectName(), 'TITLE':'Save Editor'} STATUS.emit('dialog-request', mess) def popEntry(self, obj): mess = {'NAME':self.dialog_code, 'ID':'%s__' % self.objectName(), 'OVERLAY':False, 'OBJECT':obj, 'TITLE':'Set Entry for {}'.format(obj.objectName().upper()), 'GEONAME':'_{}'.format(self.dialog_code) } STATUS.emit('dialog-request', mess) LOG.debug('message sent:{}'.format (mess)) # process the STATUS return message def returnFromDialog(self, w, message): if message.get('NAME') == self.load_dialog_code: path = message.get('RETURN') code = bool(message.get('ID') == '%s__'% self.objectName()) if path and code: self.openReturn(path) elif message.get('NAME') == self.save_dialog_code: path = message.get('RETURN') code = bool(message.get('ID') == '%s__'% self.objectName()) if path and code: self.saveReturn(path) elif message.get('NAME') == self.dialog_code: txt = message['RETURN'] code = bool(message.get('ID') == '%s__'% self.objectName()) if code and txt is not None: LOG.debug('message return:{}'.format (message)) obj = message.get('OBJECT') obj.setText(str(txt)) def killCheck(self): choice = QMessageBox.question(self, 'Warning!', """This file has changed since loading and has not been saved. You will lose your changes. Still want to proceed?""", QMessageBox.Yes | QMessageBox.No) if choice == QMessageBox.Yes: return True else: return False def emit_percent(self, percent): self.percentDone.emit(percent) def select_lineup(self): self.editor.select_lineup(None) def select_linedown(self): self.editor.select_linedown(None) def select_line(self, line): self.editor.highlight_line(None, line) def jump_line(self, jump): self.editor.jump_line(jump) def get_line(self): return self.editor.getCursorPosition()[0] +1 def set_margin_width(self,width): self.editor.set_margin_width(width) def set_font(self, font): self.editor.font = font for i in range(0,8): self.editor.lexer.setFont(font,i) def set_background_color(self, color): self.editor.set_background_color(color) def isReadOnly(self): return self.editor.isReadOnly() # designer recognized getter/setters # auto_show_mdi status # These adjust the self.editor instance def set_auto_show_mdi(self, data): self.editor.auto_show_mdi = data def get_auto_show_mdi(self): return self.editor.auto_show_mdi def reset_auto_show_mdi(self): self.editor.auto_show_mdi = True auto_show_mdi_status = pyqtProperty(bool, get_auto_show_mdi, set_auto_show_mdi, reset_auto_show_mdi) # designer recognized getter/setters # auto_show_manual status def set_auto_show_manual(self, data): self.editor.auto_show_manual = data def get_auto_show_manual(self): return self.editor.auto_show_manual def reset_auto_show_manual(self): self.editor.auto_show_manual = True auto_show_manual_status = pyqtProperty(bool, get_auto_show_manual, set_auto_show_manual, reset_auto_show_manual) # designer recognized getter/setters # show_editor status def set_show_editor(self, data): self._show_editor = data if data: self.toolBar.show() self.toolBarLexer.show() self.toolBarEntry.show() self.toolBarEdit.show() else: self.toolBar.hide() self.toolBarLexer.hide() self.toolBarEntry.hide() self.toolBarEdit.hide() def get_show_editor(self): return self._show_editor def reset_show_editor(self): self._show_editor = True self.toolBar.show() self.toolBarLexer.show() self.toolBarEntry.show() self.toolBarEdit.show() show_editor = pyqtProperty(bool, get_show_editor, set_show_editor, reset_show_editor)
class CuraContainerStack(ContainerStack): def __init__(self, container_id: str) -> None: super().__init__(container_id) self._empty_instance_container = cura_empty_instance_containers.empty_container #type: InstanceContainer self._empty_quality_changes = cura_empty_instance_containers.empty_quality_changes_container #type: InstanceContainer self._empty_quality = cura_empty_instance_containers.empty_quality_container #type: InstanceContainer self._empty_material = cura_empty_instance_containers.empty_material_container #type: InstanceContainer self._empty_variant = cura_empty_instance_containers.empty_variant_container #type: InstanceContainer self._containers = [ self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap)) ] #type: List[ContainerInterface] self._containers[ _ContainerIndexes.QualityChanges] = self._empty_quality_changes self._containers[_ContainerIndexes.Quality] = self._empty_quality self._containers[_ContainerIndexes.Material] = self._empty_material self._containers[_ContainerIndexes.Variant] = self._empty_variant self.containersChanged.connect(self._onContainersChanged) import cura.CuraApplication #Here to prevent circular imports. self.setMetaDataEntry( "setting_version", cura.CuraApplication.CuraApplication.SettingVersion) # This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted. pyqtContainersChanged = pyqtSignal() ## Set the user changes container. # # \param new_user_changes The new user changes container. It is expected to have a "type" metadata entry with the value "user". def setUserChanges(self, new_user_changes: InstanceContainer) -> None: self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes) ## Get the user changes container. # # \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset=setUserChanges, notify=pyqtContainersChanged) def userChanges(self) -> InstanceContainer: return cast(InstanceContainer, self._containers[_ContainerIndexes.UserChanges]) ## Set the quality changes container. # # \param new_quality_changes The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes". def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit=False) -> None: self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit=postpone_emit) ## Get the quality changes container. # # \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset=setQualityChanges, notify=pyqtContainersChanged) def qualityChanges(self) -> InstanceContainer: return cast(InstanceContainer, self._containers[_ContainerIndexes.QualityChanges]) ## Set the intent container. # # \param new_intent The new intent container. It is expected to have a "type" metadata entry with the value "intent". def setIntent(self, new_intent: InstanceContainer, postpone_emit: bool = False) -> None: self.replaceContainer(_ContainerIndexes.Intent, new_intent, postpone_emit=postpone_emit) ## Get the quality container. # # \return The intent container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset=setIntent, notify=pyqtContainersChanged) def intent(self) -> InstanceContainer: return cast(InstanceContainer, self._containers[_ContainerIndexes.Intent]) ## Set the quality container. # # \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality". def setQuality(self, new_quality: InstanceContainer, postpone_emit: bool = False) -> None: self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit=postpone_emit) ## Get the quality container. # # \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset=setQuality, notify=pyqtContainersChanged) def quality(self) -> InstanceContainer: return cast(InstanceContainer, self._containers[_ContainerIndexes.Quality]) ## Set the material container. # # \param new_material The new material container. It is expected to have a "type" metadata entry with the value "material". def setMaterial(self, new_material: InstanceContainer, postpone_emit: bool = False) -> None: self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit=postpone_emit) ## Get the material container. # # \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset=setMaterial, notify=pyqtContainersChanged) def material(self) -> InstanceContainer: return cast(InstanceContainer, self._containers[_ContainerIndexes.Material]) ## Set the variant container. # # \param new_variant The new variant container. It is expected to have a "type" metadata entry with the value "variant". def setVariant(self, new_variant: InstanceContainer) -> None: self.replaceContainer(_ContainerIndexes.Variant, new_variant) ## Get the variant container. # # \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset=setVariant, notify=pyqtContainersChanged) def variant(self) -> InstanceContainer: return cast(InstanceContainer, self._containers[_ContainerIndexes.Variant]) ## Set the definition changes container. # # \param new_definition_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "definition_changes". def setDefinitionChanges( self, new_definition_changes: InstanceContainer) -> None: self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes) ## Get the definition changes container. # # \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer. @pyqtProperty(InstanceContainer, fset=setDefinitionChanges, notify=pyqtContainersChanged) def definitionChanges(self) -> InstanceContainer: return cast(InstanceContainer, self._containers[_ContainerIndexes.DefinitionChanges]) ## Set the definition container. # # \param new_definition The new definition container. It is expected to have a "type" metadata entry with the value "definition". def setDefinition(self, new_definition: DefinitionContainerInterface) -> None: self.replaceContainer(_ContainerIndexes.Definition, new_definition) def getDefinition(self) -> "DefinitionContainer": return cast(DefinitionContainer, self._containers[_ContainerIndexes.Definition]) definition = pyqtProperty(QObject, fget=getDefinition, fset=setDefinition, notify=pyqtContainersChanged) @override(ContainerStack) def getBottom(self) -> "DefinitionContainer": return self.definition @override(ContainerStack) def getTop(self) -> "InstanceContainer": return self.userChanges ## Check whether the specified setting has a 'user' value. # # A user value here is defined as the setting having a value in either # the UserChanges or QualityChanges container. # # \return True if the setting has a user value, False if not. @pyqtSlot(str, result=bool) def hasUserValue(self, key: str) -> bool: if self._containers[_ContainerIndexes.UserChanges].hasProperty( key, "value"): return True if self._containers[_ContainerIndexes.QualityChanges].hasProperty( key, "value"): return True return False ## Set a property of a setting. # # This will set a property of a specified setting. Since the container stack does not contain # any settings itself, it is required to specify a container to set the property on. The target # container is matched by container type. # # \param key The key of the setting to set. # \param property_name The name of the property to set. # \param new_value The new value to set the property to. def setProperty(self, key: str, property_name: str, property_value: Any, container: "ContainerInterface" = None, set_from_cache: bool = False) -> None: container_index = _ContainerIndexes.UserChanges self._containers[container_index].setProperty(key, property_name, property_value, container, set_from_cache) ## Overridden from ContainerStack # # Since we have a fixed order of containers in the stack and this method would modify the container # ordering, we disallow this operation. @override(ContainerStack) def addContainer(self, container: ContainerInterface) -> None: raise Exceptions.InvalidOperationError( "Cannot add a container to Global stack") ## Overridden from ContainerStack # # Since we have a fixed order of containers in the stack and this method would modify the container # ordering, we disallow this operation. @override(ContainerStack) def insertContainer(self, index: int, container: ContainerInterface) -> None: raise Exceptions.InvalidOperationError( "Cannot insert a container into Global stack") ## Overridden from ContainerStack # # Since we have a fixed order of containers in the stack and this method would modify the container # ordering, we disallow this operation. @override(ContainerStack) def removeContainer(self, index: int = 0) -> None: raise Exceptions.InvalidOperationError( "Cannot remove a container from Global stack") ## Overridden from ContainerStack # # Replaces the container at the specified index with another container. # This version performs checks to make sure the new container has the expected metadata and type. # # \throws Exception.InvalidContainerError Raised when trying to replace a container with a container that has an incorrect type. @override(ContainerStack) def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None: expected_type = _ContainerIndexes.IndexTypeMap[index] if expected_type == "definition": if not isinstance(container, DefinitionContainer): raise Exceptions.InvalidContainerError( "Cannot replace container at index {index} with a container that is not a DefinitionContainer" .format(index=index)) elif container != self._empty_instance_container and container.getMetaDataEntry( "type") != expected_type: raise Exceptions.InvalidContainerError( "Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type." .format(index=index, type=expected_type, actual_type=container.getMetaDataEntry("type"))) current_container = self._containers[index] if current_container.getId() == container.getId(): return super().replaceContainer(index, container, postpone_emit) ## Overridden from ContainerStack # # This deserialize will make sure the internal list of containers matches with what we expect. # It will first check to see if the container at a certain index already matches with what we # expect. If it does not, it will search for a matching container with the correct type. Should # no container with the correct type be found, it will use the empty container. # # \throws InvalidContainerStackError Raised when no definition can be found for the stack. @override(ContainerStack) def deserialize(self, serialized: str, file_name: Optional[str] = None) -> str: # update the serialized data first serialized = super().deserialize(serialized, file_name) new_containers = self._containers.copy() while len(new_containers) < len(_ContainerIndexes.IndexTypeMap): new_containers.append(self._empty_instance_container) # Validate and ensure the list of containers matches with what we expect for index, type_name in _ContainerIndexes.IndexTypeMap.items(): container = None try: container = new_containers[index] except IndexError: pass if type_name == "definition": if not container or not isinstance(container, DefinitionContainer): definition = self.findContainer( container_type=DefinitionContainer) if not definition: raise InvalidContainerStackError( "Stack {id} does not have a definition!".format( id=self.getId())) new_containers[index] = definition continue if not container or container.getMetaDataEntry( "type") != type_name: actual_container = self.findContainer(type=type_name) if actual_container: new_containers[index] = actual_container else: new_containers[index] = self._empty_instance_container self._containers = new_containers # CURA-5281 # Some stacks can have empty definition_changes containers which will cause problems. # Make sure that all stacks here have non-empty definition_changes containers. if isinstance(new_containers[_ContainerIndexes.DefinitionChanges], type(self._empty_instance_container)): from cura.Settings.CuraStackBuilder import CuraStackBuilder CuraStackBuilder.createDefinitionChangesContainer( self, self.getId() + "_settings") ## TODO; Deserialize the containers. return serialized ## protected: # Helper to make sure we emit a PyQt signal on container changes. def _onContainersChanged(self, container: Any) -> None: Application.getInstance().callLater(self.pyqtContainersChanged.emit) # Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine # and its properties rather than, for example, the extruder. Defaults to simply returning the definition property. def _getMachineDefinition(self) -> DefinitionContainer: return self.definition ## Find the ID that should be used when searching for instance containers for a specified definition. # # This handles the situation where the definition specifies we should use a different definition when # searching for instance containers. # # \param machine_definition The definition to find the "quality definition" for. # # \return The ID of the definition container to use when searching for instance containers. @classmethod def _findInstanceContainerDefinitionId( cls, machine_definition: DefinitionContainerInterface) -> str: quality_definition = machine_definition.getMetaDataEntry( "quality_definition") if not quality_definition: return machine_definition.id #type: ignore definitions = ContainerRegistry.getInstance().findDefinitionContainers( id=quality_definition) if not definitions: Logger.log( "w", "Unable to find parent definition {parent} for machine {machine}", parent=quality_definition, machine=machine_definition.id) #type: ignore return machine_definition.id #type: ignore return cls._findInstanceContainerDefinitionId(definitions[0]) ## getProperty for extruder positions, with translation from -1 to default extruder number def getExtruderPositionValueWithDefault(self, key): value = self.getProperty(key, "value") if value == -1: value = int(Application.getInstance().getMachineManager(). defaultExtruderPosition) return value