Merge branch 'main' into improve_oauth

This commit is contained in:
Erwan MATHIEU 2024-01-10 08:45:17 +01:00 committed by GitHub
commit a5e158de9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1483 additions and 312 deletions

View File

@ -20,12 +20,8 @@ on:
- 'main'
- 'CURA-*'
- 'PP-*'
- '[0-9].[0-9]'
- '[0-9].[0-9][0-9]'
tags:
- '[0-9].[0-9].[0-9]*'
- '[0-9].[0-9].[0-9]'
- '[0-9].[0-9][0-9].[0-9]*'
- '[0-9].[0-9]*'
- '[0-9].[0-9][0-9]*'
env:
CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }}
@ -44,3 +40,11 @@ jobs:
recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }}
recipe_id_latest: ${{ needs.conan-recipe-version.outputs.recipe_id_latest }}
secrets: inherit
conan-package-create:
needs: [ conan-recipe-version, conan-package-export ]
uses: ultimaker/cura-workflows/.github/workflows/conan-package-create-linux.yml@main
with:
recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }}
conan_extra_args: "-o cura:enable_i18n=True"
secrets: inherit

View File

@ -39,19 +39,14 @@ on:
options:
- ubuntu-22.04
env:
CONAN_ARGS: ${{ inputs.conan_args || '' }}
ENTERPRISE: ${{ inputs.enterprise || false }}
STAGING: ${{ inputs.staging || false }}
jobs:
installer:
linux-installer:
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-linux.yml@main
with:
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
enterprise: ${{ inputs.enterprise == 'true' }}
staging: ${{ inputs.staging == 'true' }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
architecture: ${{ inputs.architecture }}
operating_system: ${{ inputs.operating_system }}
secrets: inherit

View File

@ -43,19 +43,14 @@ on:
- macos-11
- macos-12
env:
CONAN_ARGS: ${{ inputs.conan_args || '' }}
ENTERPRISE: ${{ inputs.enterprise || false }}
STAGING: ${{ inputs.staging || false }}
jobs:
installer:
macos-installer:
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-macos.yml@main
with:
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
enterprise: ${{ inputs.enterprise == 'true' }}
staging: ${{ inputs.staging == 'true' }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
architecture: ${{ inputs.architecture }}
operating_system: ${{ inputs.operating_system }}
secrets: inherit

View File

@ -11,3 +11,4 @@ jobs:
with:
event: ${{ github.event.workflow_run.event }}
conclusion: ${{ github.event.workflow_run.conclusion }}
secrets: inherit

View File

@ -58,4 +58,5 @@ jobs:
conan_extra_args: '-g VirtualPythonEnv -o cura:devtools=True -c tools.build:skip_test=False'
unit_test_cmd: 'pytest --junitxml=junit_cura.xml'
unit_test_dir: 'tests'
conan_generator_dir: './venv/bin'
conan_generator_dir: './venv/bin'
secrets: inherit

View File

@ -39,19 +39,14 @@ on:
options:
- windows-2022
env:
CONAN_ARGS: ${{ inputs.conan_args || '' }}
ENTERPRISE: ${{ inputs.enterprise || false }}
STAGING: ${{ inputs.staging || false }}
jobs:
installer:
windows-installer:
uses: ultimaker/cura-workflows/.github/workflows/cura-installer-windows.yml@main
with:
cura_conan_version: ${{ inputs.cura_conan_version }}
conan_args: ${{ inputs.conan_args }}
enterprise: ${{ inputs.enterprise == 'true' }}
staging: ${{ inputs.staging == 'true' }}
enterprise: ${{ inputs.enterprise }}
staging: ${{ inputs.staging }}
architecture: ${{ inputs.architecture }}
operating_system: ${{ inputs.operating_system }}
secrets: inherit

View File

@ -266,6 +266,10 @@ app = UMBUNDLE(
'CFBundlePackageType': 'APPL',
'CFBundleVersionString': {{ version }},
'CFBundleShortVersionString': {{ short_version }},
'CFBundleURLTypes': [{
'CFBundleURLName': '{{ display_name }}',
'CFBundleURLSchemes': ['cura', 'slicer'],
}],
'CFBundleDocumentTypes': [{
'CFBundleTypeRole': 'Viewer',
'CFBundleTypeExtensions': ['*'],

View File

@ -2,15 +2,18 @@
# Cura is released under the terms of the LGPLv3 or higher.
import enum
import os
import re
import sys
import tempfile
import time
import platform
from pathlib import Path
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
import requests
import numpy
from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication
from PyQt6.QtCore import QObject, QTimer, QUrl, QUrlQuery, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication, \
QByteArray
from PyQt6.QtGui import QColor, QIcon
from PyQt6.QtQml import qmlRegisterUncreatableType, qmlRegisterUncreatableMetaObject, qmlRegisterSingletonType, qmlRegisterType
from PyQt6.QtWidgets import QMessageBox
@ -250,7 +253,7 @@ class CuraApplication(QtApplication):
self._additional_components = {} # Components to add to certain areas in the interface
self._open_file_queue = [] # A list of files to open (after the application has started)
self._open_url_queue = [] # A list of urls to open (after the application has started)
self._update_platform_activity_timer = None
self._sidebar_custom_menu_items = [] # type: list # Keeps list of custom menu items for the side bar
@ -274,6 +277,8 @@ class CuraApplication(QtApplication):
self._conan_installs = ApplicationMetadata.CONAN_INSTALLS
self._python_installs = ApplicationMetadata.PYTHON_INSTALLS
self._supported_url_schemes: List[str] = ["cura", "slicer"]
@pyqtProperty(str, constant=True)
def ultimakerCloudApiRootUrl(self) -> str:
return UltimakerCloudConstants.CuraCloudAPIRoot
@ -326,7 +331,11 @@ class CuraApplication(QtApplication):
assert not "This crash is triggered by the trigger_early_crash command line argument."
for filename in self._cli_args.file:
self._files_to_open.append(os.path.abspath(filename))
url = QUrl(filename)
if url.scheme() in self._supported_url_schemes:
self._open_url_queue.append(url)
else:
self._files_to_open.append(os.path.abspath(filename))
def initialize(self) -> None:
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
@ -947,6 +956,8 @@ class CuraApplication(QtApplication):
self.callLater(self._openFile, file_name)
for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading.
self.callLater(self._openFile, file_name)
for url in self._open_url_queue:
self.callLater(self._openUrl, url)
initializationFinished = pyqtSignal()
showAddPrintersUncancellableDialog = pyqtSignal() # Used to show the add printers dialog with a greyed background
@ -1156,9 +1167,15 @@ class CuraApplication(QtApplication):
if event.type() == QEvent.Type.FileOpen:
if self._plugins_loaded:
self._openFile(event.file())
if event.file():
self._openFile(event.file())
if event.url():
self._openUrl(event.url())
else:
self._open_file_queue.append(event.file())
if event.file():
self._open_file_queue.append(event.file())
if event.url():
self._open_url_queue.append(event.url())
if int(event.type()) == 20: # 'QEvent.Type.Quit' enum isn't there, even though it should be according to docs.
# Once we're at this point, everything should have been flushed already (past OnExitCallbackManager).
@ -1542,7 +1559,7 @@ class CuraApplication(QtApplication):
if not nodes:
return
objects_in_filename = {} # type: Dict[str, List[CuraSceneNode]]
objects_in_filename: Dict[str, List[CuraSceneNode]] = {}
for node in nodes:
mesh_data = node.getMeshData()
if mesh_data:
@ -1783,6 +1800,58 @@ class CuraApplication(QtApplication):
def _openFile(self, filename):
self.readLocalFile(QUrl.fromLocalFile(filename))
def _openUrl(self, url: QUrl) -> None:
if url.scheme() not in self._supported_url_schemes:
# only handle cura:// and slicer:// urls schemes
return
match url.host() + url.path():
case "open" | "open/":
query = QUrlQuery(url.query())
model_url = QUrl(query.queryItemValue("file", options=QUrl.ComponentFormattingOption.FullyDecoded))
def on_finish(response):
content_disposition_header_key = QByteArray("content-disposition".encode())
if not response.hasRawHeader(content_disposition_header_key):
Logger.log("w", "Could not find Content-Disposition header in response from {0}".format(
model_url.url()))
# Use the last part of the url as the filename, and assume it is an STL file
filename = model_url.path().split("/")[-1] + ".stl"
else:
# content_disposition is in the format
# ```
# content_disposition attachment; "filename=[FILENAME]"
# ```
# Use a regex to extract the filename
content_disposition = str(response.rawHeader(content_disposition_header_key).data(),
encoding='utf-8')
content_disposition_match = re.match(r'attachment; filename="(?P<filename>.*)"',
content_disposition)
assert content_disposition_match is not None
filename = content_disposition_match.group("filename")
tmp = tempfile.NamedTemporaryFile(suffix=filename, delete=False)
with open(tmp.name, "wb") as f:
f.write(response.readAll())
self.readLocalFile(QUrl.fromLocalFile(tmp.name), add_to_recent_files=False)
def on_error(*args, **kwargs):
Logger.log("w", "Could not download file from {0}".format(model_url.url()))
Message("Could not download file: " + str(model_url.url()),
title= "Loading Model failed",
message_type=Message.MessageType.ERROR).show()
return
self.getHttpRequestManager().get(
model_url.url(),
callback=on_finish,
error_callback=on_error,
)
case path:
Logger.log("w", "Unsupported url scheme path: {0}".format(path))
def _addProfileReader(self, profile_reader):
# TODO: Add the profile reader to the list of plug-ins that can be used when importing profiles.
pass

View File

@ -67,7 +67,7 @@ class LayerPolygon:
# Buffering the colors shouldn't be necessary as it is not
# re-used and can save a lot of memory usage.
self._color_map = LayerPolygon.getColorMap()
self._colors = self._color_map[self._types] # type: numpy.ndarray
self._colors: numpy.ndarray = self._color_map[self._types]
# When type is used as index returns true if type == LayerPolygon.InfillType
# or type == LayerPolygon.SkinType
@ -75,8 +75,8 @@ class LayerPolygon:
# Should be generated in better way, not hardcoded.
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype=bool)
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
self._build_cache_line_mesh_mask: Optional[numpy.ndarray] = None
self._build_cache_needed_points: Optional[numpy.ndarray] = None
def buildCache(self) -> None:
# For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.

View File

@ -144,6 +144,23 @@ SectionEnd
######################################################################
Section UrlProtocol
WriteRegStr HKCR "cura" "" "URL:cura"
WriteRegStr HKCR "cura" "URL Protocol" ""
WriteRegStr HKCR "cura\DefaultIcon" "" "$INSTDIR\${MAIN_APP_EXE},1"
WriteRegStr HKCR "cura\shell" "" "open"
WriteRegStr HKCR "cura\shell\open\command" "" '"$INSTDIR\${MAIN_APP_EXE}" "%1"'
WriteRegStr HKCR "slicer" "" "URL:slicer"
WriteRegStr HKCR "slicer" "URL Protocol" ""
WriteRegStr HKCR "slicer\DefaultIcon" "" "$INSTDIR\${MAIN_APP_EXE},1"
WriteRegStr HKCR "slicer\shell" "" "open"
WriteRegStr HKCR "slicer\shell\open\command" "" '"$INSTDIR\${MAIN_APP_EXE}" "%1"'
SectionEnd
######################################################################
Section Uninstall
${INSTALL_TYPE}{% for files in mapped_out_paths.values() %}{% for file in files %}
Delete "{{ file[1] }}"{% endfor %}{% endfor %}{% for rem_dir in rmdir_paths %}
@ -187,8 +204,13 @@ RmDir "$SMPROGRAMS\{{ app_name }}"
!insertmacro APP_UNASSOCIATE "stl" "Cura.model"
!insertmacro APP_UNASSOCIATE "3mf" "Cura.project"
; Unassociate file associations for 'cura' protocol
DeleteRegKey HKCR "cura"
; Unassociate file associations for 'slicer' protocol
DeleteRegKey HKCR "slicer"
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
SectionEnd
######################################################################

View File

@ -33,6 +33,21 @@
/>
</Upgrade>
<Property Id="ASSOCIATE_URL_PROTOCOLS">
<RegistrySearch Id="CheckCuraProtocolHandler"
Type="raw"
Root="HKCR"
Key="cura"
Name="URL Protocol"
/>
<RegistrySearch Id="CheckSlicerProtocolHandler"
Type="raw"
Root="HKCR"
Key="slicer"
Name="URL Protocol"
/>
</Property>
{% if "Enterprise" in app_name %}
<Property Id="PREVIOUS_413_INSTALLED" Secure="yes" />
<Upgrade Id="53C603BB-2B17-4206-A609-29C2E0D0B0AE">
@ -144,11 +159,32 @@
</Component>
</DirectoryRef>
<!--Url Scheme-->
<Component Id="CuraRegistration" Guid="*" Directory="APPLICATIONFOLDER">
<RegistryKey Root="HKCR" Key="cura">
<RegistryValue Type="string" Value="URL:Cura Protocol"/>
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
<RegistryValue Type="string" Key="DefaultIcon" Value="[APPLICATIONFOLDER]\{{ main_app }},1"/>
<RegistryValue Type="string" Key="shell\open\command" Value="&quot;[APPLICATIONFOLDER]\{{ main_app }}&quot; &quot;%1&quot;"/>
</RegistryKey>
</Component>
<Component Id="SlicerRegistration" Guid="*" Directory="APPLICATIONFOLDER">
<RegistryKey Root="HKCR" Key="slicer">
<RegistryValue Type="string" Value="URL:Slicer Protocol"/>
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
<RegistryValue Type="string" Key="DefaultIcon" Value="[APPLICATIONFOLDER]\{{ main_app }},1"/>
<RegistryValue Type="string" Key="shell\open\command" Value="&quot;[APPLICATIONFOLDER]\{{ main_app }}&quot; &quot;%1&quot;"/>
</RegistryKey>
</Component>
<Feature Id="ProductFeature" Title="{{ app_name }}" Level="1" ConfigurableDirectory="APPLICATIONFOLDER">
<ComponentRef Id="CMP_UltiMaker_Cura_exe" />
<ComponentRef Id="CMP_CuraEngine_exe" />
<ComponentGroupRef Id="NewFilesGroup" />
<ComponentRef Id="CMP_Shortcuts" />
<ComponentRef Id="CuraRegistration"/>
<ComponentRef Id="SlicerRegistration"/>
</Feature>
<Feature Id="UninstallOlderVersionFeature" Title="Uninstall previous versions" Level="{{ 1 if "Enterprise" in app_name else 0 }}" Description="..."/>
</Product>

View File

@ -337,7 +337,7 @@ class StartSliceJob(Job):
user_id = uuid.getnode() # On all of Cura's supported platforms, this returns the MAC address which is pseudonymical information (!= anonymous).
user_id %= 2 ** 16 # So to make it anonymous, apply a bitmask selecting only the last 16 bits. This prevents it from being traceable to a specific user but still gives somewhat of an idea of whether it's just the same user hitting the same crash over and over again, or if it's widespread.
self._slice_message.sentry_id = "{user_id}"
self._slice_message.sentry_id = f"{user_id}"
self._slice_message.cura_version = CuraVersion
# Build messages for extruder stacks

View File

@ -0,0 +1,837 @@
# Designed in January 2023 by GregValiant (Greg Foresi)
## My design intent was to make this as full featured and "industrial strength" as I could. People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print. This script allows that.
# Functions:
## Remove all fan speed lines from the file (optional). This should be enabled for the first instance of the script. It is disabled by default in any following instances.
## "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print. "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.).
## If 'By Feature' then a Start Layer and/or an End Layer can be defined.
## Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}.
## A minimum fan speed of 12% is enforced. It is the slowest speed that my cooling fan will turn on so that's what I used. 'M106 S14' (as Cura might insert) was pretty useless.
## If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting. There is support for up to 4 layer cooling fan circuits.
## My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir. A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script.
## 9/14/23 (Greg Foresi) Added support for One-at-a-Time print sequence.
## 12/15/23 (Greg Foresi) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
## 1/5/24 (Greg Foresi) Revised the regex replacements.
from ..Script import Script
from UM.Application import Application
import re
class AddCoolingProfile(Script):
def getSettingDataString(self):
return """{
"name": "Advanced Cooling Fan Control",
"key": "AddCoolingProfile",
"metadata": {},
"version": 2,
"settings":
{
"fan_layer_or_feature":
{
"label": "Cooling Control by:",
"description": "A fan percentage of ''0'' turns the fan off. Minimum Fan is 12% (when on). All layer entries are the Cura Preview number. ''By Layer'': Enter as ''Layer#/Fan%'' (foreslash is the delimiter). Your final layer speed will continue to the end of the Gcode. ''By Feature'': If you enable an 'End Layer' then the ''Final %'' is available and is the speed that will finish the file. 'By Feature' is better for large slow prints than it is for short fast prints.",
"type": "enum",
"options": {
"by_layer": "Layer Numbers",
"by_feature": "Feature Types"},
"default_value": "by_layer"
},
"delete_existing_m106":
{
"label": "Remove M106 lines prior to inserting new.",
"description": "If you have 2 or more instances of 'Advanced Cooling Fan Control' running (to cool a portion of a print differently), then you must uncheck this box or the followup instances will remove all the lines inserted by the first instance. Pay attention to the Start and Stop layers. Regardless of this setting: The script always removes M106 lines starting with the lowest layer number (when 'By Layer') or the starting layer number (when 'By Feature'). If you want to keep the M106 lines that Cura inserted up to the point where this post-processor will start making insertions, then un-check the box.",
"type": "bool",
"enabled": true,
"value": true,
"default_value": true
},
"feature_fan_start_layer":
{
"label": "Starting Layer",
"description": "Layer to start the insertion at. Use the Cura preview numbers. Changes will begin at the start of that layer.",
"type": "int",
"default_value": 5,
"minimum_value": 1,
"unit": "Lay# ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_end_layer":
{
"label": "Ending Layer",
"description": "Layer to complete the insertion at. Enter '-1' for the entire file or enter a layer number. Insertions will stop at the END of this layer. If you set an End Layer then you should set the Final % that will finish the file",
"type": "int",
"default_value": -1,
"minimum_value": -1,
"unit": "Lay# ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"layer_fan_1":
{
"label": "Layer/Percent #1",
"description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage. There are up to 8 changes. If you need more then add a second instance of this script and remember to turn off 'Remove M106 lines' in the second instance. The layer numbers in the second instance must start with a layer number higher than the last layer number in a previous script. You can't end the first script with a setting for layer 80 and then start the second script with a setting for layer 40 because 'Remove M106 lines' always starts with the lowest layer number when 'By Layer' is selected.",
"type": "str",
"default_value": "5/30",
"unit": "L#/% ",
"enabled": "fan_layer_or_feature == 'by_layer'"
},
"layer_fan_2":
{
"label": "Layer/Percent #2",
"description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
"type": "str",
"default_value": "",
"unit": "L#/% ",
"enabled": "fan_layer_or_feature == 'by_layer'"
},
"layer_fan_3":
{
"label": "Layer/Percent #3",
"description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
"type": "str",
"default_value": "",
"unit": "L#/% ",
"enabled": "fan_layer_or_feature == 'by_layer'"
},
"layer_fan_4":
{
"label": "Layer/Percent #4",
"description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
"type": "str",
"default_value": "",
"unit": "L#/% ",
"enabled": "fan_layer_or_feature == 'by_layer'"
},
"layer_fan_5":
{
"label": "Layer/Percent #5",
"description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
"type": "str",
"default_value": "",
"unit": "L#/% ",
"enabled": "fan_layer_or_feature == 'by_layer'"
},
"layer_fan_6":
{
"label": "Layer/Percent #6",
"description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
"type": "str",
"default_value": "",
"unit": "L#/% ",
"enabled": "fan_layer_or_feature == 'by_layer'"
},
"layer_fan_7":
{
"label": "Layer/Percent #7",
"description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
"type": "str",
"default_value": "",
"unit": "L#/% ",
"enabled": "fan_layer_or_feature == 'by_layer'"
},
"layer_fan_8":
{
"label": "Layer/Percent #8",
"description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
"type": "str",
"default_value": "",
"unit": "L#/% ",
"enabled": "fan_layer_or_feature == 'by_layer'"
},
"feature_fan_skirt":
{
"label": "Skirt/Brim/Ooze Shield %",
"description": "Enter the fan percentage for skirt/brim. If you are starting at a layer above 1 then this setting only affects Ooze Shields and from the Start Layer up.",
"type": "int",
"default_value": 0,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_wall_inner":
{
"label": "Inner Walls %",
"description": "Enter the fan percentage for the Wall-Inner.",
"type": "int",
"default_value": 35,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_wall_outer":
{
"label": "Outer Walls %",
"description": "Enter the fan percentage for the Wall-Outer.",
"type": "int",
"default_value": 75,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_fill":
{
"label": "Infill %",
"description": "Enter the fan percentage for the Infill.",
"type": "int",
"default_value": 35,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_skin":
{
"label": "Top/Bottom (Skin) %",
"description": "Enter the fan percentage for the Skins.",
"type": "int",
"default_value": 100,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_support":
{
"label": "Support %",
"description": "Enter the fan percentage for the Supports.",
"type": "int",
"default_value": 35,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_support_interface":
{
"label": "Support Interface %",
"description": "Enter the fan percentage for the Support Interface.",
"type": "int",
"default_value": 100,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_prime_tower":
{
"label": "Prime Tower %",
"description": "Enter the fan percentage for the Prime Tower (whether it's used or not).",
"type": "int",
"default_value": 35,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_bridge":
{
"label": "Bridge %",
"description": "Enter the fan percentage for any Bridging (whether it's used on not).",
"type": "int",
"default_value": 100,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_layer_or_feature == 'by_feature'"
},
"feature_fan_combing":
{
"label": "Fan 'OFF' during Combing:",
"description": "When checked will set the fan to 0% for combing moves over 5 lines long in the gcode. When un-checked the fan speed during combing is whatever the previous speed is set to.",
"type": "bool",
"enabled": "fan_layer_or_feature == 'by_feature'",
"default_value": true
},
"feature_fan_feature_final":
{
"label": "Final %",
"description": "If you choose an 'End Layer' then this is the fan speed that will carry through to the end of the gcode file. It will go into effect at the 'END' of your End layer.",
"type": "int",
"default_value": 35,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "(int(feature_fan_end_layer) != -1) and (fan_layer_or_feature == 'by_feature')"
},
"fan_enable_raft":
{
"label": "Enable Raft Cooling",
"description": "Enable the fan for the raft layers. When enabled the Raft Fan Speed will continue until another Layer or Feature setting over-rides it.",
"type": "bool",
"default_value": false,
"enabled": true
},
"fan_raft_percent":
{
"label": "Raft Fan %:",
"description": "Enter the percentage for the Raft.",
"type": "int",
"default_value": 35,
"minimum_value": 0,
"maximum_value": 100,
"unit": "% ",
"enabled": "fan_enable_raft"
}
}
}"""
def initialize(self) -> None:
super().initialize()
scripts = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("post_processing_scripts")
if scripts != None:
script_count = scripts.count("AddCoolingProfile")
if script_count > 0:
## Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
self._instance.setProperty("delete_existing_m106", "value", False)
def execute(self, data):
#Initialize variables that are buried in if statements.
mycura = Application.getInstance().getGlobalContainerStack()
t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
#Get some information from Cura-----------------------------------
extruder = mycura.extruderList
#This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
fan_mode = True
##For 4.x versions that don't have the 0-1 option
try:
fan_mode = not bool(extruder[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
except:
pass
bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
extruder_count = mycura.getProperty("machine_extruder_count", "value")
print_sequence = str(mycura.getProperty("print_sequence", "value"))
#Assign the fan numbers to the tools------------------------------
if extruder_count == 1:
is_multi_fan = False
is_multi_extr_print = False
if int((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0:
t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
else:
#No P parameter if there is a single fan circuit------------------
t0_fan = ""
#Get the cooling fan numbers for each extruder if the printer has multiple extruders
elif extruder_count > 1:
is_multi_fan = True
t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
if is_multi_fan:
if extruder_count > 1: t1_fan = " P" + str((extruder[1].getProperty("machine_extruder_cooling_fan_number", "value")))
if extruder_count > 2: t2_fan = " P" + str((extruder[2].getProperty("machine_extruder_cooling_fan_number", "value")))
if extruder_count > 3: t3_fan = " P" + str((extruder[3].getProperty("machine_extruder_cooling_fan_number", "value")))
#Initialize the fan_list with defaults----------------------------
fan_list = ["z"] * 16
for num in range(0,15,2):
fan_list[num] = len(data)
fan_list[num + 1] = "M106 S0"
#Assign the variable values if "By Layer"-------------------------
by_layer_or_feature = self.getSettingValueByKey("fan_layer_or_feature")
if by_layer_or_feature == "by_layer":
## By layer doesn't do any feature search so there is no need to look for combing moves
feature_fan_combing = False
fan_list[0] = self.getSettingValueByKey("layer_fan_1")
fan_list[2] = self.getSettingValueByKey("layer_fan_2")
fan_list[4] = self.getSettingValueByKey("layer_fan_3")
fan_list[6] = self.getSettingValueByKey("layer_fan_4")
fan_list[8] = self.getSettingValueByKey("layer_fan_5")
fan_list[10] = self.getSettingValueByKey("layer_fan_6")
fan_list[12] = self.getSettingValueByKey("layer_fan_7")
fan_list[14] = self.getSettingValueByKey("layer_fan_8")
## If there is no '/' delimiter then ignore the line else put the settings in a list
for num in range(0,15,2):
if "/" in fan_list[num]:
fan_list[num + 1] = self._layer_checker(fan_list[num], "p", fan_mode)
fan_list[num] = self._layer_checker(fan_list[num], "l", fan_mode)
## Assign the variable values if "By Feature"
elif by_layer_or_feature == "by_feature":
the_start_layer = self.getSettingValueByKey("feature_fan_start_layer") - 1
the_end_layer = self.getSettingValueByKey("feature_fan_end_layer")
try:
if int(the_end_layer) != -1:
## Catch a possible input error.
if the_end_layer < the_start_layer:
the_end_layer = the_start_layer
except:
the_end_layer = -1 ## If there is an input error default to the entire gcode file.
## Get the speed for each feature
feature_name_list = []
feature_speed_list = []
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_skirt"), fan_mode)); feature_name_list.append(";TYPE:SKIRT")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_wall_inner"), fan_mode)); feature_name_list.append(";TYPE:WALL-INNER")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_wall_outer"), fan_mode)); feature_name_list.append(";TYPE:WALL-OUTER")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_fill"), fan_mode)); feature_name_list.append(";TYPE:FILL")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_skin"), fan_mode)); feature_name_list.append(";TYPE:SKIN")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_support"), fan_mode)); feature_name_list.append(";TYPE:SUPPORT")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_support_interface"), fan_mode)); feature_name_list.append(";TYPE:SUPPORT-INTERFACE")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_prime_tower"), fan_mode)); feature_name_list.append(";TYPE:PRIME-TOWER")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_bridge"), fan_mode)); feature_name_list.append(";BRIDGE")
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_feature_final"), fan_mode)); feature_name_list.append("FINAL_FAN")
feature_fan_combing = self.getSettingValueByKey("feature_fan_combing")
if the_end_layer > -1 and by_layer_or_feature == "by_feature":
## Required so the final speed input can be determined
the_end_is_enabled = True
else:
## There is no ending layer so do the whole file
the_end_is_enabled = False
if the_end_layer == -1 or the_end_is_enabled == False:
the_end_layer = len(data) + 2
## Find the Layer0Index and the RaftIndex
raft_start_index = 0
number_of_raft_layers = 0
layer_0_index = 0
## Catch the number of raft layers.
for l_num in range(1,10,1):
layer = data[l_num]
if ";LAYER:-" in layer:
number_of_raft_layers += 1
if raft_start_index == 0:
raft_start_index = l_num
if ";LAYER:0" in layer:
layer_0_index = l_num
break
## Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used.
if is_multi_fan:
T0_used = False
T1_used = False
T2_used = False
T3_used = False
## Bypass the file header and ending gcode.
for num in range(1,len(data)-1,1):
lines = data[num]
if "T0" in lines:
T0_used = True
if "T1" in lines:
T1_used = True
if "T2" in lines:
T2_used = True
if "T3" in lines:
T3_used = True
is_multi_extr_print = True if sum([T0_used, T1_used, T2_used, T3_used]) > 1 else False
## On a multi-extruder printer and single extruder print find out which extruder starts the file.
init_fan = t0_fan
if not is_multi_extr_print:
startup = data[1]
lines = startup.split("\n")
for line in lines:
if line == "T1":
t0_fan = t1_fan
elif line == "T2":
t0_fan = t2_fan
elif line == "T3":
t0_fan = t3_fan
elif is_multi_extr_print:
## On a multi-extruder printer and multi extruder print find out which extruder starts the file.
startup = data[1]
lines = startup.split("\n")
for line in lines:
if line == "T0":
init_fan = t0_fan
elif line == "T1":
init_fan = t1_fan
elif line == "T2":
init_fan = t2_fan
elif line == "T3":
init_fan = t3_fan
else:
init_fan = ""
## Assign the variable values if "Raft Enabled"
raft_enabled = self.getSettingValueByKey("fan_enable_raft")
if raft_enabled and bed_adhesion == "raft":
fan_sp_raft = self._feature_checker(self.getSettingValueByKey("fan_raft_percent"), fan_mode)
else:
fan_sp_raft = "M106 S0"
# Start to alter the data-----------------------------------------
## Strip the existing M106 lines from the file up to the end of the last layer. If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option.
delete_existing_m106 = self.getSettingValueByKey("delete_existing_m106")
if delete_existing_m106:
## Start deleting from the beginning
start_from = int(raft_start_index)
else:
if by_layer_or_feature == "by_layer":
altered_start_layer = str(len(data))
## The fan list layers don't need to be in ascending order. Get the lowest.
for num in range(0,15,2):
try:
if int(fan_list[num]) < int(altered_start_layer):
altered_start_layer = int(fan_list[num])
except:
pass
elif by_layer_or_feature == "by_feature":
altered_start_layer = int(the_start_layer) - 1
start_from = int(layer_0_index) + int(altered_start_layer)
## Strip the M106 and M107 lines from the file
for l_index in range(int(start_from), len(data) - 1, 1):
data[l_index] = re.sub(re.compile("M106(.*)\n"), "", data[l_index])
data[l_index] = re.sub(re.compile("M107(.*)\n"), "", data[l_index])
## Deal with a raft and with One-At-A-Time print sequence
if raft_enabled and bed_adhesion == "raft":
if print_sequence == "one_at_a_time":
for r_index in range(2,len(data)-2,1):
lines = data[r_index].split("\n")
if not raft_enabled or bed_adhesion != "raft":
if ";LAYER:0" in data[r_index] or ";LAYER:-" in data[r_index]:
lines.insert(1, "M106 S0" + str(t0_fan))
if raft_enabled and bed_adhesion == "raft":
if ";LAYER:-" in data[r_index]:
## Turn the raft fan on
lines.insert(1, fan_sp_raft + str(t0_fan))
## Shut the raft fan off at layer 0
if ";LAYER:0" in data[r_index]:
lines.insert(1,"M106 S0" + str(t0_fan))
data[r_index] = "\n".join(lines)
elif print_sequence == "all_at_once":
layer = data[raft_start_index]
lines = layer.split("\n")
if ";LAYER:-" in layer:
## Turn the raft fan on
lines.insert(1, fan_sp_raft + str(init_fan))
layer = "\n".join(lines)
data[raft_start_index] = layer
layer = data[layer_0_index]
lines = layer.split("\n")
## Shut the raft fan off
lines.insert(1, "M106 S0" + str(init_fan))
data[layer_0_index] = "\n".join(lines)
else:
for r_index in range(2,len(data)-2,1):
lines = data[r_index].split("\n")
if ";LAYER:0" in data[r_index] or ";LAYER:-" in data[r_index]:
if not "0" in fan_list:
lines.insert(1, "M106 S0" + str(t0_fan))
data[r_index] = "\n".join(lines)
## Turn off all fans at the end of data[1]. If more than one instance of this script is running then this will result in multiple M106 lines.
temp_startup = data[1].split("\n")
temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t0_fan))
## If there are multiple cooling fans shut them all off
if is_multi_fan:
if extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan))
if extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan))
if extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan))
data[1] = "\n".join(temp_startup)
## If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long
## For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH.
if feature_fan_combing:
for layer_num in range(2,len(data)):
layer = data[layer_num]
data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer)
data = self._add_travel_comment(data, layer_0_index)
# Single Fan "By Layer"--------------------------------------------
if by_layer_or_feature == "by_layer" and not is_multi_fan:
return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan)
# Multi-Fan "By Layer"---------------------------------------------
if by_layer_or_feature == "by_layer" and is_multi_fan:
return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan)
#Single Fan "By Feature"------------------------------------------
if by_layer_or_feature == "by_feature" and (not is_multi_fan or not is_multi_extr_print):
return self._single_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, feature_speed_list, feature_name_list, feature_fan_combing)
#Multi Fan "By Feature"-------------------------------------------
if by_layer_or_feature == "by_feature" and is_multi_fan:
return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing)
# The Single Fan "By Layer"----------------------------------------
def _single_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str)->str:
layer_number = "0"
single_fan_data = data
for l_index in range(layer_0_index,len(single_fan_data)-1,1):
layer = single_fan_data[l_index]
fan_lines = layer.split("\n")
for fan_line in fan_lines:
if ";LAYER:" in fan_line:
layer_number = str(fan_line.split(":")[1])
## If there is a match for the current layer number make the insertion
for num in range(0,15,2):
if layer_number == str(fan_list[num]):
layer = layer.replace(fan_lines[0],fan_lines[0] + "\n" + fan_list[num + 1] + str(t0_fan))
single_fan_data[l_index] = layer
return single_fan_data
# Multi-Fan "By Layer"-----------------------------------------
def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str)->str:
multi_fan_data = data
layer_number = "0"
current_fan_speed = "0"
prev_fan = str(t0_fan)
this_fan = str(t0_fan)
start_index = str(len(multi_fan_data))
for num in range(0,15,2):
## The fan_list may not be in ascending order. Get the lowest layer number
try:
if int(fan_list[num]) < int(start_index):
start_index = str(fan_list[num])
except:
pass
## Move the start point if delete_existing_m106 is false
start_index = int(start_index) + int(layer_0_index)
## Track the tool number
for num in range(1,int(start_index),1):
layer = multi_fan_data[num]
lines = layer.split("\n")
for line in lines:
if line == "T0":
prev_fan = this_fan
this_fan = t0_fan
elif line == "T1":
prev_fan = this_fan
this_fan = t1_fan
elif line == "T2":
prev_fan = this_fan
this_fan = t2_fan
elif line == "T3":
prev_fan = this_fan
this_fan = t3_fan
for l_index in range(int(start_index),len(multi_fan_data)-1,1):
modified_data = ""
layer = multi_fan_data[l_index]
fan_lines = layer.split("\n")
for fan_line in fan_lines:
## Prepare to shut down the previous fan and start the next one.
if fan_line.startswith("T"):
if fan_line == "T0": this_fan = str(t0_fan)
if fan_line == "T1": this_fan = str(t1_fan)
if fan_line == "T2": this_fan = str(t2_fan)
if fan_line == "T3": this_fan = str(t3_fan)
modified_data += "M106 S0" + prev_fan + "\n"
modified_data += fan_line + "\n"
modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
prev_fan = this_fan
elif ";LAYER:" in fan_line:
modified_data += fan_line + "\n"
layer_number = str(fan_line.split(":")[1])
for num in range(0,15,2):
if layer_number == str(fan_list[num]):
modified_data += fan_list[num + 1] + this_fan + "\n"
current_fan_speed = str(fan_list[num + 1].split("S")[1])
current_fan_speed = str(current_fan_speed.split(" ")[0]) ## Just in case
else:
modified_data += fan_line + "\n"
if modified_data.endswith("\n"): modified_data = modified_data[0:-1]
multi_fan_data[l_index] = modified_data
return multi_fan_data
# Single fan by feature-----------------------------------------------
def _single_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str:
single_fan_data = data
layer_number = "0"
index = 1
## Start with layer:0
for l_index in range(layer_0_index,len(single_fan_data)-1,1):
modified_data = ""
layer = single_fan_data[l_index]
lines = layer.split("\n")
for line in lines:
if ";LAYER:" in line:
layer_number = str(line.split(":")[1])
if int(layer_number) >= int(the_start_layer) and int(layer_number) < int(the_end_layer)-1:
temp = line.split(" ")[0]
try:
name_index = feature_name_list.index(temp)
except:
name_index = -1
if name_index != -1:
modified_data += feature_speed_list[name_index] + t0_fan + "\n"
elif ";MESH:NONMESH" in line:
if feature_fan_combing == True:
modified_data += "M106 S0" + t0_fan + "\n"
modified_data += line + "\n"
## If an End Layer is defined and is less than the last layer then insert the Final Speed
if line == ";LAYER:" + str(the_end_layer) and the_end_is_enabled == True:
modified_data += feature_speed_list[len(feature_speed_list) - 1] + t0_fan + "\n"
if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
single_fan_data[l_index] = modified_data
return single_fan_data
# Multi-fan by feature------------------------------------------------
def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str:
multi_fan_data = data
layer_number = "0"
start_index = 1
prev_fan = t0_fan
this_fan = t0_fan
modified_data = ""
current_fan_speed = "0"
for my_index in range(1, len(multi_fan_data) - 1, 1):
layer = multi_fan_data[my_index]
if ";LAYER:" + str(the_start_layer) + "\n" in layer:
start_index = int(my_index) - 1
break
## Track the previous tool changes
for num in range(1,start_index,1):
layer = multi_fan_data[num]
lines = layer.split("\n")
for line in lines:
if line == "T0":
prev_fan = this_fan
this_fan = t0_fan
elif line == "T1":
prev_fan = this_fan
this_fan = t1_fan
elif line == "T2":
prev_fan = this_fan
this_fan = t2_fan
elif line == "T3":
prev_fan = this_fan
this_fan = t3_fan
## Get the current tool.
for l_index in range(start_index,start_index + 1,1):
layer = multi_fan_data[l_index]
lines = layer.split("\n")
for line in lines:
if line.startswith("T"):
if line == "T0": this_fan = t0_fan
if line == "T1": this_fan = t1_fan
if line == "T2": this_fan = t2_fan
if line == "T3": this_fan = t3_fan
prev_fan = this_fan
## Start to make insertions-------------------------------------
for l_index in range(start_index+1,len(multi_fan_data)-1,1):
layer = multi_fan_data[l_index]
lines = layer.split("\n")
for line in lines:
if line.startswith("T"):
if line == "T0": this_fan = t0_fan
if line == "T1": this_fan = t1_fan
if line == "T2": this_fan = t2_fan
if line == "T3": this_fan = t3_fan
## Turn off the prev fan
modified_data += "M106 S0" + prev_fan + "\n"
modified_data += line + "\n"
## Turn on the current fan
modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
prev_fan = this_fan
if ";LAYER:" in line:
layer_number = str(line.split(":")[1])
modified_data += line + "\n"
if int(layer_number) >= int(the_start_layer):
temp = line.split(" ")[0]
try:
name_index = feature_name_list.index(temp)
except:
name_index = -1
if name_index != -1:
modified_data += line + "\n" + feature_speed_list[name_index] + this_fan + "\n"
#modified_data += feature_speed_list[name_index] + this_fan + "\n"
current_fan_speed = str(feature_speed_list[name_index].split("S")[1])
elif ";MESH:NONMESH" in line:
if feature_fan_combing == True:
modified_data += line + "\n"
modified_data += "M106 S0" + this_fan + "\n"
current_fan_speed = "0"
else:
modified_data += line + "\n"
## If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file
## There cannot be a break here because if there are multiple fan numbers they still need to be shut off and turned on.
elif line == ";LAYER:" + str(the_end_layer):
modified_data += feature_speed_list[len(feature_speed_list) - 1] + this_fan + "\n"
for set_speed in range(0, len(feature_speed_list) - 2):
feature_speed_list[set_speed] = feature_speed_list[len(feature_speed_list) - 1]
else:
## Layer and Tool get inserted into modified_data above. All other lines go into modified_data here
if not line.startswith("T") and not line.startswith(";LAYER:"): modified_data += line + "\n"
if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
multi_fan_data[l_index] = modified_data
modified_data = ""
return multi_fan_data
#Try to catch layer input errors, set the minimum speed to 12%, and put the strings together
def _layer_checker(self, fan_string: str, ty_pe: str, fan_mode: bool) -> str:
fan_string_l = str(fan_string.split("/")[0])
try:
if int(fan_string_l) <= 1: fan_string_l = "1"
if fan_string_l == "": fan_string_l = str(len(data))
except ValueError:
fan_string_l = str(len(data))
fan_string_l = str(int(fan_string_l) - 1)
fan_string_p = str(fan_string.split("/")[1])
if fan_string_p == "": fan_string_p = "0"
try:
if int(fan_string_p) < 0: fan_string_p = "0"
if int(fan_string_p) > 100: fan_string_p = "100"
except ValueError:
fan_string_p = "0"
## Set the minimum fan speed to 12%
if int(fan_string_p) < 12 and int(fan_string_p) != 0:
fan_string_p = "12"
fan_layer_line = str(fan_string_l)
if fan_mode:
fan_percent_line = "M106 S" + str(round(int(fan_string_p) * 2.55))
else:
fan_percent_line = "M106 S" + str(round(int(fan_string_p) / 100, 1))
if ty_pe == "l":
return str(fan_layer_line)
elif ty_pe == "p":
return fan_percent_line
#Try to catch feature input errors, set the minimum speed to 12%, and put the strings together when 'By Feature'
def _feature_checker(self, fan_feat_string: int, fan_mode: bool) -> str:
if fan_feat_string < 0: fan_feat_string = 0
## Set the minimum fan speed to 12%
if fan_feat_string > 0 and fan_feat_string < 12: fan_feat_string = 12
if fan_feat_string > 100: fan_feat_string = 100
if fan_mode:
fan_sp_feat = "M106 S" + str(round(fan_feat_string * 2.55))
else:
fan_sp_feat = "M106 S" + str(round(fan_feat_string / 100, 1))
return fan_sp_feat
# Add additional travel comments to turn the fan off during combing.
def _add_travel_comment(self, comment_data: str, lay_0_index: str) -> str:
for lay_num in range(int(lay_0_index), len(comment_data)-1,1):
layer = comment_data[lay_num]
lines = layer.split("\n")
## Copy the data to new_data and make the insertions there
new_data = lines
g0_count = 0
g0_index = -1
feature_type = ";TYPE:SUPPORT"
is_travel = False
for index, line in enumerate(lines):
insert_index = 0
if ";TYPE:" in line:
feature_type = line
is_travel = False
g0_count = 0
if ";MESH:NONMESH" in line:
is_travel = True
g0_count = 0
if line.startswith("G0 ") and not is_travel:
g0_count += 1
if g0_index == -1:
g0_index = lines.index(line)
elif not line.startswith("G0 ") and not is_travel:
## Add additional 'NONMESH' lines to shut the fan off during long combing moves--------
if g0_count > 5:
if not is_travel:
new_data.insert(g0_index + insert_index, ";MESH:NONMESH")
insert_index += 1
## Add the feature_type at the end of the combing move to turn the fan back on
new_data.insert(g0_index + g0_count + 1, feature_type)
insert_index += 1
g0_count = 0
g0_index = -1
is_travel = False
elif g0_count <= 5:
g0_count = 0
g0_index = -1
is_travel = False
comment_data[lay_num] = "\n".join(new_data)
return comment_data

View File

@ -1,5 +1,6 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import math
from UM.Math.Color import Color
from UM.Math.Vector import Vector
@ -35,7 +36,7 @@ class SimulationPass(RenderPass):
self._nozzle_shader = None
self._disabled_shader = None
self._old_current_layer = 0
self._old_current_path = 0
self._old_current_path: float = 0.0
self._switching_layers = True # Tracking whether the user is moving across layers (True) or across paths (False). If false, lower layers render as shadowy.
self._gl = OpenGL.getInstance().getBindingsObject()
self._scene = Application.getInstance().getController().getScene()
@ -139,7 +140,7 @@ class SimulationPass(RenderPass):
continue
# Render all layers below a certain number as line mesh instead of vertices.
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
if self._layer_view.getCurrentLayer() > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
start = 0
end = 0
element_counts = layer_data.getElementCounts()
@ -147,33 +148,42 @@ class SimulationPass(RenderPass):
# In the current layer, we show just the indicated paths
if layer == self._layer_view._current_layer_num:
# We look for the position of the head, searching the point of the current path
index = self._layer_view._current_path_num
offset = 0
index = int(self._layer_view.getCurrentPath())
for polygon in layer_data.getLayer(layer).polygons:
# The size indicates all values in the two-dimension array, and the second dimension is
# always size 3 because we have 3D points.
if index >= polygon.data.size // 3 - offset:
index -= polygon.data.size // 3 - offset
offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
if index >= polygon.data.size // 3 :
index -= polygon.data.size // 3
continue
# The head position is calculated and translated
head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition()
ratio = self._layer_view.getCurrentPath() - math.floor(self._layer_view.getCurrentPath())
pos_a = Vector(polygon.data[index][0], polygon.data[index][1],
polygon.data[index][2])
if ratio <= 0.0001 or index + 1 == len(polygon.data):
# in case there multiple polygons and polygon changes, the first point has the same value as the last point in the previous polygon
head_position = pos_a + node.getWorldPosition()
else:
pos_b = Vector(polygon.data[index + 1][0],
polygon.data[index + 1][1],
polygon.data[index + 1][2])
vec = pos_a * (1.0 - ratio) + pos_b * ratio
head_position = vec + node.getWorldPosition()
break
break
if self._layer_view._minimum_layer_num > layer:
if self._layer_view.getMinimumLayer() > layer:
start += element_counts[layer]
end += element_counts[layer]
# Calculate the range of paths in the last layer
current_layer_start = end
current_layer_end = end + self._layer_view._current_path_num * 2 # Because each point is used twice
current_layer_end = end + int( self._layer_view.getCurrentPath()) * 2 # Because each point is used twice
# This uses glDrawRangeElements internally to only draw a certain range of lines.
# All the layers but the current selected layer are rendered first
if self._old_current_path != self._layer_view._current_path_num:
if self._old_current_path != self._layer_view.getCurrentPath():
self._current_shader = self._layer_shadow_shader
self._switching_layers = False
if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num:
if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view.getCurrentLayer():
self._current_shader = self._layer_shader
self._switching_layers = True
@ -193,8 +203,8 @@ class SimulationPass(RenderPass):
current_layer_batch.addItem(node.getWorldTransformation(), layer_data)
current_layer_batch.render(self._scene.getActiveCamera())
self._old_current_layer = self._layer_view._current_layer_num
self._old_current_path = self._layer_view._current_path_num
self._old_current_layer = self._layer_view.getCurrentLayer()
self._old_current_path = self._layer_view.getCurrentPath()
# Create a new batch that is not range-limited
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
@ -230,4 +240,4 @@ class SimulationPass(RenderPass):
if changed_object.callDecoration("getLayerData"): # Any layer data has changed.
self._switching_layers = True
self._old_current_layer = 0
self._old_current_path = 0
self._old_current_path = 0.0

View File

@ -1,6 +1,5 @@
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import sys
from PyQt6.QtCore import Qt
@ -58,6 +57,7 @@ class SimulationView(CuraView):
LAYER_VIEW_TYPE_LINE_TYPE = 1
LAYER_VIEW_TYPE_FEEDRATE = 2
LAYER_VIEW_TYPE_THICKNESS = 3
SIMULATION_FACTOR = 2
_no_layers_warning_preference = "view/no_layers_warning"
@ -74,21 +74,20 @@ class SimulationView(CuraView):
self._old_max_layers = 0
self._max_paths = 0
self._current_path_num = 0
self._current_path_num: float = 0.0
self._current_time = 0.0
self._minimum_path_num = 0
self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged)
self._current_feedrates = {}
self._lengths_of_polyline ={}
self._busy = False
self._simulation_running = False
self._ghost_shader = None # type: Optional["ShaderProgram"]
self._layer_pass = None # type: Optional[SimulationPass]
self._composite_pass = None # type: Optional[CompositePass]
self._old_layer_bindings = None # type: Optional[List[str]]
self._simulationview_composite_shader = None # type: Optional["ShaderProgram"]
self._old_composite_shader = None # type: Optional["ShaderProgram"]
self._ghost_shader: Optional["ShaderProgram"] = None
self._layer_pass: Optional[SimulationPass] = None
self._composite_pass: Optional[CompositePass] = None
self._old_layer_bindings: Optional[List[str]] = None
self._simulationview_composite_shader: Optional["ShaderProgram"] = None
self._old_composite_shader: Optional["ShaderProgram"] = None
self._max_feedrate = sys.float_info.min
self._min_feedrate = sys.float_info.max
@ -98,14 +97,16 @@ class SimulationView(CuraView):
self._min_line_width = sys.float_info.max
self._min_flow_rate = sys.float_info.max
self._max_flow_rate = sys.float_info.min
self._cumulative_line_duration_layer: Optional[int] = None
self._cumulative_line_duration: List[float] = []
self._global_container_stack = None # type: Optional[ContainerStack]
self._global_container_stack: Optional[ContainerStack] = None
self._proxy = None
self._resetSettings()
self._legend_items = None
self._show_travel_moves = False
self._nozzle_node = None # type: Optional[NozzleNode]
self._nozzle_node: Optional[NozzleNode] = None
Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5)
Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False)
@ -127,13 +128,12 @@ class SimulationView(CuraView):
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
self._compatibility_mode = self._evaluateCompatibilityMode()
self._slice_first_warning_message = Message(catalog.i18nc("@info:status",
"Nothing is shown because you need to slice first."),
title = catalog.i18nc("@info:title", "No layers to show"),
option_text = catalog.i18nc("@info:option_text",
"Do not show this message again"),
option_state = False,
message_type = Message.MessageType.WARNING)
self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."),
title=catalog.i18nc("@info:title", "No layers to show"),
option_text=catalog.i18nc("@info:option_text",
"Do not show this message again"),
option_state=False,
message_type=Message.MessageType.WARNING)
self._slice_first_warning_message.optionToggled.connect(self._onDontAskMeAgain)
CuraApplication.getInstance().getPreferences().addPreference(self._no_layers_warning_preference, True)
@ -189,9 +189,90 @@ class SimulationView(CuraView):
def getMaxLayers(self) -> int:
return self._max_layers
def getCurrentPath(self) -> int:
def getCurrentPath(self) -> float:
return self._current_path_num
def setTime(self, time: float) -> None:
cumulative_line_duration = self.cumulativeLineDuration()
if len(cumulative_line_duration) > 0:
self._current_time = time
left_i = 0
right_i = len(cumulative_line_duration) - 1
total_duration = cumulative_line_duration[-1]
# make an educated guess about where to start
i = int(right_i * max(0.0, min(1.0, self._current_time / total_duration)))
# binary search for the correct path
while left_i < right_i:
if cumulative_line_duration[i] <= self._current_time:
left_i = i + 1
else:
right_i = i
i = int((left_i + right_i) / 2)
left_value = cumulative_line_duration[i - 1] if i > 0 else 0.0
right_value = cumulative_line_duration[i]
if not (left_value <= self._current_time <= right_value):
Logger.warn(
f"Binary search error (out of bounds): index {i}: left value {left_value} right value {right_value} and current time is {self._current_time}")
fractional_value = (self._current_time - left_value) / (right_value - left_value)
self.setPath(i + fractional_value)
def advanceTime(self, time_increase: float) -> bool:
"""
Advance the time by the given amount.
:param time_increase: The amount of time to advance (in seconds).
:return: True if the time was advanced, False if the end of the simulation was reached.
"""
total_duration = 0.0
if len(self.cumulativeLineDuration()) > 0:
total_duration = self.cumulativeLineDuration()[-1]
if self._current_time + time_increase > total_duration:
# If we have reached the end of the simulation, go to the next layer.
if self.getCurrentLayer() == self.getMaxLayers():
# If we are already at the last layer, go to the first layer.
self.setTime(total_duration)
return False
# advance to the next layer, and reset the time
self.setLayer(self.getCurrentLayer() + 1)
self.setTime(0.0)
else:
self.setTime(self._current_time + time_increase)
return True
def cumulativeLineDuration(self) -> List[float]:
# Make sure _cumulative_line_duration is initialized properly
if self.getCurrentLayer() != self._cumulative_line_duration_layer:
#clear cache
self._cumulative_line_duration = []
total_duration = 0.0
polylines = self.getLayerData()
if polylines is not None:
for polyline in polylines.polygons:
for line_duration in list((polyline.lineLengths / polyline.lineFeedrates)[0]):
total_duration += line_duration / SimulationView.SIMULATION_FACTOR
self._cumulative_line_duration.append(total_duration)
# for tool change we add an extra tool path
self._cumulative_line_duration.append(total_duration)
# set current cached layer
self._cumulative_line_duration_layer = self.getCurrentLayer()
return self._cumulative_line_duration
def getLayerData(self) -> Optional["LayerData"]:
scene = self.getController().getScene()
for node in DepthFirstIterator(scene.getRoot()): # type: ignore
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
return layer_data.getLayer(self.getCurrentLayer())
return None
def getMinimumPath(self) -> int:
return self._minimum_path_num
@ -279,7 +360,7 @@ class SimulationView(CuraView):
self._startUpdateTopLayers()
self.currentLayerNumChanged.emit()
def setPath(self, value: int) -> None:
def setPath(self, value: float) -> None:
"""
Set the upper end of the range of visible paths on the current layer.
@ -289,6 +370,9 @@ class SimulationView(CuraView):
if self._current_path_num != value:
self._current_path_num = min(max(value, 0), self._max_paths)
self._minimum_path_num = min(self._minimum_path_num, self._current_path_num)
# update _current time when the path is changed by user
if self._current_path_num < self._max_paths and round(self._current_path_num)== self._current_path_num:
self._current_time = self.cumulativeLineDuration()[int(self._current_path_num)]
self._startUpdateTopLayers()
self.currentPathNumChanged.emit()
@ -402,15 +486,6 @@ class SimulationView(CuraView):
def getMaxFeedrate(self) -> float:
return self._max_feedrate
def getSimulationTime(self, currentIndex) -> float:
try:
return (self._lengths_of_polyline[self._current_layer_num][currentIndex] / self._current_feedrates[self._current_layer_num][currentIndex])[0]
except:
# In case of change in layers, currentIndex comes one more than the items in the lengths_of_polyline
# We give 1 second time for layer change
return 1.0
def getMinThickness(self) -> float:
if abs(self._min_thickness - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
@ -503,6 +578,7 @@ class SimulationView(CuraView):
self._max_thickness = sys.float_info.min
self._min_flow_rate = sys.float_info.max
self._max_flow_rate = sys.float_info.min
self._cumulative_line_duration = {}
# The colour scheme is only influenced by the visible lines, so filter the lines by if they should be visible.
visible_line_types = []
@ -535,10 +611,8 @@ class SimulationView(CuraView):
visible_indicies_with_extrusion = numpy.where(numpy.isin(polyline.types, visible_line_types_with_extrusion))[0]
if visible_indices.size == 0: # No items to take maximum or minimum of.
continue
self._lengths_of_polyline[layer_index] = polyline.lineLengths
visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices)
visible_feedrates_with_extrusion = numpy.take(polyline.lineFeedrates, visible_indicies_with_extrusion)
self._current_feedrates[layer_index] = polyline.lineFeedrates
visible_linewidths = numpy.take(polyline.lineWidths, visible_indices)
visible_linewidths_with_extrusion = numpy.take(polyline.lineWidths, visible_indicies_with_extrusion)
visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices)

View File

@ -127,6 +127,7 @@ Item
function resumeSimulation()
{
UM.SimulationView.setSimulationRunning(true)
UM.SimulationView.setCurrentPath(UM.SimulationView.currentPath)
simulationTimer.start()
layerSlider.manuallyChanged = false
pathSlider.manuallyChanged = false
@ -136,54 +137,19 @@ Item
Timer
{
id: simulationTimer
interval: UM.SimulationView.simulationTime
interval: 1000 / 15
running: false
repeat: true
onTriggered:
{
var currentPath = UM.SimulationView.currentPath
var numPaths = UM.SimulationView.numPaths
var currentLayer = UM.SimulationView.currentLayer
var numLayers = UM.SimulationView.numLayers
// When the user plays the simulation, if the path slider is at the end of this layer, we start
// the simulation at the beginning of the current layer.
if (!isSimulationPlaying)
{
if (currentPath >= numPaths)
{
UM.SimulationView.setCurrentPath(0)
}
else
{
UM.SimulationView.setCurrentPath(currentPath + 1)
}
}
// If the simulation is already playing and we reach the end of a layer, then it automatically
// starts at the beginning of the next layer.
else
{
if (currentPath >= numPaths)
{
// At the end of the model, the simulation stops
if (currentLayer >= numLayers)
{
playButton.pauseSimulation()
}
else
{
UM.SimulationView.setCurrentLayer(currentLayer + 1)
UM.SimulationView.setCurrentPath(0)
}
}
else
{
UM.SimulationView.setCurrentPath(currentPath + 1)
}
// divide by 1000 to account for ms to s conversion
const advance_time = simulationTimer.interval / 1000.0;
if (!UM.SimulationView.advanceTime(advance_time)) {
playButton.pauseSimulation();
}
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
// correctly, because part of the logic is in this trigger function.
isSimulationPlaying = true
isSimulationPlaying = true;
}
}

View File

@ -2,7 +2,6 @@
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
import numpy
from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
@ -12,11 +11,6 @@ if TYPE_CHECKING:
class SimulationViewProxy(QObject):
S_TO_MS = 1000
SPEED_OF_SIMULATION = 10
FACTOR = S_TO_MS/SPEED_OF_SIMULATION
def __init__(self, simulation_view: "SimulationView", parent=None) -> None:
super().__init__(parent)
self._simulation_view = simulation_view
@ -56,17 +50,13 @@ class SimulationViewProxy(QObject):
def numPaths(self):
return self._simulation_view.getMaxPaths()
@pyqtProperty(int, notify=currentPathChanged)
@pyqtProperty(float, notify=currentPathChanged)
def currentPath(self):
return self._simulation_view.getCurrentPath()
@pyqtProperty(int, notify=currentPathChanged)
def simulationTime(self):
# Extracts the currents paths simulation time (in seconds) for the current path from the dict of simulation time of the current layer.
# We multiply the time with 100 to make it to ms from s.(Should be 1000 in real time). This scaling makes the simulation time 10x faster than the real time.
simulationTimeOfpath = self._simulation_view.getSimulationTime(self._simulation_view.getCurrentPath()) * SimulationViewProxy.FACTOR
# Since the timer cannot process time less than 1 ms, we put a lower limit here
return int(max(1, simulationTimeOfpath))
@pyqtSlot(float, result=bool)
def advanceTime(self, duration: float) -> bool:
return self._simulation_view.advanceTime(duration)
@pyqtProperty(int, notify=currentPathChanged)
def minimumPath(self):
@ -92,8 +82,8 @@ class SimulationViewProxy(QObject):
def setMinimumLayer(self, layer_num):
self._simulation_view.setMinimumLayer(layer_num)
@pyqtSlot(int)
def setCurrentPath(self, path_num):
@pyqtSlot(float)
def setCurrentPath(self, path_num: float):
self._simulation_view.setPath(path_num)
@pyqtSlot(int)
@ -229,4 +219,3 @@ class SimulationViewProxy(QObject):
self._simulation_view.activityChanged.disconnect(self._onActivityChanged)
self._simulation_view.globalStackChanged.disconnect(self._onGlobalStackChanged)
self._simulation_view.preferencesChanged.disconnect(self._onPreferencesChanged)

View File

@ -331,7 +331,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
return False
[printer, *_] = self._printers
return printer.name in ("ultimaker_methodx", "ultimaker_methodxl")
return printer.type in ("MakerBot Method X", "MakerBot Method XL")
@pyqtProperty(bool, notify=_cloudClusterPrintersChanged)
def supportsPrintJobActions(self) -> bool:

View File

@ -1159,7 +1159,7 @@
"label": "Optimize Wall Printing Order",
"description": "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization. First layer is not optimized when choosing brim as build plate adhesion type.",
"type": "bool",
"default_value": false,
"default_value": true,
"settable_per_mesh": true
},
"inset_direction":
@ -1478,6 +1478,7 @@
"description": "Print top surface lines in an ordering that causes them to always overlap with adjacent lines in a single direction. This takes slightly more time to print, but makes flat surfaces look more consistent.",
"type": "bool",
"value": true,
"default_value": true,
"enabled": "roofing_layer_count > 0 and top_layers > 0 and roofing_pattern != 'concentric'",
"limit_to_extruder": "roofing_extruder_nr",
"settable_per_mesh": true
@ -4578,6 +4579,7 @@
"unit": "\u00b0C",
"type": "float",
"value": "material_print_temperature",
"default_value": 0,
"enabled": "cool_min_layer_time > 0",
"minimum_value_warning": "max(material_final_print_temperature, material_initial_print_temperature)",
"maximum_value_warning": "material_print_temperature",
@ -6112,7 +6114,101 @@
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"limit_to_extruder": "adhesion_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
"settable_per_extruder": true,
"children":
{
"raft_base_margin":
{
"label": "Raft Base Extra Margin",
"description": "If the raft base is enabled, this is the extra raft area around the model which is also given a raft. Increasing this margin will create a stronger raft while using more material and leaving less area for your print.",
"unit": "mm",
"type": "float",
"value": "raft_margin",
"default_value": 15,
"minimum_value_warning": "raft_interface_line_width",
"maximum_value_warning": "min(raft_interface_margin, 20)",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"limit_to_extruder": "raft_base_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"raft_interface_margin":
{
"label": "Raft Middle Extra Margin",
"description": "If the raft middle is enabled, this is the extra raft area around the model which is also given a raft. Increasing this margin will create a stronger raft while using more material and leaving less area for your print.",
"unit": "mm",
"type": "float",
"value": "raft_margin",
"default_value": 15,
"minimum_value_warning": "raft_interface_line_width",
"maximum_value_warning": "min(raft_surface_margin, 20)",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"limit_to_extruder": "raft_interface_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"raft_surface_margin":
{
"label": "Raft Top Extra Margin",
"description": "If the raft top is enabled, this is the extra raft area around the model which is also given a raft. Increasing this margin will create a stronger raft while using more material and leaving less area for your print.",
"unit": "mm",
"type": "float",
"value": "raft_margin",
"default_value": 15,
"minimum_value_warning": "raft_interface_line_width",
"maximum_value_warning": "20",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"limit_to_extruder": "raft_surface_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
}
}
},
"raft_remove_inside_corners":
{
"label": "Remove Raft Inside Corners",
"description": "Remove inside corners from the raft, causing the raft to become convex.",
"type": "bool",
"default_value": false,
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false,
"children":
{
"raft_base_remove_inside_corners":
{
"label": "Remove Raft Base Inside Corners",
"description": "Remove inside corners from the raft base, causing the raft to become convex.",
"type": "bool",
"value": "raft_remove_inside_corners",
"default_value": false,
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"raft_interface_remove_inside_corners":
{
"label": "Remove Raft Middle Inside Corners",
"description": "Remove inside corners from the raft middle part, causing the raft to become convex.",
"type": "bool",
"value": "raft_remove_inside_corners",
"default_value": false,
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"raft_surface_remove_inside_corners":
{
"label": "Remove Raft Top Inside Corners",
"description": "Remove inside corners from the raft top part, causing the raft to become convex.",
"type": "bool",
"value": "raft_remove_inside_corners",
"default_value": false,
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false
}
}
},
"raft_smoothing":
{
@ -6124,9 +6220,53 @@
"minimum_value": "0",
"minimum_value_warning": "raft_interface_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and not raft_remove_inside_corners",
"limit_to_extruder": "adhesion_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
"settable_per_extruder": false,
"children":
{
"raft_base_smoothing":
{
"label": "Raft Base Smoothing",
"description": "This setting controls how much inner corners in the raft base outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle.",
"unit": "mm",
"type": "float",
"value": "raft_smoothing",
"default_value": 5,
"minimum_value": "0",
"minimum_value_warning": "raft_interface_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and not raft_base_remove_inside_corners",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"raft_interface_smoothing":
{
"label": "Raft Middle Smoothing",
"description": "This setting controls how much inner corners in the raft middle outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle.",
"unit": "mm",
"type": "float",
"value": "raft_smoothing",
"default_value": 5,
"minimum_value": "0",
"minimum_value_warning": "raft_interface_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and not raft_interface_remove_inside_corners",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"raft_surface_smoothing":
{
"label": "Raft Top Smoothing",
"description": "This setting controls how much inner corners in the raft top outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle.",
"unit": "mm",
"type": "float",
"value": "raft_smoothing",
"default_value": 5,
"minimum_value": "0",
"minimum_value_warning": "raft_interface_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and not raft_surface_remove_inside_corners",
"settable_per_mesh": false,
"settable_per_extruder": false
}
}
},
"raft_airgap":
{
@ -6157,66 +6297,53 @@
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
},
"raft_surface_layers":
"raft_base_thickness":
{
"label": "Raft Top Layers",
"description": "The number of top layers on top of the 2nd raft layer. These are fully filled layers that the model sits on. 2 layers result in a smoother top surface than 1.",
"type": "int",
"default_value": 2,
"minimum_value": "0",
"maximum_value_warning": "20",
"label": "Raft Base Thickness",
"description": "Layer thickness of the base raft layer. This should be a thick layer which sticks firmly to the printer build plate.",
"unit": "mm",
"type": "float",
"default_value": 0.3,
"value": "resolveOrValue('layer_height_0') * 1.2",
"minimum_value": "0.001",
"minimum_value_warning": "0.04",
"maximum_value_warning": "0.75 * raft_base_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
"limit_to_extruder": "raft_base_extruder_nr"
},
"raft_surface_thickness":
"raft_base_line_width":
{
"label": "Raft Top Layer Thickness",
"description": "Layer thickness of the top raft layers.",
"label": "Raft Base Line Width",
"description": "Width of the lines in the base raft layer. These should be thick lines to assist in build plate adhesion.",
"unit": "mm",
"type": "float",
"default_value": 0.1,
"value": "resolveOrValue('layer_height')",
"default_value": 0.8,
"minimum_value": "0.001",
"minimum_value_warning": "0.04",
"maximum_value_warning": "0.75 * machine_nozzle_size",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"value": "machine_nozzle_size * 2",
"minimum_value_warning": "machine_nozzle_size * 0.5",
"maximum_value_warning": "machine_nozzle_size * 3",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
"limit_to_extruder": "raft_base_extruder_nr"
},
"raft_surface_line_width":
"raft_base_line_spacing":
{
"label": "Raft Top Line Width",
"description": "Width of the lines in the top surface of the raft. These can be thin lines so that the top of the raft becomes smooth.",
"label": "Raft Base Line Spacing",
"description": "The distance between the raft lines for the base raft layer. Wide spacing makes for easy removal of the raft from the build plate.",
"unit": "mm",
"type": "float",
"default_value": 0.4,
"value": "line_width",
"minimum_value": "0.001",
"minimum_value_warning": "machine_nozzle_size * 0.1",
"maximum_value_warning": "machine_nozzle_size * 2",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
},
"raft_surface_line_spacing":
{
"label": "Raft Top Spacing",
"description": "The distance between the raft lines for the top raft layers. The spacing should be equal to the line width, so that the surface is solid.",
"unit": "mm",
"type": "float",
"default_value": 0.4,
"default_value": 1.6,
"value": "raft_base_line_width * 2",
"minimum_value": "0",
"minimum_value_warning": "raft_surface_line_width",
"maximum_value_warning": "raft_surface_line_width * 3",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"value": "raft_surface_line_width",
"minimum_value_warning": "raft_base_line_width",
"maximum_value_warning": "100",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
"limit_to_extruder": "raft_base_extruder_nr"
},
"raft_interface_layers":
{
@ -6279,53 +6406,114 @@
"settable_per_extruder": true,
"limit_to_extruder": "raft_interface_extruder_nr"
},
"raft_base_thickness":
"raft_surface_layers":
{
"label": "Raft Base Thickness",
"description": "Layer thickness of the base raft layer. This should be a thick layer which sticks firmly to the printer build plate.",
"label": "Raft Top Layers",
"description": "The number of top layers on top of the 2nd raft layer. These are fully filled layers that the model sits on. 2 layers result in a smoother top surface than 1.",
"type": "int",
"default_value": 2,
"minimum_value": "0",
"maximum_value_warning": "20",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
},
"raft_surface_thickness":
{
"label": "Raft Top Layer Thickness",
"description": "Layer thickness of the top raft layers.",
"unit": "mm",
"type": "float",
"default_value": 0.3,
"value": "resolveOrValue('layer_height_0') * 1.2",
"default_value": 0.1,
"value": "resolveOrValue('layer_height')",
"minimum_value": "0.001",
"minimum_value_warning": "0.04",
"maximum_value_warning": "0.75 * raft_base_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"maximum_value_warning": "0.75 * machine_nozzle_size",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_base_extruder_nr"
"limit_to_extruder": "raft_surface_extruder_nr"
},
"raft_base_line_width":
"raft_surface_line_width":
{
"label": "Raft Base Line Width",
"description": "Width of the lines in the base raft layer. These should be thick lines to assist in build plate adhesion.",
"label": "Raft Top Line Width",
"description": "Width of the lines in the top surface of the raft. These can be thin lines so that the top of the raft becomes smooth.",
"unit": "mm",
"type": "float",
"default_value": 0.8,
"default_value": 0.4,
"value": "line_width",
"minimum_value": "0.001",
"value": "machine_nozzle_size * 2",
"minimum_value_warning": "machine_nozzle_size * 0.5",
"maximum_value_warning": "machine_nozzle_size * 3",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"minimum_value_warning": "machine_nozzle_size * 0.1",
"maximum_value_warning": "machine_nozzle_size * 2",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_base_extruder_nr"
"limit_to_extruder": "raft_surface_extruder_nr"
},
"raft_base_line_spacing":
"raft_surface_line_spacing":
{
"label": "Raft Base Line Spacing",
"description": "The distance between the raft lines for the base raft layer. Wide spacing makes for easy removal of the raft from the build plate.",
"label": "Raft Top Spacing",
"description": "The distance between the raft lines for the top raft layers. The spacing should be equal to the line width, so that the surface is solid.",
"unit": "mm",
"type": "float",
"default_value": 1.6,
"value": "raft_base_line_width * 2",
"default_value": 0.4,
"minimum_value": "0",
"minimum_value_warning": "raft_base_line_width",
"maximum_value_warning": "100",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"minimum_value_warning": "raft_surface_line_width",
"maximum_value_warning": "raft_surface_line_width * 3",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"value": "raft_surface_line_width",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_base_extruder_nr"
"limit_to_extruder": "raft_surface_extruder_nr"
},
"raft_wall_count":
{
"label": "Raft Wall Count",
"description": "The number of contours to print around the linear pattern of the raft.",
"type": "int",
"default_value": 1,
"minimum_value": "0",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false,
"children":
{
"raft_base_wall_count":
{
"label": "Raft Base Wall Count",
"description": "The number of contours to print around the linear pattern in the base layer of the raft.",
"type": "int",
"default_value": 1,
"value": "raft_wall_count",
"minimum_value": "0",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"raft_interface_wall_count":
{
"label": "Raft Middle Wall Count",
"description": "The number of contours to print around the linear pattern in the middle layers of the raft.",
"type": "int",
"default_value": 0,
"minimum_value": "0",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"raft_surface_wall_count":
{
"label": "Raft Top Wall Count",
"description": "The number of contours to print around the linear pattern in the top layers of the raft.",
"type": "int",
"default_value": 0,
"minimum_value": "0",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false
}
}
},
"raft_speed":
{
@ -6344,21 +6532,21 @@
"limit_to_extruder": "adhesion_extruder_nr",
"children":
{
"raft_surface_speed":
"raft_base_speed":
{
"label": "Raft Top Print Speed",
"description": "The speed at which the top raft layers are printed. These should be printed a bit slower, so that the nozzle can slowly smooth out adjacent surface lines.",
"label": "Raft Base Print Speed",
"description": "The speed at which the base raft layer is printed. This should be printed quite slowly, as the volume of material coming out of the nozzle is quite high.",
"unit": "mm/s",
"type": "float",
"default_value": 20,
"default_value": 15,
"minimum_value": "0.1",
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
"maximum_value_warning": "100",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"value": "raft_speed",
"maximum_value_warning": "200",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"value": "0.75 * raft_speed",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
"limit_to_extruder": "raft_base_extruder_nr"
},
"raft_interface_speed":
{
@ -6376,21 +6564,21 @@
"settable_per_extruder": true,
"limit_to_extruder": "raft_interface_extruder_nr"
},
"raft_base_speed":
"raft_surface_speed":
{
"label": "Raft Base Print Speed",
"description": "The speed at which the base raft layer is printed. This should be printed quite slowly, as the volume of material coming out of the nozzle is quite high.",
"label": "Raft Top Print Speed",
"description": "The speed at which the top raft layers are printed. These should be printed a bit slower, so that the nozzle can slowly smooth out adjacent surface lines.",
"unit": "mm/s",
"type": "float",
"default_value": 15,
"default_value": 20,
"minimum_value": "0.1",
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
"maximum_value_warning": "200",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"value": "0.75 * raft_speed",
"maximum_value_warning": "100",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"value": "raft_speed",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_base_extruder_nr"
"limit_to_extruder": "raft_surface_extruder_nr"
}
}
},
@ -6410,10 +6598,10 @@
"limit_to_extruder": "adhesion_extruder_nr",
"children":
{
"raft_surface_acceleration":
"raft_base_acceleration":
{
"label": "Raft Top Print Acceleration",
"description": "The acceleration with which the top raft layers are printed.",
"label": "Raft Base Print Acceleration",
"description": "The acceleration with which the base raft layer is printed.",
"unit": "mm/s\u00b2",
"type": "float",
"default_value": 3000,
@ -6421,9 +6609,9 @@
"minimum_value": "0.1",
"minimum_value_warning": "100",
"maximum_value_warning": "10000",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and resolveOrValue('acceleration_enabled') and raft_surface_layers > 0",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and resolveOrValue('acceleration_enabled')",
"settable_per_mesh": false,
"limit_to_extruder": "raft_surface_extruder_nr"
"limit_to_extruder": "raft_base_extruder_nr"
},
"raft_interface_acceleration":
{
@ -6440,10 +6628,10 @@
"settable_per_mesh": false,
"limit_to_extruder": "raft_interface_extruder_nr"
},
"raft_base_acceleration":
"raft_surface_acceleration":
{
"label": "Raft Base Print Acceleration",
"description": "The acceleration with which the base raft layer is printed.",
"label": "Raft Top Print Acceleration",
"description": "The acceleration with which the top raft layers are printed.",
"unit": "mm/s\u00b2",
"type": "float",
"default_value": 3000,
@ -6451,9 +6639,9 @@
"minimum_value": "0.1",
"minimum_value_warning": "100",
"maximum_value_warning": "10000",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and resolveOrValue('acceleration_enabled')",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and resolveOrValue('acceleration_enabled') and raft_surface_layers > 0",
"settable_per_mesh": false,
"limit_to_extruder": "raft_base_extruder_nr"
"limit_to_extruder": "raft_surface_extruder_nr"
}
}
},
@ -6473,20 +6661,20 @@
"limit_to_extruder": "adhesion_extruder_nr",
"children":
{
"raft_surface_jerk":
"raft_base_jerk":
{
"label": "Raft Top Print Jerk",
"description": "The jerk with which the top raft layers are printed.",
"label": "Raft Base Print Jerk",
"description": "The jerk with which the base raft layer is printed.",
"unit": "mm/s",
"type": "float",
"default_value": 20,
"value": "raft_jerk",
"minimum_value": "0",
"minimum_value_warning": "5",
"maximum_value_warning": "100",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and resolveOrValue('jerk_enabled') and raft_surface_layers > 0",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and resolveOrValue('jerk_enabled')",
"settable_per_mesh": false,
"limit_to_extruder": "raft_surface_extruder_nr"
"limit_to_extruder": "raft_base_extruder_nr"
},
"raft_interface_jerk":
{
@ -6503,20 +6691,20 @@
"settable_per_mesh": false,
"limit_to_extruder": "raft_interface_extruder_nr"
},
"raft_base_jerk":
"raft_surface_jerk":
{
"label": "Raft Base Print Jerk",
"description": "The jerk with which the base raft layer is printed.",
"label": "Raft Top Print Jerk",
"description": "The jerk with which the top raft layers are printed.",
"unit": "mm/s",
"type": "float",
"default_value": 20,
"value": "raft_jerk",
"minimum_value": "0",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and resolveOrValue('jerk_enabled')",
"maximum_value_warning": "100",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and resolveOrValue('jerk_enabled') and raft_surface_layers > 0",
"settable_per_mesh": false,
"limit_to_extruder": "raft_base_extruder_nr"
"limit_to_extruder": "raft_surface_extruder_nr"
}
}
},
@ -6535,20 +6723,20 @@
"limit_to_extruder": "adhesion_extruder_nr",
"children":
{
"raft_surface_fan_speed":
"raft_base_fan_speed":
{
"label": "Raft Top Fan Speed",
"description": "The fan speed for the top raft layers.",
"label": "Raft Base Fan Speed",
"description": "The fan speed for the base raft layer.",
"unit": "%",
"type": "float",
"minimum_value": "0",
"maximum_value": "100",
"default_value": 0,
"value": "raft_fan_speed",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_surface_extruder_nr"
"limit_to_extruder": "raft_base_extruder_nr"
},
"raft_interface_fan_speed":
{
@ -6565,20 +6753,20 @@
"settable_per_extruder": true,
"limit_to_extruder": "raft_interface_extruder_nr"
},
"raft_base_fan_speed":
"raft_surface_fan_speed":
{
"label": "Raft Base Fan Speed",
"description": "The fan speed for the base raft layer.",
"label": "Raft Top Fan Speed",
"description": "The fan speed for the top raft layers.",
"unit": "%",
"type": "float",
"minimum_value": "0",
"maximum_value": "100",
"default_value": 0,
"value": "raft_fan_speed",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"enabled": "resolveOrValue('adhesion_type') == 'raft' and raft_surface_layers > 0",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "raft_base_extruder_nr"
"limit_to_extruder": "raft_surface_extruder_nr"
}
}
}
@ -6689,7 +6877,7 @@
"type": "float",
"unit": "mm",
"enabled": "resolveOrValue('prime_tower_enable') and (resolveOrValue('prime_tower_brim_enable') or resolveOrValue('adhesion_type') == 'raft')",
"default_value": "resolveOrValue('brim_width')",
"default_value": 1.2,
"minimum_value": "0",
"maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)",
"settable_per_mesh": false,
@ -8273,28 +8461,6 @@
"settable_per_mesh": true,
"settable_per_extruder": true
},
"raft_remove_inside_corners":
{
"label": "Remove Raft Inside Corners",
"description": "Remove inside corners from the raft, causing the raft to become convex.",
"type": "bool",
"default_value": false,
"resolve": "any(extruderValues('raft_remove_inside_corners'))",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"raft_base_wall_count":
{
"label": "Raft Base Wall Count",
"description": "The number of contours to print around the linear pattern in the base layer of the raft.",
"type": "int",
"default_value": 1,
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"resolve": "max(extruderValues('raft_base_wall_count'))",
"settable_per_mesh": false,
"settable_per_extruder": false
},
"group_outer_walls":
{
"label": "Group Outer Walls",
@ -8329,7 +8495,7 @@
{
"label": "Flow Warning",
"description": "Limit on the flow warning for detection.",
"default_value": "15.0",
"default_value": 15.0,
"enabled": "ppr_enable",
"unit": "%",
"type": "float",
@ -8339,7 +8505,7 @@
{
"label": "Flow Limit",
"description": "Limit on flow anomaly for detection.",
"default_value": "25.0",
"default_value": 25.0,
"enabled": "ppr_enable",
"unit": "%",
"type": "float",
@ -8351,7 +8517,7 @@
"description": "Limit on Print temperature warning for detection.",
"unit": "\u00b0C",
"type": "float",
"default_value": "3.0",
"default_value": 3.0,
"enabled": "ppr_enable",
"settable_per_extruder": true
},
@ -8361,7 +8527,7 @@
"description": "Limit on Print Temperature anomaly for detection.",
"unit": "\u00b0C",
"type": "float",
"default_value": "7.0",
"default_value": 7.0,
"enabled": "ppr_enable",
"settable_per_extruder": true
},
@ -8371,7 +8537,7 @@
"description": "Limit on Build Volume Temperature warning for detection.",
"unit": "\u00b0C",
"type": "float",
"default_value": "7.5",
"default_value": 7.5,
"enabled": "ppr_enable",
"settable_per_extruder": false
},
@ -8381,7 +8547,7 @@
"description": "Limit on Build Volume temperature Anomaly for detection.",
"unit": "\u00b0C",
"type": "float",
"default_value": "10.0",
"default_value": 10.0,
"enabled": "ppr_enable",
"settable_per_extruder": false
}

View File

@ -354,11 +354,12 @@
"print_sequence": { "enabled": false },
"raft_base_line_spacing": { "value": "2*raft_base_line_width" },
"raft_base_line_width": { "value": 1.4 },
"raft_base_speed": { "value": 5 },
"raft_base_speed": { "value": 10 },
"raft_base_thickness": { "value": 0.8 },
"raft_interface_extruder_nr": { "value": "raft_surface_extruder_nr" },
"raft_interface_layers": { "value": 2 },
"raft_interface_line_width": { "value": 1.2 },
"raft_interface_line_width": { "value": 0.7 },
"raft_interface_speed": { "value": 90 },
"raft_interface_thickness": { "value": 0.3 },
"raft_margin": { "value": 3 },
"raft_surface_extruder_nr": { "value": "int(anyExtruderWithMaterial('material_is_support_material')) if support_enable and extruderValue(support_extruder_nr,'material_is_support_material') else raft_base_extruder_nr" },

View File

@ -15,7 +15,7 @@ variant = CC 0.4
infill_sparse_density = 100
jerk_print = 30
material_shrinkage_percentage_xy = 100.3
material_shrinkage_percentage_z = 102.0
material_shrinkage_percentage_z = 100.9
speed_infill = =speed_print
speed_layer_0 = 20
speed_print = 25

View File

@ -15,7 +15,7 @@ variant = CC 0.4
infill_sparse_density = 100
jerk_print = 30
material_shrinkage_percentage_xy = 100.3
material_shrinkage_percentage_z = 102.0
material_shrinkage_percentage_z = 100.9
speed_infill = =speed_print
speed_layer_0 = 20
speed_print = 20

View File

@ -15,7 +15,7 @@ variant = CC 0.6
infill_sparse_density = 100
jerk_print = 30
material_shrinkage_percentage_xy = 100.3
material_shrinkage_percentage_z = 102.0
material_shrinkage_percentage_z = 100.9
speed_infill = =speed_print
speed_layer_0 = 20
speed_print = 25

View File

@ -15,7 +15,7 @@ variant = CC 0.6
infill_sparse_density = 100
jerk_print = 30
material_shrinkage_percentage_xy = 100.3
material_shrinkage_percentage_z = 102.0
material_shrinkage_percentage_z = 100.9
speed_infill = =speed_print
speed_layer_0 = 20
speed_print = 25

View File

@ -15,7 +15,7 @@ variant = CC 0.4
infill_sparse_density = 100
jerk_print = 30
material_shrinkage_percentage_xy = 100.3
material_shrinkage_percentage_z = 102.0
material_shrinkage_percentage_z = 100.9
speed_infill = =speed_print
speed_layer_0 = 20
speed_print = 25

View File

@ -15,7 +15,7 @@ variant = CC 0.4
infill_sparse_density = 100
jerk_print = 30
material_shrinkage_percentage_xy = 100.3
material_shrinkage_percentage_z = 102.0
material_shrinkage_percentage_z = 100.9
speed_infill = =speed_print
speed_layer_0 = 20
speed_print = 20

View File

@ -15,7 +15,7 @@ variant = CC 0.6
infill_sparse_density = 100
jerk_print = 30
material_shrinkage_percentage_xy = 100.3
material_shrinkage_percentage_z = 102.0
material_shrinkage_percentage_z = 100.9
speed_infill = =speed_print
speed_layer_0 = 20
speed_print = 25

View File

@ -15,7 +15,7 @@ variant = CC 0.6
infill_sparse_density = 100
jerk_print = 30
material_shrinkage_percentage_xy = 100.3
material_shrinkage_percentage_z = 102.0
material_shrinkage_percentage_z = 100.9
speed_infill = =speed_print
speed_layer_0 = 20
speed_print = 25

View File

@ -123,7 +123,7 @@ UM.PreferencesPage
var idx = -1;
for(var i = 0; i < settingVisibilityPresetsModel.items.length; ++i)
{
if(settingVisibilityPresetsModel.items[i].presetId == settingVisibilityPresetsModel.activePreset)
if(settingVisibilityPresetsModel.items[i].presetId === settingVisibilityPresetsModel.activePreset)
{
idx = i;
break;
@ -159,7 +159,7 @@ UM.PreferencesPage
id: definitionsModel
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
showAll: true
exclude: ["machine_settings", "command_line_settings"]
exclude: ["machine_settings", "command_line_settings", "ppr"]
showAncestors: true
expanded: ["*"]
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
@ -173,13 +173,13 @@ UM.PreferencesPage
id: loader
width: settingsListView.width - scrollBar.width
height: model.type != undefined ? UM.Theme.getSize("section").height : 0
height: model.type !== undefined ? UM.Theme.getSize("section").height : 0
property var definition: model
property var settingDefinitionsModel: definitionsModel
asynchronous: true
active: model.type != undefined
active: model.type !== undefined
sourceComponent:
{
switch (model.type)

View File

@ -11,7 +11,7 @@ Cura.ExpandablePopup
{
id: machineSelector
property Cura.MachineManager machineManager
property var machineManager: Cura.MachineManager
property bool isNetworkPrinter: machineManager.activeMachineHasNetworkConnection
property bool isConnectedCloudPrinter: machineManager.activeMachineHasCloudConnection
property bool isCloudRegistered: machineManager.activeMachineHasCloudRegistration
@ -107,6 +107,7 @@ Cura.ExpandablePopup
{
return UM.Theme.getIcon("Printer", "medium")
}
else
{
return ""

View File

@ -332,6 +332,10 @@ brim_outside_only
brim_smart_ordering
raft_margin
raft_smoothing
raft_remove_inside_corners
raft_base_smoothing
raft_interface_smoothing
raft_surface_smoothing
raft_airgap
raft_surface_layers
raft_surface_thickness
@ -344,6 +348,7 @@ raft_interface_line_spacing
raft_base_thickness
raft_base_line_width
raft_base_line_spacing
raft_wall_count
raft_speed
raft_acceleration
raft_jerk