Example #1
0
    def _settingIsOverwritingInheritance(self, key: str, stack: ContainerStack = None) -> bool:
        """Check if a setting has an inheritance function that is overwritten"""

        has_setting_function = False
        if not stack:
            stack = self._active_container_stack
        if not stack:  # No active container stack yet!
            return False

        if self._active_container_stack is None:
            return False
        all_keys = self._active_container_stack.getAllKeys()

        containers = []  # type: List[ContainerInterface]

        has_user_state = stack.getProperty(key, "state") == InstanceState.User
        """Check if the setting has a user state. If not, it is never overwritten."""

        if not has_user_state:
            return False

        # If a setting is not enabled, don't label it as overwritten (It's never visible anyway).
        if not stack.getProperty(key, "enabled"):
            return False

        user_container = stack.getTop()
        """Also check if the top container is not a setting function (this happens if the inheritance is restored)."""

        if user_container and isinstance(user_container.getProperty(key, "value"), SettingFunction):
            return False

        ##  Mash all containers for all the stacks together.
        while stack:
            containers.extend(stack.getContainers())
            stack = stack.getNextStack()
        has_non_function_value = False
        for container in containers:
            try:
                value = container.getProperty(key, "value")
            except AttributeError:
                continue
            if value is not None:
                # If a setting doesn't use any keys, it won't change it's value, so treat it as if it's a fixed value
                has_setting_function = isinstance(value, SettingFunction)
                if has_setting_function:
                    for setting_key in value.getUsedSettingKeys():
                        if setting_key in all_keys:
                            break  # We found an actual setting. So has_setting_function can remain true
                    else:
                        # All of the setting_keys turned out to not be setting keys at all!
                        # This can happen due enum keys also being marked as settings.
                        has_setting_function = False

                if has_setting_function is False:
                    has_non_function_value = True
                    continue

            if has_setting_function:
                break  # There is a setting function somewhere, stop looking deeper.
        return has_setting_function and has_non_function_value
Example #2
0
    def _buildExtruderMessage(self, stack: ContainerStack) -> None:
        """Create extruder message from stack"""

        message = self._slice_message.addRepeatedMessage("extruders")
        message.id = int(stack.getMetaDataEntry("position"))
        if not self._all_extruders_settings:
            self._cacheAllExtruderSettings()

        if self._all_extruders_settings is None:
            return

        extruder_nr = stack.getProperty("extruder_nr", "value")
        settings = self._all_extruders_settings[str(extruder_nr)].copy()

        # Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
        settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")

        # Replace the setting tokens in start and end g-code.
        extruder_nr = stack.getProperty("extruder_nr", "value")
        settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], extruder_nr)
        settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], extruder_nr)

        global_definition = cast(ContainerInterface, cast(ContainerStack, stack.getNextStack()).getBottom())
        own_definition = cast(ContainerInterface, stack.getBottom())

        for key, value in settings.items():
            # Do not send settings that are not settable_per_extruder.
            # Since these can only be set in definition files, we only have to ask there.
            if not global_definition.getProperty(key, "settable_per_extruder") and \
                    not own_definition.getProperty(key, "settable_per_extruder"):
                    continue
            setting = message.getMessage("settings").addRepeatedMessage("settings")
            setting.name = key
            setting.value = str(value).encode("utf-8")
            Job.yieldThread()
Example #3
0
    def _buildExtruderMessage(self, stack: ContainerStack) -> None:
        message = self._arcus_message.addRepeatedMessage("extruders")
        message.id = int(stack.getMetaDataEntry("position"))

        settings = self._buildReplacementTokens(stack)

        # Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
        settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")

        # Replace the setting tokens in start and end g-code.
        extruder_nr = stack.getProperty("extruder_nr", "value")
        settings["machine_extruder_start_code"] = self._expandGcodeTokens(
            settings["machine_extruder_start_code"], extruder_nr)
        settings["machine_extruder_end_code"] = self._expandGcodeTokens(
            settings["machine_extruder_end_code"], extruder_nr)
        settings["machine_fiber_cut_code"] = self._expandGcodeTokens(
            settings["machine_fiber_cut_code"], extruder_nr)
        settings["machine_fiber_prime_code"] = self._expandGcodeTokens(
            settings["machine_fiber_prime_code"], extruder_nr)

        for key, value in settings.items():
            # Do not send settings that are not settable_per_extruder.
            if not stack.getProperty(key, "settable_per_extruder"):
                continue
            setting = message.getMessage("settings").addRepeatedMessage(
                "settings")
            setting.name = key
            setting.value = str(value).encode("utf-8")
            Job.yieldThread()
Example #4
0
    def _buildExtruderMessage(self, stack: ContainerStack) -> None:
        message = self._slice_message.addRepeatedMessage("extruders")
        message.id = int(stack.getMetaDataEntry("position"))
        if not self._all_extruders_settings:
            self._cacheAllExtruderSettings()

        if self._all_extruders_settings is None:
            return

        extruder_nr = stack.getProperty("extruder_nr", "value")
        settings = self._all_extruders_settings[str(extruder_nr)].copy()

        # Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
        settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")

        # Replace the setting tokens in start and end g-code.
        extruder_nr = stack.getProperty("extruder_nr", "value")
        settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], extruder_nr)
        settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], extruder_nr)

        for key, value in settings.items():
            # Do not send settings that are not settable_per_extruder.
            if not stack.getProperty(key, "settable_per_extruder"):
                continue
            setting = message.getMessage("settings").addRepeatedMessage("settings")
            setting.name = key
            setting.value = str(value).encode("utf-8")
            Job.yieldThread()
Example #5
0
    def _checkStackForErrors(self, stack: ContainerStack) -> bool:
        """Check if a stack has any errors."""

        """returns true if it has errors, false otherwise."""

        top_of_stack = cast(InstanceContainer, stack.getTop())  # Cache for efficiency.
        changed_setting_keys = top_of_stack.getAllKeys()

        # Add all relations to changed settings as well.
        for key in top_of_stack.getAllKeys():
            instance = top_of_stack.getInstance(key)
            if instance is None:
                continue
            self._addRelations(changed_setting_keys, instance.definition.relations)
            Job.yieldThread()

        for changed_setting_key in changed_setting_keys:
            if not stack.getProperty(changed_setting_key, "enabled"):
                continue

            validation_state = stack.getProperty(changed_setting_key, "validationState")

            if validation_state is None:
                definition = cast(SettingDefinition, stack.getSettingDefinition(changed_setting_key))
                validator_type = SettingDefinition.getValidatorForType(definition.type)
                if validator_type:
                    validator = validator_type(changed_setting_key)
                    validation_state = validator(stack)
            if validation_state in (
            ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
                Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", changed_setting_key, validation_state)
                return True
            Job.yieldThread()

        return False
Example #6
0
    def _settingIsOverwritingInheritance(self, key: str, stack: ContainerStack = None) -> bool:
        has_setting_function = False
        if not stack:
            stack = self._active_container_stack
        if not stack: #No active container stack yet!
            return False
        containers = []

        ## Check if the setting has a user state. If not, it is never overwritten.
        has_user_state = stack.getProperty(key, "state") == InstanceState.User
        if not has_user_state:
            return False

        ## If a setting is not enabled, don't label it as overwritten (It's never visible anyway).
        if not stack.getProperty(key, "enabled"):
            return False

        ## Also check if the top container is not a setting function (this happens if the inheritance is restored).
        if isinstance(stack.getTop().getProperty(key, "value"), SettingFunction):
            return False

        ##  Mash all containers for all the stacks together.
        while stack:
            containers.extend(stack.getContainers())
            stack = stack.getNextStack()
        has_non_function_value = False
        for container in containers:
            try:
                value = container.getProperty(key, "value")
            except AttributeError:
                continue
            if value is not None:
                # If a setting doesn't use any keys, it won't change it's value, so treat it as if it's a fixed value
                has_setting_function = isinstance(value, SettingFunction)
                if has_setting_function:
                    for setting_key in value.getUsedSettingKeys():
                        if setting_key in self._active_container_stack.getAllKeys():
                            break # We found an actual setting. So has_setting_function can remain true
                    else:
                        # All of the setting_keys turned out to not be setting keys at all!
                        # This can happen due enum keys also being marked as settings.
                        has_setting_function = False

                if has_setting_function is False:
                    has_non_function_value = True
                    continue

            if has_setting_function:
                break  # There is a setting function somewhere, stop looking deeper.
        return has_setting_function and has_non_function_value
Example #7
0
    def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
        result = {}
        for key in stack.getAllKeys():
            value = stack.getProperty(key, "value")
            result[key] = value
            Job.yieldThread()

        result["print_bed_temperature"] = result[
            "material_bed_temperature"]  # Renamed settings.
        result["print_bed_temperature_layer_0"] = result[
            "material_bed_temperature_layer_0"]  # Renamed settings.

        result["print_temperature"] = result["material_print_temperature"]
        result["print_chamber_temperature"] = result[
            "default_material_chamber_temperature"]
        result["travel_speed"] = result["speed_travel"]
        result["time"] = time.strftime("%H:%M:%S")  #Some extra settings.
        result["date"] = time.strftime("%d-%m-%Y")
        result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
                         "Sat"][int(time.strftime("%w"))]

        if (result["support_solubile"] == True):
            print("SUPPORT TRUE!!!!")
            result["support_bottom_distance"] = "0.0"
            result["support_top_distance"] = "0.0"

        initial_extruder_stack = CuraApplication.getInstance(
        ).getExtruderManager().getUsedExtruderStacks()[0]
        initial_extruder_nr = initial_extruder_stack.getProperty(
            "extruder_nr", "value")
        result["initial_extruder_nr"] = initial_extruder_nr

        return result
Example #8
0
    def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
        """Creates a dictionary of tokens to replace in g-code pieces.

        This indicates what should be replaced in the start and end g-codes.
        :param stack: The stack to get the settings from to replace the tokens with.
        :return: A dictionary of replacement tokens to the values they should be replaced with.
        """

        result = {}
        for key in stack.getAllKeys():
            result[key] = stack.getProperty(key, "value")
            Job.yieldThread()

        # Material identification in addition to non-human-readable GUID
        result["material_id"] = stack.material.getMetaDataEntry(
            "base_file", "")
        result["material_type"] = stack.material.getMetaDataEntry(
            "material", "")
        result["material_name"] = stack.material.getMetaDataEntry("name", "")
        result["material_brand"] = stack.material.getMetaDataEntry("brand", "")

        # Renamed settings.
        result["print_bed_temperature"] = result["material_bed_temperature"]
        result["print_temperature"] = result["material_print_temperature"]
        result["travel_speed"] = result["speed_travel"]

        #Some extra settings.
        result["time"] = time.strftime("%H:%M:%S")
        result["date"] = time.strftime("%d-%m-%Y")
        result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
                         "Sat"][int(time.strftime("%w"))]
        result["initial_extruder_nr"] = CuraApplication.getInstance(
        ).getExtruderManager().getInitialExtruderNr()

        return result
Example #9
0
    def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
        result = {}
        for key in stack.getAllKeys():
            value = stack.getProperty(key, "value")
            result[key] = value
            Job.yieldThread()

        result["print_bed_temperature"] = result[
            "material_bed_temperature"]  # Renamed settings.
        result["print_temperature"] = result["material_print_temperature"]
        result["time"] = time.strftime("%H:%M:%S")  # Some extra settings.
        result["date"] = time.strftime("%d-%m-%Y")
        result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
                         "Sat"][int(time.strftime("%w"))]
        printing_mode = result["printing_mode"]
        if printing_mode in ["cylindrical_full", "cylindrical"]:
            result["cylindrical_rotate"] = "G0 A%.2f" % (
                90 * result["machine_a_axis_multiplier"] /
                result["machine_a_axis_divider"])
            result["coordinate_system"] = "G56"
        elif printing_mode in ["spherical_full", "spherical"]:
            result["cylindrical_rotate"] = "G0 A0"
            result["coordinate_system"] = "G55"

        initial_extruder_stack = SteSlicerApplication.getInstance(
        ).getExtruderManager().getUsedExtruderStacks()[0]
        initial_extruder_nr = initial_extruder_stack.getProperty(
            "extruder_nr", "value")
        result["initial_extruder_nr"] = initial_extruder_nr

        return result
Example #10
0
 def _buildGlobalInheritsStackMessage(self, stack: ContainerStack) -> None:
     for key in stack.getAllKeys():
         extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
         if extruder_position >= 0:  # Set to a specific extruder.
             setting_extruder = self._slice_message.addRepeatedMessage("limit_to_extruder")
             setting_extruder.name = key
             setting_extruder.extruder = extruder_position
         Job.yieldThread()
Example #11
0
 def _buildGlobalInheritsStackMessage(self, stack: ContainerStack) -> None:
     for key in stack.getAllKeys():
         extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
         if extruder_position >= 0:  # Set to a specific extruder.
             setting_extruder = self._slice_message.addRepeatedMessage("limit_to_extruder")
             setting_extruder.name = key
             setting_extruder.extruder = extruder_position
         Job.yieldThread()
Example #12
0
    def _checkStackForErrors(self, stack: ContainerStack) -> bool:
        if stack is None:
            return False

        for key in stack.getAllKeys():
            validation_state = stack.getProperty(key, "validationState")
            if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
                Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", key, validation_state)
                return True
            Job.yieldThread()
        return False
Example #13
0
    def _checkStackForErrors(self, stack: ContainerStack) -> bool:
        if stack is None:
            return False

        for key in stack.getAllKeys():
            validation_state = stack.getProperty(key, "validationState")
            if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
                Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", key, validation_state)
                return True
            Job.yieldThread()
        return False
Example #14
0
    def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
        result = {}
        for key in stack.getAllKeys():
            value = stack.getProperty(key, "value")
            result[key] = value
            Job.yieldThread()

        result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
        result["print_temperature"] = result["material_print_temperature"]
        result["travel_speed"] = result["speed_travel"]
        result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
        result["date"] = time.strftime("%d-%m-%Y")
        result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
        result["initial_extruder_nr"] = CuraApplication.getInstance().getExtruderManager().getInitialExtruderNr()

        return result
Example #15
0
    def _checkStackForErrors(self, stack: ContainerStack) -> bool:
        if stack is None:
            return False

        # if there are no per-object settings we don't need to check the other settings here
        stack_top = stack.getTop()
        if stack_top is None or not stack_top.getAllKeys():
            return False

        for key in stack.getAllKeys():
            validation_state = stack.getProperty(key, "validationState")
            if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
                Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", key, validation_state)
                return True
            Job.yieldThread()
        return False
Example #16
0
    def _buildGlobalInheritsStackMessage(self, stack: ContainerStack) -> None:
        """Sends for some settings which extruder they should fallback to if not set.

        This is only set for settings that have the limit_to_extruder
        property.

        :param stack: The global stack with all settings, from which to read the
            limit_to_extruder property.
        """

        for key in stack.getAllKeys():
            extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
            if extruder_position >= 0:  # Set to a specific extruder.
                setting_extruder = self._slice_message.addRepeatedMessage("limit_to_extruder")
                setting_extruder.name = key
                setting_extruder.extruder = extruder_position
            Job.yieldThread()
Example #17
0
    def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
        result = {}
        for key in stack.getAllKeys():
            value = stack.getProperty(key, "value")
            result[key] = value
            Job.yieldThread()

        result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
        result["print_temperature"] = result["material_print_temperature"]
        result["travel_speed"] = result["speed_travel"]
        result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
        result["date"] = time.strftime("%d-%m-%Y")
        result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]

        initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
        initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
        result["initial_extruder_nr"] = initial_extruder_nr

        return result
Example #18
0
class Script:
    def __init__(self) -> None:
        super().__init__()
        self._stack = None  # type: Optional[ContainerStack]
        self._definition = None  # type: Optional[DefinitionContainerInterface]
        self._instance = None  # type: Optional[InstanceContainer]

    def initialize(self) -> None:
        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id=str(id(self)))
        self._stack.setDirty(False)  # This stack does not need to be saved.

        #  Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance(
            ).findDefinitionContainers(id=setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                try:
                    self._definition.deserialize(json.dumps(setting_data))
                    ContainerRegistry.getInstance().addContainer(
                        self._definition)
                except ContainerFormatError:
                    self._definition = None
                    return
        if self._definition is None:
            return
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(
            container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.setMetaDataEntry(
            "setting_version",
            self._definition.getMetaDataEntry("setting_version", default=0))
        self._stack.addContainer(self._instance)
        self._stack.propertyChanged.connect(self._onPropertyChanged)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()
    valueChanged = Signal(
    )  # Signal emitted whenever a value of a setting is changed

    def _onPropertyChanged(self, key: str, property_name: str) -> None:
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Re-slicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance(
            ).getGlobalContainerStack()
            if global_container_stack is not None:
                global_container_stack.propertyChanged.emit(key, property_name)

    #   Needs to return a dict that can be used to construct a settingcategory file.
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    #   Scripts can either override getSettingData directly, or use getSettingDataString
    #   to return a string that will be parsed as json. The latter has the benefit over
    #   returning a dict in that the order of settings is maintained.
    def getSettingData(self) -> Dict[str, Any]:
        setting_data_as_string = self.getSettingDataString()
        setting_data = json.loads(setting_data_as_string,
                                  object_pairs_hook=collections.OrderedDict)
        return setting_data

    def getSettingDataString(self) -> str:
        raise NotImplementedError()

    def getDefinitionId(self) -> Optional[str]:
        if self._stack:
            bottom = self._stack.getBottom()
            if bottom is not None:
                return bottom.getId()
        return None

    def getStackId(self) -> Optional[str]:
        if self._stack:
            return self._stack.getId()
        return None

    #   Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key: str) -> Any:
        if self._stack is not None:
            return self._stack.getProperty(key, "value")
        return None

    #   Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line: str, key: str, default=None) -> Any:
        if key not in line or (';' in line
                               and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return int(m.group(0))
        except ValueError:  # Not an integer.
            try:
                return float(m.group(0))
            except ValueError:  # Not a number at all.
                return default

    #   Convenience function to produce a line of g-code.
    #
    #   You can put in an original g-code line and it will re-use all the values in that line.
    #   All other keyword parameters are put in the result in g-code's format.
    #   For instance, if you put "G=1" in the parameters, it will output "G1".
    #   If you put "G=1", X=100" in the parameters, it will output "G1 X100".
    #   The parameters will be put in this order "G", "M", "T", "S", "F", "X", "Y", "Z", "E"
    #   followed by any other parameters
    #   \param line The original g-code line that must be modified. If not provided, an entirely new g-code line will be produced.
    #   \return A line of g-code with the desired parameters filled in.
    def putValue(self, line: str = "", **kwargs) -> str:
        # Strip the comment.
        comment = ""
        if ";" in line:
            comment = line[line.find(";"):]
            line = line[:line.find(";")]  # Strip the comment.

        # Parse the original g-code line and add them to kwargs.
        for part in line.split(" "):
            if part == "":
                continue
            parameter = part[0]
            if parameter not in kwargs:
                value = part[1:]
                kwargs[parameter] = value

        # Start writing the new g-code line.
        line_parts = list()
        # First add these parameters in order
        for parameter in ["G", "M", "T", "S", "F", "X", "Y", "Z", "E"]:
            if parameter in kwargs:
                line_parts.append(parameter + str(kwargs.pop(parameter)))
        # Then add the rest of the parameters
        for parameter, value in kwargs.items():
            line_parts.append(parameter + str(value))

        # If there was a comment, put it back in.
        if comment != "":
            line_parts.append(comment)

        # Construct the new line
        return " ".join(line_parts)

    #   This is called when the script is executed.
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data: List[str]) -> List[str]:
        raise NotImplementedError()
Example #19
0
class Script:
    def __init__(self) -> None:
        super().__init__()
        self._stack = None  # type: Optional[ContainerStack]
        self._definition = None  # type: Optional[DefinitionContainerInterface]
        self._instance = None  # type: Optional[InstanceContainer]

    def initialize(self) -> None:
        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id=str(id(self)))
        self._stack.setDirty(False)  # This stack does not need to be saved.

        ## Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance().findDefinitionContainers(id=setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                try:
                    self._definition.deserialize(json.dumps(setting_data))
                    ContainerRegistry.getInstance().addContainer(self._definition)
                except ContainerFormatError:
                    self._definition = None
                    return
        if self._definition is None:
            return
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.setMetaDataEntry("setting_version",
                                        self._definition.getMetaDataEntry("setting_version", default=0))
        self._stack.addContainer(self._instance)
        self._stack.propertyChanged.connect(self._onPropertyChanged)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()
    valueChanged = Signal()  # Signal emitted whenever a value of a setting is changed

    def _onPropertyChanged(self, key: str, property_name: str) -> None:
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Re-slicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance().getGlobalContainerStack()
            if global_container_stack is not None:
                global_container_stack.propertyChanged.emit(key, property_name)

    ##  Needs to return a dict that can be used to construct a settingcategory file.
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    #   Scripts can either override getSettingData directly, or use getSettingDataString
    #   to return a string that will be parsed as json. The latter has the benefit over
    #   returning a dict in that the order of settings is maintained.
    def getSettingData(self) -> Dict[str, Any]:
        setting_data_as_string = self.getSettingDataString()
        setting_data = json.loads(setting_data_as_string, object_pairs_hook = collections.OrderedDict)
        return setting_data

    def getSettingDataString(self) -> str:
        raise NotImplementedError()

    def getDefinitionId(self) -> Optional[str]:
        if self._stack:
            bottom = self._stack.getBottom()
            if bottom is not None:
                return bottom.getId()
        return None

    def getStackId(self) -> Optional[str]:
        if self._stack:
            return self._stack.getId()
        return None

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key: str) -> Any:
        if self._stack is not None:
            return self._stack.getProperty(key, "value")
        return None

    ##  Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line: str, key: str, default = None) -> Any:
        if not key in line or (';' in line and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return int(m.group(0))
        except ValueError: #Not an integer.
            try:
                return float(m.group(0))
            except ValueError: #Not a number at all.
                return default

    ##  Convenience function to produce a line of g-code.
    #
    #   You can put in an original g-code line and it'll re-use all the values
    #   in that line.
    #   All other keyword parameters are put in the result in g-code's format.
    #   For instance, if you put ``G=1`` in the parameters, it will output
    #   ``G1``. If you put ``G=1, X=100`` in the parameters, it will output
    #   ``G1 X100``. The parameters G and M will always be put first. The
    #   parameters T and S will be put second (or first if there is no G or M).
    #   The rest of the parameters will be put in arbitrary order.
    #   \param line The original g-code line that must be modified. If not
    #   provided, an entirely new g-code line will be produced.
    #   \return A line of g-code with the desired parameters filled in.
    def putValue(self, line: str = "", **kwargs) -> str:
        #Strip the comment.
        comment = ""
        if ";" in line:
            comment = line[line.find(";"):]
            line = line[:line.find(";")] #Strip the comment.

        #Parse the original g-code line.
        for part in line.split(" "):
            if part == "":
                continue
            parameter = part[0]
            if parameter in kwargs:
                continue #Skip this one. The user-provided parameter overwrites the one in the line.
            value = part[1:]
            kwargs[parameter] = value

        #Write the new g-code line.
        result = ""
        priority_parameters = ["G", "M", "T", "S", "F", "X", "Y", "Z", "E"] #First some parameters that get priority. In order of priority!
        for priority_key in priority_parameters:
            if priority_key in kwargs:
                if result != "":
                    result += " "
                result += priority_key + str(kwargs[priority_key])
                del kwargs[priority_key]
        for key, value in kwargs.items():
            if result != "":
                result += " "
            result += key + str(value)

        #Put the comment back in.
        if comment != "":
            if result != "":
                result += " "
            result += ";" + comment

        return result

    ##  This is called when the script is executed. 
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data: List[str]) -> List[str]:
        raise NotImplementedError()
Example #20
0
class Script:
    def __init__(self):
        super().__init__()
        self._settings = None
        self._stack = None

        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id=str(id(self)))
        self._stack.setDirty(False)  # This stack does not need to be saved.

        ## Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance(
            ).findDefinitionContainers(id=setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                try:
                    self._definition.deserialize(json.dumps(setting_data))
                    ContainerRegistry.getInstance().addContainer(
                        self._definition)
                except ContainerFormatError:
                    self._definition = None
                    return
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(
            container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.addMetaDataEntry(
            "setting_version",
            self._definition.getMetaDataEntry("setting_version", default=0))
        self._stack.addContainer(self._instance)
        self._stack.propertyChanged.connect(self._onPropertyChanged)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()
    valueChanged = Signal(
    )  # Signal emitted whenever a value of a setting is changed

    def _onPropertyChanged(self, key, property_name):
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Reslicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance(
            ).getGlobalContainerStack()
            global_container_stack.propertyChanged.emit(key, property_name)

    ##  Needs to return a dict that can be used to construct a settingcategory file.
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    #   Scripts can either override getSettingData directly, or use getSettingDataString
    #   to return a string that will be parsed as json. The latter has the benefit over
    #   returning a dict in that the order of settings is maintained.
    def getSettingData(self):
        setting_data = self.getSettingDataString()
        if type(setting_data) == str:
            setting_data = json.loads(
                setting_data, object_pairs_hook=collections.OrderedDict)
        return setting_data

    def getSettingDataString(self):
        raise NotImplementedError()

    def getDefinitionId(self):
        if self._stack:
            return self._stack.getBottom().getId()

    def getStackId(self):
        if self._stack:
            return self._stack.getId()

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key):
        return self._stack.getProperty(key, "value")

    ##  Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line, key, default=None):
        if not key in line or (';' in line
                               and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return float(m.group(0))
        except:
            return default

    ##  Convenience function to produce a line of g-code.
    #
    #   You can put in an original g-code line and it'll re-use all the values
    #   in that line.
    #   All other keyword parameters are put in the result in g-code's format.
    #   For instance, if you put ``G=1`` in the parameters, it will output
    #   ``G1``. If you put ``G=1, X=100`` in the parameters, it will output
    #   ``G1 X100``. The parameters G and M will always be put first. The
    #   parameters T and S will be put second (or first if there is no G or M).
    #   The rest of the parameters will be put in arbitrary order.
    #   \param line The original g-code line that must be modified. If not
    #   provided, an entirely new g-code line will be produced.
    #   \return A line of g-code with the desired parameters filled in.
    def putValue(self, line="", **kwargs):
        #Strip the comment.
        comment = ""
        if ";" in line:
            comment = line[line.find(";"):]
            line = line[:line.find(";")]  #Strip the comment.

        #Parse the original g-code line.
        for part in line.split(" "):
            if part == "":
                continue
            parameter = part[0]
            if parameter in kwargs:
                continue  #Skip this one. The user-provided parameter overwrites the one in the line.
            value = part[1:]
            kwargs[parameter] = value

        #Write the new g-code line.
        result = ""
        priority_parameters = [
            "G", "M", "T", "S", "F", "X", "Y", "Z", "E"
        ]  #First some parameters that get priority. In order of priority!
        for priority_key in priority_parameters:
            if priority_key in kwargs:
                if result != "":
                    result += " "
                result += priority_key + str(kwargs[priority_key])
                del kwargs[priority_key]
        for key, value in kwargs.items():
            if result != "":
                result += " "
            result += key + str(value)

        #Put the comment back in.
        if comment != "":
            if result != "":
                result += " "
            result += ";" + comment

        return result

    ##  This is called when the script is executed.
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data):
        raise NotImplementedError()
Example #21
0
class CuraSettingsGuide(Extension, QObject):
	"""
	The main manager and entry point for the Cura Settings Guide.

	This adds a menu item to the extensions menu and to the context menu of
	every article. It creates a dialogue window to display the guide in, and
	makes the menu items open said dialogue window.

	This class is also exposed to the QML code, which can use it to request the
	articles and metadata about the article.
	"""

	def __init__(self, parent=None) -> None:
		"""
		Executed during registration of the plug-in.

		This adds a menu item to the extensions menu and the context menu of the
		settings.
		:param parent: The parent QObject this is located in. Unused by this
		particular object.
		"""
		QObject.__init__(self, parent)
		Extension.__init__(self)

		self.setMenuName("Ѕettings Guide")  # Using Cyrillic Ѕ instead of normal S to prevent MacOS detecting the word "setting" and pulling this menu item out of context.
		self.addMenuItem("Ѕettings Guide", self.startWelcomeGuide)
		self._dialog = None  # Cached instance of the dialogue window.
		self._container_stack = None  # Stack that provides not only the normal settings but also the extra articles added by this guide.

		self._markdown_per_folder = {}  # For each directory containing Markdown files, create one renderer that correctly dereferences images relatively.

		self.articles = {}  # type: Dict[str, Dict[str, List[List[str]]]]  # All of the articles by key and language. Key: article ID, value: Dict with language as key and lists of items in each article as value.
		self.load_definitions()
		self.article_locations = self.find_articles()
		self._selected_article_id = ""  # Which article is currently shown for the user. Empty string indicates it's the welcome screen.

		# Add context menu item to the settings list to open the guide for that setting.
		application = CuraApplication.getInstance()
		self._selected_language = application.getPreferences().getValue("general/language")
		application.getCuraAPI().interface.settings.addContextMenuItem({
			"name": "Settings Guide",
			"icon_name": "help-contents",
			"actions": ["sidebarMenuItemOnClickHandler"],
			"menu_item": MenuItemHandler.MenuItemHandler(self)
		})

		application.getPreferences().addPreference("settings_guide/show+articles+in+setting+tooltips+%28requires+restart%29", False)

		self.adjust_theme()
		application.initializationFinished.connect(self.load_all_in_background)

	def adjust_theme(self):
		"""
		Makes the tooltips wider, if displaying articles in the tooltips.
		"""
		application = CuraApplication.getInstance()
		preferences = application.getPreferences()
		if preferences.getValue("settings_guide/show+articles+in+setting+tooltips+%28requires+restart%29"):
			preferences.addPreference("general/theme", application.default_theme)
			theme_name = preferences.getValue("general/theme")
			if theme_name.endswith("_settingsguideadjust"):
				return  # Already adjusted.
			theme_path = Resources.getPath(Resources.Themes, theme_name)

			# Create a new theme where we can adjust the tooltip.
			new_theme_name = theme_name + "_settingsguideadjust"
			new_theme_path = Resources.getStoragePath(Resources.Themes, new_theme_name)
			try:
				shutil.copytree(theme_path, new_theme_path)
			except OSError:  # Already exists. Happens when the user manually adjusted the theme back in the preferences screen.
				try:
					os.removedirs(new_theme_path)
					shutil.copytree(theme_path, new_theme_path)
				except OSError:
					pass  # Perhaps no rights?

			# Adjust the tooltip width.
			with open(os.path.join(theme_path, "theme.json")) as f:
				adjusted_theme = json.load(f)
			if "sizes" not in adjusted_theme:
				adjusted_theme["sizes"] = {}
			adjusted_theme["sizes"]["tooltip"] = [50.0, 10.0]
			with open(os.path.join(new_theme_path, "theme.json"), "w") as f:
				json.dump(adjusted_theme, f)

			# Enable the new theme.
			preferences.setValue("general/theme", new_theme_name)
			application.default_theme = new_theme_name

	def load_all_in_background(self):
		"""
		Runs the load_all() function as a background task.
		"""
		class ArticleLoadJob(Job):
			"""
			A background task that loads all articles of the guide.
			"""

			def __init__(self, guide):
				"""
				Creates the background task.
				:param guide: The CuraSettingsGuide object which has the
				function to call.
				"""
				self.guide = guide

			def run(self):
				"""
				Runs the background task.

				Cura will call this function from a different thread.
				"""
				self.guide.load_all()
		JobQueue.getInstance().add(ArticleLoadJob(self))


	def load_all(self):
		"""
		Pre-cache all articles.

		This is meant to run as a background task. This makes sure all setting
		descriptions are loaded so that they can load faster next time the
		article is requested. It also makes sure that the setting description
		is correctly displayed (after a while).
		"""
		for article_id in self._container_stack.getAllKeys():
			self._getArticle(article_id)  # Load articles one by one.
		Logger.log("i", "Finished loading Settings Guide articles.")

	def load_definitions(self):
		"""
		Load all the setting definitions into a custom container stack.

		This container stack also contains extra entries for the articles that
		are not settings.

		The result is stored in self._container_stack.
		"""
		if self._container_stack:
			return  # Already done earlier. Don't re-load.
		with open(os.path.join(os.path.dirname(__file__), "resources", "settings_guide_definitions.def.json")) as f:
			definitions_serialised = f.read()
		definition_container = DefinitionContainer("settings_guide_definitions")
		definition_container.deserialize(definitions_serialised)
		ContainerRegistry.getInstance().addContainer(definition_container)
		self._container_stack = ContainerStack("settings_guide_stack")
		self._container_stack.addContainer(definition_container)
		ContainerRegistry.getInstance().addContainer(self._container_stack)

	def find_articles(self):
		"""
		For each article and language, find where the Markdown file is
		located.
		:return: A nested dictionary mapping article ID to language to file
		path.
		"""
		result = {}
		# Find the English translations first.
		for root, _, files in os.walk(os.path.join(os.path.dirname(__file__), "resources", "articles")):
			for filename in files:
				base_name, extension = os.path.splitext(filename)
				if extension != ".md":
					continue  # Only interested in article files.
				result[base_name] = {"en_US": os.path.join(root, filename)}

		# Find the translated articles in the translations folder.
		for language in os.listdir(os.path.join(os.path.dirname(__file__), "resources", "translations")):
			language_path = os.path.join(os.path.dirname(__file__), "resources", "translations", language)
			if not os.path.isdir(language_path):
				continue  # Not a translation folder.
			for root, _, files in os.walk(language_path):
				for filename in files:
					base_name, extension = os.path.splitext(filename)
					if extension != ".md":
						continue  # Only interested in article files.
					if base_name not in result:
						continue  # Translation for an article that doesn't exist in English?
					result[base_name][language] = os.path.join(root, filename)

		return result

	def load_window(self):
		"""
		Create the GUI window for the guide.

		The window's QML element is stored in a field. There can only be one
		instance at a time.
		"""
		self.load_definitions()

		if self._dialog:
			return  # Dialogue already open.
		Logger.log("d", "Creating Settings Guide window.")
		plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
		if plugin_path is None:
			plugin_path = os.path.dirname(__file__)
		path = os.path.join(plugin_path, "resources", "qml", "SettingsGuide.qml")
		self._dialog = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
		if not self._dialog:
			Logger.log("e", "Unable to create settings guide dialogue.")

	def startWelcomeGuide(self) -> None:
		"""
		Opens the guide in the welcome page.
		"""
		self.load_window()
		if not self._dialog:
			return
		self.setSelectedArticleId("")  # Display welcome page.
		self._dialog.show()

	def startWelcomeGuideAndSelectArticle(self, article_key: str) -> None:
		"""
		Opens the guide and immediately selects a certain article.
		:param article_key: The key of the article to show the guide of.
		"""
		self.load_window()
		if not self._dialog:
			return
		self.currentArticleReset.emit()
		self.setSelectedArticleId(article_key)
		self._dialog.show()

	currentArticleReset = pyqtSignal() #Signal to indicate that the article list should reset its current index.

	def _getArticle(self, article_id, language="en_US") -> List[List[str]]:
		"""
		Gets the rich text of a specified article.

		This function lazily loads an article from a file. If it's never been
		loaded before the article gets parsed and stored. Otherwise it'll get
		taken from the cache.
		:param article_id: The ID of the article to get.
		:param language: The language to get the article in.
		"""
		if article_id in self.articles and language in self.articles[article_id]:
			return self.articles[article_id][language]

		images_path = os.path.join(os.path.dirname(__file__), "resources", "articles", "images")
		try:
			if language not in self.article_locations[article_id]:
				language = "en_US"  # Fall back to English if the preferred language is not available.
			markdown_file = self.article_locations[article_id][language]
			with open(markdown_file, encoding="utf-8") as f:
				markdown_str = f.read()
			images_path = os.path.dirname(markdown_file)
		except (OSError, KeyError):  # File doesn't exist or is otherwise not readable.
			if self._container_stack and article_id in self._container_stack.getAllKeys():
				markdown_str = "*" + self._container_stack.getProperty(article_id, "description") + "*"  # Use the setting description as fallback.
			else:
				markdown_str = "There is no article on this topic."

		if images_path not in self._markdown_per_folder:
			renderer = QtMarkdownRenderer.QtMarkdownRenderer(images_path)
			self._markdown_per_folder[images_path] = mistune.Markdown(renderer=renderer)  # Renders the Markdown articles into the subset of HTML supported by Qt.

		preferences = CuraApplication.getInstance().getPreferences()
		find_images = re.compile(r"!\[(.*)\]\(([^\)]+)\)")
		find_checkboxes = re.compile(r"\[ \]\s*([^\n]+)")
		image_description = None
		parts = []  # type: List[List[str]]  # List of items in the article. Each item starts with a type ID, and then a variable number of data items.
		for index, part_between_images in enumerate(find_images.split(markdown_str)):
			# The parts of the regex split alternate between text, image description and image URL.
			if index % 3 == 0:
				part_between_images = part_between_images.strip()
				if part_between_images or index == 0:
					parts_between_checkboxes = find_checkboxes.split(part_between_images)
					for index2, part_between_checkboxes in enumerate(parts_between_checkboxes):
						part_between_checkboxes = part_between_checkboxes.strip()
						# The parts of the regex split alternate between text and checkbox description.
						if index2 % 2 == 0:
							if part_between_checkboxes:
								rich_text = self._markdown_per_folder[images_path](part_between_checkboxes)
								parts.append(["rich_text", rich_text])
						else:  # if index2 == 1:
							preference_key = "settings_guide/" + urllib.parse.quote_plus(part_between_checkboxes).lower()
							parts.append(["checkbox", preference_key, part_between_checkboxes])
			elif index % 3 == 1:
				image_description = mistune.markdown(part_between_images)
			else:  # if index % 3 == 2:
				if image_description is not None:
					if parts[-1][0] != "images":  # List of images.
						parts.append(["images"])
					image_url = os.path.join(images_path, part_between_images)
					parts[-1].append(QUrl.fromLocalFile(image_url).toString() + "|" + image_description)
					image_description = None

		if article_id not in self.articles:
			self.articles[article_id] = {}
		self.articles[article_id][language] = parts

		if preferences.getValue("settings_guide/show+articles+in+setting+tooltips+%28requires+restart%29"):
			# Load the article into the actual setting description as well.
			global_stack = CuraApplication.getInstance().getGlobalContainerStack()
			if global_stack and article_id in global_stack.getAllKeys():
				complete_article = self._markdown_per_folder[images_path](markdown_str)
				definition = global_stack.definition.findDefinitions(key=article_id)[0]
				definition._SettingDefinition__property_values["description"] = complete_article

		return self.articles[article_id][language]

	@pyqtSlot(str, result=bool)
	def isArticleFile(self, filename: str) -> bool:
		"""
		Tests whether a file name is the file name of an existing article.

		This test is used to determine if a link should refer to a different
		article or to the internet.
		:param filename: The file name to test for.
		:return: True if the file name is the file name of an existing article,
		or False if it isn't.
		"""
		return os.path.exists(filename)

	@pyqtProperty(QObject, constant=True)
	def containerStack(self) -> Optional[ContainerStack]:
		"""
		The specialised container stack containing setting definitions for all
		of the articles in the guide.
		:return: A container stack with extra definitions for all articles in
		the guide.
		"""
		return self._container_stack

	@pyqtProperty(str, constant=True)
	def pluginVersion(self) -> str:
		"""
		Get the version number of this plug-in.

		This plug-in version is a semantic version number in three parts
		("1.2.3").
		:return: The version number of this plug-in.
		"""
		return self.getVersion()

	selectedArticleChanged = pyqtSignal()

	@pyqtSlot(str)
	def setSelectedArticleId(self, article_key: str) -> None:
		"""
		Show a certain article in the dialogue.
		:param article_key: The key of the article to display.
		"""
		if self._selected_article_id != article_key:
			self._selected_article_id = article_key
			self.selectedArticleChanged.emit()

	@pyqtProperty(str, fset=setSelectedArticleId, notify=selectedArticleChanged)
	def selectedArticleId(self) -> str:
		"""
		Returns the key of the article that is currently selected.

		If no article has been selected (the logo is being shown), an empty
		string is returned.
		:return: The key of the article that is currently selected.
		"""
		return self._selected_article_id

	@pyqtProperty("QVariantList", notify=selectedArticleChanged)
	def selectedArticle(self) -> List[List[str]]:
		"""
		Returns the currently selected article.

		This article data is a rich text document, properly formatted from the
		Markdown files in the article directory. Each article is a list of
		items, some of which are text and some of which are image lists.
		:return: The the currently selected article.
		"""
		return self._getArticle(self._selected_article_id, self._selected_language)
Example #22
0
class Script:
    def __init__(self):
        super().__init__()
        self._settings = None
        self._stack = None

        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id=id(self))
        self._stack.setDirty(False)  # This stack does not need to be saved.


        ## Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                self._definition.deserialize(json.dumps(setting_data))
                ContainerRegistry.getInstance().addContainer(self._definition)
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition)
        self._stack.addContainer(self._instance)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()

    ##  Needs to return a dict that can be used to construct a settingcategory file. 
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    def getSettingData(self):
        raise NotImplementedError()

    def getDefinitionId(self):
        if self._stack:
            return self._stack.getBottom().getId()

    def getStackId(self):
        if self._stack:
            return self._stack.getId()

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key):
        return self._stack.getProperty(key, "value")

    ##  Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line, key, default = None):
        if not key in line or (';' in line and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return float(m.group(0))
        except:
            return default

    ##  This is called when the script is executed. 
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data):
        raise NotImplementedError()
Example #23
0
class Script:
    def __init__(self):
        super().__init__()
        self._settings = None
        self._stack = None

        setting_data = self.getSettingData()
        self._stack = ContainerStack(stack_id = str(id(self)))
        self._stack.setDirty(False)  # This stack does not need to be saved.


        ## Check if the definition of this script already exists. If not, add it to the registry.
        if "key" in setting_data:
            definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"])
            if definitions:
                # Definition was found
                self._definition = definitions[0]
            else:
                self._definition = DefinitionContainer(setting_data["key"])
                self._definition.deserialize(json.dumps(setting_data))
                ContainerRegistry.getInstance().addContainer(self._definition)
        self._stack.addContainer(self._definition)
        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
        self._instance.setDefinition(self._definition.getId())
        self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0))
        self._stack.addContainer(self._instance)
        self._stack.propertyChanged.connect(self._onPropertyChanged)

        ContainerRegistry.getInstance().addContainer(self._stack)

    settingsLoaded = Signal()
    valueChanged = Signal()  # Signal emitted whenever a value of a setting is changed

    def _onPropertyChanged(self, key, property_name):
        if property_name == "value":
            self.valueChanged.emit()

            # Property changed: trigger reslice
            # To do this we use the global container stack propertyChanged.
            # Reslicing is necessary for setting changes in this plugin, because the changes
            # are applied only once per "fresh" gcode
            global_container_stack = Application.getInstance().getGlobalContainerStack()
            global_container_stack.propertyChanged.emit(key, property_name)

    ##  Needs to return a dict that can be used to construct a settingcategory file.
    #   See the example script for an example.
    #   It follows the same style / guides as the Uranium settings.
    #   Scripts can either override getSettingData directly, or use getSettingDataString
    #   to return a string that will be parsed as json. The latter has the benefit over
    #   returning a dict in that the order of settings is maintained.
    def getSettingData(self):
        setting_data = self.getSettingDataString()
        if type(setting_data) == str:
            setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict)
        return setting_data

    def getSettingDataString(self):
        raise NotImplementedError()

    def getDefinitionId(self):
        if self._stack:
            return self._stack.getBottom().getId()

    def getStackId(self):
        if self._stack:
            return self._stack.getId()

    ##  Convenience function that retrieves value of a setting from the stack.
    def getSettingValueByKey(self, key):
        return self._stack.getProperty(key, "value")

    ##  Convenience function that finds the value in a line of g-code.
    #   When requesting key = x from line "G1 X100" the value 100 is returned.
    def getValue(self, line, key, default = None):
        if not key in line or (';' in line and line.find(key) > line.find(';')):
            return default
        sub_part = line[line.find(key) + 1:]
        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
        if m is None:
            return default
        try:
            return float(m.group(0))
        except:
            return default

    ##  This is called when the script is executed. 
    #   It gets a list of g-code strings and needs to return a (modified) list.
    def execute(self, data):
        raise NotImplementedError()