diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000000..97c849144d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,39 @@
+
+
+**Application Version**
+
+
+**Platform**
+
+
+**Qt**
+
+
+**PyQt**
+
+
+**Display Driver**
+
+
+**Steps to Reproduce**
+
+
+**Actual Results**
+
+
+**Expected results**
+
+
+**Additional Information**
+
diff --git a/.gitignore b/.gitignore
index 8d9ba4b2d2..a91d3f9377 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,20 +33,22 @@ cura.desktop
.settings
#Externally located plug-ins.
-plugins/CuraSolidWorksPlugin
-plugins/Doodle3D-cura-plugin
-plugins/GodMode
-plugins/PostProcessingPlugin
-plugins/X3GWriter
-plugins/FlatProfileExporter
-plugins/ProfileFlattener
-plugins/cura-god-mode-plugin
plugins/cura-big-flame-graph
+plugins/cura-god-mode-plugin
plugins/cura-siemensnx-plugin
-plugins/CuraVariSlicePlugin
+plugins/CuraBlenderPlugin
+plugins/CuraCloudPlugin
plugins/CuraLiveScriptingPlugin
+plugins/CuraOpenSCADPlugin
plugins/CuraPrintProfileCreator
+plugins/CuraSolidWorksPlugin
+plugins/CuraVariSlicePlugin
+plugins/Doodle3D-cura-plugin
+plugins/FlatProfileExporter
+plugins/GodMode
plugins/OctoPrintPlugin
+plugins/ProfileFlattener
+plugins/X3GWriter
#Build stuff
CMakeCache.txt
diff --git a/Jenkinsfile b/Jenkinsfile
index 6408cbf9b2..83104aea18 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,45 +1,47 @@
parallel_nodes(['linux && cura', 'windows && cura']) {
- // Prepare building
- stage('Prepare') {
- // Ensure we start with a clean build directory.
- step([$class: 'WsCleanup'])
+ timeout(time: 2, unit: "HOURS") {
+ // Prepare building
+ stage('Prepare') {
+ // Ensure we start with a clean build directory.
+ step([$class: 'WsCleanup'])
- // Checkout whatever sources are linked to this pipeline.
- checkout scm
- }
+ // Checkout whatever sources are linked to this pipeline.
+ checkout scm
+ }
- // If any error occurs during building, we want to catch it and continue with the "finale" stage.
- catchError {
- // Building and testing should happen in a subdirectory.
- dir('build') {
- // Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
- stage('Build') {
- def branch = env.BRANCH_NAME
- if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
- branch = "master"
+ // If any error occurs during building, we want to catch it and continue with the "finale" stage.
+ catchError {
+ // Building and testing should happen in a subdirectory.
+ dir('build') {
+ // Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
+ stage('Build') {
+ def branch = env.BRANCH_NAME
+ if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
+ branch = "master"
+ }
+
+ // Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
+ def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
+ cmake("..", "-DCMAKE_PREFIX_PATH=\"${env.CURA_ENVIRONMENT_PATH}/${branch}\" -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=\"${uranium_dir}\"")
}
- // Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
- def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
- cmake("..", "-DCMAKE_PREFIX_PATH=\"${env.CURA_ENVIRONMENT_PATH}/${branch}\" -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=\"${uranium_dir}\"")
- }
-
- // Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
- stage('Unit Test') {
- try {
- make('test')
- } catch(e) {
- currentBuild.result = "UNSTABLE"
+ // Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
+ stage('Unit Test') {
+ try {
+ make('test')
+ } catch(e) {
+ currentBuild.result = "UNSTABLE"
+ }
}
}
}
- }
- // Perform any post-build actions like notification and publishing of unit tests.
- stage('Finalize') {
- // Publish the test results to Jenkins.
- junit allowEmptyResults: true, testResults: 'build/junit*.xml'
+ // Perform any post-build actions like notification and publishing of unit tests.
+ stage('Finalize') {
+ // Publish the test results to Jenkins.
+ junit allowEmptyResults: true, testResults: 'build/junit*.xml'
- notify_build_result(env.CURA_EMAIL_RECIPIENTS, '#cura-dev', ['master', '2.'])
+ notify_build_result(env.CURA_EMAIL_RECIPIENTS, '#cura-dev', ['master', '2.'])
+ }
}
}
diff --git a/README.md b/README.md
index 90f17a5463..70466e9c22 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,16 @@
Cura
====
-
-This is the new, shiny frontend for Cura. [daid/Cura](https://github.com/daid/Cura.git) is the old legacy Cura that everyone knows and loves/hates.
-
-We re-worked the whole GUI code at Ultimaker, because the old code started to become unmaintainable.
-
+This is the new, shiny frontend for Cura. Check [daid/LegacyCura](https://github.com/daid/LegacyCura) for the legacy Cura that everyone knows and loves/hates. We re-worked the whole GUI code at Ultimaker, because the old code started to become unmaintainable.
Logging Issues
------------
-Use [this](https://github.com/Ultimaker/Uranium/wiki/Bug-Reporting-Template) template to report issues. New issues that do not adhere to this template will take us a lot longer to handle and will therefore have a lower pirority.
-
For crashes and similar issues, please attach the following information:
* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output)
* The Cura GUI log file, located at
- * %APPDATA%\cura\\``\cura.log (Windows), or usually C:\Users\\``\AppData\Roaming\cura\\``\cura.log
- * $User/Library/Application Support/cura/``/cura.log (OSX)
- * $USER/.local/share/cura/``/cura.log (Ubuntu/Linux)
+ * `%APPDATA%\cura\\cura.log` (Windows), or usually `C:\Users\\\AppData\Roaming\cura\\cura.log`
+ * `$USER/Library/Application Support/cura//cura.log` (OSX)
+ * `$USER/.local/share/cura//cura.log` (Ubuntu/Linux)
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
@@ -24,72 +18,35 @@ For additional support, you could also ask in the #cura channel on FreeNode IRC.
Dependencies
------------
-
-* [Uranium](https://github.com/Ultimaker/Uranium)
- Cura is built on top of the Uranium framework.
-* [CuraEngine](https://github.com/Ultimaker/CuraEngine)
- This will be needed at runtime to perform the actual slicing.
-* [PySerial](https://github.com/pyserial/pyserial)
- Only required for USB printing support.
-* [python-zeroconf](https://github.com/jstasiak/python-zeroconf)
- Only required to detect mDNS-enabled printers
-
-Configuring Cura
-----------------
-Link your CuraEngine backend by inserting the following lines in `$HOME/.config/cura/config.cfg` :
-```
-[backend]
-location = /[path_to_the..]/CuraEngine/build/CuraEngine
-```
+* [Uranium](https://github.com/Ultimaker/Uranium) Cura is built on top of the Uranium framework.
+* [CuraEngine](https://github.com/Ultimaker/CuraEngine) This will be needed at runtime to perform the actual slicing.
+* [PySerial](https://github.com/pyserial/pyserial) Only required for USB printing support.
+* [python-zeroconf](https://github.com/jstasiak/python-zeroconf) Only required to detect mDNS-enabled printers
Build scripts
-------------
+Please checkout [cura-build](https://github.com/Ultimaker/cura-build) for detailed building instructions.
-Please checkout [cura-build](https://github.com/Ultimaker/cura-build)
-
-Third party plugins
+Running from Source
-------------
-* [Post Processing Plugin](https://github.com/nallath/PostProcessingPlugin): Allows for post-processing scripts to run on g-code.
-* [Barbarian Plugin](https://github.com/nallath/BarbarianPlugin): Simple scale tool for imperial to metric.
-* [X3G Writer](https://github.com/Ghostkeeper/X3GWriter): Adds support for exporting X3G files.
-* [Auto orientation](https://github.com/nallath/CuraOrientationPlugin): Calculate the optimal orientation for a model.
-* [OctoPrint Plugin](https://github.com/fieldofview/OctoPrintPlugin): Send printjobs directly to OctoPrint and monitor their progress in Cura.
-* [Electric Print Cost Calculator Plugin](https://github.com/zoff99/ElectricPrintCostCalculator): Calculate the electric costs of a print.
+Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source) for details about running Cura from source.
-Making profiles for other printers
-----------------------------------
-If your make of printer is not in the list of supported printers, and using the "Custom FDM Printer" does not offer enough flexibility, you can use [this](https://github.com/Ultimaker/Cura/blob/master/resources/definitions/ultimaker_original.def.json) as a template.
+Plugins
+-------------
+Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Plugin-Directory) for details about creating and using plugins.
-* Change the machine ID to something unique
-* Change the machine_name to your printer's name
-* If you have a 3D model of your platform you can put it in resources/meshes and put its name under platform
-* Set your machine's dimensions with machine_width, machine_depth, and machine_height
-* If your printer's origin is in the center of the bed, set machine_center_is_zero to true.
-* Set your print head dimensions with the machine_head_shape parameters
-* Set the start and end gcode in machine_start_gcode and machine_end_gcode
+Supported printers
+-------------
+Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Adding-new-machine-profiles-to-Cura) for guidelines about adding support for new machines.
-Once you are done, put the profile you have made into resources/definitions, or in definitions in your cura profile folder.
-
-If you want to make a definition for a multi-extrusion printer, have a look at [this](https://github.com/Ultimaker/Cura/blob/master/resources/definitions/ultimaker_original_dual.def.json) as a template, along with the two extruder definitions it references [here](https://github.com/Ultimaker/Cura/blob/master/resources/extruders/ultimaker_original_dual_1st.def.json) and [here](https://github.com/Ultimaker/Cura/blob/master/resources/extruders/ultimaker_original_dual_2nd.def.json)
+Configuring Cura
+----------------
+Please check out [Wiki page](https://github.com/Ultimaker/Cura/wiki/Cura-Settings) about configuration options for developers.
Translating Cura
----------------
-If you'd like to contribute a translation of Cura, please first look for [any existing translation](https://github.com/Ultimaker/Cura/tree/master/resources/i18n). If your language is already there in the source code but not in Cura's interface, it may be partially translated.
+Please check out [Wiki page](https://github.com/Ultimaker/Cura/wiki/Translating-Cura) about how to translate Cura into other languages.
-There are four files that need to be translated for Cura:
-1. https://github.com/Ultimaker/Cura/blob/master/resources/i18n/cura.pot
-2. https://github.com/Ultimaker/Cura/blob/master/resources/i18n/fdmextruder.def.json.pot
-3. https://github.com/Ultimaker/Cura/blob/master/resources/i18n/fdmprinter.def.json.pot (This one is the most work.)
-4. https://github.com/Ultimaker/Uranium/blob/master/resources/i18n/uranium.pot
-
-Copy these files and rename them to `*.po` (remove the `t`). Then create the actual translations by filling in the empty `msgstr` entries. These are gettext files, which are plain text so you can open them with any text editor such as Notepad or GEdit, but it is probably easier with a specialised tool such as [POEdit](https://poedit.net/) or [Virtaal](http://virtaal.translatehouse.org/).
-
-Do not hestiate to ask us about a translation or the meaning of some text via Github Issues.
-
-Once the translation is complete, it's probably best to test them in Cura. Use your favourite software to convert the .po file to a .mo file (such as [GetText](https://www.gnu.org/software/gettext/)). Then put the .mo files in the `.../resources/i18n//LC_MESSAGES` folder in your Cura installation. Then find your Cura configuration file (next to the log as described above, except on Linux where it is located in `~/.config/cura`) and change the language preference to the name of the folder you just created. Then start Cura. If working correctly, your Cura should now be translated.
-
-To submit your translation, ideally you would make two pull requests where all `*.po` files are located in that same `` folder in the resources of both the Cura and Uranium repositories. Put `cura.po`, `fdmprinter.def.json.po` and `fdmextruder.def.json.po` in the Cura repository, and put `uranium.po` in the Uranium repository. Then submit the pull requests to Github. For people with less experience with Git, you can also e-mail the translations to the e-mail address listed at the top of the [cura.pot](https://github.com/Ultimaker/Cura/blob/master/resources/i18n/cura.pot) file as the `Report-Msgid-Bugs-To` entry and we'll make sure it gets checked and included.
-
-After the translation is submitted, the Cura maintainers will check for its completeness and check whether it is consistent. We will take special care to look for common mistakes, such as translating mark-up `` code and such. We are often not fluent in every language, so we expect the translator and the international users to make corrections where necessary. Of course, there will always be some mistakes in every translation.
-
-When the next Cura release comes around, some of the texts will have changed and some new texts will have been added. Around the time when the beta is released we will invoke a string freeze, meaning that no developer is allowed to make changes to the texts. Then we will update the translation template `.pot` files and ask all our translators to update their translations. If you are unable to update the translation in time for the actual release, we will remove the language from the drop-down menu in the Preferences window. The translation stays in Cura however, so that someone might pick it up again later and update it with the newest texts. Also, users who had previously selected the language can still continue Cura in their language but English text will appear among the original text.
+License
+----------------
+Cura is released under the terms of the LGPLv3 or higher. A copy of this license should be included with the software.
diff --git a/cmake/CuraTests.cmake b/cmake/CuraTests.cmake
index a8af16c28c..ffe4616bf3 100644
--- a/cmake/CuraTests.cmake
+++ b/cmake/CuraTests.cmake
@@ -24,16 +24,23 @@ function(cura_add_test)
if(WIN32)
string(REPLACE "|" "\\;" _PYTHONPATH ${_PYTHONPATH})
+ set(_PYTHONPATH "${_PYTHONPATH}\\;$ENV{PYTHONPATH}")
else()
string(REPLACE "|" ":" _PYTHONPATH ${_PYTHONPATH})
+ set(_PYTHONPATH "${_PYTHONPATH}:$ENV{PYTHONPATH}")
endif()
- add_test(
- NAME ${_NAME}
- COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
- )
- set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
- set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
+ get_test_property(${_NAME} ENVIRONMENT test_exists) #Find out if the test exists by getting a property from it that always exists (such as ENVIRONMENT because we set that ourselves).
+ if (NOT ${test_exists})
+ add_test(
+ NAME ${_NAME}
+ COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
+ )
+ set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
+ set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
+ else()
+ message(WARNING "Duplicate test ${_NAME}!")
+ endif()
endfunction()
cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
diff --git a/cura.appdata.xml b/cura.appdata.xml
index 5c67814fae..2d8bff15ec 100644
--- a/cura.appdata.xml
+++ b/cura.appdata.xml
@@ -3,7 +3,7 @@
cura.desktopCC0-1.0
- AGPL-3.0 and CC-BY-SA-4.0
+ LGPL-3.0 and CC-BY-SA-4.0CuraThe world's most advanced 3d printer software
@@ -15,7 +15,7 @@
Novices can start printing right away
-
Experts are able to customize 200 settings to achieve the best results
+
Experts are able to customize 300 settings to achieve the best results
Optimized profiles for Ultimaker materials
Supported by a global network of Ultimaker certified service partners
Print multiple objects at once with different settings for each object
@@ -26,6 +26,6 @@
http://software.ultimaker.com/Cura.png
- https://ultimaker.com/en/products/cura-software
+ https://ultimaker.com/en/products/cura-software?utm_source=cura&utm_medium=software&utm_campaign=resourcesCura
diff --git a/cura/Arrange.py b/cura/Arranging/Arrange.py
old mode 100755
new mode 100644
similarity index 92%
rename from cura/Arrange.py
rename to cura/Arranging/Arrange.py
index 335e42a267..a90a97c3c2
--- a/cura/Arrange.py
+++ b/cura/Arranging/Arrange.py
@@ -1,8 +1,8 @@
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
from UM.Math.Vector import Vector
-from cura.ShapeArray import ShapeArray
-from cura import ZOffsetDecorator
+from cura.Arranging.ShapeArray import ShapeArray
+from cura.Scene import ZOffsetDecorator
from collections import namedtuple
@@ -30,6 +30,7 @@ class Arrange:
self._offset_x = offset_x
self._offset_y = offset_y
self._last_priority = 0
+ self._is_empty = True
## Helper to create an Arranger instance
#
@@ -38,8 +39,8 @@ class Arrange:
# \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed
@classmethod
- def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5):
- arranger = Arrange(220, 220, 110, 110, scale = scale)
+ def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 220, y = 220):
+ arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst()
if fixed_nodes is None:
@@ -64,7 +65,7 @@ class Arrange:
for area in disallowed_areas:
points = copy.deepcopy(area._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
- arranger.place(0, 0, shape_arr)
+ arranger.place(0, 0, shape_arr, update_empty = False)
return arranger
## Find placement for a node (using offset shape) and place it (using hull shape)
@@ -168,7 +169,8 @@ class Arrange:
# \param x x-coordinate
# \param y y-coordinate
# \param shape_arr ShapeArray object
- def place(self, x, y, shape_arr):
+ # \param update_empty updates the _is_empty, used when adding disallowed areas
+ def place(self, x, y, shape_arr, update_empty = True):
x = int(self._scale * x)
y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x
@@ -181,10 +183,17 @@ class Arrange:
max_y = min(max(offset_y + shape_arr.arr.shape[0], 0), shape_y - 1)
occupied_slice = self._occupied[min_y:max_y, min_x:max_x]
# we use a slice of shape because it can be out of bounds
- occupied_slice[numpy.where(shape_arr.arr[
- min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 1
+ new_occupied = numpy.where(shape_arr.arr[
+ min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)
+ if update_empty and new_occupied:
+ self._is_empty = False
+ occupied_slice[new_occupied] = 1
# Set priority to low (= high number), so it won't get picked at trying out.
prio_slice = self._priority[min_y:max_y, min_x:max_x]
prio_slice[numpy.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 999
+
+ @property
+ def isEmpty(self):
+ return self._is_empty
diff --git a/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py b/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py
new file mode 100644
index 0000000000..3f23b0dbe7
--- /dev/null
+++ b/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Job import Job
+from UM.Scene.SceneNode import SceneNode
+from UM.Math.Vector import Vector
+from UM.Operations.TranslateOperation import TranslateOperation
+from UM.Operations.GroupedOperation import GroupedOperation
+from UM.Message import Message
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("cura")
+
+from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
+from cura.Arranging.Arrange import Arrange
+from cura.Arranging.ShapeArray import ShapeArray
+
+from typing import List
+
+
+class ArrangeArray:
+ def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]):
+ self._x = x
+ self._y = y
+ self._fixed_nodes = fixed_nodes
+ self._count = 0
+ self._first_empty = None
+ self._has_empty = False
+ self._arrange = []
+
+ def _update_first_empty(self):
+ for i, a in enumerate(self._arrange):
+ if a.isEmpty:
+ self._first_empty = i
+ self._has_empty = True
+ return
+ self._first_empty = None
+ self._has_empty = False
+
+ def add(self):
+ new_arrange = Arrange.create(x = self._x, y = self._y, fixed_nodes = self._fixed_nodes)
+ self._arrange.append(new_arrange)
+ self._count += 1
+ self._update_first_empty()
+
+ def count(self):
+ return self._count
+
+ def get(self, index):
+ return self._arrange[index]
+
+ def getFirstEmpty(self):
+ if not self._is_empty:
+ self.add()
+ return self._arrange[self._first_empty]
+
+
+class ArrangeObjectsAllBuildPlatesJob(Job):
+ def __init__(self, nodes: List[SceneNode], min_offset = 8):
+ super().__init__()
+ self._nodes = nodes
+ self._min_offset = min_offset
+
+ def run(self):
+ status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"),
+ lifetime = 0,
+ dismissable=False,
+ progress = 0,
+ title = i18n_catalog.i18nc("@info:title", "Finding Location"))
+ status_message.show()
+
+
+ # Collect nodes to be placed
+ nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
+ for node in self._nodes:
+ offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset)
+ nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr))
+
+ # Sort the nodes with the biggest area first.
+ nodes_arr.sort(key=lambda item: item[0])
+ nodes_arr.reverse()
+
+ x, y = 200, 200
+
+ arrange_array = ArrangeArray(x = x, y = y, fixed_nodes = [])
+ arrange_array.add()
+
+ # Place nodes one at a time
+ start_priority = 0
+ grouped_operation = GroupedOperation()
+ found_solution_for_all = True
+ left_over_nodes = [] # nodes that do not fit on an empty build plate
+
+ for idx, (size, node, offset_shape_arr, hull_shape_arr) in enumerate(nodes_arr):
+ # For performance reasons, we assume that when a location does not fit,
+ # it will also not fit for the next object (while what can be untrue).
+ # We also skip possibilities by slicing through the possibilities (step = 10)
+
+ try_placement = True
+
+ current_build_plate_number = 0 # always start with the first one
+
+ # # Only for first build plate
+ # if last_size == size and last_build_plate_number == current_build_plate_number:
+ # # This optimization works if many of the objects have the same size
+ # # Continue with same build plate number
+ # start_priority = last_priority
+ # else:
+ # start_priority = 0
+
+ while try_placement:
+ # make sure that current_build_plate_number is not going crazy or you'll have a lot of arrange objects
+ while current_build_plate_number >= arrange_array.count():
+ arrange_array.add()
+ arranger = arrange_array.get(current_build_plate_number)
+
+ best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority, step=10)
+ x, y = best_spot.x, best_spot.y
+ node.removeDecorator(ZOffsetDecorator)
+ if node.getBoundingBox():
+ center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
+ else:
+ center_y = 0
+ if x is not None: # We could find a place
+ arranger.place(x, y, hull_shape_arr) # place the object in the arranger
+
+ node.callDecoration("setBuildPlateNumber", current_build_plate_number)
+ grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
+ try_placement = False
+ else:
+ # very naive, because we skip to the next build plate if one model doesn't fit.
+ if arranger.isEmpty:
+ # apparently we can never place this object
+ left_over_nodes.append(node)
+ try_placement = False
+ else:
+ # try next build plate
+ current_build_plate_number += 1
+ try_placement = True
+
+ status_message.setProgress((idx + 1) / len(nodes_arr) * 100)
+ Job.yieldThread()
+
+ for node in left_over_nodes:
+ node.callDecoration("setBuildPlateNumber", -1) # these are not on any build plate
+ found_solution_for_all = False
+
+ grouped_operation.push()
+
+ status_message.hide()
+
+ if not found_solution_for_all:
+ no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"),
+ title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
+ no_full_solution_message.show()
diff --git a/cura/ArrangeObjectsJob.py b/cura/Arranging/ArrangeObjectsJob.py
old mode 100755
new mode 100644
similarity index 95%
rename from cura/ArrangeObjectsJob.py
rename to cura/Arranging/ArrangeObjectsJob.py
index d650fd7f57..765c3333cb
--- a/cura/ArrangeObjectsJob.py
+++ b/cura/Arranging/ArrangeObjectsJob.py
@@ -4,7 +4,6 @@
from UM.Job import Job
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
-from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Operations.TranslateOperation import TranslateOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Logger import Logger
@@ -12,9 +11,9 @@ from UM.Message import Message
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
-from cura.ZOffsetDecorator import ZOffsetDecorator
-from cura.Arrange import Arrange
-from cura.ShapeArray import ShapeArray
+from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
+from cura.Arranging.Arrange import Arrange
+from cura.Arranging.ShapeArray import ShapeArray
from typing import List
@@ -89,3 +88,5 @@ class ArrangeObjectsJob(Job):
no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"),
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
no_full_solution_message.show()
+
+ self.finished.emit(self)
diff --git a/cura/ShapeArray.py b/cura/Arranging/ShapeArray.py
old mode 100755
new mode 100644
similarity index 92%
rename from cura/ShapeArray.py
rename to cura/Arranging/ShapeArray.py
index 73fc2023e3..68be9a6478
--- a/cura/ShapeArray.py
+++ b/cura/Arranging/ShapeArray.py
@@ -29,8 +29,12 @@ class ShapeArray:
offset_x = int(numpy.amin(flip_vertices[:, 1]))
flip_vertices[:, 0] = numpy.add(flip_vertices[:, 0], -offset_y)
flip_vertices[:, 1] = numpy.add(flip_vertices[:, 1], -offset_x)
- shape = [int(numpy.amax(flip_vertices[:, 0])), int(numpy.amax(flip_vertices[:, 1]))]
+ shape = numpy.array([int(numpy.amax(flip_vertices[:, 0])), int(numpy.amax(flip_vertices[:, 1]))])
+ shape[numpy.where(shape == 0)] = 1
arr = cls.arrayFromPolygon(shape, flip_vertices)
+ if not numpy.ndarray.any(arr):
+ # set at least 1 pixel
+ arr[0][0] = 1
return cls(arr, offset_x, offset_y)
## Instantiate an offset and hull ShapeArray from a scene node.
@@ -43,13 +47,12 @@ class ShapeArray:
transform_x = transform._data[0][3]
transform_y = transform._data[2][3]
hull_verts = node.callDecoration("getConvexHull")
+ # If a model is too small then it will not contain any points
+ if hull_verts is None or not hull_verts.getPoints().any():
+ return None, None
# For one_at_a_time printing you need the convex hull head.
hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts
- # If a model is to small then it will not contain any points
- if not hull_verts.getPoints().any():
- return None, None
-
offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
offset_points = copy.deepcopy(offset_verts._points) # x, y
offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x)
diff --git a/cura/Arranging/__init__.py b/cura/Arranging/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py
index 2567641cc9..b929f0f8f1 100755
--- a/cura/BuildVolume.py
+++ b/cura/BuildVolume.py
@@ -1,6 +1,7 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
@@ -25,7 +26,7 @@ catalog = i18nCatalog("cura")
import numpy
import math
-from typing import List
+from typing import List, Optional
# Setting for clearance around the prime
PRIME_CLEARANCE = 6.5
@@ -73,6 +74,11 @@ class BuildVolume(SceneNode):
self._adhesion_type = None
self._platform = Platform(self)
+ self._build_volume_message = Message(catalog.i18nc("@info:status",
+ "The build volume height has been reduced due to the value of the"
+ " \"Print Sequence\" setting to prevent the gantry from colliding"
+ " with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
+
self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onStackChanged)
self._onStackChanged()
@@ -96,11 +102,6 @@ class BuildVolume(SceneNode):
self._setting_change_timer.setSingleShot(True)
self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished)
- self._build_volume_message = Message(catalog.i18nc("@info:status",
- "The build volume height has been reduced due to the value of the"
- " \"Print Sequence\" setting to prevent the gantry from colliding"
- " with printed models."), title = catalog.i18nc("@info:title","Build Volume"))
-
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
# Therefore this works.
@@ -241,6 +242,44 @@ class BuildVolume(SceneNode):
for child_node in group_node.getAllChildren():
child_node._outside_buildarea = group_node._outside_buildarea
+ ## Update the outsideBuildArea of a single node, given bounds or current build volume
+ def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None):
+ if not isinstance(node, CuraSceneNode):
+ return
+
+ if bounds is None:
+ build_volume_bounding_box = self.getBoundingBox()
+ if build_volume_bounding_box:
+ # It's over 9000!
+ build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001)
+ else:
+ # No bounding box. This is triggered when running Cura from command line with a model for the first time
+ # In that situation there is a model, but no machine (and therefore no build volume.
+ return
+ else:
+ build_volume_bounding_box = bounds
+
+ if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
+ bbox = node.getBoundingBox()
+
+ # Mark the node as outside the build volume if the bounding box test fails.
+ if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
+ node.setOutsideBuildArea(True)
+ return
+
+ convex_hull = self.callDecoration("getConvexHull")
+ if convex_hull:
+ if not convex_hull.isValid():
+ return
+ # Check for collisions between disallowed areas and the object
+ for area in self.getDisallowedAreas():
+ overlap = convex_hull.intersectsPolygon(area)
+ if overlap is None:
+ continue
+ node.setOutsideBuildArea(True)
+ return
+ node.setOutsideBuildArea(False)
+
## Recalculates the build volume & disallowed areas.
def rebuild(self):
if not self._width or not self._height or not self._depth:
@@ -698,12 +737,17 @@ class BuildVolume(SceneNode):
prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
prime_tower_y = prime_tower_y + machine_depth / 2
- prime_tower_area = Polygon([
- [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size],
- [prime_tower_x, prime_tower_y - prime_tower_size],
- [prime_tower_x, prime_tower_y],
- [prime_tower_x - prime_tower_size, prime_tower_y],
- ])
+ if self._global_container_stack.getProperty("prime_tower_circular", "value"):
+ radius = prime_tower_size / 2
+ prime_tower_area = Polygon.approximatedCircle(radius)
+ prime_tower_area = prime_tower_area.translate(prime_tower_x - radius, prime_tower_y - radius)
+ else:
+ prime_tower_area = Polygon([
+ [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size],
+ [prime_tower_x, prime_tower_y - prime_tower_size],
+ [prime_tower_x, prime_tower_y],
+ [prime_tower_x - prime_tower_size, prime_tower_y],
+ ])
prime_tower_area = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(0))
for extruder in used_extruders:
result[extruder.getId()].append(prime_tower_area) #The prime tower location is the same for each extruder, regardless of offset.
@@ -782,6 +826,7 @@ class BuildVolume(SceneNode):
offset_y = extruder.getProperty("machine_nozzle_offset_y", "value")
if offset_y is None:
offset_y = 0
+ offset_y = -offset_y #Y direction of g-code is the inverse of Y direction of Cura's scene space.
result[extruder_id] = []
for polygon in machine_disallowed_polygons:
@@ -892,8 +937,8 @@ class BuildVolume(SceneNode):
# stack.
#
# \return A sequence of setting values, one for each extruder.
- def _getSettingFromAllExtruders(self, setting_key, property = "value"):
- all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, property)
+ def _getSettingFromAllExtruders(self, setting_key):
+ all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
for i in range(len(all_values)):
if not all_values[i] and (all_types[i] == "int" or all_types[i] == "float"):
@@ -906,7 +951,7 @@ class BuildVolume(SceneNode):
# not part of the collision radius, such as bed adhesion (skirt/brim/raft)
# and travel avoid distance.
def _getEdgeDisallowedSize(self):
- if not self._global_container_stack:
+ if not self._global_container_stack or not self._global_container_stack.extruders:
return 0
container_stack = self._global_container_stack
@@ -984,7 +1029,7 @@ class BuildVolume(SceneNode):
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
- _tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
+ _tower_settings = ["prime_tower_enable", "prime_tower_circular", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"]
_extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
diff --git a/cura/CameraImageProvider.py b/cura/CameraImageProvider.py
index ff66170f3c..ddf978f625 100644
--- a/cura/CameraImageProvider.py
+++ b/cura/CameraImageProvider.py
@@ -12,7 +12,7 @@ class CameraImageProvider(QQuickImageProvider):
def requestImage(self, id, size):
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
try:
- return output_device.getCameraImage(), QSize(15, 15)
+ return output_device.activePrinter.camera.getImage(), QSize(15, 15)
except AttributeError:
pass
return QImage(), QSize(15, 15)
\ No newline at end of file
diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py
index 81eca67a46..d74b48a53a 100644
--- a/cura/CrashHandler.py
+++ b/cura/CrashHandler.py
@@ -1,7 +1,6 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-import sys
import platform
import traceback
import faulthandler
@@ -13,15 +12,18 @@ import json
import ssl
import urllib.request
import urllib.error
+import shutil
-from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox
+from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
+from PyQt5.QtGui import QDesktopServices
from UM.Application import Application
from UM.Logger import Logger
from UM.View.GL.OpenGL import OpenGL
from UM.i18n import i18nCatalog
from UM.Platform import Platform
+from UM.Resources import Resources
catalog = i18nCatalog("cura")
@@ -37,7 +39,7 @@ else:
# List of exceptions that should be considered "fatal" and abort the program.
# These are primarily some exception types that we simply cannot really recover from
# (MemoryError and SystemError) and exceptions that indicate grave errors in the
-# code that cause the Python interpreter to fail (SyntaxError, ImportError).
+# code that cause the Python interpreter to fail (SyntaxError, ImportError).
fatal_exception_types = [
MemoryError,
SyntaxError,
@@ -49,11 +51,12 @@ fatal_exception_types = [
class CrashHandler:
crash_url = "https://stats.ultimaker.com/api/cura"
- def __init__(self, exception_type, value, tb):
+ def __init__(self, exception_type, value, tb, has_started = True):
self.exception_type = exception_type
self.value = value
self.traceback = tb
- self.dialog = QDialog()
+ self.has_started = has_started
+ self.dialog = None # Don't create a QDialog before there is a QApplication
# While we create the GUI, the information will be stored for sending afterwards
self.data = dict()
@@ -64,20 +67,143 @@ class CrashHandler:
for part in line.rstrip("\n").split("\n"):
Logger.log("c", part)
- if not CuraDebugMode and exception_type not in fatal_exception_types:
+ # If Cura has fully started, we only show fatal errors.
+ # If Cura has not fully started yet, we always show the early crash dialog. Otherwise, Cura will just crash
+ # without any information.
+ if has_started and exception_type not in fatal_exception_types:
return
- application = QCoreApplication.instance()
- if not application:
- sys.exit(1)
+ if not has_started:
+ self._send_report_checkbox = None
+ self.early_crash_dialog = self._createEarlyCrashDialog()
+ self.dialog = QDialog()
self._createDialog()
+ def _createEarlyCrashDialog(self):
+ dialog = QDialog()
+ dialog.setMinimumWidth(500)
+ dialog.setMinimumHeight(170)
+ dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura Crashed"))
+ dialog.finished.connect(self._closeEarlyCrashDialog)
+
+ layout = QVBoxLayout(dialog)
+
+ label = QLabel()
+ label.setText(catalog.i18nc("@label crash message", """
A fatal error has occurred.
+
Unfortunately, Cura encountered an unrecoverable error during start up. It was possibly caused by some incorrect configuration files. We suggest to backup and reset your configuration.
+
Backups can be found in the configuration folder.
+
Please send us this Crash Report to fix the problem.
+ """))
+ label.setWordWrap(True)
+ layout.addWidget(label)
+
+ # "send report" check box and show details
+ self._send_report_checkbox = QCheckBox(catalog.i18nc("@action:button", "Send crash report to Ultimaker"), dialog)
+ self._send_report_checkbox.setChecked(True)
+
+ show_details_button = QPushButton(catalog.i18nc("@action:button", "Show detailed crash report"), dialog)
+ show_details_button.setMaximumWidth(200)
+ show_details_button.clicked.connect(self._showDetailedReport)
+
+ show_configuration_folder_button = QPushButton(catalog.i18nc("@action:button", "Show configuration folder"), dialog)
+ show_configuration_folder_button.setMaximumWidth(200)
+ show_configuration_folder_button.clicked.connect(self._showConfigurationFolder)
+
+ layout.addWidget(self._send_report_checkbox)
+ layout.addWidget(show_details_button)
+ layout.addWidget(show_configuration_folder_button)
+
+ # "backup and start clean" and "close" buttons
+ buttons = QDialogButtonBox()
+ buttons.addButton(QDialogButtonBox.Close)
+ buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.AcceptRole)
+ buttons.rejected.connect(self._closeEarlyCrashDialog)
+ buttons.accepted.connect(self._backupAndStartClean)
+
+ layout.addWidget(buttons)
+
+ return dialog
+
+ def _closeEarlyCrashDialog(self):
+ if self._send_report_checkbox.isChecked():
+ self._sendCrashReport()
+ os._exit(1)
+
+ def _backupAndStartClean(self):
+ # backup the current cura directories and create clean ones
+ from cura.CuraVersion import CuraVersion
+ from UM.Resources import Resources
+ # The early crash may happen before those information is set in Resources, so we need to set them here to
+ # make sure that Resources can find the correct place.
+ Resources.ApplicationIdentifier = "cura"
+ Resources.ApplicationVersion = CuraVersion
+ config_path = Resources.getConfigStoragePath()
+ data_path = Resources.getDataStoragePath()
+ cache_path = Resources.getCacheStoragePath()
+
+ folders_to_backup = []
+ folders_to_remove = [] # only cache folder needs to be removed
+
+ folders_to_backup.append(config_path)
+ if data_path != config_path:
+ folders_to_backup.append(data_path)
+
+ # Only remove the cache folder if it's not the same as data or config
+ if cache_path not in (config_path, data_path):
+ folders_to_remove.append(cache_path)
+
+ for folder in folders_to_remove:
+ shutil.rmtree(folder, ignore_errors = True)
+ for folder in folders_to_backup:
+ base_name = os.path.basename(folder)
+ root_dir = os.path.dirname(folder)
+
+ import datetime
+ date_now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
+ idx = 0
+ file_name = base_name + "_" + date_now
+ zip_file_path = os.path.join(root_dir, file_name + ".zip")
+ while os.path.exists(zip_file_path):
+ idx += 1
+ file_name = base_name + "_" + date_now + "_" + idx
+ zip_file_path = os.path.join(root_dir, file_name + ".zip")
+ try:
+ # only create the zip backup when the folder exists
+ if os.path.exists(folder):
+ # remove the .zip extension because make_archive() adds it
+ zip_file_path = zip_file_path[:-4]
+ shutil.make_archive(zip_file_path, "zip", root_dir = root_dir, base_dir = base_name)
+
+ # remove the folder only when the backup is successful
+ shutil.rmtree(folder, ignore_errors = True)
+
+ # create an empty folder so Resources will not try to copy the old ones
+ os.makedirs(folder, 0o0755, exist_ok=True)
+
+ except Exception as e:
+ Logger.logException("e", "Failed to backup [%s] to file [%s]", folder, zip_file_path)
+ if not self.has_started:
+ print("Failed to backup [%s] to file [%s]: %s", folder, zip_file_path, e)
+
+ self.early_crash_dialog.close()
+
+ def _showConfigurationFolder(self):
+ path = Resources.getConfigStoragePath();
+ QDesktopServices.openUrl(QUrl.fromLocalFile( path ))
+
+ def _showDetailedReport(self):
+ self.dialog.exec_()
+
## Creates a modal dialog.
def _createDialog(self):
self.dialog.setMinimumWidth(640)
self.dialog.setMinimumHeight(640)
self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
+ # if the application has not fully started, this will be a detailed report dialog which should not
+ # close the application when it's closed.
+ if self.has_started:
+ self.dialog.finished.connect(self._close)
layout = QVBoxLayout(self.dialog)
@@ -88,6 +214,9 @@ class CrashHandler:
layout.addWidget(self._userDescriptionWidget())
layout.addWidget(self._buttonsWidget())
+ def _close(self):
+ os._exit(1)
+
def _messageWidget(self):
label = QLabel()
label.setText(catalog.i18nc("@label crash message", """
A fatal error has occurred. Please send us this Crash Report to fix the problem
@@ -147,8 +276,8 @@ class CrashHandler:
layout = QVBoxLayout()
text_area = QTextEdit()
- trace_dict = traceback.format_exception(self.exception_type, self.value, self.traceback)
- trace = "".join(trace_dict)
+ trace_list = traceback.format_exception(self.exception_type, self.value, self.traceback)
+ trace = "".join(trace_list)
text_area.setText(trace)
text_area.setReadOnly(True)
@@ -156,14 +285,28 @@ class CrashHandler:
group.setLayout(layout)
# Parsing all the information to fill the dictionary
- summary = trace_dict[len(trace_dict)-1].rstrip("\n")
- module = trace_dict[len(trace_dict)-2].rstrip("\n").split("\n")
+ summary = ""
+ if len(trace_list) >= 1:
+ summary = trace_list[len(trace_list)-1].rstrip("\n")
+ module = [""]
+ if len(trace_list) >= 2:
+ module = trace_list[len(trace_list)-2].rstrip("\n").split("\n")
module_split = module[0].split(", ")
- filepath = module_split[0].split("\"")[1]
+
+ filepath_directory_split = module_split[0].split("\"")
+ filepath = ""
+ if len(filepath_directory_split) > 1:
+ filepath = filepath_directory_split[1]
directory, filename = os.path.split(filepath)
- line = int(module_split[1].lstrip("line "))
- function = module_split[2].lstrip("in ")
- code = module[1].lstrip(" ")
+ line = ""
+ if len(module_split) > 1:
+ line = int(module_split[1].lstrip("line "))
+ function = ""
+ if len(module_split) > 2:
+ function = module_split[2].lstrip("in ")
+ code = ""
+ if len(module) > 1:
+ code = module[1].lstrip(" ")
# Using this workaround for a cross-platform path splitting
split_path = []
@@ -188,7 +331,7 @@ class CrashHandler:
json_metadata_file = os.path.join(directory, "plugin.json")
try:
- with open(json_metadata_file, "r") as f:
+ with open(json_metadata_file, "r", encoding = "utf-8") as f:
try:
metadata = json.loads(f.read())
module_version = metadata["version"]
@@ -216,9 +359,9 @@ class CrashHandler:
text_area = QTextEdit()
tmp_file_fd, tmp_file_path = tempfile.mkstemp(prefix = "cura-crash", text = True)
os.close(tmp_file_fd)
- with open(tmp_file_path, "w") as f:
+ with open(tmp_file_path, "w", encoding = "utf-8") as f:
faulthandler.dump_traceback(f, all_threads=True)
- with open(tmp_file_path, "r") as f:
+ with open(tmp_file_path, "r", encoding = "utf-8") as f:
logdata = f.read()
text_area.setText(logdata)
@@ -248,9 +391,13 @@ class CrashHandler:
def _buttonsWidget(self):
buttons = QDialogButtonBox()
buttons.addButton(QDialogButtonBox.Close)
- buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole)
+ # Like above, this will be served as a separate detailed report dialog if the application has not yet been
+ # fully loaded. In this case, "send report" will be a check box in the early crash dialog, so there is no
+ # need for this extra button.
+ if self.has_started:
+ buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole)
+ buttons.accepted.connect(self._sendCrashReport)
buttons.rejected.connect(self.dialog.close)
- buttons.accepted.connect(self._sendCrashReport)
return buttons
@@ -268,15 +415,23 @@ class CrashHandler:
kwoptions["context"] = ssl._create_unverified_context()
Logger.log("i", "Sending crash report info to [%s]...", self.crash_url)
+ if not self.has_started:
+ print("Sending crash report info to [%s]...\n" % self.crash_url)
try:
f = urllib.request.urlopen(self.crash_url, **kwoptions)
Logger.log("i", "Sent crash report info.")
+ if not self.has_started:
+ print("Sent crash report info.\n")
f.close()
- except urllib.error.HTTPError:
+ except urllib.error.HTTPError as e:
Logger.logException("e", "An HTTP error occurred while trying to send crash report")
- except Exception: # We don't want any exception to cause problems
+ if not self.has_started:
+ print("An HTTP error occurred while trying to send crash report: %s" % e)
+ except Exception as e: # We don't want any exception to cause problems
Logger.logException("e", "An exception occurred while trying to send crash report")
+ if not self.has_started:
+ print("An exception occurred while trying to send crash report: %s" % e)
os._exit(1)
@@ -288,4 +443,4 @@ class CrashHandler:
# When the exception is not in the fatal_exception_types list, the dialog is not created, so we don't need to show it
if self.dialog:
self.dialog.exec_()
- os._exit(1)
+ os._exit(1)
diff --git a/cura/CuraActions.py b/cura/CuraActions.py
index b51728f028..f517ec4217 100644
--- a/cura/CuraActions.py
+++ b/cura/CuraActions.py
@@ -13,12 +13,18 @@ from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
+from UM.Operations.TranslateOperation import TranslateOperation
-from cura.SetParentOperation import SetParentOperation
+from cura.Operations.SetParentOperation import SetParentOperation
from cura.MultiplyObjectsJob import MultiplyObjectsJob
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
from cura.Settings.ExtruderManager import ExtruderManager
+from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOperation
+
+from UM.Logger import Logger
+
+
class CuraActions(QObject):
def __init__(self, parent = None):
super().__init__(parent)
@@ -54,7 +60,11 @@ class CuraActions(QObject):
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
current_node = current_node.getParent()
- center_operation = SetTransformOperation(current_node, Vector())
+ # This was formerly done with SetTransformOperation but because of
+ # unpredictable matrix deconstruction it was possible that mirrors
+ # could manifest as rotations. Centering is therefore done by
+ # moving the node to negative whatever its position is:
+ center_operation = TranslateOperation(current_node, -current_node._position)
operation.addOperation(center_operation)
operation.push()
@@ -63,7 +73,7 @@ class CuraActions(QObject):
# \param count The number of times to multiply the selection.
@pyqtSlot(int)
def multiplySelection(self, count: int) -> None:
- job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, 8)
+ job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = 8)
job.start()
## Delete all selected objects.
@@ -84,6 +94,10 @@ class CuraActions(QObject):
removed_group_nodes.append(group_node)
op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
op.addOperation(RemoveSceneNodeOperation(group_node))
+
+ # Reset the print information
+ Application.getInstance().getController().getScene().sceneChanged.emit(node)
+
op.push()
## Set the extruder that should be used to print the selection.
@@ -124,5 +138,31 @@ class CuraActions(QObject):
operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
operation.push()
+ @pyqtSlot(int)
+ def setBuildPlateForSelection(self, build_plate_nr: int) -> None:
+ Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
+ operation = GroupedOperation()
+
+ root = Application.getInstance().getController().getScene().getRoot()
+
+ nodes_to_change = []
+ for node in Selection.getAllSelectedObjects():
+ parent_node = node # Find the parent node to change instead
+ while parent_node.getParent() != root:
+ parent_node = parent_node.getParent()
+
+ for single_node in BreadthFirstIterator(parent_node):
+ nodes_to_change.append(single_node)
+
+ if not nodes_to_change:
+ Logger.log("d", "Nothing to change.")
+ return
+
+ for node in nodes_to_change:
+ operation.addOperation(SetBuildPlateNumberOperation(node, build_plate_nr))
+ operation.push()
+
+ Selection.clear()
+
def _openUrl(self, url):
QDesktopServices.openUrl(url)
diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index 18d1bde57e..2ca321e4cc 100755
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -1,5 +1,9 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+#Type hinting.
+from typing import Dict
+
+from PyQt5.QtCore import QObject
from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket
@@ -10,13 +14,13 @@ from UM.Math.Vector import Vector
from UM.Math.Quaternion import Quaternion
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Math.Matrix import Matrix
+from UM.Platform import Platform
from UM.Resources import Resources
from UM.Scene.ToolHandle import ToolHandle
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Mesh.ReadMeshJob import ReadMeshJob
from UM.Logger import Logger
from UM.Preferences import Preferences
-from UM.SaveFile import SaveFile
from UM.Scene.Selection import Selection
from UM.Scene.GroupDecorator import GroupDecorator
from UM.Settings.ContainerStack import ContainerStack
@@ -32,48 +36,61 @@ from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
-from cura.Arrange import Arrange
-from cura.ShapeArray import ShapeArray
-from cura.ConvexHullDecorator import ConvexHullDecorator
-from cura.SetParentOperation import SetParentOperation
-from cura.SliceableObjectDecorator import SliceableObjectDecorator
-from cura.BlockSlicingDecorator import BlockSlicingDecorator
-
-from cura.ArrangeObjectsJob import ArrangeObjectsJob
+from cura.Arranging.Arrange import Arrange
+from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
+from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
+from cura.Arranging.ShapeArray import ShapeArray
from cura.MultiplyObjectsJob import MultiplyObjectsJob
+from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
+from cura.Operations.SetParentOperation import SetParentOperation
+from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
+from cura.Scene.BlockSlicingDecorator import BlockSlicingDecorator
+from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
+from cura.Scene.CuraSceneNode import CuraSceneNode
+
+from cura.Scene.CuraSceneController import CuraSceneController
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
from cura.Settings.MachineNameValidator import MachineNameValidator
-from cura.Settings.ProfilesModel import ProfilesModel
-from cura.Settings.MaterialsModel import MaterialsModel
-from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel
+
+from cura.Machines.Models.BuildPlateModel import BuildPlateModel
+from cura.Machines.Models.NozzleModel import NozzleModel
+from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
+from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
+
+from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
+
+from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
+from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
+from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
+
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
-from cura.Settings.UserProfilesModel import UserProfilesModel
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
+from cura.Machines.VariantManager import VariantManager
+from cura.Machines.Models.QualityManagementModel import QualityManagementModel
+
from . import PlatformPhysics
from . import BuildVolume
from . import CameraAnimation
from . import PrintInformation
from . import CuraActions
-from . import ZOffsetDecorator
+from cura.Scene import ZOffsetDecorator
from . import CuraSplashScreen
from . import CameraImageProvider
from . import MachineActionManager
from cura.Settings.MachineManager import MachineManager
-from cura.Settings.MaterialManager import MaterialManager
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.UserChangesModel import UserChangesModel
from cura.Settings.ExtrudersModel import ExtrudersModel
-from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
-from cura.Settings.QualitySettingsModel import QualitySettingsModel
+from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Settings.ContainerManager import ContainerManager
-from cura.Settings.GlobalStack import GlobalStack
-from cura.Settings.ExtruderStack import ExtruderStack
+
+from cura.ObjectsModel import ObjectsModel
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from UM.FlameProfiler import pyqtSlot
@@ -81,15 +98,16 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
+from configparser import ConfigParser
import sys
import os.path
import numpy
import copy
-import urllib.parse
import os
import argparse
import json
+
numpy.seterr(all="ignore")
MYPY = False
@@ -108,6 +126,8 @@ class CuraApplication(QtApplication):
# changes of the settings.
SettingVersion = 4
+ Created = False
+
class ResourceTypes:
QmlFiles = Resources.UserType + 1
Firmware = Resources.UserType + 2
@@ -128,7 +148,6 @@ class CuraApplication(QtApplication):
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
def __init__(self, **kwargs):
-
# this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
Resources.addExpectedDirNameInData(dir_name)
@@ -137,6 +156,7 @@ class CuraApplication(QtApplication):
if not hasattr(sys, "frozen"):
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
+ self._use_gui = True
self._open_file_queue = [] # Files to open when plug-ins are loaded.
# Need to do this before ContainerRegistry tries to load the machines
@@ -203,8 +223,12 @@ class CuraApplication(QtApplication):
self._machine_manager = None # This is initialized on demand.
self._extruder_manager = None
self._material_manager = None
+ self._object_manager = None
+ self._build_plate_model = None
+ self._multi_build_plate_model = None
self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None
+ self._cura_scene_controller = None
self._additional_components = {} # Components to add to certain areas in the interface
@@ -215,6 +239,12 @@ class CuraApplication(QtApplication):
tray_icon_name = "cura-icon-32.png",
**kwargs)
+ # FOR TESTING ONLY
+ if kwargs["parsed_command_line"].get("trigger_early_crash", False):
+ assert not "This crash is triggered by the trigger_early_crash command line argument."
+
+ self._variant_manager = None
+
self.default_theme = "cura-light"
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@@ -248,7 +278,7 @@ class CuraApplication(QtApplication):
self._center_after_select = False
self._camera_animation = None
self._cura_actions = None
- self._started = False
+ self.started = False
self._message_box_callback = None
self._message_box_callback_arguments = []
@@ -258,6 +288,7 @@ class CuraApplication(QtApplication):
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
+ self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivity)
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware")
@@ -268,16 +299,25 @@ class CuraApplication(QtApplication):
# Since they are empty, they should never be serialized and instead just programmatically created.
# We need them to simplify the switching between materials.
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
+ self.empty_container = empty_container
+
+ empty_definition_changes_container = copy.deepcopy(empty_container)
+ empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
+ empty_definition_changes_container.addMetaDataEntry("type", "definition_changes")
+ ContainerRegistry.getInstance().addContainer(empty_definition_changes_container)
+ self.empty_definition_changes_container = empty_definition_changes_container
empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container)
+ self.empty_variant_container = empty_variant_container
empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container)
+ self.empty_material_container = empty_material_container
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality")
@@ -286,11 +326,14 @@ class CuraApplication(QtApplication):
empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
ContainerRegistry.getInstance().addContainer(empty_quality_container)
+ self.empty_quality_container = empty_quality_container
empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
+ empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported")
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
+ self.empty_quality_changes_container = empty_quality_changes_container
with ContainerRegistry.getInstance().lockFile():
ContainerRegistry.getInstance().loadAllMetadata()
@@ -311,12 +354,15 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/asked_dialog_on_project_save", False)
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
preferences.addPreference("cura/choice_on_open_project", "always_ask")
+ preferences.addPreference("cura/not_arrange_objects_on_load", False)
+ preferences.addPreference("cura/use_multi_build_plate", False)
preferences.addPreference("cura/currency", "€")
preferences.addPreference("cura/material_settings", "{}")
preferences.addPreference("view/invert_zoom", False)
- preferences.addPreference("cura/sidebar_collapse", False)
+ preferences.addPreference("view/filter_current_build_plate", False)
+ preferences.addPreference("cura/sidebar_collapsed", False)
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
@@ -329,57 +375,19 @@ class CuraApplication(QtApplication):
preferences.setDefault("local_file/last_used_type", "text/x-gcode")
- preferences.setDefault("general/visible_settings", """
- machine_settings
- resolution
- layer_height
- shell
- wall_thickness
- top_bottom_thickness
- z_seam_x
- z_seam_y
- infill
- infill_sparse_density
- gradual_infill_steps
- material
- material_print_temperature
- material_bed_temperature
- material_diameter
- material_flow
- retraction_enable
- speed
- speed_print
- speed_travel
- acceleration_print
- acceleration_travel
- jerk_print
- jerk_travel
- travel
- cooling
- cool_fan_enabled
- support
- support_enable
- support_extruder_nr
- support_type
- platform_adhesion
- adhesion_type
- adhesion_extruder_nr
- brim_width
- raft_airgap
- layer_0_z_overlap
- raft_surface_layers
- dual
- prime_tower_enable
- prime_tower_size
- prime_tower_position_x
- prime_tower_position_y
- meshfix
- blackmagic
- print_sequence
- infill_mesh
- cutting_mesh
- experimental
- """.replace("\n", ";").replace(" ", ""))
+ setting_visibily_preset_names = self.getVisibilitySettingPresetTypes()
+ preferences.setDefault("general/visible_settings_preset", setting_visibily_preset_names)
+
+ preset_setting_visibility_choice = Preferences.getInstance().getValue("general/preset_setting_visibility_choice")
+
+ default_preset_visibility_group_name = "Basic"
+ if preset_setting_visibility_choice == "" or preset_setting_visibility_choice is None:
+ if preset_setting_visibility_choice not in setting_visibily_preset_names:
+ preset_setting_visibility_choice = default_preset_visibility_group_name
+
+ visible_settings = self.getVisibilitySettingPreset(settings_preset_name = preset_setting_visibility_choice)
+ preferences.setDefault("general/visible_settings", visible_settings)
+ preferences.setDefault("general/preset_setting_visibility_choice", preset_setting_visibility_choice)
self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated)
@@ -389,6 +397,99 @@ class CuraApplication(QtApplication):
self._plugin_registry.addSupportedPluginExtension("curaplugin", "Cura Plugin")
+ self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
+
+ self._quality_profile_drop_down_menu_model = None
+ self._custom_quality_profile_drop_down_menu_model = None
+
+ CuraApplication.Created = True
+
+ @pyqtSlot(str, result = str)
+ def getVisibilitySettingPreset(self, settings_preset_name) -> str:
+ result = self._loadPresetSettingVisibilityGroup(settings_preset_name)
+ formatted_preset_settings = self._serializePresetSettingVisibilityData(result)
+
+ return formatted_preset_settings
+
+ ## Serialise the given preset setting visibitlity group dictionary into a string which is concatenated by ";"
+ #
+ def _serializePresetSettingVisibilityData(self, settings_data: dict) -> str:
+ result_string = ""
+
+ for key in settings_data:
+ result_string += key + ";"
+ for value in settings_data[key]:
+ result_string += value + ";"
+
+ return result_string
+
+ ## Load the preset setting visibility group with the given name
+ #
+ def _loadPresetSettingVisibilityGroup(self, visibility_preset_name) -> Dict[str, str]:
+ preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
+
+ result = {}
+ right_preset_found = False
+
+ for item in os.listdir(preset_dir):
+ file_path = os.path.join(preset_dir, item)
+ if not os.path.isfile(file_path):
+ continue
+
+ parser = ConfigParser(allow_no_value = True) # accept options without any value,
+
+ try:
+ parser.read([file_path])
+
+ if not parser.has_option("general", "name"):
+ continue
+
+ if parser["general"]["name"] == visibility_preset_name:
+ right_preset_found = True
+ for section in parser.sections():
+ if section == 'general':
+ continue
+ else:
+ section_settings = []
+ for option in parser[section].keys():
+ section_settings.append(option)
+
+ result[section] = section_settings
+
+ if right_preset_found:
+ break
+
+ except Exception as e:
+ Logger.log("e", "Failed to load setting visibility preset %s: %s", file_path, str(e))
+
+ return result
+
+ ## Check visibility setting preset folder and returns available types
+ #
+ def getVisibilitySettingPresetTypes(self):
+ preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
+ result = {}
+
+ for item in os.listdir(preset_dir):
+ file_path = os.path.join(preset_dir, item)
+ if not os.path.isfile(file_path):
+ continue
+
+ parser = ConfigParser(allow_no_value=True) # accept options without any value,
+
+ try:
+ parser.read([file_path])
+
+ if not parser.has_option("general", "name") and not parser.has_option("general", "weight"):
+ continue
+
+ result[parser["general"]["weight"]] = parser["general"]["name"]
+
+ except Exception as e:
+ Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e))
+
+ return result
+
def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@@ -409,6 +510,14 @@ class CuraApplication(QtApplication):
else:
self.exit(0)
+ ## Signal to connect preferences action in QML
+ showPreferencesWindow = pyqtSignal()
+
+ ## Show the preferences window
+ @pyqtSlot()
+ def showPreferences(self):
+ self.showPreferencesWindow.emit()
+
## A reusable dialogbox
#
showMessageBox = pyqtSignal(str, str, str, str, int, int, arguments = ["title", "text", "informativeText", "detailedText", "buttons", "icon"])
@@ -429,14 +538,12 @@ class CuraApplication(QtApplication):
elif choice == "always_keep":
# don't show dialog and KEEP the profile
self.discardOrKeepProfileChangesClosed("keep")
- else:
+ elif self._use_gui:
# ALWAYS ask whether to keep or discard the profile
self.showDiscardOrKeepProfileChanges.emit()
has_user_interaction = True
return has_user_interaction
- onDiscardOrKeepProfileChangesClosed = pyqtSignal() # Used to notify other managers that the dialog was closed
-
@pyqtSlot(str)
def discardOrKeepProfileChangesClosed(self, option):
if option == "discard":
@@ -459,7 +566,6 @@ class CuraApplication(QtApplication):
user_global_container.update()
# notify listeners that quality has changed (after user selected discard or keep)
- self.onDiscardOrKeepProfileChangesClosed.emit()
self.getMachineManager().activeQualityChanged.emit()
@pyqtSlot(int)
@@ -475,7 +581,7 @@ class CuraApplication(QtApplication):
#
# Note that the AutoSave plugin also calls this method.
def saveSettings(self):
- if not self._started: # Do not do saving during application start
+ if not self.started: # Do not do saving during application start
return
ContainerRegistry.getInstance().saveDirtyContainers()
@@ -501,7 +607,13 @@ class CuraApplication(QtApplication):
def _loadPlugins(self):
self._plugin_registry.addType("profile_reader", self._addProfileReader)
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
- self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
+
+ if Platform.isLinux():
+ lib_suffixes = {"", "64", "32", "x32"} #A few common ones on different distributions.
+ else:
+ lib_suffixes = {""}
+ for suffix in lib_suffixes:
+ self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib" + suffix, "cura"))
if not hasattr(sys, "frozen"):
self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))
self._plugin_registry.loadPlugin("ConsoleLogger")
@@ -625,16 +737,65 @@ class CuraApplication(QtApplication):
if parsed_args["help"]:
parser.print_help()
sys.exit(0)
-
+
def run(self):
self.preRun()
-
- self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
+ container_registry = ContainerRegistry.getInstance()
+ self._variant_manager = VariantManager(container_registry)
+ self._variant_manager.initialize()
+
+ from cura.Machines.MaterialManager import MaterialManager
+ self._material_manager = MaterialManager(container_registry, parent = self)
+ self._material_manager.initialize()
+
+ from cura.Machines.QualityManager import QualityManager
+ self._quality_manager = QualityManager(container_registry, parent = self)
+ self._quality_manager.initialize()
+
+ self._machine_manager = MachineManager(self)
+
+ # Check if we should run as single instance or not
self._setUpSingleInstanceServer()
+ # Setup scene and build volume
+ root = self.getController().getScene().getRoot()
+ self._volume = BuildVolume.BuildVolume(self.getController().getScene().getRoot())
+ Arrange.build_volume = self._volume
+
+ # initialize info objects
+ self._print_information = PrintInformation.PrintInformation()
+ self._cura_actions = CuraActions.CuraActions(self)
+
+ # Detect in which mode to run and execute that mode
+ if self.getCommandLineOption("headless", False):
+ self.runWithoutGUI()
+ else:
+ self.runWithGUI()
+
+ # Pre-load files if requested
+ for file_name in self.getCommandLineOption("file", []):
+ 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._openFile(file_name)
+
+ self.started = True
+ self.exec_()
+
+ ## Run Cura without GUI elements and interaction (server mode).
+ def runWithoutGUI(self):
+ self._use_gui = False
+ self.closeSplash()
+
+ ## Run Cura with GUI (desktop mode).
+ def runWithGUI(self):
+ self._use_gui = True
+
+ self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
+
controller = self.getController()
+ # Initialize UI state
controller.setActiveStage("PrepareStage")
controller.setActiveView("SolidView")
controller.setCameraTool("CameraTool")
@@ -646,63 +807,48 @@ class CuraApplication(QtApplication):
Selection.selectionChanged.connect(self.onSelectionChanged)
- root = controller.getScene().getRoot()
-
- # The platform is a child of BuildVolume
- self._volume = BuildVolume.BuildVolume(root)
-
- # Set the build volume of the arranger to the used build volume
- Arrange.build_volume = self._volume
-
+ # Set default background color for scene
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
+ # Initialize platform physics
self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
+ # Initialize camera
+ root = controller.getScene().getRoot()
camera = Camera("3d", root)
camera.setPosition(Vector(-80, 250, 700))
camera.setPerspective(True)
camera.lookAt(Vector(0, 0, 0))
controller.getScene().setActiveCamera("3d")
- camera_tool = self.getController().getTool("CameraTool")
+ # Initialize camera tool
+ camera_tool = controller.getTool("CameraTool")
camera_tool.setOrigin(Vector(0, 100, 0))
camera_tool.setZoomRange(0.1, 200000)
+ # Initialize camera animations
self._camera_animation = CameraAnimation.CameraAnimation()
self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
- qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
- qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
- qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
- qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
- qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
- qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
-
+ # Initialize QML engine
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
+ self.initializeEngine()
- run_without_gui = self.getCommandLineOption("headless", False) or self.getCommandLineOption("invisible", False)
- if not run_without_gui:
- self.initializeEngine()
- controller.setActiveStage("PrepareStage")
+ # Make sure the correct stage is activated after QML is loaded
+ controller.setActiveStage("PrepareStage")
- if run_without_gui or self._engine.rootObjects:
- self.closeSplash()
+ # Hide the splash screen
+ self.closeSplash()
- for file_name in self.getCommandLineOption("file", []):
- 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._openFile(file_name)
-
- self._started = True
-
- self.exec_()
+ def hasGui(self):
+ return self._use_gui
def getMachineManager(self, *args) -> MachineManager:
if self._machine_manager is None:
- self._machine_manager = MachineManager.createMachineManager()
+ self._machine_manager = MachineManager(self)
return self._machine_manager
def getExtruderManager(self, *args):
@@ -710,11 +856,39 @@ class CuraApplication(QtApplication):
self._extruder_manager = ExtruderManager.createExtruderManager()
return self._extruder_manager
+ def getVariantManager(self, *args):
+ return self._variant_manager
+
+ @pyqtSlot(result = QObject)
def getMaterialManager(self, *args):
- if self._material_manager is None:
- self._material_manager = MaterialManager.createMaterialManager()
return self._material_manager
+ @pyqtSlot(result = QObject)
+ def getQualityManager(self, *args):
+ return self._quality_manager
+
+ def getObjectsModel(self, *args):
+ if self._object_manager is None:
+ self._object_manager = ObjectsModel.createObjectsModel()
+ return self._object_manager
+
+ @pyqtSlot(result = QObject)
+ def getMultiBuildPlateModel(self, *args):
+ if self._multi_build_plate_model is None:
+ self._multi_build_plate_model = MultiBuildPlateModel(self)
+ return self._multi_build_plate_model
+
+ @pyqtSlot(result = QObject)
+ def getBuildPlateModel(self, *args):
+ if self._build_plate_model is None:
+ self._build_plate_model = BuildPlateModel(self)
+ return self._build_plate_model
+
+ def getCuraSceneController(self, *args):
+ if self._cura_scene_controller is None:
+ self._cura_scene_controller = CuraSceneController.createCuraSceneController()
+ return self._cura_scene_controller
+
def getSettingInheritanceManager(self, *args):
if self._setting_inheritance_manager is None:
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
@@ -745,30 +919,58 @@ class CuraApplication(QtApplication):
def getPrintInformation(self):
return self._print_information
+ def getQualityProfilesDropDownMenuModel(self, *args, **kwargs):
+ if self._quality_profile_drop_down_menu_model is None:
+ self._quality_profile_drop_down_menu_model = QualityProfilesDropDownMenuModel(self)
+ return self._quality_profile_drop_down_menu_model
+
+ def getCustomQualityProfilesDropDownMenuModel(self, *args, **kwargs):
+ if self._custom_quality_profile_drop_down_menu_model is None:
+ self._custom_quality_profile_drop_down_menu_model = CustomQualityProfilesDropDownMenuModel(self)
+ return self._custom_quality_profile_drop_down_menu_model
+
## Registers objects for the QML engine to use.
#
# \param engine The QML engine.
def registerObjects(self, engine):
super().registerObjects(engine)
+
+ # global contexts
engine.rootContext().setContextProperty("Printer", self)
engine.rootContext().setContextProperty("CuraApplication", self)
- self._print_information = PrintInformation.PrintInformation()
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
- self._cura_actions = CuraActions.CuraActions(self)
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
+ qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
+ qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
+ qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
+ qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
+ qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
+ qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
+
+ qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
+ qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
+ qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
+ qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
- qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
- qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
- qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel")
- qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel")
- qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel")
+
+ qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
+ qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
+ qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
+ qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
+
+ qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
+ "QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
+ qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
+ "CustomQualityProfilesDropDownMenuModel", self.getCustomQualityProfilesDropDownMenuModel)
+ qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
+
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
- qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel")
+ qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
@@ -845,12 +1047,18 @@ class CuraApplication(QtApplication):
def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
+ ## Update scene bounding box for current build plate
def updatePlatformActivity(self, node = None):
count = 0
scene_bounding_box = None
is_block_slicing_node = False
+ active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if type(node) is not SceneNode or (not node.getMeshData() and not node.callDecoration("getLayerData")):
+ if (
+ not issubclass(type(node), CuraSceneNode) or
+ (not node.getMeshData() and not node.callDecoration("getLayerData")) or
+ (node.callDecoration("getBuildPlateNumber") != active_build_plate)):
+
continue
if node.callDecoration("isBlockSlicing"):
is_block_slicing_node = True
@@ -870,7 +1078,7 @@ class CuraApplication(QtApplication):
if not scene_bounding_box:
scene_bounding_box = AxisAlignedBox.Null
- if repr(self._scene_bounding_box) != repr(scene_bounding_box) and scene_bounding_box.isValid():
+ if repr(self._scene_bounding_box) != repr(scene_bounding_box):
self._scene_bounding_box = scene_bounding_box
self.sceneBoundingBoxChanged.emit()
@@ -967,7 +1175,7 @@ class CuraApplication(QtApplication):
Selection.clear()
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if type(node) is not SceneNode:
+ if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
@@ -975,21 +1183,29 @@ class CuraApplication(QtApplication):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable():
continue # i.e. node with layer data
+ if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
+ continue # i.e. node with layer data
+
Selection.add(node)
## Delete all nodes containing mesh data in the scene.
+ # \param only_selectable. Set this to False to delete objects from all build plates
@pyqtSlot()
- def deleteAll(self):
+ def deleteAll(self, only_selectable = True):
Logger.log("i", "Clearing scene")
if not self.getController().getToolsEnabled():
return
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if type(node) is not SceneNode:
+ if not isinstance(node, SceneNode):
continue
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
+ if only_selectable and not node.isSelectable():
+ continue
+ if not node.callDecoration("isSliceable") and not node.callDecoration("getLayerData") and not node.callDecoration("isGroup"):
+ continue # Only remove nodes that are selectable.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
nodes.append(node)
@@ -999,16 +1215,19 @@ class CuraApplication(QtApplication):
for node in nodes:
op.addOperation(RemoveSceneNodeOperation(node))
+ # Reset the print information
+ self.getController().getScene().sceneChanged.emit(node)
+
op.push()
Selection.clear()
- ## Reset all translation on nodes with mesh data.
+ ## Reset all translation on nodes with mesh data.
@pyqtSlot()
def resetAllTranslation(self):
Logger.log("i", "Resetting all scene translations")
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if type(node) is not SceneNode:
+ if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
@@ -1036,13 +1255,13 @@ class CuraApplication(QtApplication):
Logger.log("i", "Resetting all scene transformations")
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if type(node) is not SceneNode:
+ if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
- if not node.isSelectable():
+ if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
nodes.append(node)
@@ -1060,10 +1279,31 @@ class CuraApplication(QtApplication):
## Arrange all objects.
@pyqtSlot()
- def arrangeAll(self):
+ def arrangeObjectsToAllBuildPlates(self):
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if type(node) is not SceneNode:
+ if not isinstance(node, SceneNode):
+ continue
+ if not node.getMeshData() and not node.callDecoration("isGroup"):
+ continue # Node that doesnt have a mesh and is not a group.
+ if node.getParent() and node.getParent().callDecoration("isGroup"):
+ continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
+ if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
+ continue # i.e. node with layer data
+ # Skip nodes that are too big
+ if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
+ nodes.append(node)
+ job = ArrangeObjectsAllBuildPlatesJob(nodes)
+ job.start()
+ self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
+
+ # Single build plate
+ @pyqtSlot()
+ def arrangeAll(self):
+ nodes = []
+ active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
+ for node in DepthFirstIterator(self.getController().getScene().getRoot()):
+ if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
@@ -1071,9 +1311,12 @@ class CuraApplication(QtApplication):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable():
continue # i.e. node with layer data
- # Skip nodes that are too big
- if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
- nodes.append(node)
+ if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
+ continue # i.e. node with layer data
+ if node.callDecoration("getBuildPlateNumber") == active_build_plate:
+ # Skip nodes that are too big
+ if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
+ nodes.append(node)
self.arrange(nodes, fixed_nodes = [])
## Arrange Selection
@@ -1084,7 +1327,7 @@ class CuraApplication(QtApplication):
# What nodes are on the build plate and are not being moved
fixed_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if type(node) is not SceneNode:
+ if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
@@ -1092,6 +1335,8 @@ class CuraApplication(QtApplication):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable():
continue # i.e. node with layer data
+ if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
+ continue # i.e. node with layer data
if node in nodes: # exclude selected node from fixed_nodes
continue
fixed_nodes.append(node)
@@ -1110,7 +1355,7 @@ class CuraApplication(QtApplication):
Logger.log("i", "Reloading all loaded mesh data.")
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
- if type(node) is not SceneNode or not node.getMeshData():
+ if not isinstance(node, CuraSceneNode) or not node.getMeshData():
continue
nodes.append(node)
@@ -1127,7 +1372,7 @@ class CuraApplication(QtApplication):
job.start()
else:
Logger.log("w", "Unable to reload data because we don't have a filename.")
-
+
## Get logging data of the backend engine
# \returns \type{string} Logging data
@pyqtSlot(result = str)
@@ -1201,10 +1446,11 @@ class CuraApplication(QtApplication):
@pyqtSlot()
def groupSelected(self):
# Create a group-node
- group_node = SceneNode()
+ group_node = CuraSceneNode()
group_decorator = GroupDecorator()
group_node.addDecorator(group_decorator)
group_node.addDecorator(ConvexHullDecorator())
+ group_node.addDecorator(BuildPlateDecorator(self.getMultiBuildPlateModel().activeBuildPlate))
group_node.setParent(self.getController().getScene().getRoot())
group_node.setSelectable(True)
center = Selection.getSelectionCenter()
@@ -1249,6 +1495,7 @@ class CuraApplication(QtApplication):
pass
fileLoaded = pyqtSignal(str)
+ fileCompleted = pyqtSignal(str)
def _reloadMeshFinished(self, job):
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
@@ -1333,7 +1580,7 @@ class CuraApplication(QtApplication):
self._currently_loading_files.append(f)
if extension in self._non_sliceable_extensions:
- self.deleteAll()
+ self.deleteAll(only_selectable = False)
job = ReadMeshJob(f)
job.finished.connect(self._readMeshFinished)
@@ -1344,23 +1591,41 @@ class CuraApplication(QtApplication):
filename = job.getFileName()
self._currently_loading_files.remove(filename)
+ self.fileLoaded.emit(filename)
+ arrange_objects_on_load = (
+ not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
+ not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load"))
+ target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
+
root = self.getController().getScene().getRoot()
- arranger = Arrange.create(scene_root = root)
+ fixed_nodes = []
+ for node_ in DepthFirstIterator(root):
+ if node_.callDecoration("isSliceable") and node_.callDecoration("getBuildPlateNumber") == target_build_plate:
+ fixed_nodes.append(node_)
+ arranger = Arrange.create(fixed_nodes = fixed_nodes)
min_offset = 8
- self.fileLoaded.emit(filename)
+ for original_node in nodes:
+
+ # Create a CuraSceneNode just if the original node is not that type
+ if isinstance(original_node, CuraSceneNode):
+ node = original_node
+ else:
+ node = CuraSceneNode()
+ node.setMeshData(original_node.getMeshData())
+
+ #Setting meshdata does not apply scaling.
+ if(original_node.getScale() != Vector(1.0, 1.0, 1.0)):
+ node.scale(original_node.getScale())
+
- for node in nodes:
node.setSelectable(True)
node.setName(os.path.basename(filename))
+ self.getBuildVolume().checkBoundsAndUpdate(node)
extension = os.path.splitext(filename)[1]
if extension.lower() in self._non_sliceable_extensions:
- self.getController().setActiveView("SimulationView")
- view = self.getController().getActiveView()
- view.resetLayerData()
- view.setLayer(9999999)
- view.calculateMaxLayers()
+ self.callLater(lambda: self.getController().setActiveView("SimulationView"))
block_slicing_decorator = BlockSlicingDecorator()
node.addDecorator(block_slicing_decorator)
@@ -1377,26 +1642,37 @@ class CuraApplication(QtApplication):
if not child.getDecorator(ConvexHullDecorator):
child.addDecorator(ConvexHullDecorator())
- if node.callDecoration("isSliceable"):
- # Only check position if it's not already blatantly obvious that it won't fit.
- if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
- # Find node location
- offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
+ if arrange_objects_on_load:
+ if node.callDecoration("isSliceable"):
+ # Only check position if it's not already blatantly obvious that it won't fit.
+ if node.getBoundingBox() is None or self._volume.getBoundingBox() is None or node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
+ # Find node location
+ offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
- # If a model is to small then it will not contain any points
- if offset_shape_arr is None and hull_shape_arr is None:
- Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
- title=self._i18n_catalog.i18nc("@info:title", "Warning")
- ).show()
- return
+ # If a model is to small then it will not contain any points
+ if offset_shape_arr is None and hull_shape_arr is None:
+ Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
+ title=self._i18n_catalog.i18nc("@info:title", "Warning")).show()
+ return
- # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
- node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
+ # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
+ node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
+
+ # This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
+ # of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if
+ # the BuildPlateDecorator exists or not and always set the correct build plate number.
+ build_plate_decorator = node.getDecorator(BuildPlateDecorator)
+ if build_plate_decorator is None:
+ build_plate_decorator = BuildPlateDecorator(target_build_plate)
+ node.addDecorator(build_plate_decorator)
+ build_plate_decorator.setBuildPlateNumber(target_build_plate)
op = AddSceneNodeOperation(node, scene.getRoot())
op.push()
scene.sceneChanged.emit(node)
+ self.fileCompleted.emit(filename)
+
def addNonSliceableExtension(self, extension):
self._non_sliceable_extensions.append(extension)
@@ -1405,12 +1681,11 @@ class CuraApplication(QtApplication):
"""
Checks if the given file URL is a valid project file.
"""
+ file_path = QUrl(file_url).toLocalFile()
+ workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path)
+ if workspace_reader is None:
+ return False # non-project files won't get a reader
try:
- file_path = QUrl(file_url).toLocalFile()
- workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path)
- if workspace_reader is None:
- return False # non-project files won't get a reader
-
result = workspace_reader.preRead(file_path, show_dialog=False)
return result == WorkspaceReader.PreReadResult.accepted
except Exception as e:
diff --git a/cura/Machines/ContainerNode.py b/cura/Machines/ContainerNode.py
new file mode 100644
index 0000000000..6a839fb921
--- /dev/null
+++ b/cura/Machines/ContainerNode.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Optional
+
+from collections import OrderedDict
+
+from UM.Logger import Logger
+from UM.Settings.InstanceContainer import InstanceContainer
+
+
+##
+# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
+#
+# ContainerNode is a multi-purpose class. It has two main purposes:
+# 1. It encapsulates an InstanceContainer. It contains that InstanceContainer's
+# - metadata (Always)
+# - container (lazy-loaded when needed)
+# 2. It also serves as a node in a hierarchical InstanceContainer lookup table/tree.
+# This is used in Variant, Material, and Quality Managers.
+#
+class ContainerNode:
+ __slots__ = ("metadata", "container", "children_map")
+
+ def __init__(self, metadata: Optional[dict] = None):
+ self.metadata = metadata
+ self.container = None
+ self.children_map = OrderedDict()
+
+ def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
+ return self.children_map.get(child_key)
+
+ def getContainer(self) -> "InstanceContainer":
+ if self.metadata is None:
+ raise RuntimeError("Cannot get container for a ContainerNode without metadata")
+
+ if self.container is None:
+ container_id = self.metadata["id"]
+ Logger.log("i", "Lazy-loading container [%s]", container_id)
+ from UM.Settings.ContainerRegistry import ContainerRegistry
+ container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
+ if not container_list:
+ raise RuntimeError("Failed to lazy-load container [%s], cannot find it" % container_id)
+ self.container = container_list[0]
+
+ return self.container
+
+ def __str__(self) -> str:
+ return "%s[%s]" % (self.__class__.__name__, self.metadata.get("id"))
diff --git a/cura/Machines/MaterialGroup.py b/cura/Machines/MaterialGroup.py
new file mode 100644
index 0000000000..009778943a
--- /dev/null
+++ b/cura/Machines/MaterialGroup.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+
+#
+# A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
+# The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For
+# example: "generic_abs" is the root material (ID) of "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4",
+# and "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4" are derived materials of "generic_abs".
+#
+# Using "generic_abs" as an example, the MaterialGroup for "generic_abs" will contain the following information:
+# - name: "generic_abs", root_material_id
+# - root_material_node: MaterialNode of "generic_abs"
+# - derived_material_node_list: A list of MaterialNodes that are derived from "generic_abs",
+# so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc.
+#
+class MaterialGroup:
+ __slots__ = ("name", "root_material_node", "derived_material_node_list")
+
+ def __init__(self, name: str):
+ self.name = name
+ self.root_material_node = None
+ self.derived_material_node_list = []
+
+ def __str__(self) -> str:
+ return "%s[%s]" % (self.__class__.__name__, self.name)
diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py
new file mode 100644
index 0000000000..98e4f67f82
--- /dev/null
+++ b/cura/Machines/MaterialManager.py
@@ -0,0 +1,479 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from collections import defaultdict, OrderedDict
+import copy
+import uuid
+from typing import Optional, TYPE_CHECKING
+
+from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
+
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.Settings.SettingFunction import SettingFunction
+from UM.Util import parseBool
+
+from .MaterialNode import MaterialNode
+from .MaterialGroup import MaterialGroup
+
+if TYPE_CHECKING:
+ from cura.Settings.GlobalStack import GlobalStack
+
+
+#
+# MaterialManager maintains a number of maps and trees for material lookup.
+# The models GUI and QML use are now only dependent on the MaterialManager. That means as long as the data in
+# MaterialManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI.
+#
+# For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them
+# again. This means the update is exactly the same as initialization. There are performance concerns about this approach
+# but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this
+# because it's simple.
+#
+class MaterialManager(QObject):
+
+ materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
+
+ def __init__(self, container_registry, parent = None):
+ super().__init__(parent)
+ self._application = Application.getInstance()
+ self._container_registry = container_registry # type: ContainerRegistry
+
+ self._fallback_materials_map = dict() # material_type -> generic material metadata
+ self._material_group_map = dict() # root_material_id -> MaterialGroup
+ self._diameter_machine_variant_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
+
+ # We're using these two maps to convert between the specific diameter material id and the generic material id
+ # because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
+ # i.e. generic_pla -> generic_pla_175
+ self._material_diameter_map = defaultdict(dict) # root_material_id -> approximate diameter str -> root_material_id for that diameter
+ self._diameter_material_map = dict() # material id including diameter (generic_pla_175) -> material root id (generic_pla)
+
+ # This is used in Legacy UM3 send material function and the material management page.
+ self._guid_material_groups_map = defaultdict(list) # GUID -> a list of material_groups
+
+ # The machine definition ID for the non-machine-specific materials.
+ # This is used as the last fallback option if the given machine-specific material(s) cannot be found.
+ self._default_machine_definition_id = "fdmprinter"
+ self._default_approximate_diameter_for_quality_search = "3"
+
+ # When a material gets added/imported, there can be more than one InstanceContainers. In those cases, we don't
+ # want to react on every container/metadata changed signal. The timer here is to buffer it a bit so we don't
+ # react too many time.
+ self._update_timer = QTimer(self)
+ self._update_timer.setInterval(300)
+ self._update_timer.setSingleShot(True)
+ self._update_timer.timeout.connect(self._updateMaps)
+
+ self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
+ self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
+ self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
+
+ def initialize(self):
+ # Find all materials and put them in a matrix for quick search.
+ material_metadata_list = self._container_registry.findContainersMetadata(type = "material")
+
+ self._material_group_map = dict()
+
+ # Map #1
+ # root_material_id -> MaterialGroup
+ for material_metadata in material_metadata_list:
+ material_id = material_metadata["id"]
+ # We don't store empty material in the lookup tables
+ if material_id == "empty_material":
+ continue
+
+ root_material_id = material_metadata.get("base_file")
+ if root_material_id not in self._material_group_map:
+ self._material_group_map[root_material_id] = MaterialGroup(root_material_id)
+ group = self._material_group_map[root_material_id]
+
+ # We only add root materials here
+ if material_id == root_material_id:
+ group.root_material_node = MaterialNode(material_metadata)
+ else:
+ new_node = MaterialNode(material_metadata)
+ group.derived_material_node_list.append(new_node)
+ # Order this map alphabetically so it's easier to navigate in a debugger
+ self._material_group_map = OrderedDict(sorted(self._material_group_map.items(), key = lambda x: x[0]))
+
+ # Map #1.5
+ # GUID -> material group list
+ self._guid_material_groups_map = defaultdict(list)
+ for root_material_id, material_group in self._material_group_map.items():
+ guid = material_group.root_material_node.metadata["GUID"]
+ self._guid_material_groups_map[guid].append(material_group)
+
+ # Map #2
+ # Lookup table for material type -> fallback material metadata, only for read-only materials
+ grouped_by_type_dict = dict()
+ for root_material_id, material_node in self._material_group_map.items():
+ if not self._container_registry.isReadOnly(root_material_id):
+ continue
+ material_type = material_node.root_material_node.metadata["material"]
+ if material_type not in grouped_by_type_dict:
+ grouped_by_type_dict[material_type] = {"generic": None,
+ "others": []}
+ brand = material_node.root_material_node.metadata["brand"]
+ if brand.lower() == "generic":
+ to_add = True
+ if material_type in grouped_by_type_dict:
+ diameter = material_node.root_material_node.metadata.get("approximate_diameter")
+ if diameter != self._default_approximate_diameter_for_quality_search:
+ to_add = False # don't add if it's not the default diameter
+ if to_add:
+ grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
+ self._fallback_materials_map = grouped_by_type_dict
+
+ # Map #3
+ # There can be multiple material profiles for the same material with different diameters, such as "generic_pla"
+ # and "generic_pla_175". This is inconvenient when we do material-specific quality lookup because a quality can
+ # be for either "generic_pla" or "generic_pla_175", but not both. This map helps to get the correct material ID
+ # for quality search.
+ self._material_diameter_map = defaultdict(dict)
+ self._diameter_material_map = dict()
+
+ # Group the material IDs by the same name, material, brand, and color but with different diameters.
+ material_group_dict = dict()
+ keys_to_fetch = ("name", "material", "brand", "color")
+ for root_material_id, machine_node in self._material_group_map.items():
+ if not self._container_registry.isReadOnly(root_material_id):
+ continue
+
+ root_material_metadata = machine_node.root_material_node.metadata
+
+ key_data = []
+ for key in keys_to_fetch:
+ key_data.append(root_material_metadata.get(key))
+ key_data = tuple(key_data)
+
+ if key_data not in material_group_dict:
+ material_group_dict[key_data] = dict()
+ approximate_diameter = root_material_metadata.get("approximate_diameter")
+ material_group_dict[key_data][approximate_diameter] = root_material_metadata["id"]
+
+ # Map [root_material_id][diameter] -> root_material_id for this diameter
+ for data_dict in material_group_dict.values():
+ for root_material_id1 in data_dict.values():
+ if root_material_id1 in self._material_diameter_map:
+ continue
+ diameter_map = data_dict
+ for root_material_id2 in data_dict.values():
+ self._material_diameter_map[root_material_id2] = diameter_map
+
+ default_root_material_id = data_dict.get(self._default_approximate_diameter_for_quality_search)
+ if default_root_material_id is None:
+ default_root_material_id = list(data_dict.values())[0] # no default diameter present, just take "the" only one
+ for root_material_id in data_dict.values():
+ self._diameter_material_map[root_material_id] = default_root_material_id
+
+ # Map #4
+ # "machine" -> "variant_name" -> "root material ID" -> specific material InstanceContainer
+ # Construct the "machine" -> "variant" -> "root material ID" -> specific material InstanceContainer
+ self._diameter_machine_variant_material_map = dict()
+ for material_metadata in material_metadata_list:
+ # We don't store empty material in the lookup tables
+ if material_metadata["id"] == "empty_material":
+ continue
+
+ root_material_id = material_metadata["base_file"]
+ definition = material_metadata["definition"]
+ approximate_diameter = material_metadata["approximate_diameter"]
+
+ if approximate_diameter not in self._diameter_machine_variant_material_map:
+ self._diameter_machine_variant_material_map[approximate_diameter] = {}
+
+ machine_variant_material_map = self._diameter_machine_variant_material_map[approximate_diameter]
+ if definition not in machine_variant_material_map:
+ machine_variant_material_map[definition] = MaterialNode()
+
+ machine_node = machine_variant_material_map[definition]
+ variant_name = material_metadata.get("variant_name")
+ if not variant_name:
+ # if there is no variant, this material is for the machine, so put its metadata in the machine node.
+ machine_node.material_map[root_material_id] = MaterialNode(material_metadata)
+ else:
+ # this material is variant-specific, so we save it in a variant-specific node under the
+ # machine-specific node
+ if variant_name not in machine_node.children_map:
+ machine_node.children_map[variant_name] = MaterialNode()
+
+ variant_node = machine_node.children_map[variant_name]
+ if root_material_id not in variant_node.material_map:
+ variant_node.material_map[root_material_id] = MaterialNode(material_metadata)
+ else:
+ # Sanity check: make sure we don't have duplicated variant-specific materials for the same machine
+ raise RuntimeError("Found duplicate variant name [%s] for machine [%s] in material [%s]" %
+ (variant_name, definition, material_metadata["id"]))
+
+ self.materialsUpdated.emit()
+
+ def _updateMaps(self):
+ self.initialize()
+
+ def _onContainerMetadataChanged(self, container):
+ self._onContainerChanged(container)
+
+ def _onContainerChanged(self, container):
+ container_type = container.getMetaDataEntry("type")
+ if container_type != "material":
+ return
+
+ # update the maps
+ self._update_timer.start()
+
+ def getMaterialGroup(self, root_material_id: str) -> Optional[MaterialGroup]:
+ return self._material_group_map.get(root_material_id)
+
+ def getRootMaterialIDForDiameter(self, root_material_id: str, approximate_diameter: str) -> str:
+ return self._material_diameter_map.get(root_material_id).get(approximate_diameter, root_material_id)
+
+ def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
+ return self._diameter_material_map.get(root_material_id)
+
+ def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
+ return self._guid_material_groups_map.get(guid)
+
+ #
+ # Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
+ #
+ def getAvailableMaterials(self, machine_definition_id: str, extruder_variant_name: Optional[str],
+ diameter: float) -> dict:
+ # round the diameter to get the approximate diameter
+ rounded_diameter = str(round(diameter))
+ if rounded_diameter not in self._diameter_machine_variant_material_map:
+ Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
+ return dict()
+
+ # If there are variant materials, get the variant material
+ machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
+ machine_node = machine_variant_material_map.get(machine_definition_id)
+ default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
+ variant_node = None
+ if extruder_variant_name is not None and machine_node is not None:
+ variant_node = machine_node.getChildNode(extruder_variant_name)
+
+ nodes_to_check = [variant_node, machine_node, default_machine_node]
+
+ # Fallback mechanism of finding materials:
+ # 1. variant-specific material
+ # 2. machine-specific material
+ # 3. generic material (for fdmprinter)
+ material_id_metadata_dict = dict()
+ for node in nodes_to_check:
+ if node is not None:
+ for material_id, node in node.material_map.items():
+ if material_id not in material_id_metadata_dict:
+ material_id_metadata_dict[material_id] = node
+
+ return material_id_metadata_dict
+
+ #
+ # A convenience function to get available materials for the given machine with the extruder position.
+ #
+ def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
+ extruder_stack: "ExtruderStack") -> Optional[dict]:
+ machine_definition_id = machine.definition.getId()
+ variant_name = None
+ if extruder_stack.variant.getId() != "empty_variant":
+ variant_name = extruder_stack.variant.getName()
+ diameter = extruder_stack.approximateMaterialDiameter
+
+ # Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
+ return self.getAvailableMaterials(machine_definition_id, variant_name, diameter)
+
+ #
+ # Gets MaterialNode for the given extruder and machine with the given material name.
+ # Returns None if:
+ # 1. the given machine doesn't have materials;
+ # 2. cannot find any material InstanceContainers with the given settings.
+ #
+ def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str],
+ diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
+ # round the diameter to get the approximate diameter
+ rounded_diameter = str(round(diameter))
+ if rounded_diameter not in self._diameter_machine_variant_material_map:
+ Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
+ diameter, rounded_diameter, root_material_id)
+ return None
+
+ # If there are variant materials, get the variant material
+ machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
+ machine_node = machine_variant_material_map.get(machine_definition_id)
+ variant_node = None
+
+ # Fallback for "fdmprinter" if the machine-specific materials cannot be found
+ if machine_node is None:
+ machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
+ if machine_node is not None and extruder_variant_name is not None:
+ variant_node = machine_node.getChildNode(extruder_variant_name)
+
+ # Fallback mechanism of finding materials:
+ # 1. variant-specific material
+ # 2. machine-specific material
+ # 3. generic material (for fdmprinter)
+ nodes_to_check = [variant_node, machine_node,
+ machine_variant_material_map.get(self._default_machine_definition_id)]
+
+ material_node = None
+ for node in nodes_to_check:
+ if node is not None:
+ material_node = node.material_map.get(root_material_id)
+ if material_node:
+ break
+
+ return material_node
+
+ #
+ # Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla".
+ # For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use
+ # the generic material IDs to search for qualities.
+ #
+ # An example would be, suppose we have machine with preferred material set to "filo3d_pla" (1.75mm), but its
+ # extruders only use 2.85mm materials, then we won't be able to find the preferred material for this machine.
+ # A fallback would be to fetch a generic material of the same type "PLA" as "filo3d_pla", and in this case it will
+ # be "generic_pla". This function is intended to get a generic fallback material for the given material type.
+ #
+ # This function returns the generic root material ID for the given material type, where material types are "PLA",
+ # "ABS", etc.
+ #
+ def getFallbackMaterialIdByMaterialType(self, material_type: str) -> Optional[str]:
+ # For safety
+ if material_type not in self._fallback_materials_map:
+ Logger.log("w", "The material type [%s] does not have a fallback material" % material_type)
+ return None
+ fallback_material = self._fallback_materials_map[material_type]
+ if fallback_material:
+ return self.getRootMaterialIDWithoutDiameter(fallback_material["id"])
+ else:
+ return None
+
+ def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: str) -> Optional["MaterialNode"]:
+ node = None
+ machine_definition = global_stack.definition
+ if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
+ material_diameter = machine_definition.getProperty("material_diameter", "value")
+ if isinstance(material_diameter, SettingFunction):
+ material_diameter = material_diameter(global_stack)
+ approximate_material_diameter = str(round(material_diameter))
+ root_material_id = machine_definition.getMetaDataEntry("preferred_material")
+ root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter)
+ node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
+ material_diameter, root_material_id)
+ return node
+
+ #
+ # Methods for GUI
+ #
+
+ #
+ # Sets the new name for the given material.
+ #
+ @pyqtSlot("QVariant", str)
+ def setMaterialName(self, material_node: "MaterialNode", name: str):
+ root_material_id = material_node.metadata["base_file"]
+ if self._container_registry.isReadOnly(root_material_id):
+ Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
+ return
+
+ material_group = self.getMaterialGroup(root_material_id)
+ material_group.root_material_node.getContainer().setName(name)
+
+ #
+ # Removes the given material.
+ #
+ @pyqtSlot("QVariant")
+ def removeMaterial(self, material_node: "MaterialNode"):
+ root_material_id = material_node.metadata["base_file"]
+ material_group = self.getMaterialGroup(root_material_id)
+ if not material_group:
+ Logger.log("d", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
+ return
+
+ nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
+ for node in nodes_to_remove:
+ self._container_registry.removeContainer(node.metadata["id"])
+
+ #
+ # Creates a duplicate of a material, which has the same GUID and base_file metadata.
+ # Returns the root material ID of the duplicated material if successful.
+ #
+ @pyqtSlot("QVariant", result = str)
+ def duplicateMaterial(self, material_node, new_base_id = None, new_metadata = None) -> Optional[str]:
+ root_material_id = material_node.metadata["base_file"]
+
+ material_group = self.getMaterialGroup(root_material_id)
+ if not material_group:
+ Logger.log("i", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id)
+ return None
+
+ base_container = material_group.root_material_node.getContainer()
+
+ # Ensure all settings are saved.
+ self._application.saveSettings()
+
+ # Create a new ID & container to hold the data.
+ new_containers = []
+ if new_base_id is None:
+ new_base_id = self._container_registry.uniqueName(base_container.getId())
+ new_base_container = copy.deepcopy(base_container)
+ new_base_container.getMetaData()["id"] = new_base_id
+ new_base_container.getMetaData()["base_file"] = new_base_id
+ if new_metadata is not None:
+ for key, value in new_metadata.items():
+ new_base_container.getMetaData()[key] = value
+ new_containers.append(new_base_container)
+
+ # Clone all of them.
+ for node in material_group.derived_material_node_list:
+ container_to_copy = node.getContainer()
+ # Create unique IDs for every clone.
+ new_id = new_base_id
+ if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
+ new_id += "_" + container_to_copy.getMetaDataEntry("definition")
+ if container_to_copy.getMetaDataEntry("variant_name"):
+ variant_name = container_to_copy.getMetaDataEntry("variant_name")
+ new_id += "_" + variant_name.replace(" ", "_")
+
+ new_container = copy.deepcopy(container_to_copy)
+ new_container.getMetaData()["id"] = new_id
+ new_container.getMetaData()["base_file"] = new_base_id
+ if new_metadata is not None:
+ for key, value in new_metadata.items():
+ new_container.getMetaData()[key] = value
+
+ new_containers.append(new_container)
+
+ for container_to_add in new_containers:
+ container_to_add.setDirty(True)
+ self._container_registry.addContainer(container_to_add)
+ return new_base_id
+
+ #
+ # Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
+ #
+ @pyqtSlot(result = str)
+ def createMaterial(self) -> str:
+ from UM.i18n import i18nCatalog
+ catalog = i18nCatalog("cura")
+ # Ensure all settings are saved.
+ self._application.saveSettings()
+
+ global_stack = self._application.getGlobalContainerStack()
+ approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
+ root_material_id = "generic_pla"
+ root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
+ material_group = self.getMaterialGroup(root_material_id)
+
+ # Create a new ID & container to hold the data.
+ new_id = self._container_registry.uniqueName("custom_material")
+ new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
+ "brand": catalog.i18nc("@label", "Custom"),
+ "GUID": str(uuid.uuid4()),
+ }
+
+ self.duplicateMaterial(material_group.root_material_node,
+ new_base_id = new_id,
+ new_metadata = new_metadata)
+ return new_id
diff --git a/cura/Machines/MaterialNode.py b/cura/Machines/MaterialNode.py
new file mode 100644
index 0000000000..fde11186c2
--- /dev/null
+++ b/cura/Machines/MaterialNode.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Optional
+
+from .ContainerNode import ContainerNode
+
+
+#
+# A MaterialNode is a node in the material lookup tree/map/table. It contains 2 (extra) fields:
+# - material_map: a one-to-one map of "material_root_id" to material_node.
+# - children_map: the key-value map for child nodes of this node. This is used in a lookup tree.
+#
+#
+class MaterialNode(ContainerNode):
+ __slots__ = ("material_map", "children_map")
+
+ def __init__(self, metadata: Optional[dict] = None):
+ super().__init__(metadata = metadata)
+ self.material_map = {} # material_root_id -> material_node
+ self.children_map = {} # mapping for the child nodes
diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py
new file mode 100644
index 0000000000..de0c68d60a
--- /dev/null
+++ b/cura/Machines/Models/BaseMaterialsModel.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
+
+from UM.Qt.ListModel import ListModel
+
+
+#
+# This is the base model class for GenericMaterialsModel and BrandMaterialsModel
+# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
+# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
+# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
+#
+class BaseMaterialsModel(ListModel):
+ RootMaterialIdRole = Qt.UserRole + 1
+ IdRole = Qt.UserRole + 2
+ NameRole = Qt.UserRole + 3
+ BrandRole = Qt.UserRole + 4
+ MaterialRole = Qt.UserRole + 5
+ ColorRole = Qt.UserRole + 6
+ ContainerNodeRole = Qt.UserRole + 7
+
+ extruderPositionChanged = pyqtSignal()
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(self.RootMaterialIdRole, "root_material_id")
+ self.addRoleName(self.IdRole, "id")
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.BrandRole, "brand")
+ self.addRoleName(self.MaterialRole, "material")
+ self.addRoleName(self.ColorRole, "color_name")
+ self.addRoleName(self.ContainerNodeRole, "container_node")
+
+ self._extruder_position = 0
+
+ def setExtruderPosition(self, position: int):
+ if self._extruder_position != position:
+ self._extruder_position = position
+ self.extruderPositionChanged.emit()
+
+ @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
+ def extruderPosition(self) -> int:
+ return self._extruder_positoin
diff --git a/cura/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py
new file mode 100644
index 0000000000..6628d924f1
--- /dev/null
+++ b/cura/Machines/Models/BrandMaterialsModel.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
+
+from UM.Qt.ListModel import ListModel
+
+from .BaseMaterialsModel import BaseMaterialsModel
+
+
+#
+# This is an intermediate model to group materials with different colours for a same brand and type.
+#
+class MaterialsModelGroupedByType(ListModel):
+ NameRole = Qt.UserRole + 1
+ ColorsRole = Qt.UserRole + 2
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.ColorsRole, "colors")
+
+
+#
+# This model is used to show branded materials in the material drop down menu.
+# The structure of the menu looks like this:
+# Brand -> Material Type -> list of materials
+#
+# To illustrate, a branded material menu may look like this:
+# Ultimaker -> PLA -> Yellow PLA
+# -> Black PLA
+# -> ...
+# -> ABS -> White ABS
+# ...
+#
+class BrandMaterialsModel(ListModel):
+ NameRole = Qt.UserRole + 1
+ MaterialsRole = Qt.UserRole + 2
+
+ extruderPositionChanged = pyqtSignal()
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.MaterialsRole, "materials")
+
+ self._extruder_position = 0
+
+ from cura.CuraApplication import CuraApplication
+ self._machine_manager = CuraApplication.getInstance().getMachineManager()
+ self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
+ self._material_manager = CuraApplication.getInstance().getMaterialManager()
+
+ self._machine_manager.globalContainerChanged.connect(self._update)
+ self._extruder_manager.activeExtruderChanged.connect(self._update)
+ self._material_manager.materialsUpdated.connect(self._update)
+
+ self._update()
+
+ def setExtruderPosition(self, position: int):
+ if self._extruder_position != position:
+ self._extruder_position = position
+ self.extruderPositionChanged.emit()
+
+ @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
+ def extruderPosition(self) -> int:
+ return self._extruder_position
+
+ def _update(self):
+ global_stack = self._machine_manager.activeMachine
+ if global_stack is None:
+ self.setItems([])
+ return
+ extruder_position = str(self._extruder_position)
+ if extruder_position not in global_stack.extruders:
+ self.setItems([])
+ return
+ extruder_stack = global_stack.extruders[str(self._extruder_position)]
+
+ available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
+ extruder_stack)
+ if available_material_dict is None:
+ self.setItems([])
+ return
+
+ brand_item_list = []
+ brand_group_dict = {}
+ for root_material_id, container_node in available_material_dict.items():
+ metadata = container_node.metadata
+ brand = metadata["brand"]
+ # Only add results for generic materials
+ if brand.lower() == "generic":
+ continue
+
+ if brand not in brand_group_dict:
+ brand_group_dict[brand] = {}
+
+ material_type = metadata["material"]
+ if material_type not in brand_group_dict[brand]:
+ brand_group_dict[brand][material_type] = []
+
+ item = {"root_material_id": root_material_id,
+ "id": metadata["id"],
+ "name": metadata["name"],
+ "brand": metadata["brand"],
+ "material": metadata["material"],
+ "color_name": metadata["color_name"],
+ "container_node": container_node
+ }
+ brand_group_dict[brand][material_type].append(item)
+
+ for brand, material_dict in brand_group_dict.items():
+ brand_item = {"name": brand,
+ "materials": MaterialsModelGroupedByType(self)}
+
+ material_type_item_list = []
+ for material_type, material_list in material_dict.items():
+ material_type_item = {"name": material_type,
+ "colors": BaseMaterialsModel(self)}
+ material_type_item["colors"].clear()
+ material_type_item["colors"].setItems(material_list)
+
+ material_type_item_list.append(material_type_item)
+
+ brand_item["materials"].setItems(material_type_item_list)
+
+ brand_item_list.append(brand_item)
+
+ self.setItems(brand_item_list)
diff --git a/cura/Machines/Models/BuildPlateModel.py b/cura/Machines/Models/BuildPlateModel.py
new file mode 100644
index 0000000000..1cb94216a6
--- /dev/null
+++ b/cura/Machines/Models/BuildPlateModel.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt
+
+from UM.Application import Application
+from UM.Qt.ListModel import ListModel
+from UM.Util import parseBool
+
+from cura.Machines.VariantManager import VariantType
+
+
+class BuildPlateModel(ListModel):
+ NameRole = Qt.UserRole + 1
+ ContainerNodeRole = Qt.UserRole + 2
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.ContainerNodeRole, "container_node")
+
+ self._application = Application.getInstance()
+ self._variant_manager = self._application._variant_manager
+ self._machine_manager = self._application.getMachineManager()
+
+ self._machine_manager.globalContainerChanged.connect(self._update)
+
+ self._update()
+
+ def _update(self):
+ global_stack = self._machine_manager._global_container_stack
+ if not global_stack:
+ self.setItems([])
+ return
+
+ has_variants = parseBool(global_stack.getMetaDataEntry("has_variant_buildplates", False))
+ if not has_variants:
+ self.setItems([])
+ return
+
+ variant_dict = self._variant_manager.getVariantNodes(global_stack, variant_type = VariantType.BUILD_PLATE)
+
+ item_list = []
+ for name, variant_node in variant_dict.items():
+ item = {"name": name,
+ "container_node": variant_node}
+ item_list.append(item)
+ self.setItems(item_list)
diff --git a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py
new file mode 100644
index 0000000000..0d297379cd
--- /dev/null
+++ b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Logger import Logger
+
+from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
+
+
+#
+# This model is used for the custom profile items in the profile drop down menu.
+#
+class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
+
+ def _update(self):
+ Logger.log("d", "Updating %s ...", self.__class__.__name__)
+
+ active_global_stack = self._machine_manager.activeMachine
+ if active_global_stack is None:
+ self.setItems([])
+ Logger.log("d", "No active GlobalStack, set %s as empty.", self.__class__.__name__)
+ return
+
+ quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(active_global_stack)
+
+ item_list = []
+ for key in sorted(quality_changes_group_dict):
+ quality_changes_group = quality_changes_group_dict[key]
+
+ item = {"name": quality_changes_group.name,
+ "layer_height": "",
+ "layer_height_without_unit": "",
+ "available": quality_changes_group.is_available,
+ "quality_changes_group": quality_changes_group}
+
+ item_list.append(item)
+
+ self.setItems(item_list)
diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py
new file mode 100644
index 0000000000..d20fc05b6e
--- /dev/null
+++ b/cura/Machines/Models/GenericMaterialsModel.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from .BaseMaterialsModel import BaseMaterialsModel
+
+
+class GenericMaterialsModel(BaseMaterialsModel):
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ from cura.CuraApplication import CuraApplication
+ self._machine_manager = CuraApplication.getInstance().getMachineManager()
+ self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
+ self._material_manager = CuraApplication.getInstance().getMaterialManager()
+
+ self._machine_manager.globalContainerChanged.connect(self._update)
+ self._extruder_manager.activeExtruderChanged.connect(self._update)
+ self._material_manager.materialsUpdated.connect(self._update)
+
+ self._update()
+
+ def _update(self):
+ global_stack = self._machine_manager.activeMachine
+ if global_stack is None:
+ self.setItems([])
+ return
+ extruder_position = str(self._extruder_position)
+ if extruder_position not in global_stack.extruders:
+ self.setItems([])
+ return
+ extruder_stack = global_stack.extruders[extruder_position]
+
+ available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
+ extruder_stack)
+ if available_material_dict is None:
+ self.setItems([])
+ return
+
+ item_list = []
+ for root_material_id, container_node in available_material_dict.items():
+ metadata = container_node.metadata
+ # Only add results for generic materials
+ if metadata["brand"].lower() != "generic":
+ continue
+
+ item = {"root_material_id": root_material_id,
+ "id": metadata["id"],
+ "name": metadata["name"],
+ "brand": metadata["brand"],
+ "material": metadata["material"],
+ "color_name": metadata["color_name"],
+ "container_node": container_node
+ }
+ item_list.append(item)
+
+ # Sort the item list by material name alphabetically
+ item_list = sorted(item_list, key = lambda d: d["name"])
+
+ self.setItems(item_list)
diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py
new file mode 100644
index 0000000000..b250232282
--- /dev/null
+++ b/cura/Machines/Models/MaterialManagementModel.py
@@ -0,0 +1,101 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt, pyqtProperty
+
+from UM.Qt.ListModel import ListModel
+
+
+#
+# This model is for the Material management page.
+#
+class MaterialManagementModel(ListModel):
+ RootMaterialIdRole = Qt.UserRole + 1
+ DisplayNameRole = Qt.UserRole + 2
+ BrandRole = Qt.UserRole + 3
+ MaterialTypeRole = Qt.UserRole + 4
+ ColorNameRole = Qt.UserRole + 5
+ ColorCodeRole = Qt.UserRole + 6
+ ContainerNodeRole = Qt.UserRole + 7
+ ContainerIdRole = Qt.UserRole + 8
+
+ DescriptionRole = Qt.UserRole + 9
+ AdhesionInfoRole = Qt.UserRole + 10
+ ApproximateDiameterRole = Qt.UserRole + 11
+ GuidRole = Qt.UserRole + 12
+ DensityRole = Qt.UserRole + 13
+ DiameterRole = Qt.UserRole + 14
+ IsReadOnlyRole = Qt.UserRole + 15
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(self.RootMaterialIdRole, "root_material_id")
+ self.addRoleName(self.DisplayNameRole, "name")
+ self.addRoleName(self.BrandRole, "brand")
+ self.addRoleName(self.MaterialTypeRole, "material")
+ self.addRoleName(self.ColorNameRole, "color_name")
+ self.addRoleName(self.ColorCodeRole, "color_code")
+ self.addRoleName(self.ContainerNodeRole, "container_node")
+ self.addRoleName(self.ContainerIdRole, "container_id")
+
+ self.addRoleName(self.DescriptionRole, "description")
+ self.addRoleName(self.AdhesionInfoRole, "adhesion_info")
+ self.addRoleName(self.ApproximateDiameterRole, "approximate_diameter")
+ self.addRoleName(self.GuidRole, "guid")
+ self.addRoleName(self.DensityRole, "density")
+ self.addRoleName(self.DiameterRole, "diameter")
+ self.addRoleName(self.IsReadOnlyRole, "is_read_only")
+
+ from cura.CuraApplication import CuraApplication
+ self._container_registry = CuraApplication.getInstance().getContainerRegistry()
+ self._machine_manager = CuraApplication.getInstance().getMachineManager()
+ self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
+ self._material_manager = CuraApplication.getInstance().getMaterialManager()
+
+ self._machine_manager.globalContainerChanged.connect(self._update)
+ self._extruder_manager.activeExtruderChanged.connect(self._update)
+ self._material_manager.materialsUpdated.connect(self._update)
+
+ self._update()
+
+ def _update(self):
+ global_stack = self._machine_manager.activeMachine
+ if global_stack is None:
+ self.setItems([])
+ return
+ active_extruder_stack = self._machine_manager.activeStack
+
+ available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
+ active_extruder_stack)
+ if available_material_dict is None:
+ self.setItems([])
+ return
+
+ material_list = []
+ for root_material_id, container_node in available_material_dict.items():
+ keys_to_fetch = ("name",
+ "brand",
+ "material",
+ "color_name",
+ "color_code",
+ "description",
+ "adhesion_info",
+ "approximate_diameter",)
+
+ item = {"root_material_id": container_node.metadata["base_file"],
+ "container_node": container_node,
+ "guid": container_node.metadata["GUID"],
+ "container_id": container_node.metadata["id"],
+ "density": container_node.metadata.get("properties", {}).get("density", ""),
+ "diameter": container_node.metadata.get("properties", {}).get("diameter", ""),
+ "is_read_only": self._container_registry.isReadOnly(container_node.metadata["id"]),
+ }
+
+ for key in keys_to_fetch:
+ item[key] = container_node.metadata.get(key, "")
+
+ material_list.append(item)
+
+ material_list = sorted(material_list, key = lambda k: (k["brand"].lower(), k["name"]))
+ self.setItems(material_list)
diff --git a/cura/Machines/Models/MultiBuildPlateModel.py b/cura/Machines/Models/MultiBuildPlateModel.py
new file mode 100644
index 0000000000..f0f4997014
--- /dev/null
+++ b/cura/Machines/Models/MultiBuildPlateModel.py
@@ -0,0 +1,57 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtSignal, pyqtProperty
+
+from UM.Application import Application
+from UM.Scene.Selection import Selection
+from UM.Qt.ListModel import ListModel
+
+
+#
+# This is the model for multi build plate feature.
+# This has nothing to do with the build plate types you can choose on the sidebar for a machine.
+#
+class MultiBuildPlateModel(ListModel):
+
+ maxBuildPlateChanged = pyqtSignal()
+ activeBuildPlateChanged = pyqtSignal()
+ selectionChanged = pyqtSignal()
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self._application = Application.getInstance()
+ self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
+ Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
+
+ self._max_build_plate = 1 # default
+ self._active_build_plate = -1
+
+ def setMaxBuildPlate(self, max_build_plate):
+ self._max_build_plate = max_build_plate
+ self.maxBuildPlateChanged.emit()
+
+ ## Return the highest build plate number
+ @pyqtProperty(int, notify = maxBuildPlateChanged)
+ def maxBuildPlate(self):
+ return self._max_build_plate
+
+ def setActiveBuildPlate(self, nr):
+ self._active_build_plate = nr
+ self.activeBuildPlateChanged.emit()
+
+ @pyqtProperty(int, notify = activeBuildPlateChanged)
+ def activeBuildPlate(self):
+ return self._active_build_plate
+
+ def _updateSelectedObjectBuildPlateNumbers(self, *args):
+ result = set()
+ for node in Selection.getAllSelectedObjects():
+ result.add(node.callDecoration("getBuildPlateNumber"))
+ self._selection_build_plates = list(result)
+ self.selectionChanged.emit()
+
+ @pyqtProperty("QVariantList", notify = selectionChanged)
+ def selectionBuildPlates(self):
+ return self._selection_build_plates
diff --git a/cura/Machines/Models/NozzleModel.py b/cura/Machines/Models/NozzleModel.py
new file mode 100644
index 0000000000..19d4a800c8
--- /dev/null
+++ b/cura/Machines/Models/NozzleModel.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt
+
+from UM.Application import Application
+from UM.Qt.ListModel import ListModel
+from UM.Util import parseBool
+
+
+class NozzleModel(ListModel):
+ IdRole = Qt.UserRole + 1
+ HotendNameRole = Qt.UserRole + 2
+ ContainerNodeRole = Qt.UserRole + 3
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(self.IdRole, "id")
+ self.addRoleName(self.HotendNameRole, "hotend_name")
+ self.addRoleName(self.ContainerNodeRole, "container_node")
+
+ Application.getInstance().globalContainerStackChanged.connect(self._update)
+ Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
+ Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
+ Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
+
+ def _update(self):
+ self.items.clear()
+
+ variant_manager = Application.getInstance()._variant_manager
+ active_global_stack = Application.getInstance().getMachineManager()._global_container_stack
+ if active_global_stack is None:
+ self.setItems([])
+ return
+
+ has_variants = parseBool(active_global_stack.getMetaDataEntry("has_variants", False))
+ if not has_variants:
+ self.setItems([])
+ return
+
+ variant_node_dict = variant_manager.getVariantNodes(active_global_stack)
+ if not variant_node_dict:
+ self.setItems([])
+ return
+
+ item_list = []
+ for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0]):
+ item = {"id": hotend_name,
+ "hotend_name": hotend_name,
+ "container_node": container_node
+ }
+
+ item_list.append(item)
+
+ self.setItems(item_list)
diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py
new file mode 100644
index 0000000000..e089f92329
--- /dev/null
+++ b/cura/Machines/Models/QualityManagementModel.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt, pyqtSlot
+
+from UM.Qt.ListModel import ListModel
+
+
+#
+# This the QML model for the quality management page.
+#
+class QualityManagementModel(ListModel):
+ NameRole = Qt.UserRole + 1
+ IsReadOnlyRole = Qt.UserRole + 2
+ QualityGroupRole = Qt.UserRole + 3
+ QualityChangesGroupRole = Qt.UserRole + 4
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.IsReadOnlyRole, "is_read_only")
+ self.addRoleName(self.QualityGroupRole, "quality_group")
+ self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
+
+ from cura.CuraApplication import CuraApplication
+ self._container_registry = CuraApplication.getInstance().getContainerRegistry()
+ self._machine_manager = CuraApplication.getInstance().getMachineManager()
+ self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
+ self._quality_manager = CuraApplication.getInstance().getQualityManager()
+
+ self._machine_manager.globalContainerChanged.connect(self._update)
+ self._quality_manager.qualitiesUpdated.connect(self._update)
+
+ self._update()
+
+ def _update(self):
+ global_stack = self._machine_manager.activeMachine
+
+ quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
+ quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack)
+
+ available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items()
+ if quality_group.is_available)
+ if not available_quality_types and not quality_changes_group_dict:
+ # Nothing to show
+ self.setItems([])
+ return
+
+ item_list = []
+ # Create quality group items
+ for quality_group in quality_group_dict.values():
+ if not quality_group.is_available:
+ continue
+
+ item = {"name": quality_group.name,
+ "is_read_only": True,
+ "quality_group": quality_group,
+ "quality_changes_group": None}
+ item_list.append(item)
+ # Sort by quality names
+ item_list = sorted(item_list, key = lambda x: x["name"])
+
+ # Create quality_changes group items
+ quality_changes_item_list = []
+ for quality_changes_group in quality_changes_group_dict.values():
+ if quality_changes_group.quality_type not in available_quality_types:
+ continue
+ quality_group = quality_group_dict[quality_changes_group.quality_type]
+ item = {"name": quality_changes_group.name,
+ "is_read_only": False,
+ "quality_group": quality_group,
+ "quality_changes_group": quality_changes_group}
+ quality_changes_item_list.append(item)
+
+ # Sort quality_changes items by names and append to the item list
+ quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"])
+ item_list += quality_changes_item_list
+
+ self.setItems(item_list)
+
+ # TODO: Duplicated code here from InstanceContainersModel. Refactor and remove this later.
+ #
+ ## Gets a list of the possible file filters that the plugins have
+ # registered they can read or write. The convenience meta-filters
+ # "All Supported Types" and "All Files" are added when listing
+ # readers, but not when listing writers.
+ #
+ # \param io_type \type{str} name of the needed IO type
+ # \return A list of strings indicating file name filters for a file
+ # dialog.
+ @pyqtSlot(str, result = "QVariantList")
+ def getFileNameFilters(self, io_type):
+ from UM.i18n import i18nCatalog
+ catalog = i18nCatalog("uranium")
+ #TODO: This function should be in UM.Resources!
+ filters = []
+ all_types = []
+ for plugin_id, meta_data in self._getIOPlugins(io_type):
+ for io_plugin in meta_data[io_type]:
+ filters.append(io_plugin["description"] + " (*." + io_plugin["extension"] + ")")
+ all_types.append("*.{0}".format(io_plugin["extension"]))
+
+ if "_reader" in io_type:
+ # if we're listing readers, add the option to show all supported files as the default option
+ filters.insert(0, catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types)))
+ filters.append(catalog.i18nc("@item:inlistbox", "All Files (*)")) # Also allow arbitrary files, if the user so prefers.
+ return filters
+
+ ## Gets a list of profile reader or writer plugins
+ # \return List of tuples of (plugin_id, meta_data).
+ def _getIOPlugins(self, io_type):
+ from UM.PluginRegistry import PluginRegistry
+ pr = PluginRegistry.getInstance()
+ active_plugin_ids = pr.getActivePlugins()
+
+ result = []
+ for plugin_id in active_plugin_ids:
+ meta_data = pr.getMetaData(plugin_id)
+ if io_type in meta_data:
+ result.append( (plugin_id, meta_data) )
+ return result
diff --git a/cura/Machines/Models/QualityProfilesDropDownMenuModel.py b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
new file mode 100644
index 0000000000..fd919639c3
--- /dev/null
+++ b/cura/Machines/Models/QualityProfilesDropDownMenuModel.py
@@ -0,0 +1,106 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import Qt
+
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Qt.ListModel import ListModel
+
+from cura.Machines.QualityManager import QualityGroup
+
+
+#
+# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
+#
+class QualityProfilesDropDownMenuModel(ListModel):
+ NameRole = Qt.UserRole + 1
+ QualityTypeRole = Qt.UserRole + 2
+ LayerHeightRole = Qt.UserRole + 3
+ LayerHeightUnitRole = Qt.UserRole + 4
+ AvailableRole = Qt.UserRole + 5
+ QualityGroupRole = Qt.UserRole + 6
+ QualityChangesGroupRole = Qt.UserRole + 7
+
+ def __init__(self, parent = None):
+ super().__init__(parent)
+
+ self.addRoleName(self.NameRole, "name")
+ self.addRoleName(self.QualityTypeRole, "quality_type")
+ self.addRoleName(self.LayerHeightRole, "layer_height")
+ self.addRoleName(self.LayerHeightUnitRole, "layer_height_unit")
+ self.addRoleName(self.AvailableRole, "available")
+ self.addRoleName(self.QualityGroupRole, "quality_group")
+ self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
+
+ self._application = Application.getInstance()
+ self._machine_manager = self._application.getMachineManager()
+ self._quality_manager = Application.getInstance().getQualityManager()
+
+ self._application.globalContainerStackChanged.connect(self._update)
+ self._machine_manager.activeQualityGroupChanged.connect(self._update)
+ self._quality_manager.qualitiesUpdated.connect(self._update)
+
+ self._layer_height_unit = "" # This is cached
+
+ self._update()
+
+ def _update(self):
+ Logger.log("d", "Updating quality profile model ...")
+
+ global_stack = self._machine_manager.activeMachine
+ if global_stack is None:
+ self.setItems([])
+ Logger.log("d", "No active GlobalStack, set quality profile model as empty.")
+ return
+
+ # Check for material compatibility
+ if not self._machine_manager.activeMaterialsCompatible():
+ Logger.log("d", "No active material compatibility, set quality profile model as empty.")
+ self.setItems([])
+ return
+
+ quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
+
+ item_list = []
+ for key in sorted(quality_group_dict):
+ quality_group = quality_group_dict[key]
+
+ layer_height = self._fetchLayerHeight(quality_group)
+
+ item = {"name": quality_group.name,
+ "quality_type": quality_group.quality_type,
+ "layer_height": layer_height,
+ "layer_height_unit": self._layer_height_unit,
+ "available": quality_group.is_available,
+ "quality_group": quality_group}
+
+ item_list.append(item)
+
+ # Sort items based on layer_height
+ item_list = sorted(item_list, key = lambda x: x["layer_height"])
+
+ self.setItems(item_list)
+
+ def _fetchLayerHeight(self, quality_group: "QualityGroup"):
+ global_stack = self._machine_manager.activeMachine
+ if not self._layer_height_unit:
+ unit = global_stack.definition.getProperty("layer_height", "unit")
+ if not unit:
+ unit = ""
+ self._layer_height_unit = unit
+
+ default_layer_height = global_stack.definition.getProperty("layer_height", "value")
+
+ # Get layer_height from the quality profile for the GlobalStack
+ container = quality_group.node_for_global.getContainer()
+
+ layer_height = default_layer_height
+ if container.hasProperty("layer_height", "value"):
+ layer_height = container.getProperty("layer_height", "value")
+ else:
+ # Look for layer_height in the GlobalStack from material -> definition
+ container = global_stack.definition
+ if container.hasProperty("layer_height", "value"):
+ layer_height = container.getProperty("layer_height", "value")
+ return float(layer_height)
diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py
new file mode 100644
index 0000000000..38b7ec28e4
--- /dev/null
+++ b/cura/Machines/Models/QualitySettingsModel.py
@@ -0,0 +1,159 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
+
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Qt.ListModel import ListModel
+from UM.Settings.ContainerRegistry import ContainerRegistry
+
+
+#
+# This model is used to show details settings of the selected quality in the quality management page.
+#
+class QualitySettingsModel(ListModel):
+ KeyRole = Qt.UserRole + 1
+ LabelRole = Qt.UserRole + 2
+ UnitRole = Qt.UserRole + 3
+ ProfileValueRole = Qt.UserRole + 4
+ ProfileValueSourceRole = Qt.UserRole + 5
+ UserValueRole = Qt.UserRole + 6
+ CategoryRole = Qt.UserRole + 7
+
+ def __init__(self, parent = None):
+ super().__init__(parent = parent)
+
+ self.addRoleName(self.KeyRole, "key")
+ self.addRoleName(self.LabelRole, "label")
+ self.addRoleName(self.UnitRole, "unit")
+ self.addRoleName(self.ProfileValueRole, "profile_value")
+ self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
+ self.addRoleName(self.UserValueRole, "user_value")
+ self.addRoleName(self.CategoryRole, "category")
+
+ self._container_registry = ContainerRegistry.getInstance()
+ self._application = Application.getInstance()
+ self._quality_manager = self._application.getQualityManager()
+
+ self._selected_position = "" # empty string means GlobalStack
+ # strings such as "0", "1", etc. mean extruder positions
+ self._selected_quality_item = None # The selected quality in the quality management page
+ self._i18n_catalog = None
+
+ self._quality_manager.qualitiesUpdated.connect(self._update)
+
+ self._update()
+
+ selectedPositionChanged = pyqtSignal()
+ selectedQualityItemChanged = pyqtSignal()
+
+ def setSelectedPosition(self, selected_position):
+ if selected_position != self._selected_position:
+ self._selected_position = selected_position
+ self.selectedPositionChanged.emit()
+ self._update()
+
+ @pyqtProperty(str, fset = setSelectedPosition, notify = selectedPositionChanged)
+ def selectedPosition(self):
+ return self._selected_position
+
+ def setSelectedQualityItem(self, selected_quality_item):
+ if selected_quality_item != self._selected_quality_item:
+ self._selected_quality_item = selected_quality_item
+ self.selectedQualityItemChanged.emit()
+ self._update()
+
+ @pyqtProperty("QVariantMap", fset = setSelectedQualityItem, notify = selectedQualityItemChanged)
+ def selectedQualityItem(self):
+ return self._selected_quality_item
+
+ def _update(self):
+ if self._selected_quality_item is None:
+ self.setItems([])
+ return
+
+ items = []
+
+ global_container_stack = self._application.getGlobalContainerStack()
+ definition_container = global_container_stack.definition
+
+ quality_group = self._selected_quality_item["quality_group"]
+ quality_changes_group = self._selected_quality_item["quality_changes_group"]
+
+ if self._selected_position == "":
+ quality_node = quality_group.node_for_global
+ else:
+ quality_node = quality_group.nodes_for_extruders.get(self._selected_position)
+ settings_keys = quality_group.getAllKeys()
+ quality_containers = [quality_node.getContainer()]
+
+ # Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch
+ # the settings in that quality_changes_group.
+ if quality_changes_group is not None:
+ if self._selected_position == "":
+ quality_changes_node = quality_changes_group.node_for_global
+ else:
+ quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._selected_position)
+ if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime
+ try:
+ quality_containers.insert(0, quality_changes_node.getContainer())
+ except:
+ # FIXME: This is to prevent incomplete update of QualityManager
+ Logger.logException("d", "Failed to get container for quality changes node %s", quality_changes_node)
+ return
+ settings_keys.update(quality_changes_group.getAllKeys())
+
+ # We iterate over all definitions instead of settings in a quality/qualtiy_changes group is because in the GUI,
+ # the settings are grouped together by categories, and we had to go over all the definitions to figure out
+ # which setting belongs in which category.
+ current_category = ""
+ for definition in definition_container.findDefinitions():
+ if definition.type == "category":
+ current_category = definition.label
+ if self._i18n_catalog:
+ current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
+ continue
+
+ profile_value = None
+ profile_value_source = ""
+ for quality_container in quality_containers:
+ new_value = quality_container.getProperty(definition.key, "value")
+
+ if new_value is not None:
+ profile_value_source = quality_container.getMetaDataEntry("type")
+ profile_value = new_value
+
+ # Global tab should use resolve (if there is one)
+ if self._selected_position == "":
+ resolve_value = global_container_stack.getProperty(definition.key, "resolve")
+ if resolve_value is not None and definition.key in settings_keys:
+ profile_value = resolve_value
+
+ if profile_value is not None:
+ break
+
+ if not self._selected_position:
+ user_value = global_container_stack.userChanges.getProperty(definition.key, "value")
+ else:
+ extruder_stack = global_container_stack.extruders[self._selected_position]
+ user_value = extruder_stack.userChanges.getProperty(definition.key, "value")
+
+ if profile_value is None and user_value is None:
+ continue
+
+ label = definition.label
+ if self._i18n_catalog:
+ label = self._i18n_catalog.i18nc(definition.key + " label", label)
+
+ items.append({
+ "key": definition.key,
+ "label": label,
+ "unit": definition.unit,
+ "profile_value": "" if profile_value is None else str(profile_value), # it is for display only
+ "profile_value_source": profile_value_source,
+ "user_value": "" if user_value is None else str(user_value),
+ "category": current_category
+ })
+
+ self.setItems(items)
diff --git a/cura/Machines/Models/__init__.py b/cura/Machines/Models/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cura/Machines/QualityChangesGroup.py b/cura/Machines/QualityChangesGroup.py
new file mode 100644
index 0000000000..f8de3d2011
--- /dev/null
+++ b/cura/Machines/QualityChangesGroup.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Application import Application
+
+from .QualityGroup import QualityGroup
+
+
+class QualityChangesGroup(QualityGroup):
+
+ def __init__(self, name: str, quality_type: str, parent = None):
+ super().__init__(name, quality_type, parent)
+ self._container_registry = Application.getInstance().getContainerRegistry()
+
+ def addNode(self, node: "QualityNode"):
+ # TODO: in 3.2 and earlier, a quality_changes container may have a field called "extruder" which contains the
+ # extruder definition ID it belongs to. But, in fact, we only need to know the following things:
+ # 1. which machine a custom profile is suitable for,
+ # 2. if this profile is for the GlobalStack,
+ # 3. if this profile is for an ExtruderStack and which one (the position).
+ #
+ # So, it is preferred to have a field like this:
+ # extruder_position = 1
+ # instead of this:
+ # extruder = custom_extruder_1
+ #
+ # An upgrade needs to be done if we want to do it this way. Before that, we use the extruder's definition
+ # to figure out its position.
+ #
+ extruder_definition_id = node.metadata.get("extruder")
+ if extruder_definition_id:
+ metadata_list = self._container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)
+ if not metadata_list:
+ raise RuntimeError("%s cannot get metadata for extruder definition [%s]" %
+ (self, extruder_definition_id))
+ extruder_definition_metadata = metadata_list[0]
+ extruder_position = str(extruder_definition_metadata["position"])
+
+ if extruder_position in self.nodes_for_extruders:
+ raise RuntimeError("%s tries to overwrite the existing nodes_for_extruders position [%s] %s with %s" %
+ (self, extruder_position, self.node_for_global, node))
+
+ self.nodes_for_extruders[extruder_position] = node
+
+ else:
+ # This is a quality_changes for the GlobalStack
+ if self.node_for_global is not None:
+ raise RuntimeError("%s tries to overwrite the existing node_for_global %s with %s" %
+ (self, self.node_for_global, node))
+ self.node_for_global = node
+
+ def __str__(self) -> str:
+ return "%s[<%s>, available = %s]" % (self.__class__.__name__, self.name, self.is_available)
diff --git a/cura/Machines/QualityGroup.py b/cura/Machines/QualityGroup.py
new file mode 100644
index 0000000000..6945162401
--- /dev/null
+++ b/cura/Machines/QualityGroup.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Optional, List
+
+from PyQt5.QtCore import QObject, pyqtSlot
+
+
+#
+# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
+# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type
+# must be applied to all stacks in a machine, although each stack can have different containers. Use an Ultimaker 3
+# as an example, suppose we choose quality type "normal", the actual InstanceContainers on each stack may look
+# as below:
+# GlobalStack ExtruderStack 1 ExtruderStack 2
+# quality container: um3_global_normal um3_aa04_pla_normal um3_aa04_abs_normal
+#
+# This QualityGroup is mainly used in quality and quality_changes to group the containers that can be applied to
+# a machine, so when a quality/custom quality is selected, the container can be directly applied to each stack instead
+# of looking them up again.
+#
+class QualityGroup(QObject):
+
+ def __init__(self, name: str, quality_type: str, parent = None):
+ super().__init__(parent)
+ self.name = name
+ self.node_for_global = None # type: Optional["QualityGroup"]
+ self.nodes_for_extruders = dict() # position str -> QualityGroup
+ self.quality_type = quality_type
+ self.is_available = False
+
+ @pyqtSlot(result = str)
+ def getName(self) -> str:
+ return self.name
+
+ def getAllKeys(self) -> set:
+ result = set()
+ for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
+ if node is None:
+ continue
+ result.update(node.getContainer().getAllKeys())
+ return result
+
+ def getAllNodes(self) -> List["QualityGroup"]:
+ result = []
+ if self.node_for_global is not None:
+ result.append(self.node_for_global)
+ for extruder_node in self.nodes_for_extruders.values():
+ result.append(extruder_node)
+ return result
diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py
new file mode 100644
index 0000000000..a2871880f0
--- /dev/null
+++ b/cura/Machines/QualityManager.py
@@ -0,0 +1,491 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import TYPE_CHECKING, Optional
+
+from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
+
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Util import parseBool
+from UM.Settings.InstanceContainer import InstanceContainer
+
+from cura.Settings.ExtruderStack import ExtruderStack
+
+from .QualityGroup import QualityGroup
+from .QualityNode import QualityNode
+
+if TYPE_CHECKING:
+ from cura.Settings.GlobalStack import GlobalStack
+ from .QualityChangesGroup import QualityChangesGroup
+
+
+#
+# Similar to MaterialManager, QualityManager maintains a number of maps and trees for quality profile lookup.
+# The models GUI and QML use are now only dependent on the QualityManager. That means as long as the data in
+# QualityManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI.
+#
+# For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them
+# again. This means the update is exactly the same as initialization. There are performance concerns about this approach
+# but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this
+# because it's simple.
+#
+class QualityManager(QObject):
+
+ qualitiesUpdated = pyqtSignal()
+
+ def __init__(self, container_registry, parent = None):
+ super().__init__(parent)
+ self._application = Application.getInstance()
+ self._material_manager = self._application.getMaterialManager()
+ self._container_registry = container_registry
+
+ self._empty_quality_container = self._application.empty_quality_container
+ self._empty_quality_changes_container = self._application.empty_quality_changes_container
+
+ self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup
+ self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
+
+ self._default_machine_definition_id = "fdmprinter"
+
+ self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
+ self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
+ self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
+
+ # When a custom quality gets added/imported, there can be more than one InstanceContainers. In those cases,
+ # we don't want to react on every container/metadata changed signal. The timer here is to buffer it a bit so
+ # we don't react too many time.
+ self._update_timer = QTimer(self)
+ self._update_timer.setInterval(300)
+ self._update_timer.setSingleShot(True)
+ self._update_timer.timeout.connect(self._updateMaps)
+
+ def initialize(self):
+ # Initialize the lookup tree for quality profiles with following structure:
+ # -> ->
+ # ->
+
+ self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup
+ self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
+
+ quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality")
+ for metadata in quality_metadata_list:
+ if metadata["id"] == "empty_quality":
+ continue
+
+ definition_id = metadata["definition"]
+ quality_type = metadata["quality_type"]
+
+ root_material_id = metadata.get("material")
+ variant_name = metadata.get("variant")
+ is_global_quality = metadata.get("global_quality", False)
+ is_global_quality = is_global_quality or (root_material_id is None and variant_name is None)
+
+ # Sanity check: material+variant and is_global_quality cannot be present at the same time
+ if is_global_quality and (root_material_id or variant_name):
+ raise RuntimeError("Quality profile [%s] contains invalid data: it is a global quality but contains 'material' and 'nozzle' info." % metadata["id"])
+
+ if definition_id not in self._machine_variant_material_quality_type_to_quality_dict:
+ self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode()
+ machine_node = self._machine_variant_material_quality_type_to_quality_dict[definition_id]
+
+ if is_global_quality:
+ # For global qualities, save data in the machine node
+ machine_node.addQualityMetadata(quality_type, metadata)
+ continue
+
+ if variant_name is not None:
+ # If variant_name is specified in the quality/quality_changes profile, check if material is specified,
+ # too.
+ if variant_name not in machine_node.children_map:
+ machine_node.children_map[variant_name] = QualityNode()
+ variant_node = machine_node.children_map[variant_name]
+
+ if root_material_id is None:
+ # If only variant_name is specified but material is not, add the quality/quality_changes metadata
+ # into the current variant node.
+ variant_node.addQualityMetadata(quality_type, metadata)
+ else:
+ # If only variant_name and material are both specified, go one level deeper: create a material node
+ # under the current variant node, and then add the quality/quality_changes metadata into the
+ # material node.
+ if root_material_id not in variant_node.children_map:
+ variant_node.children_map[root_material_id] = QualityNode()
+ material_node = variant_node.children_map[root_material_id]
+
+ material_node.addQualityMetadata(quality_type, metadata)
+
+ else:
+ # If variant_name is not specified, check if material is specified.
+ if root_material_id is not None:
+ if root_material_id not in machine_node.children_map:
+ machine_node.children_map[root_material_id] = QualityNode()
+ material_node = machine_node.children_map[root_material_id]
+
+ material_node.addQualityMetadata(quality_type, metadata)
+
+ # Initialize the lookup tree for quality_changes profiles with following structure:
+ # -> ->
+ quality_changes_metadata_list = self._container_registry.findContainersMetadata(type = "quality_changes")
+ for metadata in quality_changes_metadata_list:
+ if metadata["id"] == "empty_quality_changes":
+ continue
+
+ machine_definition_id = metadata["definition"]
+ quality_type = metadata["quality_type"]
+
+ if machine_definition_id not in self._machine_quality_type_to_quality_changes_dict:
+ self._machine_quality_type_to_quality_changes_dict[machine_definition_id] = QualityNode()
+ machine_node = self._machine_quality_type_to_quality_changes_dict[machine_definition_id]
+ machine_node.addQualityChangesMetadata(quality_type, metadata)
+
+ Logger.log("d", "Lookup tables updated.")
+ self.qualitiesUpdated.emit()
+
+ def _updateMaps(self):
+ self.initialize()
+
+ def _onContainerMetadataChanged(self, container):
+ self._onContainerChanged(container)
+
+ def _onContainerChanged(self, container):
+ container_type = container.getMetaDataEntry("type")
+ if container_type not in ("quality", "quality_changes"):
+ return
+
+ # update the cache table
+ self._update_timer.start()
+
+ # Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
+ def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
+ used_extruders = set()
+ # TODO: This will change after the Machine refactoring
+ for i in range(machine.getProperty("machine_extruder_count", "value")):
+ used_extruders.add(str(i))
+
+ # Update the "is_available" flag for each quality group.
+ for quality_group in quality_group_list:
+ is_available = True
+ if quality_group.node_for_global is None:
+ is_available = False
+ if is_available:
+ for position in used_extruders:
+ if position not in quality_group.nodes_for_extruders:
+ is_available = False
+ break
+
+ quality_group.is_available = is_available
+
+ # Returns a dict of "custom profile name" -> QualityChangesGroup
+ def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
+ machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
+
+ machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
+ if not machine_node:
+ Logger.log("i", "Cannot find node for machine def [%s] in QualityChanges lookup table", machine_definition_id)
+ return dict()
+
+ # Update availability for each QualityChangesGroup:
+ # A custom profile is always available as long as the quality_type it's based on is available
+ quality_group_dict = self.getQualityGroups(machine)
+ available_quality_type_list = [qt for qt, qg in quality_group_dict.items() if qg.is_available]
+
+ # Iterate over all quality_types in the machine node
+ quality_changes_group_dict = dict()
+ for quality_type, quality_changes_node in machine_node.quality_type_map.items():
+ for quality_changes_name, quality_changes_group in quality_changes_node.children_map.items():
+ quality_changes_group_dict[quality_changes_name] = quality_changes_group
+ quality_changes_group.is_available = quality_type in available_quality_type_list
+
+ return quality_changes_group_dict
+
+ #
+ # Gets all quality groups for the given machine. Both available and none available ones will be included.
+ # It returns a dictionary with "quality_type"s as keys and "QualityGroup"s as values.
+ # Whether a QualityGroup is available can be unknown via the field QualityGroup.is_available.
+ # For more details, see QualityGroup.
+ #
+ def getQualityGroups(self, machine: "GlobalStack") -> dict:
+ machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
+
+ # This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
+ has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
+
+ # To find the quality container for the GlobalStack, check in the following fall-back manner:
+ # (1) the machine-specific node
+ # (2) the generic node
+ machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
+ default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
+ nodes_to_check = [machine_node, default_machine_node]
+
+ # Iterate over all quality_types in the machine node
+ quality_group_dict = {}
+ for node in nodes_to_check:
+ if node and node.quality_type_map:
+ # Only include global qualities
+ if has_variant_materials:
+ quality_node = list(node.quality_type_map.values())[0]
+ is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
+ if not is_global_quality:
+ continue
+
+ for quality_type, quality_node in node.quality_type_map.items():
+ quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
+ quality_group.node_for_global = quality_node
+ quality_group_dict[quality_type] = quality_group
+ break
+
+ # Iterate over all extruders to find quality containers for each extruder
+ for position, extruder in machine.extruders.items():
+ variant_name = None
+ if extruder.variant.getId() != "empty_variant":
+ variant_name = extruder.variant.getName()
+
+ # This is a list of root material IDs to use for searching for suitable quality profiles.
+ # The root material IDs in this list are in prioritized order.
+ root_material_id_list = []
+ has_material = False # flag indicating whether this extruder has a material assigned
+ if extruder.material.getId() != "empty_material":
+ has_material = True
+ root_material_id = extruder.material.getMetaDataEntry("base_file")
+ # Convert possible generic_pla_175 -> generic_pla
+ root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter(root_material_id)
+ root_material_id_list.append(root_material_id)
+
+ # Also try to get the fallback material
+ material_type = extruder.material.getMetaDataEntry("material")
+ fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type)
+ if fallback_root_material_id:
+ root_material_id_list.append(fallback_root_material_id)
+
+ # Here we construct a list of nodes we want to look for qualities with the highest priority first.
+ # The use case is that, when we look for qualities for a machine, we first want to search in the following
+ # order:
+ # 1. machine-variant-and-material-specific qualities if exist
+ # 2. machine-variant-specific qualities if exist
+ # 3. machine-material-specific qualities if exist
+ # 4. machine-specific qualities if exist
+ # 5. generic qualities if exist
+ # Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
+ # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
+ # qualities from there.
+ nodes_to_check = []
+
+ if variant_name:
+ # In this case, we have both a specific variant and a specific material
+ variant_node = machine_node.getChildNode(variant_name)
+ if variant_node and has_material:
+ for root_material_id in root_material_id_list:
+ material_node = variant_node.getChildNode(root_material_id)
+ if material_node:
+ nodes_to_check.append(material_node)
+ break
+ nodes_to_check.append(variant_node)
+
+ # In this case, we only have a specific material but NOT a variant
+ if has_material:
+ for root_material_id in root_material_id_list:
+ material_node = machine_node.getChildNode(root_material_id)
+ if material_node:
+ nodes_to_check.append(material_node)
+ break
+
+ nodes_to_check += [machine_node, default_machine_node]
+ for node in nodes_to_check:
+ if node and node.quality_type_map:
+ if has_variant_materials:
+ # Only include variant qualities; skip non global qualities
+ quality_node = list(node.quality_type_map.values())[0]
+ is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
+ if is_global_quality:
+ continue
+
+ for quality_type, quality_node in node.quality_type_map.items():
+ if quality_type not in quality_group_dict:
+ quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
+ quality_group_dict[quality_type] = quality_group
+
+ quality_group = quality_group_dict[quality_type]
+ quality_group.nodes_for_extruders[position] = quality_node
+ break
+
+ # Update availabilities for each quality group
+ self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
+
+ return quality_group_dict
+
+ def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
+ machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
+
+ # To find the quality container for the GlobalStack, check in the following fall-back manner:
+ # (1) the machine-specific node
+ # (2) the generic node
+ machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
+ default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(
+ self._default_machine_definition_id)
+ nodes_to_check = [machine_node, default_machine_node]
+
+ # Iterate over all quality_types in the machine node
+ quality_group_dict = dict()
+ for node in nodes_to_check:
+ if node and node.quality_type_map:
+ for quality_type, quality_node in node.quality_type_map.items():
+ quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
+ quality_group.node_for_global = quality_node
+ quality_group_dict[quality_type] = quality_group
+ break
+
+ return quality_group_dict
+
+ #
+ # Methods for GUI
+ #
+
+ #
+ # Remove the given quality changes group.
+ #
+ @pyqtSlot(QObject)
+ def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
+ Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
+ for node in quality_changes_group.getAllNodes():
+ self._container_registry.removeContainer(node.metadata["id"])
+
+ #
+ # Rename a set of quality changes containers. Returns the new name.
+ #
+ @pyqtSlot(QObject, str, result = str)
+ def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
+ Logger.log("i", "Renaming QualityChangesGroup[%s] to [%s]", quality_changes_group.name, new_name)
+ if new_name == quality_changes_group.name:
+ Logger.log("i", "QualityChangesGroup name [%s] unchanged.", quality_changes_group.name)
+ return new_name
+
+ new_name = self._container_registry.uniqueName(new_name)
+ for node in quality_changes_group.getAllNodes():
+ node.getContainer().setName(new_name)
+
+ quality_changes_group.name = new_name
+
+ self._application.getMachineManager().activeQualityChanged.emit()
+ self._application.getMachineManager().activeQualityGroupChanged.emit()
+
+ return new_name
+
+ #
+ # Duplicates the given quality.
+ #
+ @pyqtSlot(str, "QVariantMap")
+ def duplicateQualityChanges(self, quality_changes_name, quality_model_item):
+ global_stack = self._application.getGlobalContainerStack()
+ if not global_stack:
+ Logger.log("i", "No active global stack, cannot duplicate quality changes.")
+ return
+
+ quality_group = quality_model_item["quality_group"]
+ quality_changes_group = quality_model_item["quality_changes_group"]
+ if quality_changes_group is None:
+ # create global quality changes only
+ new_quality_changes = self._createQualityChanges(quality_group.quality_type, quality_changes_name,
+ global_stack, extruder_id = None)
+ self._container_registry.addContainer(new_quality_changes)
+ else:
+ new_name = self._container_registry.uniqueName(quality_changes_name)
+ for node in quality_changes_group.getAllNodes():
+ container = node.getContainer()
+ new_id = self._container_registry.uniqueName(container.getId())
+ self._container_registry.addContainer(container.duplicate(new_id, new_name))
+
+ ## Create quality changes containers from the user containers in the active stacks.
+ #
+ # This will go through the global and extruder stacks and create quality_changes containers from
+ # the user containers in each stack. These then replace the quality_changes containers in the
+ # stack and clear the user settings.
+ @pyqtSlot(str)
+ def createQualityChanges(self, base_name):
+ machine_manager = Application.getInstance().getMachineManager()
+
+ global_stack = machine_manager.activeMachine
+ if not global_stack:
+ return
+
+ active_quality_name = machine_manager.activeQualityOrQualityChangesName
+ if active_quality_name == "":
+ Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
+ return
+
+ machine_manager.blurSettings.emit()
+ if base_name is None or base_name == "":
+ base_name = active_quality_name
+ unique_name = self._container_registry.uniqueName(base_name)
+
+ # Go through the active stacks and create quality_changes containers from the user containers.
+ stack_list = [global_stack] + list(global_stack.extruders.values())
+ for stack in stack_list:
+ user_container = stack.userChanges
+ quality_container = stack.quality
+ quality_changes_container = stack.qualityChanges
+ if not quality_container or not quality_changes_container:
+ Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
+ continue
+
+ extruder_definition_id = None
+ if isinstance(stack, ExtruderStack):
+ extruder_definition_id = stack.definition.getId()
+ quality_type = quality_container.getMetaDataEntry("quality_type")
+ new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_definition_id)
+ from cura.Settings.ContainerManager import ContainerManager
+ ContainerManager.getInstance()._performMerge(new_changes, quality_changes_container, clear_settings = False)
+ ContainerManager.getInstance()._performMerge(new_changes, user_container)
+
+ self._container_registry.addContainer(new_changes)
+
+ #
+ # Create a quality changes container with the given setup.
+ #
+ def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack",
+ extruder_id: Optional[str]) -> "InstanceContainer":
+ base_id = machine.definition.getId() if extruder_id is None else extruder_id
+ new_id = base_id + "_" + new_name
+ new_id = new_id.lower().replace(" ", "_")
+ new_id = self._container_registry.uniqueName(new_id)
+
+ # Create a new quality_changes container for the quality.
+ quality_changes = InstanceContainer(new_id)
+ quality_changes.setName(new_name)
+ quality_changes.addMetaDataEntry("type", "quality_changes")
+ quality_changes.addMetaDataEntry("quality_type", quality_type)
+
+ # If we are creating a container for an extruder, ensure we add that to the container
+ if extruder_id is not None:
+ quality_changes.addMetaDataEntry("extruder", extruder_id)
+
+ # If the machine specifies qualities should be filtered, ensure we match the current criteria.
+ machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
+ quality_changes.setDefinition(machine_definition_id)
+
+ quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion)
+ return quality_changes
+
+
+#
+# Gets the machine definition ID that can be used to search for Quality containers that are suitable for the given
+# machine. The rule is as follows:
+# 1. By default, the machine definition ID for quality container search will be "fdmprinter", which is the generic
+# machine.
+# 2. If a machine has its own machine quality (with "has_machine_quality = True"), we should use the given machine's
+# own machine definition ID for quality search.
+# Example: for an Ultimaker 3, the definition ID should be "ultimaker3".
+# 3. When condition (2) is met, AND the machine has "quality_definition" defined in its definition file, then the
+# definition ID specified in "quality_definition" should be used.
+# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
+# shares the same set of qualities profiles as Ultimaker 3.
+#
+def getMachineDefinitionIDForQualitySearch(machine: "GlobalStack", default_definition_id: str = "fdmprinter") -> str:
+ machine_definition_id = default_definition_id
+ if parseBool(machine.getMetaDataEntry("has_machine_quality", False)):
+ # Only use the machine's own quality definition ID if this machine has machine quality.
+ machine_definition_id = machine.getMetaDataEntry("quality_definition")
+ if machine_definition_id is None:
+ machine_definition_id = machine.definition.getId()
+
+ return machine_definition_id
diff --git a/cura/Machines/QualityNode.py b/cura/Machines/QualityNode.py
new file mode 100644
index 0000000000..a30e219da3
--- /dev/null
+++ b/cura/Machines/QualityNode.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Optional
+
+from .ContainerNode import ContainerNode
+from .QualityChangesGroup import QualityChangesGroup
+
+
+#
+# QualityNode is used for BOTH quality and quality_changes containers.
+#
+class QualityNode(ContainerNode):
+
+ def __init__(self, metadata: Optional[dict] = None):
+ super().__init__(metadata = metadata)
+ self.quality_type_map = {} # quality_type -> QualityNode for InstanceContainer
+
+ def addQualityMetadata(self, quality_type: str, metadata: dict):
+ if quality_type not in self.quality_type_map:
+ self.quality_type_map[quality_type] = QualityNode(metadata)
+
+ def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
+ return self.quality_type_map.get(quality_type)
+
+ def addQualityChangesMetadata(self, quality_type: str, metadata: dict):
+ if quality_type not in self.quality_type_map:
+ self.quality_type_map[quality_type] = QualityNode()
+ quality_type_node = self.quality_type_map[quality_type]
+
+ name = metadata["name"]
+ if name not in quality_type_node.children_map:
+ quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type)
+ quality_changes_group = quality_type_node.children_map[name]
+ quality_changes_group.addNode(QualityNode(metadata))
diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py
new file mode 100644
index 0000000000..6cb0a3d7b2
--- /dev/null
+++ b/cura/Machines/VariantManager.py
@@ -0,0 +1,111 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from enum import Enum
+from collections import OrderedDict
+from typing import Optional, TYPE_CHECKING
+
+from UM.Logger import Logger
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.Util import parseBool
+
+from cura.Machines.ContainerNode import ContainerNode
+from cura.Settings.GlobalStack import GlobalStack
+
+if TYPE_CHECKING:
+ from UM.Settings.DefinitionContainer import DefinitionContainer
+
+
+class VariantType(Enum):
+ BUILD_PLATE = "buildplate"
+ NOZZLE = "nozzle"
+
+
+ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
+
+
+#
+# VariantManager is THE place to look for a specific variant. It maintains a variant lookup table with the following
+# structure:
+#
+# [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container)
+# Example: "ultimaker3" -> "buildplate" -> "Glass" (if present) -> ContainerNode
+# -> ...
+# -> "nozzle" -> "AA 0.4"
+# -> "BB 0.8"
+# -> ...
+#
+# Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading.
+# A container is loaded when getVariant() is called to load a variant InstanceContainer.
+#
+class VariantManager:
+
+ def __init__(self, container_registry):
+ self._container_registry = container_registry # type: ContainerRegistry
+
+ self._machine_to_variant_dict_map = dict() # ->
+
+ self._exclude_variant_id_list = ["empty_variant"]
+
+ #
+ # Initializes the VariantManager including:
+ # - initializing the variant lookup table based on the metadata in ContainerRegistry.
+ #
+ def initialize(self):
+ self._machine_to_variant_dict_map = OrderedDict()
+
+ # Cache all variants from the container registry to a variant map for better searching and organization.
+ variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant")
+ for variant_metadata in variant_metadata_list:
+ if variant_metadata["id"] in self._exclude_variant_id_list:
+ Logger.log("d", "Exclude variant [%s]", variant_metadata["id"])
+ continue
+
+ variant_name = variant_metadata["name"]
+ variant_definition = variant_metadata["definition"]
+ if variant_definition not in self._machine_to_variant_dict_map:
+ self._machine_to_variant_dict_map[variant_definition] = OrderedDict()
+ for variant_type in ALL_VARIANT_TYPES:
+ self._machine_to_variant_dict_map[variant_definition][variant_type] = dict()
+
+ variant_type = variant_metadata["hardware_type"]
+ variant_type = VariantType(variant_type)
+ variant_dict = self._machine_to_variant_dict_map[variant_definition][variant_type]
+ if variant_name in variant_dict:
+ # ERROR: duplicated variant name.
+ raise RuntimeError("Found duplicated variant name [%s], type [%s] for machine [%s]" %
+ (variant_name, variant_type, variant_definition))
+
+ variant_dict[variant_name] = ContainerNode(metadata = variant_metadata)
+
+ #
+ # Gets the variant InstanceContainer with the given information.
+ # Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
+ #
+ def getVariantNode(self, machine_definition_id: str, variant_name: str,
+ variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> Optional["ContainerNode"]:
+ return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
+
+ def getVariantNodes(self, machine: "GlobalStack",
+ variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> dict:
+ machine_definition_id = machine.definition.getId()
+ return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
+
+ #
+ # Gets the default variant for the given machine definition.
+ #
+ def getDefaultVariantNode(self, machine_definition: "DefinitionContainer",
+ variant_type: VariantType) -> Optional["ContainerNode"]:
+ machine_definition_id = machine_definition.getId()
+ preferred_variant_name = None
+ if variant_type == VariantType.BUILD_PLATE:
+ if parseBool(machine_definition.getMetaDataEntry("has_variant_buildplates", False)):
+ preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_buildplate_name")
+ else:
+ if parseBool(machine_definition.getMetaDataEntry("has_variants", False)):
+ preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_name")
+
+ node = None
+ if preferred_variant_name:
+ node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
+ return node
diff --git a/cura/Machines/__init__.py b/cura/Machines/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py
index 721c0e4c07..441d4c96c3 100644
--- a/cura/MultiplyObjectsJob.py
+++ b/cura/MultiplyObjectsJob.py
@@ -2,24 +2,15 @@
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Job import Job
-from UM.Scene.SceneNode import SceneNode
-from UM.Math.Vector import Vector
-from UM.Operations.SetTransformOperation import SetTransformOperation
-from UM.Operations.TranslateOperation import TranslateOperation
from UM.Operations.GroupedOperation import GroupedOperation
-from UM.Logger import Logger
from UM.Message import Message
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
-from cura.ZOffsetDecorator import ZOffsetDecorator
-from cura.Arrange import Arrange
-from cura.ShapeArray import ShapeArray
-
-from typing import List
+from cura.Arranging.Arrange import Arrange
+from cura.Arranging.ShapeArray import ShapeArray
from UM.Application import Application
-from UM.Scene.Selection import Selection
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
@@ -65,6 +56,10 @@ class MultiplyObjectsJob(Job):
new_location = new_location.set(z = 100 - i * 20)
node.setPosition(new_location)
+ # Same build plate
+ build_plate_number = current_node.callDecoration("getBuildPlateNumber")
+ node.callDecoration("setBuildPlateNumber", build_plate_number)
+
nodes.append(node)
current_progress += 1
status_message.setProgress((current_progress / total_progress) * 100)
diff --git a/cura/ObjectsModel.py b/cura/ObjectsModel.py
new file mode 100644
index 0000000000..d7077d3d85
--- /dev/null
+++ b/cura/ObjectsModel.py
@@ -0,0 +1,79 @@
+from UM.Application import Application
+from UM.Qt.ListModel import ListModel
+from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
+from UM.Scene.SceneNode import SceneNode
+from UM.Scene.Selection import Selection
+from UM.Preferences import Preferences
+from UM.i18n import i18nCatalog
+
+catalog = i18nCatalog("cura")
+
+
+## Keep track of all objects in the project
+class ObjectsModel(ListModel):
+ def __init__(self):
+ super().__init__()
+
+ Application.getInstance().getController().getScene().sceneChanged.connect(self._update)
+ Preferences.getInstance().preferenceChanged.connect(self._update)
+
+ self._build_plate_number = -1
+
+ self._stacks_have_errors = None # type:Optional[bool]
+
+ def setActiveBuildPlate(self, nr):
+ self._build_plate_number = nr
+ self._update()
+
+ def _update(self, *args):
+ nodes = []
+ filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")
+ active_build_plate_number = self._build_plate_number
+ group_nr = 1
+ for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
+ if not isinstance(node, SceneNode):
+ continue
+ if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
+ continue
+ if node.getParent() and node.getParent().callDecoration("isGroup"):
+ continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
+ if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
+ continue
+ node_build_plate_number = node.callDecoration("getBuildPlateNumber")
+ if filter_current_build_plate and node_build_plate_number != active_build_plate_number:
+ continue
+
+ if not node.callDecoration("isGroup"):
+ name = node.getName()
+ else:
+ name = catalog.i18nc("@label", "Group #{group_nr}").format(group_nr = str(group_nr))
+ group_nr += 1
+
+ if hasattr(node, "isOutsideBuildArea"):
+ is_outside_build_area = node.isOutsideBuildArea()
+ else:
+ is_outside_build_area = False
+
+ nodes.append({
+ "name": name,
+ "isSelected": Selection.isSelected(node),
+ "isOutsideBuildArea": is_outside_build_area,
+ "buildPlateNumber": node_build_plate_number,
+ "node": node
+ })
+ nodes = sorted(nodes, key=lambda n: n["name"])
+ self.setItems(nodes)
+
+ self.itemsChanged.emit()
+
+ @staticmethod
+ def createObjectsModel():
+ return ObjectsModel()
+
+ ## Check if none of the model's stacks contain error states
+ # The setting applied for the settings per model
+ def stacksHaveErrors(self) -> bool:
+ return bool(self._stacks_have_errors)
+
+ def setStacksHaveErrors(self, value):
+ self._stacks_have_errors = value
\ No newline at end of file
diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py
index 44f8d2766a..84d65bae8e 100644
--- a/cura/OneAtATimeIterator.py
+++ b/cura/OneAtATimeIterator.py
@@ -18,12 +18,13 @@ class OneAtATimeIterator(Iterator.Iterator):
def _fillStack(self):
node_list = []
for node in self._scene_node.getChildren():
- if not type(node) is SceneNode:
+ if not issubclass(type(node), SceneNode):
continue
if node.callDecoration("getConvexHull"):
node_list.append(node)
+
if len(node_list) < 2:
self._node_stack = node_list[:]
return
diff --git a/cura/PlatformPhysicsOperation.py b/cura/Operations/PlatformPhysicsOperation.py
similarity index 100%
rename from cura/PlatformPhysicsOperation.py
rename to cura/Operations/PlatformPhysicsOperation.py
diff --git a/cura/Operations/SetBuildPlateNumberOperation.py b/cura/Operations/SetBuildPlateNumberOperation.py
new file mode 100644
index 0000000000..96230639f9
--- /dev/null
+++ b/cura/Operations/SetBuildPlateNumberOperation.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Scene.SceneNode import SceneNode
+from UM.Operations.Operation import Operation
+
+from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
+
+## Simple operation to set the buildplate number of a scenenode.
+class SetBuildPlateNumberOperation(Operation):
+
+ def __init__(self, node: SceneNode, build_plate_nr: int) -> None:
+ super().__init__()
+ self._node = node
+ self._build_plate_nr = build_plate_nr
+ self._previous_build_plate_nr = None
+ self._decorator_added = False
+
+ def undo(self):
+ if self._previous_build_plate_nr:
+ self._node.callDecoration("setBuildPlateNumber", self._previous_build_plate_nr)
+
+ def redo(self):
+ stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
+ if not stack:
+ self._node.addDecorator(SettingOverrideDecorator())
+
+ self._previous_build_plate_nr = self._node.callDecoration("getBuildPlateNumber")
+ self._node.callDecoration("setBuildPlateNumber", self._build_plate_nr)
diff --git a/cura/SetParentOperation.py b/cura/Operations/SetParentOperation.py
similarity index 100%
rename from cura/SetParentOperation.py
rename to cura/Operations/SetParentOperation.py
diff --git a/cura/Operations/__init__.py b/cura/Operations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py
index 23197dac24..69890178e4 100755
--- a/cura/PlatformPhysics.py
+++ b/cura/PlatformPhysics.py
@@ -10,10 +10,10 @@ from UM.Math.Vector import Vector
from UM.Scene.Selection import Selection
from UM.Preferences import Preferences
-from cura.ConvexHullDecorator import ConvexHullDecorator
+from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
-from . import PlatformPhysicsOperation
-from . import ZOffsetDecorator
+from cura.Operations import PlatformPhysicsOperation
+from cura.Scene import ZOffsetDecorator
import random # used for list shuffling
@@ -34,6 +34,7 @@ class PlatformPhysics:
self._change_timer.timeout.connect(self._onChangeTimerFinished)
self._move_factor = 1.1 # By how much should we multiply overlap to calculate a new spot?
self._max_overlap_checks = 10 # How many times should we try to find a new spot per tick?
+ self._minimum_gap = 2 # It is a minimum distance (in mm) between two models, applicable for small models
Preferences.getInstance().addPreference("physics/automatic_push_free", True)
Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
@@ -41,7 +42,7 @@ class PlatformPhysics:
def _onSceneChanged(self, source):
self._change_timer.start()
- def _onChangeTimerFinished(self, was_triggered_by_tool=False):
+ def _onChangeTimerFinished(self):
if not self._enabled:
return
@@ -60,7 +61,7 @@ class PlatformPhysics:
random.shuffle(nodes)
for node in nodes:
- if node is root or type(node) is not SceneNode or node.getBoundingBox() is None:
+ if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None:
continue
bbox = node.getBoundingBox()
@@ -70,17 +71,18 @@ class PlatformPhysics:
if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup")) and node.isEnabled(): #If an object is grouped, don't move it down
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
- move_vector = move_vector.set(y=-bbox.bottom + z_offset)
+ move_vector = move_vector.set(y = -bbox.bottom + z_offset)
# If there is no convex hull for the node, start calculating it and continue.
if not node.getDecorator(ConvexHullDecorator):
node.addDecorator(ConvexHullDecorator())
- if Preferences.getInstance().getValue("physics/automatic_push_free"):
+ # only push away objects if this node is a printing mesh
+ if not node.callDecoration("isNonPrintingMesh") and Preferences.getInstance().getValue("physics/automatic_push_free"):
# Check for collisions between convex hulls
for other_node in BreadthFirstIterator(root):
# Ignore root, ourselves and anything that is not a normal SceneNode.
- if other_node is root or type(other_node) is not SceneNode or other_node is node:
+ if other_node is root or not issubclass(type(other_node), SceneNode) or other_node is node or other_node.callDecoration("getBuildPlateNumber") != node.callDecoration("getBuildPlateNumber"):
continue
# Ignore collisions of a group with it's own children
@@ -98,6 +100,9 @@ class PlatformPhysics:
if other_node in transformed_nodes:
continue # Other node is already moving, wait for next pass.
+ if other_node.callDecoration("isNonPrintingMesh"):
+ continue
+
overlap = (0, 0) # Start loop with no overlap
current_overlap_checks = 0
# Continue to check the overlap until we no longer find one.
@@ -112,26 +117,38 @@ class PlatformPhysics:
overlap = node.callDecoration("getConvexHull").translate(move_vector.x, move_vector.z).intersectsPolygon(other_head_hull)
if overlap:
# Moving ensured that overlap was still there. Try anew!
- move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
- z=move_vector.z + overlap[1] * self._move_factor)
+ move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor,
+ z = move_vector.z + overlap[1] * self._move_factor)
else:
# Moving ensured that overlap was still there. Try anew!
- move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
- z=move_vector.z + overlap[1] * self._move_factor)
+ move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor,
+ z = move_vector.z + overlap[1] * self._move_factor)
else:
own_convex_hull = node.callDecoration("getConvexHull")
other_convex_hull = other_node.callDecoration("getConvexHull")
if own_convex_hull and other_convex_hull:
overlap = own_convex_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_convex_hull)
if overlap: # Moving ensured that overlap was still there. Try anew!
- move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
- z=move_vector.z + overlap[1] * self._move_factor)
+ temp_move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor,
+ z = move_vector.z + overlap[1] * self._move_factor)
+
+ # if the distance between two models less than 2mm then try to find a new factor
+ if abs(temp_move_vector.x - overlap[0]) < self._minimum_gap and abs(temp_move_vector.y - overlap[1]) < self._minimum_gap:
+ temp_x_factor = (abs(overlap[0]) + self._minimum_gap) / overlap[0] if overlap[0] != 0 else 0 # find x move_factor, like (3.4 + 2) / 3.4 = 1.58
+ temp_y_factor = (abs(overlap[1]) + self._minimum_gap) / overlap[1] if overlap[1] != 0 else 0 # find y move_factor
+
+ temp_scale_factor = temp_x_factor if abs(temp_x_factor) > abs(temp_y_factor) else temp_y_factor
+
+ move_vector = move_vector.set(x = move_vector.x + overlap[0] * temp_scale_factor,
+ z = move_vector.z + overlap[1] * temp_scale_factor)
+ else:
+ move_vector = temp_move_vector
else:
# This can happen in some cases if the object is not yet done with being loaded.
- # Simply waiting for the next tick seems to resolve this correctly.
+ # Simply waiting for the next tick seems to resolve this correctly.
overlap = None
- if not Vector.Null.equals(move_vector, epsilon=1e-5):
+ if not Vector.Null.equals(move_vector, epsilon = 1e-5):
transformed_nodes.append(node)
op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
op.push()
@@ -160,4 +177,4 @@ class PlatformPhysics:
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
self._enabled = True
- self._onChangeTimerFinished(True)
+ self._onChangeTimerFinished()
diff --git a/cura/PreviewPass.py b/cura/PreviewPass.py
index c1880e82ef..de21a5dc86 100644
--- a/cura/PreviewPass.py
+++ b/cura/PreviewPass.py
@@ -1,7 +1,7 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Uranium is released under the terms of the LGPLv3 or higher.
-
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
+from UM.Math.Color import Color
from UM.Resources import Resources
from UM.View.RenderPass import RenderPass
@@ -17,6 +17,18 @@ MYPY = False
if MYPY:
from UM.Scene.Camera import Camera
+
+# Make color brighter by normalizing it (maximum factor 2.5 brighter)
+# color_list is a list of 4 elements: [r, g, b, a], each element is a float 0..1
+def prettier_color(color_list):
+ maximum = max(color_list[:3])
+ if maximum > 0:
+ factor = min(1 / maximum, 2.5)
+ else:
+ factor = 1.0
+ return [min(i * factor, 1.0) for i in color_list]
+
+
## A render pass subclass that renders slicable objects with default parameters.
# It uses the active camera by default, but it can be overridden to use a different camera.
#
@@ -39,7 +51,14 @@ class PreviewPass(RenderPass):
def render(self) -> None:
if not self._shader:
- self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "object.shader"))
+ self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
+ self._shader.setUniformValue("u_overhangAngle", 1.0)
+ self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
+ self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
+ self._shader.setUniformValue("u_shininess", 20.0)
+
+ self._gl.glClearColor(0.0, 0.0, 0.0, 0.0)
+ self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
# Create a new batch to be rendered
batch = RenderBatch(self._shader)
@@ -47,7 +66,9 @@ class PreviewPass(RenderPass):
# Fill up the batch with objects that can be sliced. `
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
- batch.addItem(node.getWorldTransformation(), node.getMeshData())
+ uniforms = {}
+ uniforms["diffuse_color"] = prettier_color(node.getDiffuseColor())
+ batch.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms)
self.bind()
if self._camera is None:
@@ -55,3 +76,4 @@ class PreviewPass(RenderPass):
else:
batch.render(self._camera)
self.release()
+
diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py
index 5e64413d27..05b740637d 100644
--- a/cura/PrintInformation.py
+++ b/cura/PrintInformation.py
@@ -1,24 +1,22 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
-from UM.FlameProfiler import pyqtSlot
+from typing import Dict
+import math
+import os.path
+import unicodedata
+import json
+import re # To create abbreviations for printer names.
+
+from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.Duration import Duration
from UM.Preferences import Preferences
-from UM.Settings.ContainerRegistry import ContainerRegistry
-
-from cura.Settings.ExtruderManager import ExtruderManager
-
-import math
-import os.path
-import unicodedata
-import json
-import re #To create abbreviations for printer names.
-
+from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
+
catalog = i18nCatalog("cura")
## A class for processing and calculating minimum, current and maximum print time as well as managing the job name
@@ -54,36 +52,45 @@ class PrintInformation(QObject):
self.initializeCuraMessagePrintTimeProperties()
- self._material_lengths = []
- self._material_weights = []
- self._material_costs = []
- self._material_names = []
+ self._material_lengths = {} # indexed by build plate number
+ self._material_weights = {}
+ self._material_costs = {}
+ self._material_names = {}
self._pre_sliced = False
self._backend = Application.getInstance().getBackend()
if self._backend:
self._backend.printDurationMessage.connect(self._onPrintDurationMessage)
+ Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
self._base_name = ""
self._abbr_machine = ""
self._job_name = ""
+ self._project_name = ""
+ self._active_build_plate = 0
+ self._initVariablesWithBuildPlate(self._active_build_plate)
+
+ self._application = Application.getInstance()
+ self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
+
+ self._application.globalContainerStackChanged.connect(self._updateJobName)
+ self._application.globalContainerStackChanged.connect(self.setToZeroPrintInformation)
+ self._application.fileLoaded.connect(self.setBaseName)
+ self._application.workspaceLoaded.connect(self.setProjectName)
+ self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
- Application.getInstance().globalContainerStackChanged.connect(self._updateJobName)
- Application.getInstance().fileLoaded.connect(self.setBaseName)
- Application.getInstance().workspaceLoaded.connect(self.setProjectName)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
- self._active_material_container = None
- Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._onActiveMaterialChanged)
- self._onActiveMaterialChanged()
+ self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
+ self._onActiveMaterialsChanged()
self._material_amounts = []
# Crate cura message translations and using translation keys initialize empty time Duration object for total time
# and time for each feature
def initializeCuraMessagePrintTimeProperties(self):
- self._current_print_time = Duration(None, self)
+ self._current_print_time = {} # Duration(None, self)
self._print_time_message_translations = {
"inset_0": catalog.i18nc("@tooltip", "Outer Wall"),
@@ -101,10 +108,25 @@ class PrintInformation(QObject):
self._print_time_message_values = {}
+ def _initPrintTimeMessageValues(self, build_plate_number):
# Full fill message values using keys from _print_time_message_translations
+ self._print_time_message_values[build_plate_number] = {}
for key in self._print_time_message_translations.keys():
- self._print_time_message_values[key] = Duration(None, self)
+ self._print_time_message_values[build_plate_number][key] = Duration(None, self)
+ def _initVariablesWithBuildPlate(self, build_plate_number):
+ if build_plate_number not in self._print_time_message_values:
+ self._initPrintTimeMessageValues(build_plate_number)
+ if self._active_build_plate not in self._material_lengths:
+ self._material_lengths[self._active_build_plate] = []
+ if self._active_build_plate not in self._material_weights:
+ self._material_weights[self._active_build_plate] = []
+ if self._active_build_plate not in self._material_costs:
+ self._material_costs[self._active_build_plate] = []
+ if self._active_build_plate not in self._material_names:
+ self._material_names[self._active_build_plate] = []
+ if self._active_build_plate not in self._current_print_time:
+ self._current_print_time[self._active_build_plate] = Duration(None, self)
currentPrintTimeChanged = pyqtSignal()
@@ -120,79 +142,84 @@ class PrintInformation(QObject):
@pyqtProperty(Duration, notify = currentPrintTimeChanged)
def currentPrintTime(self):
- return self._current_print_time
+ return self._current_print_time[self._active_build_plate]
materialLengthsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialLengthsChanged)
def materialLengths(self):
- return self._material_lengths
+ return self._material_lengths[self._active_build_plate]
materialWeightsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialWeightsChanged)
def materialWeights(self):
- return self._material_weights
+ return self._material_weights[self._active_build_plate]
materialCostsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialCostsChanged)
def materialCosts(self):
- return self._material_costs
+ return self._material_costs[self._active_build_plate]
materialNamesChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialNamesChanged)
def materialNames(self):
- return self._material_names
+ return self._material_names[self._active_build_plate]
- def _onPrintDurationMessage(self, print_time, material_amounts):
+ def printTimes(self):
+ return self._print_time_message_values[self._active_build_plate]
- self._updateTotalPrintTimePerFeature(print_time)
+ def _onPrintDurationMessage(self, build_plate_number, print_time: Dict[str, int], material_amounts: list):
+ self._updateTotalPrintTimePerFeature(build_plate_number, print_time)
self.currentPrintTimeChanged.emit()
self._material_amounts = material_amounts
- self._calculateInformation()
+ self._calculateInformation(build_plate_number)
- def _updateTotalPrintTimePerFeature(self, print_time):
+ def _updateTotalPrintTimePerFeature(self, build_plate_number, print_time: Dict[str, int]):
total_estimated_time = 0
+ if build_plate_number not in self._print_time_message_values:
+ self._initPrintTimeMessageValues(build_plate_number)
+
for feature, time in print_time.items():
if time != time: # Check for NaN. Engine can sometimes give us weird values.
- self._print_time_message_values.get(feature).setDuration(0)
+ self._print_time_message_values[build_plate_number].get(feature).setDuration(0)
Logger.log("w", "Received NaN for print duration message")
continue
total_estimated_time += time
- self._print_time_message_values.get(feature).setDuration(time)
+ self._print_time_message_values[build_plate_number].get(feature).setDuration(time)
- self._current_print_time.setDuration(total_estimated_time)
+ if build_plate_number not in self._current_print_time:
+ self._current_print_time[build_plate_number] = Duration(None, self)
+ self._current_print_time[build_plate_number].setDuration(total_estimated_time)
- def _calculateInformation(self):
- if Application.getInstance().getGlobalContainerStack() is None:
+ def _calculateInformation(self, build_plate_number):
+ global_stack = Application.getInstance().getGlobalContainerStack()
+ if global_stack is None:
return
- # Material amount is sent as an amount of mm^3, so calculate length from that
- radius = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
- self._material_lengths = []
- self._material_weights = []
- self._material_costs = []
- self._material_names = []
+ self._material_lengths[build_plate_number] = []
+ self._material_weights[build_plate_number] = []
+ self._material_costs[build_plate_number] = []
+ self._material_names[build_plate_number] = []
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
- extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(Application.getInstance().getGlobalContainerStack().getId()))
- for index, amount in enumerate(self._material_amounts):
+ extruder_stacks = global_stack.extruders
+ for position, extruder_stack in extruder_stacks.items():
+ index = int(position)
+ if index >= len(self._material_amounts):
+ continue
+ amount = self._material_amounts[index]
## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some
# list comprehension filtering to solve this for us.
- material = None
- if extruder_stacks: # Multi extrusion machine
- extruder_stack = [extruder for extruder in extruder_stacks if extruder.getMetaDataEntry("position") == str(index)][0]
- density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
- material = extruder_stack.findContainer({"type": "material"})
- else: # Machine with no extruder stacks
- density = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("properties", {}).get("density", 0)
- material = Application.getInstance().getGlobalContainerStack().findContainer({"type": "material"})
+ density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
+ material = extruder_stack.findContainer({"type": "material"})
+ radius = extruder_stack.getProperty("material_diameter", "value") / 2
weight = float(amount) * float(density) / 1000
cost = 0
@@ -211,14 +238,15 @@ class PrintInformation(QObject):
else:
cost = 0
+ # Material amount is sent as an amount of mm^3, so calculate length from that
if radius != 0:
length = round((amount / (math.pi * radius ** 2)) / 1000, 2)
else:
length = 0
- self._material_weights.append(weight)
- self._material_lengths.append(length)
- self._material_costs.append(cost)
- self._material_names.append(material_name)
+ self._material_weights[build_plate_number].append(weight)
+ self._material_lengths[build_plate_number].append(length)
+ self._material_costs[build_plate_number].append(cost)
+ self._material_names[build_plate_number].append(material_name)
self.materialLengthsChanged.emit()
self.materialWeightsChanged.emit()
@@ -229,24 +257,25 @@ class PrintInformation(QObject):
if preference != "cura/material_settings":
return
- self._calculateInformation()
+ for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
+ self._calculateInformation(build_plate_number)
- def _onActiveMaterialChanged(self):
- if self._active_material_container:
- try:
- self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged)
- except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that is already disconnected.
- pass
+ def _onActiveBuildPlateChanged(self):
+ new_active_build_plate = self._multi_build_plate_model.activeBuildPlate
+ if new_active_build_plate != self._active_build_plate:
+ self._active_build_plate = new_active_build_plate
- active_material_id = Application.getInstance().getMachineManager().activeMaterialId
- active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id = active_material_id)
+ self._initVariablesWithBuildPlate(self._active_build_plate)
- if active_material_containers:
- self._active_material_container = active_material_containers[0]
- self._active_material_container.metaDataChanged.connect(self._onMaterialMetaDataChanged)
+ self.materialLengthsChanged.emit()
+ self.materialWeightsChanged.emit()
+ self.materialCostsChanged.emit()
+ self.materialNamesChanged.emit()
+ self.currentPrintTimeChanged.emit()
- def _onMaterialMetaDataChanged(self, *args, **kwargs):
- self._calculateInformation()
+ def _onActiveMaterialsChanged(self, *args, **kwargs):
+ for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
+ self._calculateInformation(build_plate_number)
@pyqtSlot(str)
def setJobName(self, name):
@@ -315,10 +344,10 @@ class PrintInformation(QObject):
if not global_container_stack:
self._abbr_machine = ""
return
+ active_machine_type_name = global_container_stack.definition.getName()
- global_stack_name = global_container_stack.getName()
abbr_machine = ""
- for word in re.findall(r"[\w']+", global_stack_name):
+ for word in re.findall(r"[\w']+", active_machine_type_name):
if word.lower() == "ultimaker":
abbr_machine += "UM"
elif word.isdigit():
@@ -340,7 +369,9 @@ class PrintInformation(QObject):
@pyqtSlot(result = "QVariantMap")
def getFeaturePrintTimes(self):
result = {}
- for feature, time in self._print_time_message_values.items():
+ if self._active_build_plate not in self._print_time_message_values:
+ self._initPrintTimeMessageValues(self._active_build_plate)
+ for feature, time in self._print_time_message_values[self._active_build_plate].items():
if feature in self._print_time_message_translations:
result[self._print_time_message_translations[feature]] = time
else:
@@ -348,10 +379,27 @@ class PrintInformation(QObject):
return result
# Simulate message with zero time duration
- def setToZeroPrintInformation(self):
- temp_message = {}
- for key in self._print_time_message_values.keys():
- temp_message[key] = 0
+ def setToZeroPrintInformation(self, build_plate = None):
+ if build_plate is None:
+ build_plate = self._active_build_plate
+ # Construct the 0-time message
+ temp_message = {}
+ if build_plate not in self._print_time_message_values:
+ self._print_time_message_values[build_plate] = {}
+ for key in self._print_time_message_values[build_plate].keys():
+ temp_message[key] = 0
temp_material_amounts = [0]
- self._onPrintDurationMessage(temp_message, temp_material_amounts)
+
+ self._onPrintDurationMessage(build_plate, temp_message, temp_material_amounts)
+
+ ## Listen to scene changes to check if we need to reset the print information
+ def _onSceneChanged(self, scene_node):
+
+ # Ignore any changes that are not related to sliceable objects
+ if not isinstance(scene_node, SceneNode)\
+ or not scene_node.callDecoration("isSliceable")\
+ or not scene_node.callDecoration("getBuildPlateNumber") == self._active_build_plate:
+ return
+
+ self.setToZeroPrintInformation(self._active_build_plate)
diff --git a/cura/PrinterOutput/ExtruderOuputModel.py b/cura/PrinterOutput/ExtruderOuputModel.py
new file mode 100644
index 0000000000..b0be6cbbe4
--- /dev/null
+++ b/cura/PrinterOutput/ExtruderOuputModel.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
+from UM.Logger import Logger
+
+from typing import Optional
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+ from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
+
+
+class ExtruderOutputModel(QObject):
+ hotendIDChanged = pyqtSignal()
+ targetHotendTemperatureChanged = pyqtSignal()
+ hotendTemperatureChanged = pyqtSignal()
+ activeMaterialChanged = pyqtSignal()
+
+ def __init__(self, printer: "PrinterOutputModel", parent=None):
+ super().__init__(parent)
+ self._printer = printer
+ self._target_hotend_temperature = 0
+ self._hotend_temperature = 0
+ self._hotend_id = ""
+ self._active_material = None # type: Optional[MaterialOutputModel]
+
+ @pyqtProperty(QObject, notify = activeMaterialChanged)
+ def activeMaterial(self) -> "MaterialOutputModel":
+ return self._active_material
+
+ def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
+ if self._active_material != material:
+ self._active_material = material
+ self.activeMaterialChanged.emit()
+
+ ## Update the hotend temperature. This only changes it locally.
+ def updateHotendTemperature(self, temperature: float):
+ if self._hotend_temperature != temperature:
+ self._hotend_temperature = temperature
+ self.hotendTemperatureChanged.emit()
+
+ def updateTargetHotendTemperature(self, temperature: float):
+ if self._target_hotend_temperature != temperature:
+ self._target_hotend_temperature = temperature
+ self.targetHotendTemperatureChanged.emit()
+
+ ## Set the target hotend temperature. This ensures that it's actually sent to the remote.
+ @pyqtSlot(float)
+ def setTargetHotendTemperature(self, temperature: float):
+ self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
+ self.updateTargetHotendTemperature(temperature)
+
+ @pyqtProperty(float, notify = targetHotendTemperatureChanged)
+ def targetHotendTemperature(self) -> float:
+ return self._target_hotend_temperature
+
+ @pyqtProperty(float, notify=hotendTemperatureChanged)
+ def hotendTemperature(self) -> float:
+ return self._hotend_temperature
+
+ @pyqtProperty(str, notify = hotendIDChanged)
+ def hotendID(self) -> str:
+ return self._hotend_id
+
+ def updateHotendID(self, id: str):
+ if self._hotend_id != id:
+ self._hotend_id = id
+ self.hotendIDChanged.emit()
diff --git a/cura/PrinterOutput/MaterialOutputModel.py b/cura/PrinterOutput/MaterialOutputModel.py
new file mode 100644
index 0000000000..64ebd3c94c
--- /dev/null
+++ b/cura/PrinterOutput/MaterialOutputModel.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
+
+
+class MaterialOutputModel(QObject):
+ def __init__(self, guid, type, color, brand, name, parent = None):
+ super().__init__(parent)
+ self._guid = guid
+ self._type = type
+ self._color = color
+ self._brand = brand
+ self._name = name
+
+ @pyqtProperty(str, constant = True)
+ def guid(self):
+ return self._guid
+
+ @pyqtProperty(str, constant=True)
+ def type(self):
+ return self._type
+
+ @pyqtProperty(str, constant=True)
+ def brand(self):
+ return self._brand
+
+ @pyqtProperty(str, constant=True)
+ def color(self):
+ return self._color
+
+ @pyqtProperty(str, constant=True)
+ def name(self):
+ return self._name
\ No newline at end of file
diff --git a/cura/PrinterOutput/NetworkCamera.py b/cura/PrinterOutput/NetworkCamera.py
new file mode 100644
index 0000000000..5b28ffd30d
--- /dev/null
+++ b/cura/PrinterOutput/NetworkCamera.py
@@ -0,0 +1,119 @@
+from UM.Logger import Logger
+
+from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, QObject, pyqtSlot
+from PyQt5.QtGui import QImage
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
+
+
+class NetworkCamera(QObject):
+ newImage = pyqtSignal()
+
+ def __init__(self, target = None, parent = None):
+ super().__init__(parent)
+ self._stream_buffer = b""
+ self._stream_buffer_start_index = -1
+ self._manager = None
+ self._image_request = None
+ self._image_reply = None
+ self._image = QImage()
+ self._image_id = 0
+
+ self._target = target
+ self._started = False
+
+ @pyqtSlot(str)
+ def setTarget(self, target):
+ restart_required = False
+ if self._started:
+ self.stop()
+ restart_required = True
+
+ self._target = target
+
+ if restart_required:
+ self.start()
+
+ @pyqtProperty(QUrl, notify=newImage)
+ def latestImage(self):
+ self._image_id += 1
+ # There is an image provider that is called "camera". In order to ensure that the image qml object, that
+ # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
+ # as new (instead of relying on cached version and thus forces an update.
+ temp = "image://camera/" + str(self._image_id)
+
+ return QUrl(temp, QUrl.TolerantMode)
+
+ @pyqtSlot()
+ def start(self):
+ # Ensure that previous requests (if any) are stopped.
+ self.stop()
+ if self._target is None:
+ Logger.log("w", "Unable to start camera stream without target!")
+ return
+ self._started = True
+ url = QUrl(self._target)
+ self._image_request = QNetworkRequest(url)
+ if self._manager is None:
+ self._manager = QNetworkAccessManager()
+
+ self._image_reply = self._manager.get(self._image_request)
+ self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
+
+ @pyqtSlot()
+ def stop(self):
+ self._stream_buffer = b""
+ self._stream_buffer_start_index = -1
+
+ if self._image_reply:
+ try:
+ # disconnect the signal
+ try:
+ self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
+ except Exception:
+ pass
+ # abort the request if it's not finished
+ if not self._image_reply.isFinished():
+ self._image_reply.close()
+ except Exception as e: # RuntimeError
+ pass # It can happen that the wrapped c++ object is already deleted.
+
+ self._image_reply = None
+ self._image_request = None
+
+ self._manager = None
+
+ self._started = False
+
+ def getImage(self):
+ return self._image
+
+ ## Ensure that close gets called when object is destroyed
+ def __del__(self):
+ self.stop()
+
+ def _onStreamDownloadProgress(self, bytes_received, bytes_total):
+ # An MJPG stream is (for our purpose) a stream of concatenated JPG images.
+ # JPG images start with the marker 0xFFD8, and end with 0xFFD9
+ if self._image_reply is None:
+ return
+ self._stream_buffer += self._image_reply.readAll()
+
+ if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
+ Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
+ self.stop() # resets stream buffer and start index
+ self.start()
+ return
+
+ if self._stream_buffer_start_index == -1:
+ self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
+ stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
+ # If this happens to be more than a single frame, then so be it; the JPG decoder will
+ # ignore the extra data. We do it like this in order not to get a buildup of frames
+
+ if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
+ jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
+ self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
+ self._stream_buffer_start_index = -1
+ self._image.loadFromData(jpg_data)
+
+ self.newImage.emit()
diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
new file mode 100644
index 0000000000..315b195e2a
--- /dev/null
+++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
@@ -0,0 +1,307 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Application import Application
+from UM.Logger import Logger
+
+from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
+
+from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
+from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, pyqtSignal, QUrl, QCoreApplication
+from time import time
+from typing import Callable, Any, Optional, Dict, Tuple
+from enum import IntEnum
+from typing import List
+
+import os # To get the username
+import gzip
+
+class AuthState(IntEnum):
+ NotAuthenticated = 1
+ AuthenticationRequested = 2
+ Authenticated = 3
+ AuthenticationDenied = 4
+ AuthenticationReceived = 5
+
+
+class NetworkedPrinterOutputDevice(PrinterOutputDevice):
+ authenticationStateChanged = pyqtSignal()
+
+ def __init__(self, device_id, address: str, properties, parent = None) -> None:
+ super().__init__(device_id = device_id, parent = parent)
+ self._manager = None # type: QNetworkAccessManager
+ self._last_manager_create_time = None # type: float
+ self._recreate_network_manager_time = 30
+ self._timeout_time = 10 # After how many seconds of no response should a timeout occur?
+
+ self._last_response_time = None # type: float
+ self._last_request_time = None # type: float
+
+ self._api_prefix = ""
+ self._address = address
+ self._properties = properties
+ self._user_agent = "%s/%s " % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion())
+
+ self._onFinishedCallbacks = {} # type: Dict[str, Callable[[QNetworkReply], None]]
+ self._authentication_state = AuthState.NotAuthenticated
+
+ # QHttpMultiPart objects need to be kept alive and not garbage collected during the
+ # HTTP which uses them. We hold references to these QHttpMultiPart objects here.
+ self._kept_alive_multiparts = {} # type: Dict[QNetworkReply, QHttpMultiPart]
+
+ self._sending_gcode = False
+ self._compressing_gcode = False
+ self._gcode = [] # type: List[str]
+
+ self._connection_state_before_timeout = None # type: Optional[ConnectionState]
+
+ def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None:
+ raise NotImplementedError("requestWrite needs to be implemented")
+
+ def setAuthenticationState(self, authentication_state) -> None:
+ if self._authentication_state != authentication_state:
+ self._authentication_state = authentication_state
+ self.authenticationStateChanged.emit()
+
+ @pyqtProperty(int, notify=authenticationStateChanged)
+ def authenticationState(self) -> int:
+ return self._authentication_state
+
+ def _compressDataAndNotifyQt(self, data_to_append: str) -> bytes:
+ compressed_data = gzip.compress(data_to_append.encode("utf-8"))
+ self._progress_message.setProgress(-1) # Tickle the message so that it's clear that it's still being used.
+ QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
+
+ # Pretend that this is a response, as zipping might take a bit of time.
+ # If we don't do this, the device might trigger a timeout.
+ self._last_response_time = time()
+ return compressed_data
+
+ def _compressGCode(self) -> Optional[bytes]:
+ self._compressing_gcode = True
+
+ ## Mash the data into single string
+ max_chars_per_line = int(1024 * 1024 / 4) # 1/4 MB per line.
+ file_data_bytes_list = []
+ batched_lines = []
+ batched_lines_count = 0
+
+ for line in self._gcode:
+ if not self._compressing_gcode:
+ self._progress_message.hide()
+ # Stop trying to zip / send as abort was called.
+ return None
+
+ # if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
+ # Compressing line by line in this case is extremely slow, so we need to batch them.
+ batched_lines.append(line)
+ batched_lines_count += len(line)
+
+ if batched_lines_count >= max_chars_per_line:
+ file_data_bytes_list.append(self._compressDataAndNotifyQt("".join(batched_lines)))
+ batched_lines = []
+ batched_lines_count = 0
+
+ # Don't miss the last batch (If any)
+ if len(batched_lines) != 0:
+ file_data_bytes_list.append(self._compressDataAndNotifyQt("".join(batched_lines)))
+
+ self._compressing_gcode = False
+ return b"".join(file_data_bytes_list)
+
+ def _update(self) -> bool:
+ if self._last_response_time:
+ time_since_last_response = time() - self._last_response_time
+ else:
+ time_since_last_response = 0
+
+ if self._last_request_time:
+ time_since_last_request = time() - self._last_request_time
+ else:
+ time_since_last_request = float("inf") # An irrelevantly large number of seconds
+
+ if time_since_last_response > self._timeout_time >= time_since_last_request:
+ # Go (or stay) into timeout.
+ if self._connection_state_before_timeout is None:
+ self._connection_state_before_timeout = self._connection_state
+
+ self.setConnectionState(ConnectionState.closed)
+
+ # We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to
+ # sleep.
+ if time_since_last_response > self._recreate_network_manager_time:
+ if self._last_manager_create_time is None:
+ self._createNetworkManager()
+ if time() - self._last_manager_create_time > self._recreate_network_manager_time:
+ self._createNetworkManager()
+ elif self._connection_state == ConnectionState.closed:
+ # Go out of timeout.
+ self.setConnectionState(self._connection_state_before_timeout)
+ self._connection_state_before_timeout = None
+
+ return True
+
+ def _createEmptyRequest(self, target, content_type: Optional[str] = "application/json") -> QNetworkRequest:
+ url = QUrl("http://" + self._address + self._api_prefix + target)
+ request = QNetworkRequest(url)
+ if content_type is not None:
+ request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
+ request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
+ return request
+
+ def _createFormPart(self, content_header, data, content_type = None) -> QHttpPart:
+ part = QHttpPart()
+
+ if not content_header.startswith("form-data;"):
+ content_header = "form_data; " + content_header
+ part.setHeader(QNetworkRequest.ContentDispositionHeader, content_header)
+
+ if content_type is not None:
+ part.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
+
+ part.setBody(data)
+ return part
+
+ ## Convenience function to get the username from the OS.
+ # The code was copied from the getpass module, as we try to use as little dependencies as possible.
+ def _getUserName(self) -> str:
+ for name in ("LOGNAME", "USER", "LNAME", "USERNAME"):
+ user = os.environ.get(name)
+ if user:
+ return user
+ return "Unknown User" # Couldn't find out username.
+
+ def _clearCachedMultiPart(self, reply: QNetworkReply) -> None:
+ if reply in self._kept_alive_multiparts:
+ del self._kept_alive_multiparts[reply]
+
+ def put(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
+ if self._manager is None:
+ self._createNetworkManager()
+ request = self._createEmptyRequest(target)
+ self._last_request_time = time()
+ reply = self._manager.put(request, data.encode())
+ self._registerOnFinishedCallback(reply, onFinished)
+
+ def get(self, target: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
+ if self._manager is None:
+ self._createNetworkManager()
+ request = self._createEmptyRequest(target)
+ self._last_request_time = time()
+ reply = self._manager.get(request)
+ self._registerOnFinishedCallback(reply, onFinished)
+
+ def post(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
+ if self._manager is None:
+ self._createNetworkManager()
+ request = self._createEmptyRequest(target)
+ self._last_request_time = time()
+ reply = self._manager.post(request, data)
+ if onProgress is not None:
+ reply.uploadProgress.connect(onProgress)
+ self._registerOnFinishedCallback(reply, onFinished)
+
+ def postFormWithParts(self, target:str, parts: List[QHttpPart], onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
+ if self._manager is None:
+ self._createNetworkManager()
+ request = self._createEmptyRequest(target, content_type=None)
+ multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
+ for part in parts:
+ multi_post_part.append(part)
+
+ self._last_request_time = time()
+
+ reply = self._manager.post(request, multi_post_part)
+
+ self._kept_alive_multiparts[reply] = multi_post_part
+
+ if onProgress is not None:
+ reply.uploadProgress.connect(onProgress)
+ self._registerOnFinishedCallback(reply, onFinished)
+
+
+ return reply
+
+ def postForm(self, target: str, header_data: str, body_data: bytes, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
+ post_part = QHttpPart()
+ post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
+ post_part.setBody(body_data)
+
+ self.postFormWithParts(target, [post_part], onFinished, onProgress)
+
+ def _onAuthenticationRequired(self, reply, authenticator) -> None:
+ Logger.log("w", "Request to {url} required authentication, which was not implemented".format(url = reply.url().toString()))
+
+ def _createNetworkManager(self) -> None:
+ Logger.log("d", "Creating network manager")
+ if self._manager:
+ self._manager.finished.disconnect(self.__handleOnFinished)
+ self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired)
+
+ self._manager = QNetworkAccessManager()
+ self._manager.finished.connect(self.__handleOnFinished)
+ self._last_manager_create_time = time()
+ self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
+
+ def _registerOnFinishedCallback(self, reply: QNetworkReply, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
+ if onFinished is not None:
+ self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
+
+ def __handleOnFinished(self, reply: QNetworkReply) -> None:
+ # Due to garbage collection, we need to cache certain bits of post operations.
+ # As we don't want to keep them around forever, delete them if we get a reply.
+ if reply.operation() == QNetworkAccessManager.PostOperation:
+ self._clearCachedMultiPart(reply)
+
+ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) is None:
+ # No status code means it never even reached remote.
+ return
+
+ self._last_response_time = time()
+
+ if self._connection_state == ConnectionState.connecting:
+ self.setConnectionState(ConnectionState.connected)
+
+ callback_key = reply.url().toString() + str(reply.operation())
+ try:
+ if callback_key in self._onFinishedCallbacks:
+ self._onFinishedCallbacks[callback_key](reply)
+ except Exception:
+ Logger.logException("w", "something went wrong with callback")
+
+ @pyqtSlot(str, result=str)
+ def getProperty(self, key: str) -> str:
+ bytes_key = key.encode("utf-8")
+ if bytes_key in self._properties:
+ return self._properties.get(bytes_key, b"").decode("utf-8")
+ else:
+ return ""
+
+ def getProperties(self):
+ return self._properties
+
+ ## Get the unique key of this machine
+ # \return key String containing the key of the machine.
+ @pyqtProperty(str, constant=True)
+ def key(self) -> str:
+ return self._id
+
+ ## The IP address of the printer.
+ @pyqtProperty(str, constant=True)
+ def address(self) -> str:
+ return self._properties.get(b"address", b"").decode("utf-8")
+
+ ## Name of the printer (as returned from the ZeroConf properties)
+ @pyqtProperty(str, constant=True)
+ def name(self) -> str:
+ return self._properties.get(b"name", b"").decode("utf-8")
+
+ ## Firmware version (as returned from the ZeroConf properties)
+ @pyqtProperty(str, constant=True)
+ def firmwareVersion(self) -> str:
+ return self._properties.get(b"firmware_version", b"").decode("utf-8")
+
+ ## IPadress of this printer
+ @pyqtProperty(str, constant=True)
+ def ipAddress(self) -> str:
+ return self._address
diff --git a/cura/PrinterOutput/PrintJobOutputModel.py b/cura/PrinterOutput/PrintJobOutputModel.py
new file mode 100644
index 0000000000..92376ad1dd
--- /dev/null
+++ b/cura/PrinterOutput/PrintJobOutputModel.py
@@ -0,0 +1,101 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
+from typing import Optional
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+
+
+class PrintJobOutputModel(QObject):
+ stateChanged = pyqtSignal()
+ timeTotalChanged = pyqtSignal()
+ timeElapsedChanged = pyqtSignal()
+ nameChanged = pyqtSignal()
+ keyChanged = pyqtSignal()
+ assignedPrinterChanged = pyqtSignal()
+ ownerChanged = pyqtSignal()
+
+ def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None):
+ super().__init__(parent)
+ self._output_controller = output_controller
+ self._state = ""
+ self._time_total = 0
+ self._time_elapsed = 0
+ self._name = name # Human readable name
+ self._key = key # Unique identifier
+ self._assigned_printer = None # type: Optional[PrinterOutputModel]
+ self._owner = "" # Who started/owns the print job?
+
+ @pyqtProperty(str, notify=ownerChanged)
+ def owner(self):
+ return self._owner
+
+ def updateOwner(self, owner):
+ if self._owner != owner:
+ self._owner = owner
+ self.ownerChanged.emit()
+
+ @pyqtProperty(QObject, notify=assignedPrinterChanged)
+ def assignedPrinter(self):
+ return self._assigned_printer
+
+ def updateAssignedPrinter(self, assigned_printer: "PrinterOutputModel"):
+ if self._assigned_printer != assigned_printer:
+ old_printer = self._assigned_printer
+ self._assigned_printer = assigned_printer
+ if old_printer is not None:
+ # If the previously assigned printer is set, this job is moved away from it.
+ old_printer.updateActivePrintJob(None)
+ self.assignedPrinterChanged.emit()
+
+ @pyqtProperty(str, notify=keyChanged)
+ def key(self):
+ return self._key
+
+ def updateKey(self, key: str):
+ if self._key != key:
+ self._key = key
+ self.keyChanged.emit()
+
+ @pyqtProperty(str, notify = nameChanged)
+ def name(self):
+ return self._name
+
+ def updateName(self, name: str):
+ if self._name != name:
+ self._name = name
+ self.nameChanged.emit()
+
+ @pyqtProperty(int, notify = timeTotalChanged)
+ def timeTotal(self):
+ return self._time_total
+
+ @pyqtProperty(int, notify = timeElapsedChanged)
+ def timeElapsed(self):
+ return self._time_elapsed
+
+ @pyqtProperty(str, notify=stateChanged)
+ def state(self):
+ return self._state
+
+ def updateTimeTotal(self, new_time_total):
+ if self._time_total != new_time_total:
+ self._time_total = new_time_total
+ self.timeTotalChanged.emit()
+
+ def updateTimeElapsed(self, new_time_elapsed):
+ if self._time_elapsed != new_time_elapsed:
+ self._time_elapsed = new_time_elapsed
+ self.timeElapsedChanged.emit()
+
+ def updateState(self, new_state):
+ if self._state != new_state:
+ self._state = new_state
+ self.stateChanged.emit()
+
+ @pyqtSlot(str)
+ def setState(self, state):
+ self._output_controller.setJobState(self, state)
\ No newline at end of file
diff --git a/cura/PrinterOutput/PrinterOutputController.py b/cura/PrinterOutput/PrinterOutputController.py
new file mode 100644
index 0000000000..86ca10e2d3
--- /dev/null
+++ b/cura/PrinterOutput/PrinterOutputController.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Logger import Logger
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+ from cura.PrinterOutput.ExtruderOuputModel import ExtruderOuputModel
+ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+
+
+class PrinterOutputController:
+ def __init__(self, output_device):
+ self.can_pause = True
+ self.can_abort = True
+ self.can_pre_heat_bed = True
+ self.can_control_manually = True
+ self._output_device = output_device
+
+ def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOuputModel", temperature: int):
+ Logger.log("w", "Set target hotend temperature not implemented in controller")
+
+ def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
+ Logger.log("w", "Set target bed temperature not implemented in controller")
+
+ def setJobState(self, job: "PrintJobOutputModel", state: str):
+ Logger.log("w", "Set job state not implemented in controller")
+
+ def cancelPreheatBed(self, printer: "PrinterOutputModel"):
+ Logger.log("w", "Cancel preheat bed not implemented in controller")
+
+ def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
+ Logger.log("w", "Preheat bed not implemented in controller")
+
+ def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed):
+ Logger.log("w", "Set head position not implemented in controller")
+
+ def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
+ Logger.log("w", "Move head not implemented in controller")
+
+ def homeBed(self, printer):
+ Logger.log("w", "Home bed not implemented in controller")
+
+ def homeHead(self, printer):
+ Logger.log("w", "Home head not implemented in controller")
\ No newline at end of file
diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py
new file mode 100644
index 0000000000..8234989519
--- /dev/null
+++ b/cura/PrinterOutput/PrinterOutputModel.py
@@ -0,0 +1,240 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
+from UM.Logger import Logger
+from typing import Optional, List
+from UM.Math.Vector import Vector
+from cura.PrinterOutput.ExtruderOuputModel import ExtruderOutputModel
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+ from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+
+
+class PrinterOutputModel(QObject):
+ bedTemperatureChanged = pyqtSignal()
+ targetBedTemperatureChanged = pyqtSignal()
+ isPreheatingChanged = pyqtSignal()
+ stateChanged = pyqtSignal()
+ activePrintJobChanged = pyqtSignal()
+ nameChanged = pyqtSignal()
+ headPositionChanged = pyqtSignal()
+ keyChanged = pyqtSignal()
+ typeChanged = pyqtSignal()
+ cameraChanged = pyqtSignal()
+
+ def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = ""):
+ super().__init__(parent)
+ self._bed_temperature = -1 # Use -1 for no heated bed.
+ self._target_bed_temperature = 0
+ self._name = ""
+ self._key = "" # Unique identifier
+ self._controller = output_controller
+ self._extruders = [ExtruderOutputModel(printer=self) for i in range(number_of_extruders)]
+ self._head_position = Vector(0, 0, 0)
+ self._active_print_job = None # type: Optional[PrintJobOutputModel]
+ self._firmware_version = firmware_version
+ self._printer_state = "unknown"
+ self._is_preheating = False
+ self._type = ""
+
+ self._camera = None
+
+ @pyqtProperty(str, constant = True)
+ def firmwareVersion(self):
+ return self._firmware_version
+
+ def setCamera(self, camera):
+ if self._camera is not camera:
+ self._camera = camera
+ self.cameraChanged.emit()
+
+ def updateIsPreheating(self, pre_heating):
+ if self._is_preheating != pre_heating:
+ self._is_preheating = pre_heating
+ self.isPreheatingChanged.emit()
+
+ @pyqtProperty(bool, notify=isPreheatingChanged)
+ def isPreheating(self):
+ return self._is_preheating
+
+ @pyqtProperty(QObject, notify=cameraChanged)
+ def camera(self):
+ return self._camera
+
+ @pyqtProperty(str, notify = typeChanged)
+ def type(self):
+ return self._type
+
+ def updateType(self, type):
+ if self._type != type:
+ self._type = type
+ self.typeChanged.emit()
+
+ @pyqtProperty(str, notify=keyChanged)
+ def key(self):
+ return self._key
+
+ def updateKey(self, key: str):
+ if self._key != key:
+ self._key = key
+ self.keyChanged.emit()
+
+ @pyqtSlot()
+ def homeHead(self):
+ self._controller.homeHead(self)
+
+ @pyqtSlot()
+ def homeBed(self):
+ self._controller.homeBed(self)
+
+ @pyqtProperty("QVariantList", constant = True)
+ def extruders(self):
+ return self._extruders
+
+ @pyqtProperty(QVariant, notify = headPositionChanged)
+ def headPosition(self):
+ return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position_z}
+
+ def updateHeadPosition(self, x, y, z):
+ if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
+ self._head_position = Vector(x, y, z)
+ self.headPositionChanged.emit()
+
+ @pyqtProperty("long", "long", "long")
+ @pyqtProperty("long", "long", "long", "long")
+ def setHeadPosition(self, x, y, z, speed = 3000):
+ self.updateHeadPosition(x, y, z)
+ self._controller.setHeadPosition(self, x, y, z, speed)
+
+ @pyqtProperty("long")
+ @pyqtProperty("long", "long")
+ def setHeadX(self, x, speed = 3000):
+ self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
+ self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
+
+ @pyqtProperty("long")
+ @pyqtProperty("long", "long")
+ def setHeadY(self, y, speed = 3000):
+ self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
+ self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
+
+ @pyqtProperty("long")
+ @pyqtProperty("long", "long")
+ def setHeadZ(self, z, speed = 3000):
+ self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
+ self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
+
+ @pyqtSlot("long", "long", "long")
+ @pyqtSlot("long", "long", "long", "long")
+ def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
+ self._controller.moveHead(self, x, y, z, speed)
+
+ ## Pre-heats the heated bed of the printer.
+ #
+ # \param temperature The temperature to heat the bed to, in degrees
+ # Celsius.
+ # \param duration How long the bed should stay warm, in seconds.
+ @pyqtSlot(float, float)
+ def preheatBed(self, temperature, duration):
+ self._controller.preheatBed(self, temperature, duration)
+
+ @pyqtSlot()
+ def cancelPreheatBed(self):
+ self._controller.cancelPreheatBed(self)
+
+ def getController(self):
+ return self._controller
+
+ @pyqtProperty(str, notify=nameChanged)
+ def name(self):
+ return self._name
+
+ def setName(self, name):
+ self._setName(name)
+ self.updateName(name)
+
+ def updateName(self, name):
+ if self._name != name:
+ self._name = name
+ self.nameChanged.emit()
+
+ ## Update the bed temperature. This only changes it locally.
+ def updateBedTemperature(self, temperature):
+ if self._bed_temperature != temperature:
+ self._bed_temperature = temperature
+ self.bedTemperatureChanged.emit()
+
+ def updateTargetBedTemperature(self, temperature):
+ if self._target_bed_temperature != temperature:
+ self._target_bed_temperature = temperature
+ self.targetBedTemperatureChanged.emit()
+
+ ## Set the target bed temperature. This ensures that it's actually sent to the remote.
+ @pyqtSlot(int)
+ def setTargetBedTemperature(self, temperature):
+ self._controller.setTargetBedTemperature(self, temperature)
+ self.updateTargetBedTemperature(temperature)
+
+ def updateActivePrintJob(self, print_job):
+ if self._active_print_job != print_job:
+ old_print_job = self._active_print_job
+
+ if print_job is not None:
+ print_job.updateAssignedPrinter(self)
+ self._active_print_job = print_job
+
+ if old_print_job is not None:
+ old_print_job.updateAssignedPrinter(None)
+ self.activePrintJobChanged.emit()
+
+ def updateState(self, printer_state):
+ if self._printer_state != printer_state:
+ self._printer_state = printer_state
+ self.stateChanged.emit()
+
+ @pyqtProperty(QObject, notify = activePrintJobChanged)
+ def activePrintJob(self):
+ return self._active_print_job
+
+ @pyqtProperty(str, notify=stateChanged)
+ def state(self):
+ return self._printer_state
+
+ @pyqtProperty(int, notify = bedTemperatureChanged)
+ def bedTemperature(self):
+ return self._bed_temperature
+
+ @pyqtProperty(int, notify=targetBedTemperatureChanged)
+ def targetBedTemperature(self):
+ return self._target_bed_temperature
+
+ # Does the printer support pre-heating the bed at all
+ @pyqtProperty(bool, constant=True)
+ def canPreHeatBed(self):
+ if self._controller:
+ return self._controller.can_pre_heat_bed
+ return False
+
+ # Does the printer support pause at all
+ @pyqtProperty(bool, constant=True)
+ def canPause(self):
+ if self._controller:
+ return self._controller.can_pause
+ return False
+
+ # Does the printer support abort at all
+ @pyqtProperty(bool, constant=True)
+ def canAbort(self):
+ if self._controller:
+ return self._controller.can_abort
+ return False
+
+ # Does the printer support manual control at all
+ @pyqtProperty(bool, constant=True)
+ def canControlManually(self):
+ if self._controller:
+ return self._controller.can_control_manually
+ return False
diff --git a/cura/PrinterOutput/__init__.py b/cura/PrinterOutput/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py
index b147019b37..9e603b83ae 100644
--- a/cura/PrinterOutputDevice.py
+++ b/cura/PrinterOutputDevice.py
@@ -3,15 +3,21 @@
from UM.i18n import i18nCatalog
from UM.OutputDevice.OutputDevice import OutputDevice
-from PyQt5.QtCore import pyqtProperty, pyqtSlot, QObject, QTimer, pyqtSignal
+from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
from PyQt5.QtWidgets import QMessageBox
-from enum import IntEnum # For the connection state tracking.
-from UM.Settings.ContainerRegistry import ContainerRegistry
+
from UM.Logger import Logger
from UM.Signal import signalemitter
from UM.Application import Application
+from enum import IntEnum # For the connection state tracking.
+from typing import List, Optional
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+
i18n_catalog = i18nCatalog("cura")
## Printer output device adds extra interface options on top of output device.
@@ -25,662 +31,149 @@ i18n_catalog = i18nCatalog("cura")
# For all other uses it should be used in the same way as a "regular" OutputDevice.
@signalemitter
class PrinterOutputDevice(QObject, OutputDevice):
+ printersChanged = pyqtSignal()
+ connectionStateChanged = pyqtSignal(str)
+ acceptsCommandsChanged = pyqtSignal()
+
+ # Signal to indicate that the material of the active printer on the remote changed.
+ materialIdChanged = pyqtSignal()
+
+ # # Signal to indicate that the hotend of the active printer on the remote changed.
+ hotendIdChanged = pyqtSignal()
+
+ # Signal to indicate that the info text about the connection has changed.
+ connectionTextChanged = pyqtSignal()
+
def __init__(self, device_id, parent = None):
super().__init__(device_id = device_id, parent = parent)
- self._container_registry = ContainerRegistry.getInstance()
- self._target_bed_temperature = 0
- self._bed_temperature = 0
- self._num_extruders = 1
- self._hotend_temperatures = [0] * self._num_extruders
- self._target_hotend_temperatures = [0] * self._num_extruders
- self._material_ids = [""] * self._num_extruders
- self._hotend_ids = [""] * self._num_extruders
- self._progress = 0
- self._head_x = 0
- self._head_y = 0
- self._head_z = 0
- self._connection_state = ConnectionState.closed
- self._connection_text = ""
- self._time_elapsed = 0
- self._time_total = 0
- self._job_state = ""
- self._job_name = ""
- self._error_text = ""
- self._accepts_commands = True
- self._preheat_bed_timeout = 900 # Default time-out for pre-heating the bed, in seconds.
- self._preheat_bed_timer = QTimer() # Timer that tracks how long to preheat still.
- self._preheat_bed_timer.setSingleShot(True)
- self._preheat_bed_timer.timeout.connect(self.cancelPreheatBed)
-
- self._printer_state = ""
- self._printer_type = "unknown"
-
- self._camera_active = False
+ self._printers = [] # type: List[PrinterOutputModel]
self._monitor_view_qml_path = ""
+ self._monitor_component = None
self._monitor_item = None
self._control_view_qml_path = ""
+ self._control_component = None
self._control_item = None
self._qml_context = None
- self._can_pause = True
- self._can_abort = True
- self._can_pre_heat_bed = True
- self._can_control_manually = True
+ self._accepts_commands = False
- def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
+ self._update_timer = QTimer()
+ self._update_timer.setInterval(2000) # TODO; Add preference for update interval
+ self._update_timer.setSingleShot(False)
+ self._update_timer.timeout.connect(self._update)
+
+ self._connection_state = ConnectionState.closed
+
+ self._address = ""
+ self._connection_text = ""
+
+ @pyqtProperty(str, notify = connectionTextChanged)
+ def address(self):
+ return self._address
+
+ def setConnectionText(self, connection_text):
+ if self._connection_text != connection_text:
+ self._connection_text = connection_text
+ self.connectionTextChanged.emit()
+
+ @pyqtProperty(str, constant=True)
+ def connectionText(self):
+ return self._connection_text
+
+ def materialHotendChangedMessage(self, callback):
+ Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'")
+ callback(QMessageBox.Yes)
+
+ def isConnected(self):
+ return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error
+
+ def setConnectionState(self, connection_state):
+ if self._connection_state != connection_state:
+ self._connection_state = connection_state
+ self.connectionStateChanged.emit(self._id)
+
+ @pyqtProperty(str, notify = connectionStateChanged)
+ def connectionState(self):
+ return self._connection_state
+
+ def _update(self):
+ pass
+
+ def _getPrinterByKey(self, key) -> Optional["PrinterOutputModel"]:
+ for printer in self._printers:
+ if printer.key == key:
+ return printer
+
+ return None
+
+ def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
raise NotImplementedError("requestWrite needs to be implemented")
- ## Signals
+ @pyqtProperty(QObject, notify = printersChanged)
+ def activePrinter(self) -> Optional["PrinterOutputModel"]:
+ if len(self._printers):
+ return self._printers[0]
+ return None
- # Signal to be emitted when bed temp is changed
- bedTemperatureChanged = pyqtSignal()
-
- # Signal to be emitted when target bed temp is changed
- targetBedTemperatureChanged = pyqtSignal()
-
- # Signal when the progress is changed (usually when this output device is printing / sending lots of data)
- progressChanged = pyqtSignal()
-
- # Signal to be emitted when hotend temp is changed
- hotendTemperaturesChanged = pyqtSignal()
-
- # Signal to be emitted when target hotend temp is changed
- targetHotendTemperaturesChanged = pyqtSignal()
-
- # Signal to be emitted when head position is changed (x,y,z)
- headPositionChanged = pyqtSignal()
-
- # Signal to be emitted when either of the material ids is changed
- materialIdChanged = pyqtSignal(int, str, arguments = ["index", "id"])
-
- # Signal to be emitted when either of the hotend ids is changed
- hotendIdChanged = pyqtSignal(int, str, arguments = ["index", "id"])
-
- # Signal that is emitted every time connection state is changed.
- # it also sends it's own device_id (for convenience sake)
- connectionStateChanged = pyqtSignal(str)
-
- connectionTextChanged = pyqtSignal()
-
- timeElapsedChanged = pyqtSignal()
-
- timeTotalChanged = pyqtSignal()
-
- jobStateChanged = pyqtSignal()
-
- jobNameChanged = pyqtSignal()
-
- errorTextChanged = pyqtSignal()
-
- acceptsCommandsChanged = pyqtSignal()
-
- printerStateChanged = pyqtSignal()
-
- printerTypeChanged = pyqtSignal()
-
- # Signal to be emitted when some drastic change occurs in the remaining time (not when the time just passes on normally).
- preheatBedRemainingTimeChanged = pyqtSignal()
-
- # Does the printer support pre-heating the bed at all
- @pyqtProperty(bool, constant=True)
- def canPreHeatBed(self):
- return self._can_pre_heat_bed
-
- # Does the printer support pause at all
- @pyqtProperty(bool, constant=True)
- def canPause(self):
- return self._can_pause
-
- # Does the printer support abort at all
- @pyqtProperty(bool, constant=True)
- def canAbort(self):
- return self._can_abort
-
- # Does the printer support manual control at all
- @pyqtProperty(bool, constant=True)
- def canControlManually(self):
- return self._can_control_manually
+ @pyqtProperty("QVariantList", notify = printersChanged)
+ def printers(self):
+ return self._printers
@pyqtProperty(QObject, constant=True)
def monitorItem(self):
# Note that we specifically only check if the monitor component is created.
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
# create the item (and fail) every time.
- if not self._monitor_item:
+ if not self._monitor_component:
self._createMonitorViewFromQML()
return self._monitor_item
@pyqtProperty(QObject, constant=True)
def controlItem(self):
- if not self._control_item:
+ if not self._control_component:
self._createControlViewFromQML()
return self._control_item
def _createControlViewFromQML(self):
if not self._control_view_qml_path:
return
-
- self._control_item = Application.getInstance().createQmlComponent(self._control_view_qml_path, {
- "OutputDevice": self
- })
+ if self._control_item is None:
+ self._control_item = Application.getInstance().createQmlComponent(self._control_view_qml_path, {"OutputDevice": self})
def _createMonitorViewFromQML(self):
if not self._monitor_view_qml_path:
return
- self._monitor_item = Application.getInstance().createQmlComponent(self._monitor_view_qml_path, {
- "OutputDevice": self
- })
-
- @pyqtProperty(str, notify=printerTypeChanged)
- def printerType(self):
- return self._printer_type
-
- @pyqtProperty(str, notify=printerStateChanged)
- def printerState(self):
- return self._printer_state
-
- @pyqtProperty(str, notify = jobStateChanged)
- def jobState(self):
- return self._job_state
-
- def _updatePrinterType(self, printer_type):
- if self._printer_type != printer_type:
- self._printer_type = printer_type
- self.printerTypeChanged.emit()
-
- def _updatePrinterState(self, printer_state):
- if self._printer_state != printer_state:
- self._printer_state = printer_state
- self.printerStateChanged.emit()
-
- def _updateJobState(self, job_state):
- if self._job_state != job_state:
- self._job_state = job_state
- self.jobStateChanged.emit()
-
- @pyqtSlot(str)
- def setJobState(self, job_state):
- self._setJobState(job_state)
-
- def _setJobState(self, job_state):
- Logger.log("w", "_setJobState is not implemented by this output device")
-
- @pyqtSlot()
- def startCamera(self):
- self._camera_active = True
- self._startCamera()
-
- def _startCamera(self):
- Logger.log("w", "_startCamera is not implemented by this output device")
-
- @pyqtSlot()
- def stopCamera(self):
- self._camera_active = False
- self._stopCamera()
-
- def _stopCamera(self):
- Logger.log("w", "_stopCamera is not implemented by this output device")
-
- @pyqtProperty(str, notify = jobNameChanged)
- def jobName(self):
- return self._job_name
-
- def setJobName(self, name):
- if self._job_name != name:
- self._job_name = name
- self.jobNameChanged.emit()
-
- ## Gives a human-readable address where the device can be found.
- @pyqtProperty(str, constant = True)
- def address(self):
- Logger.log("w", "address is not implemented by this output device.")
-
- ## A human-readable name for the device.
- @pyqtProperty(str, constant = True)
- def name(self):
- Logger.log("w", "name is not implemented by this output device.")
- return ""
-
- @pyqtProperty(str, notify = errorTextChanged)
- def errorText(self):
- return self._error_text
-
- ## Set the error-text that is shown in the print monitor in case of an error
- def setErrorText(self, error_text):
- if self._error_text != error_text:
- self._error_text = error_text
- self.errorTextChanged.emit()
-
- @pyqtProperty(bool, notify = acceptsCommandsChanged)
- def acceptsCommands(self):
- return self._accepts_commands
-
- ## Set a flag to signal the UI that the printer is not (yet) ready to receive commands
- def setAcceptsCommands(self, accepts_commands):
- if self._accepts_commands != accepts_commands:
- self._accepts_commands = accepts_commands
- self.acceptsCommandsChanged.emit()
-
- ## Get the bed temperature of the bed (if any)
- # This function is "final" (do not re-implement)
- # /sa _getBedTemperature implementation function
- @pyqtProperty(float, notify = bedTemperatureChanged)
- def bedTemperature(self):
- return self._bed_temperature
-
- ## Set the (target) bed temperature
- # This function is "final" (do not re-implement)
- # /param temperature new target temperature of the bed (in deg C)
- # /sa _setTargetBedTemperature implementation function
- @pyqtSlot(int)
- def setTargetBedTemperature(self, temperature):
- self._setTargetBedTemperature(temperature)
- if self._target_bed_temperature != temperature:
- self._target_bed_temperature = temperature
- self.targetBedTemperatureChanged.emit()
-
- ## The total duration of the time-out to pre-heat the bed, in seconds.
- #
- # \return The duration of the time-out to pre-heat the bed, in seconds.
- @pyqtProperty(int, constant = True)
- def preheatBedTimeout(self):
- return self._preheat_bed_timeout
-
- ## The remaining duration of the pre-heating of the bed.
- #
- # This is formatted in M:SS format.
- # \return The duration of the time-out to pre-heat the bed, formatted.
- @pyqtProperty(str, notify = preheatBedRemainingTimeChanged)
- def preheatBedRemainingTime(self):
- if not self._preheat_bed_timer.isActive():
- return ""
- period = self._preheat_bed_timer.remainingTime()
- if period <= 0:
- return ""
- minutes, period = divmod(period, 60000) #60000 milliseconds in a minute.
- seconds, _ = divmod(period, 1000) #1000 milliseconds in a second.
- if minutes <= 0 and seconds <= 0:
- return ""
- return "%d:%02d" % (minutes, seconds)
-
- ## Time the print has been printing.
- # Note that timeTotal - timeElapsed should give time remaining.
- @pyqtProperty(float, notify = timeElapsedChanged)
- def timeElapsed(self):
- return self._time_elapsed
-
- ## Total time of the print
- # Note that timeTotal - timeElapsed should give time remaining.
- @pyqtProperty(float, notify=timeTotalChanged)
- def timeTotal(self):
- return self._time_total
-
- @pyqtSlot(float)
- def setTimeTotal(self, new_total):
- if self._time_total != new_total:
- self._time_total = new_total
- self.timeTotalChanged.emit()
-
- @pyqtSlot(float)
- def setTimeElapsed(self, time_elapsed):
- if self._time_elapsed != time_elapsed:
- self._time_elapsed = time_elapsed
- self.timeElapsedChanged.emit()
-
- ## Home the head of the connected printer
- # This function is "final" (do not re-implement)
- # /sa _homeHead implementation function
- @pyqtSlot()
- def homeHead(self):
- self._homeHead()
-
- ## Home the head of the connected printer
- # This is an implementation function and should be overriden by children.
- def _homeHead(self):
- Logger.log("w", "_homeHead is not implemented by this output device")
-
- ## Home the bed of the connected printer
- # This function is "final" (do not re-implement)
- # /sa _homeBed implementation function
- @pyqtSlot()
- def homeBed(self):
- self._homeBed()
-
- ## Home the bed of the connected printer
- # This is an implementation function and should be overriden by children.
- # /sa homeBed
- def _homeBed(self):
- Logger.log("w", "_homeBed is not implemented by this output device")
-
- ## Protected setter for the bed temperature of the connected printer (if any).
- # /parameter temperature Temperature bed needs to go to (in deg celsius)
- # /sa setTargetBedTemperature
- def _setTargetBedTemperature(self, temperature):
- Logger.log("w", "_setTargetBedTemperature is not implemented by this output device")
-
- ## Pre-heats the heated bed of the printer.
- #
- # \param temperature The temperature to heat the bed to, in degrees
- # Celsius.
- # \param duration How long the bed should stay warm, in seconds.
- @pyqtSlot(float, float)
- def preheatBed(self, temperature, duration):
- Logger.log("w", "preheatBed is not implemented by this output device.")
-
- ## Cancels pre-heating the heated bed of the printer.
- #
- # If the bed is not pre-heated, nothing happens.
- @pyqtSlot()
- def cancelPreheatBed(self):
- Logger.log("w", "cancelPreheatBed is not implemented by this output device.")
-
- ## Protected setter for the current bed temperature.
- # This simply sets the bed temperature, but ensures that a signal is emitted.
- # /param temperature temperature of the bed.
- def _setBedTemperature(self, temperature):
- if self._bed_temperature != temperature:
- self._bed_temperature = temperature
- self.bedTemperatureChanged.emit()
-
- ## Get the target bed temperature if connected printer (if any)
- @pyqtProperty(int, notify = targetBedTemperatureChanged)
- def targetBedTemperature(self):
- return self._target_bed_temperature
-
- ## Set the (target) hotend temperature
- # This function is "final" (do not re-implement)
- # /param index the index of the hotend that needs to change temperature
- # /param temperature The temperature it needs to change to (in deg celsius).
- # /sa _setTargetHotendTemperature implementation function
- @pyqtSlot(int, int)
- def setTargetHotendTemperature(self, index, temperature):
- self._setTargetHotendTemperature(index, temperature)
-
- if self._target_hotend_temperatures[index] != temperature:
- self._target_hotend_temperatures[index] = temperature
- self.targetHotendTemperaturesChanged.emit()
-
- ## Implementation function of setTargetHotendTemperature.
- # /param index Index of the hotend to set the temperature of
- # /param temperature Temperature to set the hotend to (in deg C)
- # /sa setTargetHotendTemperature
- def _setTargetHotendTemperature(self, index, temperature):
- Logger.log("w", "_setTargetHotendTemperature is not implemented by this output device")
-
- @pyqtProperty("QVariantList", notify = targetHotendTemperaturesChanged)
- def targetHotendTemperatures(self):
- return self._target_hotend_temperatures
-
- @pyqtProperty("QVariantList", notify = hotendTemperaturesChanged)
- def hotendTemperatures(self):
- return self._hotend_temperatures
-
- ## Protected setter for the current hotend temperature.
- # This simply sets the hotend temperature, but ensures that a signal is emitted.
- # /param index Index of the hotend
- # /param temperature temperature of the hotend (in deg C)
- def _setHotendTemperature(self, index, temperature):
- if self._hotend_temperatures[index] != temperature:
- self._hotend_temperatures[index] = temperature
- self.hotendTemperaturesChanged.emit()
-
- @pyqtProperty("QVariantList", notify = materialIdChanged)
- def materialIds(self):
- return self._material_ids
-
- @pyqtProperty("QVariantList", notify = materialIdChanged)
- def materialNames(self):
- result = []
- for material_id in self._material_ids:
- if material_id is None:
- result.append(i18n_catalog.i18nc("@item:material", "No material loaded"))
- continue
-
- containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_id)
- if containers:
- result.append(containers[0]["name"])
- else:
- result.append(i18n_catalog.i18nc("@item:material", "Unknown material"))
- return result
-
- ## List of the colours of the currently loaded materials.
- #
- # The list is in order of extruders. If there is no material in an
- # extruder, the colour is shown as transparent.
- #
- # The colours are returned in hex-format AARRGGBB or RRGGBB
- # (e.g. #800000ff for transparent blue or #00ff00 for pure green).
- @pyqtProperty("QVariantList", notify = materialIdChanged)
- def materialColors(self):
- result = []
- for material_id in self._material_ids:
- if material_id is None:
- result.append("#00000000") #No material.
- continue
-
- containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_id)
- if containers:
- result.append(containers[0]["color_code"])
- else:
- result.append("#00000000") #Unknown material.
- return result
-
- ## Protected setter for the current material id.
- # /param index Index of the extruder
- # /param material_id id of the material
- def _setMaterialId(self, index, material_id):
- if material_id and material_id != "" and material_id != self._material_ids[index]:
- Logger.log("d", "Setting material id of hotend %d to %s" % (index, material_id))
- self._material_ids[index] = material_id
- self.materialIdChanged.emit(index, material_id)
-
- @pyqtProperty("QVariantList", notify = hotendIdChanged)
- def hotendIds(self):
- return self._hotend_ids
-
- ## Protected setter for the current hotend id.
- # /param index Index of the extruder
- # /param hotend_id id of the hotend
- def _setHotendId(self, index, hotend_id):
- if hotend_id and hotend_id != self._hotend_ids[index]:
- Logger.log("d", "Setting hotend id of hotend %d to %s" % (index, hotend_id))
- self._hotend_ids[index] = hotend_id
- self.hotendIdChanged.emit(index, hotend_id)
- elif not hotend_id:
- Logger.log("d", "Removing hotend id of hotend %d.", index)
- self._hotend_ids[index] = None
- self.hotendIdChanged.emit(index, None)
-
- ## Let the user decide if the hotends and/or material should be synced with the printer
- # NB: the UX needs to be implemented by the plugin
- def materialHotendChangedMessage(self, callback):
- Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'")
- callback(QMessageBox.Yes)
+ if self._monitor_item is None:
+ self._monitor_item = Application.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self})
## Attempt to establish connection
def connect(self):
- raise NotImplementedError("connect needs to be implemented")
+ self.setConnectionState(ConnectionState.connecting)
+ self._update_timer.start()
## Attempt to close the connection
def close(self):
- raise NotImplementedError("close needs to be implemented")
-
- @pyqtProperty(bool, notify = connectionStateChanged)
- def connectionState(self):
- return self._connection_state
-
- ## Set the connection state of this output device.
- # /param connection_state ConnectionState enum.
- def setConnectionState(self, connection_state):
- if self._connection_state != connection_state:
- self._connection_state = connection_state
- self.connectionStateChanged.emit(self._id)
-
- @pyqtProperty(str, notify = connectionTextChanged)
- def connectionText(self):
- return self._connection_text
-
- ## Set a text that is shown on top of the print monitor tab
- def setConnectionText(self, connection_text):
- if self._connection_text != connection_text:
- self._connection_text = connection_text
- self.connectionTextChanged.emit()
+ self._update_timer.stop()
+ self.setConnectionState(ConnectionState.closed)
## Ensure that close gets called when object is destroyed
def __del__(self):
self.close()
- ## Get the x position of the head.
- # This function is "final" (do not re-implement)
- @pyqtProperty(float, notify = headPositionChanged)
- def headX(self):
- return self._head_x
+ @pyqtProperty(bool, notify=acceptsCommandsChanged)
+ def acceptsCommands(self):
+ return self._accepts_commands
- ## Get the y position of the head.
- # This function is "final" (do not re-implement)
- @pyqtProperty(float, notify = headPositionChanged)
- def headY(self):
- return self._head_y
+ ## Set a flag to signal the UI that the printer is not (yet) ready to receive commands
+ def _setAcceptsCommands(self, accepts_commands):
+ if self._accepts_commands != accepts_commands:
+ self._accepts_commands = accepts_commands
- ## Get the z position of the head.
- # In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
- # This function is "final" (do not re-implement)
- @pyqtProperty(float, notify = headPositionChanged)
- def headZ(self):
- return self._head_z
-
- ## Update the saved position of the head
- # This function should be called when a new position for the head is received.
- def _updateHeadPosition(self, x, y ,z):
- position_changed = False
- if self._head_x != x:
- self._head_x = x
- position_changed = True
- if self._head_y != y:
- self._head_y = y
- position_changed = True
- if self._head_z != z:
- self._head_z = z
- position_changed = True
-
- if position_changed:
- self.headPositionChanged.emit()
-
- ## Set the position of the head.
- # In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
- # This function is "final" (do not re-implement)
- # /param x new x location of the head.
- # /param y new y location of the head.
- # /param z new z location of the head.
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa _setHeadPosition implementation function
- @pyqtSlot("long", "long", "long")
- @pyqtSlot("long", "long", "long", "long")
- def setHeadPosition(self, x, y, z, speed = 3000):
- self._setHeadPosition(x, y , z, speed)
-
- ## Set the X position of the head.
- # This function is "final" (do not re-implement)
- # /param x x position head needs to move to.
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa _setHeadx implementation function
- @pyqtSlot("long")
- @pyqtSlot("long", "long")
- def setHeadX(self, x, speed = 3000):
- self._setHeadX(x, speed)
-
- ## Set the Y position of the head.
- # This function is "final" (do not re-implement)
- # /param y y position head needs to move to.
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa _setHeadY implementation function
- @pyqtSlot("long")
- @pyqtSlot("long", "long")
- def setHeadY(self, y, speed = 3000):
- self._setHeadY(y, speed)
-
- ## Set the Z position of the head.
- # In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
- # This function is "final" (do not re-implement)
- # /param z z position head needs to move to.
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa _setHeadZ implementation function
- @pyqtSlot("long")
- @pyqtSlot("long", "long")
- def setHeadZ(self, z, speed = 3000):
- self._setHeadZ(z, speed)
-
- ## Move the head of the printer.
- # Note that this is a relative move. If you want to move the head to a specific position you can use
- # setHeadPosition
- # This function is "final" (do not re-implement)
- # /param x distance in x to move
- # /param y distance in y to move
- # /param z distance in z to move
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa _moveHead implementation function
- @pyqtSlot("long", "long", "long")
- @pyqtSlot("long", "long", "long", "long")
- def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
- self._moveHead(x, y, z, speed)
-
- ## Implementation function of moveHead.
- # /param x distance in x to move
- # /param y distance in y to move
- # /param z distance in z to move
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa moveHead
- def _moveHead(self, x, y, z, speed):
- Logger.log("w", "_moveHead is not implemented by this output device")
-
- ## Implementation function of setHeadPosition.
- # /param x new x location of the head.
- # /param y new y location of the head.
- # /param z new z location of the head.
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa setHeadPosition
- def _setHeadPosition(self, x, y, z, speed):
- Logger.log("w", "_setHeadPosition is not implemented by this output device")
-
- ## Implementation function of setHeadX.
- # /param x new x location of the head.
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa setHeadX
- def _setHeadX(self, x, speed):
- Logger.log("w", "_setHeadX is not implemented by this output device")
-
- ## Implementation function of setHeadY.
- # /param y new y location of the head.
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa _setHeadY
- def _setHeadY(self, y, speed):
- Logger.log("w", "_setHeadY is not implemented by this output device")
-
- ## Implementation function of setHeadZ.
- # /param z new z location of the head.
- # /param speed Speed by which it needs to move (in mm/minute)
- # /sa _setHeadZ
- def _setHeadZ(self, z, speed):
- Logger.log("w", "_setHeadZ is not implemented by this output device")
-
- ## Get the progress of any currently active process.
- # This function is "final" (do not re-implement)
- # /sa _getProgress
- # /returns float progress of the process. -1 indicates that there is no process.
- @pyqtProperty(float, notify = progressChanged)
- def progress(self):
- return self._progress
-
- ## Set the progress of any currently active process
- # /param progress Progress of the process.
- def setProgress(self, progress):
- if self._progress != progress:
- self._progress = progress
- self.progressChanged.emit()
+ self.acceptsCommandsChanged.emit()
## The current processing state of the backend.
@@ -689,4 +182,4 @@ class ConnectionState(IntEnum):
connecting = 1
connected = 2
busy = 3
- error = 4
\ No newline at end of file
+ error = 4
diff --git a/cura/QualityManager.py b/cura/QualityManager.py
deleted file mode 100644
index 76a0c86a5f..0000000000
--- a/cura/QualityManager.py
+++ /dev/null
@@ -1,332 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-# This collects a lot of quality and quality changes related code which was split between ContainerManager
-# and the MachineManager and really needs to usable from both.
-from typing import Any, Dict, List, Optional, TYPE_CHECKING
-
-from UM.Application import Application
-from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.InstanceContainer import InstanceContainer
-from cura.Settings.ExtruderManager import ExtruderManager
-
-if TYPE_CHECKING:
- from cura.Settings.GlobalStack import GlobalStack
- from cura.Settings.ExtruderStack import ExtruderStack
- from UM.Settings.DefinitionContainer import DefinitionContainerInterface
-
-class QualityManager:
-
- ## Get the singleton instance for this class.
- @classmethod
- def getInstance(cls) -> "QualityManager":
- # Note: Explicit use of class name to prevent issues with inheritance.
- if not QualityManager.__instance:
- QualityManager.__instance = cls()
- return QualityManager.__instance
-
- __instance = None # type: "QualityManager"
-
- ## Find a quality by name for a specific machine definition and materials.
- #
- # \param quality_name
- # \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
- # specified then the currently selected machine definition is used.
- # \param material_containers_metadata If nothing is specified then the
- # current set of selected materials is used.
- # \return the matching quality container \type{InstanceContainer}
- def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None) -> Optional[InstanceContainer]:
- criteria = {"type": "quality", "name": quality_name}
- result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
-
- # Fall back to using generic materials and qualities if nothing could be found.
- if not result and material_containers_metadata and len(material_containers_metadata) == 1:
- basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
- result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
-
- return result[0] if result else None
-
- ## Find a quality changes container by name.
- #
- # \param quality_changes_name \type{str} the name of the quality changes container.
- # \param machine_definition (Optional) \type{DefinitionContainer} If nothing is
- # specified then the currently selected machine definition is used..
- # \return the matching quality changes containers \type{List[InstanceContainer]}
- def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None):
- if not machine_definition:
- global_stack = Application.getGlobalContainerStack()
- if not global_stack:
- return [] #No stack, so no current definition could be found, so there are no quality changes either.
- machine_definition = global_stack.definition
-
- result = self.findAllQualityChangesForMachine(machine_definition)
- result = [quality_change for quality_change in result if quality_change.getName() == quality_changes_name]
- return result
-
- ## Fetch the list of available quality types for this combination of machine definition and materials.
- #
- # \param machine_definition \type{DefinitionContainer}
- # \param material_containers \type{List[InstanceContainer]}
- # \return \type{List[str]}
- def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]:
- # Determine the common set of quality types which can be
- # applied to all of the materials for this machine.
- quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
- common_quality_types = set(quality_type_dict.keys())
- for material_container in material_containers[1:]:
- next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
- common_quality_types.intersection_update(set(next_quality_type_dict.keys()))
-
- return list(common_quality_types)
-
- def findAllQualitiesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[InstanceContainer]:
- # Determine the common set of quality types which can be
- # applied to all of the materials for this machine.
- quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
- qualities = set(quality_type_dict.values())
- for material_container in material_containers[1:]:
- next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
- qualities.intersection_update(set(next_quality_type_dict.values()))
-
- return list(qualities)
-
- ## Fetches a dict of quality types names to quality profiles for a combination of machine and material.
- #
- # \param machine_definition \type{DefinitionContainer} the machine definition.
- # \param material \type{InstanceContainer} the material.
- # \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities.
- def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]:
- qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
- quality_type_dict = {}
- for quality in qualities:
- quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
- return quality_type_dict
-
- ## Find a quality container by quality type.
- #
- # \param quality_type \type{str} the name of the quality type to search for.
- # \param machine_definition (Optional) \type{InstanceContainer} If nothing is
- # specified then the currently selected machine definition is used.
- # \param material_containers_metadata If nothing is specified then the
- # current set of selected materials is used.
- # \return the matching quality container \type{InstanceContainer}
- def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs) -> InstanceContainer:
- criteria = kwargs
- criteria["type"] = "quality"
- if quality_type:
- criteria["quality_type"] = quality_type
- result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
- # Fall back to using generic materials and qualities if nothing could be found.
- if not result and material_containers_metadata and len(material_containers_metadata) == 1:
- basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
- if basic_materials:
- result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
- return result[0] if result else None
-
- ## Find all suitable qualities for a combination of machine and material.
- #
- # \param machine_definition \type{DefinitionContainer} the machine definition.
- # \param material_container \type{InstanceContainer} the material.
- # \return \type{List[InstanceContainer]} the list of suitable qualities.
- def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
- criteria = {"type": "quality"}
- result = self._getFilteredContainersForStack(machine_definition, [material_container.getMetaData()], **criteria)
- if not result:
- basic_materials = self._getBasicMaterialMetadatas(material_container.getMetaData())
- if basic_materials:
- result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
-
- return result
-
- ## Find all quality changes for a machine.
- #
- # \param machine_definition \type{DefinitionContainer} the machine definition.
- # \return \type{List[InstanceContainer]} the list of quality changes
- def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
- if machine_definition.getMetaDataEntry("has_machine_quality"):
- definition_id = machine_definition.getId()
- else:
- definition_id = "fdmprinter"
-
- filter_dict = { "type": "quality_changes", "definition": definition_id }
- quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
- return quality_changes_list
-
- def findAllExtruderDefinitionsForMachine(self, machine_definition: "DefinitionContainerInterface") -> List["DefinitionContainerInterface"]:
- filter_dict = { "machine": machine_definition.getId() }
- return ContainerRegistry.getInstance().findDefinitionContainers(**filter_dict)
-
- ## Find all quality changes for a given extruder.
- #
- # \param extruder_definition The extruder to find the quality changes for.
- # \return The list of quality changes for the given extruder.
- def findAllQualityChangesForExtruder(self, extruder_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
- filter_dict = {"type": "quality_changes", "extruder": extruder_definition.getId()}
- return ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
-
- ## Find all usable qualities for a machine and extruders.
- #
- # Finds all of the qualities for this combination of machine and extruders.
- # Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
- # then only one of then is returned (at random).
- #
- # \param global_container_stack \type{GlobalStack} the global machine definition
- # \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks
- # \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
- # return come from the first extruder in the given list of extruders.
- def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
- global_machine_definition = global_container_stack.getBottom()
-
- machine_manager = Application.getInstance().getMachineManager()
- active_stack_id = machine_manager.activeStackId
-
- materials = []
-
- for stack in extruder_stacks:
- if stack.getId() == active_stack_id and machine_manager.newMaterial:
- materials.append(machine_manager.newMaterial)
- else:
- materials.append(stack.material)
-
- quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
-
- # Map the list of quality_types to InstanceContainers
- qualities = self.findAllQualitiesForMachineMaterial(global_machine_definition, materials[0])
- quality_type_dict = {}
- for quality in qualities:
- quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
-
- return [quality_type_dict[quality_type] for quality_type in quality_types]
-
- ## Fetch more basic versions of a material.
- #
- # This tries to find a generic or basic version of the given material.
- # \param material_container \type{Dict[str, Any]} The metadata of a
- # material to find the basic versions of.
- # \return \type{List[Dict[str, Any]]} A list of the metadata of basic
- # materials, or an empty list if none could be found.
- def _getBasicMaterialMetadatas(self, material_container: Dict[str, Any]) -> List[Dict[str, Any]]:
- if "definition" not in material_container:
- definition_id = "fdmprinter"
- else:
- material_container_definition = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = material_container["definition"])
- if not material_container_definition:
- definition_id = "fdmprinter"
- else:
- material_container_definition = material_container_definition[0]
- if "has_machine_quality" not in material_container_definition:
- definition_id = "fdmprinter"
- else:
- definition_id = material_container_definition.get("quality_definition", material_container_definition["id"])
-
- base_material = material_container.get("material")
- if base_material:
- # There is a basic material specified
- criteria = {
- "type": "material",
- "name": base_material,
- "definition": definition_id,
- "variant": material_container.get("variant")
- }
- containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
- return containers
-
- return []
-
- def _getFilteredContainers(self, **kwargs):
- return self._getFilteredContainersForStack(None, None, **kwargs)
-
- def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs):
- # Fill in any default values.
- if machine_definition is None:
- machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
- quality_definition_id = machine_definition.getMetaDataEntry("quality_definition")
- if quality_definition_id is not None:
- machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition_id)[0]
-
- if not material_metadata:
- active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
- if active_stacks:
- material_metadata = [stack.material.getMetaData() for stack in active_stacks]
-
- criteria = kwargs
- filter_by_material = False
-
- machine_definition = self.getParentMachineDefinition(machine_definition)
- criteria["definition"] = machine_definition.getId()
- found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
- whole_machine_definition = self.getWholeMachineDefinition(machine_definition)
- if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
- definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId())
- criteria["definition"] = definition_id
-
- filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
- # only fall back to "fdmprinter" when there is no container for this machine
- elif not found_containers_with_machine_definition:
- criteria["definition"] = "fdmprinter"
-
- # Stick the material IDs in a set
- material_ids = set()
-
- for material_instance in material_metadata:
- if material_instance is not None:
- # Add the parent material too.
- for basic_material in self._getBasicMaterialMetadatas(material_instance):
- material_ids.add(basic_material["id"])
- material_ids.add(material_instance["id"])
- containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
-
- result = []
- for container in containers:
- # If the machine specifies we should filter by material, exclude containers that do not match any active material.
- if filter_by_material and container.getMetaDataEntry("material") not in material_ids and "global_quality" not in kwargs:
- continue
- result.append(container)
-
- return result
-
- ## Get the parent machine definition of a machine definition.
- #
- # \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
- # an extruder definition.
- # \return \type{DefinitionContainer} the parent machine definition. If the given machine
- # definition doesn't have a parent then it is simply returned.
- def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
- container_registry = ContainerRegistry.getInstance()
-
- machine_entry = machine_definition.getMetaDataEntry("machine")
- if machine_entry is None:
- # We have a normal (whole) machine defintion
- quality_definition = machine_definition.getMetaDataEntry("quality_definition")
- if quality_definition is not None:
- parent_machine_definition = container_registry.findDefinitionContainers(id = quality_definition)[0]
- return self.getParentMachineDefinition(parent_machine_definition)
- else:
- return machine_definition
- else:
- # This looks like an extruder. Find the rest of the machine.
- whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
- parent_machine = self.getParentMachineDefinition(whole_machine)
- if whole_machine is parent_machine:
- # This extruder already belongs to a 'parent' machine def.
- return machine_definition
- else:
- # Look up the corresponding extruder definition in the parent machine definition.
- extruder_position = machine_definition.getMetaDataEntry("position")
- parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
- return container_registry.findDefinitionContainers(id = parent_extruder_id)[0]
-
- ## Get the whole/global machine definition from an extruder definition.
- #
- # \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
- # an extruder definition.
- # \return \type{DefinitionContainerInterface}
- def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
- machine_entry = machine_definition.getMetaDataEntry("machine")
- if machine_entry is None:
- # This already is a 'global' machine definition.
- return machine_definition
- else:
- container_registry = ContainerRegistry.getInstance()
- whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
- return whole_machine
diff --git a/cura/BlockSlicingDecorator.py b/cura/Scene/BlockSlicingDecorator.py
similarity index 100%
rename from cura/BlockSlicingDecorator.py
rename to cura/Scene/BlockSlicingDecorator.py
diff --git a/cura/Scene/BuildPlateDecorator.py b/cura/Scene/BuildPlateDecorator.py
new file mode 100644
index 0000000000..c2fd3145dd
--- /dev/null
+++ b/cura/Scene/BuildPlateDecorator.py
@@ -0,0 +1,26 @@
+from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
+from cura.Scene.CuraSceneNode import CuraSceneNode
+
+
+## Make a SceneNode build plate aware CuraSceneNode objects all have this decorator.
+class BuildPlateDecorator(SceneNodeDecorator):
+ def __init__(self, build_plate_number = -1):
+ super().__init__()
+ self._build_plate_number = None
+ self.setBuildPlateNumber(build_plate_number)
+
+ def setBuildPlateNumber(self, nr):
+ # Make sure that groups are set correctly
+ # setBuildPlateForSelection in CuraActions makes sure that no single childs are set.
+ self._build_plate_number = nr
+ if isinstance(self._node, CuraSceneNode):
+ self._node.transformChanged() # trigger refresh node without introducing a new signal
+ if self._node and self._node.callDecoration("isGroup"):
+ for child in self._node.getChildren():
+ child.callDecoration("setBuildPlateNumber", nr)
+
+ def getBuildPlateNumber(self):
+ return self._build_plate_number
+
+ def __deepcopy__(self, memo):
+ return BuildPlateDecorator()
diff --git a/cura/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py
similarity index 99%
rename from cura/ConvexHullDecorator.py
rename to cura/Scene/ConvexHullDecorator.py
index 50fa8ce7f6..3a563c2764 100644
--- a/cura/ConvexHullDecorator.py
+++ b/cura/Scene/ConvexHullDecorator.py
@@ -7,7 +7,7 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Settings.ExtruderManager import ExtruderManager
-from . import ConvexHullNode
+from cura.Scene import ConvexHullNode
import numpy
diff --git a/cura/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py
similarity index 89%
rename from cura/ConvexHullNode.py
rename to cura/Scene/ConvexHullNode.py
index cc4720c197..1131958627 100644
--- a/cura/ConvexHullNode.py
+++ b/cura/Scene/ConvexHullNode.py
@@ -6,7 +6,6 @@ from UM.Scene.SceneNode import SceneNode
from UM.Resources import Resources
from UM.Math.Color import Color
from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the convex hull with.
-
from UM.View.GL.OpenGL import OpenGL
@@ -25,7 +24,10 @@ class ConvexHullNode(SceneNode):
self._original_parent = parent
# Color of the drawn convex hull
- self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
+ if Application.getInstance().hasGui():
+ self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
+ else:
+ self._color = Color(0, 0, 0)
# The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
self._mesh_height = 0.1
@@ -66,7 +68,7 @@ class ConvexHullNode(SceneNode):
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
if self.getParent():
- if self.getMeshData():
+ if self.getMeshData() and isinstance(self._node, SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate:
renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8)
if self._convex_hull_head_mesh:
renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8)
diff --git a/cura/Scene/CuraSceneController.py b/cura/Scene/CuraSceneController.py
new file mode 100644
index 0000000000..749c5257a2
--- /dev/null
+++ b/cura/Scene/CuraSceneController.py
@@ -0,0 +1,115 @@
+from UM.Logger import Logger
+
+from PyQt5.QtCore import Qt, pyqtSlot, QObject
+from PyQt5.QtWidgets import QApplication
+
+from cura.ObjectsModel import ObjectsModel
+from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
+
+from UM.Application import Application
+from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
+from UM.Scene.SceneNode import SceneNode
+from UM.Scene.Selection import Selection
+from UM.Signal import Signal
+
+
+class CuraSceneController(QObject):
+ activeBuildPlateChanged = Signal()
+
+ def __init__(self, objects_model: ObjectsModel, multi_build_plate_model: MultiBuildPlateModel):
+ super().__init__()
+
+ self._objects_model = objects_model
+ self._multi_build_plate_model = multi_build_plate_model
+ self._active_build_plate = -1
+
+ self._last_selected_index = 0
+ self._max_build_plate = 1 # default
+
+ Application.getInstance().getController().getScene().sceneChanged.connect(self.updateMaxBuildPlate) # it may be a bit inefficient when changing a lot simultaneously
+
+ def updateMaxBuildPlate(self, *args):
+ if args:
+ source = args[0]
+ else:
+ source = None
+ if not isinstance(source, SceneNode):
+ return
+ max_build_plate = self._calcMaxBuildPlate()
+ changed = False
+ if max_build_plate != self._max_build_plate:
+ self._max_build_plate = max_build_plate
+ changed = True
+ if changed:
+ self._multi_build_plate_model.setMaxBuildPlate(self._max_build_plate)
+ build_plates = [{"name": "Build Plate %d" % (i + 1), "buildPlateNumber": i} for i in range(self._max_build_plate + 1)]
+ self._multi_build_plate_model.setItems(build_plates)
+ if self._active_build_plate > self._max_build_plate:
+ build_plate_number = 0
+ if self._last_selected_index >= 0: # go to the buildplate of the item you last selected
+ item = self._objects_model.getItem(self._last_selected_index)
+ if "node" in item:
+ node = item["node"]
+ build_plate_number = node.callDecoration("getBuildPlateNumber")
+ self.setActiveBuildPlate(build_plate_number)
+ # self.buildPlateItemsChanged.emit() # TODO: necessary after setItems?
+
+ def _calcMaxBuildPlate(self):
+ max_build_plate = 0
+ for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
+ if node.callDecoration("isSliceable"):
+ build_plate_number = node.callDecoration("getBuildPlateNumber")
+ if build_plate_number is None:
+ build_plate_number = 0
+ max_build_plate = max(build_plate_number, max_build_plate)
+ return max_build_plate
+
+ ## Either select or deselect an item
+ @pyqtSlot(int)
+ def changeSelection(self, index):
+ modifiers = QApplication.keyboardModifiers()
+ ctrl_is_active = modifiers & Qt.ControlModifier
+ shift_is_active = modifiers & Qt.ShiftModifier
+
+ if ctrl_is_active:
+ item = self._objects_model.getItem(index)
+ node = item["node"]
+ if Selection.isSelected(node):
+ Selection.remove(node)
+ else:
+ Selection.add(node)
+ elif shift_is_active:
+ polarity = 1 if index + 1 > self._last_selected_index else -1
+ for i in range(self._last_selected_index, index + polarity, polarity):
+ item = self._objects_model.getItem(i)
+ node = item["node"]
+ Selection.add(node)
+ else:
+ # Single select
+ item = self._objects_model.getItem(index)
+ node = item["node"]
+ build_plate_number = node.callDecoration("getBuildPlateNumber")
+ if build_plate_number is not None and build_plate_number != -1:
+ self.setActiveBuildPlate(build_plate_number)
+ Selection.clear()
+ Selection.add(node)
+
+ self._last_selected_index = index
+
+ @pyqtSlot(int)
+ def setActiveBuildPlate(self, nr):
+ if nr == self._active_build_plate:
+ return
+ Logger.log("d", "Select build plate: %s" % nr)
+ self._active_build_plate = nr
+ Selection.clear()
+
+ self._multi_build_plate_model.setActiveBuildPlate(nr)
+ self._objects_model.setActiveBuildPlate(nr)
+ self.activeBuildPlateChanged.emit()
+
+ @staticmethod
+ def createCuraSceneController():
+ objects_model = Application.getInstance().getObjectsModel()
+ multi_build_plate_model = Application.getInstance().getMultiBuildPlateModel()
+ return CuraSceneController(objects_model = objects_model, multi_build_plate_model = multi_build_plate_model)
diff --git a/cura/Scene/CuraSceneNode.py b/cura/Scene/CuraSceneNode.py
new file mode 100644
index 0000000000..df346baaad
--- /dev/null
+++ b/cura/Scene/CuraSceneNode.py
@@ -0,0 +1,92 @@
+from typing import List
+
+from UM.Application import Application
+from UM.Scene.SceneNode import SceneNode
+from copy import deepcopy
+from cura.Settings.ExtrudersModel import ExtrudersModel
+
+
+## Scene nodes that are models are only seen when selecting the corresponding build plate
+# Note that many other nodes can just be UM SceneNode objects.
+class CuraSceneNode(SceneNode):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._outside_buildarea = False
+
+ def setOutsideBuildArea(self, new_value):
+ self._outside_buildarea = new_value
+
+ def isOutsideBuildArea(self):
+ return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
+
+ def isVisible(self):
+ return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+
+ def isSelectable(self) -> bool:
+ return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+
+ ## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
+ # TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
+ def getPrintingExtruder(self):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ per_mesh_stack = self.callDecoration("getStack")
+ extruders = list(global_container_stack.extruders.values())
+
+ # Use the support extruder instead of the active extruder if this is a support_mesh
+ if per_mesh_stack:
+ if per_mesh_stack.getProperty("support_mesh", "value"):
+ return extruders[int(global_container_stack.getProperty("support_extruder_nr", "value"))]
+
+ # It's only set if you explicitly choose an extruder
+ extruder_id = self.callDecoration("getActiveExtruder")
+
+ for extruder in extruders:
+ # Find out the extruder if we know the id.
+ if extruder_id is not None:
+ if extruder_id == extruder.getId():
+ return extruder
+ else: # If the id is unknown, then return the extruder in the position 0
+ try:
+ if extruder.getMetaDataEntry("position", default = "0") == "0": # Check if the position is zero
+ return extruder
+ except ValueError:
+ continue
+
+ # This point should never be reached
+ return None
+
+ ## Return the color of the material used to print this model
+ def getDiffuseColor(self) -> List[float]:
+ printing_extruder = self.getPrintingExtruder()
+
+ material_color = "#808080" # Fallback color
+ if printing_extruder is not None and printing_extruder.material:
+ material_color = printing_extruder.material.getMetaDataEntry("color_code", default = material_color)
+
+ # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
+ # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
+ return [
+ int(material_color[1:3], 16) / 255,
+ int(material_color[3:5], 16) / 255,
+ int(material_color[5:7], 16) / 255,
+ 1.0
+ ]
+
+ ## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
+ def __deepcopy__(self, memo):
+ copy = CuraSceneNode()
+ copy.setTransformation(self.getLocalTransformation())
+ copy.setMeshData(self._mesh_data)
+ copy.setVisible(deepcopy(self._visible, memo))
+ copy._selectable = deepcopy(self._selectable, memo)
+ copy._name = deepcopy(self._name, memo)
+ for decorator in self._decorators:
+ copy.addDecorator(deepcopy(decorator, memo))
+
+ for child in self._children:
+ copy.addChild(deepcopy(child, memo))
+ self.calculateBoundingBoxMesh()
+ return copy
+
+ def transformChanged(self) -> None:
+ self._transformChanged()
diff --git a/cura/GCodeListDecorator.py b/cura/Scene/GCodeListDecorator.py
similarity index 100%
rename from cura/GCodeListDecorator.py
rename to cura/Scene/GCodeListDecorator.py
diff --git a/cura/SliceableObjectDecorator.py b/cura/Scene/SliceableObjectDecorator.py
similarity index 100%
rename from cura/SliceableObjectDecorator.py
rename to cura/Scene/SliceableObjectDecorator.py
diff --git a/cura/ZOffsetDecorator.py b/cura/Scene/ZOffsetDecorator.py
similarity index 100%
rename from cura/ZOffsetDecorator.py
rename to cura/Scene/ZOffsetDecorator.py
diff --git a/cura/Scene/__init__.py b/cura/Scene/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py
index bbabdadbde..f910f0c0e2 100644
--- a/cura/Settings/ContainerManager.py
+++ b/cura/Settings/ContainerManager.py
@@ -1,16 +1,14 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-import copy
import os.path
-import urllib
+import urllib.parse
import uuid
-from typing import Any, Dict, List, Union
+from typing import Dict, Union
from PyQt5.QtCore import QObject, QUrl, QVariant
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox
-from UM.Util import parseBool
from UM.PluginRegistry import PluginRegistry
from UM.SaveFile import SaveFile
@@ -22,7 +20,6 @@ from UM.Application import Application
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
-from cura.QualityManager import QualityManager
from UM.MimeTypeDatabase import MimeTypeNotFoundError
from UM.Settings.ContainerRegistry import ContainerRegistry
@@ -30,9 +27,11 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from cura.Settings.ExtruderManager import ExtruderManager
+from cura.Settings.ExtruderStack import ExtruderStack
catalog = i18nCatalog("cura")
+
## Manager class that contains common actions to deal with containers in Cura.
#
# This is primarily intended as a class to be able to perform certain actions
@@ -42,154 +41,12 @@ class ContainerManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
+ self._application = Application.getInstance()
self._container_registry = ContainerRegistry.getInstance()
- self._machine_manager = Application.getInstance().getMachineManager()
+ self._machine_manager = self._application.getMachineManager()
+ self._material_manager = self._application.getMaterialManager()
self._container_name_filters = {}
- ## Create a duplicate of the specified container
- #
- # This will create and add a duplicate of the container corresponding
- # to the container ID.
- #
- # \param container_id \type{str} The ID of the container to duplicate.
- #
- # \return The ID of the new container, or an empty string if duplication failed.
- @pyqtSlot(str, result = str)
- def duplicateContainer(self, container_id):
- #TODO: It should be able to duplicate a container of which only the metadata is known.
- containers = self._container_registry.findContainers(id = container_id)
- if not containers:
- Logger.log("w", "Could duplicate container %s because it was not found.", container_id)
- return ""
-
- container = containers[0]
- new_container = self.duplicateContainerInstance(container)
- return new_container.getId()
-
- ## Create a duplicate of the given container instance
- #
- # This will create and add a duplicate of the container that was passed.
- #
- # \param container \type{ContainerInterface} The container to duplicate.
- #
- # \return The duplicated container, or None if duplication failed.
- def duplicateContainerInstance(self, container):
- new_container = None
- new_name = self._container_registry.uniqueName(container.getName())
- # Only InstanceContainer has a duplicate method at the moment.
- # So fall back to serialize/deserialize when no duplicate method exists.
- if hasattr(container, "duplicate"):
- new_container = container.duplicate(new_name)
- else:
- new_container = container.__class__(new_name)
- new_container.deserialize(container.serialize())
- new_container.setName(new_name)
-
- # TODO: we probably don't want to add it to the registry here!
- if new_container:
- self._container_registry.addContainer(new_container)
-
- return new_container
-
- ## Change the name of a specified container to a new name.
- #
- # \param container_id \type{str} The ID of the container to change the name of.
- # \param new_id \type{str} The new ID of the container.
- # \param new_name \type{str} The new name of the specified container.
- #
- # \return True if successful, False if not.
- @pyqtSlot(str, str, str, result = bool)
- def renameContainer(self, container_id, new_id, new_name):
- containers = self._container_registry.findContainers(id = container_id)
- if not containers:
- Logger.log("w", "Could rename container %s because it was not found.", container_id)
- return False
-
- container = containers[0]
- # First, remove the container from the registry. This will clean up any files related to the container.
- self._container_registry.removeContainer(container_id)
-
- # Ensure we have a unique name for the container
- new_name = self._container_registry.uniqueName(new_name)
-
- # Then, update the name and ID of the container
- container.setName(new_name)
- container._id = new_id # TODO: Find a nicer way to set a new, unique ID
-
- # Finally, re-add the container so it will be properly serialized again.
- self._container_registry.addContainer(container)
-
- return True
-
- ## Remove the specified container.
- #
- # \param container_id \type{str} The ID of the container to remove.
- #
- # \return True if the container was successfully removed, False if not.
- @pyqtSlot(str, result = bool)
- def removeContainer(self, container_id):
- containers = self._container_registry.findContainers(id = container_id)
- if not containers:
- Logger.log("w", "Could not remove container %s because it was not found.", container_id)
- return False
-
- self._container_registry.removeContainer(containers[0].getId())
-
- return True
-
- ## Merge a container with another.
- #
- # This will try to merge one container into the other, by going through the container
- # and setting the right properties on the other container.
- #
- # \param merge_into_id \type{str} The ID of the container to merge into.
- # \param merge_id \type{str} The ID of the container to merge.
- #
- # \return True if successfully merged, False if not.
- @pyqtSlot(str, result = bool)
- def mergeContainers(self, merge_into_id, merge_id):
- containers = self._container_registry.findContainers(id = merge_into_id)
- if not containers:
- Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id)
- return False
-
- merge_into = containers[0]
-
- containers = self._container_registry.findContainers(id = merge_id)
- if not containers:
- Logger.log("w", "Could not merge container %s because it was not found", merge_id)
- return False
-
- merge = containers[0]
-
- if not isinstance(merge, type(merge_into)):
- Logger.log("w", "Cannot merge two containers of different types")
- return False
-
- self._performMerge(merge_into, merge)
-
- return True
-
- ## Clear the contents of a container.
- #
- # \param container_id \type{str} The ID of the container to clear.
- #
- # \return True if successful, False if not.
- @pyqtSlot(str, result = bool)
- def clearContainer(self, container_id):
- if self._container_registry.isReadOnly(container_id):
- Logger.log("w", "Cannot clear read-only container %s", container_id)
- return False
-
- containers = self._container_registry.findContainers(id = container_id)
- if not containers:
- Logger.log("w", "Could clear container %s because it was not found.", container_id)
- return False
-
- containers[0].clear()
-
- return True
-
@pyqtSlot(str, str, result=str)
def getContainerMetaDataEntry(self, container_id, entry_name):
metadatas = self._container_registry.findContainersMetadata(id = container_id)
@@ -211,18 +68,15 @@ class ContainerManager(QObject):
# \param entry_value The new value of the entry.
#
# \return True if successful, False if not.
- @pyqtSlot(str, str, str, result = bool)
- def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
- if self._container_registry.isReadOnly(container_id):
- Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
+ # TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
+ @pyqtSlot("QVariant", str, str)
+ def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
+ root_material_id = container_node.metadata["base_file"]
+ if self._container_registry.isReadOnly(root_material_id):
+ Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
return False
- containers = self._container_registry.findContainers(id = container_id) #We need the complete container, since we need to know whether the container is read-only or not.
- if not containers:
- Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
- return False
-
- container = containers[0]
+ material_group = self._material_manager.getMaterialGroup(root_material_id)
entries = entry_name.split("/")
entry_name = entries.pop()
@@ -230,7 +84,7 @@ class ContainerManager(QObject):
sub_item_changed = False
if entries:
root_name = entries.pop(0)
- root = container.getMetaDataEntry(root_name)
+ root = material_group.root_material_node.metadata.get(root_name)
item = root
for _ in range(len(entries)):
@@ -243,12 +97,11 @@ class ContainerManager(QObject):
entry_name = root_name
entry_value = root
+ container = material_group.root_material_node.getContainer()
container.setMetaDataEntry(entry_name, entry_value)
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
container.metaDataChanged.emit(container)
- return True
-
## Set a setting property of the specified container.
#
# This will set the specified property of the specified setting of the container
@@ -306,60 +159,6 @@ class ContainerManager(QObject):
return container.getProperty(setting_key, property_name)
- ## Set the name of the specified container.
- @pyqtSlot(str, str, result = bool)
- def setContainerName(self, container_id, new_name):
- if self._container_registry.isReadOnly(container_id):
- Logger.log("w", "Cannot set name of read-only container %s.", container_id)
- return False
-
- containers = self._container_registry.findContainers(id = container_id) #We need to get the full container, not just metadata, since we need to know whether it's read-only.
- if not containers:
- Logger.log("w", "Could not set name of container %s because it was not found.", container_id)
- return False
-
- containers[0].setName(new_name)
-
- return True
-
- ## Find instance containers matching certain criteria.
- #
- # This effectively forwards to
- # ContainerRegistry::findInstanceContainersMetadata.
- #
- # \param criteria A dict of key - value pairs to search for.
- #
- # \return A list of container IDs that match the given criteria.
- @pyqtSlot("QVariantMap", result = "QVariantList")
- def findInstanceContainers(self, criteria):
- return [entry["id"] for entry in self._container_registry.findInstanceContainersMetadata(**criteria)]
-
- @pyqtSlot(str, result = bool)
- def isContainerUsed(self, container_id):
- Logger.log("d", "Checking if container %s is currently used", container_id)
- # check if this is a material container. If so, check if any material with the same base is being used by any
- # stacks.
- container_ids_to_check = [container_id]
- container_results = self._container_registry.findInstanceContainersMetadata(id = container_id, type = "material")
- if container_results:
- this_container = container_results[0]
- material_base_file = this_container["id"]
- if "base_file" in this_container:
- material_base_file = this_container["base_file"]
- # check all material container IDs with the same base
- material_containers = self._container_registry.findInstanceContainersMetadata(base_file = material_base_file,
- type = "material")
- if material_containers:
- container_ids_to_check = [container["id"] for container in material_containers]
-
- all_stacks = self._container_registry.findContainerStacks()
- for stack in all_stacks:
- for used_container_id in container_ids_to_check:
- if used_container_id in [child.getId() for child in stack.getContainers()]:
- Logger.log("d", "The container is in use by %s", stack.getId())
- return True
- return False
-
@pyqtSlot(str, result = str)
def makeUniqueName(self, original_name):
return self._container_registry.uniqueName(original_name)
@@ -399,7 +198,7 @@ class ContainerManager(QObject):
@pyqtSlot(str, str, QUrl, result = "QVariantMap")
def exportContainer(self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
if not container_id or not file_type or not file_url_or_string:
- return { "status": "error", "message": "Invalid arguments"}
+ return {"status": "error", "message": "Invalid arguments"}
if isinstance(file_url_or_string, QUrl):
file_url = file_url_or_string.toLocalFile()
@@ -407,20 +206,20 @@ class ContainerManager(QObject):
file_url = file_url_or_string
if not file_url:
- return { "status": "error", "message": "Invalid path"}
+ return {"status": "error", "message": "Invalid path"}
mime_type = None
- if not file_type in self._container_name_filters:
+ if file_type not in self._container_name_filters:
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
except MimeTypeNotFoundError:
- return { "status": "error", "message": "Unknown File Type" }
+ return {"status": "error", "message": "Unknown File Type"}
else:
mime_type = self._container_name_filters[file_type]["mime"]
containers = self._container_registry.findContainers(id = container_id)
if not containers:
- return { "status": "error", "message": "Container not found"}
+ return {"status": "error", "message": "Container not found"}
container = containers[0]
if Platform.isOSX() and "." in file_url:
@@ -437,12 +236,12 @@ class ContainerManager(QObject):
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
catalog.i18nc("@label Don't translate the XML tag !", "The file {0} already exists. Are you sure you want to overwrite it?").format(file_url))
if result == QMessageBox.No:
- return { "status": "cancelled", "message": "User cancelled"}
+ return {"status": "cancelled", "message": "User cancelled"}
try:
contents = container.serialize()
except NotImplementedError:
- return { "status": "error", "message": "Unable to serialize container"}
+ return {"status": "error", "message": "Unable to serialize container"}
if contents is None:
return {"status": "error", "message": "Serialization returned None. Unable to write to file"}
@@ -450,7 +249,7 @@ class ContainerManager(QObject):
with SaveFile(file_url, "w") as f:
f.write(contents)
- return { "status": "success", "message": "Succesfully exported container", "path": file_url}
+ return {"status": "success", "message": "Successfully exported container", "path": file_url}
## Imports a profile from a file
#
@@ -459,9 +258,9 @@ class ContainerManager(QObject):
# \return \type{Dict} dict with a 'status' key containing the string 'success' or 'error', and a 'message' key
# containing a message for the user
@pyqtSlot(QUrl, result = "QVariantMap")
- def importContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
+ def importMaterialContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
if not file_url_or_string:
- return { "status": "error", "message": "Invalid path"}
+ return {"status": "error", "message": "Invalid path"}
if isinstance(file_url_or_string, QUrl):
file_url = file_url_or_string.toLocalFile()
@@ -469,16 +268,16 @@ class ContainerManager(QObject):
file_url = file_url_or_string
if not file_url or not os.path.exists(file_url):
- return { "status": "error", "message": "Invalid path" }
+ return {"status": "error", "message": "Invalid path"}
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
except MimeTypeNotFoundError:
- return { "status": "error", "message": "Could not determine mime type of file" }
+ return {"status": "error", "message": "Could not determine mime type of file"}
container_type = self._container_registry.getContainerForMimeType(mime_type)
if not container_type:
- return { "status": "error", "message": "Could not find a container to handle the specified file."}
+ return {"status": "error", "message": "Could not find a container to handle the specified file."}
container_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_url)))
container_id = self._container_registry.uniqueName(container_id)
@@ -486,16 +285,18 @@ class ContainerManager(QObject):
container = container_type(container_id)
try:
- with open(file_url, "rt") as f:
+ with open(file_url, "rt", encoding = "utf-8") as f:
container.deserialize(f.read())
except PermissionError:
- return { "status": "error", "message": "Permission denied when trying to read the file"}
+ return {"status": "error", "message": "Permission denied when trying to read the file"}
+ except Exception as ex:
+ return {"status": "error", "message": str(ex)}
- container.setName(container_id)
+ container.setDirty(True)
self._container_registry.addContainer(container)
- return { "status": "success", "message": "Successfully imported container {0}".format(container.getName()) }
+ return {"status": "success", "message": "Successfully imported container {0}".format(container.getName())}
## Update the current active quality changes container with the settings from the user container.
#
@@ -514,13 +315,13 @@ class ContainerManager(QObject):
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
# Find the quality_changes container for this stack and merge the contents of the top container into it.
quality_changes = stack.qualityChanges
- if not quality_changes or quality_changes.isReadOnly():
+ if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
continue
self._performMerge(quality_changes, stack.getTop())
- self._machine_manager.activeQualityChanged.emit()
+ self._machine_manager.activeQualityChangesGroupChanged.emit()
return True
@@ -540,391 +341,37 @@ class ContainerManager(QObject):
for container in send_emits_containers:
container.sendPostponedEmits()
- ## Create quality changes containers from the user containers in the active stacks.
- #
- # This will go through the global and extruder stacks and create quality_changes containers from
- # the user containers in each stack. These then replace the quality_changes containers in the
- # stack and clear the user settings.
- #
- # \return \type{bool} True if the operation was successfully, False if not.
- @pyqtSlot(str, result = bool)
- def createQualityChanges(self, base_name):
- global_stack = Application.getInstance().getGlobalContainerStack()
- if not global_stack:
- return False
-
- active_quality_name = self._machine_manager.activeQualityName
- if active_quality_name == "":
- Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
- return False
-
- self._machine_manager.blurSettings.emit()
- if base_name is None or base_name == "":
- base_name = active_quality_name
- unique_name = self._container_registry.uniqueName(base_name)
-
- # Go through the active stacks and create quality_changes containers from the user containers.
- for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
- user_container = stack.getTop()
- quality_container = stack.quality
- quality_changes_container = stack.qualityChanges
- if not quality_container or not quality_changes_container:
- Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
- continue
-
- extruder_id = None if stack is global_stack else QualityManager.getInstance().getParentMachineDefinition(stack.getBottom()).getId()
- new_changes = self._createQualityChanges(quality_container, unique_name,
- Application.getInstance().getGlobalContainerStack().getBottom(),
- extruder_id)
- self._performMerge(new_changes, quality_changes_container, clear_settings = False)
- self._performMerge(new_changes, user_container)
-
- self._container_registry.addContainer(new_changes)
- stack.replaceContainer(stack.getContainerIndex(quality_changes_container), new_changes)
-
- self._machine_manager.activeQualityChanged.emit()
- return True
-
- ## Remove all quality changes containers matching a specified name.
- #
- # This will search for quality_changes containers matching the supplied name and remove them.
- # Note that if the machine specifies that qualities should be filtered by machine and/or material
- # only the containers related to the active machine/material are removed.
- #
- # \param quality_name The name of the quality changes to remove.
- #
- # \return \type{bool} True if successful, False if not.
- @pyqtSlot(str, result = bool)
- def removeQualityChanges(self, quality_name):
- Logger.log("d", "Attempting to remove the quality change containers with name %s", quality_name)
- containers_found = False
-
- if not quality_name:
- return containers_found # Without a name we will never find a container to remove.
-
- # If the container that is being removed is the currently active quality, set another quality as the active quality
- activate_quality = quality_name == self._machine_manager.activeQualityName
- activate_quality_type = None
-
- global_stack = Application.getInstance().getGlobalContainerStack()
- if not global_stack or not quality_name:
- return ""
- machine_definition = QualityManager.getInstance().getParentMachineDefinition(global_stack.getBottom())
-
- for container in QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition):
- containers_found = True
- if activate_quality and not activate_quality_type:
- activate_quality_type = container.getMetaDataEntry("quality")
- self._container_registry.removeContainer(container.getId())
-
- if not containers_found:
- Logger.log("d", "Unable to remove quality containers, as we did not find any by the name of %s", quality_name)
-
- elif activate_quality:
- definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId
- containers = self._container_registry.findInstanceContainersMetadata(type = "quality", definition = definition_id, quality_type = activate_quality_type)
- if containers:
- self._machine_manager.setActiveQuality(containers[0]["id"])
- self._machine_manager.activeQualityChanged.emit()
-
- return containers_found
-
- ## Rename a set of quality changes containers.
- #
- # This will search for quality_changes containers matching the supplied name and rename them.
- # Note that if the machine specifies that qualities should be filtered by machine and/or material
- # only the containers related to the active machine/material are renamed.
- #
- # \param quality_name The name of the quality changes containers to rename.
- # \param new_name The new name of the quality changes.
- #
- # \return True if successful, False if not.
- @pyqtSlot(str, str, result = bool)
- def renameQualityChanges(self, quality_name, new_name):
- Logger.log("d", "User requested QualityChanges container rename of %s to %s", quality_name, new_name)
- if not quality_name or not new_name:
- return False
-
- if quality_name == new_name:
- Logger.log("w", "Unable to rename %s to %s, because they are the same.", quality_name, new_name)
- return True
-
- global_stack = Application.getInstance().getGlobalContainerStack()
- if not global_stack:
- return False
-
- self._machine_manager.blurSettings.emit()
-
- new_name = self._container_registry.uniqueName(new_name)
-
- container_registry = self._container_registry
-
- containers_to_rename = self._container_registry.findInstanceContainersMetadata(type = "quality_changes", name = quality_name)
-
- for container in containers_to_rename:
- stack_id = global_stack.getId()
- if "extruder" in container:
- stack_id = container["extruder"]
- container_registry.renameContainer(container["id"], new_name, self._createUniqueId(stack_id, new_name))
-
- if not containers_to_rename:
- Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name)
-
- self._machine_manager.activeQualityChanged.emit()
- return True
-
- ## Duplicate a specified set of quality or quality_changes containers.
- #
- # This will search for containers matching the specified name. If the container is a "quality" type container, a new
- # quality_changes container will be created with the specified quality as base. If the container is a "quality_changes"
- # container, it is simply duplicated and renamed.
- #
- # \param quality_name The name of the quality to duplicate.
- #
- # \return A string containing the name of the duplicated containers, or an empty string if it failed.
- @pyqtSlot(str, str, result = str)
- def duplicateQualityOrQualityChanges(self, quality_name, base_name):
- global_stack = Application.getInstance().getGlobalContainerStack()
- if not global_stack or not quality_name:
- return ""
- machine_definition = global_stack.getBottom()
-
- active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
- if active_stacks is None:
- return ""
- material_metadatas = [stack.material.getMetaData() for stack in active_stacks]
-
- result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
- QualityManager.getInstance().getParentMachineDefinition(machine_definition),
- material_metadatas)
- return result[0].getName() if result else ""
-
- ## Duplicate a quality or quality changes profile specific to a machine type
- #
- # \param quality_name The name of the quality or quality changes container to duplicate.
- # \param base_name The desired name for the new container.
- # \param machine_definition The machine with the specific machine type.
- # \param material_metadatas Metadata of materials
- # \return List of duplicated quality profiles.
- def _duplicateQualityOrQualityChangesForMachineType(self, quality_name: str, base_name: str, machine_definition: DefinitionContainer, material_metadatas: List[Dict[str, Any]]) -> List[InstanceContainer]:
- Logger.log("d", "Attempting to duplicate the quality %s", quality_name)
-
- if base_name is None:
- base_name = quality_name
- # Try to find a Quality with the name.
- container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_metadatas)
- if container:
- Logger.log("d", "We found a quality to duplicate.")
- return self._duplicateQualityForMachineType(container, base_name, machine_definition)
- Logger.log("d", "We found a quality_changes to duplicate.")
- # Assume it is a quality changes.
- return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition)
-
- # Duplicate a quality profile
- def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition) -> List[InstanceContainer]:
- if base_name is None:
- base_name = quality_container.getName()
- new_name = self._container_registry.uniqueName(base_name)
-
- new_change_instances = []
-
- # Handle the global stack first.
- global_changes = self._createQualityChanges(quality_container, new_name, machine_definition, None)
- new_change_instances.append(global_changes)
- self._container_registry.addContainer(global_changes)
-
- # Handle the extruders if present.
- extruders = machine_definition.getMetaDataEntry("machine_extruder_trains")
- if extruders:
- for extruder_id in extruders:
- extruder = extruders[extruder_id]
- new_changes = self._createQualityChanges(quality_container, new_name, machine_definition, extruder)
- new_change_instances.append(new_changes)
- self._container_registry.addContainer(new_changes)
-
- return new_change_instances
-
- # Duplicate a quality changes container
- def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition) -> List[InstanceContainer]:
- new_change_instances = []
- for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name,
- machine_definition):
- base_id = container.getMetaDataEntry("extruder")
- if not base_id:
- base_id = container.getDefinition().getId()
- new_unique_id = self._createUniqueId(base_id, base_name)
- new_container = container.duplicate(new_unique_id, base_name)
- new_change_instances.append(new_container)
- self._container_registry.addContainer(new_container)
-
- return new_change_instances
-
- ## Create a duplicate of a material, which has the same GUID and base_file metadata
- #
- # \return \type{str} the id of the newly created container.
- @pyqtSlot(str, result = str)
- def duplicateMaterial(self, material_id: str) -> str:
- original = self._container_registry.findContainersMetadata(id = material_id)
- if not original:
- Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
- return ""
- original = original[0]
-
- base_container_id = original.get("base_file")
- base_container = self._container_registry.findContainers(id = base_container_id)
- if not base_container:
- Logger.log("d", "Unable to duplicate the material with id {material_id}, because base_file {base_container_id} doesn't exist.".format(material_id = material_id, base_container_id = base_container_id))
- return ""
- base_container = base_container[0]
-
- #We'll copy all containers with the same base.
- #This way the correct variant and machine still gets assigned when loading the copy of the material.
- containers_to_copy = self._container_registry.findInstanceContainers(base_file = base_container_id)
-
- # Ensure all settings are saved.
- Application.getInstance().saveSettings()
-
- # Create a new ID & container to hold the data.
- new_containers = []
- new_base_id = self._container_registry.uniqueName(base_container.getId())
- new_base_container = copy.deepcopy(base_container)
- new_base_container.getMetaData()["id"] = new_base_id
- new_base_container.getMetaData()["base_file"] = new_base_id
- new_containers.append(new_base_container)
-
- #Clone all of them.
- clone_of_original = None #Keeping track of which one is the clone of the original material, since we need to return that.
- for container_to_copy in containers_to_copy:
- #Create unique IDs for every clone.
- current_id = container_to_copy.getId()
- new_id = new_base_id
- if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
- new_id += "_" + container_to_copy.getMetaDataEntry("definition")
- if container_to_copy.getMetaDataEntry("variant"):
- variant = self._container_registry.findContainers(id = container_to_copy.getMetaDataEntry("variant"))[0]
- new_id += "_" + variant.getName().replace(" ", "_")
- if current_id == material_id:
- clone_of_original = new_id
-
- new_container = copy.deepcopy(container_to_copy)
- new_container.getMetaData()["id"] = new_id
- new_container.getMetaData()["base_file"] = new_base_id
- new_containers.append(new_container)
-
- for container_to_add in new_containers:
- container_to_add.setDirty(True)
- ContainerRegistry.getInstance().addContainer(container_to_add)
- return self._getMaterialContainerIdForActiveMachine(clone_of_original)
-
- ## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
- #
- # \return \type{str} the id of the newly created container.
- @pyqtSlot(result = str)
- def createMaterial(self) -> str:
- # Ensure all settings are saved.
- Application.getInstance().saveSettings()
-
- global_stack = Application.getInstance().getGlobalContainerStack()
- if not global_stack:
- return ""
-
- approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
- containers = self._container_registry.findInstanceContainersMetadata(id = "generic_pla*", approximate_diameter = approximate_diameter)
- if not containers:
- Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
- return ""
-
- base_file = containers[0].get("base_file")
- containers = self._container_registry.findInstanceContainers(id = base_file)
- if not containers:
- Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.")
- return ""
-
- # Create a new ID & container to hold the data.
- new_id = self._container_registry.uniqueName("custom_material")
- container_type = type(containers[0]) # Always XMLMaterialProfile, since we specifically clone the base_file
- duplicated_container = container_type(new_id)
-
- # Instead of duplicating we load the data from the basefile again.
- # This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
- # are also correctly created.
- with open(containers[0].getPath(), encoding="utf-8") as f:
- duplicated_container.deserialize(f.read())
-
- duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4()))
- duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom"))
- # We're defaulting to PLA, as machines with material profiles don't like material types they don't know.
- # TODO: This is a hack, the only reason this is in now is to bandaid the problem as we're close to a release!
- duplicated_container.setMetaDataEntry("material", "PLA")
- duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
-
- self._container_registry.addContainer(duplicated_container)
- return self._getMaterialContainerIdForActiveMachine(new_id)
-
- ## Find the id of a material container based on the new material
- # Utilty function that is shared between duplicateMaterial and createMaterial
- #
- # \param base_file \type{str} the id of the created container.
- def _getMaterialContainerIdForActiveMachine(self, base_file):
- global_stack = Application.getInstance().getGlobalContainerStack()
- if not global_stack:
- return base_file
-
- has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False))
- has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False))
- has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
- if has_machine_materials or has_variant_materials:
- if has_variants:
- materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
- else:
- materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
-
- if materials:
- return materials[0]["id"]
-
- Logger.log("w", "Unable to find a suitable container based on %s for the current machine.", base_file)
- return "" # do not activate a new material if a container can not be found
-
- return base_file
-
## Get a list of materials that have the same GUID as the reference material
#
# \param material_id \type{str} the id of the material for which to get the linked materials.
# \return \type{list} a list of names of materials with the same GUID
- @pyqtSlot(str, result = "QStringList")
- def getLinkedMaterials(self, material_id: str):
- containers = self._container_registry.findInstanceContainersMetadata(id = material_id)
- if not containers:
- Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
- return []
+ @pyqtSlot("QVariant", result = "QStringList")
+ def getLinkedMaterials(self, material_node):
+ guid = material_node.metadata["GUID"]
- material_container = containers[0]
- material_base_file = material_container.get("base_file", "")
- material_guid = material_container.get("GUID", "")
- if not material_guid:
- Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
- return []
+ material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
- containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid)
linked_material_names = []
- for container in containers:
- if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]:
- continue
-
- linked_material_names.append(container["name"])
+ if material_group_list:
+ for material_group in material_group_list:
+ linked_material_names.append(material_group.root_material_node.metadata["name"])
return linked_material_names
## Unlink a material from all other materials by creating a new GUID
# \param material_id \type{str} the id of the material to create a new GUID for.
- @pyqtSlot(str)
- def unlinkMaterial(self, material_id: str):
- containers = self._container_registry.findInstanceContainers(id=material_id)
- if not containers:
- Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
- return ""
+ @pyqtSlot("QVariant")
+ def unlinkMaterial(self, material_node):
+ # Get the material group
+ material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
- containers[0].setMetaDataEntry("GUID", str(uuid.uuid4()))
+ # Generate a new GUID
+ new_guid = str(uuid.uuid4())
+ # Update the GUID
+ # NOTE: We only need to set the root material container because XmlMaterialProfile.setMetaDataEntry() will
+ # take care of the derived containers too
+ container = material_group.root_material_node.getContainer()
+ container.setMetaDataEntry("GUID", new_guid)
## Get the singleton instance for this class.
@classmethod
@@ -997,81 +444,6 @@ class ContainerManager(QObject):
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
self._container_name_filters[name_filter] = entry
- ## Creates a unique ID for a container by prefixing the name with the stack ID.
- #
- # This method creates a unique ID for a container by prefixing it with a specified stack ID.
- # This is done to ensure we have an easily identified ID for quality changes, which have the
- # same name across several stacks.
- #
- # \param stack_id The ID of the stack to prepend.
- # \param container_name The name of the container that we are creating a unique ID for.
- #
- # \return Container name prefixed with stack ID, in lower case with spaces replaced by underscores.
- def _createUniqueId(self, stack_id, container_name):
- result = stack_id + "_" + container_name
- result = result.lower()
- result.replace(" ", "_")
- return result
-
- ## Create a quality changes container for a specified quality container.
- #
- # \param quality_container The quality container to create a changes container for.
- # \param new_name The name of the new quality_changes container.
- # \param machine_definition The machine definition this quality changes container is specific to.
- # \param extruder_id
- #
- # \return A new quality_changes container with the specified container as base.
- def _createQualityChanges(self, quality_container, new_name, machine_definition, extruder_id):
- base_id = machine_definition.getId() if extruder_id is None else extruder_id
-
- # Create a new quality_changes container for the quality.
- quality_changes = InstanceContainer(self._createUniqueId(base_id, new_name))
- quality_changes.setName(new_name)
- quality_changes.addMetaDataEntry("type", "quality_changes")
- quality_changes.addMetaDataEntry("quality_type", quality_container.getMetaDataEntry("quality_type"))
-
- # If we are creating a container for an extruder, ensure we add that to the container
- if extruder_id is not None:
- quality_changes.addMetaDataEntry("extruder", extruder_id)
-
- # If the machine specifies qualities should be filtered, ensure we match the current criteria.
- if not machine_definition.getMetaDataEntry("has_machine_quality"):
- quality_changes.setDefinition("fdmprinter")
- else:
- quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition).getId())
-
- from cura.CuraApplication import CuraApplication
- quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
- return quality_changes
-
-
- ## Import profiles from a list of file_urls.
- # Each QUrl item must end with .curaprofile, or it will not be imported.
- #
- # \param QVariant, essentially a list with QUrl objects.
- # \return Dict with keys status, text
- @pyqtSlot("QVariantList", result="QVariantMap")
- def importProfiles(self, file_urls):
- status = "ok"
- results = {"ok": [], "error": []}
- for file_url in file_urls:
- if not file_url.isValid():
- continue
- path = file_url.toLocalFile()
- if not path:
- continue
- if not path.endswith(".curaprofile"):
- continue
-
- single_result = self._container_registry.importProfile(path)
- if single_result["status"] == "error":
- status = "error"
- results[single_result["status"]].append(single_result["message"])
-
- return {
- "status": status,
- "message": "\n".join(results["ok"] + results["error"])}
-
## Import single profile, file_url does not have to end with curaprofile
@pyqtSlot(QUrl, result="QVariantMap")
def importProfile(self, file_url):
@@ -1082,11 +454,13 @@ class ContainerManager(QObject):
return
return self._container_registry.importProfile(path)
- @pyqtSlot("QVariantList", QUrl, str)
- def exportProfile(self, instance_id: str, file_url: QUrl, file_type: str) -> None:
+ @pyqtSlot(QObject, QUrl, str)
+ def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
if not file_url.isValid():
return
path = file_url.toLocalFile()
if not path:
return
- self._container_registry.exportProfile(instance_id, path, file_type)
+
+ container_list = [n.getContainer() for n in quality_changes_group.getAllNodes()]
+ self._container_registry.exportQualityProfile(container_list, path, file_type)
diff --git a/cura/Settings/ContainerSettingsModel.py b/cura/Settings/ContainerSettingsModel.py
deleted file mode 100644
index 2c4bef6464..0000000000
--- a/cura/Settings/ContainerSettingsModel.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# Copyright (c) 2016 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from UM.Application import Application
-from UM.Qt.ListModel import ListModel
-
-from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot, QUrl
-
-from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.InstanceContainer import InstanceContainer
-from UM.Settings.SettingFunction import SettingFunction
-
-class ContainerSettingsModel(ListModel):
- LabelRole = Qt.UserRole + 1
- CategoryRole = Qt.UserRole + 2
- UnitRole = Qt.UserRole + 3
- ValuesRole = Qt.UserRole + 4
-
- def __init__(self, parent = None):
- super().__init__(parent)
- self.addRoleName(self.LabelRole, "label")
- self.addRoleName(self.CategoryRole, "category")
- self.addRoleName(self.UnitRole, "unit")
- self.addRoleName(self.ValuesRole, "values")
-
- self._container_ids = []
- self._containers = []
-
- def _onPropertyChanged(self, key, property_name):
- if property_name == "value":
- self._update()
-
- def _update(self):
- items = []
-
- if len(self._container_ids) == 0:
- return
-
- keys = []
- for container in self._containers:
- keys = keys + list(container.getAllKeys())
-
- keys = list(set(keys)) # remove duplicate keys
-
- for key in keys:
- definition = None
- category = None
- values = []
- for container in self._containers:
- instance = container.getInstance(key)
- if instance:
- definition = instance.definition
-
- # Traverse up to find the category
- category = definition
- while category.type != "category":
- category = category.parent
-
- value = container.getProperty(key, "value")
- if type(value) == SettingFunction:
- values.append("=\u0192")
- else:
- values.append(container.getProperty(key, "value"))
- else:
- values.append("")
-
- items.append({
- "key": key,
- "values": values,
- "label": definition.label,
- "unit": definition.unit,
- "category": category.label
- })
- items.sort(key = lambda k: (k["category"], k["key"]))
- self.setItems(items)
-
- ## Set the ids of the containers which have the settings this model should list.
- # Also makes sure the model updates when the containers have property changes
- def setContainers(self, container_ids):
- for container in self._containers:
- container.propertyChanged.disconnect(self._onPropertyChanged)
-
- self._container_ids = container_ids
- self._containers = []
-
- for container_id in self._container_ids:
- containers = ContainerRegistry.getInstance().findContainers(id = container_id)
- if containers:
- containers[0].propertyChanged.connect(self._onPropertyChanged)
- self._containers.append(containers[0])
-
- self._update()
-
- containersChanged = pyqtSignal()
- @pyqtProperty("QVariantList", fset = setContainers, notify = containersChanged)
- def containers(self):
- return self.container_ids
diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py
index 43e2e072b0..e79cfa5335 100644
--- a/cura/Settings/CuraContainerRegistry.py
+++ b/cura/Settings/CuraContainerRegistry.py
@@ -14,6 +14,7 @@ from UM.Decorators import override
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Settings.SettingInstance import SettingInstance
from UM.Application import Application
from UM.Logger import Logger
from UM.Message import Message
@@ -24,14 +25,15 @@ from UM.Resources import Resources
from . import ExtruderStack
from . import GlobalStack
-from .ContainerManager import ContainerManager
from .ExtruderManager import ExtruderManager
from cura.CuraApplication import CuraApplication
+from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
+
class CuraContainerRegistry(ContainerRegistry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -93,7 +95,7 @@ class CuraContainerRegistry(ContainerRegistry):
def _containerExists(self, container_type, container_name):
container_class = ContainerStack if container_type == "machine" else InstanceContainer
- return self.findContainersMetadata(id = container_name, type = container_type, ignore_case = True) or \
+ return self.findContainersMetadata(container_type = container_class, id = container_name, type = container_type, ignore_case = True) or \
self.findContainersMetadata(container_type = container_class, name = container_name, type = container_type)
## Exports an profile to a file
@@ -101,7 +103,7 @@ class CuraContainerRegistry(ContainerRegistry):
# \param instance_ids \type{list} the IDs of the profiles to export.
# \param file_name \type{str} the full path and filename to export to.
# \param file_type \type{str} the file type with the format " (*.)"
- def exportProfile(self, instance_ids, file_name, file_type):
+ def exportQualityProfile(self, container_list, file_name, file_type):
# Parse the fileType to deduce what plugin can save the file format.
# fileType has the format " (*.)"
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
@@ -120,31 +122,10 @@ class CuraContainerRegistry(ContainerRegistry):
catalog.i18nc("@label Don't translate the XML tag !", "The file {0} already exists. Are you sure you want to overwrite it?").format(file_name))
if result == QMessageBox.No:
return
- found_containers = []
- extruder_positions = []
- for instance_id in instance_ids:
- containers = ContainerRegistry.getInstance().findInstanceContainers(id = instance_id)
- if containers:
- found_containers.append(containers[0])
-
- # Determine the position of the extruder of this container
- extruder_id = containers[0].getMetaDataEntry("extruder", "")
- if extruder_id == "":
- # Global stack
- extruder_positions.append(-1)
- else:
- extruder_containers = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = extruder_id)
- if extruder_containers:
- extruder_positions.append(int(extruder_containers[0].get("position", 0)))
- else:
- extruder_positions.append(0)
- # Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...)
- found_containers = [containers for (positions, containers) in sorted(zip(extruder_positions, found_containers))]
profile_writer = self._findProfileWriter(extension, description)
-
try:
- success = profile_writer.write(file_name, found_containers)
+ success = profile_writer.write(file_name, container_list)
except Exception as e:
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
m = Message(catalog.i18nc("@info:status Don't translate the XML tags or !", "Failed to export profile to {0}: {1}", file_name, str(e)),
@@ -201,16 +182,43 @@ class CuraContainerRegistry(ContainerRegistry):
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
if meta_data["profile_reader"][0]["extension"] != extension:
continue
-
profile_reader = plugin_registry.getPluginObject(plugin_id)
try:
profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader.
except Exception as e:
# Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name,profile_reader.getPluginId(), str(e))
- return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "Failed to import profile from {0}: {1}", file_name, str(e))}
+ return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "Failed to import profile from {0}: {1}", file_name, "\n" + str(e))}
if profile_or_list:
+ # Ensure it is always a list of profiles
+ if not isinstance(profile_or_list, list):
+ profile_or_list = [profile_or_list]
+
+ # First check if this profile is suitable for this machine
+ global_profile = None
+ if len(profile_or_list) == 1:
+ global_profile = profile_or_list[0]
+ else:
+ for profile in profile_or_list:
+ if not profile.getMetaDataEntry("extruder"):
+ global_profile = profile
+ break
+ if not global_profile:
+ Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name)
+ return { "status": "error",
+ "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "This profile {0} contains incorrect data, could not import it.", file_name)}
+ profile_definition = global_profile.getMetaDataEntry("definition")
+ expected_machine_definition = "fdmprinter"
+ if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")):
+ expected_machine_definition = global_container_stack.getMetaDataEntry("quality_definition")
+ if not expected_machine_definition:
+ expected_machine_definition = global_container_stack.definition.getId()
+ if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition:
+ Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition)
+ return { "status": "error",
+ "message": catalog.i18nc("@info:status Don't translate the XML tags or !", "The machine defined in profile {0} ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)}
+
name_seed = os.path.splitext(os.path.basename(file_name))[0]
new_name = self.uniqueName(name_seed)
@@ -218,24 +226,40 @@ class CuraContainerRegistry(ContainerRegistry):
if type(profile_or_list) is not list:
profile_or_list = [profile_or_list]
+ # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack
if len(profile_or_list) == 1:
- # If there is only 1 stack file it means we're loading a legacy (pre-3.1) .curaprofile.
- # In that case we find the per-extruder settings and put those in a new quality_changes container
- # so that it is compatible with the new stack setup.
- profile = profile_or_list[0]
- extruder_stack_quality_changes_container = ContainerManager.getInstance().duplicateContainerInstance(profile)
- extruder_stack_quality_changes_container.addMetaDataEntry("extruder", "fdmextruder")
+ global_profile = profile_or_list[0]
+ extruder_profiles = []
+ for idx, extruder in enumerate(global_container_stack.extruders.values()):
+ profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1))
+ profile = InstanceContainer(profile_id)
+ profile.setName(global_profile.getName())
+ profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
+ profile.addMetaDataEntry("type", "quality_changes")
+ profile.addMetaDataEntry("definition", global_profile.getMetaDataEntry("definition"))
+ profile.addMetaDataEntry("quality_type", global_profile.getMetaDataEntry("quality_type"))
+ profile.addMetaDataEntry("extruder", extruder.getId())
+ profile.setDirty(True)
+ if idx == 0:
+ # move all per-extruder settings to the first extruder's quality_changes
+ for qc_setting_key in global_profile.getAllKeys():
+ settable_per_extruder = global_container_stack.getProperty(qc_setting_key,
+ "settable_per_extruder")
+ if settable_per_extruder:
+ setting_value = global_profile.getProperty(qc_setting_key, "value")
- for quality_changes_setting_key in extruder_stack_quality_changes_container.getAllKeys():
- settable_per_extruder = extruder_stack_quality_changes_container.getProperty(quality_changes_setting_key, "settable_per_extruder")
- if settable_per_extruder:
- profile.removeInstance(quality_changes_setting_key, postpone_emit = True)
- else:
- extruder_stack_quality_changes_container.removeInstance(quality_changes_setting_key, postpone_emit = True)
+ setting_definition = global_container_stack.getSettingDefinition(qc_setting_key)
+ new_instance = SettingInstance(setting_definition, profile)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ profile.addInstance(new_instance)
+ profile.setDirty(True)
- # We add the new container to the profile list so things like extruder positions are taken care of
- # in the next code segment.
- profile_or_list.append(extruder_stack_quality_changes_container)
+ global_profile.removeInstance(qc_setting_key, postpone_emit=True)
+ extruder_profiles.append(profile)
+
+ for profile in extruder_profiles:
+ profile_or_list.append(profile)
# Import all profiles
for profile_index, profile in enumerate(profile_or_list):
@@ -243,15 +267,18 @@ class CuraContainerRegistry(ContainerRegistry):
# This is assumed to be the global profile
profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
- elif len(machine_extruders) > profile_index:
+ elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile
- extruder_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_extruders[profile_index - 1].getBottom())
+ extruder_id = machine_extruders[profile_index - 1].definition.getId()
if not profile.getMetaDataEntry("extruder"):
profile.addMetaDataEntry("extruder", extruder_id)
else:
profile.setMetaDataEntry("extruder", extruder_id)
profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_")
+ else: #More extruders in the imported file than in the machine.
+ continue #Delete the additional profiles.
+
result = self._configureProfile(profile, profile_id, new_name)
if result is not None:
return {"status": "error", "message": catalog.i18nc(
@@ -261,6 +288,9 @@ class CuraContainerRegistry(ContainerRegistry):
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())}
+ # This message is throw when the profile reader doesn't find any profile in the file
+ return {"status": "error", "message": catalog.i18nc("@info:status", "File {0} does not contain any valid profile.", file_name)}
+
# If it hasn't returned by now, none of the plugins loaded the profile successfully.
return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
@@ -281,9 +311,13 @@ class CuraContainerRegistry(ContainerRegistry):
profile.setDirty(True) # Ensure the profiles are correctly saved
new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
- profile._id = new_id
+ profile.setMetaDataEntry("id", new_id)
profile.setName(new_name)
+ # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
+ # It also solves an issue with importing profiles from G-Codes
+ profile.setMetaDataEntry("id", new_id)
+
if "type" in profile.getMetaData():
profile.setMetaDataEntry("type", "quality_changes")
else:
@@ -294,39 +328,16 @@ class CuraContainerRegistry(ContainerRegistry):
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
quality_type_criteria = {"quality_type": quality_type}
- if self._machineHasOwnQualities():
- profile.setDefinition(self._activeQualityDefinition().getId())
- if self._machineHasOwnMaterials():
- active_material_id = self._activeMaterialId()
- if active_material_id and active_material_id != "empty": # only update if there is an active material
- profile.addMetaDataEntry("material", active_material_id)
- quality_type_criteria["material"] = active_material_id
-
- quality_type_criteria["definition"] = profile.getDefinition().getId()
-
- else:
- profile.setDefinition(fdmprinter)
- quality_type_criteria["definition"] = "fdmprinter"
-
- machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
- del quality_type_criteria["definition"]
-
- # materials = None
-
- if "material" in quality_type_criteria:
- # materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"])
- del quality_type_criteria["material"]
-
- # Do not filter quality containers here with materials because we are trying to import a profile, so it should
- # NOT be restricted by the active materials on the current machine.
- materials = None
+ global_stack = Application.getInstance().getGlobalContainerStack()
+ definition_id = getMachineDefinitionIDForQualitySearch(global_stack)
+ profile.setDefinition(definition_id)
# Check to make sure the imported profile actually makes sense in context of the current configuration.
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
# successfully imported but then fail to show up.
- from cura.QualityManager import QualityManager
- qualities = QualityManager.getInstance()._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria)
- if not qualities:
+ quality_manager = CuraApplication.getInstance()._quality_manager
+ quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
+ if quality_type not in quality_group_dict:
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
ContainerRegistry.getInstance().addContainer(profile)
@@ -346,18 +357,6 @@ class CuraContainerRegistry(ContainerRegistry):
result.append( (plugin_id, meta_data) )
return result
- ## Get the definition to use to select quality profiles for the active machine
- # \return the active quality definition object or None if there is no quality definition
- def _activeQualityDefinition(self):
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack:
- definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom())
- definition = self.findDefinitionContainers(id = definition_id)[0]
-
- if definition:
- return definition
- return None
-
## Returns true if the current machine requires its own materials
# \return True if the current machine requires its own materials
def _machineHasOwnMaterials(self):
@@ -422,11 +421,22 @@ class CuraContainerRegistry(ContainerRegistry):
if not isinstance(container, ContainerStack) or container.getMetaDataEntry("type") != "machine":
return
+ machine_extruder_trains = container.getMetaDataEntry("machine_extruder_trains")
+ if machine_extruder_trains is not None and machine_extruder_trains != {"0": "fdmextruder"}:
+ return
+
extruder_stacks = self.findContainerStacks(type = "extruder_train", machine = container.getId())
if not extruder_stacks:
self.addExtruderStackForSingleExtrusionMachine(container, "fdmextruder")
- def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id):
+ #
+ # new_global_quality_changes is optional. It is only used in project loading for a scenario like this:
+ # - override the current machine
+ # - create new for custom quality profile
+ # new_global_quality_changes is the new global quality changes container in this scenario.
+ # create_new_ids indicates if new unique ids must be created
+ #
+ def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None, create_new_ids = True):
new_extruder_id = extruder_id
extruder_definitions = self.findDefinitionContainers(id = new_extruder_id)
@@ -435,20 +445,50 @@ class CuraContainerRegistry(ContainerRegistry):
return
extruder_definition = extruder_definitions[0]
- unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id)
+ unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) if create_new_ids else machine.getName() + " " + new_extruder_id
extruder_stack = ExtruderStack.ExtruderStack(unique_name)
extruder_stack.setName(extruder_definition.getName())
extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
+ # create a new definition_changes container for the extruder stack
+ definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings"
+ definition_changes_name = definition_changes_id
+ definition_changes = InstanceContainer(definition_changes_id)
+ definition_changes.setName(definition_changes_name)
+ definition_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
+ definition_changes.addMetaDataEntry("type", "definition_changes")
+ definition_changes.addMetaDataEntry("definition", extruder_definition.getId())
+
+ # move definition_changes settings if exist
+ for setting_key in definition_changes.getAllKeys():
+ if machine.definition.getProperty(setting_key, "settable_per_extruder"):
+ setting_value = machine.definitionChanges.getProperty(setting_key, "value")
+ if setting_value is not None:
+ # move it to the extruder stack's definition_changes
+ setting_definition = machine.getSettingDefinition(setting_key)
+ new_instance = SettingInstance(setting_definition, definition_changes)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ definition_changes.addInstance(new_instance)
+ definition_changes.setDirty(True)
+
+ machine.definitionChanges.removeInstance(setting_key, postpone_emit = True)
+
+ self.addContainer(definition_changes)
+ extruder_stack.setDefinitionChanges(definition_changes)
+
# create empty user changes container otherwise
- user_container = InstanceContainer(extruder_stack.id + "_user")
+ user_container_id = self.uniqueName(extruder_stack.getId() + "_user") if create_new_ids else extruder_stack.getId() + "_user"
+ user_container_name = user_container_id
+ user_container = InstanceContainer(user_container_id)
+ user_container.setName(user_container_name)
user_container.addMetaDataEntry("type", "user")
- user_container.addMetaDataEntry("machine", extruder_stack.getId())
- from cura.CuraApplication import CuraApplication
+ user_container.addMetaDataEntry("machine", machine.getId())
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(machine.definition.getId())
+ user_container.setMetaDataEntry("extruder", extruder_stack.getId())
if machine.userChanges:
# for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes
@@ -456,56 +496,152 @@ class CuraContainerRegistry(ContainerRegistry):
for user_setting_key in machine.userChanges.getAllKeys():
settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder")
if settable_per_extruder:
- user_container.addInstance(machine.userChanges.getInstance(user_setting_key))
+ setting_value = machine.getProperty(user_setting_key, "value")
+
+ setting_definition = machine.getSettingDefinition(user_setting_key)
+ new_instance = SettingInstance(setting_definition, definition_changes)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ user_container.addInstance(new_instance)
+ user_container.setDirty(True)
+
machine.userChanges.removeInstance(user_setting_key, postpone_emit = True)
self.addContainer(user_container)
extruder_stack.setUserChanges(user_container)
- variant_id = "default"
+ application = CuraApplication.getInstance()
+ empty_variant = application.empty_variant_container
+ empty_material = application.empty_material_container
+ empty_quality = application.empty_quality_container
+
if machine.variant.getId() not in ("empty", "empty_variant"):
- variant_id = machine.variant.getId()
+ variant = machine.variant
else:
- variant_id = "empty_variant"
- extruder_stack.setVariantById(variant_id)
+ variant = empty_variant
+ extruder_stack.variant = variant
- material_id = "default"
if machine.material.getId() not in ("empty", "empty_material"):
- material_id = machine.material.getId()
+ material = machine.material
else:
- material_id = "empty_material"
- extruder_stack.setMaterialById(material_id)
+ material = empty_material
+ extruder_stack.material = material
- quality_id = "default"
if machine.quality.getId() not in ("empty", "empty_quality"):
- quality_id = machine.quality.getId()
+ quality = machine.quality
else:
- quality_id = "empty_quality"
- extruder_stack.setQualityById(quality_id)
+ quality = empty_quality
+ extruder_stack.quality = quality
- if machine.qualityChanges.getId() not in ("empty", "empty_quality_changes"):
- extruder_quality_changes_container = self.findInstanceContainers(name = machine.qualityChanges.getName(), extruder = extruder_id)
+ machine_quality_changes = machine.qualityChanges
+ if new_global_quality_changes is not None:
+ machine_quality_changes = new_global_quality_changes
+
+ if machine_quality_changes.getId() not in ("empty", "empty_quality_changes"):
+ extruder_quality_changes_container = self.findInstanceContainers(name = machine_quality_changes.getName(), extruder = extruder_id)
if extruder_quality_changes_container:
extruder_quality_changes_container = extruder_quality_changes_container[0]
+
quality_changes_id = extruder_quality_changes_container.getId()
- extruder_stack.setQualityChangesById(quality_changes_id)
+ extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
else:
# Some extruder quality_changes containers can be created at runtime as files in the qualities
# folder. Those files won't be loaded in the registry immediately. So we also need to search
# the folder to see if the quality_changes exists.
- extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine.qualityChanges.getName())
+ extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
if extruder_quality_changes_container:
quality_changes_id = extruder_quality_changes_container.getId()
- extruder_stack.setQualityChangesById(quality_changes_id)
+ extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
+ extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
+ else:
+ # if we still cannot find a quality changes container for the extruder, create a new one
+ container_name = machine_quality_changes.getName()
+ container_id = self.uniqueName(extruder_stack.getId() + "_qc_" + container_name)
+ extruder_quality_changes_container = InstanceContainer(container_id)
+ extruder_quality_changes_container.setName(container_name)
+ extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes")
+ extruder_quality_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
+ extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
+ extruder_quality_changes_container.addMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
+ extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId())
+
+ self.addContainer(extruder_quality_changes_container)
+ extruder_stack.qualityChanges = extruder_quality_changes_container
if not extruder_quality_changes_container:
Logger.log("w", "Could not find quality_changes named [%s] for extruder [%s]",
- machine.qualityChanges.getName(), extruder_stack.getId())
+ machine_quality_changes.getName(), extruder_stack.getId())
+ else:
+ # move all per-extruder settings to the extruder's quality changes
+ for qc_setting_key in machine_quality_changes.getAllKeys():
+ settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
+ if settable_per_extruder:
+ setting_value = machine_quality_changes.getProperty(qc_setting_key, "value")
+
+ setting_definition = machine.getSettingDefinition(qc_setting_key)
+ new_instance = SettingInstance(setting_definition, definition_changes)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ extruder_quality_changes_container.addInstance(new_instance)
+ extruder_quality_changes_container.setDirty(True)
+
+ machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True)
else:
- extruder_stack.setQualityChangesById("empty_quality_changes")
+ extruder_stack.qualityChanges = self.findInstanceContainers(id = "empty_quality_changes")[0]
self.addContainer(extruder_stack)
+ # Also need to fix the other qualities that are suitable for this machine. Those quality changes may still have
+ # per-extruder settings in the container for the machine instead of the extruder.
+ if machine_quality_changes.getId() not in ("empty", "empty_quality_changes"):
+ quality_changes_machine_definition_id = machine_quality_changes.getDefinition().getId()
+ else:
+ whole_machine_definition = machine.definition
+ machine_entry = machine.definition.getMetaDataEntry("machine")
+ if machine_entry is not None:
+ container_registry = ContainerRegistry.getInstance()
+ whole_machine_definition = container_registry.findDefinitionContainers(id = machine_entry)[0]
+
+ quality_changes_machine_definition_id = "fdmprinter"
+ if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
+ quality_changes_machine_definition_id = machine.definition.getMetaDataEntry("quality_definition",
+ whole_machine_definition.getId())
+ qcs = self.findInstanceContainers(type = "quality_changes", definition = quality_changes_machine_definition_id)
+ qc_groups = {} # map of qc names -> qc containers
+ for qc in qcs:
+ qc_name = qc.getName()
+ if qc_name not in qc_groups:
+ qc_groups[qc_name] = []
+ qc_groups[qc_name].append(qc)
+ # try to find from the quality changes cura directory too
+ quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
+ if quality_changes_container:
+ qc_groups[qc_name].append(quality_changes_container)
+
+ for qc_name, qc_list in qc_groups.items():
+ qc_dict = {"global": None, "extruders": []}
+ for qc in qc_list:
+ extruder_def_id = qc.getMetaDataEntry("extruder")
+ if extruder_def_id is not None:
+ qc_dict["extruders"].append(qc)
+ else:
+ qc_dict["global"] = qc
+ if qc_dict["global"] is not None and len(qc_dict["extruders"]) == 1:
+ # move per-extruder settings
+ for qc_setting_key in qc_dict["global"].getAllKeys():
+ settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
+ if settable_per_extruder:
+ setting_value = qc_dict["global"].getProperty(qc_setting_key, "value")
+
+ setting_definition = machine.getSettingDefinition(qc_setting_key)
+ new_instance = SettingInstance(setting_definition, definition_changes)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ qc_dict["extruders"][0].addInstance(new_instance)
+ qc_dict["extruders"][0].setDirty(True)
+
+ qc_dict["global"].removeInstance(qc_setting_key, postpone_emit=True)
+
# Set next stack at the end
extruder_stack.setNextStack(machine)
@@ -521,7 +657,7 @@ class CuraContainerRegistry(ContainerRegistry):
if not os.path.isfile(file_path):
continue
- parser = configparser.ConfigParser()
+ parser = configparser.ConfigParser(interpolation=None)
try:
parser.read([file_path])
except:
@@ -534,9 +670,12 @@ class CuraContainerRegistry(ContainerRegistry):
if parser["general"]["name"] == name:
# load the container
container_id = os.path.basename(file_path).replace(".inst.cfg", "")
+ if self.findInstanceContainers(id = container_id):
+ # this container is already in the registry, skip it
+ continue
instance_container = InstanceContainer(container_id)
- with open(file_path, "r") as f:
+ with open(file_path, "r", encoding = "utf-8") as f:
serialized = f.read()
instance_container.deserialize(serialized, file_path)
self.addContainer(instance_container)
diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py
index 8e13b24358..00db4f57c7 100755
--- a/cura/Settings/CuraContainerStack.py
+++ b/cura/Settings/CuraContainerStack.py
@@ -83,20 +83,6 @@ class CuraContainerStack(ContainerStack):
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
- ## Set the quality changes container by an ID.
- #
- # This will search for the specified container and set it. If no container was found, an error will be raised.
- #
- # \param new_quality_changes_id The ID of the new quality changes container.
- #
- # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
- def setQualityChangesById(self, new_quality_changes_id: str) -> None:
- quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id)
- if quality_changes:
- self.setQualityChanges(quality_changes[0])
- else:
- raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id))
-
## Get the quality changes container.
#
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@@ -110,31 +96,6 @@ class CuraContainerStack(ContainerStack):
def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
- ## Set the quality container by an ID.
- #
- # This will search for the specified container and set it. If no container was found, an error will be raised.
- # There is a special value for ID, which is "default". The "default" value indicates the quality should be set
- # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultQuality
- # for details.
- #
- # \param new_quality_id The ID of the new quality container.
- #
- # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
- def setQualityById(self, new_quality_id: str) -> None:
- quality = self._empty_quality
- if new_quality_id == "default":
- new_quality = self.findDefaultQuality()
- if new_quality:
- quality = new_quality
- else:
- qualities = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id)
- if qualities:
- quality = qualities[0]
- else:
- raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id))
-
- self.setQuality(quality)
-
## Get the quality container.
#
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@@ -144,35 +105,10 @@ class CuraContainerStack(ContainerStack):
## Set the material container.
#
- # \param new_quality_changes The new material container. It is expected to have a "type" metadata entry with the value "quality_changes".
+ # \param new_material The new material container. It is expected to have a "type" metadata entry with the value "material".
def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
- ## Set the material container by an ID.
- #
- # This will search for the specified container and set it. If no container was found, an error will be raised.
- # There is a special value for ID, which is "default". The "default" value indicates the quality should be set
- # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial
- # for details.
- #
- # \param new_quality_changes_id The ID of the new material container.
- #
- # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
- def setMaterialById(self, new_material_id: str) -> None:
- material = self._empty_material
- if new_material_id == "default":
- new_material = self.findDefaultMaterial()
- if new_material:
- material = new_material
- else:
- materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
- if materials:
- material = materials[0]
- else:
- raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id))
-
- self.setMaterial(material)
-
## Get the material container.
#
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@@ -182,35 +118,10 @@ class CuraContainerStack(ContainerStack):
## Set the variant container.
#
- # \param new_quality_changes The new variant container. It is expected to have a "type" metadata entry with the value "quality_changes".
+ # \param new_variant The new variant container. It is expected to have a "type" metadata entry with the value "variant".
def setVariant(self, new_variant: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.Variant, new_variant)
- ## Set the variant container by an ID.
- #
- # This will search for the specified container and set it. If no container was found, an error will be raised.
- # There is a special value for ID, which is "default". The "default" value indicates the quality should be set
- # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant
- # for details.
- #
- # \param new_quality_changes_id The ID of the new variant container.
- #
- # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
- def setVariantById(self, new_variant_id: str) -> None:
- variant = self._empty_variant
- if new_variant_id == "default":
- new_variant = self.findDefaultVariant()
- if new_variant:
- variant = new_variant
- else:
- variants = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id)
- if variants:
- variant = variants[0]
- else:
- raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id))
-
- self.setVariant(variant)
-
## Get the variant container.
#
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@@ -220,22 +131,10 @@ class CuraContainerStack(ContainerStack):
## Set the definition changes container.
#
- # \param new_quality_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
+ # \param new_definition_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "definition_changes".
def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
- ## Set the definition changes container by an ID.
- #
- # \param new_quality_changes_id The ID of the new definition changes container.
- #
- # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
- def setDefinitionChangesById(self, new_definition_changes_id: str) -> None:
- new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id)
- if new_definition_changes:
- self.setDefinitionChanges(new_definition_changes[0])
- else:
- raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id))
-
## Get the definition changes container.
#
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@@ -245,22 +144,10 @@ class CuraContainerStack(ContainerStack):
## Set the definition container.
#
- # \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes".
+ # \param new_definition The new definition container. It is expected to have a "type" metadata entry with the value "definition".
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
- ## Set the definition container by an ID.
- #
- # \param new_quality_changes_id The ID of the new definition container.
- #
- # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
- def setDefinitionById(self, new_definition_id: str) -> None:
- new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id)
- if new_definition:
- self.setDefinition(new_definition[0])
- else:
- raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id))
-
## Get the definition container.
#
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@@ -348,6 +235,10 @@ class CuraContainerStack(ContainerStack):
elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type")))
+ current_container = self._containers[index]
+ if current_container.getId() == container.getId():
+ return
+
super().replaceContainer(index, container, postpone_emit)
## Overridden from ContainerStack
@@ -391,198 +282,6 @@ class CuraContainerStack(ContainerStack):
self._containers = new_containers
- ## Find the variant that should be used as "default" variant.
- #
- # This will search for variants that match the current definition and pick the preferred one,
- # if specified by the machine definition.
- #
- # The following criteria are used to find the default variant:
- # - If the machine definition does not have a metadata entry "has_variants" set to True, return None
- # - The definition of the variant should be the same as the machine definition for this stack.
- # - The container should have a metadata entry "type" with value "variant".
- # - If the machine definition has a metadata entry "preferred_variant", filter the variant IDs based on that.
- #
- # \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
- #
- # \note This method assumes the stack has a valid machine definition.
- def findDefaultVariant(self) -> Optional[ContainerInterface]:
- definition = self._getMachineDefinition()
- # has_variants can be overridden in other containers and stacks.
- # In the case of UM2, it is overridden in the GlobalStack
- if not self.getMetaDataEntry("has_variants"):
- # If the machine does not use variants, we should never set a variant.
- return None
-
- # First add any variant. Later, overwrite with preference if the preference is valid.
- variant = None
- definition_id = self._findInstanceContainerDefinitionId(definition)
- variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant")
- if variants:
- variant = variants[0]
-
- preferred_variant_id = definition.getMetaDataEntry("preferred_variant")
- if preferred_variant_id:
- preferred_variants = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_id, definition = definition_id, type = "variant")
- if preferred_variants:
- variant = preferred_variants[0]
- else:
- Logger.log("w", "The preferred variant \"{variant}\" of stack {stack} does not exist or is not a variant.", variant = preferred_variant_id, stack = self.id)
- # And leave it at the default variant.
-
- if variant:
- return variant
-
- Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id)
- return None
-
- ## Find the material that should be used as "default" material.
- #
- # This will search for materials that match the current definition and pick the preferred one,
- # if specified by the machine definition.
- #
- # The following criteria are used to find the default material:
- # - If the machine definition does not have a metadata entry "has_materials" set to True, return None
- # - If the machine definition has a metadata entry "has_machine_materials", the definition of the material should
- # be the same as the machine definition for this stack. Otherwise, the definition should be "fdmprinter".
- # - The container should have a metadata entry "type" with value "material".
- # - The material should have an approximate diameter that matches the machine
- # - If the machine definition has a metadata entry "has_variants" and set to True, the "variant" metadata entry of
- # the material should be the same as the ID of the variant in the stack. Only applies if "has_machine_materials" is also True.
- # - If the stack currently has a material set, try to find a material that matches the current material by name.
- # - Otherwise, if the machine definition has a metadata entry "preferred_material", try to find a material that matches the specified ID.
- #
- # \return The container that should be used as default, or None if nothing was found or the machine does not use materials.
- def findDefaultMaterial(self) -> Optional[ContainerInterface]:
- definition = self._getMachineDefinition()
- if not definition.getMetaDataEntry("has_materials"):
- # Machine does not use materials, never try to set it.
- return None
-
- search_criteria = {"type": "material"}
- if definition.getMetaDataEntry("has_machine_materials"):
- search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
-
- if definition.getMetaDataEntry("has_variants"):
- search_criteria["variant"] = self.variant.id
- else:
- search_criteria["definition"] = "fdmprinter"
-
- if self.material != self._empty_material:
- search_criteria["name"] = self.material.name
- else:
- preferred_material = definition.getMetaDataEntry("preferred_material")
- if preferred_material:
- search_criteria["id"] = preferred_material
-
- approximate_material_diameter = str(round(self.getProperty("material_diameter", "value")))
- search_criteria["approximate_diameter"] = approximate_material_diameter
-
- materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
- if not materials:
- Logger.log("w", "The preferred material \"{material}\" could not be found for stack {stack}", material = preferred_material, stack = self.id)
- # We failed to find any materials matching the specified criteria, drop some specific criteria and try to find
- # a material that sort-of matches what we want.
- search_criteria.pop("variant", None)
- search_criteria.pop("id", None)
- search_criteria.pop("name", None)
- materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
-
- if not materials:
- Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
- return None
-
- for material in materials:
- # Prefer a read-only material
- if ContainerRegistry.getInstance().isReadOnly(material.getId()):
- return material
-
- return materials[0]
-
-
- ## Find the quality that should be used as "default" quality.
- #
- # This will search for qualities that match the current definition and pick the preferred one,
- # if specified by the machine definition.
- #
- # \return The container that should be used as default, or None if nothing was found.
- def findDefaultQuality(self) -> Optional[ContainerInterface]:
- definition = self._getMachineDefinition()
- registry = ContainerRegistry.getInstance()
- material_container = self.material if self.material.getId() not in (self._empty_material.getId(), self._empty_instance_container.getId()) else None
-
- search_criteria = {"type": "quality"}
-
- if definition.getMetaDataEntry("has_machine_quality"):
- search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
-
- if definition.getMetaDataEntry("has_materials") and material_container:
- search_criteria["material"] = material_container.id
- else:
- search_criteria["definition"] = "fdmprinter"
-
- if self.quality != self._empty_quality:
- search_criteria["name"] = self.quality.name
- else:
- preferred_quality = definition.getMetaDataEntry("preferred_quality")
- if preferred_quality:
- search_criteria["id"] = preferred_quality
-
- containers = registry.findInstanceContainers(**search_criteria)
- if containers:
- return containers[0]
-
- if "material" in search_criteria:
- # First check if we can solve our material not found problem by checking if we can find quality containers
- # that are assigned to the parents of this material profile.
- try:
- inherited_files = material_container.getInheritedFiles()
- except AttributeError: # Material_container does not support inheritance.
- inherited_files = []
-
- if inherited_files:
- for inherited_file in inherited_files:
- # Extract the ID from the path we used to load the file.
- search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
- containers = registry.findInstanceContainers(**search_criteria)
- if containers:
- return containers[0]
-
- # We still weren't able to find a quality for this specific material.
- # Try to find qualities for a generic version of the material.
- material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
- if definition.getMetaDataEntry("has_machine_quality"):
- if self.material != self._empty_instance_container:
- material_search_criteria["definition"] = material_container.getMetaDataEntry("definition")
-
- if definition.getMetaDataEntry("has_variants"):
- material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
- else:
- material_search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
-
- if definition.getMetaDataEntry("has_variants") and self.variant != self._empty_instance_container:
- material_search_criteria["variant"] = self.variant.id
- else:
- material_search_criteria["definition"] = "fdmprinter"
- material_containers = registry.findInstanceContainersMetadata(**material_search_criteria)
- # Try all materials to see if there is a quality profile available.
- for material_container in material_containers:
- search_criteria["material"] = material_container["id"]
-
- containers = registry.findInstanceContainers(**search_criteria)
- if containers:
- return containers[0]
-
- if "name" in search_criteria or "id" in search_criteria:
- # If a quality by this name can not be found, try a wider set of search criteria
- search_criteria.pop("name", None)
- search_criteria.pop("id", None)
-
- containers = registry.findInstanceContainers(**search_criteria)
- if containers:
- return containers[0]
-
- return None
-
## protected:
# Helper to make sure we emit a PyQt signal on container changes.
diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py
index 8d2d66ea87..a8234b9de9 100644
--- a/cura/Settings/CuraStackBuilder.py
+++ b/cura/Settings/CuraStackBuilder.py
@@ -1,15 +1,18 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from UM.Logger import Logger
+from typing import Optional
+from UM.Logger import Logger
from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.Settings.SettingFunction import SettingFunction
+from UM.Util import parseBool
+from cura.Machines.VariantManager import VariantType
from .GlobalStack import GlobalStack
from .ExtruderStack import ExtruderStack
-from typing import Optional
## Contains helper functions to create new machines.
@@ -22,7 +25,13 @@ class CuraStackBuilder:
# \return The new global stack or None if an error occurred.
@classmethod
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
+ from cura.CuraApplication import CuraApplication
+ application = CuraApplication.getInstance()
+ variant_manager = application.getVariantManager()
+ material_manager = application.getMaterialManager()
+ quality_manager = application.getQualityManager()
registry = ContainerRegistry.getInstance()
+
definitions = registry.findDefinitionContainers(id = definition_id)
if not definitions:
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
@@ -30,7 +39,21 @@ class CuraStackBuilder:
machine_definition = definitions[0]
- generated_name = registry.createUniqueName("machine", "", name, machine_definition.name)
+ # get variant container for the global stack
+ global_variant_container = application.empty_variant_container
+ global_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.BUILD_PLATE)
+ if global_variant_node:
+ global_variant_container = global_variant_node.getContainer()
+
+ # get variant container for extruders
+ extruder_variant_container = application.empty_variant_container
+ extruder_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.NOZZLE)
+ extruder_variant_name = None
+ if extruder_variant_node:
+ extruder_variant_container = extruder_variant_node.getContainer()
+ extruder_variant_name = extruder_variant_container.getName()
+
+ generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName())
# Make sure the new name does not collide with any definition or (quality) profile
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles
# Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true
@@ -40,47 +63,59 @@ class CuraStackBuilder:
new_global_stack = cls.createGlobalStack(
new_stack_id = generated_name,
definition = machine_definition,
- quality = "default",
- material = "default",
- variant = "default",
+ variant_container = global_variant_container,
+ material_container = application.empty_material_container,
+ quality_container = application.empty_quality_container,
)
-
new_global_stack.setName(generated_name)
- extruder_definition = registry.findDefinitionContainers(machine = machine_definition.getId())
+ # get material container for extruders
+ material_container = application.empty_material_container
+ material_node = material_manager.getDefaultMaterial(new_global_stack, extruder_variant_name)
+ if material_node:
+ material_container = material_node.getContainer()
- if not extruder_definition:
- # create extruder stack for single extrusion machines that have no separate extruder definition files
- extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0]
- new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id)
+ # Create ExtruderStacks
+ extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains")
+
+ for position, extruder_definition_id in extruder_dict.items():
+ # Sanity check: make sure that the positions in the extruder definitions are same as in the machine
+ # definition
+ extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
+ position_in_extruder_def = extruder_definition.getMetaDataEntry("position")
+ if position_in_extruder_def != position:
+ raise RuntimeError("Extruder position [%s] defined in extruder definition [%s] is not the same as in machine definition [%s] position [%s]" %
+ (position_in_extruder_def, extruder_definition_id, definition_id, position))
+
+ new_extruder_id = registry.uniqueName(extruder_definition_id)
new_extruder = cls.createExtruderStack(
new_extruder_id,
- definition = extruder_definition,
- machine_definition_id = machine_definition.getId(),
- quality = "default",
- material = "default",
- variant = "default",
- next_stack = new_global_stack
+ extruder_definition = extruder_definition,
+ machine_definition_id = definition_id,
+ position = position,
+ variant_container = extruder_variant_container,
+ material_container = material_container,
+ quality_container = application.empty_quality_container,
+ global_stack = new_global_stack,
)
+ new_extruder.setNextStack(new_global_stack)
new_global_stack.addExtruder(new_extruder)
- else:
- # create extruder stack for each found extruder definition
- for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id):
- position = extruder_definition.getMetaDataEntry("position", None)
- if not position:
- Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id)
+ registry.addContainer(new_extruder)
- new_extruder_id = registry.uniqueName(extruder_definition.id)
- new_extruder = cls.createExtruderStack(
- new_extruder_id,
- definition = extruder_definition,
- machine_definition_id = machine_definition.getId(),
- quality = "default",
- material = "default",
- variant = "default",
- next_stack = new_global_stack
- )
- new_global_stack.addExtruder(new_extruder)
+ preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type")
+ quality_group_dict = quality_manager.getQualityGroups(new_global_stack)
+ quality_group = quality_group_dict.get(preferred_quality_type)
+
+ new_global_stack.quality = quality_group.node_for_global.getContainer()
+ for position, extruder_stack in new_global_stack.extruders.items():
+ if position in quality_group.nodes_for_extruders:
+ extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer()
+ else:
+ extruder_stack.quality = application.empty_quality_container
+
+ # Register the global stack after the extruder stacks are created. This prevents the registry from adding another
+ # extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1).
+ registry.addContainer(new_global_stack)
return new_global_stack
@@ -94,50 +129,32 @@ class CuraStackBuilder:
#
# \return A new Global stack instance with the specified parameters.
@classmethod
- def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, machine_definition_id: str, **kwargs) -> ExtruderStack:
- stack = ExtruderStack(new_stack_id)
- stack.setName(definition.getName())
- stack.setDefinition(definition)
- stack.addMetaDataEntry("position", definition.getMetaDataEntry("position"))
-
- if "next_stack" in kwargs:
- # Add stacks before containers are added, since they may trigger a setting update.
- stack.setNextStack(kwargs["next_stack"])
-
- user_container = InstanceContainer(new_stack_id + "_user")
- user_container.addMetaDataEntry("type", "user")
- user_container.addMetaDataEntry("extruder", new_stack_id)
+ def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
+ position: int,
+ variant_container, material_container, quality_container, global_stack) -> ExtruderStack:
from cura.CuraApplication import CuraApplication
- user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
- user_container.setDefinition(machine_definition_id)
+ application = CuraApplication.getInstance()
- stack.setUserChanges(user_container)
+ stack = ExtruderStack(new_stack_id, parent = global_stack)
+ stack.setName(extruder_definition.getName())
+ stack.setDefinition(extruder_definition)
- # Important! The order here matters, because that allows the stack to
- # assume the material and variant have already been set.
- if "definition_changes" in kwargs:
- stack.setDefinitionChangesById(kwargs["definition_changes"])
- else:
- stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings"))
+ stack.addMetaDataEntry("position", position)
- if "variant" in kwargs:
- stack.setVariantById(kwargs["variant"])
+ user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
+ is_global_stack = False)
- if "material" in kwargs:
- stack.setMaterialById(kwargs["material"])
-
- if "quality" in kwargs:
- stack.setQualityById(kwargs["quality"])
-
- if "quality_changes" in kwargs:
- stack.setQualityChangesById(kwargs["quality_changes"])
+ stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
+ stack.variant = variant_container
+ stack.material = material_container
+ stack.quality = quality_container
+ stack.qualityChanges = application.empty_quality_changes_container
+ stack.userChanges = user_container
# Only add the created containers to the registry after we have set all the other
# properties. This makes the create operation more transactional, since any problems
# setting properties will not result in incomplete containers being added.
- registry = ContainerRegistry.getInstance()
- registry.addContainer(stack)
- registry.addContainer(user_container)
+ ContainerRegistry.getInstance().addContainer(user_container)
return stack
@@ -149,46 +166,48 @@ class CuraStackBuilder:
#
# \return A new Global stack instance with the specified parameters.
@classmethod
- def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, **kwargs) -> GlobalStack:
+ def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface,
+ variant_container, material_container, quality_container) -> GlobalStack:
+ from cura.CuraApplication import CuraApplication
+ application = CuraApplication.getInstance()
+
stack = GlobalStack(new_stack_id)
stack.setDefinition(definition)
- user_container = InstanceContainer(new_stack_id + "_user")
- user_container.addMetaDataEntry("type", "user")
- user_container.addMetaDataEntry("machine", new_stack_id)
- from cura.CuraApplication import CuraApplication
- user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
- user_container.setDefinition(definition.getId())
+ # Create user container
+ user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id,
+ is_global_stack = True)
- stack.setUserChanges(user_container)
+ stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
+ stack.variant = variant_container
+ stack.material = material_container
+ stack.quality = quality_container
+ stack.qualityChanges = application.empty_quality_changes_container
+ stack.userChanges = user_container
- # Important! The order here matters, because that allows the stack to
- # assume the material and variant have already been set.
- if "definition_changes" in kwargs:
- stack.setDefinitionChangesById(kwargs["definition_changes"])
- else:
- stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings"))
-
- if "variant" in kwargs:
- stack.setVariantById(kwargs["variant"])
-
- if "material" in kwargs:
- stack.setMaterialById(kwargs["material"])
-
- if "quality" in kwargs:
- stack.setQualityById(kwargs["quality"])
-
- if "quality_changes" in kwargs:
- stack.setQualityChangesById(kwargs["quality_changes"])
-
- registry = ContainerRegistry.getInstance()
- registry.addContainer(stack)
- registry.addContainer(user_container)
+ ContainerRegistry.getInstance().addContainer(user_container)
return stack
@classmethod
- def createDefinitionChangesContainer(cls, container_stack, container_name, container_index = None):
+ def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
+ is_global_stack: bool) -> "InstanceContainer":
+ from cura.CuraApplication import CuraApplication
+
+ unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
+
+ container = InstanceContainer(unique_container_name)
+ container.setDefinition(definition_id)
+ container.addMetaDataEntry("type", "user")
+ container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
+
+ metadata_key_to_add = "machine" if is_global_stack else "extruder"
+ container.addMetaDataEntry(metadata_key_to_add, stack_id)
+
+ return container
+
+ @classmethod
+ def createDefinitionChangesContainer(cls, container_stack, container_name):
from cura.CuraApplication import CuraApplication
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py
index 32e3867300..06342b68e5 100755
--- a/cura/Settings/ExtruderManager.py
+++ b/cura/Settings/ExtruderManager.py
@@ -12,6 +12,7 @@ from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
from UM.Settings.SettingFunction import SettingFunction
+from UM.Settings.SettingInstance import SettingInstance
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from typing import Optional, List, TYPE_CHECKING, Union
@@ -30,22 +31,19 @@ class ExtruderManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
+ self._application = Application.getInstance()
+
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
self._selected_object_extruders = []
- self._global_container_stack_definition_id = None
self._addCurrentMachineExtruders()
- Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
+ #Application.getInstance().globalContainerStackChanged.connect(self._globalContainerStackChanged)
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
## Signal to notify other components when the list of extruders for a machine definition changes.
extrudersChanged = pyqtSignal(QVariant)
- ## Signal to notify other components when the global container stack is switched to a definition
- # that has different extruders than the previous global container stack
- globalContainerStackDefinitionChanged = pyqtSignal()
-
## Notify when the user switches the currently active extruder.
activeExtruderChanged = pyqtSignal()
@@ -181,6 +179,7 @@ class ExtruderManager(QObject):
self._selected_object_extruders = []
self.selectedObjectExtrudersChanged.emit()
+ @pyqtSlot(result = QObject)
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
global_container_stack = Application.getInstance().getGlobalContainerStack()
@@ -270,7 +269,7 @@ class ExtruderManager(QObject):
return []
# Get the extruders of all printable meshes in the scene
- meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()]
+ meshes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()]
for mesh in meshes:
extruder_stack_id = mesh.callDecoration("getActiveExtruder")
if not extruder_stack_id:
@@ -368,12 +367,7 @@ class ExtruderManager(QObject):
return result[:machine_extruder_count]
- def __globalContainerStackChanged(self) -> None:
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id:
- self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
- self.globalContainerStackDefinitionChanged.emit()
-
+ def _globalContainerStackChanged(self) -> None:
# If the global container changed, the machine changed and might have extruders that were not registered yet
self._addCurrentMachineExtruders()
@@ -381,7 +375,7 @@ class ExtruderManager(QObject):
## Adds the extruders of the currently active machine.
def _addCurrentMachineExtruders(self) -> None:
- global_stack = Application.getInstance().getGlobalContainerStack()
+ global_stack = self._application.getGlobalContainerStack()
extruders_changed = False
if global_stack:
@@ -401,13 +395,82 @@ class ExtruderManager(QObject):
self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train
# regardless of what the next stack is, we have to set it again, because of signal routing. ???
+ extruder_train.setParent(global_stack)
extruder_train.setNextStack(global_stack)
extruders_changed = True
+ self._fixMaterialDiameterAndNozzleSize(global_stack, extruder_trains)
if extruders_changed:
self.extrudersChanged.emit(global_stack_id)
self.setActiveExtruderIndex(0)
+ #
+ # This function tries to fix the problem with per-extruder-settable nozzle size and material diameter problems
+ # in early versions (3.0 - 3.2.1).
+ #
+ # In earlier versions, "nozzle size" and "material diameter" are only applicable to the complete machine, so all
+ # extruders share the same values. In this case, "nozzle size" and "material diameter" are saved in the
+ # GlobalStack's DefinitionChanges container.
+ #
+ # Later, we could have different "nozzle size" for each extruder, but "material diameter" could only be set for
+ # the entire machine. In this case, "nozzle size" should be saved in each ExtruderStack's DefinitionChanges, but
+ # "material diameter" still remains in the GlobalStack's DefinitionChanges.
+ #
+ # Lateer, both "nozzle size" and "material diameter" are settable per-extruder, and both settings should be saved
+ # in the ExtruderStack's DefinitionChanges.
+ #
+ # There were some bugs in upgrade so the data weren't saved correct as described above. This function tries fix
+ # this.
+ #
+ # One more thing is about material diameter and single-extrusion machines. Most single-extrusion machines don't
+ # specifically define their extruder definition, so they reuse "fdmextruder", but for those machines, they may
+ # define "material diameter = 1.75" in their machine definition, but in "fdmextruder", it's still "2.85". This
+ # causes a problem with incorrect default values.
+ #
+ # This is also fixed here in this way: If no "material diameter" is specified, it will look for the default value
+ # in both the Extruder's definition and the Global's definition. If 2 values don't match, we will use the value
+ # from the Global definition by setting it in the Extruder's DefinitionChanges container.
+ #
+ def _fixMaterialDiameterAndNozzleSize(self, global_stack, extruder_stack_list):
+ keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
+
+ extruder_positions_to_update = set()
+ for extruder_stack in extruder_stack_list:
+ for key in keys_to_copy:
+ # Only copy the value when this extruder doesn't have the value.
+ if extruder_stack.definitionChanges.hasProperty(key, "value"):
+ continue
+
+ setting_value_in_global_def_changes = global_stack.definitionChanges.getProperty(key, "value")
+ setting_value_in_global_def = global_stack.definition.getProperty(key, "value")
+ setting_value = setting_value_in_global_def
+ if setting_value_in_global_def_changes is not None:
+ setting_value = setting_value_in_global_def_changes
+ if setting_value == extruder_stack.definition.getProperty(key, "value"):
+ continue
+
+ setting_definition = global_stack.getSettingDefinition(key)
+ new_instance = SettingInstance(setting_definition, extruder_stack.definitionChanges)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ extruder_stack.definitionChanges.addInstance(new_instance)
+ extruder_stack.definitionChanges.setDirty(True)
+
+ # Make sure the material diameter is up to date for the extruder stack.
+ if key == "material_diameter":
+ position = int(extruder_stack.getMetaDataEntry("position"))
+ extruder_positions_to_update.add(position)
+
+ # We have to remove those settings here because we know that those values have been copied to all
+ # the extruders at this point.
+ for key in keys_to_copy:
+ if global_stack.definitionChanges.hasProperty(key, "value"):
+ global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
+
+ # Update material diameter for extruders
+ for position in extruder_positions_to_update:
+ self.updateMaterialForDiameter(position, global_stack = global_stack)
+
## Get all extruder values for a certain setting.
#
# This is exposed to SettingFunction so it can be used in value functions.
@@ -492,6 +555,96 @@ class ExtruderManager(QObject):
def getInstanceExtruderValues(self, key):
return ExtruderManager.getExtruderValues(key)
+ ## Updates the material container to a material that matches the material diameter set for the printer
+ def updateMaterialForDiameter(self, extruder_position: int, global_stack = None):
+ if not global_stack:
+ global_stack = Application.getInstance().getGlobalContainerStack()
+ if not global_stack:
+ return
+
+ if not global_stack.getMetaDataEntry("has_materials", False):
+ return
+
+ extruder_stack = global_stack.extruders[str(extruder_position)]
+
+ material_diameter = extruder_stack.material.getProperty("material_diameter", "value")
+ if not material_diameter:
+ # in case of "empty" material
+ material_diameter = 0
+
+ material_approximate_diameter = str(round(material_diameter))
+ material_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
+ setting_provider = extruder_stack
+ if not material_diameter:
+ if extruder_stack.definition.hasProperty("material_diameter", "value"):
+ material_diameter = extruder_stack.definition.getProperty("material_diameter", "value")
+ else:
+ material_diameter = global_stack.definition.getProperty("material_diameter", "value")
+ setting_provider = global_stack
+
+ if isinstance(material_diameter, SettingFunction):
+ material_diameter = material_diameter(setting_provider)
+
+ machine_approximate_diameter = str(round(material_diameter))
+
+ if material_approximate_diameter != machine_approximate_diameter:
+ Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
+
+ if global_stack.getMetaDataEntry("has_machine_materials", False):
+ materials_definition = global_stack.definition.getId()
+ has_material_variants = global_stack.getMetaDataEntry("has_variants", False)
+ else:
+ materials_definition = "fdmprinter"
+ has_material_variants = False
+
+ old_material = extruder_stack.material
+ search_criteria = {
+ "type": "material",
+ "approximate_diameter": machine_approximate_diameter,
+ "material": old_material.getMetaDataEntry("material", "value"),
+ "brand": old_material.getMetaDataEntry("brand", "value"),
+ "supplier": old_material.getMetaDataEntry("supplier", "value"),
+ "color_name": old_material.getMetaDataEntry("color_name", "value"),
+ "definition": materials_definition
+ }
+ if has_material_variants:
+ search_criteria["variant"] = extruder_stack.variant.getId()
+
+ container_registry = Application.getInstance().getContainerRegistry()
+ empty_material = container_registry.findInstanceContainers(id = "empty_material")[0]
+
+ if old_material == empty_material:
+ search_criteria.pop("material", None)
+ search_criteria.pop("supplier", None)
+ search_criteria.pop("brand", None)
+ search_criteria.pop("definition", None)
+ search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
+
+ materials = container_registry.findInstanceContainers(**search_criteria)
+ if not materials:
+ # Same material with new diameter is not found, search for generic version of the same material type
+ search_criteria.pop("supplier", None)
+ search_criteria.pop("brand", None)
+ search_criteria["color_name"] = "Generic"
+ materials = container_registry.findInstanceContainers(**search_criteria)
+ if not materials:
+ # Generic material with new diameter is not found, search for preferred material
+ search_criteria.pop("color_name", None)
+ search_criteria.pop("material", None)
+ search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
+ materials = container_registry.findInstanceContainers(**search_criteria)
+ if not materials:
+ # Preferred material with new diameter is not found, search for any material
+ search_criteria.pop("id", None)
+ materials = container_registry.findInstanceContainers(**search_criteria)
+ if not materials:
+ # Just use empty material as a final fallback
+ materials = [empty_material]
+
+ Logger.log("i", "Selecting new material: %s", materials[0].getId())
+
+ extruder_stack.material = materials[0]
+
## Get the value for a setting from a specific extruder.
#
# This is exposed to SettingFunction to use in value functions.
diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py
index 42a2733879..4400f621c6 100644
--- a/cura/Settings/ExtruderStack.py
+++ b/cura/Settings/ExtruderStack.py
@@ -3,6 +3,8 @@
from typing import Any, TYPE_CHECKING, Optional
+from PyQt5.QtCore import pyqtProperty
+
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack
@@ -10,12 +12,13 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
from . import Exceptions
-from .CuraContainerStack import CuraContainerStack
+from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
from .ExtruderManager import ExtruderManager
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
+
## Represents an Extruder and its related containers.
#
#
@@ -31,7 +34,7 @@ class ExtruderStack(CuraContainerStack):
#
# This will set the next stack and ensure that we register this stack as an extruder.
@override(ContainerStack)
- def setNextStack(self, stack: ContainerStack) -> None:
+ def setNextStack(self, stack: CuraContainerStack) -> None:
super().setNextStack(stack)
stack.addExtruder(self)
self.addMetaDataEntry("machine", stack.id)
@@ -47,6 +50,29 @@ class ExtruderStack(CuraContainerStack):
def getLoadingPriority(cls) -> int:
return 3
+ ## Return the filament diameter that the machine requires.
+ #
+ # If the machine has no requirement for the diameter, -1 is returned.
+ # \return The filament diameter for the printer
+ @property
+ def materialDiameter(self) -> float:
+ context = PropertyEvaluationContext(self)
+ context.context["evaluate_from_container_index"] = _ContainerIndexes.Variant
+
+ return self.getProperty("material_diameter", "value", context = context)
+
+ ## Return the approximate filament diameter that the machine requires.
+ #
+ # The approximate material diameter is the material diameter rounded to
+ # the nearest millimetre.
+ #
+ # If the machine has no requirement for the diameter, -1 is returned.
+ #
+ # \return The approximate filament diameter for the printer
+ @pyqtProperty(float)
+ def approximateMaterialDiameter(self) -> float:
+ return round(float(self.materialDiameter))
+
## Overridden from ContainerStack
#
# It will perform a few extra checks when trying to get properties.
@@ -115,11 +141,6 @@ class ExtruderStack(CuraContainerStack):
if has_global_dependencies:
self.getNextStack().propertiesChanged.emit(key, properties)
- def findDefaultVariant(self):
- # The default variant is defined in the machine stack and/or definition, so use the machine stack to find
- # the default variant.
- return self.getNextStack().findDefaultVariant()
-
extruder_stack_mime = MimeType(
name = "application/x-cura-extruderstack",
diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py
index dae1c103b4..ae1f1370ed 100755
--- a/cura/Settings/GlobalStack.py
+++ b/cura/Settings/GlobalStack.py
@@ -1,6 +1,8 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+from collections import defaultdict
+import threading
from typing import Any, Dict, Optional
from PyQt5.QtCore import pyqtProperty
@@ -30,7 +32,8 @@ class GlobalStack(CuraContainerStack):
# This property is used to track which settings we are calculating the "resolve" for
# and if so, to bypass the resolve to prevent an infinite recursion that would occur
# if the resolve function tried to access the same property it is a resolve for.
- self._resolving_settings = set()
+ # Per thread we have our own resolving_settings, or strange things sometimes occur.
+ self._resolving_settings = defaultdict(set) # keys are thread names
## Get the list of extruders of this stack.
#
@@ -91,9 +94,10 @@ class GlobalStack(CuraContainerStack):
# Handle the "resolve" property.
if self._shouldResolve(key, property_name, context):
- self._resolving_settings.add(key)
+ current_thread = threading.current_thread()
+ self._resolving_settings[current_thread.name].add(key)
resolve = super().getProperty(key, "resolve", context)
- self._resolving_settings.remove(key)
+ self._resolving_settings[current_thread.name].remove(key)
if resolve is not None:
return resolve
@@ -121,21 +125,6 @@ class GlobalStack(CuraContainerStack):
def setNextStack(self, next_stack: ContainerStack) -> None:
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
- ## Gets the approximate filament diameter that the machine requires.
- #
- # The approximate material diameter is the material diameter rounded to
- # the nearest millimetre.
- #
- # If the machine has no requirement for the diameter, -1 is returned.
- #
- # \return The approximate filament diameter for the printer, as a string.
- @pyqtProperty(str)
- def approximateMaterialDiameter(self) -> str:
- material_diameter = self.definition.getProperty("material_diameter", "value")
- if material_diameter is None:
- return "-1"
- return str(round(float(material_diameter))) #Round, then convert back to string.
-
# protected:
# Determine whether or not we should try to get the "resolve" property instead of the
@@ -145,7 +134,8 @@ class GlobalStack(CuraContainerStack):
# Do not try to resolve anything but the "value" property
return False
- if key in self._resolving_settings:
+ current_thread = threading.current_thread()
+ if key in self._resolving_settings[current_thread.name]:
# To prevent infinite recursion, if getProperty is called with the same key as
# we are already trying to resolve, we should not try to resolve again. Since
# this can happen multiple times when trying to resolve a value, we need to
diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py
index 05aed1f5e2..e357d778ca 100755
--- a/cura/Settings/MachineManager.py
+++ b/cura/Settings/MachineManager.py
@@ -1,29 +1,31 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
+import collections
+import time
#Type hinting.
-from typing import Union, List, Dict
+from typing import Union, List, Dict, TYPE_CHECKING, Optional
+
+from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Signal import Signal
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
+import UM.FlameProfiler
from UM.FlameProfiler import pyqtSlot
-from PyQt5.QtWidgets import QMessageBox
from UM import Util
from UM.Application import Application
from UM.Preferences import Preferences
from UM.Logger import Logger
from UM.Message import Message
-from UM.Decorators import deprecated
from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique
-import UM.FlameProfiler
-from cura.QualityManager import QualityManager
+from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
+
from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.Settings.ExtruderManager import ExtruderManager
@@ -32,26 +34,25 @@ from .CuraStackBuilder import CuraStackBuilder
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
-from cura.Settings.ProfilesModel import ProfilesModel
-from typing import TYPE_CHECKING, Optional
-
if TYPE_CHECKING:
- from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.Settings.CuraContainerStack import CuraContainerStack
from cura.Settings.GlobalStack import GlobalStack
class MachineManager(QObject):
+
def __init__(self, parent = None):
super().__init__(parent)
self._active_container_stack = None # type: CuraContainerStack
self._global_container_stack = None # type: GlobalStack
- # Used to store the new containers until after confirming the dialog
- self._new_variant_container = None
- self._new_material_container = None
- self._new_quality_containers = []
+ self._current_root_material_id = {}
+ self._current_root_material_name = {}
+ self._current_quality_group = None
+ self._current_quality_changes_group = None
+
+ self.machine_extruder_material_update_dict = collections.defaultdict(list)
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
@@ -63,17 +64,21 @@ class MachineManager(QObject):
self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
- Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
- Application.getInstance().getContainerRegistry().containerLoadComplete.connect(self._onInstanceContainersChanged)
- self._connected_to_profiles_model = False
+ self._application = Application.getInstance()
+ self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
+ self._application.getContainerRegistry().containerLoadComplete.connect(self._onInstanceContainersChanged)
## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged)
self.globalContainerChanged.connect(self.activeVariantChanged)
self.globalContainerChanged.connect(self.activeQualityChanged)
- self._stacks_have_errors = None
+ self.globalContainerChanged.connect(self.activeQualityChangesGroupChanged)
+ self.globalContainerChanged.connect(self.activeQualityGroupChanged)
+ self._stacks_have_errors = None # type:Optional[bool]
+
+ self._empty_definition_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0]
self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0]
self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0]
self._empty_quality_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
@@ -93,37 +98,35 @@ class MachineManager(QObject):
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
self.activeStackChanged.connect(self.activeStackValueChanged)
- # when a user closed dialog check if any delayed material or variant changes need to be applied
- Application.getInstance().onDiscardOrKeepProfileChangesClosed.connect(self._executeDelayedActiveContainerStackChanges)
-
Preferences.getInstance().addPreference("cura/active_machine", "")
self._global_event_keys = set()
- active_machine_id = Preferences.getInstance().getValue("cura/active_machine")
-
self._printer_output_devices = []
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
# There might already be some output devices by the time the signal is connected
self._onOutputDevicesChanged()
- if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id):
- # An active machine was saved, so restore it.
- self.setActiveMachine(active_machine_id)
- # Make sure _active_container_stack is properly initiated
- ExtruderManager.getInstance().setActiveExtruderIndex(0)
-
- self._auto_materials_changed = {}
- self._auto_hotends_changed = {}
+ self._application.callLater(self.setInitialActiveMachine)
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
- "The selected material is incompatible with the selected machine or configuration."),
+ "The selected material is incompatible with the selected machine or configuration."),
title = catalog.i18nc("@info:title", "Incompatible Material"))
containers = ContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId)
if containers:
containers[0].nameChanged.connect(self._onMaterialNameChanged)
+ self._material_manager = self._application._material_manager
+ self._quality_manager = self._application.getQualityManager()
+
+ # When the materials lookup table gets updated, it can mean that a material has its name changed, which should
+ # be reflected on the GUI. This signal emission makes sure that it happens.
+ self._material_manager.materialsUpdated.connect(self.rootMaterialChanged)
+
+ activeQualityGroupChanged = pyqtSignal()
+ activeQualityChangesGroupChanged = pyqtSignal()
+
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
activeMaterialChanged = pyqtSignal()
activeVariantChanged = pyqtSignal()
@@ -135,33 +138,28 @@ class MachineManager(QObject):
activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
stacksValidationChanged = pyqtSignal() # Emitted whenever a validation is changed
- blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
+ blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
outputDevicesChanged = pyqtSignal()
+ rootMaterialChanged = pyqtSignal()
+
+ def setInitialActiveMachine(self):
+ active_machine_id = Preferences.getInstance().getValue("cura/active_machine")
+ if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id):
+ # An active machine was saved, so restore it.
+ self.setActiveMachine(active_machine_id)
+ # Make sure _active_container_stack is properly initiated
+ ExtruderManager.getInstance().setActiveExtruderIndex(0)
+
def _onOutputDevicesChanged(self) -> None:
- for printer_output_device in self._printer_output_devices:
- printer_output_device.hotendIdChanged.disconnect(self._onHotendIdChanged)
- printer_output_device.materialIdChanged.disconnect(self._onMaterialIdChanged)
-
- self._printer_output_devices.clear()
-
+ self._printer_output_devices = []
for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
if isinstance(printer_output_device, PrinterOutputDevice):
self._printer_output_devices.append(printer_output_device)
- printer_output_device.hotendIdChanged.connect(self._onHotendIdChanged)
- printer_output_device.materialIdChanged.connect(self._onMaterialIdChanged)
self.outputDevicesChanged.emit()
- @property
- def newVariant(self):
- return self._new_variant_container
-
- @property
- def newMaterial(self):
- return self._new_material_container
-
@pyqtProperty("QVariantList", notify = outputDevicesChanged)
def printerOutputDevices(self):
return self._printer_output_devices
@@ -170,103 +168,7 @@ class MachineManager(QObject):
def totalNumberOfSettings(self) -> int:
return len(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys())
- def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None:
- if not self._global_container_stack:
- return
-
- containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "variant", definition = self._global_container_stack.definition.getId(), name = hotend_id)
- if containers: # New material ID is known
- extruder_manager = ExtruderManager.getInstance()
- machine_id = self.activeMachineId
- extruders = extruder_manager.getMachineExtruders(machine_id)
- matching_extruder = None
- for extruder in extruders:
- if str(index) == extruder.getMetaDataEntry("position"):
- matching_extruder = extruder
- break
- if matching_extruder and matching_extruder.variant.getName() != hotend_id:
- # Save the material that needs to be changed. Multiple changes will be handled by the callback.
- self._auto_hotends_changed[str(index)] = containers[0]["id"]
- self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback)
- else:
- Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.definition.getId(), hotend_id))
-
- def _onMaterialIdChanged(self, index: Union[str, int], material_id: str):
- if not self._global_container_stack:
- return
-
- definition_id = "fdmprinter"
- if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
- definition_id = self.activeQualityDefinitionId
- extruder_manager = ExtruderManager.getInstance()
- containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "material", definition = definition_id, GUID = material_id)
- if containers: # New material ID is known
- extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId))
- matching_extruder = None
- for extruder in extruders:
- if str(index) == extruder.getMetaDataEntry("position"):
- matching_extruder = extruder
- break
-
- if matching_extruder and matching_extruder.material.getMetaDataEntry("GUID") != material_id:
- # Save the material that needs to be changed. Multiple changes will be handled by the callback.
- if self._global_container_stack.definition.getMetaDataEntry("has_variants") and matching_extruder.variant:
- variant_id = self.getQualityVariantId(self._global_container_stack.definition, matching_extruder.variant)
- for container in containers:
- if container.get("variant") == variant_id:
- self._auto_materials_changed[str(index)] = container["id"]
- break
- else:
- # Just use the first result we found.
- self._auto_materials_changed[str(index)] = containers[0]["id"]
- self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback)
- else:
- Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id))
-
- def _materialHotendChangedCallback(self, button):
- if button == QMessageBox.No:
- self._auto_materials_changed = {}
- self._auto_hotends_changed = {}
- return
- self._autoUpdateMaterials()
- self._autoUpdateHotends()
-
- def _autoUpdateMaterials(self):
- extruder_manager = ExtruderManager.getInstance()
- for position in self._auto_materials_changed:
- material_id = self._auto_materials_changed[position]
- old_index = extruder_manager.activeExtruderIndex
-
- if old_index != int(position):
- extruder_manager.setActiveExtruderIndex(int(position))
- else:
- old_index = None
-
- Logger.log("d", "Setting material of hotend %s to %s" % (position, material_id))
- self.setActiveMaterial(material_id)
-
- if old_index is not None:
- extruder_manager.setActiveExtruderIndex(old_index)
- self._auto_materials_changed = {} #Processed all of them now.
-
- def _autoUpdateHotends(self):
- extruder_manager = ExtruderManager.getInstance()
- for position in self._auto_hotends_changed:
- hotend_id = self._auto_hotends_changed[position]
- old_index = extruder_manager.activeExtruderIndex
-
- if old_index != int(position):
- extruder_manager.setActiveExtruderIndex(int(position))
- else:
- old_index = None
- Logger.log("d", "Setting hotend variant of hotend %s to %s" % (position, hotend_id))
- self.setActiveVariant(hotend_id)
-
- if old_index is not None:
- extruder_manager.setActiveExtruderIndex(old_index)
- self._auto_hotends_changed = {} # Processed all of them now.
-
- def _onGlobalContainerChanged(self):
+ def _onGlobalContainerChanged(self) -> None:
if self._global_container_stack:
try:
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
@@ -285,7 +187,7 @@ class MachineManager(QObject):
extruder_stack.propertyChanged.disconnect(self._onPropertyChanged)
extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
- # update the local global container stack reference
+ # Update the local global container stack reference
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
self.globalContainerChanged.emit()
@@ -298,10 +200,11 @@ class MachineManager(QObject):
self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
- # set the global variant to empty as we now use the extruder stack at all times - CURA-4482
+ # Global stack can have only a variant if it is a buildplate
global_variant = self._global_container_stack.variant
if global_variant != self._empty_variant_container:
- self._global_container_stack.setVariant(self._empty_variant_container)
+ if global_variant.getMetaDataEntry("hardware_type") != "buildplate":
+ self._global_container_stack.setVariant(self._empty_variant_container)
# set the global material to empty as we now use the extruder stack at all times - CURA-4482
global_material = self._global_container_stack.material
@@ -313,50 +216,46 @@ class MachineManager(QObject):
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
extruder_stack.containersChanged.connect(self._onInstanceContainersChanged)
+ if self._global_container_stack.getId() in self.machine_extruder_material_update_dict:
+ for func in self.machine_extruder_material_update_dict[self._global_container_stack.getId()]:
+ Application.getInstance().callLater(func)
+ del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
+
+ self.activeQualityGroupChanged.emit()
self._error_check_timer.start()
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
- def _updateStacksHaveErrors(self):
+ def _updateStacksHaveErrors(self) -> None:
old_stacks_have_errors = self._stacks_have_errors
self._stacks_have_errors = self._checkStacksHaveErrors()
if old_stacks_have_errors != self._stacks_have_errors:
self.stacksValidationChanged.emit()
Application.getInstance().stacksValidationFinished.emit()
- def _onActiveExtruderStackChanged(self):
+ def _onActiveExtruderStackChanged(self) -> None:
self.blurSettings.emit() # Ensure no-one has focus.
old_active_container_stack = self._active_container_stack
self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
- self._error_check_timer.start()
-
if old_active_container_stack != self._active_container_stack:
# Many methods and properties related to the active quality actually depend
# on _active_container_stack. If it changes, then the properties change.
self.activeQualityChanged.emit()
- def __emitChangedSignals(self):
+ def __emitChangedSignals(self) -> None:
self.activeQualityChanged.emit()
self.activeVariantChanged.emit()
self.activeMaterialChanged.emit()
- self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine
+
+ self.rootMaterialChanged.emit()
+
self._error_check_timer.start()
- def _onProfilesModelChanged(self, *args):
- self.__emitChangedSignals()
-
- def _onInstanceContainersChanged(self, container):
- # This should not trigger the ProfilesModel to be created, or there will be an infinite recursion
- if not self._connected_to_profiles_model and ProfilesModel.hasInstance():
- # This triggers updating the qualityModel in SidebarSimple whenever ProfilesModel is updated
- Logger.log("d", "Connecting profiles model...")
- ProfilesModel.getInstance().itemsChanged.connect(self._onProfilesModelChanged)
- self._connected_to_profiles_model = True
-
+ def _onInstanceContainersChanged(self, container) -> None:
self._instance_container_timer.start()
- def _onPropertyChanged(self, key: str, property_name: str):
+ def _onPropertyChanged(self, key: str, property_name: str) -> None:
if property_name == "value":
# Notify UI items, such as the "changed" star in profile pull down menu.
self.activeStackValueChanged.emit()
@@ -364,16 +263,46 @@ class MachineManager(QObject):
elif property_name == "validationState":
self._error_check_timer.start()
+ ## Given a global_stack, make sure that it's all valid by searching for this quality group and applying it again
+ def _initMachineState(self, global_stack):
+ material_dict = {}
+ for position, extruder in global_stack.extruders.items():
+ material_dict[position] = extruder.material.getMetaDataEntry("base_file")
+ self._current_root_material_id = material_dict
+ global_quality = global_stack.quality
+ quality_type = global_quality.getMetaDataEntry("quality_type")
+ global_quality_changes = global_stack.qualityChanges
+ global_quality_changes_name = global_quality_changes.getName()
+
+ if global_quality_changes.getId() != "empty_quality_changes":
+ quality_changes_groups = self._application._quality_manager.getQualityChangesGroups(global_stack)
+ if global_quality_changes_name in quality_changes_groups:
+ new_quality_changes_group = quality_changes_groups[global_quality_changes_name]
+ self._setQualityChangesGroup(new_quality_changes_group)
+ else:
+ quality_groups = self._application._quality_manager.getQualityGroups(global_stack)
+ if quality_type not in quality_groups:
+ Logger.log("w", "Quality type [%s] not found in available qualities [%s]", quality_type, str(quality_groups.values()))
+ self._setEmptyQuality()
+ return
+ new_quality_group = quality_groups[quality_type]
+ self._setQualityGroup(new_quality_group, empty_quality_changes = True)
+
@pyqtSlot(str)
def setActiveMachine(self, stack_id: str) -> None:
self.blurSettings.emit() # Ensure no-one has focus.
- self._cancelDelayedActiveContainerStackChanges()
container_registry = ContainerRegistry.getInstance()
containers = container_registry.findContainerStacks(id = stack_id)
if containers:
- Application.getInstance().setGlobalContainerStack(containers[0])
+ global_stack = containers[0]
+ ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder
+ self._global_container_stack = global_stack
+ Application.getInstance().setGlobalContainerStack(global_stack)
+ ExtruderManager.getInstance()._globalContainerStackChanged()
+ self._initMachineState(containers[0])
+ self._onGlobalContainerChanged()
self.__emitChangedSignals()
@@ -387,27 +316,30 @@ class MachineManager(QObject):
Logger.log("w", "Failed creating a new machine!")
def _checkStacksHaveErrors(self) -> bool:
+ time_start = time.time()
if self._global_container_stack is None: #No active machine.
return False
if self._global_container_stack.hasErrors():
+ Logger.log("d", "Checking global stack for errors took %0.2f s and we found and error" % (time.time() - time_start))
return True
- for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()):
+
+ # Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
+ machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
+ extruder_stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
+ count = 1 # we start with the global stack
+ for stack in extruder_stacks:
+ md = stack.getMetaData()
+ if "position" in md and int(md["position"]) >= machine_extruder_count:
+ continue
+ count += 1
if stack.hasErrors():
+ Logger.log("d", "Checking %s stacks for errors took %.2f s and we found an error in stack [%s]" % (count, time.time() - time_start, str(stack)))
return True
+ Logger.log("d", "Checking %s stacks for errors took %.2f s" % (count, time.time() - time_start))
return False
- ## Remove all instances from the top instanceContainer (effectively removing all user-changed settings)
- @pyqtSlot()
- def clearUserSettings(self):
- if not self._active_container_stack:
- return
-
- self.blurSettings.emit()
- user_settings = self._active_container_stack.getTop()
- user_settings.clear()
-
## Check if the global_container has instances in the user container
@pyqtProperty(bool, notify = activeStackValueChanged)
def hasUserSettings(self) -> bool:
@@ -438,7 +370,7 @@ class MachineManager(QObject):
## Delete a user setting from the global stack and all extruder stacks.
# \param key \type{str} the name of the key to delete
@pyqtSlot(str)
- def clearUserSettingAllCurrentStacks(self, key: str):
+ def clearUserSettingAllCurrentStacks(self, key: str) -> None:
if not self._global_container_stack:
return
@@ -473,25 +405,16 @@ class MachineManager(QObject):
def stacksHaveErrors(self) -> bool:
return bool(self._stacks_have_errors)
- @pyqtProperty(str, notify = activeStackChanged)
- def activeUserProfileId(self) -> str:
- if self._active_container_stack:
- return self._active_container_stack.getTop().getId()
-
- return ""
-
@pyqtProperty(str, notify = globalContainerChanged)
def activeMachineName(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getName()
-
return ""
@pyqtProperty(str, notify = globalContainerChanged)
def activeMachineId(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getId()
-
return ""
@pyqtProperty(QObject, notify = globalContainerChanged)
@@ -502,42 +425,11 @@ class MachineManager(QObject):
def activeStackId(self) -> str:
if self._active_container_stack:
return self._active_container_stack.getId()
-
return ""
- @pyqtProperty(str, notify = activeMaterialChanged)
- def activeMaterialName(self) -> str:
- if self._active_container_stack:
- material = self._active_container_stack.material
- if material:
- return material.getName()
-
- return ""
-
- @pyqtProperty("QVariantList", notify=activeVariantChanged)
- def activeVariantNames(self) -> List[str]:
- result = []
-
- active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
- if active_stacks is not None:
- for stack in active_stacks:
- variant_container = stack.variant
- if variant_container and variant_container != self._empty_variant_container:
- result.append(variant_container.getName())
-
- return result
-
- @pyqtProperty("QVariantList", notify = activeMaterialChanged)
- def activeMaterialNames(self) -> List[str]:
- result = []
-
- active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
- if active_stacks is not None:
- for stack in active_stacks:
- material_container = stack.material
- if material_container and material_container != self._empty_material_container:
- result.append(material_container.getName())
- return result
+ @pyqtProperty(QObject, notify = activeStackChanged)
+ def activeStack(self) -> Optional["ExtruderStack"]:
+ return self._active_container_stack
@pyqtProperty(str, notify=activeMaterialChanged)
def activeMaterialId(self) -> str:
@@ -545,24 +437,8 @@ class MachineManager(QObject):
material = self._active_container_stack.material
if material:
return material.getId()
-
return ""
- @pyqtProperty("QVariantMap", notify = activeVariantChanged)
- def allActiveVariantIds(self) -> Dict[str, str]:
- result = {}
-
- active_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
- if active_stacks is not None: #If we have a global stack.
- for stack in active_stacks:
- variant_container = stack.variant
- if not variant_container:
- continue
-
- result[stack.getId()] = variant_container.getId()
-
- return result
-
## Gets a dict with the active materials ids set in all extruder stacks and the global stack
# (when there is one extruder, the material is set in the global stack)
#
@@ -587,98 +463,45 @@ class MachineManager(QObject):
#
# \return The layer height of the currently active quality profile. If
# there is no quality profile, this returns 0.
- @pyqtProperty(float, notify=activeQualityChanged)
+ @pyqtProperty(float, notify = activeQualityGroupChanged)
def activeQualityLayerHeight(self) -> float:
if not self._global_container_stack:
return 0
-
- quality_changes = self._global_container_stack.qualityChanges
- if quality_changes:
- value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality_changes.getId())
+ if self._current_quality_changes_group:
+ value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = self._global_container_stack.qualityChanges.getId())
if isinstance(value, SettingFunction):
value = value(self._global_container_stack)
return value
- quality = self._global_container_stack.quality
- if quality:
- value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality.getId())
+ elif self._current_quality_group:
+ value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = self._global_container_stack.quality.getId())
if isinstance(value, SettingFunction):
value = value(self._global_container_stack)
return value
+ return 0
- return 0 # No quality profile.
-
- ## Get the Material ID associated with the currently active material
- # \returns MaterialID (string) if found, empty string otherwise
- @pyqtProperty(str, notify=activeQualityChanged)
- def activeQualityMaterialId(self) -> str:
- if self._active_container_stack:
- quality = self._active_container_stack.quality
- if quality:
- material_id = quality.getMetaDataEntry("material")
- if material_id:
- # if the currently active machine inherits its qualities from a different machine
- # definition, make sure to return a material that is relevant to that machine definition
- definition_id = self.activeDefinitionId
- quality_definition_id = self.activeQualityDefinitionId
- if definition_id != quality_definition_id:
- material_id = material_id.replace(definition_id, quality_definition_id, 1)
-
- return material_id
- return ""
-
- @pyqtProperty(str, notify=activeQualityChanged)
- def activeQualityName(self) -> str:
- if self._active_container_stack and self._global_container_stack:
- quality = self._global_container_stack.qualityChanges
- if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
- return quality.getName()
- quality = self._active_container_stack.quality
- if quality:
- return quality.getName()
- return ""
-
- @pyqtProperty(str, notify=activeQualityChanged)
- def activeQualityId(self) -> str:
- if self._active_container_stack:
- quality = self._active_container_stack.quality
- if isinstance(quality, type(self._empty_quality_container)):
- return ""
- quality_changes = self._active_container_stack.qualityChanges
- if quality and quality_changes:
- if isinstance(quality_changes, type(self._empty_quality_changes_container)):
- # It's a built-in profile
- return quality.getId()
- else:
- # Custom profile
- return quality_changes.getId()
- return ""
-
- @pyqtProperty(str, notify=activeQualityChanged)
- def globalQualityId(self) -> str:
+ @pyqtProperty(str, notify = activeVariantChanged)
+ def globalVariantName(self) -> str:
if self._global_container_stack:
- quality = self._global_container_stack.qualityChanges
- if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
- return quality.getId()
- quality = self._global_container_stack.quality
- if quality:
- return quality.getId()
+ variant = self._global_container_stack.variant
+ if variant and not isinstance(variant, type(self._empty_variant_container)):
+ return variant.getName()
return ""
- @pyqtProperty(str, notify = activeQualityChanged)
+ @pyqtProperty(str, notify = activeQualityGroupChanged)
def activeQualityType(self) -> str:
+ quality_type = ""
if self._active_container_stack:
- quality = self._active_container_stack.quality
- if quality:
- return quality.getMetaDataEntry("quality_type")
- return ""
+ if self._current_quality_group:
+ quality_type = self._current_quality_group.quality_type
+ return quality_type
- @pyqtProperty(bool, notify = activeQualityChanged)
+ @pyqtProperty(bool, notify = activeQualityGroupChanged)
def isActiveQualitySupported(self) -> bool:
- if self._active_container_stack:
- quality = self._active_container_stack.quality
- if quality:
- return Util.parseBool(quality.getMetaDataEntry("supported", True))
- return False
+ is_supported = False
+ if self._global_container_stack:
+ if self._current_quality_group:
+ is_supported = self._current_quality_group.is_available
+ return is_supported
## Returns whether there is anything unsupported in the current set-up.
#
@@ -697,29 +520,6 @@ class MachineManager(QObject):
return False
return True
- ## Get the Quality ID associated with the currently active extruder
- # Note that this only returns the "quality", not the "quality_changes"
- # \returns QualityID (string) if found, empty string otherwise
- # \sa activeQualityId()
- # \todo Ideally, this method would be named activeQualityId(), and the other one
- # would be named something like activeQualityOrQualityChanges() for consistency
- @pyqtProperty(str, notify = activeQualityChanged)
- def activeQualityContainerId(self) -> str:
- # We're using the active stack instead of the global stack in case the list of qualities differs per extruder
- if self._global_container_stack:
- quality = self._active_container_stack.quality
- if quality:
- return quality.getId()
- return ""
-
- @pyqtProperty(str, notify = activeQualityChanged)
- def activeQualityChangesId(self) -> str:
- if self._active_container_stack:
- quality_changes = self._active_container_stack.qualityChanges
- if quality_changes and not isinstance(quality_changes, type(self._empty_quality_changes_container)):
- return quality_changes.getId()
- return ""
-
## Check if a container is read_only
@pyqtSlot(str, result = bool)
def isReadOnly(self, container_id: str) -> bool:
@@ -736,357 +536,6 @@ class MachineManager(QObject):
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
- ## Set the active material by switching out a container
- # Depending on from/to material+current variant, a quality profile is chosen and set.
- @pyqtSlot(str)
- def setActiveMaterial(self, material_id: str):
- with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
- containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
- if not containers or not self._active_container_stack:
- return
- material_container = containers[0]
-
- Logger.log("d", "Attempting to change the active material to %s", material_id)
-
- old_material = self._active_container_stack.material
- old_quality = self._active_container_stack.quality
- old_quality_type = None
- if old_quality and old_quality.getId() != self._empty_quality_container.getId():
- old_quality_type = old_quality.getMetaDataEntry("quality_type")
- old_quality_changes = self._active_container_stack.qualityChanges
- if not old_material:
- Logger.log("w", "While trying to set the active material, no material was found to replace it.")
- return
-
- if old_quality_changes and isinstance(old_quality_changes, type(self._empty_quality_changes_container)):
- old_quality_changes = None
-
- self.blurSettings.emit()
- old_material.nameChanged.disconnect(self._onMaterialNameChanged)
-
- self._new_material_container = material_container # self._active_container_stack will be updated with a delay
- Logger.log("d", "Active material changed")
-
- material_container.nameChanged.connect(self._onMaterialNameChanged)
-
- if material_container.getMetaDataEntry("compatible") == False:
- self._material_incompatible_message.show()
- else:
- self._material_incompatible_message.hide()
-
- quality_type = None
- new_quality_id = None
- if old_quality:
- new_quality_id = old_quality.getId()
- quality_type = old_quality.getMetaDataEntry("quality_type")
- if old_quality_changes:
- quality_type = old_quality_changes.getMetaDataEntry("quality_type")
- new_quality_id = old_quality_changes.getId()
-
- global_stack = Application.getInstance().getGlobalContainerStack()
- if global_stack:
- quality_manager = QualityManager.getInstance()
-
- candidate_quality = None
- if quality_type:
- candidate_quality = quality_manager.findQualityByQualityType(quality_type,
- quality_manager.getWholeMachineDefinition(global_stack.definition),
- [material_container.getMetaData()])
-
- if not candidate_quality or candidate_quality.getId() == self._empty_quality_changes_container:
- Logger.log("d", "Attempting to find fallback quality")
- # Fall back to a quality (which must be compatible with all other extruders)
- new_qualities = quality_manager.findAllUsableQualitiesForMachineAndExtruders(
- self._global_container_stack, ExtruderManager.getInstance().getExtruderStacks())
-
- quality_types = sorted([q.getMetaDataEntry("quality_type") for q in new_qualities], reverse = True)
- quality_type_to_use = None
- if quality_types:
- # try to use the same quality as before, otherwise the first one in the quality_types
- quality_type_to_use = quality_types[0]
- if old_quality_type is not None and old_quality_type in quality_type_to_use:
- quality_type_to_use = old_quality_type
-
- new_quality = None
- for q in new_qualities:
- if quality_type_to_use is not None and q.getMetaDataEntry("quality_type") == quality_type_to_use:
- new_quality = q
- break
-
- if new_quality is not None:
- new_quality_id = new_quality.getId() # Just pick the first available one
- else:
- Logger.log("w", "No quality profile found that matches the current machine and extruders.")
- else:
- if not old_quality_changes:
- new_quality_id = candidate_quality.getId()
-
- self.setActiveQuality(new_quality_id)
-
- @pyqtSlot(str)
- def setActiveVariant(self, variant_id: str):
- with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
- containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
- if not containers or not self._active_container_stack:
- return
- Logger.log("d", "Attempting to change the active variant to %s", variant_id)
- old_variant = self._active_container_stack.variant
- old_material = self._active_container_stack.material
- if old_variant:
- self.blurSettings.emit()
- self._new_variant_container = containers[0] # self._active_container_stack will be updated with a delay
- Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId()))
- preferred_material_name = None
- if old_material:
- preferred_material_name = old_material.getName()
- preferred_material_id = self._updateMaterialContainer(self._global_container_stack.definition, self._global_container_stack, containers[0], preferred_material_name).id
- self.setActiveMaterial(preferred_material_id)
- else:
- Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
-
- ## set the active quality
- # \param quality_id The quality_id of either a quality or a quality_changes
- @pyqtSlot(str)
- def setActiveQuality(self, quality_id: str):
- with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
- self.blurSettings.emit()
-
- containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = quality_id)
- if not containers or not self._global_container_stack:
- return
-
- # Quality profile come in two flavours: type=quality and type=quality_changes
- # If we found a quality_changes profile then look up its parent quality profile.
- container_type = containers[0].get("type")
- quality_name = containers[0]["name"]
- quality_type = containers[0].get("quality_type")
-
- # Get quality container and optionally the quality_changes container.
- if container_type == "quality":
- new_quality_settings_list = self.determineQualityAndQualityChangesForQualityType(quality_type)
- elif container_type == "quality_changes":
- new_quality_settings_list = self._determineQualityAndQualityChangesForQualityChanges(quality_name)
- else:
- Logger.log("e", "Tried to set quality to a container that is not of the right type: {container_id}".format(container_id = containers[0]["id"]))
- return
-
- # Check if it was at all possible to find new settings
- if new_quality_settings_list is None:
- return
-
- # check if any of the stacks have a not supported profile
- # if that is the case, all stacks should have a not supported state (otherwise it will show quality_type normal)
- has_not_supported_quality = False
-
- # check all stacks for not supported
- for setting_info in new_quality_settings_list:
- if setting_info["quality"].getMetaDataEntry("quality_type") == "not_supported":
- has_not_supported_quality = True
- break
-
- # set all stacks to not supported if that's the case
- if has_not_supported_quality:
- for setting_info in new_quality_settings_list:
- setting_info["quality"] = self._empty_quality_container
-
- self._new_quality_containers.clear()
-
- # store the upcoming quality profile changes per stack for later execution
- # this prevents re-slicing before the user has made a choice in the discard or keep dialog
- # (see _executeDelayedActiveContainerStackChanges)
- for setting_info in new_quality_settings_list:
- stack = setting_info["stack"]
- stack_quality = setting_info["quality"]
- stack_quality_changes = setting_info["quality_changes"]
-
- self._new_quality_containers.append({
- "stack": stack,
- "quality": stack_quality,
- "quality_changes": stack_quality_changes
- })
-
- # show the keep/discard dialog after the containers have been switched. Otherwise, the default values on
- # the dialog will be the those before the switching.
- self._executeDelayedActiveContainerStackChanges()
-
- if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
- Application.getInstance().discardOrKeepProfileChanges()
-
- ## Used to update material and variant in the active container stack with a delay.
- # This delay prevents the stack from triggering a lot of signals (eventually resulting in slicing)
- # before the user decided to keep or discard any of their changes using the dialog.
- # The Application.onDiscardOrKeepProfileChangesClosed signal triggers this method.
- def _executeDelayedActiveContainerStackChanges(self):
- if self._new_variant_container is not None:
- self._active_container_stack.variant = self._new_variant_container
- self._new_variant_container = None
-
- if self._new_material_container is not None:
- self._active_container_stack.material = self._new_material_container
- self._new_material_container = None
-
- # apply the new quality to all stacks
- if self._new_quality_containers:
- for new_quality in self._new_quality_containers:
- self._replaceQualityOrQualityChangesInStack(new_quality["stack"], new_quality["quality"], postpone_emit = True)
- self._replaceQualityOrQualityChangesInStack(new_quality["stack"], new_quality["quality_changes"], postpone_emit = True)
-
- for new_quality in self._new_quality_containers:
- new_quality["stack"].nameChanged.connect(self._onQualityNameChanged)
- new_quality["stack"].sendPostponedEmits() # Send the signals that were postponed in _replaceQualityOrQualityChangesInStack
-
- self._new_quality_containers.clear()
-
- ## Cancel set changes for material and variant in the active container stack.
- # Used for ignoring any changes when switching between printers (setActiveMachine)
- def _cancelDelayedActiveContainerStackChanges(self):
- self._new_material_container = None
- self._new_variant_container = None
-
- ## Determine the quality and quality changes settings for the current machine for a quality name.
- #
- # \param quality_name \type{str} the name of the quality.
- # \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
- @UM.FlameProfiler.profile
- def determineQualityAndQualityChangesForQualityType(self, quality_type: str) -> List[Dict[str, Union["CuraContainerStack", InstanceContainer]]]:
- quality_manager = QualityManager.getInstance()
- result = []
- empty_quality_changes = self._empty_quality_changes_container
- global_container_stack = self._global_container_stack
- if not global_container_stack:
- return []
-
- global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
- extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
-
- # find qualities for extruders
- for extruder_stack in extruder_stacks:
- material_metadata = extruder_stack.material.getMetaData()
-
- # TODO: fix this
- if self._new_material_container and extruder_stack.getId() == self._active_container_stack.getId():
- material_metadata = self._new_material_container.getMetaData()
-
- quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material_metadata])
-
- if not quality:
- # No quality profile is found for this quality type.
- quality = self._empty_quality_container
-
- result.append({
- "stack": extruder_stack,
- "quality": quality,
- "quality_changes": empty_quality_changes
- })
-
- # also find a global quality for the machine
- global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True")
-
- # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482
- if not global_quality and len(extruder_stacks) == 1:
- global_quality = result[0]["quality"]
-
- # if there is still no global quality, set it to empty (not supported)
- if not global_quality:
- global_quality = self._empty_quality_container
-
- result.append({
- "stack": global_container_stack,
- "quality": global_quality,
- "quality_changes": empty_quality_changes
- })
-
- return result
-
- ## Determine the quality and quality changes settings for the current machine for a quality changes name.
- #
- # \param quality_changes_name \type{str} the name of the quality changes.
- # \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
- def _determineQualityAndQualityChangesForQualityChanges(self, quality_changes_name: str) -> Optional[List[Dict[str, Union["CuraContainerStack", InstanceContainer]]]]:
- result = []
- quality_manager = QualityManager.getInstance()
-
- global_container_stack = self._global_container_stack
- global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
- quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name, global_machine_definition)
-
- global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None]
- if global_quality_changes:
- global_quality_changes = global_quality_changes[0]
- else:
- Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
- return None
-
- # For the global stack, find a quality which matches the quality_type in
- # the quality changes profile and also satisfies any material constraints.
- quality_type = global_quality_changes.getMetaDataEntry("quality_type")
-
- extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
-
- # append the extruder quality changes
- for extruder_stack in extruder_stacks:
- extruder_definition = quality_manager.getParentMachineDefinition(extruder_stack.definition)
-
- quality_changes_list = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") == extruder_definition.getId()]
-
- if quality_changes_list:
- quality_changes = quality_changes_list[0]
- else:
- quality_changes = global_quality_changes
- if not quality_changes:
- quality_changes = self._empty_quality_changes_container
-
- material_metadata = extruder_stack.material.getMetaData()
-
- if self._new_material_container and self._active_container_stack.getId() == extruder_stack.getId():
- material_metadata = self._new_material_container.getMetaData()
-
- quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material_metadata])
-
- if not quality:
- # No quality profile found for this quality type.
- quality = self._empty_quality_container
-
- result.append({
- "stack": extruder_stack,
- "quality": quality,
- "quality_changes": quality_changes
- })
-
- # append the global quality changes
- global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, global_quality = "True")
-
- # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482
- if not global_quality and len(extruder_stacks) == 1:
- global_quality = result[0]["quality"]
-
- # if still no global quality changes are found we set it to empty (not supported)
- if not global_quality:
- global_quality = self._empty_quality_container
-
- result.append({
- "stack": global_container_stack,
- "quality": global_quality,
- "quality_changes": global_quality_changes
- })
-
- return result
-
- def _replaceQualityOrQualityChangesInStack(self, stack: "CuraContainerStack", container: "InstanceContainer", postpone_emit = False):
- # Disconnect the signal handling from the old container.
- container_type = container.getMetaDataEntry("type")
- if container_type == "quality":
- stack.quality.nameChanged.disconnect(self._onQualityNameChanged)
- stack.setQuality(container, postpone_emit = postpone_emit)
- stack.quality.nameChanged.connect(self._onQualityNameChanged)
- elif container_type == "quality_changes" or container_type is None:
- # If the container is an empty container, we need to change the quality_changes.
- # Quality can never be set to empty.
- stack.qualityChanges.nameChanged.disconnect(self._onQualityNameChanged)
- stack.setQualityChanges(container, postpone_emit = postpone_emit)
- stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
- self._onQualityNameChanged()
-
@pyqtProperty(str, notify = activeVariantChanged)
def activeVariantName(self) -> str:
if self._active_container_stack:
@@ -1097,11 +546,11 @@ class MachineManager(QObject):
return ""
@pyqtProperty(str, notify = activeVariantChanged)
- def activeVariantId(self) -> str:
- if self._active_container_stack:
- variant = self._active_container_stack.variant
+ def activeVariantBuildplateName(self) -> str:
+ if self._global_container_stack:
+ variant = self._global_container_stack.variant
if variant:
- return variant.getId()
+ return variant.getName()
return ""
@@ -1112,55 +561,14 @@ class MachineManager(QObject):
return ""
- @pyqtProperty(str, notify=globalContainerChanged)
- def activeDefinitionName(self) -> str:
- if self._global_container_stack:
- return self._global_container_stack.definition.getName()
-
- return ""
-
## Get the Definition ID to use to select quality profiles for the currently active machine
# \returns DefinitionID (string) if found, empty string otherwise
- # \sa getQualityDefinitionId
@pyqtProperty(str, notify = globalContainerChanged)
def activeQualityDefinitionId(self) -> str:
if self._global_container_stack:
- return self.getQualityDefinitionId(self._global_container_stack.definition)
+ return getMachineDefinitionIDForQualitySearch(self._global_container_stack)
return ""
- ## Get the Definition ID to use to select quality profiles for machines of the specified definition
- # This is normally the id of the definition itself, but machines can specify a different definition to inherit qualities from
- # \param definition (DefinitionContainer) machine definition
- # \returns DefinitionID (string) if found, empty string otherwise
- def getQualityDefinitionId(self, definition: "DefinitionContainer") -> str:
- return QualityManager.getInstance().getParentMachineDefinition(definition).getId()
-
- ## Get the Variant ID to use to select quality profiles for the currently active variant
- # \returns VariantID (string) if found, empty string otherwise
- # \sa getQualityVariantId
- @pyqtProperty(str, notify = activeVariantChanged)
- def activeQualityVariantId(self) -> str:
- if self._active_container_stack:
- variant = self._active_container_stack.variant
- if variant:
- return self.getQualityVariantId(self._global_container_stack.definition, variant)
- return ""
-
- ## Get the Variant ID to use to select quality profiles for variants of the specified definitions
- # This is normally the id of the variant itself, but machines can specify a different definition
- # to inherit qualities from, which has consequences for the variant to use as well
- # \param definition (DefinitionContainer) machine definition
- # \param variant (InstanceContainer) variant definition
- # \returns VariantID (string) if found, empty string otherwise
- def getQualityVariantId(self, definition: "DefinitionContainer", variant: "InstanceContainer") -> str:
- variant_id = variant.getId()
- definition_id = definition.getId()
- quality_definition_id = self.getQualityDefinitionId(definition)
-
- if definition_id != quality_definition_id:
- variant_id = variant_id.replace(definition_id, quality_definition_id, 1)
- return variant_id
-
## Gets how the active definition calls variants
# Caveat: per-definition-variant-title is currently not translated (though the fallback is)
@pyqtProperty(str, notify = globalContainerChanged)
@@ -1202,7 +610,6 @@ class MachineManager(QObject):
def hasMaterials(self) -> bool:
if self._global_container_stack:
return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False))
-
return False
@pyqtProperty(bool, notify = globalContainerChanged)
@@ -1211,6 +618,53 @@ class MachineManager(QObject):
return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variants", False))
return False
+ @pyqtProperty(bool, notify = globalContainerChanged)
+ def hasVariantBuildplates(self) -> bool:
+ if self._global_container_stack:
+ return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variant_buildplates", False))
+ return False
+
+ ## The selected buildplate is compatible if it is compatible with all the materials in all the extruders
+ @pyqtProperty(bool, notify = activeMaterialChanged)
+ def variantBuildplateCompatible(self) -> bool:
+ if not self._global_container_stack:
+ return True
+
+ buildplate_compatible = True # It is compatible by default
+ extruder_stacks = self._global_container_stack.extruders.values()
+ for stack in extruder_stacks:
+ material_container = stack.material
+ if material_container == self._empty_material_container:
+ continue
+ if material_container.getMetaDataEntry("buildplate_compatible"):
+ buildplate_compatible = buildplate_compatible and material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName]
+
+ return buildplate_compatible
+
+ ## The selected buildplate is usable if it is usable for all materials OR it is compatible for one but not compatible
+ # for the other material but the buildplate is still usable
+ @pyqtProperty(bool, notify = activeMaterialChanged)
+ def variantBuildplateUsable(self) -> bool:
+ if not self._global_container_stack:
+ return True
+
+ # Here the next formula is being calculated:
+ # result = (not (material_left_compatible and material_right_compatible)) and
+ # (material_left_compatible or material_left_usable) and
+ # (material_right_compatible or material_right_usable)
+ result = not self.variantBuildplateCompatible
+ extruder_stacks = self._global_container_stack.extruders.values()
+ for stack in extruder_stacks:
+ material_container = stack.material
+ if material_container == self._empty_material_container:
+ continue
+ buildplate_compatible = material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_compatible") else True
+ buildplate_usable = material_container.getMetaDataEntry("buildplate_recommended")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_recommended") else True
+
+ result = result and (buildplate_compatible or buildplate_usable)
+
+ return result
+
## Property to indicate if a machine has "specialized" material profiles.
# Some machines have their own material profiles that "override" the default catch all profiles.
@pyqtProperty(bool, notify = globalContainerChanged)
@@ -1236,47 +690,74 @@ class MachineManager(QObject):
if containers:
return containers[0].definition.getId()
- @staticmethod
- def createMachineManager():
- return MachineManager()
+ ## Set the amount of extruders on the active machine (global stack)
+ # \param extruder_count int the number of extruders to set
+ def setActiveMachineExtruderCount(self, extruder_count):
+ extruder_manager = Application.getInstance().getExtruderManager()
- @deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7")
- def _updateMaterialContainer(self, definition: "DefinitionContainer", stack: "ContainerStack", variant_container: Optional["InstanceContainer"] = None, preferred_material_name: Optional[str] = None) -> InstanceContainer:
- if not definition.getMetaDataEntry("has_materials"):
- return self._empty_material_container
+ definition_changes_container = self._global_container_stack.definitionChanges
+ if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container:
+ return
- approximate_material_diameter = str(round(stack.getProperty("material_diameter", "value")))
- search_criteria = { "type": "material", "approximate_diameter": approximate_material_diameter }
+ previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
+ if extruder_count == previous_extruder_count:
+ return
- if definition.getMetaDataEntry("has_machine_materials"):
- search_criteria["definition"] = self.getQualityDefinitionId(definition)
+ # reset all extruder number settings whose value is no longer valid
+ for setting_instance in self._global_container_stack.userChanges.findInstances():
+ setting_key = setting_instance.definition.key
+ if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
+ continue
- if definition.getMetaDataEntry("has_variants") and variant_container:
- search_criteria["variant"] = self.getQualityVariantId(definition, variant_container)
- else:
- search_criteria["definition"] = "fdmprinter"
+ old_value = int(self._global_container_stack.userChanges.getProperty(setting_key, "value"))
+ if old_value >= extruder_count:
+ self._global_container_stack.userChanges.removeInstance(setting_key)
+ Logger.log("d", "Reset [%s] because its old value [%s] is no longer valid ", setting_key, old_value)
- if preferred_material_name:
- search_criteria["name"] = preferred_material_name
- else:
- preferred_material = definition.getMetaDataEntry("preferred_material")
- if preferred_material:
- search_criteria["id"] = preferred_material
+ # Check to see if any objects are set to print with an extruder that will no longer exist
+ root_node = Application.getInstance().getController().getScene().getRoot()
+ for node in DepthFirstIterator(root_node):
+ if node.getMeshData():
+ extruder_nr = node.callDecoration("getActiveExtruderPosition")
- containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
- if containers:
- return containers[0]
+ if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
+ node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
- if "variant" in search_criteria or "id" in search_criteria:
- # If a material by this name can not be found, try a wider set of search criteria
- search_criteria.pop("variant", None)
- search_criteria.pop("id", None)
+ definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
- containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
- if containers:
- return containers[0]
- Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.")
- return self._empty_material_container
+ # Make sure one of the extruder stacks is active
+ extruder_manager.setActiveExtruderIndex(0)
+
+ # Move settable_per_extruder values out of the global container
+ # After CURA-4482 this should not be the case anymore, but we still want to support older project files.
+ global_user_container = self._global_container_stack.getTop()
+
+ # Make sure extruder_stacks exists
+ extruder_stacks = []
+
+ if previous_extruder_count == 1:
+ extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
+ global_user_container = self._global_container_stack.getTop()
+
+ for setting_instance in global_user_container.findInstances():
+ setting_key = setting_instance.definition.key
+ settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
+
+ if settable_per_extruder:
+ limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
+ extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
+ extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
+ global_user_container.removeInstance(setting_key)
+
+ # Signal that the global stack has changed
+ Application.getInstance().globalContainerStackChanged.emit()
+
+ @pyqtSlot(int, result = QObject)
+ def getExtruder(self, position: int):
+ extruder = None
+ if self._global_container_stack:
+ extruder = self._global_container_stack.extruders.get(str(position))
+ return extruder
def _onMachineNameChanged(self):
self.globalContainerChanged.emit()
@@ -1291,3 +772,263 @@ class MachineManager(QObject):
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
stacks.append(self._global_container_stack)
return [ s.containersChanged for s in stacks ]
+
+ @pyqtSlot(str, str, str)
+ def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str):
+ for key, extruder in self._global_container_stack.extruders.items():
+ container = extruder.userChanges
+ container.setProperty(setting_name, property_name, property_value)
+
+ @pyqtProperty("QVariantList", notify = rootMaterialChanged)
+ def currentExtruderPositions(self):
+ return sorted(list(self._current_root_material_id.keys()))
+
+ @pyqtProperty("QVariant", notify = rootMaterialChanged)
+ def currentRootMaterialId(self):
+ # initial filling the current_root_material_id
+ self._current_root_material_id = {}
+ for position in self._global_container_stack.extruders:
+ self._current_root_material_id[position] = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file")
+ return self._current_root_material_id
+
+ @pyqtProperty("QVariant", notify = rootMaterialChanged)
+ def currentRootMaterialName(self):
+ # initial filling the current_root_material_name
+ if self._global_container_stack:
+ self._current_root_material_name = {}
+ for position in self._global_container_stack.extruders:
+ if position not in self._current_root_material_name:
+ material = self._global_container_stack.extruders[position].material
+ self._current_root_material_name[position] = material.getName()
+ return self._current_root_material_name
+
+ ## Return the variant names in the extruder stack(s).
+ ## For the variant in the global stack, use activeVariantBuildplateName
+ @pyqtProperty("QVariant", notify = activeVariantChanged)
+ def activeVariantNames(self):
+ result = {}
+
+ active_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
+ if active_stacks is not None:
+ for stack in active_stacks:
+ variant_container = stack.variant
+ position = stack.getMetaDataEntry("position")
+ if variant_container and variant_container != self._empty_variant_container:
+ result[position] = variant_container.getName()
+
+ return result
+
+ #
+ # Sets all quality and quality_changes containers to empty_quality and empty_quality_changes containers
+ # for all stacks in the currently active machine.
+ #
+ def _setEmptyQuality(self):
+ self._current_quality_group = None
+ self._current_quality_changes_group = None
+ self._global_container_stack.quality = self._empty_quality_container
+ self._global_container_stack.qualityChanges = self._empty_quality_changes_container
+ for extruder in self._global_container_stack.extruders.values():
+ extruder.quality = self._empty_quality_container
+ extruder.qualityChanges = self._empty_quality_changes_container
+
+ self.activeQualityGroupChanged.emit()
+ self.activeQualityChangesGroupChanged.emit()
+
+ def _setQualityGroup(self, quality_group, empty_quality_changes = True):
+ self._current_quality_group = quality_group
+ if empty_quality_changes:
+ self._current_quality_changes_group = None
+
+ # Set quality and quality_changes for the GlobalStack
+ self._global_container_stack.quality = quality_group.node_for_global.getContainer()
+ if empty_quality_changes:
+ self._global_container_stack.qualityChanges = self._empty_quality_changes_container
+
+ # Set quality and quality_changes for each ExtruderStack
+ for position, node in quality_group.nodes_for_extruders.items():
+ self._global_container_stack.extruders[position].quality = node.getContainer()
+ if empty_quality_changes:
+ self._global_container_stack.extruders[position].qualityChanges = self._empty_quality_changes_container
+
+ self.activeQualityGroupChanged.emit()
+ self.activeQualityChangesGroupChanged.emit()
+
+ def _setQualityChangesGroup(self, quality_changes_group):
+ quality_type = quality_changes_group.quality_type
+ quality_group_dict = self._quality_manager.getQualityGroups(self._global_container_stack)
+ quality_group = quality_group_dict[quality_type]
+
+ quality_changes_container = self._empty_quality_changes_container
+ quality_container = self._empty_quality_changes_container
+ if quality_changes_group.node_for_global:
+ quality_changes_container = quality_changes_group.node_for_global.getContainer()
+ if quality_group.node_for_global:
+ quality_container = quality_group.node_for_global.getContainer()
+
+ self._global_container_stack.quality = quality_container
+ self._global_container_stack.qualityChanges = quality_changes_container
+
+ for position, extruder in self._global_container_stack.extruders.items():
+ quality_changes_node = quality_changes_group.nodes_for_extruders.get(position)
+ quality_node = quality_group.nodes_for_extruders.get(position)
+
+ quality_changes_container = self._empty_quality_changes_container
+ quality_container = self._empty_quality_changes_container
+ if quality_changes_node:
+ quality_changes_container = quality_changes_node.getContainer()
+ if quality_node:
+ quality_container = quality_node.getContainer()
+
+ extruder.quality = quality_container
+ extruder.qualityChanges = quality_changes_container
+
+ self._current_quality_group = quality_group
+ self._current_quality_changes_group = quality_changes_group
+ self.activeQualityGroupChanged.emit()
+ self.activeQualityChangesGroupChanged.emit()
+
+ def _setVariantNode(self, position, container_node):
+ self._global_container_stack.extruders[position].variant = container_node.getContainer()
+ self.activeVariantChanged.emit()
+
+ def _setGlobalVariant(self, container_node):
+ self._global_container_stack.variant = container_node.getContainer()
+
+ def _setMaterial(self, position, container_node = None):
+ if container_node:
+ self._global_container_stack.extruders[position].material = container_node.getContainer()
+ else:
+ self._global_container_stack.extruders[position].material = self._empty_material_container
+ # The _current_root_material_id is used in the MaterialMenu to see which material is selected
+ root_material_id = container_node.metadata["base_file"]
+ root_material_name = container_node.getContainer().getName()
+ if root_material_id != self._current_root_material_id[position]:
+ self._current_root_material_id[position] = root_material_id
+ self._current_root_material_name[position] = root_material_name
+ self.rootMaterialChanged.emit()
+
+ def activeMaterialsCompatible(self):
+ # check material - variant compatibility
+ if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
+ for position, extruder in self._global_container_stack.extruders.items():
+ if not extruder.material.getMetaDataEntry("compatible"):
+ return False
+ return True
+
+ ## Update current quality type and machine after setting material
+ def _updateQualityWithMaterial(self):
+ current_quality = None
+ if self._current_quality_group:
+ current_quality = self._current_quality_group.quality_type
+ quality_manager = Application.getInstance()._quality_manager
+ candidate_quality_groups = quality_manager.getQualityGroups(self._global_container_stack)
+ available_quality_types = {qt for qt, g in candidate_quality_groups.items() if g.is_available}
+
+ if not self.activeMaterialsCompatible():
+ self._setEmptyQuality()
+ return
+
+ if not available_quality_types:
+ self._setEmptyQuality()
+ return
+
+ if current_quality in available_quality_types:
+ self._setQualityGroup(candidate_quality_groups[current_quality], empty_quality_changes = False)
+ return
+
+ quality_type = sorted(list(available_quality_types))[0]
+ preferred_quality_type = self._global_container_stack.getMetaDataEntry("preferred_quality_type")
+ if preferred_quality_type in available_quality_types:
+ quality_type = preferred_quality_type
+
+ self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
+
+ def _updateMaterialWithVariant(self, position: Optional[str]):
+ if position is None:
+ position_list = list(self._global_container_stack.extruders.keys())
+ else:
+ position_list = [position]
+
+ for position in position_list:
+ extruder = self._global_container_stack.extruders[position]
+
+ current_material_base_name = extruder.material.getMetaDataEntry("base_file")
+ current_variant_name = extruder.variant.getMetaDataEntry("name")
+
+ material_manager = Application.getInstance()._material_manager
+ material_diameter = self._global_container_stack.getProperty("material_diameter", "value")
+ candidate_materials = material_manager.getAvailableMaterials(
+ self._global_container_stack.definition.getId(),
+ current_variant_name,
+ material_diameter)
+
+ if not candidate_materials:
+ self._setMaterial(position, container_node = None)
+ continue
+
+ if current_material_base_name in candidate_materials:
+ new_material = candidate_materials[current_material_base_name]
+ self._setMaterial(position, new_material)
+ continue
+
+ @pyqtSlot("QVariant")
+ def setGlobalVariant(self, container_node):
+ self.blurSettings.emit()
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ self._setGlobalVariant(container_node)
+ self._updateMaterialWithVariant(None) # Update all materials
+ self._updateQualityWithMaterial()
+
+ @pyqtSlot(str, "QVariant")
+ def setMaterial(self, position, container_node):
+ position = str(position)
+ self.blurSettings.emit()
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ self._setMaterial(position, container_node)
+ self._updateQualityWithMaterial()
+
+ @pyqtSlot(str, "QVariant")
+ def setVariantGroup(self, position, container_node):
+ position = str(position)
+ self.blurSettings.emit()
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ self._setVariantNode(position, container_node)
+ self._updateMaterialWithVariant(position)
+ self._updateQualityWithMaterial()
+
+ @pyqtSlot(QObject)
+ def setQualityGroup(self, quality_group):
+ self.blurSettings.emit()
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ self._setQualityGroup(quality_group)
+
+ # See if we need to show the Discard or Keep changes screen
+ if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
+ Application.getInstance().discardOrKeepProfileChanges()
+
+ @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
+ def activeQualityGroup(self):
+ return self._current_quality_group
+
+ @pyqtSlot(QObject)
+ def setQualityChangesGroup(self, quality_changes_group):
+ self.blurSettings.emit()
+ with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+ self._setQualityChangesGroup(quality_changes_group)
+
+ # See if we need to show the Discard or Keep changes screen
+ if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
+ Application.getInstance().discardOrKeepProfileChanges()
+
+ @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
+ def activeQualityChangesGroup(self):
+ return self._current_quality_changes_group
+
+ @pyqtProperty(str, notify = activeQualityGroupChanged)
+ def activeQualityOrQualityChangesName(self):
+ name = self._empty_quality_container.getName()
+ if self._current_quality_changes_group:
+ name = self._current_quality_changes_group.name
+ elif self._current_quality_group:
+ name = self._current_quality_group.name
+ return name
diff --git a/cura/Settings/MaterialManager.py b/cura/Settings/MaterialManager.py
deleted file mode 100644
index 80d2723438..0000000000
--- a/cura/Settings/MaterialManager.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from PyQt5.QtCore import QObject, pyqtSlot #To expose data to QML.
-
-from cura.Settings.ContainerManager import ContainerManager
-from UM.Logger import Logger
-from UM.Message import Message #To create a warning message about material diameter.
-from UM.i18n import i18nCatalog #Translated strings.
-
-catalog = i18nCatalog("cura")
-
-## Handles material-related data, processing requests to change them and
-# providing data for the GUI.
-#
-# TODO: Move material-related managing over from the machine manager to here.
-class MaterialManager(QObject):
- ## Creates the global values for the material manager to use.
- def __init__(self, parent = None):
- super().__init__(parent)
-
- #Material diameter changed warning message.
- self._material_diameter_warning_message = Message(catalog.i18nc("@info:status Has a cancel button next to it.",
- "The selected material diameter causes the material to become incompatible with the current printer."), title = catalog.i18nc("@info:title", "Incompatible Material"))
- self._material_diameter_warning_message.addAction("Undo", catalog.i18nc("@action:button", "Undo"), None, catalog.i18nc("@action", "Undo changing the material diameter."))
- self._material_diameter_warning_message.actionTriggered.connect(self._materialWarningMessageAction)
-
- ## Creates an instance of the MaterialManager.
- #
- # This should only be called by PyQt to create the singleton instance of
- # this class.
- @staticmethod
- def createMaterialManager(engine = None, script_engine = None):
- return MaterialManager()
-
- @pyqtSlot(str, str)
- def showMaterialWarningMessage(self, material_id, previous_diameter):
- self._material_diameter_warning_message.previous_diameter = previous_diameter #Make sure that the undo button can properly undo the action.
- self._material_diameter_warning_message.material_id = material_id
- self._material_diameter_warning_message.show()
-
- ## Called when clicking "undo" on the warning dialogue for disappeared
- # materials.
- #
- # This executes the undo action, restoring the material diameter.
- #
- # \param button The identifier of the button that was pressed.
- def _materialWarningMessageAction(self, message, button):
- if button == "Undo":
- container_manager = ContainerManager.getInstance()
- container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "properties/diameter", self._material_diameter_warning_message.previous_diameter)
- approximate_previous_diameter = str(round(float(self._material_diameter_warning_message.previous_diameter)))
- container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "approximate_diameter", approximate_previous_diameter)
- container_manager.setContainerProperty(self._material_diameter_warning_message.material_id, "material_diameter", "value", self._material_diameter_warning_message.previous_diameter);
- message.hide()
- else:
- Logger.log("w", "Unknown button action for material diameter warning message: {action}".format(action = button))
\ No newline at end of file
diff --git a/cura/Settings/MaterialSettingsVisibilityHandler.py b/cura/Settings/MaterialSettingsVisibilityHandler.py
index 5b6050d2c0..ce545f4551 100644
--- a/cura/Settings/MaterialSettingsVisibilityHandler.py
+++ b/cura/Settings/MaterialSettingsVisibilityHandler.py
@@ -3,13 +3,14 @@
import UM.Settings.Models.SettingVisibilityHandler
+
class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler):
def __init__(self, parent = None, *args, **kwargs):
super().__init__(parent = parent, *args, **kwargs)
material_settings = {
"default_material_print_temperature",
- "material_bed_temperature",
+ "default_material_bed_temperature",
"material_standby_temperature",
#"material_flow_temp_graph",
"cool_fan_speed",
diff --git a/cura/Settings/MaterialsModel.py b/cura/Settings/MaterialsModel.py
deleted file mode 100644
index c4b0329336..0000000000
--- a/cura/Settings/MaterialsModel.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from typing import Any, List
-from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials.
-from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class.
-
-## A model that shows a list of currently valid materials.
-class MaterialsModel(InstanceContainersModel):
- def __init__(self, parent = None):
- super().__init__(parent)
-
- ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerMetaDataChanged)
-
- ## Called when the metadata of the container was changed.
- #
- # This makes sure that we only update when it was a material that changed.
- #
- # \param container The container whose metadata was changed.
- def _onContainerMetaDataChanged(self, container):
- if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
- self._container_change_timer.start()
-
- def _onContainerChanged(self, container):
- if container.getMetaDataEntry("type", "") == "material":
- super()._onContainerChanged(container)
-
- ## Group brand together
- def _sortKey(self, item) -> List[Any]:
- result = []
- result.append(item["metadata"]["brand"])
- result.append(item["metadata"]["material"])
- result.append(item["metadata"]["name"])
- result.append(item["metadata"]["color_name"])
- result.append(item["metadata"]["id"])
- result.extend(super()._sortKey(item))
- return result
diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py
deleted file mode 100644
index 3b43e2740a..0000000000
--- a/cura/Settings/ProfilesModel.py
+++ /dev/null
@@ -1,224 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from collections import OrderedDict
-
-from PyQt5.QtCore import Qt
-
-from UM.Application import Application
-from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel
-
-from cura.QualityManager import QualityManager
-from cura.Settings.ExtruderManager import ExtruderManager
-
-from typing import List, TYPE_CHECKING
-
-if TYPE_CHECKING:
- from cura.Settings.ExtruderStack import ExtruderStack
-
-
-## QML Model for listing the current list of valid quality profiles.
-#
-class ProfilesModel(InstanceContainersModel):
- LayerHeightRole = Qt.UserRole + 1001
- LayerHeightWithoutUnitRole = Qt.UserRole + 1002
- AvailableRole = Qt.UserRole + 1003
-
- def __init__(self, parent = None):
- super().__init__(parent)
- self.addRoleName(self.LayerHeightRole, "layer_height")
- self.addRoleName(self.LayerHeightWithoutUnitRole, "layer_height_without_unit")
- self.addRoleName(self.AvailableRole, "available")
-
- Application.getInstance().globalContainerStackChanged.connect(self._update)
- Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
- Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
- Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
-
- # Factory function, used by QML
- @staticmethod
- def createProfilesModel(engine, js_engine):
- return ProfilesModel.getInstance()
-
- ## Get the singleton instance for this class.
- @classmethod
- def getInstance(cls) -> "ProfilesModel":
- # Note: Explicit use of class name to prevent issues with inheritance.
- if not ProfilesModel.__instance:
- ProfilesModel.__instance = cls()
- return ProfilesModel.__instance
-
- @classmethod
- def hasInstance(cls) -> bool:
- return ProfilesModel.__instance is not None
-
- __instance = None # type: "ProfilesModel"
-
- ## Fetch the list of containers to display.
- #
- # See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
- def _fetchInstanceContainers(self):
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack is None:
- return {}, {}
- global_stack_definition = global_container_stack.definition
-
- # Get the list of extruders and place the selected extruder at the front of the list.
- extruder_stacks = self._getOrderedExtruderStacksList()
- materials = [extruder.material for extruder in extruder_stacks]
-
- # Fetch the list of usable qualities across all extruders.
- # The actual list of quality profiles come from the first extruder in the extruder list.
- result = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
-
- # The usable quality types are set
- quality_type_set = set([x.getMetaDataEntry("quality_type") for x in result])
-
- # Fetch all qualities available for this machine and the materials selected in extruders
- all_qualities = QualityManager.getInstance().findAllQualitiesForMachineAndMaterials(global_stack_definition, materials)
-
- # If in the all qualities there is some of them that are not available due to incompatibility with materials
- # we also add it so that they will appear in the slide quality bar. However in recomputeItems will be marked as
- # not available so they will be shown in gray
- for quality in all_qualities:
- if quality.getMetaDataEntry("quality_type") not in quality_type_set:
- result.append(quality)
-
- # if still profiles are found, add a single empty_quality ("Not supported") instance to the drop down list
- if len(result) == 0:
- # If not qualities are found we dynamically create a not supported container for this machine + material combination
- not_supported_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
- result.append(not_supported_container)
-
- return {item.getId():item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
-
- ## Re-computes the items in this model, and adds the layer height role.
- def _recomputeItems(self):
- # Some globals that we can re-use.
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack is None:
- return
-
- extruder_stacks = self._getOrderedExtruderStacksList()
- container_registry = ContainerRegistry.getInstance()
-
- # Get a list of usable/available qualities for this machine and material
- qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
-
- unit = global_container_stack.getBottom().getProperty("layer_height", "unit")
- if not unit:
- unit = ""
-
- # group all quality items according to quality_types, so we know which profile suits the currently
- # active machine and material, and later yield the right ones.
- tmp_all_quality_items = OrderedDict()
- for item in super()._recomputeItems():
-
- profiles = container_registry.findContainersMetadata(id = item["id"])
- if not profiles or "quality_type" not in profiles[0]:
- quality_type = ""
- else:
- quality_type = profiles[0]["quality_type"]
-
- if quality_type not in tmp_all_quality_items:
- tmp_all_quality_items[quality_type] = {"suitable_container": None, "all_containers": []}
-
- tmp_all_quality_items[quality_type]["all_containers"].append(item)
- if tmp_all_quality_items[quality_type]["suitable_container"] is None:
- tmp_all_quality_items[quality_type]["suitable_container"] = item
-
- # reverse the ordering (finest first, coarsest last)
- all_quality_items = OrderedDict()
- for key in reversed(tmp_all_quality_items.keys()):
- all_quality_items[key] = tmp_all_quality_items[key]
-
- # First the suitable containers are set in the model
- containers = []
- for data_item in all_quality_items.values():
- suitable_item = data_item["suitable_container"]
- if suitable_item is not None:
- containers.append(suitable_item)
-
- # Once the suitable containers are collected, the rest of the containers are appended
- for data_item in all_quality_items.values():
- for item in data_item["all_containers"]:
- if item not in containers:
- containers.append(item)
-
- # Now all the containers are set
- for item in containers:
- profile = container_registry.findContainers(id = item["id"])
-
- # When for some reason there is no profile container in the registry
- if not profile:
- self._setItemLayerHeight(item, "", "")
- item["available"] = False
- yield item
- continue
-
- profile = profile[0]
-
- # When there is a profile but it's an empty quality should. It's shown in the list (they are "Not Supported" profiles)
- if profile.getId() == "empty_quality":
- self._setItemLayerHeight(item, "", "")
- item["available"] = True
- yield item
- continue
-
- item["available"] = profile in qualities
-
- # Easy case: This profile defines its own layer height.
- if profile.hasProperty("layer_height", "value"):
- self._setItemLayerHeight(item, profile.getProperty("layer_height", "value"), unit)
- yield item
- continue
-
- machine_manager = Application.getInstance().getMachineManager()
-
- # Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile.
- quality_type = profile.getMetaDataEntry("quality_type", None)
- if quality_type:
- quality_results = machine_manager.determineQualityAndQualityChangesForQualityType(quality_type)
- for quality_result in quality_results:
- if quality_result["stack"] is global_container_stack:
- quality = quality_result["quality"]
- break
- else:
- # No global container stack in the results:
- if quality_results:
- # Take any of the extruders.
- quality = quality_results[0]["quality"]
- else:
- quality = None
- if quality and quality.hasProperty("layer_height", "value"):
- self._setItemLayerHeight(item, quality.getProperty("layer_height", "value"), unit)
- yield item
- continue
-
- # Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
- skip_until_container = global_container_stack.material
- if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No material in stack.
- skip_until_container = global_container_stack.variant
- if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No variant in stack.
- skip_until_container = global_container_stack.getBottom()
- self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material.
- yield item
-
- ## Get a list of extruder stacks with the active extruder at the front of the list.
- @staticmethod
- def _getOrderedExtruderStacksList() -> List["ExtruderStack"]:
- extruder_manager = ExtruderManager.getInstance()
- extruder_stacks = extruder_manager.getActiveExtruderStacks()
- active_extruder = extruder_manager.getActiveExtruderStack()
-
- if active_extruder in extruder_stacks:
- extruder_stacks.remove(active_extruder)
- extruder_stacks = [active_extruder] + extruder_stacks
-
- return extruder_stacks
-
- @staticmethod
- def _setItemLayerHeight(item, value, unit):
- item["layer_height"] = str(value) + unit
- item["layer_height_without_unit"] = str(value)
diff --git a/cura/Settings/QualityAndUserProfilesModel.py b/cura/Settings/QualityAndUserProfilesModel.py
deleted file mode 100644
index bc81df976b..0000000000
--- a/cura/Settings/QualityAndUserProfilesModel.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (c) 2016 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-from UM.Application import Application
-
-from cura.QualityManager import QualityManager
-from cura.Settings.ProfilesModel import ProfilesModel
-from cura.Settings.ExtruderManager import ExtruderManager
-
-## QML Model for listing the current list of valid quality and quality changes profiles.
-#
-class QualityAndUserProfilesModel(ProfilesModel):
- def __init__(self, parent = None):
- super().__init__(parent)
-
- ## Fetch the list of containers to display.
- #
- # See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
- def _fetchInstanceContainers(self):
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if not global_container_stack:
- return {}, {}
-
- # Fetch the list of quality changes.
- quality_manager = QualityManager.getInstance()
- machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
- quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
-
- extruder_manager = ExtruderManager.getInstance()
- active_extruder = extruder_manager.getActiveExtruderStack()
- extruder_stacks = self._getOrderedExtruderStacksList()
-
- # Fetch the list of usable qualities across all extruders.
- # The actual list of quality profiles come from the first extruder in the extruder list.
- quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
-
- # Filter the quality_change by the list of available quality_types
- quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
- filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
- qc.getMetaDataEntry("quality_type") in quality_type_set and
- qc.getMetaDataEntry("extruder") is not None and
- (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
- qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
-
- result = filtered_quality_changes
- result.update({q.getId():q for q in quality_list})
- return result, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
\ No newline at end of file
diff --git a/cura/Settings/QualitySettingsModel.py b/cura/Settings/QualitySettingsModel.py
deleted file mode 100644
index 243fd146dc..0000000000
--- a/cura/Settings/QualitySettingsModel.py
+++ /dev/null
@@ -1,240 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-import collections
-
-from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
-
-from UM.Logger import Logger
-import UM.Qt
-from UM.Application import Application
-from UM.Settings.ContainerRegistry import ContainerRegistry
-import os
-
-from UM.i18n import i18nCatalog
-
-
-class QualitySettingsModel(UM.Qt.ListModel.ListModel):
- KeyRole = Qt.UserRole + 1
- LabelRole = Qt.UserRole + 2
- UnitRole = Qt.UserRole + 3
- ProfileValueRole = Qt.UserRole + 4
- ProfileValueSourceRole = Qt.UserRole + 5
- UserValueRole = Qt.UserRole + 6
- CategoryRole = Qt.UserRole + 7
-
- def __init__(self, parent = None):
- super().__init__(parent = parent)
-
- self._container_registry = ContainerRegistry.getInstance()
-
- self._extruder_id = None
- self._extruder_definition_id = None
- self._quality_id = None
- self._material_id = None
- self._i18n_catalog = None
-
- self.addRoleName(self.KeyRole, "key")
- self.addRoleName(self.LabelRole, "label")
- self.addRoleName(self.UnitRole, "unit")
- self.addRoleName(self.ProfileValueRole, "profile_value")
- self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
- self.addRoleName(self.UserValueRole, "user_value")
- self.addRoleName(self.CategoryRole, "category")
-
- def setExtruderId(self, extruder_id):
- if extruder_id != self._extruder_id:
- self._extruder_id = extruder_id
- self._update()
- self.extruderIdChanged.emit()
-
- extruderIdChanged = pyqtSignal()
- @pyqtProperty(str, fset = setExtruderId, notify = extruderIdChanged)
- def extruderId(self):
- return self._extruder_id
-
- def setExtruderDefinition(self, extruder_definition):
- if extruder_definition != self._extruder_definition_id:
- self._extruder_definition_id = extruder_definition
- self._update()
- self.extruderDefinitionChanged.emit()
-
- extruderDefinitionChanged = pyqtSignal()
- @pyqtProperty(str, fset = setExtruderDefinition, notify = extruderDefinitionChanged)
- def extruderDefinition(self):
- return self._extruder_definition_id
-
- def setQuality(self, quality):
- if quality != self._quality_id:
- self._quality_id = quality
- self._update()
- self.qualityChanged.emit()
-
- qualityChanged = pyqtSignal()
- @pyqtProperty(str, fset = setQuality, notify = qualityChanged)
- def quality(self):
- return self._quality_id
-
- def setMaterial(self, material):
- if material != self._material_id:
- self._material_id = material
- self._update()
- self.materialChanged.emit()
-
- materialChanged = pyqtSignal()
- @pyqtProperty(str, fset = setMaterial, notify = materialChanged)
- def material(self):
- return self._material_id
-
- def _update(self):
- if not self._quality_id:
- return
-
- items = []
-
- definition_container = Application.getInstance().getGlobalContainerStack().getBottom()
-
- containers = self._container_registry.findInstanceContainers(id = self._quality_id)
- if not containers:
- Logger.log("w", "Could not find a quality container with id %s", self._quality_id)
- return
-
- quality_container = None
- quality_changes_container = None
-
- if containers[0].getMetaDataEntry("type") == "quality":
- quality_container = containers[0]
- else:
- quality_changes_container = containers[0]
-
- criteria = {
- "type": "quality",
- "quality_type": quality_changes_container.getMetaDataEntry("quality_type"),
- "definition": quality_changes_container.getDefinition().getId()
- }
-
- quality_container = self._container_registry.findInstanceContainers(**criteria)
- if not quality_container:
- Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId())
- return
- quality_container = quality_container[0]
-
- quality_type = quality_container.getMetaDataEntry("quality_type")
- definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition())
- definition = quality_container.getDefinition()
-
- # Check if the definition container has a translation file.
- definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix
- catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix))
- if catalog.hasTranslationLoaded():
- self._i18n_catalog = catalog
-
- for file_name in quality_container.getDefinition().getInheritedFiles():
- catalog = i18nCatalog(os.path.basename(file_name))
- if catalog.hasTranslationLoaded():
- self._i18n_catalog = catalog
-
- criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
-
- if self._material_id and self._material_id != "empty_material":
- criteria["material"] = self._material_id
-
- criteria["extruder"] = self._extruder_id
-
- containers = self._container_registry.findInstanceContainers(**criteria)
- if not containers:
- # Try again, this time without extruder
- new_criteria = criteria.copy()
- new_criteria.pop("extruder")
- containers = self._container_registry.findInstanceContainers(**new_criteria)
-
- if not containers and "material" in criteria:
- # Try again, this time without material
- criteria.pop("material", None)
- containers = self._container_registry.findInstanceContainers(**criteria)
-
- if not containers:
- # Try again, this time without material or extruder
- criteria.pop("extruder") # "material" has already been popped
- containers = self._container_registry.findInstanceContainers(**criteria)
-
- if not containers:
- Logger.log("w", "Could not find any quality containers matching the search criteria %s" % str(criteria))
- return
-
- if quality_changes_container:
- criteria = {"type": "quality_changes", "quality_type": quality_type, "definition": definition_id, "name": quality_changes_container.getName()}
- if self._extruder_definition_id != "":
- extruder_definitions = self._container_registry.findDefinitionContainers(id = self._extruder_definition_id)
- if extruder_definitions:
- criteria["extruder"] = Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0])
- criteria["name"] = quality_changes_container.getName()
- else:
- criteria["extruder"] = None
-
- changes = self._container_registry.findInstanceContainers(**criteria)
- if changes:
- containers.extend(changes)
-
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- is_multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
-
- current_category = ""
- for definition in definition_container.findDefinitions():
- if definition.type == "category":
- current_category = definition.label
- if self._i18n_catalog:
- current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
- continue
-
- profile_value = None
- profile_value_source = ""
- for container in containers:
- new_value = container.getProperty(definition.key, "value")
-
- if new_value is not None:
- profile_value_source = container.getMetaDataEntry("type")
- profile_value = new_value
-
- # Global tab should use resolve (if there is one)
- if not self._extruder_id:
- resolve_value = global_container_stack.getProperty(definition.key, "resolve")
- if resolve_value is not None and profile_value is not None and profile_value_source != "quality_changes":
- profile_value = resolve_value
-
- user_value = None
- if not self._extruder_id:
- user_value = global_container_stack.getTop().getProperty(definition.key, "value")
- else:
- extruder_stack = self._container_registry.findContainerStacks(id = self._extruder_id)
- if extruder_stack:
- user_value = extruder_stack[0].getTop().getProperty(definition.key, "value")
-
- if profile_value is None and user_value is None:
- continue
-
- if is_multi_extrusion:
- settable_per_extruder = global_container_stack.getProperty(definition.key, "settable_per_extruder")
- # If a setting is not settable per extruder (global) and we're looking at an extruder tab, don't show this value.
- if self._extruder_id != "" and not settable_per_extruder:
- continue
-
- # If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value.
- if self._extruder_id == "" and settable_per_extruder:
- continue
-
- label = definition.label
- if self._i18n_catalog:
- label = self._i18n_catalog.i18nc(definition.key + " label", label)
-
- items.append({
- "key": definition.key,
- "label": label,
- "unit": definition.unit,
- "profile_value": "" if profile_value is None else str(profile_value), # it is for display only
- "profile_value_source": profile_value_source,
- "user_value": "" if user_value is None else str(user_value),
- "category": current_category
- })
-
- self.setItems(items)
diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py
index c70d9343cf..6e98f014dc 100644
--- a/cura/Settings/SettingOverrideDecorator.py
+++ b/cura/Settings/SettingOverrideDecorator.py
@@ -3,12 +3,14 @@
import copy
+from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Signal import Signal, signalemitter
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Logger import Logger
-
+from UM.Settings.Validator import ValidatorState
+from PyQt5.QtCore import QTimer
from UM.Application import Application
from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
@@ -32,14 +34,20 @@ class SettingOverrideDecorator(SceneNodeDecorator):
def __init__(self):
super().__init__()
- self._stack = PerObjectContainerStack(stack_id = id(self))
+ self._stack = PerObjectContainerStack(stack_id = "per_object_stack_" + str(id(self)))
self._stack.setDirty(False) # This stack does not need to be saved.
self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer"))
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
+ self._is_non_printing_mesh = False
+ self._error_check_timer = QTimer()
+ self._error_check_timer.setInterval(250)
+ self._error_check_timer.setSingleShot(True)
+ self._error_check_timer.timeout.connect(self._checkStackForErrors)
+
self._stack.propertyChanged.connect(self._onSettingChanged)
- ContainerRegistry.getInstance().addContainer(self._stack)
+ Application.getInstance().getContainerRegistry().addContainer(self._stack)
Application.getInstance().globalContainerStackChanged.connect(self._updateNextStack)
self.activeExtruderChanged.connect(self._updateNextStack)
@@ -57,6 +65,10 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# Properly set the right extruder on the copy
deep_copy.setActiveExtruder(self._extruder_stack)
+ # use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh"
+ # has not been updated yet.
+ deep_copy._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
+
return deep_copy
## Gets the currently active extruder to print this object with.
@@ -80,13 +92,28 @@ class SettingOverrideDecorator(SceneNodeDecorator):
container_stack = containers[0]
return container_stack.getMetaDataEntry("position", default=None)
+ def isNonPrintingMesh(self):
+ return self._is_non_printing_mesh
+
def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function
# Trigger slice/need slicing if the value has changed.
if property_name == "value":
- Application.getInstance().getBackend().needsSlicing()
- Application.getInstance().getBackend().tickle()
+ self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
+ if not self._is_non_printing_mesh:
+ # self._error_check_timer.start()
+ self._checkStackForErrors()
+ Application.getInstance().getBackend().needsSlicing()
+ Application.getInstance().getBackend().tickle()
- self._node._non_printing_mesh = any(self._stack.getProperty(setting, "value") for setting in self._non_printing_mesh_settings)
+ def _checkStackForErrors(self):
+ hasErrors = False;
+ for key in self._stack.getAllKeys():
+ validation_state = self._stack.getProperty(key, "validationState")
+ if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
+ Logger.log("w", "Setting Per Object %s is not valid.", key)
+ hasErrors = True
+ break
+ Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
## Makes sure that the stack upon which the container stack is placed is
# kept up to date.
diff --git a/cura/Settings/UserProfilesModel.py b/cura/Settings/UserProfilesModel.py
deleted file mode 100644
index e093c6c132..0000000000
--- a/cura/Settings/UserProfilesModel.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from UM.Application import Application
-from cura.QualityManager import QualityManager
-from cura.Settings.ProfilesModel import ProfilesModel
-from cura.Settings.ExtruderManager import ExtruderManager
-
-## QML Model for listing the current list of valid quality changes profiles.
-#
-class UserProfilesModel(ProfilesModel):
- def __init__(self, parent = None):
- super().__init__(parent)
-
- #Need to connect to the metaDataChanged signal of the active materials.
- self.__current_extruders = []
- self.__current_materials = []
-
- Application.getInstance().getExtruderManager().extrudersChanged.connect(self.__onExtrudersChanged)
- self.__onExtrudersChanged()
- self.__current_materials = [extruder.material for extruder in self.__current_extruders]
- for material in self.__current_materials:
- material.metaDataChanged.connect(self._onContainerChanged)
-
- ## Fetch the list of containers to display.
- #
- # See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
- def _fetchInstanceContainers(self):
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if not global_container_stack:
- return {}, {}
-
- # Fetch the list of quality changes.
- quality_manager = QualityManager.getInstance()
- machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
- quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
-
- extruder_manager = ExtruderManager.getInstance()
- active_extruder = extruder_manager.getActiveExtruderStack()
- extruder_stacks = self._getOrderedExtruderStacksList()
-
- # Fetch the list of usable qualities across all extruders.
- # The actual list of quality profiles come from the first extruder in the extruder list.
- quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
-
- # Filter the quality_change by the list of available quality_types
- quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
-
- filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
- qc.getMetaDataEntry("quality_type") in quality_type_set and
- qc.getMetaDataEntry("extruder") is not None and
- (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
- qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
-
- return filtered_quality_changes, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
-
- ## Called when a container changed on an extruder stack.
- #
- # If it's the material we need to connect to the metaDataChanged signal of
- # that.
- def __onContainerChanged(self, new_container):
- #Careful not to update when a quality or quality changes profile changed!
- #If you then update you're going to have an infinite recursion because the update may change the container.
- if new_container.getMetaDataEntry("type") == "material":
- for material in self.__current_materials:
- material.metaDataChanged.disconnect(self._onContainerChanged)
- self.__current_materials = [extruder.material for extruder in self.__current_extruders]
- for material in self.__current_materials:
- material.metaDataChanged.connect(self._onContainerChanged)
-
- ## Called when the current set of extruders change.
- #
- # This makes sure that we are listening to the signal for when the
- # materials change.
- def __onExtrudersChanged(self):
- for extruder in self.__current_extruders:
- extruder.containersChanged.disconnect(self.__onContainerChanged)
- self.__current_extruders = Application.getInstance().getExtruderManager().getExtruderStacks()
- for extruder in self.__current_extruders:
- extruder.containersChanged.connect(self.__onContainerChanged)
\ No newline at end of file
diff --git a/cura/Snapshot.py b/cura/Snapshot.py
new file mode 100644
index 0000000000..2a2a49d6cf
--- /dev/null
+++ b/cura/Snapshot.py
@@ -0,0 +1,116 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+import numpy
+
+from PyQt5 import QtCore
+from PyQt5.QtGui import QImage
+
+from cura.PreviewPass import PreviewPass
+from cura.Scene import ConvexHullNode
+
+from UM.Application import Application
+from UM.Math.AxisAlignedBox import AxisAlignedBox
+from UM.Math.Matrix import Matrix
+from UM.Math.Vector import Vector
+from UM.Mesh.MeshData import transformVertices
+from UM.Scene.Camera import Camera
+from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
+
+
+class Snapshot:
+ @staticmethod
+ def getImageBoundaries(image: QImage):
+ # Look at the resulting image to get a good crop.
+ # Get the pixels as byte array
+ pixel_array = image.bits().asarray(image.byteCount())
+ width, height = image.width(), image.height()
+ # Convert to numpy array, assume it's 32 bit (it should always be)
+ pixels = numpy.frombuffer(pixel_array, dtype=numpy.uint8).reshape([height, width, 4])
+ # Find indices of non zero pixels
+ nonzero_pixels = numpy.nonzero(pixels)
+ min_y, min_x, min_a_ = numpy.amin(nonzero_pixels, axis=1)
+ max_y, max_x, max_a_ = numpy.amax(nonzero_pixels, axis=1)
+
+ return min_x, max_x, min_y, max_y
+
+ ## Return a QImage of the scene
+ # Uses PreviewPass that leaves out some elements
+ # Aspect ratio assumes a square
+ @staticmethod
+ def snapshot(width = 300, height = 300):
+ scene = Application.getInstance().getController().getScene()
+ active_camera = scene.getActiveCamera()
+ render_width, render_height = active_camera.getWindowSize()
+ render_width = int(render_width)
+ render_height = int(render_height)
+ preview_pass = PreviewPass(render_width, render_height)
+
+ root = scene.getRoot()
+ camera = Camera("snapshot", root)
+
+ # determine zoom and look at
+ bbox = None
+ for node in DepthFirstIterator(root):
+ if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
+ if bbox is None:
+ bbox = node.getBoundingBox()
+ else:
+ bbox = bbox + node.getBoundingBox()
+
+ # If there is no bounding box, it means that there is no model in the buildplate
+ if bbox is None:
+ return None
+
+ look_at = bbox.center
+ # guessed size so the objects are hopefully big
+ size = max(bbox.width, bbox.height, bbox.depth * 0.5)
+
+ # Looking from this direction (x, y, z) in OGL coordinates
+ looking_from_offset = Vector(1, 1, 2)
+ if size > 0:
+ # determine the watch distance depending on the size
+ looking_from_offset = looking_from_offset * size * 1.3
+ camera.setPosition(look_at + looking_from_offset)
+ camera.lookAt(look_at)
+
+ satisfied = False
+ size = None
+ fovy = 30
+
+ while not satisfied:
+ if size is not None:
+ satisfied = True # always be satisfied after second try
+ projection_matrix = Matrix()
+ # Somehow the aspect ratio is also influenced in reverse by the screen width/height
+ # So you have to set it to render_width/render_height to get 1
+ projection_matrix.setPerspective(fovy, render_width / render_height, 1, 500)
+ camera.setProjectionMatrix(projection_matrix)
+ preview_pass.setCamera(camera)
+ preview_pass.render()
+ pixel_output = preview_pass.getOutput()
+
+ min_x, max_x, min_y, max_y = Snapshot.getImageBoundaries(pixel_output)
+
+ size = max((max_x - min_x) / render_width, (max_y - min_y) / render_height)
+ if size > 0.5 or satisfied:
+ satisfied = True
+ else:
+ # make it big and allow for some empty space around
+ fovy *= 0.5 # strangely enough this messes up the aspect ratio: fovy *= size * 1.1
+
+ # make it a square
+ if max_x - min_x >= max_y - min_y:
+ # make y bigger
+ min_y, max_y = int((max_y + min_y) / 2 - (max_x - min_x) / 2), int((max_y + min_y) / 2 + (max_x - min_x) / 2)
+ else:
+ # make x bigger
+ min_x, max_x = int((max_x + min_x) / 2 - (max_y - min_y) / 2), int((max_x + min_x) / 2 + (max_y - min_y) / 2)
+ cropped_image = pixel_output.copy(min_x, min_y, max_x - min_x, max_y - min_y)
+
+ # Scale it to the correct size
+ scaled_image = cropped_image.scaled(
+ width, height,
+ aspectRatioMode = QtCore.Qt.IgnoreAspectRatio,
+ transformMode = QtCore.Qt.SmoothTransformation)
+
+ return scaled_image
diff --git a/cura_app.py b/cura_app.py
index b5844055ab..6c2d1c2937 100755
--- a/cura_app.py
+++ b/cura_app.py
@@ -16,6 +16,12 @@ parser.add_argument('--debug',
default = False,
help = "Turn on the debug mode by setting this option."
)
+parser.add_argument('--trigger-early-crash',
+ dest = 'trigger_early_crash',
+ action = 'store_true',
+ default = False,
+ help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog."
+ )
known_args = vars(parser.parse_known_args()[0])
if not known_args["debug"]:
@@ -26,12 +32,12 @@ if not known_args["debug"]:
return os.path.expanduser("~/.local/share/cura")
elif Platform.isOSX():
return os.path.expanduser("~/Library/Logs/cura")
-
+
if hasattr(sys, "frozen"):
dirpath = get_cura_dir_path()
os.makedirs(dirpath, exist_ok = True)
- sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
- sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
+ sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")
+ sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w", encoding = "utf-8")
import platform
import faulthandler
@@ -40,11 +46,11 @@ import faulthandler
if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
# For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
linux_distro_name = platform.linux_distribution()[0].lower()
- if linux_distro_name in ("debian", "ubuntu", "linuxmint", "fedora"): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
- import ctypes
- from ctypes.util import find_library
- libGL = find_library("GL")
- ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL)
+ # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
+ import ctypes
+ from ctypes.util import find_library
+ libGL = find_library("GL")
+ ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL)
# When frozen, i.e. installer version, don't let PYTHONPATH mess up the search path for DLLs.
if Platform.isWindows() and hasattr(sys, "frozen"):
@@ -71,8 +77,45 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u
def exceptHook(hook_type, value, traceback):
from cura.CrashHandler import CrashHandler
- _crash_handler = CrashHandler(hook_type, value, traceback)
- _crash_handler.show()
+ from cura.CuraApplication import CuraApplication
+ has_started = False
+ if CuraApplication.Created:
+ has_started = CuraApplication.getInstance().started
+
+ #
+ # When the exception hook is triggered, the QApplication may not have been initialized yet. In this case, we don't
+ # have an QApplication to handle the event loop, which is required by the Crash Dialog.
+ # The flag "CuraApplication.Created" is set to True when CuraApplication finishes its constructor call.
+ #
+ # Before the "started" flag is set to True, the Qt event loop has not started yet. The event loop is a blocking
+ # call to the QApplication.exec_(). In this case, we need to:
+ # 1. Remove all scheduled events so no more unnecessary events will be processed, such as loading the main dialog,
+ # loading the machine, etc.
+ # 2. Start the Qt event loop with exec_() and show the Crash Dialog.
+ #
+ # If the application has finished its initialization and was running fine, and then something causes a crash,
+ # we run the old routine to show the Crash Dialog.
+ #
+ from PyQt5.Qt import QApplication
+ if CuraApplication.Created:
+ _crash_handler = CrashHandler(hook_type, value, traceback, has_started)
+ if CuraApplication.splash is not None:
+ CuraApplication.splash.close()
+ if not has_started:
+ CuraApplication.getInstance().removePostedEvents(None)
+ _crash_handler.early_crash_dialog.show()
+ sys.exit(CuraApplication.getInstance().exec_())
+ else:
+ _crash_handler.show()
+ else:
+ application = QApplication(sys.argv)
+ application.removePostedEvents(None)
+ _crash_handler = CrashHandler(hook_type, value, traceback, has_started)
+ # This means the QtApplication could be created and so the splash screen. Then Cura closes it
+ if CuraApplication.splash is not None:
+ CuraApplication.splash.close()
+ _crash_handler.early_crash_dialog.show()
+ sys.exit(application.exec_())
if not known_args["debug"]:
sys.excepthook = exceptHook
diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py
index 89439454f8..ec590a0212 100755
--- a/plugins/3MFReader/ThreeMFReader.py
+++ b/plugins/3MFReader/ThreeMFReader.py
@@ -1,29 +1,31 @@
-# Copyright (c) 2015 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
import zipfile
-from UM.Job import Job
+import numpy
+
+import Savitar
+
+from UM.Application import Application
from UM.Logger import Logger
from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator
+
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
-from UM.Application import Application
from cura.Settings.ExtruderManager import ExtruderManager
-from cura.QualityManager import QualityManager
-from UM.Scene.SceneNode import SceneNode
-from cura.SliceableObjectDecorator import SliceableObjectDecorator
-from cura.ZOffsetDecorator import ZOffsetDecorator
+from cura.Scene.CuraSceneNode import CuraSceneNode
+from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
+from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
+from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
+from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False
-import Savitar
-import numpy
-
try:
if not MYPY:
import xml.etree.cElementTree as ET
@@ -37,12 +39,9 @@ class ThreeMFReader(MeshReader):
super().__init__()
self._supported_extensions = [".3mf"]
self._root = None
- self._namespaces = {
- "3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
- "cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10"
- }
self._base_name = ""
self._unit = None
+ self._object_count = 0 # Used to name objects as there is no node name yet.
def _createMatrixFromTransformationString(self, transformation):
if transformation == "":
@@ -77,7 +76,14 @@ class ThreeMFReader(MeshReader):
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a Uranium scene node.
# \returns Uranium scene node.
def _convertSavitarNodeToUMNode(self, savitar_node):
- um_node = SceneNode()
+ self._object_count += 1
+ node_name = "Object %s" % self._object_count
+
+ active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+
+ um_node = CuraSceneNode()
+ um_node.addDecorator(BuildPlateDecorator(active_build_plate))
+ um_node.setName(node_name)
transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
um_node.setTransformation(transformation)
mesh_builder = MeshBuilder()
@@ -116,8 +122,8 @@ class ThreeMFReader(MeshReader):
um_node.callDecoration("setActiveExtruder", default_stack.getId())
# Get the definition & set it
- definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())
- um_node.callDecoration("getStack").getTop().setDefinition(definition.getId())
+ definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack)
+ um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
setting_container = um_node.callDecoration("getStack").getTop()
@@ -147,6 +153,7 @@ class ThreeMFReader(MeshReader):
def read(self, file_name):
result = []
+ self._object_count = 0 # Used to name objects as there is no node name yet.
# The base object of 3mf is a zipped archive.
try:
archive = zipfile.ZipFile(file_name, "r")
diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py
index 16fd2ee93c..cbe882f253 100755
--- a/plugins/3MFReader/ThreeMFWorkspaceReader.py
+++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py
@@ -23,17 +23,49 @@ from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.CuraContainerStack import _ContainerIndexes
-from cura.QualityManager import QualityManager
+from cura.CuraApplication import CuraApplication
from configparser import ConfigParser
import zipfile
import io
import configparser
import os
+import threading
i18n_catalog = i18nCatalog("cura")
+#
+# HACK:
+#
+# In project loading, when override the existing machine is selected, the stacks and containers that are correctly
+# active in the system will be overridden at runtime. Because the project loading is done in a different thread than
+# the Qt thread, something else can kick in the middle of the process. One of them is the rendering. It will access
+# the current stacks and container, which have not completely been updated yet, so Cura will crash in this case.
+#
+# This "@call_on_qt_thread" decorator makes sure that a function will always be called on the Qt thread (blocking).
+# It is applied to the read() function of project loading so it can be guaranteed that only after the project loading
+# process is completely done, everything else that needs to occupy the QT thread will be executed.
+#
+class InterCallObject:
+ def __init__(self):
+ self.finish_event = threading.Event()
+ self.result = None
+
+
+def call_on_qt_thread(func):
+ def _call_on_qt_thread_wrapper(*args, **kwargs):
+ def _handle_call(ico, *args, **kwargs):
+ ico.result = func(*args, **kwargs)
+ ico.finish_event.set()
+ inter_call_object = InterCallObject()
+ new_args = tuple([inter_call_object] + list(args)[:])
+ CuraApplication.getInstance().callLater(_handle_call, *new_args, **kwargs)
+ inter_call_object.finish_event.wait()
+ return inter_call_object.result
+ return _call_on_qt_thread_wrapper
+
+
## Base implementation for reading 3MF workspace files.
class ThreeMFWorkspaceReader(WorkspaceReader):
def __init__(self):
@@ -89,7 +121,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# The default ContainerStack.deserialize() will connect signals, which is not desired in this case.
# Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them.
serialized = archive.open(file_name).read().decode("utf-8")
- stack_config = ConfigParser()
+ stack_config = ConfigParser(interpolation = None)
stack_config.read_string(serialized)
# sanity check
@@ -168,11 +200,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "Unknown definition container type %s for %s",
definition_container_type, each_definition_container_file)
Job.yieldThread()
- # sanity check
+
if machine_definition_container_count != 1:
- msg = "Expecting one machine definition container but got %s" % machine_definition_container_count
- Logger.log("e", msg)
- raise RuntimeError(msg)
+ return WorkspaceReader.PreReadResult.failed #Not a workspace file but ordinary 3MF.
material_labels = []
material_conflict = False
@@ -271,9 +301,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# if the global stack is found, we check if there are conflicts in the extruder stacks
if containers_found_dict["machine"] and not machine_conflict:
for extruder_stack_file in extruder_stack_files:
- container_id = self._stripFileToId(extruder_stack_file)
serialized = archive.open(extruder_stack_file).read().decode("utf-8")
- parser = configparser.ConfigParser()
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
# The check should be done for the extruder stack that's associated with the existing global stack,
@@ -303,7 +332,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
break
num_visible_settings = 0
- has_visible_settings_string = False
try:
temp_preferences = Preferences()
serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8")
@@ -378,7 +406,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file):
# Get extruder position first
- extruder_config = configparser.ConfigParser()
+ extruder_config = configparser.ConfigParser(interpolation = None)
extruder_config.read_string(extruder_file_content)
if not extruder_config.has_option("metadata", "position"):
msg = "Could not find 'metadata/position' in extruder stack file"
@@ -404,6 +432,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# containing global.cfg / extruder.cfg
#
# \param file_name
+ @call_on_qt_thread
def read(self, file_name):
archive = zipfile.ZipFile(file_name, "r")
@@ -423,6 +452,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
else:
global_preferences.setValue("general/visible_settings", visible_settings)
+ global_preferences.setValue("general/preset_setting_visibility_choice", "Custom")
categories_expanded = temp_preferences.getValue("cura/categories_expanded")
if categories_expanded is None:
@@ -461,7 +491,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
global_stack_id_new = self.getNewId(global_stack_id_original)
global_stack_need_rename = True
- global_stack_name_new = self._container_registry.uniqueName(global_stack_name_original)
+ if self._container_registry.findContainerStacksMetadata(name = global_stack_id_original):
+ global_stack_name_new = self._container_registry.uniqueName(global_stack_name_original)
for each_extruder_stack_file in extruder_stack_files:
old_container_id = self._stripFileToId(each_extruder_stack_file)
@@ -527,12 +558,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
user_instance_containers = []
quality_and_definition_changes_instance_containers = []
+ quality_changes_instance_containers = []
for instance_container_file in instance_container_files:
container_id = self._stripFileToId(instance_container_file)
serialized = archive.open(instance_container_file).read().decode("utf-8")
# HACK! we ignore "quality" and "variant" instance containers!
- parser = configparser.ConfigParser()
+ parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
if not parser.has_option("metadata", "type"):
Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
@@ -583,7 +615,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if machine_id:
new_machine_id = self.getNewId(machine_id)
new_id = new_machine_id + "_current_settings"
- instance_container.setMetadataEntry("id", new_id)
+ instance_container.setMetaDataEntry("id", new_id)
instance_container.setName(new_id)
instance_container.setMetaDataEntry("machine", new_machine_id)
containers_to_add.append(instance_container)
@@ -608,7 +640,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
instance_container.setName(self._container_registry.uniqueName(instance_container.getName()))
new_changes_container_id = self.getNewId(instance_container.getId())
- instance_container._id = new_changes_container_id
+ instance_container.setMetaDataEntry("id", new_changes_container_id)
# TODO: we don't know the following is correct or not, need to verify
# AND REFACTOR!!!
@@ -632,6 +664,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# The ID already exists, but nothing in the values changed, so do nothing.
pass
quality_and_definition_changes_instance_containers.append(instance_container)
+ if container_type == "quality_changes":
+ quality_changes_instance_containers.append(instance_container)
if container_type == "definition_changes":
definition_changes_extruder_count = instance_container.getProperty("machine_extruder_count", "value")
@@ -681,18 +715,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
file_name = global_stack_file)
# Ensure a unique ID and name
- stack._id = global_stack_id_new
-
- # Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
- # bound machine also needs to change.
- if stack.getMetaDataEntry("machine", None):
- stack.setMetaDataEntry("machine", global_stack_id_new)
+ stack.setMetaDataEntry("id", global_stack_id_new)
# Only machines need a new name, stacks may be non-unique
stack.setName(global_stack_name_new)
container_stacks_added.append(stack)
- self._container_registry.addContainer(stack)
+ # self._container_registry.addContainer(stack)
containers_added.append(stack)
else:
Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
@@ -710,6 +739,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return
# load extruder stack files
+ has_extruder_stack_files = len(extruder_stack_files) > 0
+ empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
+ empty_quality_changes_container = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
try:
for extruder_stack_file in extruder_stack_files:
container_id = self._stripFileToId(extruder_stack_file)
@@ -730,7 +762,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
# also does addExtruder() to its machine stack, so we have to make sure that it's pointing
# to the right machine BEFORE deserialization.
- extruder_config = configparser.ConfigParser()
+ extruder_config = configparser.ConfigParser(interpolation = None)
extruder_config.read_string(extruder_file_content)
extruder_config.set("metadata", "machine", global_stack_id_new)
tmp_string_io = io.StringIO()
@@ -740,7 +772,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
# Ensure a unique ID and name
- stack._id = new_id
+ stack.setMetaDataEntry("id", new_id)
self._container_registry.addContainer(stack)
extruder_stacks_added.append(stack)
@@ -758,17 +790,33 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# If not extruder stacks were saved in the project file (pre 3.1) create one manually
# We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this
if not extruder_stacks:
- stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder")
- if stack:
- if self._resolve_strategies["machine"] == "override":
- # in case the extruder is newly created (for a single-extrusion machine), we need to override
- # the existing extruder stack.
- existing_extruder_stack = global_stack.extruders[stack.getMetaDataEntry("position")]
- for idx in range(len(_ContainerIndexes.IndexTypeMap)):
- existing_extruder_stack.replaceContainer(idx, stack._containers[idx], postpone_emit = True)
- extruder_stacks.append(existing_extruder_stack)
- else:
- extruder_stacks.append(stack)
+ # If we choose to override a machine but to create a new custom quality profile, the custom quality
+ # profile is not immediately applied to the global_stack, so this fix for single extrusion machines
+ # will use the current custom quality profile on the existing machine. The extra optional argument
+ # in that function is used in this case to specify a new global stack quality_changes container so
+ # the fix can correctly create and copy over the custom quality settings to the newly created extruder.
+ new_global_quality_changes = None
+ if self._resolve_strategies["quality_changes"] == "new" and len(quality_changes_instance_containers) > 0:
+ new_global_quality_changes = quality_changes_instance_containers[0]
+
+ # Depending if the strategy is to create a new or override, the ids must be or not be unique
+ stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder",
+ new_global_quality_changes,
+ create_new_ids = self._resolve_strategies["machine"] == "new")
+ if new_global_quality_changes is not None:
+ quality_changes_instance_containers.append(stack.qualityChanges)
+ quality_and_definition_changes_instance_containers.append(stack.qualityChanges)
+ if global_stack.quality.getId() in ("empty", "empty_quality"):
+ stack.quality = empty_quality_container
+ if self._resolve_strategies["machine"] == "override":
+ # in case the extruder is newly created (for a single-extrusion machine), we need to override
+ # the existing extruder stack.
+ existing_extruder_stack = global_stack.extruders[stack.getMetaDataEntry("position")]
+ for idx in range(len(_ContainerIndexes.IndexTypeMap)):
+ existing_extruder_stack.replaceContainer(idx, stack._containers[idx], postpone_emit = True)
+ extruder_stacks.append(existing_extruder_stack)
+ else:
+ extruder_stacks.append(stack)
except:
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
@@ -777,6 +825,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._container_registry.removeContainer(container.getId())
return
+ ## In case there is a new machine and once the extruders are created, the global stack is added to the registry,
+ # otherwise the addContainers function in CuraContainerRegistry will create an extruder stack and then creating
+ # useless files
+ if self._resolve_strategies["machine"] == "new":
+ self._container_registry.addContainer(global_stack)
# Check quality profiles to make sure that if one stack has the "not supported" quality profile,
# all others should have the same.
@@ -801,30 +854,24 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if machine_extruder_count is not None:
extruder_stacks_in_use = extruder_stacks[:machine_extruder_count]
- available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack,
- extruder_stacks_in_use)
+ quality_manager = CuraApplication.getInstance()._quality_manager
+ all_quality_groups = quality_manager.getQualityGroups(global_stack)
+ available_quality_types = [qt for qt, qg in all_quality_groups.items() if qg.is_available]
if not has_not_supported:
- has_not_supported = not available_quality
+ has_not_supported = not available_quality_types
quality_has_been_changed = False
if has_not_supported:
- empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
for stack in [global_stack] + extruder_stacks_in_use:
stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container)
- empty_quality_changes_container = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
- for stack in [global_stack] + extruder_stacks_in_use:
stack.replaceContainer(_ContainerIndexes.QualityChanges, empty_quality_changes_container)
quality_has_been_changed = True
else:
- empty_quality_changes_container = self._container_registry.findInstanceContainers(id="empty_quality_changes")[0]
-
# The machine in the project has non-empty quality and there are usable qualities for this machine.
# We need to check if the current quality_type is still usable for this machine, if not, then the quality
# will be reset to the "preferred quality" if present, otherwise "normal".
- available_quality_types = [q.getMetaDataEntry("quality_type") for q in available_quality]
-
if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types:
# We are here because the quality_type specified in the project is not supported any more,
# so we need to switch it to the "preferred quality" if present, otherwise "normal".
@@ -888,6 +935,34 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_stack.quality = new_quality_container
global_stack.quality = new_quality_container
+ # Now we are checking if the quality in the extruder stacks is the same as in the global. In other case,
+ # the quality is set to be the same.
+ definition_id = global_stack.definition.getId()
+ definition_id = global_stack.definition.getMetaDataEntry("quality_definition", definition_id)
+ if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")):
+ definition_id = "fdmprinter"
+
+ for extruder_stack in extruder_stacks_in_use:
+
+ # If the quality is different in the stacks, then the quality in the global stack is trusted
+ if extruder_stack.quality.getMetaDataEntry("quality_type") != global_stack.quality.getMetaDataEntry("quality_type"):
+ search_criteria = {"id": global_stack.quality.getId(),
+ "type": "quality",
+ "definition": definition_id}
+ if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"):
+ search_criteria["material"] = extruder_stack.material.getId()
+ containers = self._container_registry.findInstanceContainers(**search_criteria)
+ if containers:
+ extruder_stack.quality = containers[0]
+ extruder_stack.qualityChanges = empty_quality_changes_container
+ else:
+ Logger.log("e", "Cannot find a suitable quality for extruder [%s].", extruder_stack.getId())
+
+ quality_has_been_changed = True
+
+ else:
+ Logger.log("i", "The quality is the same for the global and the extruder stack [%s]", global_stack.quality.getId())
+
# Replacing the old containers if resolve is "new".
# When resolve is "new", some containers will get renamed, so all the other containers that reference to those
# MUST get updated too.
@@ -930,9 +1005,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# sanity checks
# NOTE: The following cases SHOULD NOT happen!!!!
- if not old_container:
+ if old_container.getId() in ("empty_quality_changes", "empty_definition_changes", "empty"):
Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
changes_container_type, global_stack.getId())
+ continue
# Replace the quality/definition changes container if it's in the GlobalStack
# NOTE: we can get an empty container here, but the IDs will not match,
@@ -945,26 +1021,29 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
continue
# Replace the quality/definition changes container if it's in one of the ExtruderStacks
- for each_extruder_stack in extruder_stacks:
- changes_container = None
- if changes_container_type == "quality_changes":
- changes_container = each_extruder_stack.qualityChanges
- elif changes_container_type == "definition_changes":
- changes_container = each_extruder_stack.definitionChanges
-
- # sanity checks
- # NOTE: The following cases SHOULD NOT happen!!!!
- if not changes_container:
- Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
- changes_container_type, each_extruder_stack.getId())
-
- # NOTE: we can get an empty container here, but the IDs will not match,
- # so this comparison is fine.
- if self._id_mapping.get(changes_container.getId()) == new_id:
+ # Only apply the change if we have loaded extruder stacks from the project
+ if has_extruder_stack_files:
+ for each_extruder_stack in extruder_stacks:
+ changes_container = None
if changes_container_type == "quality_changes":
- each_extruder_stack.qualityChanges = each_changes_container
+ changes_container = each_extruder_stack.qualityChanges
elif changes_container_type == "definition_changes":
- each_extruder_stack.definitionChanges = each_changes_container
+ changes_container = each_extruder_stack.definitionChanges
+
+ # sanity checks
+ # NOTE: The following cases SHOULD NOT happen!!!!
+ if changes_container.getId() in ("empty_quality_changes", "empty_definition_changes", "empty"):
+ Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
+ changes_container_type, each_extruder_stack.getId())
+ continue
+
+ # NOTE: we can get an empty container here, but the IDs will not match,
+ # so this comparison is fine.
+ if self._id_mapping.get(changes_container.getId()) == new_id:
+ if changes_container_type == "quality_changes":
+ each_extruder_stack.qualityChanges = each_changes_container
+ elif changes_container_type == "definition_changes":
+ each_extruder_stack.definitionChanges = each_changes_container
if self._resolve_strategies["material"] == "new":
# the actual material instance container can have an ID such as
@@ -999,12 +1078,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
for stack in extruder_stacks:
stack.setNextStack(global_stack)
stack.containersChanged.emit(stack.getTop())
+ else:
+ CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
# Actually change the active machine.
- Application.getInstance().setGlobalContainerStack(global_stack)
-
- # Notify everything/one that is to notify about changes.
- global_stack.containersChanged.emit(global_stack.getTop())
+ #
+ # This is scheduled for later is because it depends on the Variant/Material/Qualitiy Managers to have the latest
+ # data, but those managers will only update upon a container/container metadata changed signal. Because this
+ # function is running on the main thread (Qt thread), although those "changed" signals have been emitted, but
+ # they won't take effect until this function is done.
+ # To solve this, we schedule _updateActiveMachine() for later so it will have the latest data.
+ CuraApplication.getInstance().callLater(self._updateActiveMachine, global_stack)
# Load all the nodes / meshdata of the workspace
nodes = self._3mf_mesh_reader.read(file_name)
@@ -1017,6 +1101,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self.setWorkspaceName(base_file_name)
return nodes
+ def _updateActiveMachine(self, global_stack):
+ # Actually change the active machine.
+ machine_manager = Application.getInstance().getMachineManager()
+ machine_manager.setActiveMachine(global_stack.getId())
+
+ # Notify everything/one that is to notify about changes.
+ global_stack.containersChanged.emit(global_stack.getTop())
+
## HACK: Replaces the material container in the given stack with a newly created material container.
# This function is used when the user chooses to resolve material conflicts by creating new ones.
def _replaceStackMaterialWithNew(self, stack, old_new_material_dict):
@@ -1048,13 +1140,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# find the old material ID
old_material_id_in_stack = stack.material.getId()
best_matching_old_material_id = None
- best_matching_old_meterial_prefix_length = -1
+ best_matching_old_material_prefix_length = -1
for old_parent_material_id in old_new_material_dict:
- if len(old_parent_material_id) < best_matching_old_meterial_prefix_length:
+ if len(old_parent_material_id) < best_matching_old_material_prefix_length:
continue
if len(old_parent_material_id) <= len(old_material_id_in_stack):
if old_parent_material_id == old_material_id_in_stack[0:len(old_parent_material_id)]:
- best_matching_old_meterial_prefix_length = len(old_parent_material_id)
+ best_matching_old_material_prefix_length = len(old_parent_material_id)
best_matching_old_material_id = old_parent_material_id
if best_matching_old_material_id is None:
diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml
index 826b488e02..5418dcef6d 100644
--- a/plugins/3MFReader/WorkspaceDialog.qml
+++ b/plugins/3MFReader/WorkspaceDialog.qml
@@ -390,6 +390,13 @@ UM.Dialog
}
}
+ function accept() {
+ manager.closeBackend();
+ manager.onOkButtonClicked();
+ base.visible = false;
+ base.accept();
+ }
+
function reject() {
manager.onCancelButtonClicked();
base.visible = false;
diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py
index f07a37a25f..507274d355 100644
--- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py
+++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py
@@ -57,7 +57,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
# Save Cura version
version_file = zipfile.ZipInfo("Cura/version.ini")
- version_config_parser = configparser.ConfigParser()
+ version_config_parser = configparser.ConfigParser(interpolation = None)
version_config_parser.add_section("versions")
version_config_parser.set("versions", "cura_version", Application.getInstance().getVersion())
version_config_parser.set("versions", "build_type", Application.getInstance().getBuildType())
@@ -97,7 +97,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
file_in_archive.compress_type = zipfile.ZIP_DEFLATED
# Do not include the network authentication keys
- ignore_keys = {"network_authentication_id", "network_authentication_key"}
+ ignore_keys = {"network_authentication_id", "network_authentication_key", "octoprint_api_key"}
serialized_data = container.serialize(ignored_metadata_keys = ignore_keys)
archive.writestr(file_in_archive, serialized_data)
diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py
index 6254bef03a..ff6333763a 100644
--- a/plugins/3MFWriter/ThreeMFWriter.py
+++ b/plugins/3MFWriter/ThreeMFWriter.py
@@ -6,7 +6,9 @@ from UM.Math.Vector import Vector
from UM.Logger import Logger
from UM.Math.Matrix import Matrix
from UM.Application import Application
-import UM.Scene.SceneNode
+from UM.Scene.SceneNode import SceneNode
+
+from cura.CuraApplication import CuraApplication
import Savitar
@@ -61,11 +63,15 @@ class ThreeMFWriter(MeshWriter):
self._store_archive = store_archive
## Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
- # \returns Uranium Scenen node.
+ # \returns Uranium Scene node.
def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()):
- if type(um_node) is not UM.Scene.SceneNode.SceneNode:
+ if not isinstance(um_node, SceneNode):
return None
+ active_build_plate_nr = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ if um_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr:
+ return
+
savitar_node = Savitar.SceneNode()
node_matrix = um_node.getLocalTransformation()
@@ -96,6 +102,9 @@ class ThreeMFWriter(MeshWriter):
savitar_node.setSetting(key, str(stack.getProperty(key, "value")))
for child_node in um_node.getChildren():
+ # only save the nodes on the active build plate
+ if child_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr:
+ continue
savitar_child_node = self._convertUMNodeToSavitarNode(child_node)
if savitar_child_node is not None:
savitar_node.addChild(savitar_child_node)
diff --git a/plugins/AutoSave/AutoSave.py b/plugins/AutoSave/AutoSave.py
index 331f328f2d..5fdac502b5 100644
--- a/plugins/AutoSave/AutoSave.py
+++ b/plugins/AutoSave/AutoSave.py
@@ -9,6 +9,7 @@ from UM.Application import Application
from UM.Resources import Resources
from UM.Logger import Logger
+
class AutoSave(Extension):
def __init__(self):
super().__init__()
@@ -16,18 +17,40 @@ class AutoSave(Extension):
Preferences.getInstance().preferenceChanged.connect(self._triggerTimer)
self._global_stack = None
- Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
- self._onGlobalStackChanged()
Preferences.getInstance().addPreference("cura/autosave_delay", 1000 * 10)
self._change_timer = QTimer()
self._change_timer.setInterval(Preferences.getInstance().getValue("cura/autosave_delay"))
self._change_timer.setSingleShot(True)
- self._change_timer.timeout.connect(self._onTimeout)
self._saving = False
+ # At this point, the Application instance has not finished its constructor call yet, so directly using something
+ # like Application.getInstance() is not correct. The initialisation now will only gets triggered after the
+ # application finishes its start up successfully.
+ self._init_timer = QTimer()
+ self._init_timer.setInterval(1000)
+ self._init_timer.setSingleShot(True)
+ self._init_timer.timeout.connect(self.initialize)
+ self._init_timer.start()
+
+ def initialize(self):
+ # only initialise if the application is created and has started
+ from cura.CuraApplication import CuraApplication
+ if not CuraApplication.Created:
+ self._init_timer.start()
+ return
+ if not CuraApplication.getInstance().started:
+ self._init_timer.start()
+ return
+
+ self._change_timer.timeout.connect(self._onTimeout)
+ Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
+ self._onGlobalStackChanged()
+
+ self._triggerTimer()
+
def _triggerTimer(self, *args):
if not self._saving:
self._change_timer.start()
diff --git a/plugins/ChangeLogPlugin/ChangeLog.txt b/plugins/ChangeLogPlugin/ChangeLog.txt
index de895b3e41..6b394f1e2e 100755
--- a/plugins/ChangeLogPlugin/ChangeLog.txt
+++ b/plugins/ChangeLogPlugin/ChangeLog.txt
@@ -1,3 +1,49 @@
+[3.2.0]
+*Tree support
+Experimental tree-like support structure that uses ‘branches’ to support prints. Branches ‘grow’ and multiply towards the model, with fewer contact points than alternative support methods. This results in better surface finishes for organic-shaped prints.
+
+*Adaptive layers
+Prints with a variable layer thickness which adapts to the angle of the model’s surfaces. The result is high-quality surface finishes with a marginally increased print time. This setting can be found under the experimental category.
+
+*Faster startup
+Printer definitions are now loaded when adding a printer, instead of loading all available printers on startup.
+
+*Backface culling in layer view
+Doubled frame rate by only rendering visible surfaces of the model in the layer view, instead of rendering the entire model. Good for lower spec GPUs as it is less resource-intensive.
+
+*Multi build plate
+Experimental feature that creates separate build plates with shared settings in a single session, eliminating the need to clear the build plate multiple times. Multiple build plates can be sliced and sent to a printer or printer group in Cura Connect. This feature must be enabled manually in the preferences ‘general’ tab.
+
+*Improved mesh type selection
+New button in the left toolbar to edit per model settings, giving the user more control over where to place support. Objects can be used as meshes, with a drop down list where ‘Print as support’, ‘Don't overlap support with other models’, ‘Modify settings for overlap with other models’, or ‘Modify settings for infill of other models’ can be specified. Contributed by fieldOfView.
+
+*View optimization
+Quick camera controls introduced in version 3.1 have been revised to create more accurate isometric, front, left, and right views.
+
+*Updated sidebar to QtQuick 2.0
+Application framework updated to increase speed, achieve a better width and style fit, and gives users dropdown menus that are styled to fit the enabled Ultimaker Cura theme, instead of the operating system’s theme.
+
+*Hide sidebar
+The sidebar can now be hidden/shown by selecting View > Expand/Collapse Sidebar, or with the hotkey CMD + E (Mac) or CTRL + E (PC and Linux).
+
+*Disable ‘Send slice information’
+A shortcut to disable ‘Send slice information’ has been added to the first launch to make it easier for privacy-conscious users to keep slice information private.
+
+*Signed binaries (Windows)
+For security-conscious users, the Windows installer and Windows binaries have been digitally signed to prevent “Unknown application” warnings and virus scanner false-positives.
+
+*Start/end gcode script per extruder
+Variables from both extruders in the start and end gcode snippets can now be accessed and edited, creating uniformity between profiles in different slicing environments. Contributed by fieldOfView.
+
+*OctoPrint plugin added to plugin browser
+This plugin enables printers managed with OctoPrint to print via Ultimaker Cura interface (version 3.2 or later).
+
+*Bugfixes
+- Fixed a bug where the mirror tool and center model options when used together would reset the model transformations
+- Updated config file path to fix crashes caused by user config files that are located on remote drives
+- Updated Arduino drivers to fix triggering errors during OTA updates in shared environments. This also fixes an issue when upgrading the firmware of the Ultimaker Original.
+- Fixed an issue where arranging small models would fail, due to conflict with small model files combined with the “Ensure models are kept apart” option
+
[3.1.0]
*Profile added for 0.25 mm print core
This new print core gives extra fine line widths which gives prints extra definition and surface quality.
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index 4581a78479..3982a0ad06 100755
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -16,6 +16,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Qt.Duration import DurationFormat
from PyQt5.QtCore import QObject, pyqtSlot
+from collections import defaultdict
from cura.Settings.ExtruderManager import ExtruderManager
from . import ProcessSlicedLayersJob
from . import StartSliceJob
@@ -69,9 +70,10 @@ class CuraEngineBackend(QObject, Backend):
# Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
+ Application.getInstance().getMultiBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged()
self._stored_layer_data = []
- self._stored_optimized_layer_data = []
+ self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._scene = Application.getInstance().getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged)
@@ -86,7 +88,6 @@ class CuraEngineBackend(QObject, Backend):
#
self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
- Application.getInstance().getExtruderManager().activeExtruderChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
@@ -105,17 +106,18 @@ class CuraEngineBackend(QObject, Backend):
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
self._start_slice_job = None
+ self._start_slice_job_build_plate = None
self._slicing = False # Are we currently slicing?
self._restart = False # Back-end is currently restarting?
self._tool_active = False # If a tool is active, some tasks do not have to do anything
self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
- self._need_slicing = False
+ self._build_plates_to_be_sliced = [] # what needs slicing?
self._engine_is_fresh = True # Is the newly started engine used before or not?
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
self._error_message = None # Pop-up message that shows errors.
- self._last_num_objects = 0 # Count number of objects to see if there is something changed
+ self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
self.backendQuit.connect(self._onBackendQuit)
@@ -174,6 +176,7 @@ class CuraEngineBackend(QObject, Backend):
self._createSocket()
if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon.
+ Logger.log("d", "Aborting process layers job...")
self._process_layers_job.abort()
self._process_layers_job = None
@@ -183,24 +186,42 @@ class CuraEngineBackend(QObject, Backend):
## Manually triggers a reslice
@pyqtSlot()
def forceSlice(self):
- if self._use_timer:
- self._change_timer.start()
- else:
- self.slice()
+ self.markSliceAll()
+ self.slice()
## Perform a slice of the scene.
def slice(self):
+ Logger.log("d", "Starting to slice...")
self._slice_start_time = time()
- if not self._need_slicing:
+ if not self._build_plates_to_be_sliced:
self.processingProgress.emit(1.0)
- self.backendStateChange.emit(BackendState.Done)
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
return
- if Application.getInstance().getPrintInformation():
- Application.getInstance().getPrintInformation().setToZeroPrintInformation()
+
+ if self._process_layers_job:
+ Logger.log("d", "Process layers job still busy, trying later.")
+ return
+
+ if not hasattr(self._scene, "gcode_dict"):
+ self._scene.gcode_dict = {}
+
+ # see if we really have to slice
+ active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
+ Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
+ num_objects = self._numObjects()
+ if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
+ self._scene.gcode_dict[build_plate_to_be_sliced] = []
+ Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
+ if self._build_plates_to_be_sliced:
+ self.slice()
+ return
self._stored_layer_data = []
- self._stored_optimized_layer_data = []
+ self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
+
+ if Application.getInstance().getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
+ Application.getInstance().getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
if self._process is None:
self._createSocket()
@@ -210,12 +231,16 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
- self._scene.gcode_list = []
+ self._scene.gcode_dict[build_plate_to_be_sliced] = [] #[] indexed by build plate number
self._slicing = True
self.slicingStarted.emit()
+ self.determineAutoSlicing() # Switch timer on or off if appropriate
+
slice_message = self._socket.createMessage("cura.proto.Slice")
self._start_slice_job = StartSliceJob.StartSliceJob(slice_message)
+ self._start_slice_job_build_plate = build_plate_to_be_sliced
+ self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate)
self._start_slice_job.start()
self._start_slice_job.finished.connect(self._onStartSliceCompleted)
@@ -224,7 +249,8 @@ class CuraEngineBackend(QObject, Backend):
def _terminate(self):
self._slicing = False
self._stored_layer_data = []
- self._stored_optimized_layer_data = []
+ if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
+ del self._stored_optimized_layer_data[self._start_slice_job_build_plate]
if self._start_slice_job is not None:
self._start_slice_job.cancel()
@@ -262,6 +288,7 @@ class CuraEngineBackend(QObject, Backend):
self._start_slice_job = None
if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error:
+ self.backendStateChange.emit(BackendState.Error)
return
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
@@ -339,7 +366,9 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Error)
else:
self.backendStateChange.emit(BackendState.NotStarted)
+ self._invokeSlice()
return
+
# Preparation completed, send it to the backend.
self._socket.sendMessage(job.getSliceMessage())
@@ -363,7 +392,7 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Disabled)
gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None:
- self._scene.gcode_list = gcode_list
+ self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list
if self._use_timer == enable_timer:
return self._use_timer
@@ -375,33 +404,53 @@ class CuraEngineBackend(QObject, Backend):
self.disableTimer()
return False
+ ## Return a dict with number of objects per build plate
+ def _numObjects(self):
+ num_objects = defaultdict(int)
+ for node in DepthFirstIterator(self._scene.getRoot()):
+ # Only count sliceable objects
+ if node.callDecoration("isSliceable"):
+ build_plate_number = node.callDecoration("getBuildPlateNumber")
+ num_objects[build_plate_number] += 1
+ return num_objects
+
## Listener for when the scene has changed.
#
# This should start a slice if the scene is now ready to slice.
#
# \param source The scene node that was changed.
def _onSceneChanged(self, source):
- if type(source) is not SceneNode:
+ if not isinstance(source, SceneNode):
return
- root_scene_nodes_changed = False
- if source == self._scene.getRoot():
- num_objects = 0
- for node in DepthFirstIterator(self._scene.getRoot()):
- # Only count sliceable objects
- if node.callDecoration("isSliceable"):
- num_objects += 1
- if num_objects != self._last_num_objects:
- self._last_num_objects = num_objects
- root_scene_nodes_changed = True
- else:
- return
+ # This case checks if the source node is a node that contains GCode. In this case the
+ # current layer data is removed so the previous data is not rendered - CURA-4821
+ if source.callDecoration("isBlockSlicing") and source.callDecoration("getLayerData"):
+ self._stored_optimized_layer_data = {}
- if not source.callDecoration("isGroup") and not root_scene_nodes_changed:
- if source.getMeshData() is None:
- return
- if source.getMeshData().getVertices() is None:
- return
+ build_plate_changed = set()
+ source_build_plate_number = source.callDecoration("getBuildPlateNumber")
+ if source == self._scene.getRoot():
+ # we got the root node
+ num_objects = self._numObjects()
+ for build_plate_number in list(self._last_num_objects.keys()) + list(num_objects.keys()):
+ if build_plate_number not in self._last_num_objects or num_objects[build_plate_number] != self._last_num_objects[build_plate_number]:
+ self._last_num_objects[build_plate_number] = num_objects[build_plate_number]
+ build_plate_changed.add(build_plate_number)
+ else:
+ # we got a single scenenode
+ if not source.callDecoration("isGroup"):
+ if source.getMeshData() is None:
+ return
+ if source.getMeshData().getVertices() is None:
+ return
+
+ build_plate_changed.add(source_build_plate_number)
+
+ build_plate_changed.discard(None)
+ build_plate_changed.discard(-1) # object not on build plate
+ if not build_plate_changed:
+ return
if self._tool_active:
# do it later, each source only has to be done once
@@ -409,9 +458,18 @@ class CuraEngineBackend(QObject, Backend):
self._postponed_scene_change_sources.append(source)
return
- self.needsSlicing()
self.stopSlicing()
- self._onChanged()
+ for build_plate_number in build_plate_changed:
+ if build_plate_number not in self._build_plates_to_be_sliced:
+ self._build_plates_to_be_sliced.append(build_plate_number)
+ self.printDurationMessage.emit(source_build_plate_number, {}, [])
+ self.processingProgress.emit(0.0)
+ self.backendStateChange.emit(BackendState.NotStarted)
+ # if not self._use_timer:
+ # With manually having to slice, we want to clear the old invalid layer data.
+ self._clearLayerData(build_plate_changed)
+
+ self._invokeSlice()
## Called when an error occurs in the socket connection towards the engine.
#
@@ -431,16 +489,21 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("w", "A socket error caused the connection to be reset")
## Remove old layer data (if any)
- def _clearLayerData(self):
+ def _clearLayerData(self, build_plate_numbers = set()):
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData"):
- node.getParent().removeChild(node)
- break
+ if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
+ node.getParent().removeChild(node)
- ## Convenient function: set need_slicing, emit state and clear layer data
+ def markSliceAll(self):
+ for build_plate_number in range(Application.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1):
+ if build_plate_number not in self._build_plates_to_be_sliced:
+ self._build_plates_to_be_sliced.append(build_plate_number)
+
+ ## Convenient function: mark everything to slice, emit state and clear layer data
def needsSlicing(self):
self.stopSlicing()
- self._need_slicing = True
+ self.markSliceAll()
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
if not self._use_timer:
@@ -462,7 +525,7 @@ class CuraEngineBackend(QObject, Backend):
def _onStackErrorCheckFinished(self):
self._is_error_check_scheduled = False
- if not self._slicing and self._need_slicing:
+ if not self._slicing and self._build_plates_to_be_sliced:
self.needsSlicing()
self._onChanged()
@@ -476,7 +539,9 @@ class CuraEngineBackend(QObject, Backend):
#
# \param message The protobuf message containing sliced layer data.
def _onOptimizedLayerMessage(self, message):
- self._stored_optimized_layer_data.append(message)
+ if self._start_slice_job_build_plate not in self._stored_optimized_layer_data:
+ self._stored_optimized_layer_data[self._start_slice_job_build_plate] = []
+ self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
## Called when a progress message is received from the engine.
#
@@ -485,6 +550,16 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.Processing)
+ # testing
+ def _invokeSlice(self):
+ if self._use_timer:
+ # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
+ # otherwise business as usual
+ if self._is_error_check_scheduled:
+ self._change_timer.stop()
+ else:
+ self._change_timer.start()
+
## Called when the engine sends a message that slicing is finished.
#
# \param message The protobuf message signalling that slicing is finished.
@@ -492,36 +567,46 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Done)
self.processingProgress.emit(1.0)
- for line in self._scene.gcode_list:
+ gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate]
+ for index, line in enumerate(gcode_list):
replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths))
replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights))
replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts))
replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName))
- self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced
+ gcode_list[index] = replaced
self._slicing = False
- self._need_slicing = False
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
- if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()):
- self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
- self._process_layers_job.finished.connect(self._onProcessLayersFinished)
- self._process_layers_job.start()
- self._stored_optimized_layer_data = []
+
+ # See if we need to process the sliced layers job.
+ active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
+ self._startProcessSlicedLayersJob(active_build_plate)
+ # self._onActiveViewChanged()
+ self._start_slice_job_build_plate = None
+
+ Logger.log("d", "See if there is more to slice...")
+ # Somehow this results in an Arcus Error
+ # self.slice()
+ # Call slice again using the timer, allowing the backend to restart
+ if self._build_plates_to_be_sliced:
+ self.enableTimer() # manually enable timer to be able to invoke slice, also when in manual slice mode
+ self._invokeSlice()
## Called when a g-code message is received from the engine.
#
# \param message The protobuf message containing g-code, encoded as UTF-8.
def _onGCodeLayerMessage(self, message):
- self._scene.gcode_list.append(message.data.decode("utf-8", "replace"))
+ self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace"))
## Called when a g-code prefix message is received from the engine.
#
# \param message The protobuf message containing the g-code prefix,
# encoded as UTF-8.
def _onGCodePrefixMessage(self, message):
- self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace"))
+ self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace"))
## Creates a new socket connection.
def _createSocket(self):
@@ -551,7 +636,7 @@ class CuraEngineBackend(QObject, Backend):
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
times = self._parseMessagePrintTimes(message)
- self.printDurationMessage.emit(times, material_amounts)
+ self.printDurationMessage.emit(self._start_slice_job_build_plate, times, material_amounts)
## Called for parsing message to retrieve estimated time per feature
#
@@ -605,19 +690,25 @@ class CuraEngineBackend(QObject, Backend):
source = self._postponed_scene_change_sources.pop(0)
self._onSceneChanged(source)
+ def _startProcessSlicedLayersJob(self, build_plate_number):
+ self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number])
+ self._process_layers_job.setBuildPlate(build_plate_number)
+ self._process_layers_job.finished.connect(self._onProcessLayersFinished)
+ self._process_layers_job.start()
+
## Called when the user changes the active view mode.
def _onActiveViewChanged(self):
- if Application.getInstance().getController().getActiveView():
- view = Application.getInstance().getController().getActiveView()
+ application = Application.getInstance()
+ view = application.getController().getActiveView()
+ if view:
+ active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True
# There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
- if self._stored_optimized_layer_data and not self._slicing:
- self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
- self._process_layers_job.finished.connect(self._onProcessLayersFinished)
- self._process_layers_job.start()
- self._stored_optimized_layer_data = []
+ # TODO: what build plate I am slicing
+ if active_build_plate in self._stored_optimized_layer_data and not self._slicing and not self._process_layers_job:
+ self._startProcessSlicedLayersJob(active_build_plate)
else:
self._layer_view_active = False
@@ -635,7 +726,7 @@ class CuraEngineBackend(QObject, Backend):
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack.containersChanged.disconnect(self._onChanged)
- extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ extruders = list(self._global_container_stack.extruders.values())
for extruder in extruders:
extruder.propertyChanged.disconnect(self._onSettingChanged)
@@ -646,14 +737,17 @@ class CuraEngineBackend(QObject, Backend):
if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
self._global_container_stack.containersChanged.connect(self._onChanged)
- extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ extruders = list(self._global_container_stack.extruders.values())
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingChanged)
extruder.containersChanged.connect(self._onChanged)
self._onChanged()
def _onProcessLayersFinished(self, job):
+ del self._stored_optimized_layer_data[job.getBuildPlate()]
self._process_layers_job = None
+ Logger.log("d", "See if there is more to slice(2)...")
+ self._invokeSlice()
## Connect slice function to timer.
def enableTimer(self):
diff --git a/plugins/CuraEngineBackend/ProcessGCodeJob.py b/plugins/CuraEngineBackend/ProcessGCodeJob.py
index 4974907c30..817daa9f85 100644
--- a/plugins/CuraEngineBackend/ProcessGCodeJob.py
+++ b/plugins/CuraEngineBackend/ProcessGCodeJob.py
@@ -12,4 +12,6 @@ class ProcessGCodeLayerJob(Job):
self._message = message
def run(self):
- self._scene.gcode_list.append(self._message.data.decode("utf-8", "replace"))
+ active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ gcode_list = self._scene.gcode_dict[active_build_plate_id]
+ gcode_list.append(self._message.data.decode("utf-8", "replace"))
diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
index 26a8269183..c1fc597d80 100644
--- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
+++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
@@ -4,8 +4,6 @@
import gc
from UM.Job import Job
-from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
-from UM.Scene.SceneNode import SceneNode
from UM.Application import Application
from UM.Mesh.MeshData import MeshData
from UM.Preferences import Preferences
@@ -17,6 +15,8 @@ from UM.Logger import Logger
from UM.Math.Vector import Vector
+from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
+from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager
from cura import LayerDataBuilder
from cura import LayerDataDecorator
@@ -49,6 +49,7 @@ class ProcessSlicedLayersJob(Job):
self._scene = Application.getInstance().getController().getScene()
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
self._abort_requested = False
+ self._build_plate_number = None
## Aborts the processing of layers.
#
@@ -59,7 +60,14 @@ class ProcessSlicedLayersJob(Job):
def abort(self):
self._abort_requested = True
+ def setBuildPlate(self, new_value):
+ self._build_plate_number = new_value
+
+ def getBuildPlate(self):
+ return self._build_plate_number
+
def run(self):
+ Logger.log("d", "Processing new layer for build plate %s..." % self._build_plate_number)
start_time = time()
view = Application.getInstance().getController().getActiveView()
if view.getPluginId() == "SimulationView":
@@ -73,17 +81,8 @@ class ProcessSlicedLayersJob(Job):
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
- new_node = SceneNode()
-
- ## Remove old layer data (if any)
- for node in DepthFirstIterator(self._scene.getRoot()):
- if node.callDecoration("getLayerData"):
- node.getParent().removeChild(node)
- break
- if self._abort_requested:
- if self._progress_message:
- self._progress_message.hide()
- return
+ new_node = CuraSceneNode()
+ new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))
# Force garbage collection.
# For some reason, Python has a tendency to keep the layer data
diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py
index efa0e87b9e..8b7205f8b2 100644
--- a/plugins/CuraEngineBackend/StartSliceJob.py
+++ b/plugins/CuraEngineBackend/StartSliceJob.py
@@ -5,20 +5,25 @@ import numpy
from string import Formatter
from enum import IntEnum
import time
+import re
from UM.Job import Job
from UM.Application import Application
from UM.Logger import Logger
-from UM.Scene.SceneNode import SceneNode
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.Validator import ValidatorState
from UM.Settings.SettingRelation import RelationType
+from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.OneAtATimeIterator import OneAtATimeIterator
from cura.Settings.ExtruderManager import ExtruderManager
+
+NON_PRINTING_MESH_SETTINGS = ["anti_overhang_mesh", "infill_mesh", "cutting_mesh"]
+
+
class StartJobResult(IntEnum):
Finished = 1
Error = 2
@@ -32,14 +37,37 @@ class StartJobResult(IntEnum):
## Formatter class that handles token expansion in start/end gcod
class GcodeStartEndFormatter(Formatter):
def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class]
+ # The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
+ # and a default_extruder_nr to use when no extruder_nr is specified
+
if isinstance(key, str):
try:
- return kwargs[key]
+ extruder_nr = kwargs["default_extruder_nr"]
+ except ValueError:
+ extruder_nr = -1
+
+ key_fragments = [fragment.strip() for fragment in key.split(',')]
+ if len(key_fragments) == 2:
+ try:
+ extruder_nr = int(key_fragments[1])
+ except ValueError:
+ try:
+ extruder_nr = int(kwargs["-1"][key_fragments[1]]) # get extruder_nr values from the global stack
+ except (KeyError, ValueError):
+ # either the key does not exist, or the value is not an int
+ Logger.log("w", "Unable to determine stack nr '%s' for key '%s' in start/end g-code, using global stack", key_fragments[1], key_fragments[0])
+ elif len(key_fragments) != 1:
+ Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end g-code", key)
+ return "{" + str(key) + "}"
+
+ key = key_fragments[0]
+ try:
+ return kwargs[str(extruder_nr)][key]
except KeyError:
- Logger.log("w", "Unable to replace '%s' placeholder in start/end gcode", key)
+ Logger.log("w", "Unable to replace '%s' placeholder in start/end g-code", key)
return "{" + key + "}"
else:
- Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end gcode", key)
+ Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end g-code", key)
return "{" + str(key) + "}"
@@ -51,10 +79,16 @@ class StartSliceJob(Job):
self._scene = Application.getInstance().getController().getScene()
self._slice_message = slice_message
self._is_cancelled = False
+ self._build_plate_number = None
+
+ self._all_extruders_settings = None # cache for all setting values from all stacks (global & extruder) for the current machine
def getSliceMessage(self):
return self._slice_message
+ def setBuildPlate(self, build_plate_number):
+ self._build_plate_number = build_plate_number
+
## Check if a stack has any errors.
## returns true if it has errors, false otherwise.
def _checkStackForErrors(self, stack):
@@ -71,6 +105,10 @@ class StartSliceJob(Job):
## Runs the job that initiates the slicing.
def run(self):
+ if self._build_plate_number is None:
+ self.setResult(StartJobResult.Error)
+ return
+
stack = Application.getInstance().getGlobalContainerStack()
if not stack:
self.setResult(StartJobResult.Error)
@@ -85,6 +123,12 @@ class StartSliceJob(Job):
self.setResult(StartJobResult.BuildPlateError)
return
+ # Don't slice if the buildplate or the nozzle type is incompatible with the materials
+ if not Application.getInstance().getMachineManager().variantBuildplateCompatible and \
+ not Application.getInstance().getMachineManager().variantBuildplateUsable:
+ self.setResult(StartJobResult.MaterialIncompatible)
+ return
+
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
material = extruder_stack.findContainer({"type": "material"})
if material:
@@ -92,9 +136,14 @@ class StartSliceJob(Job):
self.setResult(StartJobResult.MaterialIncompatible)
return
+ # Validate settings per selectable model
+ if Application.getInstance().getObjectsModel().stacksHaveErrors():
+ self.setResult(StartJobResult.ObjectSettingError)
+ return
+
# Don't slice if there is a per object setting with an error value.
for node in DepthFirstIterator(self._scene.getRoot()):
- if type(node) is not SceneNode or not node.isSelectable():
+ if node.isSelectable():
continue
if self._checkStackForErrors(node.callDecoration("getStack")):
@@ -104,7 +153,7 @@ class StartSliceJob(Job):
with self._scene.getSceneLock():
# Remove old layer data.
for node in DepthFirstIterator(self._scene.getRoot()):
- if node.callDecoration("getLayerData"):
+ if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
node.getParent().removeChild(node)
break
@@ -118,10 +167,15 @@ class StartSliceJob(Job):
if getattr(node, "_outside_buildarea", False):
continue
+ # Filter on current build plate
+ build_plate_number = node.callDecoration("getBuildPlateNumber")
+ if build_plate_number is not None and build_plate_number != self._build_plate_number:
+ continue
+
children = node.getAllChildren()
children.append(node)
for child_node in children:
- if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
+ if child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
temp_list.append(child_node)
if temp_list:
@@ -133,12 +187,18 @@ class StartSliceJob(Job):
temp_list = []
has_printing_mesh = False
for node in DepthFirstIterator(self._scene.getRoot()):
- if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
- _non_printing_mesh = getattr(node, "_non_printing_mesh", False)
- if not getattr(node, "_outside_buildarea", False) or _non_printing_mesh:
- temp_list.append(node)
- if not _non_printing_mesh:
- has_printing_mesh = True
+ if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None:
+ per_object_stack = node.callDecoration("getStack")
+ is_non_printing_mesh = False
+ if per_object_stack:
+ is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
+
+ if node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
+ if not getattr(node, "_outside_buildarea", False) or is_non_printing_mesh:
+ temp_list.append(node)
+ if not is_non_printing_mesh:
+ has_printing_mesh = True
+
Job.yieldThread()
#If the list doesn't have any model with suitable settings then clean the list
@@ -224,19 +284,36 @@ class StartSliceJob(Job):
result["date"] = time.strftime("%d-%m-%Y")
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
+ initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
+ initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
+ result["initial_extruder_nr"] = initial_extruder_nr
+
return result
## Replace setting tokens in a piece of g-code.
# \param value A piece of g-code to replace tokens in.
- # \param settings A dictionary of tokens to replace and their respective
- # replacement strings.
- def _expandGcodeTokens(self, value: str, settings: dict):
+ # \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
+ def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1):
+ if not self._all_extruders_settings:
+ global_stack = Application.getInstance().getGlobalContainerStack()
+
+ # NB: keys must be strings for the string formatter
+ self._all_extruders_settings = {
+ "-1": self._buildReplacementTokens(global_stack)
+ }
+
+ for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
+ extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
+ self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
+
try:
# any setting can be used as a token
fmt = GcodeStartEndFormatter()
+ settings = self._all_extruders_settings.copy()
+ settings["default_extruder_nr"] = default_extruder_nr
return str(fmt.format(value, **settings))
except:
- Logger.logException("w", "Unable to do token replacement on start/end gcode")
+ Logger.logException("w", "Unable to do token replacement on start/end g-code")
return str(value)
## Create extruder message from stack
@@ -250,8 +327,9 @@ class StartSliceJob(Job):
settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")
# Replace the setting tokens in start and end g-code.
- settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], settings)
- settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], settings)
+ extruder_nr = stack.getProperty("extruder_nr", "value")
+ settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], extruder_nr)
+ settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], extruder_nr)
for key, value in settings.items():
# Do not send settings that are not settable_per_extruder.
@@ -271,18 +349,20 @@ class StartSliceJob(Job):
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
start_gcode = settings["machine_start_gcode"]
- bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"}
- settings["material_bed_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in bed_temperature_settings))
- print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"}
- settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings))
-
- # Find the correct temperatures from the first used extruder
- extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
- extruder_0_settings = self._buildReplacementTokens(extruder_stack)
+ bed_temperature_settings = ["material_bed_temperature", "material_bed_temperature_layer_0"]
+ pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(bed_temperature_settings) # match {setting} as well as {setting, extruder_nr}
+ settings["material_bed_temp_prepend"] = re.search(pattern, start_gcode) == None
+ print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"]
+ pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(print_temperature_settings) # match {setting} as well as {setting, extruder_nr}
+ settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) == None
# Replace the setting tokens in start and end g-code.
- settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], extruder_0_settings)
- settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], extruder_0_settings)
+ # Use values from the first used extruder by default so we get the expected temperatures
+ initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
+ initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
+
+ settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], initial_extruder_nr)
+ settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], initial_extruder_nr)
# Add all sub-messages for each individual setting.
for key, value in settings.items():
diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py
index 5631d138aa..69b2187541 100644
--- a/plugins/CuraProfileReader/CuraProfileReader.py
+++ b/plugins/CuraProfileReader/CuraProfileReader.py
@@ -39,7 +39,7 @@ class CuraProfileReader(ProfileReader):
except zipfile.BadZipFile:
# It must be an older profile from Cura 2.1.
- with open(file_name, encoding="utf-8") as fhandle:
+ with open(file_name, encoding = "utf-8") as fhandle:
serialized = fhandle.read()
return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized, file_name)]
@@ -52,10 +52,10 @@ class CuraProfileReader(ProfileReader):
parser = configparser.ConfigParser(interpolation=None)
parser.read_string(serialized)
- if not "general" in parser:
+ if "general" not in parser:
Logger.log("w", "Missing required section 'general'.")
return []
- if not "version" in parser["general"]:
+ if "version" not in parser["general"]:
Logger.log("w", "Missing required 'version' property")
return []
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
index 458aca5787..23a040f2e2 100644
--- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
@@ -20,7 +20,7 @@ i18n_catalog = i18nCatalog("cura")
# The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy
# to change it to work for other applications.
class FirmwareUpdateChecker(Extension):
- JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version"
+ JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources"
def __init__(self):
super().__init__()
@@ -49,7 +49,6 @@ class FirmwareUpdateChecker(Extension):
def _onContainerAdded(self, container):
# Only take care when a new GlobalStack was added
if isinstance(container, GlobalStack):
- Logger.log("i", "You have a '%s' in printer list. Let's check the firmware!", container.getId())
self.checkFirmwareVersion(container, True)
## Connect with software.ultimaker.com, load latest.version and check version info.
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
index 6dd7338cfd..fd6c4680e8 100644
--- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
@@ -44,6 +44,8 @@ class FirmwareUpdateCheckerJob(Job):
# Now we just do that if the active printer is Ultimaker 3 or Ultimaker 3 Extended or any
# other Ultimaker 3 that will come in the future
if len(machine_name_parts) >= 2 and machine_name_parts[:2] == ["ultimaker", "3"]:
+ Logger.log("i", "You have a UM3 in printer list. Let's check the firmware!")
+
# Nothing to parse, just get the string
# TODO: In the future may be done by parsing a JSON file with diferent version for each printer model
current_version = reader(current_version_file).readline().rstrip()
diff --git a/plugins/GCodeProfileReader/GCodeProfileReader.py b/plugins/GCodeProfileReader/GCodeProfileReader.py
index 9d7f0059f9..2a10e01d43 100644
--- a/plugins/GCodeProfileReader/GCodeProfileReader.py
+++ b/plugins/GCodeProfileReader/GCodeProfileReader.py
@@ -71,7 +71,7 @@ class GCodeProfileReader(ProfileReader):
try:
json_data = json.loads(serialized)
except Exception as e:
- Logger.log("e", "Could not parse serialized JSON data from GCode %s, error: %s", file_name, e)
+ Logger.log("e", "Could not parse serialized JSON data from g-code %s, error: %s", file_name, e)
return None
profiles = []
diff --git a/plugins/GCodeProfileReader/plugin.json b/plugins/GCodeProfileReader/plugin.json
index 8111bc687c..f8f7d4c291 100644
--- a/plugins/GCodeProfileReader/plugin.json
+++ b/plugins/GCodeProfileReader/plugin.json
@@ -1,5 +1,5 @@
{
- "name": "GCode Profile Reader",
+ "name": "G-code Profile Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for importing profiles from g-code files.",
diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py
index cd317027ee..c064ffbf10 100644
--- a/plugins/GCodeReader/FlavorParser.py
+++ b/plugins/GCodeReader/FlavorParser.py
@@ -8,16 +8,16 @@ from UM.Logger import Logger
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Math.Vector import Vector
from UM.Message import Message
-from UM.Scene.SceneNode import SceneNode
+from cura.Scene.CuraSceneNode import CuraSceneNode
from UM.i18n import i18nCatalog
from UM.Preferences import Preferences
catalog = i18nCatalog("cura")
from cura import LayerDataBuilder
-from cura import LayerDataDecorator
+from cura.LayerDataDecorator import LayerDataDecorator
from cura.LayerPolygon import LayerPolygon
-from cura.GCodeListDecorator import GCodeListDecorator
+from cura.Scene.GCodeListDecorator import GCodeListDecorator
from cura.Settings.ExtruderManager import ExtruderManager
import numpy
@@ -292,7 +292,7 @@ class FlavorParser:
# We obtain the filament diameter from the selected printer to calculate line widths
self._filament_diameter = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value")
- scene_node = SceneNode()
+ scene_node = CuraSceneNode()
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
# real data to calculate it from.
scene_node.getBoundingBox = self._getNullBoundingBox
@@ -418,11 +418,17 @@ class FlavorParser:
self._layer_number += 1
current_path.clear()
- material_color_map = numpy.zeros((10, 4), dtype = numpy.float32)
+ material_color_map = numpy.zeros((8, 4), dtype = numpy.float32)
material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0]
material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0]
+ material_color_map[2, :] = [0.9, 0.0, 0.7, 1.0]
+ material_color_map[3, :] = [0.7, 0.0, 0.0, 1.0]
+ material_color_map[4, :] = [0.0, 0.7, 0.0, 1.0]
+ material_color_map[5, :] = [0.0, 0.0, 0.7, 1.0]
+ material_color_map[6, :] = [0.3, 0.3, 0.3, 1.0]
+ material_color_map[7, :] = [0.7, 0.7, 0.7, 1.0]
layer_mesh = self._layer_data_builder.build(material_color_map)
- decorator = LayerDataDecorator.LayerDataDecorator()
+ decorator = LayerDataDecorator()
decorator.setLayerData(layer_mesh)
scene_node.addDecorator(decorator)
@@ -430,7 +436,10 @@ class FlavorParser:
gcode_list_decorator.setGCodeList(gcode_list)
scene_node.addDecorator(gcode_list_decorator)
- Application.getInstance().getController().getScene().gcode_list = gcode_list
+ # gcode_dict stores gcode_lists for a number of build plates.
+ active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ gcode_dict = {active_build_plate_id: gcode_list}
+ Application.getInstance().getController().getScene().gcode_dict = gcode_dict
Logger.log("d", "Finished parsing %s" % file_name)
self._message.hide()
diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py
index cb2f5d99e0..050f243f9b 100755
--- a/plugins/GCodeReader/GCodeReader.py
+++ b/plugins/GCodeReader/GCodeReader.py
@@ -26,7 +26,7 @@ class GCodeReader(MeshReader):
# PreRead is used to get the correct flavor. If not, Marlin is set by default
def preRead(self, file_name, *args, **kwargs):
- with open(file_name, "r") as file:
+ with open(file_name, "r", encoding = "utf-8") as file:
for line in file:
if line[:len(self._flavor_keyword)] == self._flavor_keyword:
try:
diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py
index f30e2dd43a..1b3b7264a1 100644
--- a/plugins/GCodeWriter/GCodeWriter.py
+++ b/plugins/GCodeWriter/GCodeWriter.py
@@ -5,6 +5,7 @@ from UM.Mesh.MeshWriter import MeshWriter
from UM.Logger import Logger
from UM.Application import Application
from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Util import parseBool
from cura.Settings.ExtruderManager import ExtruderManager
@@ -56,12 +57,16 @@ class GCodeWriter(MeshWriter):
# file. This must always be text mode.
def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode):
if mode != MeshWriter.OutputMode.TextMode:
- Logger.log("e", "GCode Writer does not support non-text mode.")
+ Logger.log("e", "GCodeWriter does not support non-text mode.")
return False
+ active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene()
- gcode_list = getattr(scene, "gcode_list")
- if gcode_list:
+ gcode_dict = getattr(scene, "gcode_dict")
+ if not gcode_dict:
+ return False
+ gcode_list = gcode_dict.get(active_build_plate, None)
+ if gcode_list is not None:
for gcode in gcode_list:
stream.write(gcode)
# Serialise the current container stack and put it at the end of the file.
@@ -103,8 +108,8 @@ class GCodeWriter(MeshWriter):
prefix_length = len(prefix)
container_with_profile = stack.qualityChanges
- if not container_with_profile:
- Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
+ if container_with_profile.getId() == "empty_quality_changes":
+ Logger.log("e", "No valid quality profile found, not writing settings to g-code!")
return ""
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
@@ -116,12 +121,20 @@ class GCodeWriter(MeshWriter):
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
+ # Change the default defintion
+ default_machine_definition = "fdmprinter"
+ if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")):
+ default_machine_definition = stack.getMetaDataEntry("quality_definition")
+ if not default_machine_definition:
+ default_machine_definition = stack.definition.getId()
+ flat_global_container.setMetaDataEntry("definition", default_machine_definition)
+
serialized = flat_global_container.serialize()
data = {"global_quality": serialized}
- for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")):
+ for extruder in sorted(stack.extruders.values(), key = lambda k: k.getMetaDataEntry("position")):
extruder_quality = extruder.qualityChanges
- if not extruder_quality:
+ if extruder_quality.getId() == "empty_quality_changes":
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
continue
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
@@ -136,6 +149,10 @@ class GCodeWriter(MeshWriter):
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
+
+ # Change the default defintion
+ flat_extruder_quality.setMetaDataEntry("definition", default_machine_definition)
+
extruder_serialized = flat_extruder_quality.serialize()
data.setdefault("extruder_quality", []).append(extruder_serialized)
diff --git a/plugins/GCodeWriter/__init__.py b/plugins/GCodeWriter/__init__.py
index a89332a371..1a5728f510 100644
--- a/plugins/GCodeWriter/__init__.py
+++ b/plugins/GCodeWriter/__init__.py
@@ -13,7 +13,7 @@ def getMetaData():
"mesh_writer": {
"output": [{
"extension": "gcode",
- "description": catalog.i18nc("@item:inlistbox", "GCode File"),
+ "description": catalog.i18nc("@item:inlistbox", "G-code File"),
"mime_type": "text/x-gcode",
"mode": GCodeWriter.GCodeWriter.OutputMode.TextMode
}]
diff --git a/plugins/GCodeWriter/plugin.json b/plugins/GCodeWriter/plugin.json
index 5788b01375..5fcb1a3bd7 100644
--- a/plugins/GCodeWriter/plugin.json
+++ b/plugins/GCodeWriter/plugin.json
@@ -1,8 +1,8 @@
{
- "name": "GCode Writer",
+ "name": "G-code Writer",
"author": "Ultimaker B.V.",
"version": "1.0.0",
- "description": "Writes GCode to a file.",
+ "description": "Writes g-code to a file.",
"api": 4,
"i18n-catalog": "cura"
}
diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py
index b419c0b496..3a98abccf5 100644
--- a/plugins/ImageReader/ImageReader.py
+++ b/plugins/ImageReader/ImageReader.py
@@ -8,12 +8,13 @@ from PyQt5.QtCore import Qt
from UM.Mesh.MeshReader import MeshReader
from UM.Mesh.MeshBuilder import MeshBuilder
-from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
from UM.Job import Job
from UM.Logger import Logger
from .ImageReaderUI import ImageReaderUI
+from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
+
class ImageReader(MeshReader):
def __init__(self):
diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py
index 32cfb5d027..824fc959a1 100644
--- a/plugins/LegacyProfileReader/LegacyProfileReader.py
+++ b/plugins/LegacyProfileReader/LegacyProfileReader.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015 Ultimaker B.V.
+# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser # For reading the legacy profile INI files.
@@ -10,8 +10,10 @@ import os.path # For concatenating the path to the plugin and the relative path
from UM.Application import Application # To get the machine manager to create the new profile in.
from UM.Logger import Logger # Logging errors.
from UM.PluginRegistry import PluginRegistry # For getting the path to this plugin's directory.
+from UM.Settings.ContainerRegistry import ContainerRegistry #To create unique profile IDs.
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
from cura.ProfileReader import ProfileReader # The plug-in type to implement.
+from cura.Settings.ExtruderManager import ExtruderManager #To get the current extruder definition.
## A plugin that reads profile data from legacy Cura versions.
@@ -77,7 +79,9 @@ class LegacyProfileReader(ProfileReader):
raise Exception("Unable to import legacy profile. Multi extrusion is not supported")
Logger.log("i", "Importing legacy profile from file " + file_name + ".")
- profile = InstanceContainer("Imported Legacy Profile") # Create an empty profile.
+ container_registry = ContainerRegistry.getInstance()
+ profile_id = container_registry.uniqueName("Imported Legacy Profile")
+ profile = InstanceContainer(profile_id) # Create an empty profile.
parser = configparser.ConfigParser(interpolation = None)
try:
@@ -120,8 +124,11 @@ class LegacyProfileReader(ProfileReader):
if "translation" not in dict_of_doom:
Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?")
return None
- current_printer_definition = global_container_stack.getBottom()
- profile.setDefinition(current_printer_definition.getId())
+ current_printer_definition = global_container_stack.definition
+ quality_definition = current_printer_definition.getMetaDataEntry("quality_definition")
+ if not quality_definition:
+ quality_definition = current_printer_definition.getId()
+ profile.setDefinition(quality_definition)
for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations.
old_setting_expression = dict_of_doom["translation"][new_setting]
compiled = compile(old_setting_expression, new_setting, "eval")
@@ -139,14 +146,13 @@ class LegacyProfileReader(ProfileReader):
if len(profile.getAllKeys()) == 0:
Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.")
-
- # We need to downgrade the container to version 1 (in Cura 2.1) so the upgrade system can correctly upgrade
- # it to the latest version.
profile.addMetaDataEntry("type", "profile")
# don't know what quality_type it is based on, so use "normal" by default
profile.addMetaDataEntry("quality_type", "normal")
+ profile.setName(profile_id)
profile.setDirty(True)
+ #Serialise and deserialise in order to perform the version upgrade.
parser = configparser.ConfigParser(interpolation=None)
data = profile.serialize()
parser.read_string(data)
@@ -159,4 +165,21 @@ class LegacyProfileReader(ProfileReader):
data = stream.getvalue()
profile.deserialize(data)
- return profile
+ # The definition can get reset to fdmprinter during the deserialization's upgrade. Here we set the definition
+ # again.
+ profile.setDefinition(quality_definition)
+
+ #We need to return one extruder stack and one global stack.
+ global_container_id = container_registry.uniqueName("Global Imported Legacy Profile")
+ global_profile = profile.duplicate(new_id = global_container_id, new_name = profile_id) #Needs to have the same name as the extruder profile.
+ global_profile.setDirty(True)
+
+ profile_definition = "fdmprinter"
+ from UM.Util import parseBool
+ if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")):
+ profile_definition = global_container_stack.getMetaDataEntry("quality_definition")
+ if not profile_definition:
+ profile_definition = global_container_stack.definition.getId()
+ global_profile.setDefinition(profile_definition)
+
+ return [global_profile]
diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py
index 7b407519e5..baa0639d3f 100755
--- a/plugins/MachineSettingsAction/MachineSettingsAction.py
+++ b/plugins/MachineSettingsAction/MachineSettingsAction.py
@@ -7,14 +7,11 @@ from UM.FlameProfiler import pyqtSlot
from cura.MachineAction import MachineAction
from UM.Application import Application
-from UM.Preferences import Preferences
-from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
-from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.CuraStackBuilder import CuraStackBuilder
@@ -30,13 +27,14 @@ class MachineSettingsAction(MachineAction):
self._qml_url = "MachineSettingsAction.qml"
self._global_container_stack = None
- self._container_index = 0
+
+ from cura.Settings.CuraContainerStack import _ContainerIndexes
+ self._container_index = _ContainerIndexes.DefinitionChanges
self._container_registry = ContainerRegistry.getInstance()
self._container_registry.containerAdded.connect(self._onContainerAdded)
self._container_registry.containerRemoved.connect(self._onContainerRemoved)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
- ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
self._empty_container = self._container_registry.getEmptyInstanceContainer()
@@ -67,7 +65,9 @@ class MachineSettingsAction(MachineAction):
self._global_container_stack, self._global_container_stack.getName() + "_settings")
# Notify the UI in which container to store the machine settings data
- container_index = self._global_container_stack.getContainerIndex(definition_changes_container)
+ from cura.Settings.CuraContainerStack import CuraContainerStack, _ContainerIndexes
+
+ container_index = _ContainerIndexes.DefinitionChanges
if container_index != self._container_index:
self._container_index = container_index
self.containerIndexChanged.emit()
@@ -82,17 +82,6 @@ class MachineSettingsAction(MachineAction):
if self._backend and self._backend.determineAutoSlicing():
self._backend.tickle()
- def _onActiveExtruderStackChanged(self):
- extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
- if not self._global_container_stack or not extruder_container_stack:
- return
-
- # Make sure there is a definition_changes container to store the machine settings
- definition_changes_container = extruder_container_stack.definitionChanges
- if definition_changes_container == self._empty_container:
- definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer(
- extruder_container_stack, extruder_container_stack.getId() + "_settings")
-
containerIndexChanged = pyqtSignal()
@pyqtProperty(int, notify = containerIndexChanged)
@@ -116,60 +105,9 @@ class MachineSettingsAction(MachineAction):
@pyqtSlot(int)
def setMachineExtruderCount(self, extruder_count):
- extruder_manager = Application.getInstance().getExtruderManager()
-
- definition_changes_container = self._global_container_stack.definitionChanges
- if not self._global_container_stack or definition_changes_container == self._empty_container:
- return
-
- previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
- if extruder_count == previous_extruder_count:
- return
-
- # reset all extruder number settings whose value is no longer valid
- for setting_instance in self._global_container_stack.userChanges.findInstances():
- setting_key = setting_instance.definition.key
- if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
- continue
-
- old_value = int(self._global_container_stack.userChanges.getProperty(setting_key, "value"))
- if old_value >= extruder_count:
- self._global_container_stack.userChanges.removeInstance(setting_key)
- Logger.log("d", "Reset [%s] because its old value [%s] is no longer valid ", setting_key, old_value)
-
- # Check to see if any objects are set to print with an extruder that will no longer exist
- root_node = Application.getInstance().getController().getScene().getRoot()
- for node in DepthFirstIterator(root_node):
- if node.getMeshData():
- extruder_nr = node.callDecoration("getActiveExtruderPosition")
-
- if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
- node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
-
- definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
-
- # Make sure one of the extruder stacks is active
- extruder_manager.setActiveExtruderIndex(0)
-
- # Move settable_per_extruder values out of the global container
- # After CURA-4482 this should not be the case anymore, but we still want to support older project files.
- global_user_container = self._global_container_stack.getTop()
-
- if previous_extruder_count == 1:
- extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
- global_user_container = self._global_container_stack.getTop()
-
- for setting_instance in global_user_container.findInstances():
- setting_key = setting_instance.definition.key
- settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
-
- if settable_per_extruder:
- limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
- extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
- extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
- global_user_container.removeInstance(setting_key)
-
- self.forceUpdate()
+ # Note: this method was in this class before, but since it's quite generic and other plugins also need it
+ # it was moved to the machine manager instead. Now this method just calls the machine manager.
+ Application.getInstance().getMachineManager().setActiveMachineExtruderCount(extruder_count)
@pyqtSlot()
def forceUpdate(self):
@@ -217,79 +155,7 @@ class MachineSettingsAction(MachineAction):
Application.getInstance().globalContainerStackChanged.emit()
- @pyqtSlot()
- def updateMaterialForDiameter(self):
+ @pyqtSlot(int)
+ def updateMaterialForDiameter(self, extruder_position: int):
# Updates the material container to a material that matches the material diameter set for the printer
- if not self._global_container_stack:
- return
-
- if not self._global_container_stack.getMetaDataEntry("has_materials", False):
- return
-
- material = ExtruderManager.getInstance().getActiveExtruderStack().material
- material_diameter = material.getProperty("material_diameter", "value")
- if not material_diameter:
- # in case of "empty" material
- material_diameter = 0
-
- material_approximate_diameter = str(round(material_diameter))
- definition_changes = self._global_container_stack.definitionChanges
- machine_diameter = definition_changes.getProperty("material_diameter", "value")
- if not machine_diameter:
- machine_diameter = self._global_container_stack.definition.getProperty("material_diameter", "value")
- machine_approximate_diameter = str(round(machine_diameter))
-
- if material_approximate_diameter != machine_approximate_diameter:
- Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
-
- stacks = ExtruderManager.getInstance().getExtruderStacks()
-
- if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
- materials_definition = self._global_container_stack.definition.getId()
- has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
- else:
- materials_definition = "fdmprinter"
- has_material_variants = False
-
- for stack in stacks:
- old_material = stack.material
- search_criteria = {
- "type": "material",
- "approximate_diameter": machine_approximate_diameter,
- "material": old_material.getMetaDataEntry("material", "value"),
- "supplier": old_material.getMetaDataEntry("supplier", "value"),
- "color_name": old_material.getMetaDataEntry("color_name", "value"),
- "definition": materials_definition
- }
- if has_material_variants:
- search_criteria["variant"] = stack.variant.getId()
-
- if old_material == self._empty_container:
- search_criteria.pop("material", None)
- search_criteria.pop("supplier", None)
- search_criteria.pop("definition", None)
- search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
-
- materials = self._container_registry.findInstanceContainers(**search_criteria)
- if not materials:
- # Same material with new diameter is not found, search for generic version of the same material type
- search_criteria.pop("supplier", None)
- search_criteria["color_name"] = "Generic"
- materials = self._container_registry.findInstanceContainers(**search_criteria)
- if not materials:
- # Generic material with new diameter is not found, search for preferred material
- search_criteria.pop("color_name", None)
- search_criteria.pop("material", None)
- search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
- materials = self._container_registry.findInstanceContainers(**search_criteria)
- if not materials:
- # Preferred material with new diameter is not found, search for any material
- search_criteria.pop("id", None)
- materials = self._container_registry.findInstanceContainers(**search_criteria)
- if not materials:
- # Just use empty material as a final fallback
- materials = [self._empty_container]
-
- Logger.log("i", "Selecting new material: %s" % materials[0].getId())
-
- stack.material = materials[0]
+ Application.getInstance().getExtruderManager().updateMaterialForDiameter(extruder_position)
diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.qml b/plugins/MachineSettingsAction/MachineSettingsAction.qml
index b36fb989f0..4d00904f76 100644
--- a/plugins/MachineSettingsAction/MachineSettingsAction.qml
+++ b/plugins/MachineSettingsAction/MachineSettingsAction.qml
@@ -70,8 +70,8 @@ Cura.MachineAction
anchors.top: pageTitle.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
- property real columnWidth: ((width - 3 * UM.Theme.getSize("default_margin").width) / 2) | 0
- property real labelColumnWidth: columnWidth * 0.5
+ property real columnWidth: Math.round((width - 3 * UM.Theme.getSize("default_margin").width) / 2)
+ property real labelColumnWidth: Math.round(columnWidth / 2)
Tab
{
@@ -165,7 +165,7 @@ Cura.MachineAction
id: gcodeFlavorComboBox
sourceComponent: comboBoxWithOptions
property string settingKey: "machine_gcode_flavor"
- property string label: catalog.i18nc("@label", "Gcode flavor")
+ property string label: catalog.i18nc("@label", "G-code flavor")
property bool forceUpdateOnChange: true
property var afterOnActivate: manager.updateHasMaterialsMetadata
}
@@ -270,6 +270,20 @@ Cura.MachineAction
}
}
}
+
+ Connections
+ {
+ target: manager
+ onDefinedExtruderCountChanged:
+ {
+ extruderCountModel.clear();
+ for(var i = 0; i < manager.definedExtruderCount; ++i)
+ {
+ extruderCountModel.append({text: String(i + 1), value: i});
+ }
+ }
+ }
+
currentIndex: machineExtruderCountProvider.properties.value - 1
onActivated:
{
@@ -278,18 +292,6 @@ Cura.MachineAction
}
}
}
-
- Loader
- {
- id: materialDiameterField
- visible: Cura.MachineManager.hasMaterials
- sourceComponent: numericTextFieldWithUnit
- property string settingKey: "material_diameter"
- property string unit: catalog.i18nc("@label", "mm")
- property string tooltip: catalog.i18nc("@tooltip", "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile.")
- property var afterOnEditingFinished: manager.updateMaterialForDiameter
- property string label: catalog.i18nc("@label", "Material diameter")
- }
}
}
@@ -305,7 +307,7 @@ Cura.MachineAction
width: settingsTabs.columnWidth
Label
{
- text: catalog.i18nc("@label", "Start Gcode")
+ text: catalog.i18nc("@label", "Start G-code")
font.bold: true
}
Loader
@@ -315,7 +317,7 @@ Cura.MachineAction
property int areaWidth: parent.width
property int areaHeight: parent.height - y
property string settingKey: "machine_start_gcode"
- property string tooltip: catalog.i18nc("@tooltip", "Gcode commands to be executed at the very start.")
+ property string tooltip: catalog.i18nc("@tooltip", "G-code commands to be executed at the very start.")
}
}
@@ -324,7 +326,7 @@ Cura.MachineAction
width: settingsTabs.columnWidth
Label
{
- text: catalog.i18nc("@label", "End Gcode")
+ text: catalog.i18nc("@label", "End G-code")
font.bold: true
}
Loader
@@ -334,7 +336,7 @@ Cura.MachineAction
property int areaWidth: parent.width
property int areaHeight: parent.height - y
property string settingKey: "machine_end_gcode"
- property string tooltip: catalog.i18nc("@tooltip", "Gcode commands to be executed at the very end.")
+ property string tooltip: catalog.i18nc("@tooltip", "G-code commands to be executed at the very end.")
}
}
}
@@ -346,7 +348,6 @@ Cura.MachineAction
if(currentIndex > 0)
{
contentItem.forceActiveFocus();
- Cura.ExtruderManager.setActiveExtruderIndex(currentIndex - 1);
}
}
@@ -383,6 +384,25 @@ Cura.MachineAction
property bool isExtruderSetting: true
}
+ Loader
+ {
+ id: materialDiameterField
+ visible: Cura.MachineManager.hasMaterials
+ sourceComponent: numericTextFieldWithUnit
+ property string settingKey: "material_diameter"
+ property string label: catalog.i18nc("@label", "Compatible material diameter")
+ property string unit: catalog.i18nc("@label", "mm")
+ property string tooltip: catalog.i18nc("@tooltip", "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile.")
+ function afterOnEditingFinished()
+ {
+ if (settingsTabs.currentIndex > 0)
+ {
+ manager.updateMaterialForDiameter(settingsTabs.currentIndex - 1);
+ }
+ }
+ property bool isExtruderSetting: true
+ }
+
Loader
{
id: extruderOffsetXField
@@ -421,7 +441,7 @@ Cura.MachineAction
width: settingsTabs.columnWidth
Label
{
- text: catalog.i18nc("@label", "Extruder Start Gcode")
+ text: catalog.i18nc("@label", "Extruder Start G-code")
font.bold: true
}
Loader
@@ -432,14 +452,14 @@ Cura.MachineAction
property int areaHeight: parent.height - y
property string settingKey: "machine_extruder_start_code"
property bool isExtruderSetting: true
- }
+ }
}
Column {
height: parent.height
width: settingsTabs.columnWidth
Label
{
- text: catalog.i18nc("@label", "Extruder End Gcode")
+ text: catalog.i18nc("@label", "Extruder End G-code")
font.bold: true
}
Loader
@@ -481,7 +501,7 @@ Cura.MachineAction
{
if(settingsTabs.currentIndex > 0)
{
- return Cura.MachineManager.activeStackId;
+ return Cura.ExtruderManager.extruderIds[String(settingsTabs.currentIndex - 1)];
}
return "";
}
@@ -499,11 +519,11 @@ Cura.MachineAction
checked: String(propertyProvider.properties.value).toLowerCase() != 'false'
onClicked:
{
- propertyProvider.setPropertyValue("value", checked);
- if(_forceUpdateOnChange)
- {
- manager.forceUpdate();
- }
+ propertyProvider.setPropertyValue("value", checked);
+ if(_forceUpdateOnChange)
+ {
+ manager.forceUpdate();
+ }
}
}
}
@@ -534,7 +554,7 @@ Cura.MachineAction
{
if(settingsTabs.currentIndex > 0)
{
- return Cura.MachineManager.activeStackId;
+ return Cura.ExtruderManager.extruderIds[String(settingsTabs.currentIndex - 1)];
}
return "";
}
@@ -567,8 +587,11 @@ Cura.MachineAction
TextField
{
id: textField
- text: (propertyProvider.properties.value) ? propertyProvider.properties.value : ""
- validator: RegExpValidator { regExp: _allowNegative ? /-?[0-9\.]{0,6}/ : /[0-9\.]{0,6}/ }
+ text: {
+ const value = propertyProvider.properties.value;
+ return value ? value : "";
+ }
+ validator: RegExpValidator { regExp: _allowNegative ? /-?[0-9\.,]{0,6}/ : /[0-9\.,]{0,6}/ }
onEditingFinished:
{
if (propertyProvider && text != propertyProvider.properties.value)
@@ -576,12 +599,7 @@ Cura.MachineAction
propertyProvider.setPropertyValue("value", text);
if(_forceUpdateOnChange)
{
- var extruderIndex = Cura.ExtruderManager.activeExtruderIndex;
manager.forceUpdate();
- if(Cura.ExtruderManager.activeExtruderIndex != extruderIndex)
- {
- Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex)
- }
}
if(_afterOnEditingFinished)
{
@@ -627,7 +645,7 @@ Cura.MachineAction
{
if(settingsTabs.currentIndex > 0)
{
- return Cura.MachineManager.activeStackId;
+ return Cura.ExtruderManager.extruderIds[String(settingsTabs.currentIndex - 1)];
}
return "";
}
@@ -714,7 +732,7 @@ Cura.MachineAction
width: gcodeArea.width
text: _tooltip
- property bool _isExtruderSetting: (typeof(isExtruderSetting) === 'undefined') ? false: isExtruderSetting
+ property bool _isExtruderSetting: (typeof(isExtruderSetting) === 'undefined') ? false : isExtruderSetting
property string _tooltip: (typeof(tooltip) === 'undefined') ? propertyProvider.properties.description : tooltip
UM.SettingPropertyProvider
@@ -726,7 +744,7 @@ Cura.MachineAction
{
if(settingsTabs.currentIndex > 0)
{
- return Cura.MachineManager.activeStackId;
+ return Cura.ExtruderManager.extruderIds[String(settingsTabs.currentIndex - 1)];
}
return "";
}
@@ -808,10 +826,10 @@ Cura.MachineAction
printHeadPolygon[axis][side] = result;
return result;
}
- validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
+ validator: RegExpValidator { regExp: /[0-9\.,]{0,6}/ }
onEditingFinished:
{
- printHeadPolygon[axis][side] = parseFloat(textField.text);
+ printHeadPolygon[axis][side] = parseFloat(textField.text.replace(',','.'));
var polygon = [];
polygon.push([-printHeadPolygon["x"]["min"], printHeadPolygon["y"]["max"]]);
polygon.push([-printHeadPolygon["x"]["min"],-printHeadPolygon["y"]["min"]]);
diff --git a/plugins/MonitorStage/MonitorMainView.qml b/plugins/MonitorStage/MonitorMainView.qml
index 15b05bed0a..c48f6d0aab 100644
--- a/plugins/MonitorStage/MonitorMainView.qml
+++ b/plugins/MonitorStage/MonitorMainView.qml
@@ -8,8 +8,9 @@ import Cura 1.0 as Cura
Item
{
- width: parent.width
- height: parent.height
+ // parent could be undefined as this component is not visible at all times
+ width: parent ? parent.width : 0
+ height: parent ? parent.height : 0
// We show a nice overlay on the 3D viewer when the current output device has no monitor view
Rectangle
diff --git a/plugins/MonitorStage/MonitorStage.py b/plugins/MonitorStage/MonitorStage.py
index 0736f49858..1a1d37cbdf 100644
--- a/plugins/MonitorStage/MonitorStage.py
+++ b/plugins/MonitorStage/MonitorStage.py
@@ -14,60 +14,124 @@ class MonitorStage(CuraStage):
super().__init__(parent)
# Wait until QML engine is created, otherwise creating the new QML components will fail
- Application.getInstance().engineCreatedSignal.connect(self._setComponents)
+ Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
+ self._printer_output_device = None
- # Update the status icon when the output device is changed
- Application.getInstance().getOutputDeviceManager().activeDeviceChanged.connect(self._setIconSource)
+ self._active_print_job = None
+ self._active_printer = None
- def _setComponents(self):
- self._setMainOverlay()
- self._setSidebar()
- self._setIconSource()
+ def _setActivePrintJob(self, print_job):
+ if self._active_print_job != print_job:
+ if self._active_print_job:
+ self._active_print_job.stateChanged.disconnect(self._updateIconSource)
+ self._active_print_job = print_job
+ if self._active_print_job:
+ self._active_print_job.stateChanged.connect(self._updateIconSource)
- def _setMainOverlay(self):
+ # Ensure that the right icon source is returned.
+ self._updateIconSource()
+
+ def _setActivePrinter(self, printer):
+ if self._active_printer != printer:
+ if self._active_printer:
+ self._active_printer.activePrintJobChanged.disconnect(self._onActivePrintJobChanged)
+ self._active_printer = printer
+ if self._active_printer:
+ self._setActivePrintJob(self._active_printer.activePrintJob)
+ # Jobs might change, so we need to listen to it's changes.
+ self._active_printer.activePrintJobChanged.connect(self._onActivePrintJobChanged)
+ else:
+ self._setActivePrintJob(None)
+
+ # Ensure that the right icon source is returned.
+ self._updateIconSource()
+
+ def _onActivePrintJobChanged(self):
+ self._setActivePrintJob(self._active_printer.activePrintJob)
+
+ def _onActivePrinterChanged(self):
+ self._setActivePrinter(self._printer_output_device.activePrinter)
+
+ def _onOutputDevicesChanged(self):
+ try:
+ # We assume that you are monitoring the device with the highest priority.
+ new_output_device = Application.getInstance().getMachineManager().printerOutputDevices[0]
+ if new_output_device != self._printer_output_device:
+ if self._printer_output_device:
+ self._printer_output_device.acceptsCommandsChanged.disconnect(self._updateIconSource)
+ self._printer_output_device.connectionStateChanged.disconnect(self._updateIconSource)
+ self._printer_output_device.printersChanged.disconnect(self._onActivePrinterChanged)
+
+ self._printer_output_device = new_output_device
+
+ self._printer_output_device.acceptsCommandsChanged.connect(self._updateIconSource)
+ self._printer_output_device.printersChanged.connect(self._onActivePrinterChanged)
+ self._printer_output_device.connectionStateChanged.connect(self._updateIconSource)
+ self._setActivePrinter(self._printer_output_device.activePrinter)
+
+ # Force an update of the icon source
+ self._updateIconSource()
+ except IndexError:
+ #If index error occurs, then the icon on monitor button also should be updated
+ self._updateIconSource()
+ pass
+
+ def _onEngineCreated(self):
+ # We can only connect now, as we need to be sure that everything is loaded (plugins get created quite early)
+ Application.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
+ self._onOutputDevicesChanged()
+ self._updateMainOverlay()
+ self._updateSidebar()
+ self._updateIconSource()
+
+ def _updateMainOverlay(self):
main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"), "MonitorMainView.qml")
self.addDisplayComponent("main", main_component_path)
- def _setSidebar(self):
+ def _updateSidebar(self):
# TODO: currently the sidebar component for prepare and monitor stages is the same, this will change with the printer output device refactor!
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml")
self.addDisplayComponent("sidebar", sidebar_component_path)
- def _setIconSource(self):
+ def _updateIconSource(self):
if Application.getInstance().getTheme() is not None:
icon_name = self._getActiveOutputDeviceStatusIcon()
self.setIconSource(Application.getInstance().getTheme().getIcon(icon_name))
## Find the correct status icon depending on the active output device state
def _getActiveOutputDeviceStatusIcon(self):
- output_device = Application.getInstance().getOutputDeviceManager().getActiveDevice()
-
- if not output_device:
+ # We assume that you are monitoring the device with the highest priority.
+ try:
+ output_device = Application.getInstance().getMachineManager().printerOutputDevices[0]
+ except IndexError:
return "tab_status_unknown"
- if hasattr(output_device, "acceptsCommands") and not output_device.acceptsCommands:
+ if not output_device.acceptsCommands:
return "tab_status_unknown"
- if not hasattr(output_device, "printerState") or not hasattr(output_device, "jobState"):
- return "tab_status_unknown"
-
- # TODO: refactor to use enum instead of hardcoded strings?
- if output_device.printerState == "maintenance":
- return "tab_status_busy"
-
- if output_device.jobState in ["printing", "pre_print", "pausing", "resuming"]:
- return "tab_status_busy"
-
- if output_device.jobState == "wait_cleanup":
- return "tab_status_finished"
-
- if output_device.jobState in ["ready", ""]:
+ if output_device.activePrinter is None:
return "tab_status_connected"
- if output_device.jobState == "paused":
+ # TODO: refactor to use enum instead of hardcoded strings?
+ if output_device.activePrinter.state == "maintenance":
+ return "tab_status_busy"
+
+ if output_device.activePrinter.activePrintJob is None:
+ return "tab_status_connected"
+
+ if output_device.activePrinter.activePrintJob.state in ["printing", "pre_print", "pausing", "resuming"]:
+ return "tab_status_busy"
+
+ if output_device.activePrinter.activePrintJob.state == "wait_cleanup":
+ return "tab_status_finished"
+
+ if output_device.activePrinter.activePrintJob.state in ["ready", ""]:
+ return "tab_status_connected"
+
+ if output_device.activePrinter.activePrintJob.state == "paused":
return "tab_status_paused"
- if output_device.jobState == "error":
+ if output_device.activePrinter.activePrintJob.state == "error":
return "tab_status_stopped"
return "tab_status_unknown"
diff --git a/plugins/PerObjectSettingsTool/PerObjectItem.qml b/plugins/PerObjectSettingsTool/PerObjectItem.qml
index 559ad2bf81..1317c00b19 100644
--- a/plugins/PerObjectSettingsTool/PerObjectItem.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectItem.qml
@@ -25,7 +25,20 @@ UM.TooltipArea
onClicked:
{
- addedSettingsModel.setVisible(model.key, checked);
+ // Important first set visible and then subscribe
+ // otherwise the setting is not yet in list
+ // For unsubscribe is important first remove the subscription and then
+ // set as invisible
+ if(checked)
+ {
+ addedSettingsModel.setVisible(model.key, checked);
+ UM.ActiveTool.triggerActionWithData("subscribeForSettingValidation", model.key)
+ }
+ else
+ {
+ UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
+ addedSettingsModel.setVisible(model.key, checked);
+ }
UM.ActiveTool.forceUpdate();
}
}
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py
index badca13468..3e1df1c7b8 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py
@@ -2,6 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
+from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
@@ -22,6 +23,9 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
self._node = None
self._stack = None
+ # this is a set of settings that will be skipped if the user chooses to reset.
+ self._skip_reset_setting_set = set()
+
def setSelectedObjectId(self, id):
if id != self._selected_object_id:
self._selected_object_id = id
@@ -36,6 +40,10 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
def selectedObjectId(self):
return self._selected_object_id
+ @pyqtSlot(str)
+ def addSkipResetSetting(self, setting_name):
+ self._skip_reset_setting_set.add(setting_name)
+
def setVisible(self, visible):
if not self._node:
return
@@ -50,6 +58,9 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
# Remove all instances that are not in visibility list
for instance in all_instances:
+ # exceptionally skip setting
+ if instance.definition.key in self._skip_reset_setting_set:
+ continue
if instance.definition.key not in visible:
settings.removeInstance(instance.definition.key)
visibility_changed = True
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
index ea126bfd44..e72e1224df 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
@@ -18,6 +18,9 @@ Item {
width: childrenRect.width;
height: childrenRect.height;
+ property var all_categories_except_support: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
+ "travel", "cooling", "platform_adhesion", "dual", "meshfix", "blackmagic", "experimental"]
+
Column
{
id: items
@@ -39,6 +42,13 @@ Item {
verticalAlignment: Text.AlignVCenter
}
+ UM.SettingPropertyProvider
+ {
+ id: meshTypePropertyProvider
+ containerStackId: Cura.MachineManager.activeMachineId
+ watchedProperties: [ "enabled" ]
+ }
+
ComboBox
{
id: meshTypeSelection
@@ -49,36 +59,55 @@ Item {
model: ListModel
{
id: meshTypeModel
- Component.onCompleted:
+ Component.onCompleted: meshTypeSelection.populateModel()
+ }
+
+ function populateModel()
+ {
+ meshTypeModel.append({
+ type: "",
+ text: catalog.i18nc("@label", "Normal model")
+ });
+ meshTypePropertyProvider.key = "support_mesh";
+ if(meshTypePropertyProvider.properties.enabled == "True")
{
- meshTypeModel.append({
- type: "",
- text: catalog.i18nc("@label", "Normal model")
- });
meshTypeModel.append({
type: "support_mesh",
text: catalog.i18nc("@label", "Print as support")
});
+ }
+ meshTypePropertyProvider.key = "anti_overhang_mesh";
+ if(meshTypePropertyProvider.properties.enabled == "True")
+ {
meshTypeModel.append({
type: "anti_overhang_mesh",
text: catalog.i18nc("@label", "Don't support overlap with other models")
});
+ }
+ meshTypePropertyProvider.key = "cutting_mesh";
+ if(meshTypePropertyProvider.properties.enabled == "True")
+ {
meshTypeModel.append({
type: "cutting_mesh",
text: catalog.i18nc("@label", "Modify settings for overlap with other models")
});
+ }
+ meshTypePropertyProvider.key = "infill_mesh";
+ if(meshTypePropertyProvider.properties.enabled == "True")
+ {
meshTypeModel.append({
type: "infill_mesh",
text: catalog.i18nc("@label", "Modify settings for infill of other models")
});
-
- meshTypeSelection.updateCurrentIndex();
}
+
+ meshTypeSelection.updateCurrentIndex();
}
function updateCurrentIndex()
{
var mesh_type = UM.ActiveTool.properties.getValue("MeshType");
+ meshTypeSelection.currentIndex = -1;
for(var index=0; index < meshTypeSelection.model.count; index++)
{
if(meshTypeSelection.model.get(index).type == mesh_type)
@@ -91,6 +120,16 @@ Item {
}
}
+ Connections
+ {
+ target: Cura.MachineManager
+ onGlobalContainerChanged:
+ {
+ meshTypeSelection.model.clear();
+ meshTypeSelection.populateModel();
+ }
+ }
+
Connections
{
target: UM.Selection
@@ -106,12 +145,12 @@ Item {
id: currentSettings
property int maximumHeight: 200 * screenScaleFactor
height: Math.min(contents.count * (UM.Theme.getSize("section").height + UM.Theme.getSize("default_lining").height), maximumHeight)
- visible: ["support_mesh", "anti_overhang_mesh"].indexOf(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type) == -1
+ visible: meshTypeSelection.model.get(meshTypeSelection.currentIndex).type != "anti_overhang_mesh"
ScrollView
{
height: parent.height
- width: UM.Theme.getSize("setting").width
+ width: UM.Theme.getSize("setting").width + UM.Theme.getSize("default_margin").width
style: UM.Theme.styles.scrollview
ListView
@@ -124,7 +163,15 @@ Item {
id: addedSettingsModel;
containerId: Cura.MachineManager.activeDefinitionId
expanded: [ "*" ]
- exclude: [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
+ exclude: {
+ var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
+
+ if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
+ {
+ excluded_settings = excluded_settings.concat(base.all_categories_except_support);
+ }
+ return excluded_settings;
+ }
visibilityHandler: Cura.PerObjectSettingVisibilityHandler
{
@@ -190,10 +237,13 @@ Item {
Button
{
- width: Math.floor(UM.Theme.getSize("setting").height / 2)
+ width: Math.round(UM.Theme.getSize("setting").height / 2)
height: UM.Theme.getSize("setting").height
- onClicked: addedSettingsModel.setVisible(model.key, false)
+ onClicked: {
+ addedSettingsModel.setVisible(model.key, false)
+ UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
+ }
style: ButtonStyle
{
@@ -306,7 +356,18 @@ Item {
}
}
- onClicked: settingPickDialog.visible = true;
+ onClicked:
+ {
+ settingPickDialog.visible = true;
+ if (meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
+ {
+ settingPickDialog.additional_excluded_settings = base.all_categories_except_support;
+ }
+ else
+ {
+ settingPickDialog.additional_excluded_settings = []
+ }
+ }
}
}
@@ -315,15 +376,18 @@ Item {
id: settingPickDialog
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
- width: screenScaleFactor * 360;
+ width: screenScaleFactor * 360
property string labelFilter: ""
+ property var additional_excluded_settings
onVisibilityChanged:
{
// force updating the model to sync it with addedSettingsModel
if(visible)
{
+ // Set skip setting, it will prevent from resetting selected mesh_type
+ contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
listview.model.forceUpdate()
}
}
@@ -394,7 +458,12 @@ Item {
}
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ]
- exclude: [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
+ exclude:
+ {
+ var excluded_settings = [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
+ excluded_settings = excluded_settings.concat(settingPickDialog.additional_excluded_settings);
+ return excluded_settings;
+ }
}
delegate:Loader
{
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
index d2db5ff420..b671db48fb 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py
@@ -10,7 +10,10 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.SettingInstance import SettingInstance
from UM.Event import Event
+from UM.Settings.Validator import ValidatorState
+from UM.Logger import Logger
+from PyQt5.QtCore import QTimer
## This tool allows the user to add & change settings per node in the scene.
# The settings per object are kept in a ContainerStack, which is linked to a node by decorator.
@@ -34,6 +37,12 @@ class PerObjectSettingsTool(Tool):
self._onGlobalContainerChanged()
Selection.selectionChanged.connect(self._updateEnabled)
+ self._scene = Application.getInstance().getController().getScene()
+
+ self._error_check_timer = QTimer()
+ self._error_check_timer.setInterval(250)
+ self._error_check_timer.setSingleShot(True)
+ self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
def event(self, event):
super().event(event)
@@ -142,3 +151,65 @@ class PerObjectSettingsTool(Tool):
else:
self._single_model_selected = True
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected)
+
+
+ def _onPropertyChanged(self, key: str, property_name: str) -> None:
+ if property_name == "validationState":
+ # self._error_check_timer.start()
+ return
+
+ def _updateStacksHaveErrors(self) -> None:
+ return
+ # self._checkStacksHaveErrors()
+
+
+ def _checkStacksHaveErrors(self):
+
+ for node in DepthFirstIterator(self._scene.getRoot()):
+
+ # valdiate only objects which can be selected because the settings per object
+ # can be applied only for them
+ if not node.isSelectable():
+ continue
+
+ hasErrors = self._checkStackForErrors(node.callDecoration("getStack"))
+ Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
+
+ #If any of models has an error then no reason check next objects on the build plate
+ if hasErrors:
+ break
+
+
+ def _checkStackForErrors(self, stack):
+ print("checking for errors")
+ if stack is None:
+ return False
+
+ for key in stack.getAllKeys():
+ validation_state = stack.getProperty(key, "validationState")
+ if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
+ Logger.log("w", "Setting Per Object %s is not valid.", key)
+ return True
+ return False
+
+ def subscribeForSettingValidation(self, setting_name):
+ selected_object = Selection.getSelectedObject(0)
+ stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway.
+ if not stack:
+ return ""
+
+ settings = stack.getTop()
+ setting_instance = settings.getInstance(setting_name)
+ if setting_instance:
+ setting_instance.propertyChanged.connect(self._onPropertyChanged)
+
+ def unsubscribeForSettingValidation(self, setting_name):
+ selected_object = Selection.getSelectedObject(0)
+ stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway.
+ if not stack:
+ return ""
+
+ settings = stack.getTop()
+ setting_instance = settings.getInstance(setting_name)
+ if setting_instance:
+ setting_instance.propertyChanged.disconnect(self._onPropertyChanged)
diff --git a/plugins/PluginBrowser/PluginBrowser.py b/plugins/PluginBrowser/PluginBrowser.py
index 35b88b3465..c8a5e1e545 100644
--- a/plugins/PluginBrowser/PluginBrowser.py
+++ b/plugins/PluginBrowser/PluginBrowser.py
@@ -1,31 +1,36 @@
# Copyright (c) 2017 Ultimaker B.V.
# PluginBrowser is released under the terms of the LGPLv3 or higher.
+
+from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
+from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
+
+from UM.Application import Application
+from UM.Qt.ListModel import ListModel
+from UM.Logger import Logger
+from UM.PluginRegistry import PluginRegistry
+from UM.Qt.Bindings.PluginsModel import PluginsModel
from UM.Extension import Extension
from UM.i18n import i18nCatalog
-from UM.Logger import Logger
-from UM.Qt.ListModel import ListModel
-from UM.PluginRegistry import PluginRegistry
-from UM.Application import Application
+
from UM.Version import Version
from UM.Message import Message
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
-from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
-
import json
import os
import tempfile
import platform
import zipfile
+import shutil
+
+from cura.CuraApplication import CuraApplication
i18n_catalog = i18nCatalog("cura")
-
class PluginBrowser(QObject, Extension):
def __init__(self, parent=None):
super().__init__(parent)
- self._api_version = 2
+ self._api_version = 4
self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version
self._plugin_list_request = None
@@ -34,11 +39,18 @@ class PluginBrowser(QObject, Extension):
self._download_plugin_reply = None
self._network_manager = None
+ self._plugin_registry = Application.getInstance().getPluginRegistry()
self._plugins_metadata = []
self._plugins_model = None
+ # Can be 'installed' or 'availble'
+ self._view = "available"
+
+ self._restart_required = False
+
self._dialog = None
+ self._restartDialog = None
self._download_progress = 0
self._is_downloading = False
@@ -52,16 +64,29 @@ class PluginBrowser(QObject, Extension):
)
]
- # Installed plugins are really installed after reboot. In order to prevent the user from downloading the
- # same file over and over again, we keep track of the upgraded plugins.
+ # Installed plugins are really installed after reboot. In order to
+ # prevent the user from downloading the same file over and over again,
+ # we keep track of the upgraded plugins.
+
+ # NOTE: This will be depreciated in favor of the 'status' system.
self._newly_installed_plugin_ids = []
+ self._newly_uninstalled_plugin_ids = []
+
+ self._plugin_statuses = {} # type: Dict[str, str]
# variables for the license agreement dialog
self._license_dialog_plugin_name = ""
self._license_dialog_license_content = ""
self._license_dialog_plugin_file_location = ""
+ self._restart_dialog_message = ""
showLicenseDialog = pyqtSignal()
+ showRestartDialog = pyqtSignal()
+ pluginsMetadataChanged = pyqtSignal()
+ onDownloadProgressChanged = pyqtSignal()
+ onIsDownloadingChanged = pyqtSignal()
+ restartRequiredChanged = pyqtSignal()
+ viewChanged = pyqtSignal()
@pyqtSlot(result = str)
def getLicenseDialogPluginName(self):
@@ -75,15 +100,19 @@ class PluginBrowser(QObject, Extension):
def getLicenseDialogLicenseContent(self):
return self._license_dialog_license_content
+ @pyqtSlot(result = str)
+ def getRestartDialogMessage(self):
+ return self._restart_dialog_message
+
def openLicenseDialog(self, plugin_name, license_content, plugin_file_location):
self._license_dialog_plugin_name = plugin_name
self._license_dialog_license_content = license_content
self._license_dialog_plugin_file_location = plugin_file_location
self.showLicenseDialog.emit()
- pluginsMetadataChanged = pyqtSignal()
- onDownloadProgressChanged = pyqtSignal()
- onIsDownloadingChanged = pyqtSignal()
+ def openRestartDialog(self, message):
+ self._restart_dialog_message = message
+ self.showRestartDialog.emit()
@pyqtProperty(bool, notify = onIsDownloadingChanged)
def isDownloading(self):
@@ -179,17 +208,46 @@ class PluginBrowser(QObject, Extension):
@pyqtSlot(str)
def installPlugin(self, file_path):
+ # Ensure that it starts with a /, as otherwise it doesn't work on windows.
if not file_path.startswith("/"):
- location = "/" + file_path # Ensure that it starts with a /, as otherwise it doesn't work on windows.
+ location = "/" + file_path
else:
location = file_path
+
result = PluginRegistry.getInstance().installPlugin("file://" + location)
self._newly_installed_plugin_ids.append(result["id"])
self.pluginsMetadataChanged.emit()
+ self.openRestartDialog(result["message"])
+ self._restart_required = True
+ self.restartRequiredChanged.emit()
+ # Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
+
+ @pyqtSlot(str)
+ def removePlugin(self, plugin_id):
+ result = PluginRegistry.getInstance().uninstallPlugin(plugin_id)
+
+ self._newly_uninstalled_plugin_ids.append(result["id"])
+ self.pluginsMetadataChanged.emit()
+
+ self._restart_required = True
+ self.restartRequiredChanged.emit()
+
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
+ @pyqtSlot(str)
+ def enablePlugin(self, plugin_id):
+ self._plugin_registry.enablePlugin(plugin_id)
+ self.pluginsMetadataChanged.emit()
+ Logger.log("i", "%s was set as 'active'", id)
+
+ @pyqtSlot(str)
+ def disablePlugin(self, plugin_id):
+ self._plugin_registry.disablePlugin(plugin_id)
+ self.pluginsMetadataChanged.emit()
+ Logger.log("i", "%s was set as 'deactive'", id)
+
@pyqtProperty(int, notify = onDownloadProgressChanged)
def downloadProgress(self):
return self._download_progress
@@ -221,55 +279,70 @@ class PluginBrowser(QObject, Extension):
self.setDownloadProgress(0)
self.setIsDownloading(False)
+ @pyqtSlot(str)
+ def setView(self, view):
+ self._view = view
+ self.viewChanged.emit()
+ self.pluginsMetadataChanged.emit()
+
@pyqtProperty(QObject, notify=pluginsMetadataChanged)
def pluginsModel(self):
- if self._plugins_model is None:
- self._plugins_model = ListModel()
- self._plugins_model.addRoleName(Qt.UserRole + 1, "name")
- self._plugins_model.addRoleName(Qt.UserRole + 2, "version")
- self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description")
- self._plugins_model.addRoleName(Qt.UserRole + 4, "author")
- self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed")
- self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location")
- self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade")
- else:
- self._plugins_model.clear()
- items = []
- for metadata in self._plugins_metadata:
- items.append({
- "name": metadata["label"],
- "version": metadata["version"],
- "short_description": metadata["short_description"],
- "author": metadata["author"],
- "already_installed": self._checkAlreadyInstalled(metadata["id"]),
- "file_location": metadata["file_location"],
- "can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"])
- })
- self._plugins_model.setItems(items)
+ self._plugins_model = PluginsModel(None, self._view)
+ # self._plugins_model.update()
+
+ # Check each plugin the registry for matching plugin from server
+ # metadata, and if found, compare the versions. Higher version sets
+ # 'can_upgrade' to 'True':
+ for plugin in self._plugins_model.items:
+ if self._checkCanUpgrade(plugin["id"], plugin["version"]):
+ plugin["can_upgrade"] = True
+
+ for item in self._plugins_metadata:
+ if item["id"] == plugin["id"]:
+ plugin["update_url"] = item["file_location"]
+
return self._plugins_model
+
+
def _checkCanUpgrade(self, id, version):
- plugin_registry = PluginRegistry.getInstance()
- metadata = plugin_registry.getMetaData(id)
- if metadata != {}:
- if id in self._newly_installed_plugin_ids:
- return False # We already updated this plugin.
- current_version = Version(metadata["plugin"]["version"])
- new_version = Version(version)
- if new_version > current_version:
- return True
+
+ # TODO: This could maybe be done more efficiently using a dictionary...
+
+ # Scan plugin server data for plugin with the given id:
+ for plugin in self._plugins_metadata:
+ if id == plugin["id"]:
+ reg_version = Version(version)
+ new_version = Version(plugin["version"])
+ if new_version > reg_version:
+ Logger.log("i", "%s has an update availible: %s", plugin["id"], plugin["version"])
+ return True
return False
def _checkAlreadyInstalled(self, id):
- plugin_registry = PluginRegistry.getInstance()
- metadata = plugin_registry.getMetaData(id)
- if metadata != {}:
+ metadata = self._plugin_registry.getMetaData(id)
+ # We already installed this plugin, but the registry just doesn't know it yet.
+ if id in self._newly_installed_plugin_ids:
+ return True
+ # We already uninstalled this plugin, but the registry just doesn't know it yet:
+ elif id in self._newly_uninstalled_plugin_ids:
+ return False
+ elif metadata != {}:
return True
else:
- if id in self._newly_installed_plugin_ids:
- return True # We already installed this plugin, but the registry just doesn't know it yet.
return False
+ def _checkInstallStatus(self, plugin_id):
+ if plugin_id in self._plugin_registry.getInstalledPlugins():
+ return "installed"
+ else:
+ return "uninstalled"
+
+ def _checkEnabled(self, id):
+ if id in self._plugin_registry.getActivePlugins():
+ return True
+ return False
+
def _onRequestFinished(self, reply):
reply_url = reply.url().toString()
if reply.error() == QNetworkReply.TimeoutError:
@@ -290,7 +363,10 @@ class PluginBrowser(QObject, Extension):
if reply_url == self._api_url + "plugins":
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
+
+ # Add metadata to the manager:
self._plugins_metadata = json_data
+ self._plugin_registry.addExternalPlugins(self._plugins_metadata)
self.pluginsMetadataChanged.emit()
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
@@ -316,3 +392,15 @@ class PluginBrowser(QObject, Extension):
self._network_manager = QNetworkAccessManager()
self._network_manager.finished.connect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)
+
+ @pyqtProperty(bool, notify=restartRequiredChanged)
+ def restartRequired(self):
+ return self._restart_required
+
+ @pyqtProperty(str, notify=viewChanged)
+ def viewing(self):
+ return self._view
+
+ @pyqtSlot()
+ def restart(self):
+ CuraApplication.getInstance().quit()
diff --git a/plugins/PluginBrowser/PluginBrowser.qml b/plugins/PluginBrowser/PluginBrowser.qml
index 13000d23ad..ec4c2a9135 100644
--- a/plugins/PluginBrowser/PluginBrowser.qml
+++ b/plugins/PluginBrowser/PluginBrowser.qml
@@ -1,191 +1,209 @@
-import UM 1.1 as UM
+// Copyright (c) 2017 Ultimaker B.V.
+// PluginBrowser is released under the terms of the LGPLv3 or higher.
+
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
-import QtQuick.Controls 1.1
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
-UM.Dialog
-{
+// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
+
+import UM 1.1 as UM
+
+Window {
id: base
- title: catalog.i18nc("@title:window", "Find & Update plugins")
- width: 600 * screenScaleFactor
- height: 450 * screenScaleFactor
+ title: catalog.i18nc("@title:tab", "Plugins");
+ width: 800 * screenScaleFactor
+ height: 640 * screenScaleFactor
minimumWidth: 350 * screenScaleFactor
minimumHeight: 350 * screenScaleFactor
- Item
- {
- anchors.fill: parent
- Item
- {
- id: topBar
- height: childrenRect.height;
- width: parent.width
- Label
- {
- id: introText
- text: catalog.i18nc("@label", "Here you can find a list of Third Party plugins.")
- width: parent.width
- height: 30
- }
+ color: UM.Theme.getColor("sidebar")
- Button
- {
- id: refresh
- text: catalog.i18nc("@action:button", "Refresh")
- onClicked: manager.requestPluginList()
- anchors.right: parent.right
- enabled: !manager.isDownloading
+ Item {
+ id: view
+ anchors {
+ fill: parent
+ leftMargin: UM.Theme.getSize("default_margin").width
+ rightMargin: UM.Theme.getSize("default_margin").width
+ topMargin: UM.Theme.getSize("default_margin").height
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+
+ Rectangle {
+ id: topBar
+ width: parent.width
+ color: "transparent"
+ height: childrenRect.height
+
+ Row {
+ spacing: 12
+ height: childrenRect.height
+ width: childrenRect.width
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Button {
+ text: "Install"
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 48
+ Rectangle {
+ visible: manager.viewing == "available" ? true : false
+ color: UM.Theme.getColor("primary")
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: 3
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ font {
+ pixelSize: 15
+ }
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: manager.setView("available")
+ }
+
+ Button {
+ text: "Manage"
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 48
+ Rectangle {
+ visible: manager.viewing == "installed" ? true : false
+ color: UM.Theme.getColor("primary")
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: 3
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ font {
+ pixelSize: 15
+ }
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: manager.setView("installed")
+ }
}
}
- ScrollView
- {
+
+ // Scroll view breaks in QtQuick.Controls 2.x
+ ScrollView {
+ id: installedPluginList
width: parent.width
- anchors.top: topBar.bottom
- anchors.bottom: bottomBar.top
- anchors.bottomMargin: UM.Theme.getSize("default_margin").height
+ height: 400
+
+ anchors {
+ top: topBar.bottom
+ topMargin: UM.Theme.getSize("default_margin").height
+ bottom: bottomBar.top
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+
frameVisible: true
- ListView
- {
+
+ ListView {
id: pluginList
- model: manager.pluginsModel
+ property var activePlugin
+ property var filter: "installed"
+
anchors.fill: parent
- property var activePlugin
- delegate: pluginDelegate
+ model: manager.pluginsModel
+ delegate: PluginEntry {}
}
}
- Item
- {
+
+ Rectangle {
id: bottomBar
width: parent.width
- height: closeButton.height
+ height: childrenRect.height
+ color: "transparent"
anchors.bottom: parent.bottom
- anchors.left: parent.left
- ProgressBar
- {
- id: progressbar
- anchors.bottom: parent.bottom
- minimumValue: 0;
- maximumValue: 100
- anchors.left:parent.left
+
+ Label {
+ visible: manager.restartRequired
+ text: "You will need to restart Cura before changes in plugins have effect."
+ height: 30
+ verticalAlignment: Text.AlignVCenter
+ }
+ Button {
+ id: restartChangedButton
+ text: "Quit Cura"
anchors.right: closeButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
- value: manager.isDownloading ? manager.downloadProgress : 0
+ visible: manager.restartRequired
+ iconName: "dialog-restart"
+ onClicked: manager.restart()
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: UM.Theme.getColor("primary")
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("button_text")
+ font {
+ pixelSize: 13
+ bold: true
+ }
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
}
- Button
- {
+ Button {
id: closeButton
text: catalog.i18nc("@action:button", "Close")
iconName: "dialog-close"
- onClicked:
- {
- if (manager.isDownloading)
- {
+ onClicked: {
+ if ( manager.isDownloading ) {
manager.cancelDownload()
}
base.close();
}
- anchors.bottom: parent.bottom
anchors.right: parent.right
- }
- }
-
- Item
- {
- SystemPalette { id: palette }
- Component
- {
- id: pluginDelegate
- Rectangle
- {
- width: pluginList.width;
- height: texts.height;
- color: index % 2 ? palette.base : palette.alternateBase
- Column
- {
- id: texts
- width: parent.width
- height: childrenRect.height
- anchors.left: parent.left
- anchors.leftMargin: UM.Theme.getSize("default_margin").width
- anchors.right: downloadButton.left
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- Label
- {
- text: "" + model.name + "" + ((model.author !== "") ? (" - " + model.author) : "")
- width: contentWidth
- height: contentHeight + UM.Theme.getSize("default_margin").height
- verticalAlignment: Text.AlignVCenter
- }
-
- Label
- {
- text: model.short_description
- width: parent.width
- height: contentHeight + UM.Theme.getSize("default_margin").height
- wrapMode: Text.WordWrap
- verticalAlignment: Text.AlignVCenter
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
}
}
- Button
- {
- id: downloadButton
- text:
- {
- if (manager.isDownloading && pluginList.activePlugin == model)
- {
- return catalog.i18nc("@action:button", "Cancel");
- }
- else if (model.already_installed)
- {
- if (model.can_upgrade)
- {
- return catalog.i18nc("@action:button", "Upgrade");
- }
- return catalog.i18nc("@action:button", "Installed");
- }
- return catalog.i18nc("@action:button", "Download");
- }
- onClicked:
- {
- if(!manager.isDownloading)
- {
- pluginList.activePlugin = model;
- manager.downloadAndInstallPlugin(model.file_location);
- }
- else
- {
- manager.cancelDownload();
- }
- }
- anchors.right: parent.right
- anchors.rightMargin: UM.Theme.getSize("default_margin").width
- anchors.verticalCenter: parent.verticalCenter
- enabled:
- {
- if (manager.isDownloading)
- {
- return (pluginList.activePlugin == model);
- }
- else
- {
- return (!model.already_installed || model.can_upgrade);
- }
- }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("text")
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
}
}
-
}
}
+
UM.I18nCatalog { id: catalog; name: "cura" }
- Connections
- {
+ Connections {
target: manager
- onShowLicenseDialog:
- {
+ onShowLicenseDialog: {
licenseDialog.pluginName = manager.getLicenseDialogPluginName();
licenseDialog.licenseContent = manager.getLicenseDialogLicenseContent();
licenseDialog.pluginFileLocation = manager.getLicenseDialogPluginFileLocation();
@@ -193,8 +211,7 @@ UM.Dialog
}
}
- UM.Dialog
- {
+ UM.Dialog {
id: licenseDialog
title: catalog.i18nc("@title:window", "Plugin License Agreement")
@@ -258,5 +275,94 @@ UM.Dialog
}
]
}
+
+ Connections {
+ target: manager
+ onShowRestartDialog: {
+ restartDialog.message = manager.getRestartDialogMessage();
+ restartDialog.show();
+ }
+ }
+
+ Window {
+ id: restartDialog
+ // title: catalog.i18nc("@title:tab", "Plugins");
+ width: 360 * screenScaleFactor
+ height: 120 * screenScaleFactor
+ minimumWidth: 360 * screenScaleFactor
+ minimumHeight: 120 * screenScaleFactor
+ color: UM.Theme.getColor("sidebar")
+ property var message;
+
+ Text {
+ id: message
+ anchors {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_margin").height
+ }
+ text: restartDialog.message != null ? restartDialog.message : ""
+ }
+ Button {
+ id: laterButton
+ text: "Later"
+ onClicked: restartDialog.close();
+ anchors {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ bottom: parent.bottom
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("text")
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+
+
+ Button {
+ id: restartButton
+ text: "Quit Cura"
+ anchors {
+ right: parent.right
+ rightMargin: UM.Theme.getSize("default_margin").width
+ bottom: parent.bottom
+ bottomMargin: UM.Theme.getSize("default_margin").height
+ }
+ onClicked: manager.restart()
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: UM.Theme.getColor("primary")
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("button_text")
+ font {
+ pixelSize: 13
+ bold: true
+ }
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+ }
+
}
}
diff --git a/plugins/PluginBrowser/PluginEntry.qml b/plugins/PluginBrowser/PluginEntry.qml
new file mode 100644
index 0000000000..eff9eb8943
--- /dev/null
+++ b/plugins/PluginBrowser/PluginEntry.qml
@@ -0,0 +1,474 @@
+// Copyright (c) 2017 Ultimaker B.V.
+// PluginBrowser is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Dialogs 1.1
+import QtQuick.Window 2.2
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+
+// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
+
+import UM 1.1 as UM
+
+Component {
+ id: pluginDelegate
+
+ Rectangle {
+
+ // Don't show required plugins as they can't be managed anyway:
+ height: !model.required ? 84 : 0
+ visible: !model.required ? true : false
+ color: "transparent"
+ anchors {
+ left: parent.left
+ leftMargin: UM.Theme.getSize("default_margin").width
+ right: parent.right
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+
+
+ // Bottom border:
+ Rectangle {
+ color: UM.Theme.getColor("lining")
+ width: parent.width
+ height: 1
+ anchors.bottom: parent.bottom
+ }
+
+ // Plugin info
+ Column {
+ id: pluginInfo
+
+ property var color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
+
+ // Styling:
+ height: parent.height
+ anchors {
+ left: parent.left
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_margin").height
+ right: authorInfo.left
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+
+
+ Label {
+ text: model.name
+ width: parent.width
+ height: 24
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ font {
+ pixelSize: 13
+ bold: true
+ }
+ color: pluginInfo.color
+
+ }
+
+ Text {
+ text: model.description
+ width: parent.width
+ height: 36
+ clip: true
+ wrapMode: Text.WordWrap
+ color: pluginInfo.color
+ elide: Text.ElideRight
+ }
+ }
+
+ // Author info
+ Column {
+ id: authorInfo
+ width: 192
+ height: parent.height
+ anchors {
+ top: parent.top
+ topMargin: UM.Theme.getSize("default_margin").height
+ right: pluginActions.left
+ rightMargin: UM.Theme.getSize("default_margin").width
+ }
+
+ Label {
+ text: ""+model.author+""
+ width: parent.width
+ height: 24
+ wrapMode: Text.WordWrap
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ onLinkActivated: Qt.openUrlExternally("mailto:"+model.author_email+"?Subject=Cura: "+model.name+" Plugin")
+ color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
+ }
+ }
+
+ // Plugin actions
+ Row {
+ id: pluginActions
+
+ width: 96
+ height: parent.height
+ anchors {
+ top: parent.top
+ right: parent.right
+ topMargin: UM.Theme.getSize("default_margin").height
+ }
+ layoutDirection: Qt.RightToLeft
+ spacing: UM.Theme.getSize("default_margin").width
+
+ // For 3rd-Party Plugins:
+ Button {
+ id: installButton
+ text: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return catalog.i18nc( "@action:button", "Cancel" );
+ } else {
+ if (model.can_upgrade) {
+ return catalog.i18nc("@action:button", "Update");
+ }
+ return catalog.i18nc("@action:button", "Install");
+ }
+ }
+ visible: model.external && ((model.status !== "installed") || model.can_upgrade)
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: "transparent"
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Label {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ manager.cancelDownload();
+ } else {
+ pluginList.activePlugin = model;
+ if ( model.can_upgrade ) {
+ manager.downloadAndInstallPlugin( model.update_url );
+ } else {
+ manager.downloadAndInstallPlugin( model.file_location );
+ }
+
+ }
+ }
+ }
+ Button {
+ id: removeButton
+ text: "Uninstall"
+ visible: model.can_uninstall && model.status == "installed"
+ enabled: !manager.isDownloading
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: "transparent"
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: manager.removePlugin( model.id )
+ }
+
+ // For Ultimaker Plugins:
+ Button {
+ id: enableButton
+ text: "Enable"
+ visible: !model.external && model.enabled == false
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: "transparent"
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: {
+ manager.enablePlugin(model.id);
+ }
+ }
+ Button {
+ id: disableButton
+ text: "Disable"
+ visible: !model.external && model.enabled == true
+ style: ButtonStyle {
+ background: Rectangle {
+ implicitWidth: 96
+ implicitHeight: 30
+ color: "transparent"
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ text: control.text
+ color: UM.Theme.getColor("text")
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ onClicked: {
+ manager.disablePlugin(model.id);
+ }
+ }
+ /*
+ Rectangle {
+ id: removeControls
+ visible: model.status == "installed" && model.enabled
+ width: 96
+ height: 30
+ color: "transparent"
+ Button {
+ id: removeButton
+ text: "Disable"
+ enabled: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return false;
+ } else if ( model.required ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ onClicked: {
+ manager.disablePlugin(model.id);
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "white"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: "grey"
+ text: control.text
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+ }
+ Button {
+ id: removeDropDown
+ property bool open: false
+ UM.RecolorImage {
+ anchors.centerIn: parent
+ height: 10
+ width: 10
+ source: UM.Theme.getIcon("arrow_bottom")
+ color: "grey"
+ }
+ enabled: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return false;
+ } else if ( model.required ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ anchors.right: parent.right
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 30
+ implicitHeight: 30
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: "grey"
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+
+
+
+ // For the disable option:
+ // onClicked: pluginList.model.setEnabled(model.id, checked)
+
+ onClicked: {
+ if ( !removeDropDown.open ) {
+ removeDropDown.open = true
+ }
+ else {
+ removeDropDown.open = false
+ }
+ }
+ }
+
+ Rectangle {
+ id: divider
+ width: 1
+ height: parent.height
+ anchors.right: removeDropDown.left
+ color: UM.Theme.getColor("lining")
+ }
+
+ Column {
+ id: options
+ anchors {
+ top: removeButton.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: childrenRect.height
+ visible: removeDropDown.open
+
+ Button {
+ id: disableButton
+ text: "Remove"
+ height: 30
+ width: parent.width
+ onClicked: {
+ removeDropDown.open = false;
+ manager.removePlugin( model.id );
+ }
+ }
+ }
+ }
+ */
+ /*
+ Button {
+ id: enableButton
+ visible: !model.enabled && model.status == "installed"
+ onClicked: manager.enablePlugin( model.id );
+
+ text: "Enable"
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: UM.Theme.getColor("text")
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+
+ Button {
+ id: updateButton
+ visible: model.status == "installed" && model.can_upgrade && model.enabled
+ // visible: model.already_installed
+ text: {
+ // If currently downloading:
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return catalog.i18nc( "@action:button", "Cancel" );
+ } else {
+ return catalog.i18nc("@action:button", "Update");
+ }
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ color: UM.Theme.getColor("primary")
+ implicitWidth: 96
+ implicitHeight: 30
+ // radius: 4
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: "white"
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+ Button {
+ id: externalControls
+ visible: model.status == "available" ? true : false
+ text: {
+ // If currently downloading:
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ return catalog.i18nc( "@action:button", "Cancel" );
+ } else {
+ return catalog.i18nc("@action:button", "Install");
+ }
+ }
+ onClicked: {
+ if ( manager.isDownloading && pluginList.activePlugin == model ) {
+ manager.cancelDownload();
+ } else {
+ pluginList.activePlugin = model;
+ manager.downloadAndInstallPlugin( model.file_location );
+ }
+ }
+ style: ButtonStyle {
+ background: Rectangle {
+ color: "transparent"
+ implicitWidth: 96
+ implicitHeight: 30
+ border {
+ width: 1
+ color: UM.Theme.getColor("lining")
+ }
+ }
+ label: Text {
+ verticalAlignment: Text.AlignVCenter
+ color: "grey"
+ text: control.text
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+ */
+ ProgressBar {
+ id: progressbar
+ minimumValue: 0;
+ maximumValue: 100
+ anchors.left: installButton.left
+ anchors.right: installButton.right
+ anchors.top: installButton.bottom
+ anchors.topMargin: 4
+ value: manager.isDownloading ? manager.downloadProgress : 0
+ visible: manager.isDownloading && pluginList.activePlugin == model
+ style: ProgressBarStyle {
+ background: Rectangle {
+ color: "lightgray"
+ implicitHeight: 6
+ }
+ progress: Rectangle {
+ color: UM.Theme.getColor("primary")
+ }
+ }
+ }
+
+ }
+ }
+}
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
new file mode 100644
index 0000000000..566b05abf3
--- /dev/null
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
@@ -0,0 +1,209 @@
+# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
+
+from UM.PluginRegistry import PluginRegistry
+from UM.Resources import Resources
+from UM.Application import Application
+from UM.Extension import Extension
+from UM.Logger import Logger
+
+import os.path
+import pkgutil
+import sys
+import importlib.util
+
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("cura")
+
+
+## The post processing plugin is an Extension type plugin that enables pre-written scripts to post process generated
+# g-code files.
+class PostProcessingPlugin(QObject, Extension):
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
+ self._view = None
+
+ # Loaded scripts are all scripts that can be used
+ self._loaded_scripts = {}
+ self._script_labels = {}
+
+ # Script list contains instances of scripts in loaded_scripts.
+ # There can be duplicates, which will be executed in sequence.
+ self._script_list = []
+ self._selected_script_index = -1
+
+ Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
+
+ selectedIndexChanged = pyqtSignal()
+ @pyqtProperty("QVariant", notify = selectedIndexChanged)
+ def selectedScriptDefinitionId(self):
+ try:
+ return self._script_list[self._selected_script_index].getDefinitionId()
+ except:
+ return ""
+
+ @pyqtProperty("QVariant", notify=selectedIndexChanged)
+ def selectedScriptStackId(self):
+ try:
+ return self._script_list[self._selected_script_index].getStackId()
+ except:
+ return ""
+
+ ## Execute all post-processing scripts on the gcode.
+ def execute(self, output_device):
+ scene = Application.getInstance().getController().getScene()
+ # If the scene does not have a gcode, do nothing
+ if not hasattr(scene, "gcode_dict"):
+ return
+ gcode_dict = getattr(scene, "gcode_dict")
+ if not gcode_dict:
+ return
+
+ # get gcode list for the active build plate
+ active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ gcode_list = gcode_dict[active_build_plate_id]
+ if not gcode_list:
+ return
+
+ if ";POSTPROCESSED" not in gcode_list[0]:
+ for script in self._script_list:
+ try:
+ gcode_list = script.execute(gcode_list)
+ except Exception:
+ Logger.logException("e", "Exception in post-processing script.")
+ if len(self._script_list): # Add comment to g-code if any changes were made.
+ gcode_list[0] += ";POSTPROCESSED\n"
+ gcode_dict[active_build_plate_id] = gcode_list
+ setattr(scene, "gcode_dict", gcode_dict)
+ else:
+ Logger.log("e", "Already post processed")
+
+ @pyqtSlot(int)
+ def setSelectedScriptIndex(self, index):
+ self._selected_script_index = index
+ self.selectedIndexChanged.emit()
+
+ @pyqtProperty(int, notify = selectedIndexChanged)
+ def selectedScriptIndex(self):
+ return self._selected_script_index
+
+ @pyqtSlot(int, int)
+ def moveScript(self, index, new_index):
+ if new_index < 0 or new_index > len(self._script_list) - 1:
+ return # nothing needs to be done
+ else:
+ # Magical switch code.
+ self._script_list[new_index], self._script_list[index] = self._script_list[index], self._script_list[new_index]
+ self.scriptListChanged.emit()
+ self.selectedIndexChanged.emit() #Ensure that settings are updated
+ self._propertyChanged()
+
+ ## Remove a script from the active script list by index.
+ @pyqtSlot(int)
+ def removeScriptByIndex(self, index):
+ self._script_list.pop(index)
+ if len(self._script_list) - 1 < self._selected_script_index:
+ self._selected_script_index = len(self._script_list) - 1
+ self.scriptListChanged.emit()
+ self.selectedIndexChanged.emit() # Ensure that settings are updated
+ self._propertyChanged()
+
+ ## Load all scripts from provided path.
+ # This should probably only be done on init.
+ # \param path Path to check for scripts.
+ def loadAllScripts(self, path):
+ scripts = pkgutil.iter_modules(path = [path])
+ for loader, script_name, ispkg in scripts:
+ # Iterate over all scripts.
+ if script_name not in sys.modules:
+ try:
+ spec = importlib.util.spec_from_file_location(__name__ + "." + script_name, os.path.join(path, script_name + ".py"))
+ loaded_script = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(loaded_script)
+ sys.modules[script_name] = loaded_script
+
+ loaded_class = getattr(loaded_script, script_name)
+ temp_object = loaded_class()
+ Logger.log("d", "Begin loading of script: %s", script_name)
+ try:
+ setting_data = temp_object.getSettingData()
+ if "name" in setting_data and "key" in setting_data:
+ self._script_labels[setting_data["key"]] = setting_data["name"]
+ self._loaded_scripts[setting_data["key"]] = loaded_class
+ else:
+ Logger.log("w", "Script %s.py has no name or key", script_name)
+ self._script_labels[script_name] = script_name
+ self._loaded_scripts[script_name] = loaded_class
+ except AttributeError:
+ Logger.log("e", "Script %s.py is not a recognised script type. Ensure it inherits Script", script_name)
+ except NotImplementedError:
+ Logger.log("e", "Script %s.py has no implemented settings", script_name)
+ except Exception as e:
+ Logger.logException("e", "Exception occurred while loading post processing plugin: {error_msg}".format(error_msg = str(e)))
+ self.loadedScriptListChanged.emit()
+
+ loadedScriptListChanged = pyqtSignal()
+ @pyqtProperty("QVariantList", notify = loadedScriptListChanged)
+ def loadedScriptList(self):
+ return sorted(list(self._loaded_scripts.keys()))
+
+ @pyqtSlot(str, result = str)
+ def getScriptLabelByKey(self, key):
+ return self._script_labels[key]
+
+ scriptListChanged = pyqtSignal()
+ @pyqtProperty("QVariantList", notify = scriptListChanged)
+ def scriptList(self):
+ script_list = [script.getSettingData()["key"] for script in self._script_list]
+ return script_list
+
+ @pyqtSlot(str)
+ def addScriptToList(self, key):
+ Logger.log("d", "Adding script %s to list.", key)
+ new_script = self._loaded_scripts[key]()
+ self._script_list.append(new_script)
+ self.setSelectedScriptIndex(len(self._script_list) - 1)
+ self.scriptListChanged.emit()
+ self._propertyChanged()
+
+ ## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
+ def _createView(self):
+ Logger.log("d", "Creating post processing plugin view.")
+
+ ## Load all scripts in the scripts folders
+ for root in [PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), Resources.getStoragePath(Resources.Preferences)]:
+ path = os.path.join(root, "scripts")
+ if not os.path.isdir(path):
+ try:
+ os.makedirs(path)
+ except OSError:
+ Logger.log("w", "Unable to create a folder for scripts: " + path)
+ continue
+
+ self.loadAllScripts(path)
+
+ # Create the plugin dialog component
+ path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
+ self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
+ Logger.log("d", "Post processing view created.")
+
+ # Create the save button component
+ Application.getInstance().addAdditionalComponent("saveButton", self._view.findChild(QObject, "postProcessingSaveAreaButton"))
+
+ ## Show the (GUI) popup of the post processing plugin.
+ def showPopup(self):
+ if self._view is None:
+ self._createView()
+ self._view.show()
+
+ ## Property changed: trigger re-slice
+ # To do this we use the global container stack propertyChanged.
+ # Re-slicing is necessary for setting changes in this plugin, because the changes
+ # are applied only once per "fresh" gcode
+ def _propertyChanged(self):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ global_container_stack.propertyChanged.emit("post_processing_plugin", "value")
+
+
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
new file mode 100644
index 0000000000..489ea6dcfb
--- /dev/null
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
@@ -0,0 +1,501 @@
+// Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+// The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+import QtQuick.Layouts 1.1
+import QtQuick.Dialogs 1.1
+import QtQuick.Window 2.2
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+UM.Dialog
+{
+ id: dialog
+
+ title: catalog.i18nc("@title:window", "Post Processing Plugin")
+ width: 700 * screenScaleFactor;
+ height: 500 * screenScaleFactor;
+ minimumWidth: 400 * screenScaleFactor;
+ minimumHeight: 250 * screenScaleFactor;
+
+ Item
+ {
+ UM.I18nCatalog{id: catalog; name:"cura"}
+ id: base
+ property int columnWidth: Math.round((base.width / 2) - UM.Theme.getSize("default_margin").width)
+ property int textMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
+ property string activeScriptName
+ SystemPalette{ id: palette }
+ SystemPalette{ id: disabledPalette; colorGroup: SystemPalette.Disabled }
+ anchors.fill: parent
+
+ ExclusiveGroup
+ {
+ id: selectedScriptGroup
+ }
+ Item
+ {
+ id: activeScripts
+ anchors.left: parent.left
+ width: base.columnWidth
+ height: parent.height
+
+ Label
+ {
+ id: activeScriptsHeader
+ text: catalog.i18nc("@label", "Post Processing Scripts")
+ anchors.top: parent.top
+ anchors.topMargin: base.textMargin
+ anchors.left: parent.left
+ anchors.leftMargin: base.textMargin
+ anchors.right: parent.right
+ anchors.rightMargin: base.textMargin
+ font: UM.Theme.getFont("large")
+ }
+ ListView
+ {
+ id: activeScriptsList
+ anchors.top: activeScriptsHeader.bottom
+ anchors.topMargin: base.textMargin
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.right: parent.right
+ anchors.rightMargin: base.textMargin
+ height: childrenRect.height
+ model: manager.scriptList
+ delegate: Item
+ {
+ width: parent.width
+ height: activeScriptButton.height
+ Button
+ {
+ id: activeScriptButton
+ text: manager.getScriptLabelByKey(modelData.toString())
+ exclusiveGroup: selectedScriptGroup
+ checkable: true
+ checked: {
+ if (manager.selectedScriptIndex == index)
+ {
+ base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
+ return true
+ }
+ else
+ {
+ return false
+ }
+ }
+ onClicked:
+ {
+ forceActiveFocus()
+ manager.setSelectedScriptIndex(index)
+ base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
+ }
+ width: parent.width
+ height: UM.Theme.getSize("setting").height
+ style: ButtonStyle
+ {
+ background: Rectangle
+ {
+ color: activeScriptButton.checked ? palette.highlight : "transparent"
+ width: parent.width
+ height: parent.height
+ }
+ label: Label
+ {
+ wrapMode: Text.Wrap
+ text: control.text
+ color: activeScriptButton.checked ? palette.highlightedText : palette.text
+ }
+ }
+ }
+ Button
+ {
+ id: removeButton
+ text: "x"
+ width: 20 * screenScaleFactor
+ height: 20 * screenScaleFactor
+ anchors.right:parent.right
+ anchors.rightMargin: base.textMargin
+ anchors.verticalCenter: parent.verticalCenter
+ onClicked: manager.removeScriptByIndex(index)
+ style: ButtonStyle
+ {
+ label: Item
+ {
+ UM.RecolorImage
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.round(control.width / 2.7)
+ height: Math.round(control.height / 2.7)
+ sourceSize.width: width
+ sourceSize.height: width
+ color: palette.text
+ source: UM.Theme.getIcon("cross1")
+ }
+ }
+ }
+ }
+ Button
+ {
+ id: downButton
+ text: ""
+ anchors.right: removeButton.left
+ anchors.verticalCenter: parent.verticalCenter
+ enabled: index != manager.scriptList.length - 1
+ width: 20 * screenScaleFactor
+ height: 20 * screenScaleFactor
+ onClicked:
+ {
+ if (manager.selectedScriptIndex == index)
+ {
+ manager.setSelectedScriptIndex(index + 1)
+ }
+ return manager.moveScript(index, index + 1)
+ }
+ style: ButtonStyle
+ {
+ label: Item
+ {
+ UM.RecolorImage
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.round(control.width / 2.5)
+ height: Math.round(control.height / 2.5)
+ sourceSize.width: width
+ sourceSize.height: width
+ color: control.enabled ? palette.text : disabledPalette.text
+ source: UM.Theme.getIcon("arrow_bottom")
+ }
+ }
+ }
+ }
+ Button
+ {
+ id: upButton
+ text: ""
+ enabled: index != 0
+ width: 20 * screenScaleFactor
+ height: 20 * screenScaleFactor
+ anchors.right: downButton.left
+ anchors.verticalCenter: parent.verticalCenter
+ onClicked:
+ {
+ if (manager.selectedScriptIndex == index)
+ {
+ manager.setSelectedScriptIndex(index - 1)
+ }
+ return manager.moveScript(index, index - 1)
+ }
+ style: ButtonStyle
+ {
+ label: Item
+ {
+ UM.RecolorImage
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.round(control.width / 2.5)
+ height: Math.round(control.height / 2.5)
+ sourceSize.width: width
+ sourceSize.height: width
+ color: control.enabled ? palette.text : disabledPalette.text
+ source: UM.Theme.getIcon("arrow_top")
+ }
+ }
+ }
+ }
+ }
+ }
+ Button
+ {
+ id: addButton
+ text: catalog.i18nc("@action", "Add a script")
+ anchors.left: parent.left
+ anchors.leftMargin: base.textMargin
+ anchors.top: activeScriptsList.bottom
+ anchors.topMargin: base.textMargin
+ menu: scriptsMenu
+ style: ButtonStyle
+ {
+ label: Label
+ {
+ text: control.text
+ }
+ }
+ }
+ Menu
+ {
+ id: scriptsMenu
+
+ Instantiator
+ {
+ model: manager.loadedScriptList
+
+ MenuItem
+ {
+ text: manager.getScriptLabelByKey(modelData.toString())
+ onTriggered: manager.addScriptToList(modelData.toString())
+ }
+
+ onObjectAdded: scriptsMenu.insertItem(index, object);
+ onObjectRemoved: scriptsMenu.removeItem(object);
+ }
+ }
+ }
+
+ Rectangle
+ {
+ color: UM.Theme.getColor("sidebar")
+ anchors.left: activeScripts.right
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.right: parent.right
+ height: parent.height
+ id: settingsPanel
+
+ Label
+ {
+ id: scriptSpecsHeader
+ text: manager.selectedScriptIndex == -1 ? catalog.i18nc("@label", "Settings") : base.activeScriptName
+ anchors.top: parent.top
+ anchors.topMargin: base.textMargin
+ anchors.left: parent.left
+ anchors.leftMargin: base.textMargin
+ anchors.right: parent.right
+ anchors.rightMargin: base.textMargin
+ height: 20 * screenScaleFactor
+ font: UM.Theme.getFont("large")
+ color: UM.Theme.getColor("text")
+ }
+
+ ScrollView
+ {
+ id: scrollView
+ anchors.top: scriptSpecsHeader.bottom
+ anchors.topMargin: settingsPanel.textMargin
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ visible: manager.selectedScriptDefinitionId != ""
+ style: UM.Theme.styles.scrollview;
+
+ ListView
+ {
+ id: listview
+ spacing: UM.Theme.getSize("default_lining").height
+ model: UM.SettingDefinitionsModel
+ {
+ id: definitionsModel;
+ containerId: manager.selectedScriptDefinitionId
+ showAll: true
+ }
+ delegate:Loader
+ {
+ id: settingLoader
+
+ width: parent.width
+ height:
+ {
+ if(provider.properties.enabled == "True")
+ {
+ if(model.type != undefined)
+ {
+ return UM.Theme.getSize("section").height;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ return 0;
+ }
+
+ }
+ Behavior on height { NumberAnimation { duration: 100 } }
+ opacity: provider.properties.enabled == "True" ? 1 : 0
+ Behavior on opacity { NumberAnimation { duration: 100 } }
+ enabled: opacity > 0
+ property var definition: model
+ property var settingDefinitionsModel: definitionsModel
+ property var propertyProvider: provider
+ property var globalPropertyProvider: inheritStackProvider
+
+ //Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
+ //In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
+ //causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
+ asynchronous: model.type != "enum" && model.type != "extruder"
+
+ onLoaded: {
+ settingLoader.item.showRevertButton = false
+ settingLoader.item.showInheritButton = false
+ settingLoader.item.showLinkedSettingIcon = false
+ settingLoader.item.doDepthIndentation = true
+ settingLoader.item.doQualityUserSettingEmphasis = false
+ }
+
+ sourceComponent:
+ {
+ switch(model.type)
+ {
+ case "int":
+ return settingTextField
+ case "float":
+ return settingTextField
+ case "enum":
+ return settingComboBox
+ case "extruder":
+ return settingExtruder
+ case "bool":
+ return settingCheckBox
+ case "str":
+ return settingTextField
+ case "category":
+ return settingCategory
+ default:
+ return settingUnknown
+ }
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: provider
+ containerStackId: manager.selectedScriptStackId
+ key: model.key ? model.key : "None"
+ watchedProperties: [ "value", "enabled", "state", "validationState" ]
+ storeIndex: 0
+ }
+
+ // Specialty provider that only watches global_inherits (we cant filter on what property changed we get events
+ // so we bypass that to make a dedicated provider).
+ UM.SettingPropertyProvider
+ {
+ id: inheritStackProvider
+ containerStackId: Cura.MachineManager.activeMachineId
+ key: model.key ? model.key : "None"
+ watchedProperties: [ "limit_to_extruder" ]
+ }
+
+ Connections
+ {
+ target: item
+
+ onShowTooltip:
+ {
+ tooltip.text = text;
+ var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0);
+ tooltip.show(position);
+ tooltip.target.x = position.x + 1
+ }
+
+ onHideTooltip:
+ {
+ tooltip.hide();
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+ Cura.SidebarTooltip
+ {
+ id: tooltip
+ }
+
+ Component
+ {
+ id: settingTextField;
+
+ Cura.SettingTextField { }
+ }
+
+ Component
+ {
+ id: settingComboBox;
+
+ Cura.SettingComboBox { }
+ }
+
+ Component
+ {
+ id: settingExtruder;
+
+ Cura.SettingExtruder { }
+ }
+
+ Component
+ {
+ id: settingCheckBox;
+
+ Cura.SettingCheckBox { }
+ }
+
+ Component
+ {
+ id: settingCategory;
+
+ Cura.SettingCategory { }
+ }
+
+ Component
+ {
+ id: settingUnknown;
+
+ Cura.SettingUnknown { }
+ }
+ }
+ rightButtons: Button
+ {
+ text: catalog.i18nc("@action:button", "Close")
+ iconName: "dialog-close"
+ onClicked: dialog.accept()
+ }
+
+ Button {
+ objectName: "postProcessingSaveAreaButton"
+ visible: activeScriptsList.count > 0
+ height: UM.Theme.getSize("save_button_save_to_button").height
+ width: height
+ tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
+ onClicked: dialog.show()
+
+ style: ButtonStyle {
+ background: Rectangle {
+ id: deviceSelectionIcon
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") :
+ control.pressed ? UM.Theme.getColor("action_button_active_border") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered_border") : UM.Theme.getColor("action_button_border")
+ color: !control.enabled ? UM.Theme.getColor("action_button_disabled") :
+ control.pressed ? UM.Theme.getColor("action_button_active") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
+ Behavior on color { ColorAnimation { duration: 50; } }
+ anchors.left: parent.left
+ anchors.leftMargin: Math.round(UM.Theme.getSize("save_button_text_margin").width / 2);
+ width: parent.height
+ height: parent.height
+
+ UM.RecolorImage {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.round(parent.width / 2)
+ height: Math.round(parent.height / 2)
+ sourceSize.width: width
+ sourceSize.height: height
+ color: !control.enabled ? UM.Theme.getColor("action_button_disabled_text") :
+ control.pressed ? UM.Theme.getColor("action_button_active_text") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered_text") : UM.Theme.getColor("action_button_text");
+ source: "postprocessing.svg"
+ }
+ }
+ label: Label{ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/README.md b/plugins/PostProcessingPlugin/README.md
new file mode 100644
index 0000000000..988f40007d
--- /dev/null
+++ b/plugins/PostProcessingPlugin/README.md
@@ -0,0 +1,2 @@
+# PostProcessingPlugin
+A post processing plugin for Cura
diff --git a/plugins/PostProcessingPlugin/Script.py b/plugins/PostProcessingPlugin/Script.py
new file mode 100644
index 0000000000..7f419cd422
--- /dev/null
+++ b/plugins/PostProcessingPlugin/Script.py
@@ -0,0 +1,163 @@
+# Copyright (c) 2015 Jaime van Kessel
+# Copyright (c) 2017 Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+from UM.Logger import Logger
+from UM.Signal import Signal, signalemitter
+from UM.i18n import i18nCatalog
+
+# Setting stuff import
+from UM.Application import Application
+from UM.Settings.ContainerStack import ContainerStack
+from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Settings.DefinitionContainer import DefinitionContainer
+from UM.Settings.ContainerRegistry import ContainerRegistry
+
+import re
+import json
+import collections
+i18n_catalog = i18nCatalog("cura")
+
+
+## Base class for scripts. All scripts should inherit the script class.
+@signalemitter
+class Script:
+ def __init__(self):
+ super().__init__()
+ self._settings = None
+ self._stack = None
+
+ setting_data = self.getSettingData()
+ self._stack = ContainerStack(stack_id = str(id(self)))
+ self._stack.setDirty(False) # This stack does not need to be saved.
+
+
+ ## Check if the definition of this script already exists. If not, add it to the registry.
+ if "key" in setting_data:
+ definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"])
+ if definitions:
+ # Definition was found
+ self._definition = definitions[0]
+ else:
+ self._definition = DefinitionContainer(setting_data["key"])
+ self._definition.deserialize(json.dumps(setting_data))
+ ContainerRegistry.getInstance().addContainer(self._definition)
+ self._stack.addContainer(self._definition)
+ self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
+ self._instance.setDefinition(self._definition.getId())
+ self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0))
+ self._stack.addContainer(self._instance)
+ self._stack.propertyChanged.connect(self._onPropertyChanged)
+
+ ContainerRegistry.getInstance().addContainer(self._stack)
+
+ settingsLoaded = Signal()
+ valueChanged = Signal() # Signal emitted whenever a value of a setting is changed
+
+ def _onPropertyChanged(self, key, property_name):
+ if property_name == "value":
+ self.valueChanged.emit()
+
+ # Property changed: trigger reslice
+ # To do this we use the global container stack propertyChanged.
+ # Reslicing is necessary for setting changes in this plugin, because the changes
+ # are applied only once per "fresh" gcode
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ global_container_stack.propertyChanged.emit(key, property_name)
+
+ ## Needs to return a dict that can be used to construct a settingcategory file.
+ # See the example script for an example.
+ # It follows the same style / guides as the Uranium settings.
+ # Scripts can either override getSettingData directly, or use getSettingDataString
+ # to return a string that will be parsed as json. The latter has the benefit over
+ # returning a dict in that the order of settings is maintained.
+ def getSettingData(self):
+ setting_data = self.getSettingDataString()
+ if type(setting_data) == str:
+ setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict)
+ return setting_data
+
+ def getSettingDataString(self):
+ raise NotImplementedError()
+
+ def getDefinitionId(self):
+ if self._stack:
+ return self._stack.getBottom().getId()
+
+ def getStackId(self):
+ if self._stack:
+ return self._stack.getId()
+
+ ## Convenience function that retrieves value of a setting from the stack.
+ def getSettingValueByKey(self, key):
+ return self._stack.getProperty(key, "value")
+
+ ## Convenience function that finds the value in a line of g-code.
+ # When requesting key = x from line "G1 X100" the value 100 is returned.
+ def getValue(self, line, key, default = None):
+ if not key in line or (';' in line and line.find(key) > line.find(';')):
+ return default
+ sub_part = line[line.find(key) + 1:]
+ m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
+ if m is None:
+ return default
+ try:
+ return float(m.group(0))
+ except:
+ return default
+
+ ## Convenience function to produce a line of g-code.
+ #
+ # You can put in an original g-code line and it'll re-use all the values
+ # in that line.
+ # All other keyword parameters are put in the result in g-code's format.
+ # For instance, if you put ``G=1`` in the parameters, it will output
+ # ``G1``. If you put ``G=1, X=100`` in the parameters, it will output
+ # ``G1 X100``. The parameters G and M will always be put first. The
+ # parameters T and S will be put second (or first if there is no G or M).
+ # The rest of the parameters will be put in arbitrary order.
+ # \param line The original g-code line that must be modified. If not
+ # provided, an entirely new g-code line will be produced.
+ # \return A line of g-code with the desired parameters filled in.
+ def putValue(self, line = "", **kwargs):
+ #Strip the comment.
+ comment = ""
+ if ";" in line:
+ comment = line[line.find(";"):]
+ line = line[:line.find(";")] #Strip the comment.
+
+ #Parse the original g-code line.
+ for part in line.split(" "):
+ if part == "":
+ continue
+ parameter = part[0]
+ if parameter in kwargs:
+ continue #Skip this one. The user-provided parameter overwrites the one in the line.
+ value = part[1:]
+ kwargs[parameter] = value
+
+ #Write the new g-code line.
+ result = ""
+ priority_parameters = ["G", "M", "T", "S", "F", "X", "Y", "Z", "E"] #First some parameters that get priority. In order of priority!
+ for priority_key in priority_parameters:
+ if priority_key in kwargs:
+ if result != "":
+ result += " "
+ result += priority_key + str(kwargs[priority_key])
+ del kwargs[priority_key]
+ for key, value in kwargs.items():
+ if result != "":
+ result += " "
+ result += key + str(value)
+
+ #Put the comment back in.
+ if comment != "":
+ if result != "":
+ result += " "
+ result += ";" + comment
+
+ return result
+
+ ## This is called when the script is executed.
+ # It gets a list of g-code strings and needs to return a (modified) list.
+ def execute(self, data):
+ raise NotImplementedError()
diff --git a/plugins/PostProcessingPlugin/__init__.py b/plugins/PostProcessingPlugin/__init__.py
new file mode 100644
index 0000000000..85f1126136
--- /dev/null
+++ b/plugins/PostProcessingPlugin/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
+from . import PostProcessingPlugin
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+def getMetaData():
+ return {}
+
+def register(app):
+ return {"extension": PostProcessingPlugin.PostProcessingPlugin()}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/plugin.json b/plugins/PostProcessingPlugin/plugin.json
new file mode 100644
index 0000000000..ebfef8145a
--- /dev/null
+++ b/plugins/PostProcessingPlugin/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Post Processing",
+ "author": "Ultimaker",
+ "version": "2.2",
+ "api": 4,
+ "description": "Extension that allows for user created scripts for post processing",
+ "catalog": "cura"
+}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/postprocessing.svg b/plugins/PostProcessingPlugin/postprocessing.svg
new file mode 100644
index 0000000000..f55face4a9
--- /dev/null
+++ b/plugins/PostProcessingPlugin/postprocessing.svg
@@ -0,0 +1,47 @@
+
+
+
+
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py b/plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py
new file mode 100644
index 0000000000..fb59378206
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py
@@ -0,0 +1,48 @@
+from ..Script import Script
+class BQ_PauseAtHeight(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Pause at height (BQ Printers)",
+ "key": "BQ_PauseAtHeight",
+ "metadata":{},
+ "version": 2,
+ "settings":
+ {
+ "pause_height":
+ {
+ "label": "Pause height",
+ "description": "At what height should the pause occur",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ x = 0.
+ y = 0.
+ current_z = 0.
+ pause_z = self.getSettingValueByKey("pause_height")
+ for layer in data:
+ lines = layer.split("\n")
+ for line in lines:
+ if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
+ current_z = self.getValue(line, 'Z')
+ if current_z != None:
+ if current_z >= pause_z:
+ prepend_gcode = ";TYPE:CUSTOM\n"
+ prepend_gcode += "; -- Pause at height (%.2f mm) --\n" % pause_z
+
+ # Insert Pause gcode
+ prepend_gcode += "M25 ; Pauses the print and waits for the user to resume it\n"
+
+ index = data.index(layer)
+ layer = prepend_gcode + layer
+ data[index] = layer # Override the data of this layer with the modified data
+ return data
+ break
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py b/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py
new file mode 100644
index 0000000000..54d6fdb155
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py
@@ -0,0 +1,495 @@
+# ChangeAtZ script - Change printing parameters at a given height
+# This script is the successor of the TweakAtZ plugin for legacy Cura.
+# It contains code from the TweakAtZ plugin V1.0-V4.x and from the ExampleScript by Jaime van Kessel, Ultimaker B.V.
+# It runs with the PostProcessingPlugin which is released under the terms of the AGPLv3 or higher.
+# This script is licensed under the Creative Commons - Attribution - Share Alike (CC BY-SA) terms
+
+#Authors of the ChangeAtZ plugin / script:
+# Written by Steven Morlock, smorloc@gmail.com
+# Modified by Ricardo Gomez, ricardoga@otulook.com, to add Bed Temperature and make it work with Cura_13.06.04+
+# Modified by Stefan Heule, Dim3nsioneer@gmx.ch since V3.0 (see changelog below)
+# Modified by Jaime van Kessel (Ultimaker), j.vankessel@ultimaker.com to make it work for 15.10 / 2.x
+# Modified by Ruben Dulek (Ultimaker), r.dulek@ultimaker.com, to debug.
+
+##history / changelog:
+##V3.0.1: TweakAtZ-state default 1 (i.e. the plugin works without any TweakAtZ comment)
+##V3.1: Recognizes UltiGCode and deactivates value reset, fan speed added, alternatively layer no. to tweak at,
+## extruder three temperature disabled by "#Ex3"
+##V3.1.1: Bugfix reset flow rate
+##V3.1.2: Bugfix disable TweakAtZ on Cool Head Lift
+##V3.2: Flow rate for specific extruder added (only for 2 extruders), bugfix parser,
+## added speed reset at the end of the print
+##V4.0: Progress bar, tweaking over multiple layers, M605&M606 implemented, reset after one layer option,
+## extruder three code removed, tweaking print speed, save call of Publisher class,
+## uses previous value from other plugins also on UltiGCode
+##V4.0.1: Bugfix for doubled G1 commands
+##V4.0.2: uses Cura progress bar instead of its own
+##V4.0.3: Bugfix for cool head lift (contributed by luisonoff)
+##V4.9.91: First version for Cura 15.06.x and PostProcessingPlugin
+##V4.9.92: Modifications for Cura 15.10
+##V4.9.93: Minor bugfixes (input settings) / documentation
+##V4.9.94: Bugfix Combobox-selection; remove logger
+##V5.0: Bugfix for fall back after one layer and doubled G0 commands when using print speed tweak, Initial version for Cura 2.x
+##V5.0.1: Bugfix for calling unknown property 'bedTemp' of previous settings storage and unkown variable 'speed'
+##V5.1: API Changes included for use with Cura 2.2
+
+## Uses -
+## M220 S - set speed factor override percentage
+## M221 S - set flow factor override percentage
+## M221 S T<0-#toolheads> - set flow factor override percentage for single extruder
+## M104 S T<0-#toolheads> - set extruder to target temperature
+## M140 S - set bed target temperature
+## M106 S - set fan speed to target speed
+## M605/606 to save and recall material settings on the UM2
+
+from ..Script import Script
+#from UM.Logger import Logger
+import re
+
+class ChangeAtZ(Script):
+ version = "5.1.1"
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"ChangeAtZ """ + self.version + """ (Experimental)",
+ "key":"ChangeAtZ",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "a_trigger":
+ {
+ "label": "Trigger",
+ "description": "Trigger at height or at layer no.",
+ "type": "enum",
+ "options": {"height":"Height","layer_no":"Layer No."},
+ "default_value": "height"
+ },
+ "b_targetZ":
+ {
+ "label": "Change Height",
+ "description": "Z height to change at",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0,
+ "minimum_value": "0",
+ "minimum_value_warning": "0.1",
+ "maximum_value_warning": "230",
+ "enabled": "a_trigger == 'height'"
+ },
+ "b_targetL":
+ {
+ "label": "Change Layer",
+ "description": "Layer no. to change at",
+ "unit": "",
+ "type": "int",
+ "default_value": 1,
+ "minimum_value": "-100",
+ "minimum_value_warning": "-1",
+ "enabled": "a_trigger == 'layer_no'"
+ },
+ "c_behavior":
+ {
+ "label": "Behavior",
+ "description": "Select behavior: Change value and keep it for the rest, Change value for single layer only",
+ "type": "enum",
+ "options": {"keep_value":"Keep value","single_layer":"Single Layer"},
+ "default_value": "keep_value"
+ },
+ "d_twLayers":
+ {
+ "label": "No. Layers",
+ "description": "No. of layers used to change",
+ "unit": "",
+ "type": "int",
+ "default_value": 1,
+ "minimum_value": "1",
+ "maximum_value_warning": "50",
+ "enabled": "c_behavior == 'keep_value'"
+ },
+ "e1_Change_speed":
+ {
+ "label": "Change Speed",
+ "description": "Select if total speed (print and travel) has to be cahnged",
+ "type": "bool",
+ "default_value": false
+ },
+ "e2_speed":
+ {
+ "label": "Speed",
+ "description": "New total speed (print and travel)",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "e1_Change_speed"
+ },
+ "f1_Change_printspeed":
+ {
+ "label": "Change Print Speed",
+ "description": "Select if print speed has to be changed",
+ "type": "bool",
+ "default_value": false
+ },
+ "f2_printspeed":
+ {
+ "label": "Print Speed",
+ "description": "New print speed",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "f1_Change_printspeed"
+ },
+ "g1_Change_flowrate":
+ {
+ "label": "Change Flow Rate",
+ "description": "Select if flow rate has to be changed",
+ "type": "bool",
+ "default_value": false
+ },
+ "g2_flowrate":
+ {
+ "label": "Flow Rate",
+ "description": "New Flow rate",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "g1_Change_flowrate"
+ },
+ "g3_Change_flowrateOne":
+ {
+ "label": "Change Flow Rate 1",
+ "description": "Select if first extruder flow rate has to be changed",
+ "type": "bool",
+ "default_value": false
+ },
+ "g4_flowrateOne":
+ {
+ "label": "Flow Rate One",
+ "description": "New Flow rate Extruder 1",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "g3_Change_flowrateOne"
+ },
+ "g5_Change_flowrateTwo":
+ {
+ "label": "Change Flow Rate 2",
+ "description": "Select if second extruder flow rate has to be changed",
+ "type": "bool",
+ "default_value": false
+ },
+ "g6_flowrateTwo":
+ {
+ "label": "Flow Rate two",
+ "description": "New Flow rate Extruder 2",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "g5_Change_flowrateTwo"
+ },
+ "h1_Change_bedTemp":
+ {
+ "label": "Change Bed Temp",
+ "description": "Select if Bed Temperature has to be changed",
+ "type": "bool",
+ "default_value": false
+ },
+ "h2_bedTemp":
+ {
+ "label": "Bed Temp",
+ "description": "New Bed Temperature",
+ "unit": "C",
+ "type": "float",
+ "default_value": 60,
+ "minimum_value": "0",
+ "minimum_value_warning": "30",
+ "maximum_value_warning": "120",
+ "enabled": "h1_Change_bedTemp"
+ },
+ "i1_Change_extruderOne":
+ {
+ "label": "Change Extruder 1 Temp",
+ "description": "Select if First Extruder Temperature has to be changed",
+ "type": "bool",
+ "default_value": false
+ },
+ "i2_extruderOne":
+ {
+ "label": "Extruder 1 Temp",
+ "description": "New First Extruder Temperature",
+ "unit": "C",
+ "type": "float",
+ "default_value": 190,
+ "minimum_value": "0",
+ "minimum_value_warning": "160",
+ "maximum_value_warning": "250",
+ "enabled": "i1_Change_extruderOne"
+ },
+ "i3_Change_extruderTwo":
+ {
+ "label": "Change Extruder 2 Temp",
+ "description": "Select if Second Extruder Temperature has to be changed",
+ "type": "bool",
+ "default_value": false
+ },
+ "i4_extruderTwo":
+ {
+ "label": "Extruder 2 Temp",
+ "description": "New Second Extruder Temperature",
+ "unit": "C",
+ "type": "float",
+ "default_value": 190,
+ "minimum_value": "0",
+ "minimum_value_warning": "160",
+ "maximum_value_warning": "250",
+ "enabled": "i3_Change_extruderTwo"
+ },
+ "j1_Change_fanSpeed":
+ {
+ "label": "Change Fan Speed",
+ "description": "Select if Fan Speed has to be changed",
+ "type": "bool",
+ "default_value": false
+ },
+ "j2_fanSpeed":
+ {
+ "label": "Fan Speed",
+ "description": "New Fan Speed (0-255)",
+ "unit": "PWM",
+ "type": "int",
+ "default_value": 255,
+ "minimum_value": "0",
+ "minimum_value_warning": "15",
+ "maximum_value_warning": "255",
+ "enabled": "j1_Change_fanSpeed"
+ }
+ }
+ }"""
+
+ def getValue(self, line, key, default = None): #replace default getvalue due to comment-reading feature
+ if not key in line or (";" in line and line.find(key) > line.find(";") and
+ not ";ChangeAtZ" in key and not ";LAYER:" in key):
+ return default
+ subPart = line[line.find(key) + len(key):] #allows for string lengths larger than 1
+ if ";ChangeAtZ" in key:
+ m = re.search("^[0-4]", subPart)
+ elif ";LAYER:" in key:
+ m = re.search("^[+-]?[0-9]*", subPart)
+ else:
+ #the minus at the beginning allows for negative values, e.g. for delta printers
+ m = re.search("^[-]?[0-9]*\.?[0-9]*", subPart)
+ if m == None:
+ return default
+ try:
+ return float(m.group(0))
+ except:
+ return default
+
+ def execute(self, data):
+ #Check which changes should apply
+ ChangeProp = {"speed": self.getSettingValueByKey("e1_Change_speed"),
+ "flowrate": self.getSettingValueByKey("g1_Change_flowrate"),
+ "flowrateOne": self.getSettingValueByKey("g3_Change_flowrateOne"),
+ "flowrateTwo": self.getSettingValueByKey("g5_Change_flowrateTwo"),
+ "bedTemp": self.getSettingValueByKey("h1_Change_bedTemp"),
+ "extruderOne": self.getSettingValueByKey("i1_Change_extruderOne"),
+ "extruderTwo": self.getSettingValueByKey("i3_Change_extruderTwo"),
+ "fanSpeed": self.getSettingValueByKey("j1_Change_fanSpeed")}
+ ChangePrintSpeed = self.getSettingValueByKey("f1_Change_printspeed")
+ ChangeStrings = {"speed": "M220 S%f\n",
+ "flowrate": "M221 S%f\n",
+ "flowrateOne": "M221 T0 S%f\n",
+ "flowrateTwo": "M221 T1 S%f\n",
+ "bedTemp": "M140 S%f\n",
+ "extruderOne": "M104 S%f T0\n",
+ "extruderTwo": "M104 S%f T1\n",
+ "fanSpeed": "M106 S%d\n"}
+ target_values = {"speed": self.getSettingValueByKey("e2_speed"),
+ "printspeed": self.getSettingValueByKey("f2_printspeed"),
+ "flowrate": self.getSettingValueByKey("g2_flowrate"),
+ "flowrateOne": self.getSettingValueByKey("g4_flowrateOne"),
+ "flowrateTwo": self.getSettingValueByKey("g6_flowrateTwo"),
+ "bedTemp": self.getSettingValueByKey("h2_bedTemp"),
+ "extruderOne": self.getSettingValueByKey("i2_extruderOne"),
+ "extruderTwo": self.getSettingValueByKey("i4_extruderTwo"),
+ "fanSpeed": self.getSettingValueByKey("j2_fanSpeed")}
+ old = {"speed": -1, "flowrate": -1, "flowrateOne": -1, "flowrateTwo": -1, "platformTemp": -1, "extruderOne": -1,
+ "extruderTwo": -1, "bedTemp": -1, "fanSpeed": -1, "state": -1}
+ twLayers = self.getSettingValueByKey("d_twLayers")
+ if self.getSettingValueByKey("c_behavior") == "single_layer":
+ behavior = 1
+ else:
+ behavior = 0
+ try:
+ twLayers = max(int(twLayers),1) #for the case someone entered something as "funny" as -1
+ except:
+ twLayers = 1
+ pres_ext = 0
+ done_layers = 0
+ z = 0
+ x = None
+ y = None
+ layer = -100000 #layer no. may be negative (raft) but never that low
+ # state 0: deactivated, state 1: activated, state 2: active, but below z,
+ # state 3: active and partially executed (multi layer), state 4: active and passed z
+ state = 1
+ # IsUM2: Used for reset of values (ok for Marlin/Sprinter),
+ # has to be set to 1 for UltiGCode (work-around for missing default values)
+ IsUM2 = False
+ oldValueUnknown = False
+ TWinstances = 0
+
+ if self.getSettingValueByKey("a_trigger") == "layer_no":
+ targetL_i = int(self.getSettingValueByKey("b_targetL"))
+ targetZ = 100000
+ else:
+ targetL_i = -100000
+ targetZ = self.getSettingValueByKey("b_targetZ")
+ index = 0
+ for active_layer in data:
+ modified_gcode = ""
+ lines = active_layer.split("\n")
+ for line in lines:
+ if ";Generated with Cura_SteamEngine" in line:
+ TWinstances += 1
+ modified_gcode += ";ChangeAtZ instances: %d\n" % TWinstances
+ if not ("M84" in line or "M25" in line or ("G1" in line and ChangePrintSpeed and (state==3 or state==4)) or
+ ";ChangeAtZ instances:" in line):
+ modified_gcode += line + "\n"
+ IsUM2 = ("FLAVOR:UltiGCode" in line) or IsUM2 #Flavor is UltiGCode!
+ if ";ChangeAtZ-state" in line: #checks for state change comment
+ state = self.getValue(line, ";ChangeAtZ-state", state)
+ if ";ChangeAtZ instances:" in line:
+ try:
+ tempTWi = int(line[20:])
+ except:
+ tempTWi = TWinstances
+ TWinstances = tempTWi
+ if ";Small layer" in line: #checks for begin of Cool Head Lift
+ old["state"] = state
+ state = 0
+ if ";LAYER:" in line: #new layer no. found
+ if state == 0:
+ state = old["state"]
+ layer = self.getValue(line, ";LAYER:", layer)
+ if targetL_i > -100000: #target selected by layer no.
+ if (state == 2 or targetL_i == 0) and layer == targetL_i: #determine targetZ from layer no.; checks for change on layer 0
+ state = 2
+ targetZ = z + 0.001
+ if (self.getValue(line, "T", None) is not None) and (self.getValue(line, "M", None) is None): #looking for single T-cmd
+ pres_ext = self.getValue(line, "T", pres_ext)
+ if "M190" in line or "M140" in line and state < 3: #looking for bed temp, stops after target z is passed
+ old["bedTemp"] = self.getValue(line, "S", old["bedTemp"])
+ if "M109" in line or "M104" in line and state < 3: #looking for extruder temp, stops after target z is passed
+ if self.getValue(line, "T", pres_ext) == 0:
+ old["extruderOne"] = self.getValue(line, "S", old["extruderOne"])
+ elif self.getValue(line, "T", pres_ext) == 1:
+ old["extruderTwo"] = self.getValue(line, "S", old["extruderTwo"])
+ if "M107" in line: #fan is stopped; is always updated in order not to miss switch off for next object
+ old["fanSpeed"] = 0
+ if "M106" in line and state < 3: #looking for fan speed
+ old["fanSpeed"] = self.getValue(line, "S", old["fanSpeed"])
+ if "M221" in line and state < 3: #looking for flow rate
+ tmp_extruder = self.getValue(line,"T",None)
+ if tmp_extruder == None: #check if extruder is specified
+ old["flowrate"] = self.getValue(line, "S", old["flowrate"])
+ elif tmp_extruder == 0: #first extruder
+ old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
+ elif tmp_extruder == 1: #second extruder
+ old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
+ if ("M84" in line or "M25" in line):
+ if state>0 and ChangeProp["speed"]: #"finish" commands for UM Original and UM2
+ modified_gcode += "M220 S100 ; speed reset to 100% at the end of print\n"
+ modified_gcode += "M117 \n"
+ modified_gcode += line + "\n"
+ if "G1" in line or "G0" in line:
+ newZ = self.getValue(line, "Z", z)
+ x = self.getValue(line, "X", None)
+ y = self.getValue(line, "Y", None)
+ e = self.getValue(line, "E", None)
+ f = self.getValue(line, "F", None)
+ if 'G1' in line and ChangePrintSpeed and (state==3 or state==4):
+ # check for pure print movement in target range:
+ if x != None and y != None and f != None and e != None and newZ==z:
+ modified_gcode += "G1 F%d X%1.3f Y%1.3f E%1.5f\n" % (int(f / 100.0 * float(target_values["printspeed"])), self.getValue(line, "X"),
+ self.getValue(line, "Y"), self.getValue(line, "E"))
+ else: #G1 command but not a print movement
+ modified_gcode += line + "\n"
+ # no changing on retraction hops which have no x and y coordinate:
+ if (newZ != z) and (x is not None) and (y is not None):
+ z = newZ
+ if z < targetZ and state == 1:
+ state = 2
+ if z >= targetZ and state == 2:
+ state = 3
+ done_layers = 0
+ for key in ChangeProp:
+ if ChangeProp[key] and old[key]==-1: #old value is not known
+ oldValueUnknown = True
+ if oldValueUnknown: #the changing has to happen within one layer
+ twLayers = 1
+ if IsUM2: #Parameters have to be stored in the printer (UltiGCode=UM2)
+ modified_gcode += "M605 S%d;stores parameters before changing\n" % (TWinstances-1)
+ if behavior == 1: #single layer change only and then reset
+ twLayers = 1
+ if ChangePrintSpeed and behavior == 0:
+ twLayers = done_layers + 1
+ if state==3:
+ if twLayers-done_layers>0: #still layers to go?
+ if targetL_i > -100000:
+ modified_gcode += ";ChangeAtZ V%s: executed at Layer %d\n" % (self.version,layer)
+ modified_gcode += "M117 Printing... ch@L%4d\n" % layer
+ else:
+ modified_gcode += (";ChangeAtZ V%s: executed at %1.2f mm\n" % (self.version,z))
+ modified_gcode += "M117 Printing... ch@%5.1f\n" % z
+ for key in ChangeProp:
+ if ChangeProp[key]:
+ modified_gcode += ChangeStrings[key] % float(old[key]+(float(target_values[key])-float(old[key]))/float(twLayers)*float(done_layers+1))
+ done_layers += 1
+ else:
+ state = 4
+ if behavior == 1: #reset values after one layer
+ if targetL_i > -100000:
+ modified_gcode += ";ChangeAtZ V%s: reset on Layer %d\n" % (self.version,layer)
+ else:
+ modified_gcode += ";ChangeAtZ V%s: reset at %1.2f mm\n" % (self.version,z)
+ if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
+ modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
+ else: #executes on RepRap, UM2 with Ultigcode and Cura setting
+ for key in ChangeProp:
+ if ChangeProp[key]:
+ modified_gcode += ChangeStrings[key] % float(old[key])
+ # re-activates the plugin if executed by pre-print G-command, resets settings:
+ if (z < targetZ or layer == 0) and state >= 3: #resets if below change level or at level 0
+ state = 2
+ done_layers = 0
+ if targetL_i > -100000:
+ modified_gcode += ";ChangeAtZ V%s: reset below Layer %d\n" % (self.version,targetL_i)
+ else:
+ modified_gcode += ";ChangeAtZ V%s: reset below %1.2f mm\n" % (self.version,targetZ)
+ if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
+ modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
+ else: #executes on RepRap, UM2 with Ultigcode and Cura setting
+ for key in ChangeProp:
+ if ChangeProp[key]:
+ modified_gcode += ChangeStrings[key] % float(old[key])
+ data[index] = modified_gcode
+ index += 1
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/ColorChange.py b/plugins/PostProcessingPlugin/scripts/ColorChange.py
new file mode 100644
index 0000000000..8db45f4033
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/ColorChange.py
@@ -0,0 +1,76 @@
+# This PostProcessing Plugin script is released
+# under the terms of the AGPLv3 or higher
+
+from ..Script import Script
+#from UM.Logger import Logger
+# from cura.Settings.ExtruderManager import ExtruderManager
+
+class ColorChange(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Color Change",
+ "key": "ColorChange",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "layer_number":
+ {
+ "label": "Layer",
+ "description": "At what layer should color change occur. This will be before the layer starts printing. Specify multiple color changes with a comma.",
+ "unit": "",
+ "type": "str",
+ "default_value": "1"
+ },
+
+ "initial_retract":
+ {
+ "label": "Initial Retraction",
+ "description": "Initial filament retraction distance",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 300.0
+ },
+ "later_retract":
+ {
+ "label": "Later Retraction Distance",
+ "description": "Later filament retraction distance for removal",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 30.0
+ }
+ }
+ }"""
+
+ def execute(self, data: list):
+
+ """data is a list. Each index contains a layer"""
+ layer_nums = self.getSettingValueByKey("layer_number")
+ initial_retract = self.getSettingValueByKey("initial_retract")
+ later_retract = self.getSettingValueByKey("later_retract")
+
+ color_change = "M600"
+
+ if initial_retract is not None and initial_retract > 0.:
+ color_change = color_change + (" E%.2f" % initial_retract)
+
+ if later_retract is not None and later_retract > 0.:
+ color_change = color_change + (" L%.2f" % later_retract)
+
+ color_change = color_change + " ; Generated by ColorChange plugin"
+
+ layer_targets = layer_nums.split(',')
+ if len(layer_targets) > 0:
+ for layer_num in layer_targets:
+ layer_num = int( layer_num.strip() )
+ if layer_num < len(data):
+ layer = data[ layer_num - 1 ]
+ lines = layer.split("\n")
+ lines.insert(2, color_change )
+ final_line = "\n".join( lines )
+ data[ layer_num - 1 ] = final_line
+
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/ExampleScript.py b/plugins/PostProcessingPlugin/scripts/ExampleScript.py
new file mode 100644
index 0000000000..416a5f5404
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/ExampleScript.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+from ..Script import Script
+
+class ExampleScript(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Example script",
+ "key": "ExampleScript",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "test":
+ {
+ "label": "Test",
+ "description": "None",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.5,
+ "minimum_value": "0",
+ "minimum_value_warning": "0.1",
+ "maximum_value_warning": "1"
+ },
+ "derp":
+ {
+ "label": "zomg",
+ "description": "afgasgfgasfgasf",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.5,
+ "minimum_value": "0",
+ "minimum_value_warning": "0.1",
+ "maximum_value_warning": "1"
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ return data
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
new file mode 100644
index 0000000000..805ab0a2c3
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
@@ -0,0 +1,275 @@
+from ..Script import Script
+# from cura.Settings.ExtruderManager import ExtruderManager
+
+class PauseAtHeight(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name": "Pause at height",
+ "key": "PauseAtHeight",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "pause_at":
+ {
+ "label": "Pause at",
+ "description": "Whether to pause at a certain height or at a certain layer.",
+ "type": "enum",
+ "options": {"height": "Height", "layer_no": "Layer No."},
+ "default_value": "height"
+ },
+ "pause_height":
+ {
+ "label": "Pause Height",
+ "description": "At what height should the pause occur",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0,
+ "minimum_value": "0",
+ "minimum_value_warning": "0.27",
+ "enabled": "pause_at == 'height'"
+ },
+ "pause_layer":
+ {
+ "label": "Pause Layer",
+ "description": "At what layer should the pause occur",
+ "type": "int",
+ "value": "math.floor((pause_height - 0.27) / 0.1) + 1",
+ "minimum_value": "0",
+ "minimum_value_warning": "1",
+ "enabled": "pause_at == 'layer_no'"
+ },
+ "head_park_x":
+ {
+ "label": "Park Print Head X",
+ "description": "What X location does the head move to when pausing.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 190
+ },
+ "head_park_y":
+ {
+ "label": "Park Print Head Y",
+ "description": "What Y location does the head move to when pausing.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 190
+ },
+ "retraction_amount":
+ {
+ "label": "Retraction",
+ "description": "How much filament must be retracted at pause.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0
+ },
+ "retraction_speed":
+ {
+ "label": "Retraction Speed",
+ "description": "How fast to retract the filament.",
+ "unit": "mm/s",
+ "type": "float",
+ "default_value": 25
+ },
+ "extrude_amount":
+ {
+ "label": "Extrude Amount",
+ "description": "How much filament should be extruded after pause. This is needed when doing a material change on Ultimaker2's to compensate for the retraction after the change. In that case 128+ is recommended.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0
+ },
+ "extrude_speed":
+ {
+ "label": "Extrude Speed",
+ "description": "How fast to extrude the material after pause.",
+ "unit": "mm/s",
+ "type": "float",
+ "default_value": 3.3333
+ },
+ "redo_layers":
+ {
+ "label": "Redo Layers",
+ "description": "Redo a number of previous layers after a pause to increases adhesion.",
+ "unit": "layers",
+ "type": "int",
+ "default_value": 0
+ },
+ "standby_temperature":
+ {
+ "label": "Standby Temperature",
+ "description": "Change the temperature during the pause",
+ "unit": "°C",
+ "type": "int",
+ "default_value": 0
+ },
+ "resume_temperature":
+ {
+ "label": "Resume Temperature",
+ "description": "Change the temperature after the pause",
+ "unit": "°C",
+ "type": "int",
+ "default_value": 0
+ }
+ }
+ }"""
+
+ def execute(self, data: list):
+
+ """data is a list. Each index contains a layer"""
+
+ x = 0.
+ y = 0.
+ pause_at = self.getSettingValueByKey("pause_at")
+ pause_height = self.getSettingValueByKey("pause_height")
+ pause_layer = self.getSettingValueByKey("pause_layer")
+ retraction_amount = self.getSettingValueByKey("retraction_amount")
+ retraction_speed = self.getSettingValueByKey("retraction_speed")
+ extrude_amount = self.getSettingValueByKey("extrude_amount")
+ extrude_speed = self.getSettingValueByKey("extrude_speed")
+ park_x = self.getSettingValueByKey("head_park_x")
+ park_y = self.getSettingValueByKey("head_park_y")
+ layers_started = False
+ redo_layers = self.getSettingValueByKey("redo_layers")
+ standby_temperature = self.getSettingValueByKey("standby_temperature")
+ resume_temperature = self.getSettingValueByKey("resume_temperature")
+
+ # T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
+ # with open("out.txt", "w") as f:
+ # f.write(T)
+
+ # use offset to calculate the current height: = -
+ layer_0_z = 0.
+ current_z = 0
+ got_first_g_cmd_on_layer_0 = False
+ for index, layer in enumerate(data):
+ lines = layer.split("\n")
+ for line in lines:
+ if ";LAYER:0" in line:
+ layers_started = True
+ if not layers_started:
+ continue
+
+ if self.getValue(line, "Z") is not None:
+ current_z = self.getValue(line, "Z")
+
+ if pause_at == "height":
+ if self.getValue(line, "G") != 1 and self.getValue(line, "G") != 0:
+ continue
+
+ if not got_first_g_cmd_on_layer_0:
+ layer_0_z = current_z
+ got_first_g_cmd_on_layer_0 = True
+
+ x = self.getValue(line, "X", x)
+ y = self.getValue(line, "Y", y)
+
+ current_height = current_z - layer_0_z
+ if current_height < pause_height:
+ break #Try the next layer.
+ else: #Pause at layer.
+ if not line.startswith(";LAYER:"):
+ continue
+ current_layer = line[len(";LAYER:"):]
+ try:
+ current_layer = int(current_layer)
+ except ValueError: #Couldn't cast to int. Something is wrong with this g-code data.
+ continue
+ if current_layer < pause_layer:
+ break #Try the next layer.
+
+ prevLayer = data[index - 1]
+ prevLines = prevLayer.split("\n")
+ current_e = 0.
+
+ # Access last layer, browse it backwards to find
+ # last extruder absolute position
+ for prevLine in reversed(prevLines):
+ current_e = self.getValue(prevLine, "E", -1)
+ if current_e >= 0:
+ break
+
+ # include a number of previous layers
+ for i in range(1, redo_layers + 1):
+ prevLayer = data[index - i]
+ layer = prevLayer + layer
+
+ # Get extruder's absolute position at the
+ # begining of the first layer redone
+ # see https://github.com/nallath/PostProcessingPlugin/issues/55
+ if i == redo_layers:
+ prevLines = prevLayer.split("\n")
+ for line in prevLines:
+ new_e = self.getValue(line, 'E', current_e)
+
+ if new_e != current_e:
+ current_e = new_e
+ break
+
+ prepend_gcode = ";TYPE:CUSTOM\n"
+ prepend_gcode += ";added code by post processing\n"
+ prepend_gcode += ";script: PauseAtHeight.py\n"
+ if pause_at == "height":
+ prepend_gcode += ";current z: {z}\n".format(z = current_z)
+ prepend_gcode += ";current height: {height}\n".format(height = current_height)
+ else:
+ prepend_gcode += ";current layer: {layer}\n".format(layer = current_layer)
+
+ # Retraction
+ prepend_gcode += self.putValue(M = 83) + "\n"
+ if retraction_amount != 0:
+ prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
+
+ # Move the head away
+ prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
+ prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n"
+ if current_z < 15:
+ prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n"
+
+ # Disable the E steppers
+ prepend_gcode += self.putValue(M = 84, E = 0) + "\n"
+
+ # Set extruder standby temperature
+ prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n"
+
+ # Wait till the user continues printing
+ prepend_gcode += self.putValue(M = 0) + ";Do the actual pause\n"
+
+ # Set extruder resume temperature
+ prepend_gcode += self.putValue(M = 109, S = resume_temperature) + "; resume temperature\n"
+
+ # Push the filament back,
+ if retraction_amount != 0:
+ prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
+
+ # Optionally extrude material
+ if extrude_amount != 0:
+ prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "\n"
+
+ # and retract again, the properly primes the nozzle
+ # when changing filament.
+ if retraction_amount != 0:
+ prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
+
+ # Move the head back
+ prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
+ prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n"
+ if retraction_amount != 0:
+ prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
+ prepend_gcode += self.putValue(G = 1, F = 9000) + "\n"
+ prepend_gcode += self.putValue(M = 82) + "\n"
+
+ # reset extrude value to pre pause value
+ prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
+
+ layer = prepend_gcode + layer
+
+
+ # Override the data of this layer with the
+ # modified data
+ data[index] = layer
+ return data
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeightforRepetier.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeightforRepetier.py
new file mode 100644
index 0000000000..f6c93d9ae6
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeightforRepetier.py
@@ -0,0 +1,169 @@
+from ..Script import Script
+class PauseAtHeightforRepetier(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Pause at height for repetier",
+ "key": "PauseAtHeightforRepetier",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "pause_height":
+ {
+ "label": "Pause height",
+ "description": "At what height should the pause occur",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "head_park_x":
+ {
+ "label": "Park print head X",
+ "description": "What x location does the head move to when pausing.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "head_park_y":
+ {
+ "label": "Park print head Y",
+ "description": "What y location does the head move to when pausing.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "head_move_Z":
+ {
+ "label": "Head move Z",
+ "description": "The Hieght of Z-axis retraction before parking.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 15.0
+ },
+ "retraction_amount":
+ {
+ "label": "Retraction",
+ "description": "How much fillament must be retracted at pause.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "extrude_amount":
+ {
+ "label": "Extrude amount",
+ "description": "How much filament should be extruded after pause. This is needed when doing a material change on Ultimaker2's to compensate for the retraction after the change. In that case 128+ is recommended.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 90.0
+ },
+ "redo_layers":
+ {
+ "label": "Redo layers",
+ "description": "Redo a number of previous layers after a pause to increases adhesion.",
+ "unit": "layers",
+ "type": "int",
+ "default_value": 0
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ x = 0.
+ y = 0.
+ current_z = 0.
+ pause_z = self.getSettingValueByKey("pause_height")
+ retraction_amount = self.getSettingValueByKey("retraction_amount")
+ extrude_amount = self.getSettingValueByKey("extrude_amount")
+ park_x = self.getSettingValueByKey("head_park_x")
+ park_y = self.getSettingValueByKey("head_park_y")
+ move_Z = self.getSettingValueByKey("head_move_Z")
+ layers_started = False
+ redo_layers = self.getSettingValueByKey("redo_layers")
+ for layer in data:
+ lines = layer.split("\n")
+ for line in lines:
+ if ";LAYER:0" in line:
+ layers_started = True
+ continue
+
+ if not layers_started:
+ continue
+
+ if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
+ current_z = self.getValue(line, 'Z')
+ x = self.getValue(line, 'X', x)
+ y = self.getValue(line, 'Y', y)
+ if current_z != None:
+ if current_z >= pause_z:
+
+ index = data.index(layer)
+ prevLayer = data[index-1]
+ prevLines = prevLayer.split("\n")
+ current_e = 0.
+ for prevLine in reversed(prevLines):
+ current_e = self.getValue(prevLine, 'E', -1)
+ if current_e >= 0:
+ break
+
+ prepend_gcode = ";TYPE:CUSTOM\n"
+ prepend_gcode += ";added code by post processing\n"
+ prepend_gcode += ";script: PauseAtHeightforRepetier.py\n"
+ prepend_gcode += ";current z: %f \n" % (current_z)
+ prepend_gcode += ";current X: %f \n" % (x)
+ prepend_gcode += ";current Y: %f \n" % (y)
+
+ #Retraction
+ prepend_gcode += "M83\n"
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E-%f F6000\n" % (retraction_amount)
+
+ #Move the head away
+ prepend_gcode += "G1 Z%f F300\n" % (1 + current_z)
+ prepend_gcode += "G1 X%f Y%f F9000\n" % (park_x, park_y)
+ if current_z < move_Z:
+ prepend_gcode += "G1 Z%f F300\n" % (current_z + move_Z)
+
+ #Disable the E steppers
+ prepend_gcode += "M84 E0\n"
+ #Wait till the user continues printing
+ prepend_gcode += "@pause now change filament and press continue printing ;Do the actual pause\n"
+
+ #Push the filament back,
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E%f F6000\n" % (retraction_amount)
+
+ # Optionally extrude material
+ if extrude_amount != 0:
+ prepend_gcode += "G1 E%f F200\n" % (extrude_amount)
+ prepend_gcode += "@info wait for cleaning nozzle from previous filament\n"
+ prepend_gcode += "@pause remove the waste filament from parking area and press continue printing\n"
+
+ # and retract again, the properly primes the nozzle when changing filament.
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E-%f F6000\n" % (retraction_amount)
+
+ #Move the head back
+ prepend_gcode += "G1 Z%f F300\n" % (1 + current_z)
+ prepend_gcode +="G1 X%f Y%f F9000\n" % (x, y)
+ if retraction_amount != 0:
+ prepend_gcode +="G1 E%f F6000\n" % (retraction_amount)
+ prepend_gcode +="G1 F9000\n"
+ prepend_gcode +="M82\n"
+
+ # reset extrude value to pre pause value
+ prepend_gcode +="G92 E%f\n" % (current_e)
+
+ layer = prepend_gcode + layer
+
+ # include a number of previous layers
+ for i in range(1, redo_layers + 1):
+ prevLayer = data[index-i]
+ layer = prevLayer + layer
+
+ data[index] = layer #Override the data of this layer with the modified data
+ return data
+ break
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/SearchAndReplace.py b/plugins/PostProcessingPlugin/scripts/SearchAndReplace.py
new file mode 100644
index 0000000000..68d697e470
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/SearchAndReplace.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2017 Ruben Dulek
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
+import re #To perform the search and replace.
+
+from ..Script import Script
+
+## Performs a search-and-replace on all g-code.
+#
+# Due to technical limitations, the search can't cross the border between
+# layers.
+class SearchAndReplace(Script):
+ def getSettingDataString(self):
+ return """{
+ "name": "Search and Replace",
+ "key": "SearchAndReplace",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "search":
+ {
+ "label": "Search",
+ "description": "All occurrences of this text will get replaced by the replacement text.",
+ "type": "str",
+ "default_value": ""
+ },
+ "replace":
+ {
+ "label": "Replace",
+ "description": "The search text will get replaced by this text.",
+ "type": "str",
+ "default_value": ""
+ },
+ "is_regex":
+ {
+ "label": "Use Regular Expressions",
+ "description": "When enabled, the search text will be interpreted as a regular expression.",
+ "type": "bool",
+ "default_value": false
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ search_string = self.getSettingValueByKey("search")
+ if not self.getSettingValueByKey("is_regex"):
+ search_string = re.escape(search_string) #Need to search for the actual string, not as a regex.
+ search_regex = re.compile(search_string)
+
+ replace_string = self.getSettingValueByKey("replace")
+
+ for layer_number, layer in enumerate(data):
+ data[layer_number] = re.sub(search_regex, replace_string, layer) #Replace all.
+
+ return data
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/Stretch.py b/plugins/PostProcessingPlugin/scripts/Stretch.py
new file mode 100644
index 0000000000..bcb923d3ff
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/Stretch.py
@@ -0,0 +1,469 @@
+# This PostProcessingPlugin script is released under the terms of the AGPLv3 or higher.
+"""
+Copyright (c) 2017 Christophe Baribaud 2017
+Python implementation of https://github.com/electrocbd/post_stretch
+Correction of hole sizes, cylinder diameters and curves
+See the original description in https://github.com/electrocbd/post_stretch
+
+WARNING This script has never been tested with several extruders
+"""
+from ..Script import Script
+import numpy as np
+from UM.Logger import Logger
+from UM.Application import Application
+import re
+
+def _getValue(line, key, default=None):
+ """
+ Convenience function that finds the value in a line of g-code.
+ When requesting key = x from line "G1 X100" the value 100 is returned.
+ It is a copy of Stript's method, so it is no DontRepeatYourself, but
+ I split the class into setup part (Stretch) and execution part (Strecher)
+ and only the setup part inherits from Script
+ """
+ if not key in line or (";" in line and line.find(key) > line.find(";")):
+ return default
+ sub_part = line[line.find(key) + 1:]
+ number = re.search(r"^-?[0-9]+\.?[0-9]*", sub_part)
+ if number is None:
+ return default
+ return float(number.group(0))
+
+class GCodeStep():
+ """
+ Class to store the current value of each G_Code parameter
+ for any G-Code step
+ """
+ def __init__(self, step):
+ self.step = step
+ self.step_x = 0
+ self.step_y = 0
+ self.step_z = 0
+ self.step_e = 0
+ self.step_f = 0
+ self.comment = ""
+
+ def readStep(self, line):
+ """
+ Reads gcode from line into self
+ """
+ self.step_x = _getValue(line, "X", self.step_x)
+ self.step_y = _getValue(line, "Y", self.step_y)
+ self.step_z = _getValue(line, "Z", self.step_z)
+ self.step_e = _getValue(line, "E", self.step_e)
+ self.step_f = _getValue(line, "F", self.step_f)
+ return
+
+ def copyPosFrom(self, step):
+ """
+ Copies positions of step into self
+ """
+ self.step_x = step.step_x
+ self.step_y = step.step_y
+ self.step_z = step.step_z
+ self.step_e = step.step_e
+ self.step_f = step.step_f
+ self.comment = step.comment
+ return
+
+
+# Execution part of the stretch plugin
+class Stretcher():
+ """
+ Execution part of the stretch algorithm
+ """
+ def __init__(self, line_width, wc_stretch, pw_stretch):
+ self.line_width = line_width
+ self.wc_stretch = wc_stretch
+ self.pw_stretch = pw_stretch
+ if self.pw_stretch > line_width / 4:
+ self.pw_stretch = line_width / 4 # Limit value of pushwall stretch distance
+ self.outpos = GCodeStep(0)
+ self.vd1 = np.empty((0, 2)) # Start points of segments
+ # of already deposited material for current layer
+ self.vd2 = np.empty((0, 2)) # End points of segments
+ # of already deposited material for current layer
+ self.layer_z = 0 # Z position of the extrusion moves of the current layer
+ self.layergcode = ""
+
+ def execute(self, data):
+ """
+ Computes the new X and Y coordinates of all g-code steps
+ """
+ Logger.log("d", "Post stretch with line width = " + str(self.line_width)
+ + "mm wide circle stretch = " + str(self.wc_stretch)+ "mm"
+ + "and push wall stretch = " + str(self.pw_stretch) + "mm")
+ retdata = []
+ layer_steps = []
+ current = GCodeStep(0)
+ self.layer_z = 0.
+ current_e = 0.
+ for layer in data:
+ lines = layer.rstrip("\n").split("\n")
+ for line in lines:
+ current.comment = ""
+ if line.find(";") >= 0:
+ current.comment = line[line.find(";"):]
+ if _getValue(line, "G") == 0:
+ current.readStep(line)
+ onestep = GCodeStep(0)
+ onestep.copyPosFrom(current)
+ elif _getValue(line, "G") == 1:
+ current.readStep(line)
+ onestep = GCodeStep(1)
+ onestep.copyPosFrom(current)
+ elif _getValue(line, "G") == 92:
+ current.readStep(line)
+ onestep = GCodeStep(-1)
+ onestep.copyPosFrom(current)
+ else:
+ onestep = GCodeStep(-1)
+ onestep.copyPosFrom(current)
+ onestep.comment = line
+ if line.find(";LAYER:") >= 0 and len(layer_steps):
+ # Previous plugin "forgot" to separate two layers...
+ Logger.log("d", "Layer Z " + "{:.3f}".format(self.layer_z)
+ + " " + str(len(layer_steps)) + " steps")
+ retdata.append(self.processLayer(layer_steps))
+ layer_steps = []
+ layer_steps.append(onestep)
+ # self.layer_z is the z position of the last extrusion move (not travel move)
+ if current.step_z != self.layer_z and current.step_e != current_e:
+ self.layer_z = current.step_z
+ current_e = current.step_e
+ if len(layer_steps): # Force a new item in the array
+ Logger.log("d", "Layer Z " + "{:.3f}".format(self.layer_z)
+ + " " + str(len(layer_steps)) + " steps")
+ retdata.append(self.processLayer(layer_steps))
+ layer_steps = []
+ retdata.append(";Wide circle stretch distance " + str(self.wc_stretch) + "\n")
+ retdata.append(";Push wall stretch distance " + str(self.pw_stretch) + "\n")
+ return retdata
+
+ def extrusionBreak(self, layer_steps, i_pos):
+ """
+ Returns true if the command layer_steps[i_pos] breaks the extruded filament
+ i.e. it is a travel move
+ """
+ if i_pos == 0:
+ return True # Begining a layer always breaks filament (for simplicity)
+ step = layer_steps[i_pos]
+ prev_step = layer_steps[i_pos - 1]
+ if step.step_e != prev_step.step_e:
+ return False
+ delta_x = step.step_x - prev_step.step_x
+ delta_y = step.step_y - prev_step.step_y
+ if delta_x * delta_x + delta_y * delta_y < self.line_width * self.line_width / 4:
+ # This is a very short movement, less than 0.5 * line_width
+ # It does not break filament, we should stay in the same extrusion sequence
+ return False
+ return True # New sequence
+
+
+ def processLayer(self, layer_steps):
+ """
+ Computes the new coordinates of g-code steps
+ for one layer (all the steps at the same Z coordinate)
+ """
+ self.outpos.step_x = -1000 # Force output of X and Y coordinates
+ self.outpos.step_y = -1000 # at each start of layer
+ self.layergcode = ""
+ self.vd1 = np.empty((0, 2))
+ self.vd2 = np.empty((0, 2))
+ orig_seq = np.empty((0, 2))
+ modif_seq = np.empty((0, 2))
+ iflush = 0
+ for i, step in enumerate(layer_steps):
+ if step.step == 0 or step.step == 1:
+ if self.extrusionBreak(layer_steps, i):
+ # No extrusion since the previous step, so it is a travel move
+ # Let process steps accumulated into orig_seq,
+ # which are a sequence of continuous extrusion
+ modif_seq = np.copy(orig_seq)
+ if len(orig_seq) >= 2:
+ self.workOnSequence(orig_seq, modif_seq)
+ self.generate(layer_steps, iflush, i, modif_seq)
+ iflush = i
+ orig_seq = np.empty((0, 2))
+ orig_seq = np.concatenate([orig_seq, np.array([[step.step_x, step.step_y]])])
+ if len(orig_seq):
+ modif_seq = np.copy(orig_seq)
+ if len(orig_seq) >= 2:
+ self.workOnSequence(orig_seq, modif_seq)
+ self.generate(layer_steps, iflush, len(layer_steps), modif_seq)
+ return self.layergcode
+
+ def stepToGcode(self, onestep):
+ """
+ Converts a step into G-Code
+ For each of the X, Y, Z, E and F parameter,
+ the parameter is written only if its value changed since the
+ previous g-code step.
+ """
+ sout = ""
+ if onestep.step_f != self.outpos.step_f:
+ self.outpos.step_f = onestep.step_f
+ sout += " F{:.0f}".format(self.outpos.step_f).rstrip(".")
+ if onestep.step_x != self.outpos.step_x or onestep.step_y != self.outpos.step_y:
+ assert onestep.step_x >= -1000 and onestep.step_x < 1000 # If this assertion fails,
+ # something went really wrong !
+ self.outpos.step_x = onestep.step_x
+ sout += " X{:.3f}".format(self.outpos.step_x).rstrip("0").rstrip(".")
+ assert onestep.step_y >= -1000 and onestep.step_y < 1000 # If this assertion fails,
+ # something went really wrong !
+ self.outpos.step_y = onestep.step_y
+ sout += " Y{:.3f}".format(self.outpos.step_y).rstrip("0").rstrip(".")
+ if onestep.step_z != self.outpos.step_z or onestep.step_z != self.layer_z:
+ self.outpos.step_z = onestep.step_z
+ sout += " Z{:.3f}".format(self.outpos.step_z).rstrip("0").rstrip(".")
+ if onestep.step_e != self.outpos.step_e:
+ self.outpos.step_e = onestep.step_e
+ sout += " E{:.5f}".format(self.outpos.step_e).rstrip("0").rstrip(".")
+ return sout
+
+ def generate(self, layer_steps, ibeg, iend, orig_seq):
+ """
+ Appends g-code lines to the plugin's returned string
+ starting from step ibeg included and until step iend excluded
+ """
+ ipos = 0
+ for i in range(ibeg, iend):
+ if layer_steps[i].step == 0:
+ layer_steps[i].step_x = orig_seq[ipos][0]
+ layer_steps[i].step_y = orig_seq[ipos][1]
+ sout = "G0" + self.stepToGcode(layer_steps[i])
+ self.layergcode = self.layergcode + sout + "\n"
+ ipos = ipos + 1
+ elif layer_steps[i].step == 1:
+ layer_steps[i].step_x = orig_seq[ipos][0]
+ layer_steps[i].step_y = orig_seq[ipos][1]
+ sout = "G1" + self.stepToGcode(layer_steps[i])
+ self.layergcode = self.layergcode + sout + "\n"
+ ipos = ipos + 1
+ else:
+ self.layergcode = self.layergcode + layer_steps[i].comment + "\n"
+
+
+ def workOnSequence(self, orig_seq, modif_seq):
+ """
+ Computes new coordinates for a sequence
+ A sequence is a list of consecutive g-code steps
+ of continuous material extrusion
+ """
+ d_contact = self.line_width / 2.0
+ if (len(orig_seq) > 2 and
+ ((orig_seq[len(orig_seq) - 1] - orig_seq[0]) ** 2).sum(0) < d_contact * d_contact):
+ # Starting and ending point of the sequence are nearby
+ # It is a closed loop
+ #self.layergcode = self.layergcode + ";wideCircle\n"
+ self.wideCircle(orig_seq, modif_seq)
+ else:
+ #self.layergcode = self.layergcode + ";wideTurn\n"
+ self.wideTurn(orig_seq, modif_seq) # It is an open curve
+ if len(orig_seq) > 6: # Don't try push wall on a short sequence
+ self.pushWall(orig_seq, modif_seq)
+ if len(orig_seq):
+ self.vd1 = np.concatenate([self.vd1, np.array(orig_seq[:-1])])
+ self.vd2 = np.concatenate([self.vd2, np.array(orig_seq[1:])])
+
+ def wideCircle(self, orig_seq, modif_seq):
+ """
+ Similar to wideTurn
+ The first and last point of the sequence are the same,
+ so it is possible to extend the end of the sequence
+ with its beginning when seeking for triangles
+
+ It is necessary to find the direction of the curve, knowing three points (a triangle)
+ If the triangle is not wide enough, there is a huge risk of finding
+ an incorrect orientation, due to insufficient accuracy.
+ So, when the consecutive points are too close, the method
+ use following and preceding points to form a wider triangle around
+ the current point
+ dmin_tri is the minimum distance between two consecutive points
+ of an acceptable triangle
+ """
+ dmin_tri = self.line_width / 2.0
+ iextra_base = np.floor_divide(len(orig_seq), 3) # Nb of extra points
+ ibeg = 0 # Index of first point of the triangle
+ iend = 0 # Index of the third point of the triangle
+ for i, step in enumerate(orig_seq):
+ if i == 0 or i == len(orig_seq) - 1:
+ # First and last point of the sequence are the same,
+ # so it is necessary to skip one of these two points
+ # when creating a triangle containing the first or the last point
+ iextra = iextra_base + 1
+ else:
+ iextra = iextra_base
+ # i is the index of the second point of the triangle
+ # pos_after is the array of positions of the original sequence
+ # after the current point
+ pos_after = np.resize(np.roll(orig_seq, -i-1, 0), (iextra, 2))
+ # Vector of distances between the current point and each following point
+ dist_from_point = ((step - pos_after) ** 2).sum(1)
+ if np.amax(dist_from_point) < dmin_tri * dmin_tri:
+ continue
+ iend = np.argmax(dist_from_point >= dmin_tri * dmin_tri)
+ # pos_before is the array of positions of the original sequence
+ # before the current point
+ pos_before = np.resize(np.roll(orig_seq, -i, 0)[::-1], (iextra, 2))
+ # This time, vector of distances between the current point and each preceding point
+ dist_from_point = ((step - pos_before) ** 2).sum(1)
+ if np.amax(dist_from_point) < dmin_tri * dmin_tri:
+ continue
+ ibeg = np.argmax(dist_from_point >= dmin_tri * dmin_tri)
+ # See https://github.com/electrocbd/post_stretch for explanations
+ # relpos is the relative position of the projection of the second point
+ # of the triangle on the segment from the first to the third point
+ # 0 means the position of the first point, 1 means the position of the third,
+ # intermediate values are positions between
+ length_base = ((pos_after[iend] - pos_before[ibeg]) ** 2).sum(0)
+ relpos = ((step - pos_before[ibeg])
+ * (pos_after[iend] - pos_before[ibeg])).sum(0)
+ if np.fabs(relpos) < 1000.0 * np.fabs(length_base):
+ relpos /= length_base
+ else:
+ relpos = 0.5 # To avoid division by zero or precision loss
+ projection = (pos_before[ibeg] + relpos * (pos_after[iend] - pos_before[ibeg]))
+ dist_from_proj = np.sqrt(((projection - step) ** 2).sum(0))
+ if dist_from_proj > 0.001: # Move central point only if points are not aligned
+ modif_seq[i] = (step - (self.wc_stretch / dist_from_proj)
+ * (projection - step))
+ return
+
+ def wideTurn(self, orig_seq, modif_seq):
+ '''
+ We have to select three points in order to form a triangle
+ These three points should be far enough from each other to have
+ a reliable estimation of the orientation of the current turn
+ '''
+ dmin_tri = self.line_width / 2.0
+ ibeg = 0
+ iend = 2
+ for i in range(1, len(orig_seq) - 1):
+ dist_from_point = ((orig_seq[i] - orig_seq[i+1:]) ** 2).sum(1)
+ if np.amax(dist_from_point) < dmin_tri * dmin_tri:
+ continue
+ iend = i + 1 + np.argmax(dist_from_point >= dmin_tri * dmin_tri)
+ dist_from_point = ((orig_seq[i] - orig_seq[i-1::-1]) ** 2).sum(1)
+ if np.amax(dist_from_point) < dmin_tri * dmin_tri:
+ continue
+ ibeg = i - 1 - np.argmax(dist_from_point >= dmin_tri * dmin_tri)
+ length_base = ((orig_seq[iend] - orig_seq[ibeg]) ** 2).sum(0)
+ relpos = ((orig_seq[i] - orig_seq[ibeg]) * (orig_seq[iend] - orig_seq[ibeg])).sum(0)
+ if np.fabs(relpos) < 1000.0 * np.fabs(length_base):
+ relpos /= length_base
+ else:
+ relpos = 0.5
+ projection = orig_seq[ibeg] + relpos * (orig_seq[iend] - orig_seq[ibeg])
+ dist_from_proj = np.sqrt(((projection - orig_seq[i]) ** 2).sum(0))
+ if dist_from_proj > 0.001:
+ modif_seq[i] = (orig_seq[i] - (self.wc_stretch / dist_from_proj)
+ * (projection - orig_seq[i]))
+ return
+
+ def pushWall(self, orig_seq, modif_seq):
+ """
+ The algorithm tests for each segment if material was
+ already deposited at one or the other side of this segment.
+ If material was deposited at one side but not both,
+ the segment is moved into the direction of the deposited material,
+ to "push the wall"
+
+ Already deposited material is stored as segments.
+ vd1 is the array of the starting points of the segments
+ vd2 is the array of the ending points of the segments
+ For example, segment nr 8 starts at position self.vd1[8]
+ and ends at position self.vd2[8]
+ """
+ dist_palp = self.line_width # Palpation distance to seek for a wall
+ mrot = np.array([[0, -1], [1, 0]]) # Rotation matrix for a quarter turn
+ for i in range(len(orig_seq)):
+ ibeg = i # Index of the first point of the segment
+ iend = i + 1 # Index of the last point of the segment
+ if iend == len(orig_seq):
+ iend = i - 1
+ xperp = np.dot(mrot, orig_seq[iend] - orig_seq[ibeg])
+ xperp = xperp / np.sqrt((xperp ** 2).sum(-1))
+ testleft = orig_seq[ibeg] + xperp * dist_palp
+ materialleft = False # Is there already extruded material at the left of the segment
+ testright = orig_seq[ibeg] - xperp * dist_palp
+ materialright = False # Is there already extruded material at the right of the segment
+ if self.vd1.shape[0]:
+ relpos = np.clip(((testleft - self.vd1) * (self.vd2 - self.vd1)).sum(1)
+ / ((self.vd2 - self.vd1) * (self.vd2 - self.vd1)).sum(1), 0., 1.)
+ nearpoints = self.vd1 + relpos[:, np.newaxis] * (self.vd2 - self.vd1)
+ # nearpoints is the array of the nearest points of each segment
+ # from the point testleft
+ dist = ((testleft - nearpoints) * (testleft - nearpoints)).sum(1)
+ # dist is the array of the squares of the distances between testleft
+ # and each segment
+ if np.amin(dist) <= dist_palp * dist_palp:
+ materialleft = True
+ # Now the same computation with the point testright at the other side of the
+ # current segment
+ relpos = np.clip(((testright - self.vd1) * (self.vd2 - self.vd1)).sum(1)
+ / ((self.vd2 - self.vd1) * (self.vd2 - self.vd1)).sum(1), 0., 1.)
+ nearpoints = self.vd1 + relpos[:, np.newaxis] * (self.vd2 - self.vd1)
+ dist = ((testright - nearpoints) * (testright - nearpoints)).sum(1)
+ if np.amin(dist) <= dist_palp * dist_palp:
+ materialright = True
+ if materialleft and not materialright:
+ modif_seq[ibeg] = modif_seq[ibeg] + xperp * self.pw_stretch
+ elif not materialleft and materialright:
+ modif_seq[ibeg] = modif_seq[ibeg] - xperp * self.pw_stretch
+ if materialleft and materialright:
+ modif_seq[ibeg] = orig_seq[ibeg] # Surrounded by walls, don't move
+
+# Setup part of the stretch plugin
+class Stretch(Script):
+ """
+ Setup part of the stretch algorithm
+ The only parameter is the stretch distance
+ """
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Post stretch script",
+ "key": "Stretch",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "wc_stretch":
+ {
+ "label": "Wide circle stretch distance",
+ "description": "Distance by which the points are moved by the correction effect in corners. The higher this value, the higher the effect",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.08,
+ "minimum_value": 0,
+ "minimum_value_warning": 0,
+ "maximum_value_warning": 0.2
+ },
+ "pw_stretch":
+ {
+ "label": "Push Wall stretch distance",
+ "description": "Distance by which the points are moved by the correction effect when two lines are nearby. The higher this value, the higher the effect",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.08,
+ "minimum_value": 0,
+ "minimum_value_warning": 0,
+ "maximum_value_warning": 0.2
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ """
+ Entry point of the plugin.
+ data is the list of original g-code instructions,
+ the returned string is the list of modified g-code instructions
+ """
+ stretcher = Stretcher(
+ Application.getInstance().getGlobalContainerStack().getProperty("line_width", "value")
+ , self.getSettingValueByKey("wc_stretch"), self.getSettingValueByKey("pw_stretch"))
+ return stretcher.execute(data)
+
diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py
index 24bdedd368..cd0eda2929 100644
--- a/plugins/SimulationView/SimulationPass.py
+++ b/plugins/SimulationView/SimulationPass.py
@@ -93,6 +93,7 @@ class SimulationPass(RenderPass):
self.bind()
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True)
+ active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
head_position = None # Indicates the current position of the print head
nozzle_node = None
diff --git a/plugins/SimulationView/SimulationSliderLabel.qml b/plugins/SimulationView/SimulationSliderLabel.qml
index 1c8daf867f..6f7749df63 100644
--- a/plugins/SimulationView/SimulationSliderLabel.qml
+++ b/plugins/SimulationView/SimulationSliderLabel.qml
@@ -49,7 +49,7 @@ UM.PointingRectangle {
anchors {
left: parent.left
- leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
+ leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter
}
@@ -91,7 +91,7 @@ UM.PointingRectangle {
anchors {
left: parent.right
- leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
+ leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter
}
diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py
index 44f472129f..35ce9cc37a 100644
--- a/plugins/SimulationView/SimulationView.py
+++ b/plugins/SimulationView/SimulationView.py
@@ -1,9 +1,10 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import sys
from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QOpenGLContext
from PyQt5.QtWidgets import QApplication
from UM.Application import Application
@@ -13,6 +14,7 @@ from UM.Logger import Logger
from UM.Math.Color import Color
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Message import Message
+from UM.Platform import Platform
from UM.PluginRegistry import PluginRegistry
from UM.Preferences import Preferences
from UM.Resources import Resources
@@ -23,7 +25,8 @@ from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.OpenGLContext import OpenGLContext
from UM.View.View import View
from UM.i18n import i18nCatalog
-from cura.ConvexHullNode import ConvexHullNode
+from cura.Scene.ConvexHullNode import ConvexHullNode
+from cura.CuraApplication import CuraApplication
from .NozzleNode import NozzleNode
from .SimulationPass import SimulationPass
@@ -95,13 +98,16 @@ class SimulationView(View):
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
- self._compatibility_mode = True # for safety
+ self._compatibility_mode = self._evaluateCompatibilityMode()
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
title = catalog.i18nc("@info:title", "Simulation View"))
+ def _evaluateCompatibilityMode(self):
+ return OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
+
def _resetSettings(self):
- self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed
+ self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
self._extruder_count = 0
self._extruder_opacity = [1.0, 1.0, 1.0, 1.0]
self._show_travel_moves = 0
@@ -124,7 +130,7 @@ class SimulationView(View):
# Currently the RenderPass constructor requires a size > 0
# This should be fixed in RenderPass's constructor.
self._layer_pass = SimulationPass(1, 1)
- self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
+ self._compatibility_mode = self._evaluateCompatibilityMode()
self._layer_pass.setSimulationView(self)
return self._layer_pass
@@ -336,12 +342,22 @@ class SimulationView(View):
min_layer_number = sys.maxsize
max_layer_number = -sys.maxsize
for layer_id in layer_data.getLayers():
+
+ # If a layer doesn't contain any polygons, skip it (for infill meshes taller than print objects
+ if len(layer_data.getLayer(layer_id).polygons) < 1:
+ continue
+
# Store the max and min feedrates and thicknesses for display purposes
for p in layer_data.getLayer(layer_id).polygons:
self._max_feedrate = max(float(p.lineFeedrates.max()), self._max_feedrate)
self._min_feedrate = min(float(p.lineFeedrates.min()), self._min_feedrate)
self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness)
- self._min_thickness = min(float(p.lineThicknesses.min()), self._min_thickness)
+ try:
+ self._min_thickness = min(float(p.lineThicknesses[numpy.nonzero(p.lineThicknesses)].min()), self._min_thickness)
+ except:
+ # Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding
+ # the zero) can't be calculated
+ Logger.log("i", "Min thickness can't be calculated because all the values are zero")
if max_layer_number < layer_id:
max_layer_number = layer_id
if min_layer_number > layer_id:
@@ -414,6 +430,23 @@ class SimulationView(View):
return True
if event.type == Event.ViewActivateEvent:
+ # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
+ # This can happen when you do the following steps:
+ # 1. Start Cura
+ # 2. Load a model
+ # 3. Switch to Custom mode
+ # 4. Select the model and click on the per-object tool icon
+ # 5. Switch view to Layer view or X-Ray
+ # 6. Cura will very likely crash
+ # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why.
+ # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL
+ # context is None.
+ if Platform.isOSX():
+ if QOpenGLContext.currentContext() is None:
+ Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
+ CuraApplication.getInstance().callLater(lambda e=event: self.event(e))
+ return
+
# Make sure the SimulationPass is created
layer_pass = self.getSimulationPass()
self.getRenderer().addRenderPass(layer_pass)
@@ -509,8 +542,7 @@ class SimulationView(View):
def _updateWithPreferences(self):
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
- self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(
- Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
+ self._compatibility_mode = self._evaluateCompatibilityMode()
self.setSimulationViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type"))));
@@ -607,4 +639,3 @@ class _CreateTopLayersJob(Job):
def cancel(self):
self._cancel = True
super().cancel()
-
diff --git a/plugins/SimulationView/SimulationView.qml b/plugins/SimulationView/SimulationView.qml
index 19ae81a6e3..6aad413f9b 100644
--- a/plugins/SimulationView/SimulationView.qml
+++ b/plugins/SimulationView/SimulationView.qml
@@ -61,7 +61,7 @@ Item
Button {
id: collapseButton
anchors.top: parent.top
- anchors.topMargin: Math.floor(UM.Theme.getSize("default_margin").height + (UM.Theme.getSize("layerview_row").height - UM.Theme.getSize("default_margin").height) / 2)
+ anchors.topMargin: Math.round(UM.Theme.getSize("default_margin").height + (UM.Theme.getSize("layerview_row").height - UM.Theme.getSize("default_margin").height) / 2)
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
@@ -176,7 +176,6 @@ Item
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
}
-
}
Label
@@ -194,7 +193,7 @@ Item
Item
{
- height: Math.floor(UM.Theme.getSize("default_margin").width / 2)
+ height: Math.round(UM.Theme.getSize("default_margin").width / 2)
width: width
}
@@ -232,7 +231,7 @@ Item
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: model.color
- radius: width / 2
+ radius: Math.round(width / 2)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: !viewSettings.show_legend & !viewSettings.show_gradient
@@ -250,7 +249,7 @@ Item
anchors.verticalCenter: parent.verticalCenter
anchors.left: extrudersModelCheckBox.left;
anchors.right: extrudersModelCheckBox.right;
- anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
+ anchors.leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width/2)
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
@@ -317,7 +316,7 @@ Item
anchors.verticalCenter: parent.verticalCenter
anchors.left: legendModelCheckBox.left;
anchors.right: legendModelCheckBox.right;
- anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
+ anchors.leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width/2)
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
@@ -462,7 +461,7 @@ Item
visible: viewSettings.show_feedrate_gradient
anchors.left: parent.right
height: parent.width
- width: UM.Theme.getSize("layerview_row").height * 1.5
+ width: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
@@ -486,37 +485,37 @@ Item
}
}
- // Gradient colors for layer thickness
+ // Gradient colors for layer thickness (similar to parula colormap)
Rectangle { // In QML 5.9 can be changed by LinearGradient
// Invert values because then the bar is rotated 90 degrees
id: thicknessGradient
visible: viewSettings.show_thickness_gradient
anchors.left: parent.right
height: parent.width
- width: UM.Theme.getSize("layerview_row").height * 1.5
+ width: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
gradient: Gradient {
GradientStop {
position: 0.000
- color: Qt.rgba(1, 0, 0, 1)
+ color: Qt.rgba(1, 1, 0, 1)
}
GradientStop {
position: 0.25
- color: Qt.rgba(0.5, 0.5, 0, 1)
+ color: Qt.rgba(1, 0.75, 0.25, 1)
}
GradientStop {
position: 0.5
- color: Qt.rgba(0, 1, 0, 1)
+ color: Qt.rgba(0, 0.75, 0.5, 1)
}
GradientStop {
position: 0.75
- color: Qt.rgba(0, 0.5, 0.5, 1)
+ color: Qt.rgba(0, 0.375, 0.75, 1)
}
GradientStop {
position: 1.0
- color: Qt.rgba(0, 0, 1, 1)
+ color: Qt.rgba(0, 0, 0.5, 1)
}
}
}
diff --git a/plugins/SimulationView/SimulationViewProxy.py b/plugins/SimulationView/SimulationViewProxy.py
index e144b841e6..a84b151983 100644
--- a/plugins/SimulationView/SimulationViewProxy.py
+++ b/plugins/SimulationView/SimulationViewProxy.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
@@ -117,7 +117,7 @@ class SimulationViewProxy(QObject):
def setSimulationViewType(self, layer_view_type):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
- active_view.setSimulationViewisinstance(layer_view_type)
+ active_view.setSimulationViewType(layer_view_type)
@pyqtSlot(result=int)
def getSimulationViewType(self):
diff --git a/plugins/SimulationView/__init__.py b/plugins/SimulationView/__init__.py
index 15e113bd8e..360fdc1de9 100644
--- a/plugins/SimulationView/__init__.py
+++ b/plugins/SimulationView/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtQml import qmlRegisterSingletonType
@@ -18,7 +18,7 @@ def getMetaData():
}
def createSimulationViewProxy(engine, script_engine):
- return SimulationViewProxy.SimulatorViewProxy()
+ return SimulationViewProxy.SimulationViewProxy()
def register(app):
simulation_view = SimulationView.SimulationView()
diff --git a/plugins/SimulationView/layers3d.shader b/plugins/SimulationView/layers3d.shader
index 86a88fab83..03e279e9eb 100644
--- a/plugins/SimulationView/layers3d.shader
+++ b/plugins/SimulationView/layers3d.shader
@@ -54,9 +54,13 @@ vertex41core =
vec4 layerThicknessGradientColor(float abs_value, float min_value, float max_value)
{
float value = (abs_value - min_value)/(max_value - min_value);
- float red = max(2*value-1, 0);
- float green = 1-abs(1-2*value);
- float blue = max(1-2*value, 0);
+ float red = min(max(4*value-2, 0), 1);
+ float green = min(1.5*value, 0.75);
+ if (value > 0.75)
+ {
+ green = value;
+ }
+ float blue = 0.75-abs(0.25-value);
return vec4(red, green, blue, 1.0);
}
@@ -187,7 +191,7 @@ geometry41core =
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
- //And reverse so that the line is also visible from the back side.
+ //And reverse so that the line is also visible from the back side.
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
@@ -212,17 +216,17 @@ geometry41core =
EndPrimitive();
// left side
- myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
- myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
EndPrimitive();
- myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
- myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
EndPrimitive();
diff --git a/plugins/SimulationView/layers3d_shadow.shader b/plugins/SimulationView/layers3d_shadow.shader
index ad75fcf9d0..15136fcf3f 100644
--- a/plugins/SimulationView/layers3d_shadow.shader
+++ b/plugins/SimulationView/layers3d_shadow.shader
@@ -145,35 +145,42 @@ geometry41core =
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
+ //And reverse so that the line is also visible from the back side.
+ myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
+ myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
EndPrimitive();
} else {
// All normal lines are rendered as 3d tubes.
- myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
- myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
- myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
- myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
- myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
- myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
+ myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
+ myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
+ myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
+ myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
EndPrimitive();
// left side
- myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
- myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
EndPrimitive();
- myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
- myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
+ myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
EndPrimitive();
diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py
index 508d174cf2..971a324aa2 100755
--- a/plugins/SliceInfoPlugin/SliceInfo.py
+++ b/plugins/SliceInfoPlugin/SliceInfo.py
@@ -39,19 +39,26 @@ class SliceInfo(Extension):
Preferences.getInstance().addPreference("info/send_slice_info", True)
Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
- if not Preferences.getInstance().getValue("info/asked_send_slice_info") and Preferences.getInstance().getValue("info/send_slice_info"):
- self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in the preferences."),
+ if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
+ self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."),
lifetime = 0,
dismissable = False,
title = catalog.i18nc("@info:title", "Collecting Data"))
- self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
+ self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
+ description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
+ self.send_slice_info_message.addAction("Disable", name = catalog.i18nc("@action:button", "Disable"), icon = None,
+ description = catalog.i18nc("@action:tooltip", "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."), button_style = Message.ActionButtonStyle.LINK)
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
self.send_slice_info_message.show()
+ ## Perform action based on user input.
+ # Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it.
def messageActionTriggered(self, message_id, action_id):
- self.send_slice_info_message.hide()
Preferences.getInstance().setValue("info/asked_send_slice_info", True)
+ if action_id == "Disable":
+ CuraApplication.getInstance().showPreferences()
+ self.send_slice_info_message.hide()
def _onWriteStarted(self, output_device):
try:
@@ -100,7 +107,7 @@ class SliceInfo(Extension):
"brand": extruder.material.getMetaData().get("brand", "")
}
extruder_position = int(extruder.getMetaDataEntry("position", "0"))
- if extruder_position in print_information.materialLengths:
+ if len(print_information.materialLengths) > extruder_position:
extruder_dict["material_used"] = print_information.materialLengths[extruder_position]
extruder_dict["variant"] = extruder.variant.getName()
extruder_dict["nozzle_size"] = extruder.getProperty("machine_nozzle_size", "value")
@@ -159,7 +166,7 @@ class SliceInfo(Extension):
data["models"].append(model)
- print_times = print_information._print_time_message_values
+ print_times = print_information.printTimes()
data["print_times"] = {"travel": int(print_times["travel"].getDisplayString(DurationFormat.Format.Seconds)),
"support": int(print_times["support"].getDisplayString(DurationFormat.Format.Seconds)),
"infill": int(print_times["infill"].getDisplayString(DurationFormat.Format.Seconds)),
diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py
index e96c7e9bda..50ff2864b7 100644
--- a/plugins/SolidView/SolidView.py
+++ b/plugins/SolidView/SolidView.py
@@ -28,6 +28,7 @@ class SolidView(View):
self._enabled_shader = None
self._disabled_shader = None
self._non_printing_shader = None
+ self._support_mesh_shader = None
self._extruders_model = ExtrudersModel()
self._theme = None
@@ -54,6 +55,11 @@ class SolidView(View):
self._non_printing_shader.setUniformValue("u_diffuseColor", Color(*self._theme.getColor("model_non_printing").getRgb()))
self._non_printing_shader.setUniformValue("u_opacity", 0.6)
+ if not self._support_mesh_shader:
+ self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
+ self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
+ self._support_mesh_shader.setUniformValue("u_width", 5.0)
+
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
@@ -110,13 +116,23 @@ class SolidView(View):
except ValueError:
pass
- if getattr(node, "_non_printing_mesh", False):
+ if node.callDecoration("isNonPrintingMesh"):
if per_mesh_stack and (per_mesh_stack.getProperty("infill_mesh", "value") or per_mesh_stack.getProperty("cutting_mesh", "value")):
renderer.queueNode(node, shader = self._non_printing_shader, uniforms = uniforms, transparent = True)
else:
renderer.queueNode(node, shader = self._non_printing_shader, transparent = True)
elif getattr(node, "_outside_buildarea", False):
renderer.queueNode(node, shader = self._disabled_shader)
+ elif per_mesh_stack and per_mesh_stack.getProperty("support_mesh", "value"):
+ # Render support meshes with a vertical stripe that is darker
+ shade_factor = 0.6
+ uniforms["diffuse_color_2"] = [
+ uniforms["diffuse_color"][0] * shade_factor,
+ uniforms["diffuse_color"][1] * shade_factor,
+ uniforms["diffuse_color"][2] * shade_factor,
+ 1.0
+ ]
+ renderer.queueNode(node, shader = self._support_mesh_shader, uniforms = uniforms)
else:
renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms)
if node.callDecoration("isGroup") and Selection.isSelected(node):
diff --git a/plugins/SupportEraser/SupportEraser.py b/plugins/SupportEraser/SupportEraser.py
new file mode 100644
index 0000000000..8b3ad0f4dd
--- /dev/null
+++ b/plugins/SupportEraser/SupportEraser.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from UM.Math.Vector import Vector
+from UM.Tool import Tool
+from PyQt5.QtCore import Qt, QUrl
+from UM.Application import Application
+from UM.Event import Event
+from UM.Mesh.MeshBuilder import MeshBuilder
+from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
+from UM.Settings.SettingInstance import SettingInstance
+from cura.Scene.CuraSceneNode import CuraSceneNode
+from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
+from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
+from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
+
+import os
+import os.path
+
+class SupportEraser(Tool):
+ def __init__(self):
+ super().__init__()
+ self._shortcut_key = Qt.Key_G
+ self._controller = Application.getInstance().getController()
+
+ def event(self, event):
+ super().event(event)
+
+ if event.type == Event.ToolActivateEvent:
+
+ # Load the remover mesh:
+ self._createEraserMesh()
+
+ # After we load the mesh, deactivate the tool again:
+ self.getController().setActiveTool(None)
+
+ def _createEraserMesh(self):
+ node = CuraSceneNode()
+
+ node.setName("Eraser")
+ node.setSelectable(True)
+ mesh = MeshBuilder()
+ mesh.addCube(10,10,10)
+ node.setMeshData(mesh.build())
+ # Place the cube in the platform. Do it manually so it works if the "automatic drop models" is OFF
+ move_vector = Vector(0, 5, 0)
+ node.setPosition(move_vector)
+
+ active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+
+ node.addDecorator(SettingOverrideDecorator())
+ node.addDecorator(BuildPlateDecorator(active_build_plate))
+ node.addDecorator(SliceableObjectDecorator())
+
+ stack = node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
+ if not stack:
+ node.addDecorator(SettingOverrideDecorator())
+ stack = node.callDecoration("getStack")
+
+ settings = stack.getTop()
+
+ if not (settings.getInstance("anti_overhang_mesh") and settings.getProperty("anti_overhang_mesh", "value")):
+ definition = stack.getSettingDefinition("anti_overhang_mesh")
+ new_instance = SettingInstance(definition, settings)
+ new_instance.setProperty("value", True)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ settings.addInstance(new_instance)
+
+ scene = self._controller.getScene()
+ op = AddSceneNodeOperation(node, scene.getRoot())
+ op.push()
+ Application.getInstance().getController().getScene().sceneChanged.emit(node)
diff --git a/plugins/SupportEraser/__init__.py b/plugins/SupportEraser/__init__.py
new file mode 100644
index 0000000000..72700571fe
--- /dev/null
+++ b/plugins/SupportEraser/__init__.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from . import SupportEraser
+
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("uranium")
+
+def getMetaData():
+ return {
+ "tool": {
+ "name": i18n_catalog.i18nc("@label", "Support Blocker"),
+ "description": i18n_catalog.i18nc("@info:tooltip", "Create a volume in which supports are not printed."),
+ "icon": "tool_icon.svg",
+ "weight": 4
+ }
+ }
+
+def register(app):
+ return { "tool": SupportEraser.SupportEraser() }
diff --git a/plugins/SupportEraser/plugin.json b/plugins/SupportEraser/plugin.json
new file mode 100644
index 0000000000..5ccb639913
--- /dev/null
+++ b/plugins/SupportEraser/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Support Eraser",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.0",
+ "description": "Creates an eraser mesh to block the printing of support in certain places",
+ "api": 4,
+ "i18n-catalog": "cura"
+}
diff --git a/plugins/SupportEraser/tool_icon.svg b/plugins/SupportEraser/tool_icon.svg
new file mode 100644
index 0000000000..a0f8a3e3c3
--- /dev/null
+++ b/plugins/SupportEraser/tool_icon.svg
@@ -0,0 +1,11 @@
+
diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py
new file mode 100644
index 0000000000..aca293e25a
--- /dev/null
+++ b/plugins/UFPWriter/UFPWriter.py
@@ -0,0 +1,56 @@
+#Copyright (c) 2018 Ultimaker B.V.
+#Cura is released under the terms of the LGPLv3 or higher.
+
+from Charon.VirtualFile import VirtualFile #To open UFP files.
+from Charon.OpenMode import OpenMode #To indicate that we want to write to UFP files.
+from io import StringIO #For converting g-code to bytes.
+
+from UM.Application import Application
+from UM.Logger import Logger
+from UM.Mesh.MeshWriter import MeshWriter #The writer we need to implement.
+from UM.PluginRegistry import PluginRegistry #To get the g-code writer.
+from PyQt5.QtCore import QBuffer
+
+from cura.Snapshot import Snapshot
+
+
+class UFPWriter(MeshWriter):
+ def __init__(self):
+ super().__init__()
+ self._snapshot = None
+ Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._createSnapshot)
+
+ def _createSnapshot(self, *args):
+ # must be called from the main thread because of OpenGL
+ Logger.log("d", "Creating thumbnail image...")
+ self._snapshot = Snapshot.snapshot(width = 300, height = 300)
+
+ def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
+ archive = VirtualFile()
+ archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
+
+ #Store the g-code from the scene.
+ archive.addContentType(extension = "gcode", mime_type = "text/x-gcode")
+ gcode_textio = StringIO() #We have to convert the g-code into bytes.
+ PluginRegistry.getInstance().getPluginObject("GCodeWriter").write(gcode_textio, None)
+ gcode = archive.getStream("/3D/model.gcode")
+ gcode.write(gcode_textio.getvalue().encode("UTF-8"))
+ archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
+
+ #Store the thumbnail.
+ if self._snapshot:
+ archive.addContentType(extension = "png", mime_type = "image/png")
+ thumbnail = archive.getStream("/Metadata/thumbnail.png")
+
+ thumbnail_buffer = QBuffer()
+ thumbnail_buffer.open(QBuffer.ReadWrite)
+ thumbnail_image = self._snapshot
+ thumbnail_image.save(thumbnail_buffer, "PNG")
+
+ thumbnail.write(thumbnail_buffer.data())
+ archive.addRelation(virtual_path = "/Metadata/thumbnail.png", relation_type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail", origin = "/3D/model.gcode")
+ else:
+ Logger.log("d", "Thumbnail not created, cannot save it")
+
+ archive.close()
+ return True
diff --git a/plugins/UFPWriter/__init__.py b/plugins/UFPWriter/__init__.py
new file mode 100644
index 0000000000..9db6b042f8
--- /dev/null
+++ b/plugins/UFPWriter/__init__.py
@@ -0,0 +1,38 @@
+#Copyright (c) 2018 Ultimaker B.V.
+#Cura is released under the terms of the LGPLv3 or higher.
+
+import sys
+
+from UM.Logger import Logger
+try:
+ from . import UFPWriter
+except ImportError:
+ Logger.log("w", "Could not import UFPWriter; libCharon may be missing")
+
+from UM.i18n import i18nCatalog #To translate the file format description.
+from UM.Mesh.MeshWriter import MeshWriter #For the binary mode flag.
+
+i18n_catalog = i18nCatalog("cura")
+
+def getMetaData():
+ if "UFPWriter.UFPWriter" not in sys.modules:
+ return {}
+
+ return {
+ "mesh_writer": {
+ "output": [
+ {
+ "mime_type": "application/x-ufp",
+ "mode": MeshWriter.OutputMode.BinaryMode,
+ "extension": "ufp",
+ "description": i18n_catalog.i18nc("@item:inlistbox", "Ultimaker Format Package")
+ }
+ ]
+ }
+ }
+
+def register(app):
+ if "UFPWriter.UFPWriter" not in sys.modules:
+ return {}
+
+ return { "mesh_writer": UFPWriter.UFPWriter() }
diff --git a/plugins/UFPWriter/kitten.png b/plugins/UFPWriter/kitten.png
new file mode 100644
index 0000000000..44738f94f0
Binary files /dev/null and b/plugins/UFPWriter/kitten.png differ
diff --git a/plugins/UFPWriter/plugin.json b/plugins/UFPWriter/plugin.json
new file mode 100644
index 0000000000..7d10b89ad4
--- /dev/null
+++ b/plugins/UFPWriter/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "UFP Writer",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.0",
+ "description": "Provides support for writing Ultimaker Format Packages.",
+ "api": 4,
+ "i18n-catalog": "cura"
+}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/ClusterControlItem.qml b/plugins/UM3NetworkPrinting/ClusterControlItem.qml
index 8ba7156da8..b42515de51 100644
--- a/plugins/UM3NetworkPrinting/ClusterControlItem.qml
+++ b/plugins/UM3NetworkPrinting/ClusterControlItem.qml
@@ -10,13 +10,12 @@ Component
{
id: base
property var manager: Cura.MachineManager.printerOutputDevices[0]
- anchors.fill: parent
- color: UM.Theme.getColor("viewport_background")
-
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
visible: manager != null
+ anchors.fill: parent
+ color: UM.Theme.getColor("viewport_background")
UM.I18nCatalog
{
@@ -97,7 +96,7 @@ Component
}
Label
{
- text: manager.numJobsPrinting
+ text: manager.activePrintJobs.length
font: UM.Theme.getFont("small")
anchors.right: parent.right
}
@@ -114,7 +113,7 @@ Component
}
Label
{
- text: manager.numJobsQueued
+ text: manager.queuedPrintJobs.length
font: UM.Theme.getFont("small")
anchors.right: parent.right
}
diff --git a/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml
index e78c7d1cc9..86bdaae0a5 100644
--- a/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml
+++ b/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml
@@ -12,10 +12,10 @@ Component
width: maximumWidth
height: maximumHeight
color: UM.Theme.getColor("viewport_background")
-
property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight")
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
+
UM.I18nCatalog
{
id: catalog
@@ -33,9 +33,9 @@ Component
horizontalCenter: parent.horizontalCenter
}
- text: OutputDevice.connectedPrinters.length == 0 ? catalog.i18nc("@label: arg 1 is group name", "%1 is not set up to host a group of connected Ultimaker 3 printers").arg(Cura.MachineManager.printerOutputDevices[0].name) : ""
+ text: OutputDevice.printers.length == 0 ? catalog.i18nc("@label: arg 1 is group name", "%1 is not set up to host a group of connected Ultimaker 3 printers").arg(Cura.MachineManager.printerOutputDevices[0].name) : ""
- visible: OutputDevice.connectedPrinters.length == 0
+ visible: OutputDevice.printers.length == 0
}
Item
@@ -46,23 +46,28 @@ Component
width: Math.min(800 * screenScaleFactor, maximumWidth)
height: children.height
- visible: OutputDevice.connectedPrinters.length != 0
+ visible: OutputDevice.printers.length != 0
Label
{
id: addRemovePrintersLabel
anchors.right: parent.right
- text: "Add / remove printers"
+ text: catalog.i18nc("@label link to connect manager", "Add/Remove printers")
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ linkColor: UM.Theme.getColor("text_link")
}
MouseArea
{
anchors.fill: addRemovePrintersLabel
+ hoverEnabled: true
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
+ onEntered: addRemovePrintersLabel.font.underline = true
+ onExited: addRemovePrintersLabel.font.underline = false
}
}
-
ScrollView
{
id: printerScrollView
@@ -79,7 +84,7 @@ Component
anchors.fill: parent
spacing: -UM.Theme.getSize("default_lining").height
- model: OutputDevice.connectedPrinters
+ model: OutputDevice.printers
delegate: PrinterInfoBlock
{
@@ -95,7 +100,7 @@ Component
PrinterVideoStream
{
- visible: OutputDevice.selectedPrinterName != ""
+ visible: OutputDevice.activePrinter != null
anchors.fill:parent
}
}
diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
new file mode 100644
index 0000000000..c0d538bb78
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py
@@ -0,0 +1,453 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Logger import Logger
+from UM.Application import Application
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.i18n import i18nCatalog
+from UM.Message import Message
+from UM.Qt.Duration import Duration, DurationFormat
+
+from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
+from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
+from cura.PrinterOutput.NetworkCamera import NetworkCamera
+
+from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
+
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
+
+from time import time
+from datetime import datetime
+from typing import Optional
+
+import json
+import os
+
+i18n_catalog = i18nCatalog("cura")
+
+
+class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
+ printJobsChanged = pyqtSignal()
+ activePrinterChanged = pyqtSignal()
+
+ # This is a bit of a hack, as the notify can only use signals that are defined by the class that they are in.
+ # Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
+ clusterPrintersChanged = pyqtSignal()
+
+ def __init__(self, device_id, address, properties, parent = None):
+ super().__init__(device_id = device_id, address = address, properties=properties, parent = parent)
+ self._api_prefix = "/cluster-api/v1/"
+
+ self._number_of_extruders = 2
+
+ self._print_jobs = []
+
+ self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
+ self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
+
+ # See comments about this hack with the clusterPrintersChanged signal
+ self.printersChanged.connect(self.clusterPrintersChanged)
+
+ self._accepts_commands = True
+
+ # Cluster does not have authentication, so default to authenticated
+ self._authentication_state = AuthState.Authenticated
+
+ self._error_message = None
+ self._progress_message = None
+
+ self._active_printer = None # type: Optional[PrinterOutputModel]
+
+ self._printer_selection_dialog = None
+
+ self.setPriority(3) # Make sure the output device gets selected above local file output
+ self.setName(self._id)
+ self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network"))
+ self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network"))
+
+ self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network"))
+
+ self._printer_uuid_to_unique_name_mapping = {}
+
+ self._finished_jobs = []
+
+ self._cluster_size = int(properties.get(b"cluster_size", 0))
+
+ self._latest_reply_handler = None
+
+
+ def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
+ self.writeStarted.emit(self)
+
+ gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
+ active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ gcode_list = gcode_dict[active_build_plate_id]
+
+ if not gcode_list:
+ # Unable to find g-code. Nothing to send
+ return
+
+ self._gcode = gcode_list
+
+ if len(self._printers) > 1:
+ self._spawnPrinterSelectionDialog()
+ else:
+ self.sendPrintJob()
+
+ # Notify the UI that a switch to the print monitor should happen
+ Application.getInstance().getController().setActiveStage("MonitorStage")
+
+ def _spawnPrinterSelectionDialog(self):
+ if self._printer_selection_dialog is None:
+ path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml")
+ self._printer_selection_dialog = Application.getInstance().createQmlComponent(path, {"OutputDevice": self})
+ if self._printer_selection_dialog is not None:
+ self._printer_selection_dialog.show()
+
+ @pyqtProperty(int, constant=True)
+ def clusterSize(self):
+ return self._cluster_size
+
+ @pyqtSlot()
+ @pyqtSlot(str)
+ def sendPrintJob(self, target_printer = ""):
+ Logger.log("i", "Sending print job to printer.")
+ if self._sending_gcode:
+ self._error_message = Message(
+ i18n_catalog.i18nc("@info:status",
+ "Sending new jobs (temporarily) blocked, still sending the previous print job."))
+ self._error_message.show()
+ return
+
+ self._sending_gcode = True
+
+ self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1,
+ i18n_catalog.i18nc("@info:title", "Sending Data"))
+ self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
+ self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered)
+ self._progress_message.show()
+
+ compressed_gcode = self._compressGCode()
+ if compressed_gcode is None:
+ # Abort was called.
+ return
+
+ parts = []
+
+ # If a specific printer was selected, it should be printed with that machine.
+ if target_printer:
+ target_printer = self._printer_uuid_to_unique_name_mapping[target_printer]
+ parts.append(self._createFormPart("name=require_printer_name", bytes(target_printer, "utf-8"), "text/plain"))
+
+ # Add user name to the print_job
+ parts.append(self._createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain"))
+
+ file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
+
+ parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, compressed_gcode))
+
+ self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress)
+
+ @pyqtProperty(QObject, notify=activePrinterChanged)
+ def activePrinter(self) -> Optional["PrinterOutputModel"]:
+ return self._active_printer
+
+ @pyqtSlot(QObject)
+ def setActivePrinter(self, printer):
+ if self._active_printer != printer:
+ if self._active_printer and self._active_printer.camera:
+ self._active_printer.camera.stop()
+ self._active_printer = printer
+ self.activePrinterChanged.emit()
+
+ def _onPostPrintJobFinished(self, reply):
+ self._progress_message.hide()
+ self._compressing_gcode = False
+ self._sending_gcode = False
+
+ def _onUploadPrintJobProgress(self, bytes_sent, bytes_total):
+ if bytes_total > 0:
+ new_progress = bytes_sent / bytes_total * 100
+ # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
+ # timeout responses if this happens.
+ self._last_response_time = time()
+ if new_progress > self._progress_message.getProgress():
+ self._progress_message.show() # Ensure that the message is visible.
+ self._progress_message.setProgress(bytes_sent / bytes_total * 100)
+ else:
+ self._progress_message.setProgress(0)
+ self._progress_message.hide()
+
+ def _progressMessageActionTriggered(self, message_id=None, action_id=None):
+ if action_id == "Abort":
+ Logger.log("d", "User aborted sending print to remote.")
+ self._progress_message.hide()
+ self._compressing_gcode = False
+ self._sending_gcode = False
+ Application.getInstance().getController().setActiveStage("PrepareStage")
+
+ # After compressing the sliced model Cura sends data to printer, to stop receiving updates from the request
+ # the "reply" should be disconnected
+ if self._latest_reply_handler:
+ self._latest_reply_handler.disconnect()
+
+
+ @pyqtSlot()
+ def openPrintJobControlPanel(self):
+ Logger.log("d", "Opening print job control panel...")
+ QDesktopServices.openUrl(QUrl("http://" + self._address + "/print_jobs"))
+
+ @pyqtSlot()
+ def openPrinterControlPanel(self):
+ Logger.log("d", "Opening printer control panel...")
+ QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
+
+ @pyqtProperty("QVariantList", notify=printJobsChanged)
+ def printJobs(self):
+ return self._print_jobs
+
+ @pyqtProperty("QVariantList", notify=printJobsChanged)
+ def queuedPrintJobs(self):
+ return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is None or print_job.state == "queued"]
+
+ @pyqtProperty("QVariantList", notify=printJobsChanged)
+ def activePrintJobs(self):
+ return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
+
+ @pyqtProperty("QVariantList", notify=clusterPrintersChanged)
+ def connectedPrintersTypeCount(self):
+ printer_count = {}
+ for printer in self._printers:
+ if printer.type in printer_count:
+ printer_count[printer.type] += 1
+ else:
+ printer_count[printer.type] = 1
+ result = []
+ for machine_type in printer_count:
+ result.append({"machine_type": machine_type, "count": printer_count[machine_type]})
+ return result
+
+ @pyqtSlot(int, result=str)
+ def formatDuration(self, seconds):
+ return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
+
+ @pyqtSlot(int, result=str)
+ def getTimeCompleted(self, time_remaining):
+ current_time = time()
+ datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
+ return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
+
+ @pyqtSlot(int, result=str)
+ def getDateCompleted(self, time_remaining):
+ current_time = time()
+ datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
+ return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
+
+ def _printJobStateChanged(self):
+ username = self._getUserName()
+
+ if username is None:
+ return # We only want to show notifications if username is set.
+
+ finished_jobs = [job for job in self._print_jobs if job.state == "wait_cleanup"]
+
+ newly_finished_jobs = [job for job in finished_jobs if job not in self._finished_jobs and job.owner == username]
+ for job in newly_finished_jobs:
+ if job.assignedPrinter:
+ job_completed_text = i18n_catalog.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.".format(printer_name=job.assignedPrinter.name, job_name = job.name))
+ else:
+ job_completed_text = i18n_catalog.i18nc("@info:status", "The print job '{job_name}' was finished.".format(job_name = job.name))
+ job_completed_message = Message(text=job_completed_text, title = i18n_catalog.i18nc("@info:status", "Print finished"))
+ job_completed_message.show()
+
+ # Ensure UI gets updated
+ self.printJobsChanged.emit()
+
+ # Keep a list of all completed jobs so we know if something changed next time.
+ self._finished_jobs = finished_jobs
+
+ def _update(self):
+ if not super()._update():
+ return
+ self.get("printers/", onFinished=self._onGetPrintersDataFinished)
+ self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished)
+
+ def _onGetPrintJobsFinished(self, reply: QNetworkReply):
+ if not checkValidGetReply(reply):
+ return
+
+ result = loadJsonFromReply(reply)
+ if result is None:
+ return
+
+ print_jobs_seen = []
+ job_list_changed = False
+ for print_job_data in result:
+ print_job = findByKey(self._print_jobs, print_job_data["uuid"])
+
+ if print_job is None:
+ print_job = self._createPrintJobModel(print_job_data)
+ job_list_changed = True
+
+ self._updatePrintJob(print_job, print_job_data)
+
+ if print_job.state != "queued": # Print job should be assigned to a printer.
+ if print_job.state == "failed":
+ # Print job was failed, so don't attach it to a printer.
+ printer = None
+ else:
+ printer = self._getPrinterByKey(print_job_data["printer_uuid"])
+ else: # The job can "reserve" a printer if some changes are required.
+ printer = self._getPrinterByKey(print_job_data["assigned_to"])
+
+ if printer:
+ printer.updateActivePrintJob(print_job)
+
+ print_jobs_seen.append(print_job)
+
+ # Check what jobs need to be removed.
+ removed_jobs = [print_job for print_job in self._print_jobs if print_job not in print_jobs_seen]
+
+ for removed_job in removed_jobs:
+ job_list_changed |= self._removeJob(removed_job)
+
+ if job_list_changed:
+ self.printJobsChanged.emit() # Do a single emit for all print job changes.
+
+ def _onGetPrintersDataFinished(self, reply: QNetworkReply):
+ if not checkValidGetReply(reply):
+ return
+
+ result = loadJsonFromReply(reply)
+ if result is None:
+ return
+
+ printer_list_changed = False
+ printers_seen = []
+
+ for printer_data in result:
+ printer = findByKey(self._printers, printer_data["uuid"])
+
+ if printer is None:
+ printer = self._createPrinterModel(printer_data)
+ printer_list_changed = True
+
+ printers_seen.append(printer)
+
+ self._updatePrinter(printer, printer_data)
+
+ removed_printers = [printer for printer in self._printers if printer not in printers_seen]
+ for printer in removed_printers:
+ self._removePrinter(printer)
+
+ if removed_printers or printer_list_changed:
+ self.printersChanged.emit()
+
+ def _createPrinterModel(self, data):
+ printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
+ number_of_extruders=self._number_of_extruders)
+ printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
+ self._printers.append(printer)
+ return printer
+
+ def _createPrintJobModel(self, data):
+ print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
+ key=data["uuid"], name= data["name"])
+ print_job.stateChanged.connect(self._printJobStateChanged)
+ self._print_jobs.append(print_job)
+ return print_job
+
+ def _updatePrintJob(self, print_job, data):
+ print_job.updateTimeTotal(data["time_total"])
+ print_job.updateTimeElapsed(data["time_elapsed"])
+ print_job.updateState(data["status"])
+ print_job.updateOwner(data["owner"])
+
+ def _updatePrinter(self, printer, data):
+ # For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
+ # Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
+ self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"]
+
+ printer.updateName(data["friendly_name"])
+ printer.updateKey(data["uuid"])
+ printer.updateType(data["machine_variant"])
+ if not data["enabled"]:
+ printer.updateState("disabled")
+ else:
+ printer.updateState(data["status"])
+
+ for index in range(0, self._number_of_extruders):
+ extruder = printer.extruders[index]
+ try:
+ extruder_data = data["configuration"][index]
+ except IndexError:
+ break
+
+ extruder.updateHotendID(extruder_data.get("print_core_id", ""))
+
+ material_data = extruder_data["material"]
+ if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_data["guid"]:
+ containers = ContainerRegistry.getInstance().findInstanceContainers(type="material",
+ GUID=material_data["guid"])
+ if containers:
+ color = containers[0].getMetaDataEntry("color_code")
+ brand = containers[0].getMetaDataEntry("brand")
+ material_type = containers[0].getMetaDataEntry("material")
+ name = containers[0].getName()
+ else:
+ Logger.log("w",
+ "Unable to find material with guid {guid}. Using data as provided by cluster".format(
+ guid=material_data["guid"]))
+ color = material_data["color"]
+ brand = material_data["brand"]
+ material_type = material_data["material"]
+ name = "Empty" if material_data["material"] == "empty" else "Unknown"
+
+ material = MaterialOutputModel(guid=material_data["guid"], type=material_type,
+ brand=brand, color=color, name=name)
+ extruder.updateActiveMaterial(material)
+
+ def _removeJob(self, job):
+ if job not in self._print_jobs:
+ return False
+
+ if job.assignedPrinter:
+ job.assignedPrinter.updateActivePrintJob(None)
+ job.stateChanged.disconnect(self._printJobStateChanged)
+ self._print_jobs.remove(job)
+
+ return True
+
+ def _removePrinter(self, printer):
+ self._printers.remove(printer)
+ if self._active_printer == printer:
+ self._active_printer = None
+ self.activePrinterChanged.emit()
+
+
+def loadJsonFromReply(reply):
+ try:
+ result = json.loads(bytes(reply.readAll()).decode("utf-8"))
+ except json.decoder.JSONDecodeError:
+ Logger.logException("w", "Unable to decode JSON from reply.")
+ return
+ return result
+
+
+def checkValidGetReply(reply):
+ status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
+
+ if status_code != 200:
+ Logger.log("w", "Got status code {status_code} while trying to get data".format(status_code=status_code))
+ return False
+ return True
+
+
+def findByKey(list, key):
+ for item in list:
+ if item.key == key:
+ return item
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
new file mode 100644
index 0000000000..4615cd62dc
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+
+
+class ClusterUM3PrinterOutputController(PrinterOutputController):
+ def __init__(self, output_device):
+ super().__init__(output_device)
+ self.can_pre_heat_bed = False
+ self.can_control_manually = False
+
+ def setJobState(self, job: "PrintJobOutputModel", state: str):
+ data = "{\"action\": \"%s\"}" % state
+ self._output_device.put("print_jobs/%s/action" % job.key, data, onFinished=None)
+
diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py
index 0d5d259ce4..0e872fed43 100644
--- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py
+++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py
@@ -12,7 +12,10 @@ from cura.MachineAction import MachineAction
catalog = i18nCatalog("cura")
+
class DiscoverUM3Action(MachineAction):
+ discoveredDevicesChanged = pyqtSignal()
+
def __init__(self):
super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network"))
self._qml_url = "DiscoverUM3Action.qml"
@@ -25,24 +28,24 @@ class DiscoverUM3Action(MachineAction):
Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)
- self._last_zeroconf_event_time = time.time()
- self._zeroconf_change_grace_period = 0.25 # Time to wait after a zeroconf service change before allowing a zeroconf reset
+ self._last_zero_conf_event_time = time.time()
- printersChanged = pyqtSignal()
+ # Time to wait after a zero-conf service change before allowing a zeroconf reset
+ self._zero_conf_change_grace_period = 0.25
@pyqtSlot()
def startDiscovery(self):
if not self._network_plugin:
- Logger.log("d", "Starting printer discovery.")
+ Logger.log("d", "Starting device discovery.")
self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
- self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged)
- self.printersChanged.emit()
+ self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged)
+ self.discoveredDevicesChanged.emit()
- ## Re-filters the list of printers.
+ ## Re-filters the list of devices.
@pyqtSlot()
def reset(self):
- Logger.log("d", "Reset the list of found printers.")
- self.printersChanged.emit()
+ Logger.log("d", "Reset the list of found devices.")
+ self.discoveredDevicesChanged.emit()
@pyqtSlot()
def restartDiscovery(self):
@@ -51,43 +54,44 @@ class DiscoverUM3Action(MachineAction):
# It's most likely that the QML engine is still creating delegates, where the python side already deleted or
# garbage collected the data.
# Whatever the case, waiting a bit ensures that it doesn't crash.
- if time.time() - self._last_zeroconf_event_time > self._zeroconf_change_grace_period:
+ if time.time() - self._last_zero_conf_event_time > self._zero_conf_change_grace_period:
if not self._network_plugin:
self.startDiscovery()
else:
self._network_plugin.startDiscovery()
@pyqtSlot(str, str)
- def removeManualPrinter(self, key, address):
+ def removeManualDevice(self, key, address):
if not self._network_plugin:
return
- self._network_plugin.removeManualPrinter(key, address)
+ self._network_plugin.removeManualDevice(key, address)
@pyqtSlot(str, str)
- def setManualPrinter(self, key, address):
+ def setManualDevice(self, key, address):
if key != "":
# This manual printer replaces a current manual printer
- self._network_plugin.removeManualPrinter(key)
+ self._network_plugin.removeManualDevice(key)
if address != "":
- self._network_plugin.addManualPrinter(address)
+ self._network_plugin.addManualDevice(address)
- def _onPrinterDiscoveryChanged(self, *args):
- self._last_zeroconf_event_time = time.time()
- self.printersChanged.emit()
+ def _onDeviceDiscoveryChanged(self, *args):
+ self._last_zero_conf_event_time = time.time()
+ self.discoveredDevicesChanged.emit()
- @pyqtProperty("QVariantList", notify = printersChanged)
+ @pyqtProperty("QVariantList", notify = discoveredDevicesChanged)
def foundDevices(self):
if self._network_plugin:
+ # TODO: Check if this needs to stay.
if Application.getInstance().getGlobalContainerStack():
global_printer_type = Application.getInstance().getGlobalContainerStack().getBottom().getId()
else:
global_printer_type = "unknown"
- printers = list(self._network_plugin.getPrinters().values())
+ printers = list(self._network_plugin.getDiscoveredDevices().values())
# TODO; There are still some testing printers that don't have a correct printer type, so don't filter out unkown ones just yet.
- printers = [printer for printer in printers if printer.printerType == global_printer_type or printer.printerType == "unknown"]
+ #printers = [printer for printer in printers if printer.printerType == global_printer_type or printer.printerType == "unknown"]
printers.sort(key = lambda k: k.name)
return printers
else:
diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
index cb574384f9..c0cb5a78b7 100644
--- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
+++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
@@ -10,7 +10,7 @@ Cura.MachineAction
{
id: base
anchors.fill: parent;
- property var selectedPrinter: null
+ property var selectedDevice: null
property bool completeProperties: true
Connections
@@ -29,9 +29,9 @@ Cura.MachineAction
function connectToPrinter()
{
- if(base.selectedPrinter && base.completeProperties)
+ if(base.selectedDevice && base.completeProperties)
{
- var printerKey = base.selectedPrinter.getKey()
+ var printerKey = base.selectedDevice.key
if(manager.getStoredKey() != printerKey)
{
manager.setKey(printerKey);
@@ -83,10 +83,10 @@ Cura.MachineAction
{
id: editButton
text: catalog.i18nc("@action:button", "Edit")
- enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true"
+ enabled: base.selectedDevice != null && base.selectedDevice.getProperty("manual") == "true"
onClicked:
{
- manualPrinterDialog.showDialog(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress);
+ manualPrinterDialog.showDialog(base.selectedDevice.key, base.selectedDevice.ipAddress);
}
}
@@ -94,8 +94,8 @@ Cura.MachineAction
{
id: removeButton
text: catalog.i18nc("@action:button", "Remove")
- enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true"
- onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress)
+ enabled: base.selectedDevice != null && base.selectedDevice.getProperty("manual") == "true"
+ onClicked: manager.removeManualDevice(base.selectedDevice.key, base.selectedDevice.ipAddress)
}
Button
@@ -114,7 +114,7 @@ Cura.MachineAction
Column
{
- width: Math.floor(parent.width * 0.5)
+ width: Math.round(parent.width * 0.5)
spacing: UM.Theme.getSize("default_margin").height
ScrollView
@@ -139,7 +139,7 @@ Cura.MachineAction
{
var selectedKey = manager.getStoredKey();
for(var i = 0; i < model.length; i++) {
- if(model[i].getKey() == selectedKey)
+ if(model[i].key == selectedKey)
{
currentIndex = i;
return
@@ -151,9 +151,9 @@ Cura.MachineAction
currentIndex: -1
onCurrentIndexChanged:
{
- base.selectedPrinter = listview.model[currentIndex];
+ base.selectedDevice = listview.model[currentIndex];
// Only allow connecting if the printer has responded to API query since the last refresh
- base.completeProperties = base.selectedPrinter != null && base.selectedPrinter.getProperty("incomplete") != "true";
+ base.completeProperties = base.selectedDevice != null && base.selectedDevice.getProperty("incomplete") != "true";
}
Component.onCompleted: manager.startDiscovery()
delegate: Rectangle
@@ -198,14 +198,14 @@ Cura.MachineAction
}
Column
{
- width: Math.floor(parent.width * 0.5)
- visible: base.selectedPrinter ? true : false
+ width: Math.round(parent.width * 0.5)
+ visible: base.selectedDevice ? true : false
spacing: UM.Theme.getSize("default_margin").height
Label
{
width: parent.width
wrapMode: Text.WordWrap
- text: base.selectedPrinter ? base.selectedPrinter.name : ""
+ text: base.selectedDevice ? base.selectedDevice.name : ""
font: UM.Theme.getFont("large")
elide: Text.ElideRight
}
@@ -216,27 +216,27 @@ Cura.MachineAction
columns: 2
Label
{
- width: Math.floor(parent.width * 0.5)
+ width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Type")
}
Label
{
- width: Math.floor(parent.width * 0.5)
+ width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
text:
{
- if(base.selectedPrinter)
+ if(base.selectedDevice)
{
- if(base.selectedPrinter.printerType == "ultimaker3")
+ if(base.selectedDevice.printerType == "ultimaker3")
{
- return catalog.i18nc("@label Printer name", "Ultimaker 3")
- } else if(base.selectedPrinter.printerType == "ultimaker3_extended")
+ return catalog.i18nc("@label", "Ultimaker 3")
+ } else if(base.selectedDevice.printerType == "ultimaker3_extended")
{
- return catalog.i18nc("@label Printer name", "Ultimaker 3 Extended")
+ return catalog.i18nc("@label", "Ultimaker 3 Extended")
} else
{
- return catalog.i18nc("@label Printer name", "Unknown") // We have no idea what type it is. Should not happen 'in the field'
+ return catalog.i18nc("@label", "Unknown") // We have no idea what type it is. Should not happen 'in the field'
}
}
else
@@ -247,27 +247,27 @@ Cura.MachineAction
}
Label
{
- width: Math.floor(parent.width * 0.5)
+ width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Firmware version")
}
Label
{
- width: Math.floor(parent.width * 0.5)
+ width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
- text: base.selectedPrinter ? base.selectedPrinter.firmwareVersion : ""
+ text: base.selectedDevice ? base.selectedDevice.firmwareVersion : ""
}
Label
{
- width: Math.floor(parent.width * 0.5)
+ width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Address")
}
Label
{
- width: Math.floor(parent.width * 0.5)
+ width: Math.round(parent.width * 0.5)
wrapMode: Text.WordWrap
- text: base.selectedPrinter ? base.selectedPrinter.ipAddress : ""
+ text: base.selectedDevice ? base.selectedDevice.ipAddress : ""
}
}
@@ -277,17 +277,17 @@ Cura.MachineAction
wrapMode: Text.WordWrap
text:{
// The property cluster size does not exist for older UM3 devices.
- if(!base.selectedPrinter || base.selectedPrinter.clusterSize == null || base.selectedPrinter.clusterSize == 1)
+ if(!base.selectedDevice || base.selectedDevice.clusterSize == null || base.selectedDevice.clusterSize == 1)
{
return "";
}
- else if (base.selectedPrinter.clusterSize === 0)
+ else if (base.selectedDevice.clusterSize === 0)
{
return catalog.i18nc("@label", "This printer is not set up to host a group of Ultimaker 3 printers.");
}
else
{
- return catalog.i18nc("@label", "This printer is the host for a group of %1 Ultimaker 3 printers.".arg(base.selectedPrinter.clusterSize));
+ return catalog.i18nc("@label", "This printer is the host for a group of %1 Ultimaker 3 printers.".arg(base.selectedDevice.clusterSize));
}
}
@@ -296,14 +296,14 @@ Cura.MachineAction
{
width: parent.width
wrapMode: Text.WordWrap
- visible: base.selectedPrinter != null && !base.completeProperties
+ visible: base.selectedDevice != null && !base.completeProperties
text: catalog.i18nc("@label", "The printer at this address has not yet responded." )
}
Button
{
text: catalog.i18nc("@action:button", "Connect")
- enabled: (base.selectedPrinter && base.completeProperties) ? true : false
+ enabled: (base.selectedDevice && base.completeProperties) ? true : false
onClicked: connectToPrinter()
}
}
@@ -337,7 +337,7 @@ Cura.MachineAction
onAccepted:
{
- manager.setManualPrinter(printerKey, addressText)
+ manager.setManualDevice(printerKey, addressText)
}
Column {
diff --git a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py
new file mode 100644
index 0000000000..42f00beceb
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py
@@ -0,0 +1,636 @@
+from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
+from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
+from cura.PrinterOutput.NetworkCamera import NetworkCamera
+
+from cura.Settings.ContainerManager import ContainerManager
+from cura.Settings.ExtruderManager import ExtruderManager
+
+from UM.Logger import Logger
+from UM.Settings.ContainerRegistry import ContainerRegistry
+from UM.Application import Application
+from UM.i18n import i18nCatalog
+from UM.Message import Message
+
+from PyQt5.QtNetwork import QNetworkRequest
+from PyQt5.QtCore import QTimer, QCoreApplication
+from PyQt5.QtWidgets import QMessageBox
+
+from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController
+
+from time import time
+
+import json
+import os
+
+
+i18n_catalog = i18nCatalog("cura")
+
+
+## This is the output device for the "Legacy" API of the UM3. All firmware before 4.0.1 uses this API.
+# Everything after that firmware uses the ClusterUM3Output.
+# The Legacy output device can only have one printer (whereas the cluster can have 0 to n).
+#
+# Authentication is done in a number of steps;
+# 1. Request an id / key pair by sending the application & user name. (state = authRequested)
+# 2. Machine sends this back and will display an approve / deny message on screen. (state = AuthReceived)
+# 3. OutputDevice will poll if the button was pressed.
+# 4. At this point the machine either has the state Authenticated or AuthenticationDenied.
+# 5. As a final step, we verify the authentication, as this forces the QT manager to setup the authenticator.
+class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
+ def __init__(self, device_id, address: str, properties, parent = None):
+ super().__init__(device_id = device_id, address = address, properties = properties, parent = parent)
+ self._api_prefix = "/api/v1/"
+ self._number_of_extruders = 2
+
+ self._authentication_id = None
+ self._authentication_key = None
+
+ self._authentication_counter = 0
+ self._max_authentication_counter = 5 * 60 # Number of attempts before authentication timed out (5 min)
+
+ self._authentication_timer = QTimer()
+ self._authentication_timer.setInterval(1000) # TODO; Add preference for update interval
+ self._authentication_timer.setSingleShot(False)
+
+ self._authentication_timer.timeout.connect(self._onAuthenticationTimer)
+
+ # The messages are created when connect is called the first time.
+ # This ensures that the messages are only created for devices that actually want to connect.
+ self._authentication_requested_message = None
+ self._authentication_failed_message = None
+ self._authentication_succeeded_message = None
+ self._not_authenticated_message = None
+
+ self.authenticationStateChanged.connect(self._onAuthenticationStateChanged)
+
+ self.setPriority(3) # Make sure the output device gets selected above local file output
+ self.setName(self._id)
+ self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network"))
+ self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network"))
+
+ self.setIconName("print")
+
+ self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
+
+ self._output_controller = LegacyUM3PrinterOutputController(self)
+
+ def _onAuthenticationStateChanged(self):
+ # We only accept commands if we are authenticated.
+ self._setAcceptsCommands(self._authentication_state == AuthState.Authenticated)
+
+ if self._authentication_state == AuthState.Authenticated:
+ self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network."))
+ elif self._authentication_state == AuthState.AuthenticationRequested:
+ self.setConnectionText(i18n_catalog.i18nc("@info:status",
+ "Connected over the network. Please approve the access request on the printer."))
+ elif self._authentication_state == AuthState.AuthenticationDenied:
+ self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network. No access to control the printer."))
+
+
+ def _setupMessages(self):
+ self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status",
+ "Access to the printer requested. Please approve the request on the printer"),
+ lifetime=0, dismissable=False, progress=0,
+ title=i18n_catalog.i18nc("@info:title",
+ "Authentication status"))
+
+ self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", ""),
+ title=i18n_catalog.i18nc("@info:title", "Authentication Status"))
+ self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry"), None,
+ i18n_catalog.i18nc("@info:tooltip", "Re-send the access request"))
+ self._authentication_failed_message.actionTriggered.connect(self._messageCallback)
+ self._authentication_succeeded_message = Message(
+ i18n_catalog.i18nc("@info:status", "Access to the printer accepted"),
+ title=i18n_catalog.i18nc("@info:title", "Authentication Status"))
+
+ self._not_authenticated_message = Message(
+ i18n_catalog.i18nc("@info:status", "No access to print with this printer. Unable to send print job."),
+ title=i18n_catalog.i18nc("@info:title", "Authentication Status"))
+ self._not_authenticated_message.addAction("Request", i18n_catalog.i18nc("@action:button", "Request Access"),
+ None, i18n_catalog.i18nc("@info:tooltip",
+ "Send access request to the printer"))
+ self._not_authenticated_message.actionTriggered.connect(self._messageCallback)
+
+ def _messageCallback(self, message_id=None, action_id="Retry"):
+ if action_id == "Request" or action_id == "Retry":
+ if self._authentication_failed_message:
+ self._authentication_failed_message.hide()
+ if self._not_authenticated_message:
+ self._not_authenticated_message.hide()
+
+ self._requestAuthentication()
+
+ def connect(self):
+ super().connect()
+ self._setupMessages()
+ global_container = Application.getInstance().getGlobalContainerStack()
+ if global_container:
+ self._authentication_id = global_container.getMetaDataEntry("network_authentication_id", None)
+ self._authentication_key = global_container.getMetaDataEntry("network_authentication_key", None)
+
+ def close(self):
+ super().close()
+ if self._authentication_requested_message:
+ self._authentication_requested_message.hide()
+ if self._authentication_failed_message:
+ self._authentication_failed_message.hide()
+ if self._authentication_succeeded_message:
+ self._authentication_succeeded_message.hide()
+ self._sending_gcode = False
+ self._compressing_gcode = False
+ self._authentication_timer.stop()
+
+ ## Send all material profiles to the printer.
+ def _sendMaterialProfiles(self):
+ Logger.log("i", "Sending material profiles to printer")
+
+ # TODO: Might want to move this to a job...
+ for container in ContainerRegistry.getInstance().findInstanceContainers(type="material"):
+ try:
+ xml_data = container.serialize()
+ if xml_data == "" or xml_data is None:
+ continue
+
+ names = ContainerManager.getInstance().getLinkedMaterials(container.getId())
+ if names:
+ # There are other materials that share this GUID.
+ if not container.isReadOnly():
+ continue # If it's not readonly, it's created by user, so skip it.
+
+ file_name = "none.xml"
+
+ self.postForm("materials", "form-data; name=\"file\";filename=\"%s\"" % file_name, xml_data.encode(), onFinished=None)
+
+ except NotImplementedError:
+ # If the material container is not the most "generic" one it can't be serialized an will raise a
+ # NotImplementedError. We can simply ignore these.
+ pass
+
+ def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
+ if not self.activePrinter:
+ # No active printer. Unable to write
+ return
+
+ if self.activePrinter.state not in ["idle", ""]:
+ # Printer is not able to accept commands.
+ return
+
+ if self._authentication_state != AuthState.Authenticated:
+ # Not authenticated, so unable to send job.
+ return
+
+ self.writeStarted.emit(self)
+
+ gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
+ active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ gcode_list = gcode_dict[active_build_plate_id]
+
+ if not gcode_list:
+ # Unable to find g-code. Nothing to send
+ return
+
+ self._gcode = gcode_list
+
+ errors = self._checkForErrors()
+ if errors:
+ text = i18n_catalog.i18nc("@label", "Unable to start a new print job.")
+ informative_text = i18n_catalog.i18nc("@label",
+ "There is an issue with the configuration of your Ultimaker, which makes it impossible to start the print. "
+ "Please resolve this issues before continuing.")
+ detailed_text = ""
+ for error in errors:
+ detailed_text += error + "\n"
+
+ Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
+ text,
+ informative_text,
+ detailed_text,
+ buttons=QMessageBox.Ok,
+ icon=QMessageBox.Critical,
+ callback = self._messageBoxCallback
+ )
+ return # Don't continue; Errors must block sending the job to the printer.
+
+ # There might be multiple things wrong with the configuration. Check these before starting.
+ warnings = self._checkForWarnings()
+
+ if warnings:
+ text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?")
+ informative_text = i18n_catalog.i18nc("@label",
+ "There is a mismatch between the configuration or calibration of the printer and Cura. "
+ "For the best result, always slice for the PrintCores and materials that are inserted in your printer.")
+ detailed_text = ""
+ for warning in warnings:
+ detailed_text += warning + "\n"
+
+ Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
+ text,
+ informative_text,
+ detailed_text,
+ buttons=QMessageBox.Yes + QMessageBox.No,
+ icon=QMessageBox.Question,
+ callback=self._messageBoxCallback
+ )
+ return
+
+ # No warnings or errors, so we're good to go.
+ self._startPrint()
+
+ # Notify the UI that a switch to the print monitor should happen
+ Application.getInstance().getController().setActiveStage("MonitorStage")
+
+ def _startPrint(self):
+ Logger.log("i", "Sending print job to printer.")
+ if self._sending_gcode:
+ self._error_message = Message(
+ i18n_catalog.i18nc("@info:status",
+ "Sending new jobs (temporarily) blocked, still sending the previous print job."))
+ self._error_message.show()
+ return
+
+ self._sending_gcode = True
+
+ self._send_gcode_start = time()
+ self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1,
+ i18n_catalog.i18nc("@info:title", "Sending Data"))
+ self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
+ self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered)
+ self._progress_message.show()
+
+ compressed_gcode = self._compressGCode()
+ if compressed_gcode is None:
+ # Abort was called.
+ return
+
+ file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
+ self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode,
+ onFinished=self._onPostPrintJobFinished)
+
+ return
+
+ def _progressMessageActionTriggered(self, message_id=None, action_id=None):
+ if action_id == "Abort":
+ Logger.log("d", "User aborted sending print to remote.")
+ self._progress_message.hide()
+ self._compressing_gcode = False
+ self._sending_gcode = False
+ Application.getInstance().getController().setActiveStage("PrepareStage")
+
+ def _onPostPrintJobFinished(self, reply):
+ self._progress_message.hide()
+ self._sending_gcode = False
+
+ def _onUploadPrintJobProgress(self, bytes_sent, bytes_total):
+ if bytes_total > 0:
+ new_progress = bytes_sent / bytes_total * 100
+ # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
+ # timeout responses if this happens.
+ self._last_response_time = time()
+ if new_progress > self._progress_message.getProgress():
+ self._progress_message.show() # Ensure that the message is visible.
+ self._progress_message.setProgress(bytes_sent / bytes_total * 100)
+ else:
+ self._progress_message.setProgress(0)
+
+ self._progress_message.hide()
+
+ def _messageBoxCallback(self, button):
+ def delayedCallback():
+ if button == QMessageBox.Yes:
+ self._startPrint()
+ else:
+ Application.getInstance().getController().setActiveStage("PrepareStage")
+ # For some unknown reason Cura on OSX will hang if we do the call back code
+ # immediately without first returning and leaving QML's event system.
+
+ QTimer.singleShot(100, delayedCallback)
+
+ def _checkForErrors(self):
+ errors = []
+ print_information = Application.getInstance().getPrintInformation()
+ if not print_information.materialLengths:
+ Logger.log("w", "There is no material length information. Unable to check for errors.")
+ return errors
+
+ for index, extruder in enumerate(self.activePrinter.extruders):
+ # Due to airflow issues, both slots must be loaded, regardless if they are actually used or not.
+ if extruder.hotendID == "":
+ # No Printcore loaded.
+ errors.append(i18n_catalog.i18nc("@info:status", "No Printcore loaded in slot {slot_number}".format(slot_number=index + 1)))
+
+ if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
+ # The extruder is by this print.
+ if extruder.activeMaterial is None:
+ # No active material
+ errors.append(i18n_catalog.i18nc("@info:status", "No material loaded in slot {slot_number}".format(slot_number=index + 1)))
+ return errors
+
+ def _checkForWarnings(self):
+ warnings = []
+ print_information = Application.getInstance().getPrintInformation()
+
+ if not print_information.materialLengths:
+ Logger.log("w", "There is no material length information. Unable to check for warnings.")
+ return warnings
+
+ extruder_manager = ExtruderManager.getInstance()
+
+ for index, extruder in enumerate(self.activePrinter.extruders):
+ if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
+ # The extruder is by this print.
+
+ # TODO: material length check
+
+ # Check if the right Printcore is active.
+ variant = extruder_manager.getExtruderStack(index).findContainer({"type": "variant"})
+ if variant:
+ if variant.getName() != extruder.hotendID:
+ warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore (Cura: {cura_printcore_name}, Printer: {remote_printcore_name}) selected for extruder {extruder_id}".format(cura_printcore_name = variant.getName(), remote_printcore_name = extruder.hotendID, extruder_id = index + 1)))
+ else:
+ Logger.log("w", "Unable to find variant.")
+
+ # Check if the right material is loaded.
+ local_material = extruder_manager.getExtruderStack(index).findContainer({"type": "material"})
+ if local_material:
+ if extruder.activeMaterial.guid != local_material.getMetaDataEntry("GUID"):
+ Logger.log("w", "Extruder %s has a different material (%s) as Cura (%s)", index + 1, extruder.activeMaterial.guid, local_material.getMetaDataEntry("GUID"))
+ warnings.append(i18n_catalog.i18nc("@label", "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}").format(local_material.getName(), extruder.activeMaterial.name, index + 1))
+ else:
+ Logger.log("w", "Unable to find material.")
+
+ return warnings
+
+ def _update(self):
+ if not super()._update():
+ return
+ if self._authentication_state == AuthState.NotAuthenticated:
+ if self._authentication_id is None and self._authentication_key is None:
+ # This machine doesn't have any authentication, so request it.
+ self._requestAuthentication()
+ elif self._authentication_id is not None and self._authentication_key is not None:
+ # We have authentication info, but we haven't checked it out yet. Do so now.
+ self._verifyAuthentication()
+ elif self._authentication_state == AuthState.AuthenticationReceived:
+ # We have an authentication, but it's not confirmed yet.
+ self._checkAuthentication()
+
+ # We don't need authentication for requesting info, so we can go right ahead with requesting this.
+ self.get("printer", onFinished=self._onGetPrinterDataFinished)
+ self.get("print_job", onFinished=self._onGetPrintJobFinished)
+
+ def _resetAuthenticationRequestedMessage(self):
+ if self._authentication_requested_message:
+ self._authentication_requested_message.hide()
+ self._authentication_timer.stop()
+ self._authentication_counter = 0
+
+ def _onAuthenticationTimer(self):
+ self._authentication_counter += 1
+ self._authentication_requested_message.setProgress(
+ self._authentication_counter / self._max_authentication_counter * 100)
+ if self._authentication_counter > self._max_authentication_counter:
+ self._authentication_timer.stop()
+ Logger.log("i", "Authentication timer ended. Setting authentication to denied for printer: %s" % self._id)
+ self.setAuthenticationState(AuthState.AuthenticationDenied)
+ self._resetAuthenticationRequestedMessage()
+ self._authentication_failed_message.show()
+
+ def _verifyAuthentication(self):
+ Logger.log("d", "Attempting to verify authentication")
+ # This will ensure that the "_onAuthenticationRequired" is triggered, which will setup the authenticator.
+ self.get("auth/verify", onFinished=self._onVerifyAuthenticationCompleted)
+
+ def _onVerifyAuthenticationCompleted(self, reply):
+ status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
+ if status_code == 401:
+ # Something went wrong; We somehow tried to verify authentication without having one.
+ Logger.log("d", "Attempted to verify auth without having one.")
+ self._authentication_id = None
+ self._authentication_key = None
+ self.setAuthenticationState(AuthState.NotAuthenticated)
+ elif status_code == 403 and self._authentication_state != AuthState.Authenticated:
+ # If we were already authenticated, we probably got an older message back all of the sudden. Drop that.
+ Logger.log("d",
+ "While trying to verify the authentication state, we got a forbidden response. Our own auth state was %s. ",
+ self._authentication_state)
+ self.setAuthenticationState(AuthState.AuthenticationDenied)
+ self._authentication_failed_message.show()
+ elif status_code == 200:
+ self.setAuthenticationState(AuthState.Authenticated)
+
+ def _checkAuthentication(self):
+ Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
+ self.get("auth/check/" + str(self._authentication_id), onFinished=self._onCheckAuthenticationFinished)
+
+ def _onCheckAuthenticationFinished(self, reply):
+ if str(self._authentication_id) not in reply.url().toString():
+ Logger.log("w", "Got an old id response.")
+ # Got response for old authentication ID.
+ return
+ try:
+ data = json.loads(bytes(reply.readAll()).decode("utf-8"))
+ except json.decoder.JSONDecodeError:
+ Logger.log("w", "Received an invalid authentication check from printer: Not valid JSON.")
+ return
+
+ if data.get("message", "") == "authorized":
+ Logger.log("i", "Authentication was approved")
+ self.setAuthenticationState(AuthState.Authenticated)
+ self._saveAuthentication()
+
+ # Double check that everything went well.
+ self._verifyAuthentication()
+
+ # Notify the user.
+ self._resetAuthenticationRequestedMessage()
+ self._authentication_succeeded_message.show()
+ elif data.get("message", "") == "unauthorized":
+ Logger.log("i", "Authentication was denied.")
+ self.setAuthenticationState(AuthState.AuthenticationDenied)
+ self._authentication_failed_message.show()
+
+ def _saveAuthentication(self):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ if "network_authentication_key" in global_container_stack.getMetaData():
+ global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
+ else:
+ global_container_stack.addMetaDataEntry("network_authentication_key", self._authentication_key)
+
+ if "network_authentication_id" in global_container_stack.getMetaData():
+ global_container_stack.setMetaDataEntry("network_authentication_id", self._authentication_id)
+ else:
+ global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
+
+ # Force save so we are sure the data is not lost.
+ Application.getInstance().saveStack(global_container_stack)
+ Logger.log("i", "Authentication succeeded for id %s and key %s", self._authentication_id,
+ self._getSafeAuthKey())
+ else:
+ Logger.log("e", "Unable to save authentication for id %s and key %s", self._authentication_id,
+ self._getSafeAuthKey())
+
+ def _onRequestAuthenticationFinished(self, reply):
+ try:
+ data = json.loads(bytes(reply.readAll()).decode("utf-8"))
+ except json.decoder.JSONDecodeError:
+ Logger.log("w", "Received an invalid authentication request reply from printer: Not valid JSON.")
+ self.setAuthenticationState(AuthState.NotAuthenticated)
+ return
+
+ self.setAuthenticationState(AuthState.AuthenticationReceived)
+ self._authentication_id = data["id"]
+ self._authentication_key = data["key"]
+ Logger.log("i", "Got a new authentication ID (%s) and KEY (%s). Waiting for authorization.",
+ self._authentication_id, self._getSafeAuthKey())
+
+ def _requestAuthentication(self):
+ self._authentication_requested_message.show()
+ self._authentication_timer.start()
+
+ # Reset any previous authentication info. If this isn't done, the "Retry" action on the failed message might
+ # give issues.
+ self._authentication_key = None
+ self._authentication_id = None
+
+ self.post("auth/request",
+ json.dumps({"application": "Cura-" + Application.getInstance().getVersion(),
+ "user": self._getUserName()}).encode(),
+ onFinished=self._onRequestAuthenticationFinished)
+
+ self.setAuthenticationState(AuthState.AuthenticationRequested)
+
+ def _onAuthenticationRequired(self, reply, authenticator):
+ if self._authentication_id is not None and self._authentication_key is not None:
+ Logger.log("d",
+ "Authentication was required for printer: %s. Setting up authenticator with ID %s and key %s",
+ self._id, self._authentication_id, self._getSafeAuthKey())
+ authenticator.setUser(self._authentication_id)
+ authenticator.setPassword(self._authentication_key)
+ else:
+ Logger.log("d", "No authentication is available to use for %s, but we did got a request for it.", self._id)
+
+ def _onGetPrintJobFinished(self, reply):
+ status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
+
+ if not self._printers:
+ return # Ignore the data for now, we don't have info about a printer yet.
+ printer = self._printers[0]
+
+ if status_code == 200:
+ try:
+ result = json.loads(bytes(reply.readAll()).decode("utf-8"))
+ except json.decoder.JSONDecodeError:
+ Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
+ return
+ if printer.activePrintJob is None:
+ print_job = PrintJobOutputModel(output_controller=self._output_controller)
+ printer.updateActivePrintJob(print_job)
+ else:
+ print_job = printer.activePrintJob
+ print_job.updateState(result["state"])
+ print_job.updateTimeElapsed(result["time_elapsed"])
+ print_job.updateTimeTotal(result["time_total"])
+ print_job.updateName(result["name"])
+ elif status_code == 404:
+ # No job found, so delete the active print job (if any!)
+ printer.updateActivePrintJob(None)
+ else:
+ Logger.log("w",
+ "Got status code {status_code} while trying to get printer data".format(status_code=status_code))
+
+ def materialHotendChangedMessage(self, callback):
+ Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"),
+ i18n_catalog.i18nc("@label",
+ "Would you like to use your current printer configuration in Cura?"),
+ i18n_catalog.i18nc("@label",
+ "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."),
+ buttons=QMessageBox.Yes + QMessageBox.No,
+ icon=QMessageBox.Question,
+ callback=callback
+ )
+
+ def _onGetPrinterDataFinished(self, reply):
+ status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
+ if status_code == 200:
+ try:
+ result = json.loads(bytes(reply.readAll()).decode("utf-8"))
+ except json.decoder.JSONDecodeError:
+ Logger.log("w", "Received an invalid printer state message: Not valid JSON.")
+ return
+
+ if not self._printers:
+ # Quickest way to get the firmware version is to grab it from the zeroconf.
+ firmware_version = self._properties.get(b"firmware_version", b"").decode("utf-8")
+ self._printers = [PrinterOutputModel(output_controller=self._output_controller, number_of_extruders=self._number_of_extruders, firmware_version=firmware_version)]
+ self._printers[0].setCamera(NetworkCamera("http://" + self._address + ":8080/?action=stream"))
+ for extruder in self._printers[0].extruders:
+ extruder.activeMaterialChanged.connect(self.materialIdChanged)
+ extruder.hotendIDChanged.connect(self.hotendIdChanged)
+ self.printersChanged.emit()
+
+ # LegacyUM3 always has a single printer.
+ printer = self._printers[0]
+ printer.updateBedTemperature(result["bed"]["temperature"]["current"])
+ printer.updateTargetBedTemperature(result["bed"]["temperature"]["target"])
+ printer.updateState(result["status"])
+
+ try:
+ # If we're still handling the request, we should ignore remote for a bit.
+ if not printer.getController().isPreheatRequestInProgress():
+ printer.updateIsPreheating(result["bed"]["pre_heat"]["active"])
+ except KeyError:
+ # Older firmwares don't support preheating, so we need to fake it.
+ pass
+
+ head_position = result["heads"][0]["position"]
+ printer.updateHeadPosition(head_position["x"], head_position["y"], head_position["z"])
+
+ for index in range(0, self._number_of_extruders):
+ temperatures = result["heads"][0]["extruders"][index]["hotend"]["temperature"]
+ extruder = printer.extruders[index]
+ extruder.updateTargetHotendTemperature(temperatures["target"])
+ extruder.updateHotendTemperature(temperatures["current"])
+
+ material_guid = result["heads"][0]["extruders"][index]["active_material"]["guid"]
+
+ if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_guid:
+ # Find matching material (as we need to set brand, type & color)
+ containers = ContainerRegistry.getInstance().findInstanceContainers(type="material",
+ GUID=material_guid)
+ if containers:
+ color = containers[0].getMetaDataEntry("color_code")
+ brand = containers[0].getMetaDataEntry("brand")
+ material_type = containers[0].getMetaDataEntry("material")
+ name = containers[0].getName()
+ else:
+ # Unknown material.
+ color = "#00000000"
+ brand = "Unknown"
+ material_type = "Unknown"
+ name = "Unknown"
+ material = MaterialOutputModel(guid=material_guid, type=material_type,
+ brand=brand, color=color, name = name)
+ extruder.updateActiveMaterial(material)
+
+ try:
+ hotend_id = result["heads"][0]["extruders"][index]["hotend"]["id"]
+ except KeyError:
+ hotend_id = ""
+ printer.extruders[index].updateHotendID(hotend_id)
+
+ else:
+ Logger.log("w",
+ "Got status code {status_code} while trying to get printer data".format(status_code = status_code))
+
+ ## Convenience function to "blur" out all but the last 5 characters of the auth key.
+ # This can be used to debug print the key, without it compromising the security.
+ def _getSafeAuthKey(self):
+ if self._authentication_key is not None:
+ result = self._authentication_key[-5:]
+ result = "********" + result
+ return result
+
+ return self._authentication_key
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py b/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py
new file mode 100644
index 0000000000..7a0e113d5b
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/LegacyUM3PrinterOutputController.py
@@ -0,0 +1,95 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+from PyQt5.QtCore import QTimer
+from UM.Version import Version
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+
+
+class LegacyUM3PrinterOutputController(PrinterOutputController):
+ def __init__(self, output_device):
+ super().__init__(output_device)
+ self._preheat_bed_timer = QTimer()
+ self._preheat_bed_timer.setSingleShot(True)
+ self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
+ self._preheat_printer = None
+
+ self.can_control_manually = False
+
+ # Are we still waiting for a response about preheat?
+ # We need this so we can already update buttons, so it feels more snappy.
+ self._preheat_request_in_progress = False
+
+ def isPreheatRequestInProgress(self):
+ return self._preheat_request_in_progress
+
+ def setJobState(self, job: "PrintJobOutputModel", state: str):
+ data = "{\"target\": \"%s\"}" % state
+ self._output_device.put("print_job/state", data, onFinished=None)
+
+ def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
+ data = str(temperature)
+ self._output_device.put("printer/bed/temperature/target", data, onFinished=self._onPutBedTemperatureCompleted)
+
+ def _onPutBedTemperatureCompleted(self, reply):
+ if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"):
+ # If it was handling a preheat, it isn't anymore.
+ self._preheat_request_in_progress = False
+
+ def _onPutPreheatBedCompleted(self, reply):
+ self._preheat_request_in_progress = False
+
+ def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
+ head_pos = printer._head_position
+ new_x = head_pos.x + x
+ new_y = head_pos.y + y
+ new_z = head_pos.z + z
+ data = "{\n\"x\":%s,\n\"y\":%s,\n\"z\":%s\n}" %(new_x, new_y, new_z)
+ self._output_device.put("printer/heads/0/position", data, onFinished=None)
+
+ def homeBed(self, printer):
+ self._output_device.put("printer/heads/0/position/z", "0", onFinished=None)
+
+ def _onPreheatBedTimerFinished(self):
+ self.setTargetBedTemperature(self._preheat_printer, 0)
+ self._preheat_printer.updateIsPreheating(False)
+ self._preheat_request_in_progress = True
+
+ def cancelPreheatBed(self, printer: "PrinterOutputModel"):
+ self.preheatBed(printer, temperature=0, duration=0)
+ self._preheat_bed_timer.stop()
+ printer.updateIsPreheating(False)
+
+ def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
+ try:
+ temperature = round(temperature) # The API doesn't allow floating point.
+ duration = round(duration)
+ except ValueError:
+ return # Got invalid values, can't pre-heat.
+
+ if duration > 0:
+ data = """{"temperature": "%i", "timeout": "%i"}""" % (temperature, duration)
+ else:
+ data = """{"temperature": "%i"}""" % temperature
+
+ # Real bed pre-heating support is implemented from 3.5.92 and up.
+
+ if Version(printer.firmwareVersion) < Version("3.5.92"):
+ # No firmware-side duration support then, so just set target bed temp and set a timer.
+ self.setTargetBedTemperature(printer, temperature=temperature)
+ self._preheat_bed_timer.setInterval(duration * 1000)
+ self._preheat_bed_timer.start()
+ self._preheat_printer = printer
+ printer.updateIsPreheating(True)
+ return
+
+ self._output_device.put("printer/bed/pre_heat", data, onFinished = self._onPutPreheatBedCompleted)
+ printer.updateIsPreheating(True)
+ self._preheat_request_in_progress = True
+
+
diff --git a/plugins/UM3NetworkPrinting/MonitorItem.qml b/plugins/UM3NetworkPrinting/MonitorItem.qml
index f69df41dd4..bbbc3feee6 100644
--- a/plugins/UM3NetworkPrinting/MonitorItem.qml
+++ b/plugins/UM3NetworkPrinting/MonitorItem.qml
@@ -6,40 +6,49 @@ import Cura 1.0 as Cura
Component
{
- Image
+ Item
{
- id: cameraImage
- property bool proportionalHeight:
+ width: maximumWidth
+ height: maximumHeight
+ Image
{
- if(sourceSize.height == 0 || maximumHeight == 0)
+ id: cameraImage
+ width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth)
+ height: Math.floor((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ z: 1
+ Component.onCompleted:
{
- return true;
+ if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+ {
+ OutputDevice.activePrinter.camera.start()
+ }
}
- return (sourceSize.width / sourceSize.height) > (maximumWidth / maximumHeight);
- }
- property real _width: Math.floor(Math.min(maximumWidth, sourceSize.width))
- property real _height: Math.floor(Math.min(maximumHeight, sourceSize.height))
- width: proportionalHeight ? _width : Math.floor(sourceSize.width * _height / sourceSize.height)
- height: !proportionalHeight ? _height : Math.floor(sourceSize.height * _width / sourceSize.width)
- anchors.horizontalCenter: parent.horizontalCenter
-
- onVisibleChanged:
- {
- if(visible)
+ onVisibleChanged:
{
- OutputDevice.startCamera()
- } else
- {
- OutputDevice.stopCamera()
+ if(visible)
+ {
+ if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+ {
+ OutputDevice.activePrinter.camera.start()
+ }
+ } else
+ {
+ if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+ {
+ OutputDevice.activePrinter.camera.stop()
+ }
+ }
}
- }
- source:
- {
- if(OutputDevice.cameraImage)
+ source:
{
- return OutputDevice.cameraImage;
+ if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
+ {
+ return OutputDevice.activePrinter.camera.latestImage;
+ }
+ return "";
}
- return "";
}
}
}
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py
deleted file mode 100644
index 853ef72f72..0000000000
--- a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py
+++ /dev/null
@@ -1,716 +0,0 @@
-import datetime
-import getpass
-import gzip
-import json
-import os
-import os.path
-import time
-
-from enum import Enum
-from PyQt5.QtNetwork import QNetworkRequest, QHttpPart, QHttpMultiPart
-from PyQt5.QtCore import QUrl, pyqtSlot, pyqtProperty, QCoreApplication, QTimer, pyqtSignal, QObject
-from PyQt5.QtGui import QDesktopServices
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply
-from UM.Application import Application
-from UM.Logger import Logger
-from UM.Message import Message
-from UM.OutputDevice import OutputDeviceError
-from UM.i18n import i18nCatalog
-from UM.Qt.Duration import Duration, DurationFormat
-from UM.PluginRegistry import PluginRegistry
-
-from . import NetworkPrinterOutputDevice
-
-
-i18n_catalog = i18nCatalog("cura")
-
-
-class OutputStage(Enum):
- ready = 0
- uploading = 2
-
-
-class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinterOutputDevice):
- printJobsChanged = pyqtSignal()
- printersChanged = pyqtSignal()
- selectedPrinterChanged = pyqtSignal()
-
- def __init__(self, key, address, properties, api_prefix):
- super().__init__(key, address, properties, api_prefix)
- # Store the address of the master.
- self._master_address = address
- name_property = properties.get(b"name", b"")
- if name_property:
- name = name_property.decode("utf-8")
- else:
- name = key
-
- self._authentication_state = NetworkPrinterOutputDevice.AuthState.Authenticated # The printer is always authenticated
-
- self.setName(name)
- description = i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network")
- self.setShortDescription(description)
- self.setDescription(description)
-
- self._stage = OutputStage.ready
- host_override = os.environ.get("CLUSTER_OVERRIDE_HOST", "")
- if host_override:
- Logger.log(
- "w",
- "Environment variable CLUSTER_OVERRIDE_HOST is set to [%s], cluster hosts are now set to this host",
- host_override)
- self._host = "http://" + host_override
- else:
- self._host = "http://" + address
-
- # is the same as in NetworkPrinterOutputDevicePlugin
- self._cluster_api_version = "1"
- self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
- self._api_base_uri = self._host + self._cluster_api_prefix
-
- self._file_name = None
- self._progress_message = None
- self._request = None
- self._reply = None
-
- # The main reason to keep the 'multipart' form data on the object
- # is to prevent the Python GC from claiming it too early.
- self._multipart = None
-
- self._print_view = None
- self._request_job = []
-
- self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
- self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
-
- self._print_jobs = []
- self._print_job_by_printer_uuid = {}
- self._print_job_by_uuid = {} # Print jobs by their own uuid
- self._printers = []
- self._printers_dict = {} # by unique_name
-
- self._connected_printers_type_count = []
- self._automatic_printer = {"unique_name": "", "friendly_name": "Automatic"} # empty unique_name IS automatic selection
- self._selected_printer = self._automatic_printer
-
- self._cluster_status_update_timer = QTimer()
- self._cluster_status_update_timer.setInterval(5000)
- self._cluster_status_update_timer.setSingleShot(False)
- self._cluster_status_update_timer.timeout.connect(self._requestClusterStatus)
-
- self._can_pause = True
- self._can_abort = True
- self._can_pre_heat_bed = False
- self._can_control_manually = False
- self._cluster_size = int(properties.get(b"cluster_size", 0))
-
- self._cleanupRequest()
-
- #These are texts that are to be translated for future features.
- temporary_translation = i18n_catalog.i18n("This printer is not set up to host a group of connected Ultimaker 3 printers.")
- temporary_translation2 = i18n_catalog.i18nc("Count is number of printers.", "This printer is the host for a group of {count} connected Ultimaker 3 printers.").format(count = 3)
- temporary_translation3 = i18n_catalog.i18n("{printer_name} has finished printing '{job_name}'. Please collect the print and confirm clearing the build plate.") #When finished.
- temporary_translation4 = i18n_catalog.i18n("{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing.") #When configuration changed.
-
- ## No authentication, so requestAuthentication should do exactly nothing
- @pyqtSlot()
- def requestAuthentication(self, message_id = None, action_id = "Retry"):
- pass # Cura Connect doesn't do any authorization
-
- def setAuthenticationState(self, auth_state):
- self._authentication_state = NetworkPrinterOutputDevice.AuthState.Authenticated # The printer is always authenticated
-
- def _verifyAuthentication(self):
- pass
-
- def _checkAuthentication(self):
- Logger.log("d", "_checkAuthentication Cura Connect - nothing to be done")
-
- @pyqtProperty(QObject, notify=selectedPrinterChanged)
- def controlItem(self):
- # TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
- if not self._control_item:
- self._createControlViewFromQML()
- name = self._selected_printer.get("friendly_name")
- if name == self._automatic_printer.get("friendly_name") or name == "":
- return self._control_item
- # Let cura use the default.
- return None
-
- @pyqtSlot(int, result = str)
- def getTimeCompleted(self, time_remaining):
- current_time = time.time()
- datetime_completed = datetime.datetime.fromtimestamp(current_time + time_remaining)
- return "{hour:02d}:{minute:02d}".format(hour = datetime_completed.hour, minute = datetime_completed.minute)
-
- @pyqtSlot(int, result = str)
- def getDateCompleted(self, time_remaining):
- current_time = time.time()
- datetime_completed = datetime.datetime.fromtimestamp(current_time + time_remaining)
- return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
-
- @pyqtProperty(int, constant = True)
- def clusterSize(self):
- return self._cluster_size
-
- @pyqtProperty(str, notify=selectedPrinterChanged)
- def name(self):
- # Show the name of the selected printer.
- # This is not the nicest way to do this, but changes to the Cura UI are required otherwise.
- name = self._selected_printer.get("friendly_name")
- if name != self._automatic_printer.get("friendly_name"):
- return name
- # Return name of cluster master.
- return self._properties.get(b"name", b"").decode("utf-8")
-
- def connect(self):
- super().connect()
- self._cluster_status_update_timer.start()
-
- def close(self):
- super().close()
- self._cluster_status_update_timer.stop()
-
- def _setJobState(self, job_state):
- if not self._selected_printer:
- return
-
- selected_printer_uuid = self._printers_dict[self._selected_printer["unique_name"]]["uuid"]
- if selected_printer_uuid not in self._print_job_by_printer_uuid:
- return
-
- print_job_uuid = self._print_job_by_printer_uuid[selected_printer_uuid]["uuid"]
-
- url = QUrl(self._api_base_uri + "print_jobs/" + print_job_uuid + "/action")
- put_request = QNetworkRequest(url)
- put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- data = '{"action": "' + job_state + '"}'
- self._manager.put(put_request, data.encode())
-
- def _requestClusterStatus(self):
- # TODO: Handle timeout. We probably want to know if the cluster is still reachable or not.
- url = QUrl(self._api_base_uri + "printers/")
- printers_request = QNetworkRequest(url)
- self._addUserAgentHeader(printers_request)
- self._manager.get(printers_request)
- # See _finishedPrintersRequest()
-
- if self._printers: # if printers is not empty
- url = QUrl(self._api_base_uri + "print_jobs/")
- print_jobs_request = QNetworkRequest(url)
- self._addUserAgentHeader(print_jobs_request)
- self._manager.get(print_jobs_request)
- # See _finishedPrintJobsRequest()
-
- def _finishedPrintJobsRequest(self, reply):
- try:
- json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
- return
- self.setPrintJobs(json_data)
-
- def _finishedPrintersRequest(self, reply):
- try:
- json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
- return
- self.setPrinters(json_data)
-
- def materialHotendChangedMessage(self, callback):
- # When there is just one printer, the activate configuration option is enabled
- if (self._cluster_size == 1):
- super().materialHotendChangedMessage(callback = callback)
-
- def _startCameraStream(self):
- ## Request new image
- url = QUrl("http://" + self._printers_dict[self._selected_printer["unique_name"]]["ip_address"] + ":8080/?action=stream")
- self._image_request = QNetworkRequest(url)
- self._addUserAgentHeader(self._image_request)
- self._image_reply = self._manager.get(self._image_request)
- self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
-
- def spawnPrintView(self):
- if self._print_view is None:
- path = os.path.join(self._plugin_path, "PrintWindow.qml")
- self._print_view = Application.getInstance().createQmlComponent(path, {"OutputDevice", self})
- if self._print_view is not None:
- self._print_view.show()
-
- ## Store job info, show Print view for settings
- def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
- self._selected_printer = self._automatic_printer # reset to default option
- self._request_job = [nodes, file_name, filter_by_machine, file_handler, kwargs]
-
- if self._stage != OutputStage.ready:
- if self._error_message:
- self._error_message.hide()
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status",
- "Sending new jobs (temporarily) blocked, still sending the previous print job."))
- self._error_message.show()
- return
-
- self.writeStarted.emit(self) # Allow postprocessing before sending data to the printer
-
- if len(self._printers) > 1:
- self.spawnPrintView() # Ask user how to print it.
- elif len(self._printers) == 1:
- # If there is only one printer, don't bother asking.
- self.selectAutomaticPrinter()
- self.sendPrintJob()
- else:
- # Cluster has no printers, warn the user of this.
- if self._error_message:
- self._error_message.hide()
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status",
- "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."))
- self._error_message.show()
-
- ## Actually send the print job, called from the dialog
- # :param: require_printer_name: name of printer, or ""
- @pyqtSlot()
- def sendPrintJob(self):
- nodes, file_name, filter_by_machine, file_handler, kwargs = self._request_job
- require_printer_name = self._selected_printer["unique_name"]
-
- self._send_gcode_start = time.time()
- Logger.log("d", "Sending print job [%s] to host..." % file_name)
-
- if self._stage != OutputStage.ready:
- Logger.log("d", "Unable to send print job as the state is %s", self._stage)
- raise OutputDeviceError.DeviceBusyError()
- self._stage = OutputStage.uploading
-
- self._file_name = "%s.gcode.gz" % file_name
- self._showProgressMessage()
-
- new_request = self._buildSendPrintJobHttpRequest(require_printer_name)
- if new_request is None or self._stage != OutputStage.uploading:
- return
- self._request = new_request
- self._reply = self._manager.post(self._request, self._multipart)
- self._reply.uploadProgress.connect(self._onUploadProgress)
- # See _finishedPostPrintJobRequest()
-
- def _buildSendPrintJobHttpRequest(self, require_printer_name):
- api_url = QUrl(self._api_base_uri + "print_jobs/")
- request = QNetworkRequest(api_url)
- # Create multipart request and add the g-code.
- self._multipart = QHttpMultiPart(QHttpMultiPart.FormDataType)
-
- # Add gcode
- part = QHttpPart()
- part.setHeader(QNetworkRequest.ContentDispositionHeader,
- 'form-data; name="file"; filename="%s"' % self._file_name)
-
- gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
- compressed_gcode = self._compressGcode(gcode)
- if compressed_gcode is None:
- return None # User aborted print, so stop trying.
-
- part.setBody(compressed_gcode)
- self._multipart.append(part)
-
- # require_printer_name "" means automatic
- if require_printer_name:
- self._multipart.append(self.__createKeyValueHttpPart("require_printer_name", require_printer_name))
- user_name = self.__get_username()
- if user_name is None:
- user_name = "unknown"
- self._multipart.append(self.__createKeyValueHttpPart("owner", user_name))
-
- self._addUserAgentHeader(request)
- return request
-
- def _compressGcode(self, gcode):
- self._compressing_print = True
- batched_line = ""
- max_chars_per_line = int(1024 * 1024 / 4) # 1 / 4 MB
-
- byte_array_file_data = b""
-
- def _compressDataAndNotifyQt(data_to_append):
- compressed_data = gzip.compress(data_to_append.encode("utf-8"))
- self._progress_message.setProgress(-1) # Tickle the message so that it's clear that it's still being used.
- QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
- # Pretend that this is a response, as zipping might take a bit of time.
- self._last_response_time = time.time()
- return compressed_data
-
- if gcode is None:
- Logger.log("e", "Unable to find sliced gcode, returning empty.")
- return byte_array_file_data
-
- for line in gcode:
- if not self._compressing_print:
- self._progress_message.hide()
- return None # Stop trying to zip, abort was called.
- batched_line += line
- # if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
- # Compressing line by line in this case is extremely slow, so we need to batch them.
- if len(batched_line) < max_chars_per_line:
- continue
- byte_array_file_data += _compressDataAndNotifyQt(batched_line)
- batched_line = ""
-
- # Also compress the leftovers.
- if batched_line:
- byte_array_file_data += _compressDataAndNotifyQt(batched_line)
-
- return byte_array_file_data
-
- def __createKeyValueHttpPart(self, key, value):
- metadata_part = QHttpPart()
- metadata_part.setHeader(QNetworkRequest.ContentTypeHeader, 'text/plain')
- metadata_part.setHeader(QNetworkRequest.ContentDispositionHeader, 'form-data; name="%s"' % (key))
- metadata_part.setBody(bytearray(value, "utf8"))
- return metadata_part
-
- def __get_username(self):
- try:
- return getpass.getuser()
- except:
- Logger.log("d", "Could not get the system user name, returning 'unknown' instead.")
- return None
-
- def _finishedPrintJobPostRequest(self, reply):
- self._stage = OutputStage.ready
- if self._progress_message:
- self._progress_message.hide()
- self._progress_message = None
- self.writeFinished.emit(self)
-
- if reply.error():
- self._showRequestFailedMessage(reply)
- self.writeError.emit(self)
- else:
- self._showRequestSucceededMessage()
- self.writeSuccess.emit(self)
-
- self._cleanupRequest()
-
- def _showRequestFailedMessage(self, reply):
- if reply is not None:
- Logger.log("w", "Unable to send print job to group {cluster_name}: {error_string} ({error})".format(
- cluster_name = self.getName(),
- error_string = str(reply.errorString()),
- error = str(reply.error())))
- error_message_template = i18n_catalog.i18nc("@info:status", "Unable to send print job to group {cluster_name}.")
- message = Message(text=error_message_template.format(
- cluster_name = self.getName()))
- message.show()
-
- def _showRequestSucceededMessage(self):
- confirmation_message_template = i18n_catalog.i18nc(
- "@info:status",
- "Sent {file_name} to group {cluster_name}."
- )
- file_name = os.path.basename(self._file_name).split(".")[0]
- message_text = confirmation_message_template.format(cluster_name = self.getName(), file_name = file_name)
- message = Message(text=message_text)
- button_text = i18n_catalog.i18nc("@action:button", "Show print jobs")
- button_tooltip = i18n_catalog.i18nc("@info:tooltip", "Opens the print jobs interface in your browser.")
- message.addAction("open_browser", button_text, "globe", button_tooltip)
- message.actionTriggered.connect(self._onMessageActionTriggered)
- message.show()
-
- def setPrintJobs(self, print_jobs):
- #TODO: hack, last seen messes up the check, so drop it.
- for job in print_jobs:
- del job["last_seen"]
- # Strip any extensions
- job["name"] = self._removeGcodeExtension(job["name"])
-
- if self._print_jobs != print_jobs:
- old_print_jobs = self._print_jobs
- self._print_jobs = print_jobs
-
- self._notifyFinishedPrintJobs(old_print_jobs, print_jobs)
- self._notifyConfigurationChangeRequired(old_print_jobs, print_jobs)
-
- # Yes, this is a hacky way of doing it, but it's quick and the API doesn't give the print job per printer
- # for some reason. ugh.
- self._print_job_by_printer_uuid = {}
- self._print_job_by_uuid = {}
- for print_job in print_jobs:
- if "printer_uuid" in print_job and print_job["printer_uuid"] is not None:
- self._print_job_by_printer_uuid[print_job["printer_uuid"]] = print_job
- self._print_job_by_uuid[print_job["uuid"]] = print_job
- self.printJobsChanged.emit()
-
- def _removeGcodeExtension(self, name):
- parts = name.split(".")
- if parts[-1].upper() == "GZ":
- parts = parts[:-1]
- if parts[-1].upper() == "GCODE":
- parts = parts[:-1]
- return ".".join(parts)
-
- def _notifyFinishedPrintJobs(self, old_print_jobs, new_print_jobs):
- """Notify the user when any of their print jobs have just completed.
-
- Arguments:
-
- old_print_jobs -- the previous list of print job status information as returned by the cluster REST API.
- new_print_jobs -- the current list of print job status information as returned by the cluster REST API.
- """
- if old_print_jobs is None:
- return
-
- username = self.__get_username()
- if username is None:
- return
-
- our_old_print_jobs = self.__filterOurPrintJobs(old_print_jobs)
- our_old_not_finished_print_jobs = [pj for pj in our_old_print_jobs if pj["status"] != "wait_cleanup"]
-
- our_new_print_jobs = self.__filterOurPrintJobs(new_print_jobs)
- our_new_finished_print_jobs = [pj for pj in our_new_print_jobs if pj["status"] == "wait_cleanup"]
-
- old_not_finished_print_job_uuids = set([pj["uuid"] for pj in our_old_not_finished_print_jobs])
-
- for print_job in our_new_finished_print_jobs:
- if print_job["uuid"] in old_not_finished_print_job_uuids:
-
- printer_name = self.__getPrinterNameFromUuid(print_job["printer_uuid"])
- if printer_name is None:
- printer_name = i18n_catalog.i18nc("@label Printer name", "Unknown")
-
- message_text = (i18n_catalog.i18nc("@info:status",
- "Printer '{printer_name}' has finished printing '{job_name}'.")
- .format(printer_name=printer_name, job_name=print_job["name"]))
- message = Message(text=message_text, title=i18n_catalog.i18nc("@info:status", "Print finished"))
- Application.getInstance().showMessage(message)
- Application.getInstance().showToastMessage(
- i18n_catalog.i18nc("@info:status", "Print finished"),
- message_text)
-
- def __filterOurPrintJobs(self, print_jobs):
- username = self.__get_username()
- return [print_job for print_job in print_jobs if print_job["owner"] == username]
-
- def _notifyConfigurationChangeRequired(self, old_print_jobs, new_print_jobs):
- if old_print_jobs is None:
- return
-
- old_change_required_print_jobs = self.__filterConfigChangePrintJobs(self.__filterOurPrintJobs(old_print_jobs))
- new_change_required_print_jobs = self.__filterConfigChangePrintJobs(self.__filterOurPrintJobs(new_print_jobs))
- old_change_required_print_job_uuids = set([pj["uuid"] for pj in old_change_required_print_jobs])
-
- for print_job in new_change_required_print_jobs:
- if print_job["uuid"] not in old_change_required_print_job_uuids:
-
- printer_name = self.__getPrinterNameFromUuid(print_job["assigned_to"])
- if printer_name is None:
- # don't report on yet unknown printers
- continue
-
- message_text = (i18n_catalog.i18n("{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing.")
- .format(printer_name=printer_name, job_name=print_job["name"]))
- message = Message(text=message_text, title=i18n_catalog.i18nc("@label:status", "Action required"))
- Application.getInstance().showMessage(message)
- Application.getInstance().showToastMessage(
- i18n_catalog.i18nc("@label:status", "Action required"),
- message_text)
-
- def __filterConfigChangePrintJobs(self, print_jobs):
- return filter(self.__isConfigurationChangeRequiredPrintJob, print_jobs)
-
- def __isConfigurationChangeRequiredPrintJob(self, print_job):
- if print_job["status"] == "queued":
- changes_required = print_job.get("configuration_changes_required", [])
- return len(changes_required) != 0
- return False
-
- def __getPrinterNameFromUuid(self, printer_uuid):
- for printer in self._printers:
- if printer["uuid"] == printer_uuid:
- return printer["friendly_name"]
- return None
-
- def setPrinters(self, printers):
- if self._printers != printers:
- self._connected_printers_type_count = []
- printers_count = {}
- self._printers = printers
- self._printers_dict = dict((p["unique_name"], p) for p in printers) # for easy lookup by unique_name
-
- for printer in printers:
- variant = printer["machine_variant"]
- if variant in printers_count:
- printers_count[variant] += 1
- else:
- printers_count[variant] = 1
- for type in printers_count:
- self._connected_printers_type_count.append({"machine_type": type, "count": printers_count[type]})
- self.printersChanged.emit()
-
- @pyqtProperty("QVariantList", notify=printersChanged)
- def connectedPrintersTypeCount(self):
- return self._connected_printers_type_count
-
- @pyqtProperty("QVariantList", notify=printersChanged)
- def connectedPrinters(self):
- return self._printers
-
- @pyqtProperty(int, notify=printJobsChanged)
- def numJobsPrinting(self):
- num_jobs_printing = 0
- for job in self._print_jobs:
- if job["status"] in ["printing", "wait_cleanup", "sent_to_printer", "pre_print", "post_print"]:
- num_jobs_printing += 1
- return num_jobs_printing
-
- @pyqtProperty(int, notify=printJobsChanged)
- def numJobsQueued(self):
- num_jobs_queued = 0
- for job in self._print_jobs:
- if job["status"] == "queued":
- num_jobs_queued += 1
- return num_jobs_queued
-
- @pyqtProperty("QVariantMap", notify=printJobsChanged)
- def printJobsByUUID(self):
- return self._print_job_by_uuid
-
- @pyqtProperty("QVariantMap", notify=printJobsChanged)
- def printJobsByPrinterUUID(self):
- return self._print_job_by_printer_uuid
-
- @pyqtProperty("QVariantList", notify=printJobsChanged)
- def printJobs(self):
- return self._print_jobs
-
- @pyqtProperty("QVariantList", notify=printersChanged)
- def printers(self):
- return [self._automatic_printer, ] + self._printers
-
- @pyqtSlot(str, str)
- def selectPrinter(self, unique_name, friendly_name):
- self.stopCamera()
- self._selected_printer = {"unique_name": unique_name, "friendly_name": friendly_name}
- Logger.log("d", "Selected printer: %s %s", friendly_name, unique_name)
- # TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
- if unique_name == "":
- self._address = self._master_address
- else:
- self._address = self._printers_dict[self._selected_printer["unique_name"]]["ip_address"]
-
- self.selectedPrinterChanged.emit()
-
- def _updateJobState(self, job_state):
- name = self._selected_printer.get("friendly_name")
- if name == "" or name == "Automatic":
- # TODO: This is now a bit hacked; If no printer is selected, don't show job state.
- if self._job_state != "":
- self._job_state = ""
- self.jobStateChanged.emit()
- else:
- if self._job_state != job_state:
- self._job_state = job_state
- self.jobStateChanged.emit()
-
- @pyqtSlot()
- def selectAutomaticPrinter(self):
- self.stopCamera()
- self._selected_printer = self._automatic_printer
- self.selectedPrinterChanged.emit()
-
- @pyqtProperty("QVariant", notify=selectedPrinterChanged)
- def selectedPrinterName(self):
- return self._selected_printer.get("unique_name", "")
-
- def getPrintJobsUrl(self):
- return self._host + "/print_jobs"
-
- def getPrintersUrl(self):
- return self._host + "/printers"
-
- def _showProgressMessage(self):
- progress_message_template = i18n_catalog.i18nc("@info:progress",
- "Sending {file_name} to group {cluster_name}")
- file_name = os.path.basename(self._file_name).split(".")[0]
- self._progress_message = Message(progress_message_template.format(file_name = file_name, cluster_name = self.getName()), 0, False, -1)
- self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
- self._progress_message.actionTriggered.connect(self._onMessageActionTriggered)
- self._progress_message.show()
-
- def _addUserAgentHeader(self, request):
- request.setRawHeader(b"User-agent", b"CuraPrintClusterOutputDevice Plugin")
-
- def _cleanupRequest(self):
- self._request = None
- self._stage = OutputStage.ready
- self._file_name = None
-
- def _onFinished(self, reply):
- super()._onFinished(reply)
- reply_url = reply.url().toString()
- status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
- if status_code == 500:
- Logger.log("w", "Request to {url} returned a 500.".format(url = reply_url))
- return
- if reply.error() == QNetworkReply.ContentOperationNotPermittedError:
- # It was probably "/api/v1/materials" for legacy UM3
- return
- if reply.error() == QNetworkReply.ContentNotFoundError:
- # It was probably "/api/v1/print_job" for legacy UM3
- return
-
- if reply.operation() == QNetworkAccessManager.PostOperation:
- if self._cluster_api_prefix + "print_jobs" in reply_url:
- self._finishedPrintJobPostRequest(reply)
- return
-
- # We need to do this check *after* we process the post operation!
- # If the sending of g-code is cancelled by the user it will result in an error, but we do need to handle this.
- if reply.error() != QNetworkReply.NoError:
- Logger.log("e", "After requesting [%s] we got a network error [%s]. Not processing anything...", reply_url, reply.error())
- return
-
- elif reply.operation() == QNetworkAccessManager.GetOperation:
- if self._cluster_api_prefix + "print_jobs" in reply_url:
- self._finishedPrintJobsRequest(reply)
- elif self._cluster_api_prefix + "printers" in reply_url:
- self._finishedPrintersRequest(reply)
-
- @pyqtSlot()
- def openPrintJobControlPanel(self):
- Logger.log("d", "Opening print job control panel...")
- QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))
-
- @pyqtSlot()
- def openPrinterControlPanel(self):
- Logger.log("d", "Opening printer control panel...")
- QDesktopServices.openUrl(QUrl(self.getPrintersUrl()))
-
- def _onMessageActionTriggered(self, message, action):
- if action == "open_browser":
- QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))
-
- if action == "Abort":
- Logger.log("d", "User aborted sending print to remote.")
- self._progress_message.hide()
- self._compressing_print = False
- if self._reply:
- self._reply.abort()
- self._stage = OutputStage.ready
- Application.getInstance().getController().setActiveStage("PrepareStage")
-
- @pyqtSlot(int, result=str)
- def formatDuration(self, seconds):
- return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
-
- ## For cluster below
- def _get_plugin_directory_name(self):
- current_file_absolute_path = os.path.realpath(__file__)
- directory_path = os.path.dirname(current_file_absolute_path)
- _, directory_name = os.path.split(directory_path)
- return directory_name
-
- @property
- def _plugin_path(self):
- return PluginRegistry.getInstance().getPluginPath(self._get_plugin_directory_name())
diff --git a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py
deleted file mode 100755
index 17e4bce0ef..0000000000
--- a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py
+++ /dev/null
@@ -1,1307 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-from UM.i18n import i18nCatalog
-from UM.Application import Application
-from UM.Logger import Logger
-from UM.Signal import signalemitter
-
-from UM.Message import Message
-
-import UM.Settings.ContainerRegistry
-import UM.Version #To compare firmware version numbers.
-
-from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
-from cura.Settings.ContainerManager import ContainerManager
-import cura.Settings.ExtruderManager
-
-from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
-from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot, QCoreApplication
-from PyQt5.QtGui import QImage, QColor
-from PyQt5.QtWidgets import QMessageBox
-
-import json
-import os
-import gzip
-
-from time import time
-
-from time import gmtime
-from enum import IntEnum
-
-i18n_catalog = i18nCatalog("cura")
-
-class AuthState(IntEnum):
- NotAuthenticated = 1
- AuthenticationRequested = 2
- Authenticated = 3
- AuthenticationDenied = 4
-
-## Network connected (wifi / lan) printer that uses the Ultimaker API
-@signalemitter
-class NetworkPrinterOutputDevice(PrinterOutputDevice):
- def __init__(self, key, address, properties, api_prefix):
- super().__init__(key)
- self._address = address
- self._key = key
- self._properties = properties # Properties dict as provided by zero conf
- self._api_prefix = api_prefix
-
- self._gcode = None
- self._print_finished = True # _print_finished == False means we're halfway in a print
- self._write_finished = True # _write_finished == False means we're currently sending a G-code file
-
- self._use_gzip = True # Should we use g-zip compression before sending the data?
-
- # This holds the full JSON file that was received from the last request.
- # The JSON looks like:
- #{
- # "led": {"saturation": 0.0, "brightness": 100.0, "hue": 0.0},
- # "beep": {},
- # "network": {
- # "wifi_networks": [],
- # "ethernet": {"connected": true, "enabled": true},
- # "wifi": {"ssid": "xxxx", "connected": False, "enabled": False}
- # },
- # "diagnostics": {},
- # "bed": {"temperature": {"target": 60.0, "current": 44.4}},
- # "heads": [{
- # "max_speed": {"z": 40.0, "y": 300.0, "x": 300.0},
- # "position": {"z": 20.0, "y": 6.0, "x": 180.0},
- # "fan": 0.0,
- # "jerk": {"z": 0.4, "y": 20.0, "x": 20.0},
- # "extruders": [
- # {
- # "feeder": {"max_speed": 45.0, "jerk": 5.0, "acceleration": 3000.0},
- # "active_material": {"guid": "xxxxxxx", "length_remaining": -1.0},
- # "hotend": {"temperature": {"target": 0.0, "current": 22.8}, "id": "AA 0.4"}
- # },
- # {
- # "feeder": {"max_speed": 45.0, "jerk": 5.0, "acceleration": 3000.0},
- # "active_material": {"guid": "xxxx", "length_remaining": -1.0},
- # "hotend": {"temperature": {"target": 0.0, "current": 22.8}, "id": "BB 0.4"}
- # }
- # ],
- # "acceleration": 3000.0
- # }],
- # "status": "printing"
- #}
-
- self._json_printer_state = {}
-
- ## Todo: Hardcoded value now; we should probably read this from the machine file.
- ## It's okay to leave this for now, as this plugin is um3 only (and has 2 extruders by definition)
- self._num_extruders = 2
-
- # These are reinitialised here (from PrinterOutputDevice) to match the new _num_extruders
- self._hotend_temperatures = [0] * self._num_extruders
- self._target_hotend_temperatures = [0] * self._num_extruders
-
- self._material_ids = [""] * self._num_extruders
- self._hotend_ids = [""] * self._num_extruders
- self._target_bed_temperature = 0
- self._processing_preheat_requests = True
-
- self._can_control_manually = False
-
- self.setPriority(3) # Make sure the output device gets selected above local file output
- self.setName(key)
- self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network"))
- self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network"))
- self.setIconName("print")
-
- self._manager = None
-
- self._post_request = None
- self._post_reply = None
- self._post_multi_part = None
- self._post_part = None
-
- self._material_multi_part = None
- self._material_part = None
-
- self._progress_message = None
- self._error_message = None
- self._connection_message = None
-
- self._update_timer = QTimer()
- self._update_timer.setInterval(2000) # TODO; Add preference for update interval
- self._update_timer.setSingleShot(False)
- self._update_timer.timeout.connect(self._update)
-
- self._camera_timer = QTimer()
- self._camera_timer.setInterval(500) # Todo: Add preference for camera update interval
- self._camera_timer.setSingleShot(False)
- self._camera_timer.timeout.connect(self._updateCamera)
-
- self._image_request = None
- self._image_reply = None
-
- self._use_stream = True
- self._stream_buffer = b""
- self._stream_buffer_start_index = -1
-
- self._camera_image_id = 0
-
- self._authentication_counter = 0
- self._max_authentication_counter = 5 * 60 # Number of attempts before authentication timed out (5 min)
-
- self._authentication_timer = QTimer()
- self._authentication_timer.setInterval(1000) # TODO; Add preference for update interval
- self._authentication_timer.setSingleShot(False)
- self._authentication_timer.timeout.connect(self._onAuthenticationTimer)
- self._authentication_request_active = False
-
- self._authentication_state = AuthState.NotAuthenticated
- self._authentication_id = None
- self._authentication_key = None
-
- self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer requested. Please approve the request on the printer"), lifetime = 0, dismissable = False, progress = 0, title = i18n_catalog.i18nc("@info:title", "Connection status"))
- self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", ""), title = i18n_catalog.i18nc("@info:title", "Connection Status"))
- self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry"), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the access request"))
- self._authentication_failed_message.actionTriggered.connect(self.requestAuthentication)
- self._authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer accepted"), title = i18n_catalog.i18nc("@info:title", "Connection Status"))
- self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", "No access to print with this printer. Unable to send print job."), title = i18n_catalog.i18nc("@info:title", "Connection Status"))
- self._not_authenticated_message.addAction("Request", i18n_catalog.i18nc("@action:button", "Request Access"), None, i18n_catalog.i18nc("@info:tooltip", "Send access request to the printer"))
- self._not_authenticated_message.actionTriggered.connect(self.requestAuthentication)
-
- self._camera_image = QImage()
-
- self._material_post_objects = {}
- self._connection_state_before_timeout = None
-
- self._last_response_time = time()
- self._last_request_time = None
- self._response_timeout_time = 10
- self._recreate_network_manager_time = 30 # If we have no connection, re-create network manager every 30 sec.
- self._recreate_network_manager_count = 1
-
- self._send_gcode_start = time() # Time when the sending of the g-code started.
-
- self._last_command = ""
-
- self._compressing_print = False
- self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
- printer_type = self._properties.get(b"machine", b"").decode("utf-8")
- if printer_type.startswith("9511"):
- self._updatePrinterType("ultimaker3_extended")
- elif printer_type.startswith("9066"):
- self._updatePrinterType("ultimaker3")
- else:
- self._updatePrinterType("unknown")
-
- Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
-
- def _onNetworkAccesibleChanged(self, accessible):
- Logger.log("d", "Network accessible state changed to: %s", accessible)
-
- ## Triggered when the output device manager changes devices.
- #
- # This is how we can detect that our device is no longer active now.
- def _onOutputDevicesChanged(self):
- if self.getId() not in Application.getInstance().getOutputDeviceManager().getOutputDeviceIds():
- self.stopCamera()
-
- def _onAuthenticationTimer(self):
- self._authentication_counter += 1
- self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100)
- if self._authentication_counter > self._max_authentication_counter:
- self._authentication_timer.stop()
- Logger.log("i", "Authentication timer ended. Setting authentication to denied for printer: %s" % self._key)
- self.setAuthenticationState(AuthState.AuthenticationDenied)
-
- def _onAuthenticationRequired(self, reply, authenticator):
- if self._authentication_id is not None and self._authentication_key is not None:
- Logger.log("d", "Authentication was required for printer: %s. Setting up authenticator with ID %s and key %s", self._key, self._authentication_id, self._getSafeAuthKey())
- authenticator.setUser(self._authentication_id)
- authenticator.setPassword(self._authentication_key)
- else:
- Logger.log("d", "No authentication is available to use for %s, but we did got a request for it.", self._key)
-
- def getProperties(self):
- return self._properties
-
- @pyqtSlot(str, result = str)
- def getProperty(self, key):
- key = key.encode("utf-8")
- if key in self._properties:
- return self._properties.get(key, b"").decode("utf-8")
- else:
- return ""
-
- ## Get the unique key of this machine
- # \return key String containing the key of the machine.
- @pyqtSlot(result = str)
- def getKey(self):
- return self._key
-
- ## The IP address of the printer.
- @pyqtProperty(str, constant = True)
- def address(self):
- return self._properties.get(b"address", b"").decode("utf-8")
-
- ## Name of the printer (as returned from the ZeroConf properties)
- @pyqtProperty(str, constant = True)
- def name(self):
- return self._properties.get(b"name", b"").decode("utf-8")
-
- ## Firmware version (as returned from the ZeroConf properties)
- @pyqtProperty(str, constant=True)
- def firmwareVersion(self):
- return self._properties.get(b"firmware_version", b"").decode("utf-8")
-
- ## IPadress of this printer
- @pyqtProperty(str, constant=True)
- def ipAddress(self):
- return self._address
-
- ## Pre-heats the heated bed of the printer.
- #
- # \param temperature The temperature to heat the bed to, in degrees
- # Celsius.
- # \param duration How long the bed should stay warm, in seconds.
- @pyqtSlot(float, float)
- def preheatBed(self, temperature, duration):
- temperature = round(temperature) #The API doesn't allow floating point.
- duration = round(duration)
- if UM.Version.Version(self.firmwareVersion) < UM.Version.Version("3.5.92"): #Real bed pre-heating support is implemented from 3.5.92 and up.
- self.setTargetBedTemperature(temperature = temperature) #No firmware-side duration support then.
- return
- url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/pre_heat")
- if duration > 0:
- data = """{"temperature": "%i", "timeout": "%i"}""" % (temperature, duration)
- else:
- data = """{"temperature": "%i"}""" % temperature
- Logger.log("i", "Pre-heating bed to %i degrees.", temperature)
- put_request = QNetworkRequest(url)
- put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- self._processing_preheat_requests = False
- self._manager.put(put_request, data.encode())
- self._preheat_bed_timer.start(self._preheat_bed_timeout * 1000) #Times 1000 because it needs to be provided as milliseconds.
- self.preheatBedRemainingTimeChanged.emit()
-
- ## Cancels pre-heating the heated bed of the printer.
- #
- # If the bed is not pre-heated, nothing happens.
- @pyqtSlot()
- def cancelPreheatBed(self):
- Logger.log("i", "Cancelling pre-heating of the bed.")
- self.preheatBed(temperature = 0, duration = 0)
- self._preheat_bed_timer.stop()
- self._preheat_bed_timer.setInterval(0)
- self.preheatBedRemainingTimeChanged.emit()
-
- ## Changes the target bed temperature on the printer.
- #
- # /param temperature The new target temperature of the bed.
- def _setTargetBedTemperature(self, temperature):
- if not self._updateTargetBedTemperature(temperature):
- return
-
- url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/temperature/target")
- data = str(temperature)
- put_request = QNetworkRequest(url)
- put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- self._manager.put(put_request, data.encode())
-
- ## Updates the target bed temperature from the printer, and emit a signal if it was changed.
- #
- # /param temperature The new target temperature of the bed.
- # /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature
- def _updateTargetBedTemperature(self, temperature):
- if self._target_bed_temperature == temperature:
- return False
- self._target_bed_temperature = temperature
- self.targetBedTemperatureChanged.emit()
- return True
-
- ## Updates the target hotend temperature from the printer, and emit a signal if it was changed.
- #
- # /param index The index of the hotend.
- # /param temperature The new target temperature of the hotend.
- # /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature
- def _updateTargetHotendTemperature(self, index, temperature):
- if self._target_hotend_temperatures[index] == temperature:
- return False
- self._target_hotend_temperatures[index] = temperature
- self.targetHotendTemperaturesChanged.emit()
- return True
-
- def _stopCamera(self):
- self._stream_buffer = b""
- self._stream_buffer_start_index = -1
-
- if self._camera_timer.isActive():
- self._camera_timer.stop()
-
- if self._image_reply:
- try:
- # disconnect the signal
- try:
- self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
- except Exception:
- pass
- # abort the request if it's not finished
- if not self._image_reply.isFinished():
- self._image_reply.close()
- except Exception as e: #RuntimeError
- pass # It can happen that the wrapped c++ object is already deleted.
- self._image_reply = None
- self._image_request = None
-
- def _startCamera(self):
- if self._use_stream:
- self._startCameraStream()
- else:
- self._camera_timer.start()
-
- def _startCameraStream(self):
- ## Request new image
- url = QUrl("http://" + self._address + ":8080/?action=stream")
- self._image_request = QNetworkRequest(url)
- self._image_reply = self._manager.get(self._image_request)
- self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
-
- def _updateCamera(self):
- if not self._manager.networkAccessible():
- return
- ## Request new image
- url = QUrl("http://" + self._address + ":8080/?action=snapshot")
- image_request = QNetworkRequest(url)
- self._manager.get(image_request)
- self._last_request_time = time()
-
- ## Set the authentication state.
- # \param auth_state \type{AuthState} Enum value representing the new auth state
- def setAuthenticationState(self, auth_state):
- if auth_state == self._authentication_state:
- return # Nothing to do here.
-
- Logger.log("d", "Attempting to update auth state from %s to %s for printer %s" % (self._authentication_state, auth_state, self._key))
-
- if auth_state == AuthState.AuthenticationRequested:
- Logger.log("d", "Authentication state changed to authentication requested.")
- self.setAcceptsCommands(False)
- self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network. Please approve the access request on the printer."))
- self._authentication_requested_message.show()
- self._authentication_request_active = True
- self._authentication_timer.start() # Start timer so auth will fail after a while.
- elif auth_state == AuthState.Authenticated:
- Logger.log("d", "Authentication state changed to authenticated")
- self.setAcceptsCommands(True)
- self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network."))
- self._authentication_requested_message.hide()
- if self._authentication_request_active:
- self._authentication_succeeded_message.show()
-
- # Stop waiting for a response
- self._authentication_timer.stop()
- self._authentication_counter = 0
-
- # Once we are authenticated we need to send all material profiles.
- self.sendMaterialProfiles()
- elif auth_state == AuthState.AuthenticationDenied:
- self.setAcceptsCommands(False)
- self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network. No access to control the printer."))
- self._authentication_requested_message.hide()
- if self._authentication_request_active:
- if self._authentication_timer.remainingTime() > 0:
- Logger.log("d", "Authentication state changed to authentication denied before the request timeout.")
- self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request was denied on the printer."))
- else:
- Logger.log("d", "Authentication state changed to authentication denied due to a timeout")
- self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request failed due to a timeout."))
-
- self._authentication_failed_message.show()
- self._authentication_request_active = False
-
- # Stop waiting for a response
- self._authentication_timer.stop()
- self._authentication_counter = 0
-
- self._authentication_state = auth_state
- self.authenticationStateChanged.emit()
-
- authenticationStateChanged = pyqtSignal()
-
- @pyqtProperty(int, notify = authenticationStateChanged)
- def authenticationState(self):
- return self._authentication_state
-
- @pyqtSlot()
- def requestAuthentication(self, message_id = None, action_id = "Retry"):
- if action_id == "Request" or action_id == "Retry":
- Logger.log("d", "Requestion authentication for %s due to action %s" % (self._key, action_id))
- self._authentication_failed_message.hide()
- self._not_authenticated_message.hide()
- self.setAuthenticationState(AuthState.NotAuthenticated)
- self._authentication_counter = 0
- self._authentication_requested_message.setProgress(0)
- self._authentication_id = None
- self._authentication_key = None
- self._createNetworkManager() # Re-create network manager to force re-authentication.
-
- ## Request data from the connected device.
- def _update(self):
- if self._last_response_time:
- time_since_last_response = time() - self._last_response_time
- else:
- time_since_last_response = 0
- if self._last_request_time:
- time_since_last_request = time() - self._last_request_time
- else:
- time_since_last_request = float("inf") # An irrelevantly large number of seconds
-
- # Connection is in timeout, check if we need to re-start the connection.
- # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows.
- # Re-creating the QNetworkManager seems to fix this issue.
- if self._last_response_time and self._connection_state_before_timeout:
- if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count:
- self._recreate_network_manager_count += 1
- counter = 0 # Counter to prevent possible indefinite while loop.
- # It can happen that we had a very long timeout (multiple times the recreate time).
- # In that case we should jump through the point that the next update won't be right away.
- while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time and counter < 10:
- counter += 1
- self._recreate_network_manager_count += 1
- Logger.log("d", "Timeout lasted over %.0f seconds (%.1fs), re-checking connection.", self._recreate_network_manager_time, time_since_last_response)
- self._createNetworkManager()
- return
-
- # Check if we have an connection in the first place.
- if not self._manager.networkAccessible():
- if not self._connection_state_before_timeout:
- Logger.log("d", "The network connection seems to be disabled. Going into timeout mode")
- self._connection_state_before_timeout = self._connection_state
- self.setConnectionState(ConnectionState.error)
- self._connection_message = Message(i18n_catalog.i18nc("@info:status",
- "The connection with the network was lost."),
- title = i18n_catalog.i18nc("@info:title", "Connection Status"))
- self._connection_message.show()
-
- if self._progress_message:
- self._progress_message.hide()
-
- # Check if we were uploading something. Abort if this is the case.
- # Some operating systems handle this themselves, others give weird issues.
- if self._post_reply:
- Logger.log("d", "Stopping post upload because the connection was lost.")
- self._finalizePostReply()
- return
- else:
- if not self._connection_state_before_timeout:
- self._recreate_network_manager_count = 1
-
- # Check that we aren't in a timeout state
- if self._last_response_time and self._last_request_time and not self._connection_state_before_timeout:
- if time_since_last_response > self._response_timeout_time and time_since_last_request <= self._response_timeout_time:
- # Go into timeout state.
- Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time_since_last_response)
- self._connection_state_before_timeout = self._connection_state
- self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your printer to see if it is connected."),
- title = i18n_catalog.i18nc("@info:title", "Connection Status"))
- self._connection_message.show()
-
- if self._progress_message:
- self._progress_message.hide()
-
- # Check if we were uploading something. Abort if this is the case.
- # Some operating systems handle this themselves, others give weird issues.
- if self._post_reply:
- Logger.log("d", "Stopping post upload because the connection was lost.")
- self._finalizePostReply()
- self.setConnectionState(ConnectionState.error)
- return
-
- if self._authentication_state == AuthState.NotAuthenticated:
- self._verifyAuthentication() # We don't know if we are authenticated; check if we have correct auth.
- elif self._authentication_state == AuthState.AuthenticationRequested:
- self._checkAuthentication() # We requested authentication at some point. Check if we got permission.
-
- ## Request 'general' printer data
- url = QUrl("http://" + self._address + self._api_prefix + "printer")
- printer_request = QNetworkRequest(url)
- self._manager.get(printer_request)
-
- ## Request print_job data
- url = QUrl("http://" + self._address + self._api_prefix + "print_job")
- print_job_request = QNetworkRequest(url)
- self._manager.get(print_job_request)
-
- self._last_request_time = time()
-
- def _finalizePostReply(self):
- # Indicate uploading was finished (so another file can be send)
- self._write_finished = True
-
- if self._post_reply is None:
- return
-
- try:
- try:
- self._post_reply.uploadProgress.disconnect(self._onUploadProgress)
- except TypeError:
- pass # The disconnection can fail on mac in some cases. Ignore that.
-
- try:
- self._post_reply.finished.disconnect(self._onUploadFinished)
- except TypeError:
- pass # The disconnection can fail on mac in some cases. Ignore that.
-
- self._post_reply.abort()
- self._post_reply = None
- except RuntimeError:
- self._post_reply = None # It can happen that the wrapped c++ object is already deleted.
-
- def _createNetworkManager(self):
- if self._manager:
- self._manager.finished.disconnect(self._onFinished)
- self._manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
- self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired)
-
- self._manager = QNetworkAccessManager()
- self._manager.finished.connect(self._onFinished)
- self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
- self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) # for debug purposes
-
- ## Convenience function that gets information from the received json data and converts it to the right internal
- # values / variables
- def _spliceJSONData(self):
- # Check for hotend temperatures
- for index in range(0, self._num_extruders):
- temperatures = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["temperature"]
- self._setHotendTemperature(index, temperatures["current"])
- self._updateTargetHotendTemperature(index, temperatures["target"])
- try:
- material_id = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"]
- except KeyError:
- material_id = ""
- self._setMaterialId(index, material_id)
- try:
- hotend_id = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]
- except KeyError:
- hotend_id = ""
- self._setHotendId(index, hotend_id)
-
- bed_temperatures = self._json_printer_state["bed"]["temperature"]
- self._setBedTemperature(bed_temperatures["current"])
- self._updateTargetBedTemperature(bed_temperatures["target"])
-
- head_x = self._json_printer_state["heads"][0]["position"]["x"]
- head_y = self._json_printer_state["heads"][0]["position"]["y"]
- head_z = self._json_printer_state["heads"][0]["position"]["z"]
- self._updateHeadPosition(head_x, head_y, head_z)
- self._updatePrinterState(self._json_printer_state["status"])
-
- if self._processing_preheat_requests:
- try:
- is_preheating = self._json_printer_state["bed"]["pre_heat"]["active"]
- except KeyError: #Old firmware doesn't support that.
- pass #Don't update the pre-heat remaining time.
- else:
- if is_preheating:
- try:
- remaining_preheat_time = self._json_printer_state["bed"]["pre_heat"]["remaining"]
- except KeyError: #Error in firmware. If "active" is supported, "remaining" should also be supported.
- pass #Anyway, don't update.
- else:
- #Only update if time estimate is significantly off (>5000ms).
- #Otherwise we get issues with latency causing the timer to count inconsistently.
- if abs(self._preheat_bed_timer.remainingTime() - remaining_preheat_time * 1000) > 5000:
- self._preheat_bed_timer.setInterval(remaining_preheat_time * 1000)
- self._preheat_bed_timer.start()
- self.preheatBedRemainingTimeChanged.emit()
- else: #Not pre-heating. Must've cancelled.
- if self._preheat_bed_timer.isActive():
- self._preheat_bed_timer.setInterval(0)
- self._preheat_bed_timer.stop()
- self.preheatBedRemainingTimeChanged.emit()
-
- def close(self):
- Logger.log("d", "Closing connection of printer %s with ip %s", self._key, self._address)
- self._updateJobState("")
- self.setConnectionState(ConnectionState.closed)
- if self._progress_message:
- self._progress_message.hide()
-
- # Reset authentication state
- self._authentication_requested_message.hide()
- self.setAuthenticationState(AuthState.NotAuthenticated)
- self._authentication_counter = 0
- self._authentication_timer.stop()
-
- self._authentication_requested_message.hide()
- self._authentication_failed_message.hide()
- self._authentication_succeeded_message.hide()
-
- # Reset stored material & hotend data.
- self._material_ids = [""] * self._num_extruders
- self._hotend_ids = [""] * self._num_extruders
-
- if self._error_message:
- self._error_message.hide()
-
- # Reset timeout state
- self._connection_state_before_timeout = None
- self._last_response_time = time()
- self._last_request_time = None
-
- # Stop update timers
- self._update_timer.stop()
-
- self.stopCamera()
-
- ## Request the current scene to be sent to a network-connected printer.
- #
- # \param nodes A collection of scene nodes to send. This is ignored.
- # \param file_name \type{string} A suggestion for a file name to write.
- # This is ignored.
- # \param filter_by_machine Whether to filter MIME types by machine. This
- # is ignored.
- # \param kwargs Keyword arguments.
- def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
-
- if self._printer_state not in ["idle", ""]:
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is busy. Current printer status is %s.") % self._printer_state,
- title = i18n_catalog.i18nc("@info:title", "Printer Status"))
- self._error_message.show()
- return
- elif self._authentication_state != AuthState.Authenticated:
- self._not_authenticated_message.show()
- Logger.log("d", "Attempting to perform an action without authentication for printer %s. Auth state is %s", self._key, self._authentication_state)
- return
-
- Application.getInstance().getController().setActiveStage("MonitorStage")
- self._print_finished = True
- self.writeStarted.emit(self)
- self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
-
- print_information = Application.getInstance().getPrintInformation()
- warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about.
-
- # Only check for mistakes if there is material length information.
- if print_information.materialLengths:
- # Check if PrintCores / materials are loaded at all. Any failure in these results in an Error.
- for index in range(0, self._num_extruders):
- if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
- if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "":
- Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1)
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No Printcore loaded in slot {0}".format(index + 1)),
- title = i18n_catalog.i18nc("@info:title", "Error"))
- self._error_message.show()
- return
- if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "":
- Logger.log("e", "No material loaded in slot %s, unable to start print", index + 1)
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status",
- "Unable to start a new print job. No material loaded in slot {0}".format(index + 1)),
- title = i18n_catalog.i18nc("@info:title", "Error"))
- self._error_message.show()
- return
-
- for index in range(0, self._num_extruders):
- # Check if there is enough material. Any failure in these results in a warning.
- material_length = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["length_remaining"]
- if material_length != -1 and index < len(print_information.materialLengths) and print_information.materialLengths[index] > material_length:
- Logger.log("w", "Printer reports that there is not enough material left for extruder %s. We need %s and the printer has %s", index + 1, print_information.materialLengths[index], material_length)
- warnings.append(i18n_catalog.i18nc("@label", "Not enough material for spool {0}.").format(index+1))
-
- # Check if the right cartridges are loaded. Any failure in these results in a warning.
- extruder_manager = cura.Settings.ExtruderManager.ExtruderManager.getInstance()
- if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
- variant = extruder_manager.getExtruderStack(index).findContainer({"type": "variant"})
- core_name = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]
- if variant:
- if variant.getName() != core_name:
- Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, core_name, variant.getName())
- warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}".format(variant.getName(), core_name, index + 1)))
-
- material = extruder_manager.getExtruderStack(index).findContainer({"type": "material"})
- if material:
- remote_material_guid = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"]
- if material.getMetaDataEntry("GUID") != remote_material_guid:
- Logger.log("w", "Extruder %s has a different material (%s) as Cura (%s)", index + 1,
- remote_material_guid,
- material.getMetaDataEntry("GUID"))
-
- remote_materials = UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "material", GUID = remote_material_guid, read_only = True)
- remote_material_name = "Unknown"
- if remote_materials:
- remote_material_name = remote_materials[0]["name"]
- warnings.append(i18n_catalog.i18nc("@label", "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}").format(material.getName(), remote_material_name, index + 1))
-
- try:
- is_offset_calibrated = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["offset"]["state"] == "valid"
- except KeyError: # Older versions of the API don't expose the offset property, so we must asume that all is well.
- is_offset_calibrated = True
-
- if not is_offset_calibrated:
- warnings.append(i18n_catalog.i18nc("@label", "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer.").format(index + 1))
- else:
- Logger.log("w", "There was no material usage found. No check to match used material with machine is done.")
-
- if warnings:
- text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?")
- informative_text = i18n_catalog.i18nc("@label", "There is a mismatch between the configuration or calibration of the printer and Cura. "
- "For the best result, always slice for the PrintCores and materials that are inserted in your printer.")
- detailed_text = ""
- for warning in warnings:
- detailed_text += warning + "\n"
-
- Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
- text,
- informative_text,
- detailed_text,
- buttons=QMessageBox.Yes + QMessageBox.No,
- icon=QMessageBox.Question,
- callback=self._configurationMismatchMessageCallback
- )
- return
-
- self.startPrint()
-
- def _configurationMismatchMessageCallback(self, button):
- def delayedCallback():
- if button == QMessageBox.Yes:
- self.startPrint()
- else:
- Application.getInstance().getController().setActiveStage("PrepareStage")
- # For some unknown reason Cura on OSX will hang if we do the call back code
- # immediately without first returning and leaving QML's event system.
- QTimer.singleShot(100, delayedCallback)
-
- def isConnected(self):
- return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error
-
- ## Start requesting data from printer
- def connect(self):
- # Don't allow to connect to a printer with a faulty connection state.
- # For instance when switching printers but the printer is disconnected from the network
- if self._connection_state == ConnectionState.error:
- return
-
- if self.isConnected():
- self.close() # Close previous connection
-
- self._createNetworkManager()
-
- self._last_response_time = time() # Ensure we reset the time when trying to connect (again)
-
- self.setConnectionState(ConnectionState.connecting)
- self._update() # Manually trigger the first update, as we don't want to wait a few secs before it starts.
- if not self._use_stream:
- self._updateCamera()
- Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address)
-
- ## Check if this machine was authenticated before.
- self._authentication_id = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_id", None)
- self._authentication_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_key", None)
-
- if self._authentication_id is None and self._authentication_key is None:
- Logger.log("d", "No authentication found in metadata.")
- else:
- Logger.log("d", "Loaded authentication id %s and key %s from the metadata entry for printer %s", self._authentication_id, self._getSafeAuthKey(), self._key)
-
- self._update_timer.start()
-
- ## Stop requesting data from printer
- def disconnect(self):
- Logger.log("d", "Connection with printer %s with ip %s stopped", self._key, self._address)
- self.close()
-
- newImage = pyqtSignal()
-
- @pyqtProperty(QUrl, notify = newImage)
- def cameraImage(self):
- self._camera_image_id += 1
- # There is an image provider that is called "camera". In order to ensure that the image qml object, that
- # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
- # as new (instead of relying on cached version and thus forces an update.
- temp = "image://camera/" + str(self._camera_image_id)
- return QUrl(temp, QUrl.TolerantMode)
-
- def getCameraImage(self):
- return self._camera_image
-
- def _setJobState(self, job_state):
- self._last_command = job_state
- url = QUrl("http://" + self._address + self._api_prefix + "print_job/state")
- put_request = QNetworkRequest(url)
- put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- data = "{\"target\": \"%s\"}" % job_state
- self._manager.put(put_request, data.encode())
-
- ## Convenience function to get the username from the OS.
- # The code was copied from the getpass module, as we try to use as little dependencies as possible.
- def _getUserName(self):
- for name in ("LOGNAME", "USER", "LNAME", "USERNAME"):
- user = os.environ.get(name)
- if user:
- return user
- return "Unknown User" # Couldn't find out username.
-
- def _progressMessageActionTrigger(self, message_id = None, action_id = None):
- if action_id == "Abort":
- Logger.log("d", "User aborted sending print to remote.")
- self._progress_message.hide()
- self._compressing_print = False
- self._write_finished = True # post_reply does not always exist, so make sure we unblock writing
- if self._post_reply:
- self._finalizePostReply()
- Application.getInstance().getController().setActiveStage("PrepareStage")
-
- ## Attempt to start a new print.
- # This function can fail to actually start a print due to not being authenticated or another print already
- # being in progress.
- def startPrint(self):
-
- # Check if we're already writing
- if not self._write_finished:
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status",
- "Sending new jobs (temporarily) blocked, still sending the previous print job."))
- self._error_message.show()
- return
-
- # Indicate we're starting a new write action, is set back to True at the end of this method
- self._write_finished = False
-
- try:
- self._send_gcode_start = time()
- self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1, i18n_catalog.i18nc("@info:title", "Sending Data"))
- self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
- self._progress_message.actionTriggered.connect(self._progressMessageActionTrigger)
- self._progress_message.show()
- Logger.log("d", "Started sending g-code to remote printer.")
- self._compressing_print = True
- ## Mash the data into single string
-
- max_chars_per_line = 1024 * 1024 / 4 # 1 / 4 MB
-
- byte_array_file_data = b""
- batched_line = ""
-
- def _compress_data_and_notify_qt(data_to_append):
- compressed_data = gzip.compress(data_to_append.encode("utf-8"))
- self._progress_message.setProgress(-1) # Tickle the message so that it's clear that it's still being used.
- QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
- # Pretend that this is a response, as zipping might take a bit of time.
- self._last_response_time = time()
- return compressed_data
-
- for line in self._gcode:
- if not self._compressing_print:
- self._progress_message.hide()
- return # Stop trying to zip, abort was called.
-
- if self._use_gzip:
- batched_line += line
- # if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
- # Compressing line by line in this case is extremely slow, so we need to batch them.
- if len(batched_line) < max_chars_per_line:
- continue
-
- byte_array_file_data += _compress_data_and_notify_qt(batched_line)
- batched_line = ""
- else:
- byte_array_file_data += line.encode("utf-8")
-
- # don't miss the last batch if it's there
- if self._use_gzip:
- if batched_line:
- byte_array_file_data += _compress_data_and_notify_qt(batched_line)
-
- if self._use_gzip:
- file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
- else:
- file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName
-
- self._compressing_print = False
- ## Create multi_part request
- self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
-
- ## Create part (to be placed inside multipart)
- self._post_part = QHttpPart()
- self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
- "form-data; name=\"file\"; filename=\"%s\"" % file_name)
- self._post_part.setBody(byte_array_file_data)
- self._post_multi_part.append(self._post_part)
-
- url = QUrl("http://" + self._address + self._api_prefix + "print_job")
-
- ## Create the QT request
- self._post_request = QNetworkRequest(url)
-
- ## Post request + data
- self._post_reply = self._manager.post(self._post_request, self._post_multi_part)
- self._post_reply.uploadProgress.connect(self._onUploadProgress)
- self._post_reply.finished.connect(self._onUploadFinished) # used to unblock new write actions
-
- except IOError:
- self._progress_message.hide()
- self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data to printer. Is another job still active?"),
- title = i18n_catalog.i18nc("@info:title", "Warning"))
- self._error_message.show()
- except Exception as e:
- self._progress_message.hide()
- Logger.log("e", "An exception occurred in network connection: %s" % str(e))
-
- ## Verify if we are authenticated to make requests.
- def _verifyAuthentication(self):
- url = QUrl("http://" + self._address + self._api_prefix + "auth/verify")
- request = QNetworkRequest(url)
- self._manager.get(request)
-
- ## Check if the authentication request was allowed by the printer.
- def _checkAuthentication(self):
- Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
- self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id))))
-
- ## Request a authentication key from the printer so we can be authenticated
- def _requestAuthentication(self):
- url = QUrl("http://" + self._address + self._api_prefix + "auth/request")
- request = QNetworkRequest(url)
- request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- self._authentication_key = None
- self._authentication_id = None
- self._manager.post(request, json.dumps({"application": "Cura-" + Application.getInstance().getVersion(), "user": self._getUserName()}).encode())
- self.setAuthenticationState(AuthState.AuthenticationRequested)
-
- ## Send all material profiles to the printer.
- def sendMaterialProfiles(self):
- registry = UM.Settings.ContainerRegistry.ContainerRegistry.getInstance()
- for container in registry.findInstanceContainers(type = "material"):
- try:
- xml_data = container.serialize()
- if xml_data == "" or xml_data is None:
- continue
-
- names = ContainerManager.getInstance().getLinkedMaterials(container.getId())
- if names:
- # There are other materials that share this GUID.
- if not registry.isReadOnly(container.getId()):
- continue # If it's not readonly, it's created by user, so skip it.
-
- material_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
-
- material_part = QHttpPart()
- file_name = "none.xml"
- material_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\";filename=\"%s\"" % file_name)
- material_part.setBody(xml_data.encode())
- material_multi_part.append(material_part)
- url = QUrl("http://" + self._address + self._api_prefix + "materials")
- material_post_request = QNetworkRequest(url)
- reply = self._manager.post(material_post_request, material_multi_part)
-
- # Keep reference to material_part, material_multi_part and reply so the garbage collector won't touch them.
- self._material_post_objects[id(reply)] = (material_part, material_multi_part, reply)
- except NotImplementedError:
- # If the material container is not the most "generic" one it can't be serialized an will raise a
- # NotImplementedError. We can simply ignore these.
- pass
-
- ## Handler for all requests that have finished.
- def _onFinished(self, reply):
- if reply.error() == QNetworkReply.TimeoutError:
- Logger.log("w", "Received a timeout on a request to the printer")
- self._connection_state_before_timeout = self._connection_state
- # Check if we were uploading something. Abort if this is the case.
- # Some operating systems handle this themselves, others give weird issues.
- if self._post_reply:
- self._finalizePostReply()
- Logger.log("d", "Uploading of print failed after %s", time() - self._send_gcode_start)
- self._progress_message.hide()
-
- self.setConnectionState(ConnectionState.error)
- return
-
- if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError: # There was a timeout, but we got a correct answer again.
- Logger.log("d", "We got a response (%s) from the server after %0.1f of silence. Going back to previous state %s", reply.url().toString(), time() - self._last_response_time, self._connection_state_before_timeout)
-
- # Camera was active before timeout. Start it again
- if self._camera_active:
- self._startCamera()
-
- self.setConnectionState(self._connection_state_before_timeout)
- self._connection_state_before_timeout = None
-
- if reply.error() == QNetworkReply.NoError:
- self._last_response_time = time()
-
- status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
- if not status_code:
- if self._connection_state != ConnectionState.error:
- Logger.log("d", "A reply from %s did not have status code.", reply.url().toString())
- # Received no or empty reply
- return
- reply_url = reply.url().toString()
-
- if reply.operation() == QNetworkAccessManager.GetOperation:
- # "printer" is also in "printers", therefore _api_prefix is added.
- if self._api_prefix + "printer" in reply_url: # Status update from printer.
- if status_code == 200:
- if self._connection_state == ConnectionState.connecting:
- self.setConnectionState(ConnectionState.connected)
- try:
- self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid printer state message: Not valid JSON.")
- return
- self._spliceJSONData()
-
- # Hide connection error message if the connection was restored
- if self._connection_message:
- self._connection_message.hide()
- self._connection_message = None
- else:
- Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code)
- pass # TODO: Handle errors
- elif self._api_prefix + "print_job" in reply_url: # Status update from print_job:
- if status_code == 200:
- try:
- json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
- return
- progress = json_data["progress"]
- ## If progress is 0 add a bit so another print can't be sent.
- if progress == 0:
- progress += 0.001
- elif progress == 1:
- self._print_finished = True
- else:
- self._print_finished = False
- self.setProgress(progress * 100)
-
- state = json_data["state"]
-
- # There is a short period after aborting or finishing a print where the printer
- # reports a "none" state (but the printer is not ready to receive a print)
- # If this happens before the print has reached progress == 1, the print has
- # been aborted.
- if state == "none" or state == "":
- if self._last_command == "abort":
- self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Aborting print..."))
- state = "error"
- else:
- state = "printing"
- if state == "wait_cleanup" and self._last_command == "abort":
- # Keep showing the "aborted" error state until after the buildplate has been cleaned
- self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Print aborted. Please check the printer"))
- state = "error"
-
- # NB/TODO: the following two states are intentionally added for future proofing the i18n strings
- # but are currently non-functional
- if state == "!pausing":
- self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Pausing print..."))
- if state == "!resuming":
- self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Resuming print..."))
-
- self._updateJobState(state)
- self.setTimeElapsed(json_data["time_elapsed"])
- self.setTimeTotal(json_data["time_total"])
- self.setJobName(json_data["name"])
- elif status_code == 404:
- self.setProgress(0) # No print job found, so there can't be progress or other data.
- self._updateJobState("")
- self.setErrorText("")
- self.setTimeElapsed(0)
- self.setTimeTotal(0)
- self.setJobName("")
- else:
- Logger.log("w", "We got an unexpected status (%s) while requesting print job state", status_code)
- elif "snapshot" in reply_url: # Status update from image:
- if status_code == 200:
- self._camera_image.loadFromData(reply.readAll())
- self.newImage.emit()
- elif "auth/verify" in reply_url: # Answer when requesting authentication
- if status_code == 401:
- if self._authentication_state != AuthState.AuthenticationRequested:
- # Only request a new authentication when we have not already done so.
- Logger.log("i", "Not authenticated (Current auth state is %s). Attempting to request authentication for printer %s", self._authentication_state, self._key )
- self._requestAuthentication()
- elif status_code == 403:
- # If we already had an auth (eg; didn't request one), we only need a single 403 to see it as denied.
- if self._authentication_state != AuthState.AuthenticationRequested:
- Logger.log("d", "While trying to verify the authentication state, we got a forbidden response. Our own auth state was %s", self._authentication_state)
- self.setAuthenticationState(AuthState.AuthenticationDenied)
- elif status_code == 200:
- self.setAuthenticationState(AuthState.Authenticated)
- global_container_stack = Application.getInstance().getGlobalContainerStack()
-
- ## Save authentication details.
- if global_container_stack:
- if "network_authentication_key" in global_container_stack.getMetaData():
- global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
- else:
- global_container_stack.addMetaDataEntry("network_authentication_key", self._authentication_key)
- if "network_authentication_id" in global_container_stack.getMetaData():
- global_container_stack.setMetaDataEntry("network_authentication_id", self._authentication_id)
- else:
- global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
- Logger.log("i", "Authentication succeeded for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
- Application.getInstance().saveStack(global_container_stack) # Force save so we are sure the data is not lost.
- else:
- Logger.log("w", "Unable to save authentication for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
-
- # Request 'system' printer data once, when we know we have authentication, so we know we can set the system time.
- url = QUrl("http://" + self._address + self._api_prefix + "system")
- system_data_request = QNetworkRequest(url)
- self._manager.get(system_data_request)
-
- else: # Got a response that we didn't expect, so something went wrong.
- Logger.log("e", "While trying to authenticate, we got an unexpected response: %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))
- self.setAuthenticationState(AuthState.NotAuthenticated)
-
- elif "auth/check" in reply_url: # Check if we are authenticated (user can refuse this!)
- try:
- data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid authentication check from printer: Not valid JSON.")
- return
- if data.get("message", "") == "authorized":
- Logger.log("i", "Authentication was approved")
- self._verifyAuthentication() # Ensure that the verification is really used and correct.
- elif data.get("message", "") == "unauthorized":
- Logger.log("i", "Authentication was denied.")
- self.setAuthenticationState(AuthState.AuthenticationDenied)
- else:
- pass
-
- elif self._api_prefix + "system" in reply_url:
- # Check if the printer has time, and if this has a valid system time.
- try:
- data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid authentication request reply from printer: Not valid JSON.")
- return
- if "time" in data and "utc" in data["time"]:
- try:
- printer_time = gmtime(float(data["time"]["utc"]))
- Logger.log("i", "Printer has system time of: %s", str(printer_time))
- except ValueError:
- printer_time = None
- if printer_time is not None and printer_time.tm_year < 1990:
- # The system time is not valid, sync our current system time to it, so we at least have some reasonable time in the printer.
- Logger.log("w", "Printer system time invalid, setting system time")
- url = QUrl("http://" + self._address + self._api_prefix + "system/time/utc")
- put_request = QNetworkRequest(url)
- put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- self._manager.put(put_request, str(time()).encode())
-
- elif reply.operation() == QNetworkAccessManager.PostOperation:
- if "/auth/request" in reply_url:
- # We got a response to requesting authentication.
- try:
- data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid authentication request reply from printer: Not valid JSON.")
- return
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack: # Remove any old data.
- Logger.log("d", "Removing old network authentication data for %s as a new one was requested.", self._key)
- global_container_stack.removeMetaDataEntry("network_authentication_key")
- global_container_stack.removeMetaDataEntry("network_authentication_id")
- Application.getInstance().saveStack(global_container_stack) # Force saving so we don't keep wrong auth data.
-
- self._authentication_key = data["key"]
- self._authentication_id = data["id"]
- Logger.log("i", "Got a new authentication ID (%s) and KEY (%s). Waiting for authorization.", self._authentication_id, self._getSafeAuthKey())
-
- # Check if the authentication is accepted.
- self._checkAuthentication()
- elif "materials" in reply_url:
- # Remove cached post request items.
- del self._material_post_objects[id(reply)]
- elif "print_job" in reply_url:
- self._onUploadFinished() # Make sure the upload flag is reset as reply.finished is not always triggered
- try:
- reply.uploadProgress.disconnect(self._onUploadProgress)
- except:
- pass
- try:
- reply.finished.disconnect(self._onUploadFinished)
- except:
- pass
- Logger.log("d", "Uploading of print succeeded after %s", time() - self._send_gcode_start)
- # Only reset the _post_reply if it was the same one.
- if reply == self._post_reply:
- self._post_reply = None
- self._progress_message.hide()
-
- elif reply.operation() == QNetworkAccessManager.PutOperation:
- if "printer/bed/pre_heat" in reply_url: #Pre-heat command has completed. Re-enable syncing pre-heating.
- self._processing_preheat_requests = True
- if status_code in [200, 201, 202, 204]:
- pass # Request was successful!
- else:
- Logger.log("d", "Something went wrong when trying to update data of API (%s). Message: %s Statuscode: %s", reply_url, reply.readAll(), status_code)
- else:
- Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation())
-
- def _onStreamDownloadProgress(self, bytes_received, bytes_total):
- # An MJPG stream is (for our purpose) a stream of concatenated JPG images.
- # JPG images start with the marker 0xFFD8, and end with 0xFFD9
- if self._image_reply is None:
- return
- self._stream_buffer += self._image_reply.readAll()
-
- if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
- Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
- self._stopCamera() # resets stream buffer and start index
- self._startCamera()
- return
-
- if self._stream_buffer_start_index == -1:
- self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
- stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
- # If this happens to be more than a single frame, then so be it; the JPG decoder will
- # ignore the extra data. We do it like this in order not to get a buildup of frames
-
- if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
- jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
- self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
- self._stream_buffer_start_index = -1
-
- self._camera_image.loadFromData(jpg_data)
- self.newImage.emit()
-
- def _onUploadProgress(self, bytes_sent, bytes_total):
- if bytes_total > 0:
- new_progress = bytes_sent / bytes_total * 100
- # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
- # timeout responses if this happens.
- self._last_response_time = time()
- if new_progress > self._progress_message.getProgress():
- self._progress_message.show() # Ensure that the message is visible.
- self._progress_message.setProgress(bytes_sent / bytes_total * 100)
- else:
- self._progress_message.setProgress(0)
- self._progress_message.hide()
-
- ## Allow new write actions (uploads) again when uploading is finished.
- def _onUploadFinished(self):
- self._write_finished = True
-
- ## Let the user decide if the hotends and/or material should be synced with the printer
- def materialHotendChangedMessage(self, callback):
- Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"),
- i18n_catalog.i18nc("@label",
- "Would you like to use your current printer configuration in Cura?"),
- i18n_catalog.i18nc("@label",
- "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."),
- buttons=QMessageBox.Yes + QMessageBox.No,
- icon=QMessageBox.Question,
- callback=callback
- )
-
- ## Convenience function to "blur" out all but the last 5 characters of the auth key.
- # This can be used to debug print the key, without it compromising the security.
- def _getSafeAuthKey(self):
- if self._authentication_key is not None:
- result = self._authentication_key[-5:]
- result = "********" + result
- return result
- return self._authentication_key
diff --git a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py
deleted file mode 100644
index 0d3ed52f03..0000000000
--- a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py
+++ /dev/null
@@ -1,361 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.
-
-import time
-import json
-from queue import Queue
-from threading import Event, Thread
-
-from PyQt5.QtCore import QObject, pyqtSlot
-from PyQt5.QtCore import QUrl
-from PyQt5.QtGui import QDesktopServices
-from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
-from UM.Application import Application
-from UM.Logger import Logger
-from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
-from UM.Preferences import Preferences
-from UM.Signal import Signal, signalemitter
-from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo # type: ignore
-
-from . import NetworkPrinterOutputDevice, NetworkClusterPrinterOutputDevice
-
-
-## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
-# Zero-Conf is used to detect printers, which are saved in a dict.
-# If we discover a printer that has the same key as the active machine instance a connection is made.
-@signalemitter
-class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin):
- def __init__(self):
- super().__init__()
- self._zero_conf = None
- self._browser = None
- self._printers = {}
- self._cluster_printers_seen = {} # do not forget a cluster printer when we have seen one, to not 'downgrade' from Connect to legacy printer
-
- self._api_version = "1"
- self._api_prefix = "/api/v" + self._api_version + "/"
- self._cluster_api_version = "1"
- self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
-
- self._network_manager = QNetworkAccessManager()
- self._network_manager.finished.connect(self._onNetworkRequestFinished)
-
- # List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces
- # authentication requests.
- self._old_printers = []
- self._excluded_addresses = ["127.0.0.1"] # Adding a list of not allowed IP addresses. At this moment, just localhost
-
- # Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
- self.addPrinterSignal.connect(self.addPrinter)
- self.removePrinterSignal.connect(self.removePrinter)
- Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
-
- # Get list of manual printers from preferences
- self._preferences = Preferences.getInstance()
- self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames
- self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",")
-
- self._network_requests_buffer = {} # store api responses until data is complete
-
- # The zeroconf service changed requests are handled in a separate thread, so we can re-schedule the requests
- # which fail to get detailed service info.
- # Any new or re-scheduled requests will be appended to the request queue, and the handling thread will pick
- # them up and process them.
- self._service_changed_request_queue = Queue()
- self._service_changed_request_event = Event()
- self._service_changed_request_thread = Thread(target = self._handleOnServiceChangedRequests,
- daemon = True)
- self._service_changed_request_thread.start()
-
- addPrinterSignal = Signal()
- removePrinterSignal = Signal()
- printerListChanged = Signal()
-
- ## Start looking for devices on network.
- def start(self):
- self.startDiscovery()
-
- def startDiscovery(self):
- self.stop()
- if self._browser:
- self._browser.cancel()
- self._browser = None
- self._old_printers = [printer_name for printer_name in self._printers]
- self._printers = {}
- self.printerListChanged.emit()
- # After network switching, one must make a new instance of Zeroconf
- # On windows, the instance creation is very fast (unnoticable). Other platforms?
- self._zero_conf = Zeroconf()
- self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._appendServiceChangedRequest])
-
- # Look for manual instances from preference
- for address in self._manual_instances:
- if address:
- self.addManualPrinter(address)
-
- def addManualPrinter(self, address):
- if address not in self._manual_instances:
- self._manual_instances.append(address)
- self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
-
- instance_name = "manual:%s" % address
- properties = {
- b"name": address.encode("utf-8"),
- b"address": address.encode("utf-8"),
- b"manual": b"true",
- b"incomplete": b"true"
- }
-
- if instance_name not in self._printers:
- # Add a preliminary printer instance
- self.addPrinter(instance_name, address, properties)
-
- self.checkManualPrinter(address)
- self.checkClusterPrinter(address)
-
- def removeManualPrinter(self, key, address = None):
- if key in self._printers:
- if not address:
- address = self._printers[key].ipAddress
- self.removePrinter(key)
-
- if address in self._manual_instances:
- self._manual_instances.remove(address)
- self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
-
- def checkManualPrinter(self, address):
- # Check if a printer exists at this address
- # If a printer responds, it will replace the preliminary printer created above
- # origin=manual is for tracking back the origin of the call
- url = QUrl("http://" + address + self._api_prefix + "system?origin=manual_name")
- name_request = QNetworkRequest(url)
- self._network_manager.get(name_request)
-
- def checkClusterPrinter(self, address):
- cluster_url = QUrl("http://" + address + self._cluster_api_prefix + "printers/?origin=check_cluster")
- cluster_request = QNetworkRequest(cluster_url)
- self._network_manager.get(cluster_request)
-
- ## Handler for all requests that have finished.
- def _onNetworkRequestFinished(self, reply):
- reply_url = reply.url().toString()
- status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
-
- if reply.operation() == QNetworkAccessManager.GetOperation:
- address = reply.url().host()
- if "origin=manual_name" in reply_url: # Name returned from printer.
- if status_code == 200:
-
- try:
- system_info = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.JSONDecodeError:
- Logger.log("e", "Printer returned invalid JSON.")
- return
- except UnicodeDecodeError:
- Logger.log("e", "Printer returned incorrect UTF-8.")
- return
-
- if address not in self._network_requests_buffer:
- self._network_requests_buffer[address] = {}
- self._network_requests_buffer[address]["system"] = system_info
- elif "origin=check_cluster" in reply_url:
- if address not in self._network_requests_buffer:
- self._network_requests_buffer[address] = {}
- if status_code == 200:
- # We know it's a cluster printer
- Logger.log("d", "Cluster printer detected: [%s]", reply.url())
-
- try:
- cluster_printers_list = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.JSONDecodeError:
- Logger.log("e", "Printer returned invalid JSON.")
- return
- except UnicodeDecodeError:
- Logger.log("e", "Printer returned incorrect UTF-8.")
- return
-
- self._network_requests_buffer[address]["cluster"] = True
- self._network_requests_buffer[address]["cluster_size"] = len(cluster_printers_list)
- else:
- Logger.log("d", "This url is not from a cluster printer: [%s]", reply.url())
- self._network_requests_buffer[address]["cluster"] = False
-
- # Both the system call and cluster call are finished
- if (address in self._network_requests_buffer and
- "system" in self._network_requests_buffer[address] and
- "cluster" in self._network_requests_buffer[address]):
-
- instance_name = "manual:%s" % address
- system_info = self._network_requests_buffer[address]["system"]
- machine = "unknown"
- if "variant" in system_info:
- variant = system_info["variant"]
- if variant == "Ultimaker 3":
- machine = "9066"
- elif variant == "Ultimaker 3 Extended":
- machine = "9511"
-
- properties = {
- b"name": system_info["name"].encode("utf-8"),
- b"address": address.encode("utf-8"),
- b"firmware_version": system_info["firmware"].encode("utf-8"),
- b"manual": b"true",
- b"machine": machine.encode("utf-8")
- }
-
- if self._network_requests_buffer[address]["cluster"]:
- properties[b"cluster_size"] = self._network_requests_buffer[address]["cluster_size"]
-
- if instance_name in self._printers:
- # Only replace the printer if it is still in the list of (manual) printers
- self.removePrinter(instance_name)
- self.addPrinter(instance_name, address, properties)
-
- del self._network_requests_buffer[address]
-
- ## Stop looking for devices on network.
- def stop(self):
- if self._zero_conf is not None:
- Logger.log("d", "zeroconf close...")
- self._zero_conf.close()
-
- def getPrinters(self):
- return self._printers
-
- def reCheckConnections(self):
- active_machine = Application.getInstance().getGlobalContainerStack()
- if not active_machine:
- return
-
- for key in self._printers:
- if key == active_machine.getMetaDataEntry("um_network_key"):
- if not self._printers[key].isConnected():
- Logger.log("d", "Connecting [%s]..." % key)
- self._printers[key].connect()
- self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
- else:
- if self._printers[key].isConnected():
- Logger.log("d", "Closing connection [%s]..." % key)
- self._printers[key].close()
- self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
-
- ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
- def addPrinter(self, name, address, properties):
- cluster_size = int(properties.get(b"cluster_size", -1))
- if cluster_size >= 0:
- printer = NetworkClusterPrinterOutputDevice.NetworkClusterPrinterOutputDevice(
- name, address, properties, self._api_prefix)
- else:
- printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties, self._api_prefix)
- self._printers[printer.getKey()] = printer
- self._cluster_printers_seen[printer.getKey()] = name # Cluster printers that may be temporary unreachable or is rebooted keep being stored here
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"):
- if printer.getKey() not in self._old_printers: # Was the printer already connected, but a re-scan forced?
- Logger.log("d", "addPrinter, connecting [%s]..." % printer.getKey())
- self._printers[printer.getKey()].connect()
- printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
- self.printerListChanged.emit()
-
- def removePrinter(self, name):
- printer = self._printers.pop(name, None)
- if printer:
- if printer.isConnected():
- printer.disconnect()
- printer.connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
- Logger.log("d", "removePrinter, disconnecting [%s]..." % name)
- self.printerListChanged.emit()
-
- ## Handler for when the connection state of one of the detected printers changes
- def _onPrinterConnectionStateChanged(self, key):
- if key not in self._printers:
- return
- if self._printers[key].isConnected():
- self.getOutputDeviceManager().addOutputDevice(self._printers[key])
- else:
- self.getOutputDeviceManager().removeOutputDevice(key)
-
- ## Handler for zeroConf detection.
- # Return True or False indicating if the process succeeded.
- def _onServiceChanged(self, zeroconf, service_type, name, state_change):
- if state_change == ServiceStateChange.Added:
- Logger.log("d", "Bonjour service added: %s" % name)
-
- # First try getting info from zeroconf cache
- info = ServiceInfo(service_type, name, properties = {})
- for record in zeroconf.cache.entries_with_name(name.lower()):
- info.update_record(zeroconf, time.time(), record)
-
- for record in zeroconf.cache.entries_with_name(info.server):
- info.update_record(zeroconf, time.time(), record)
- if info.address:
- break
-
- # Request more data if info is not complete
- if not info.address:
- Logger.log("d", "Trying to get address of %s", name)
- info = zeroconf.get_service_info(service_type, name)
-
- if info:
- type_of_device = info.properties.get(b"type", None)
- if type_of_device:
- if type_of_device == b"printer":
- address = '.'.join(map(lambda n: str(n), info.address))
- if address in self._excluded_addresses:
- Logger.log("d", "The IP address %s of the printer \'%s\' is not correct. Trying to reconnect.", address, name)
- return False # When getting the localhost IP, then try to reconnect
- self.addPrinterSignal.emit(str(name), address, info.properties)
- else:
- Logger.log("w", "The type of the found device is '%s', not 'printer'! Ignoring.." % type_of_device )
- else:
- Logger.log("w", "Could not get information about %s" % name)
- return False
-
- elif state_change == ServiceStateChange.Removed:
- Logger.log("d", "Bonjour service removed: %s" % name)
- self.removePrinterSignal.emit(str(name))
-
- return True
-
- ## Appends a service changed request so later the handling thread will pick it up and processes it.
- def _appendServiceChangedRequest(self, zeroconf, service_type, name, state_change):
- # append the request and set the event so the event handling thread can pick it up
- item = (zeroconf, service_type, name, state_change)
- self._service_changed_request_queue.put(item)
- self._service_changed_request_event.set()
-
- def _handleOnServiceChangedRequests(self):
- while True:
- # wait for the event to be set
- self._service_changed_request_event.wait(timeout = 5.0)
- # stop if the application is shutting down
- if Application.getInstance().isShuttingDown():
- return
-
- self._service_changed_request_event.clear()
-
- # handle all pending requests
- reschedule_requests = [] # a list of requests that have failed so later they will get re-scheduled
- while not self._service_changed_request_queue.empty():
- request = self._service_changed_request_queue.get()
- zeroconf, service_type, name, state_change = request
- try:
- result = self._onServiceChanged(zeroconf, service_type, name, state_change)
- if not result:
- reschedule_requests.append(request)
- except Exception:
- Logger.logException("e", "Failed to get service info for [%s] [%s], the request will be rescheduled",
- service_type, name)
- reschedule_requests.append(request)
-
- # re-schedule the failed requests if any
- if reschedule_requests:
- for request in reschedule_requests:
- self._service_changed_request_queue.put(request)
-
- @pyqtSlot()
- def openControlPanel(self):
- Logger.log("d", "Opening print jobs web UI...")
- selected_device = self.getOutputDeviceManager().getActiveDevice()
- if isinstance(selected_device, NetworkClusterPrinterOutputDevice.NetworkClusterPrinterOutputDevice):
- QDesktopServices.openUrl(QUrl(selected_device.getPrintJobsUrl()))
diff --git a/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml b/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
index 03ff4542e1..267516091b 100644
--- a/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
+++ b/plugins/UM3NetworkPrinting/PrintCoreConfiguration.qml
@@ -10,12 +10,12 @@ Item
id: extruderInfo
property var printCoreConfiguration
- width: Math.floor(parent.width / 2)
+ width: Math.round(parent.width / 2)
height: childrenRect.height
Label
{
id: materialLabel
- text: printCoreConfiguration.material.material + " (" + printCoreConfiguration.material.color + ")"
+ text: printCoreConfiguration.activeMaterial != null ? printCoreConfiguration.activeMaterial.name : ""
elide: Text.ElideRight
width: parent.width
font: UM.Theme.getFont("very_small")
@@ -23,7 +23,7 @@ Item
Label
{
id: printCoreLabel
- text: printCoreConfiguration.print_core_id
+ text: printCoreConfiguration.hotendID
anchors.top: materialLabel.bottom
elide: Text.ElideRight
width: parent.width
diff --git a/plugins/UM3NetworkPrinting/PrintWindow.qml b/plugins/UM3NetworkPrinting/PrintWindow.qml
index 7afe174da2..d84b0f30ec 100644
--- a/plugins/UM3NetworkPrinting/PrintWindow.qml
+++ b/plugins/UM3NetworkPrinting/PrintWindow.qml
@@ -20,8 +20,25 @@ UM.Dialog
visible: true
modality: Qt.ApplicationModal
+ onVisibleChanged:
+ {
+ if(visible)
+ {
+ resetPrintersModel()
+ }
+ }
+ title: catalog.i18nc("@title:window", "Print over network")
- title: catalog.i18nc("@title:window","Print over network")
+ property var printersModel: ListModel{}
+ function resetPrintersModel() {
+ printersModel.clear()
+ printersModel.append({ name: "Automatic", key: ""})
+
+ for (var index in OutputDevice.printers)
+ {
+ printersModel.append({name: OutputDevice.printers[index].name, key: OutputDevice.printers[index].key})
+ }
+ }
Column
{
@@ -31,8 +48,7 @@ UM.Dialog
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.rightMargin: UM.Theme.getSize("default_margin").width
- height: 50 * screenScaleFactor
-
+ height: 50 * screenScaleFactord
Label
{
id: manualPrinterSelectionLabel
@@ -42,7 +58,7 @@ UM.Dialog
topMargin: UM.Theme.getSize("default_margin").height
right: parent.right
}
- text: "Printer selection"
+ text: catalog.i18nc("@label", "Printer selection")
wrapMode: Text.Wrap
height: 20 * screenScaleFactor
}
@@ -50,18 +66,12 @@ UM.Dialog
ComboBox
{
id: printerSelectionCombobox
- model: OutputDevice.printers
- textRole: "friendly_name"
+ model: base.printersModel
+ textRole: "name"
width: parent.width
height: 40 * screenScaleFactor
Behavior on height { NumberAnimation { duration: 100 } }
-
- onActivated:
- {
- var printerData = OutputDevice.printers[index];
- OutputDevice.selectPrinter(printerData.unique_name, printerData.friendly_name);
- }
}
SystemPalette
@@ -79,8 +89,6 @@ UM.Dialog
enabled: true
onClicked: {
base.visible = false;
- // reset to defaults
- OutputDevice.selectAutomaticPrinter()
printerSelectionCombobox.currentIndex = 0
}
}
@@ -93,9 +101,8 @@ UM.Dialog
enabled: true
onClicked: {
base.visible = false;
- OutputDevice.sendPrintJob();
+ OutputDevice.sendPrintJob(printerSelectionCombobox.model.get(printerSelectionCombobox.currentIndex).key)
// reset to defaults
- OutputDevice.selectAutomaticPrinter()
printerSelectionCombobox.currentIndex = 0
}
}
diff --git a/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml b/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
index c253ebae89..54a34fae46 100644
--- a/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
+++ b/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml
@@ -22,16 +22,16 @@ Rectangle
{
return "";
}
- if (printJob.time_total === 0)
+ if (printJob.timeTotal === 0)
{
return "";
}
- return Math.min(100, Math.round(printJob.time_elapsed / printJob.time_total * 100)) + "%";
+ return Math.min(100, Math.round(printJob.timeElapsed / printJob.timeTotal * 100)) + "%";
}
function printerStatusText(printer)
{
- switch (printer.status)
+ switch (printer.state)
{
case "pre_print":
return catalog.i18nc("@label", "Preparing to print")
@@ -49,31 +49,23 @@ Rectangle
}
id: printerDelegate
- property var printer
+
+ property var printer: null
+ property var printJob: printer != null ? printer.activePrintJob: null
border.width: UM.Theme.getSize("default_lining").width
border.color: mouse.containsMouse ? emphasisColor : lineColor
z: mouse.containsMouse ? 1 : 0 // Push this item up a bit on mouse over to ensure that the highlighted bottom border is visible.
- property var printJob:
- {
- if (printer.reserved_by != null)
- {
- // Look in another list.
- return OutputDevice.printJobsByUUID[printer.reserved_by]
- }
- return OutputDevice.printJobsByPrinterUUID[printer.uuid]
- }
-
MouseArea
{
id: mouse
anchors.fill:parent
- onClicked: OutputDevice.selectPrinter(printer.unique_name, printer.friendly_name)
+ onClicked: OutputDevice.setActivePrinter(printer)
hoverEnabled: true;
// Only clickable if no printer is selected
- enabled: OutputDevice.selectedPrinterName == "" && printer.status !== "unreachable"
+ enabled: OutputDevice.activePrinter == null && printer.state !== "unreachable"
}
Row
@@ -86,7 +78,7 @@ Rectangle
Rectangle
{
- width: Math.floor(parent.width / 3)
+ width: Math.round(parent.width / 3)
height: parent.height
Label // Print job name
@@ -122,7 +114,7 @@ Rectangle
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
- text: printJob != null ? getPrettyTime(printJob.time_total) : ""
+ text: printJob != null ? getPrettyTime(printJob.timeTotal) : ""
opacity: 0.65
font: UM.Theme.getFont("default")
elide: Text.ElideRight
@@ -131,7 +123,7 @@ Rectangle
Rectangle
{
- width: Math.floor(parent.width / 3 * 2)
+ width: Math.round(parent.width / 3 * 2)
height: parent.height
Label // Friendly machine name
@@ -139,8 +131,8 @@ Rectangle
id: printerNameLabel
anchors.top: parent.top
anchors.left: parent.left
- width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width)
- text: printer.friendly_name
+ width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width)
+ text: printer.name
font: UM.Theme.getFont("default_bold")
elide: Text.ElideRight
}
@@ -149,8 +141,8 @@ Rectangle
{
id: printerTypeLabel
anchors.top: printerNameLabel.bottom
- width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
- text: printer.machine_variant
+ width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
+ text: printer.type
anchors.left: parent.left
elide: Text.ElideRight
font: UM.Theme.getFont("very_small")
@@ -166,7 +158,7 @@ Rectangle
anchors.right: printProgressArea.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
color: emphasisColor
- opacity: printer != null && printer.status === "unreachable" ? 0.3 : 1
+ opacity: printer != null && printer.state === "unreachable" ? 0.3 : 1
Image
{
@@ -183,7 +175,7 @@ Rectangle
id: extruderInfo
anchors.bottom: parent.bottom
- width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
+ width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
@@ -191,8 +183,8 @@ Rectangle
PrintCoreConfiguration
{
id: leftExtruderInfo
- width: Math.floor((parent.width - extruderSeperator.width) / 2)
- printCoreConfiguration: printer.configuration[0]
+ width: Math.round((parent.width - extruderSeperator.width) / 2)
+ printCoreConfiguration: printer.extruders[0]
}
Rectangle
@@ -206,8 +198,8 @@ Rectangle
PrintCoreConfiguration
{
id: rightExtruderInfo
- width: Math.floor((parent.width - extruderSeperator.width) / 2)
- printCoreConfiguration: printer.configuration[1]
+ width: Math.round((parent.width - extruderSeperator.width) / 2)
+ printCoreConfiguration: printer.extruders[1]
}
}
@@ -217,7 +209,7 @@ Rectangle
anchors.right: parent.right
anchors.top: parent.top
height: showExtended ? parent.height: printProgressTitleBar.height
- width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
+ width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
border.width: UM.Theme.getSize("default_lining").width
border.color: lineColor
radius: cornerRadius
@@ -225,9 +217,9 @@ Rectangle
if(printJob != null)
{
var extendStates = ["sent_to_printer", "wait_for_configuration", "printing", "pre_print", "post_print", "wait_cleanup", "queued"];
- return extendStates.indexOf(printJob.status) !== -1;
+ return extendStates.indexOf(printJob.state) !== -1;
}
- return !printer.enabled;
+ return printer.state == "disabled"
}
Item // Status and Percent
@@ -235,7 +227,7 @@ Rectangle
id: printProgressTitleBar
property var showPercent: {
- return printJob != null && (["printing", "post_print", "pre_print", "sent_to_printer"].indexOf(printJob.status) !== -1);
+ return printJob != null && (["printing", "post_print", "pre_print", "sent_to_printer"].indexOf(printJob.state) !== -1);
}
width: parent.width
@@ -252,19 +244,19 @@ Rectangle
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
text: {
- if (!printer.enabled)
+ if (printer.state == "disabled")
{
return catalog.i18nc("@label:status", "Disabled");
}
- if (printer.status === "unreachable")
+ if (printer.state === "unreachable")
{
return printerStatusText(printer);
}
if (printJob != null)
{
- switch (printJob.status)
+ switch (printJob.state)
{
case "printing":
case "post_print":
@@ -272,19 +264,13 @@ Rectangle
case "wait_for_configuration":
return catalog.i18nc("@label:status", "Reserved")
case "wait_cleanup":
+ case "wait_user_action":
return catalog.i18nc("@label:status", "Finished")
case "pre_print":
case "sent_to_printer":
return catalog.i18nc("@label", "Preparing to print")
case "queued":
- if (printJob.configuration_changes_required != null && printJob.configuration_changes_required.length !== 0)
- {
return catalog.i18nc("@label:status", "Action required");
- }
- else
- {
- return "";
- }
case "pausing":
case "paused":
return catalog.i18nc("@label:status", "Paused");
@@ -293,6 +279,7 @@ Rectangle
case "aborted":
return catalog.i18nc("@label:status", "Print aborted");
default:
+ // If print job has unknown status show printer.status
return printerStatusText(printer);
}
}
@@ -328,26 +315,23 @@ Rectangle
visible: !printProgressTitleBar.showPercent
source: {
- if (!printer.enabled)
+ if (printer.state == "disabled")
{
return "blocked-icon.svg";
}
- if (printer.status === "unreachable")
+ if (printer.state === "unreachable")
{
return "";
}
if (printJob != null)
{
- if(printJob.status === "queued")
+ if(printJob.state === "queued")
{
- if (printJob.configuration_changes_required != null && printJob.configuration_changes_required.length !== 0)
- {
- return "action-required-icon.svg";
- }
+ return "action-required-icon.svg";
}
- else if (printJob.status === "wait_cleanup")
+ else if (printJob.state === "wait_cleanup")
{
return "checkmark-icon.svg";
}
@@ -384,23 +368,23 @@ Rectangle
{
text:
{
- if (!printer.enabled)
+ if (printer.state == "disabled")
{
return catalog.i18nc("@label", "Not accepting print jobs");
}
- if (printer.status === "unreachable")
+ if (printer.state === "unreachable")
{
return "";
}
if(printJob != null)
{
- switch (printJob.status)
+ switch (printJob.state)
{
case "printing":
case "post_print":
- return catalog.i18nc("@label", "Finishes at: ") + OutputDevice.getTimeCompleted(printJob.time_total - printJob.time_elapsed)
+ return catalog.i18nc("@label", "Finishes at: ") + OutputDevice.getTimeCompleted(printJob.timeTotal - printJob.timeElapsed)
case "wait_cleanup":
return catalog.i18nc("@label", "Clear build plate")
case "sent_to_printer":
@@ -409,10 +393,7 @@ Rectangle
case "wait_for_configuration":
return catalog.i18nc("@label", "Not accepting print jobs")
case "queued":
- if (printJob.configuration_changes_required != undefined)
- {
- return catalog.i18nc("@label", "Waiting for configuration change");
- }
+ return catalog.i18nc("@label", "Waiting for configuration change");
default:
return "";
}
@@ -432,9 +413,9 @@ Rectangle
text: {
if(printJob != null)
{
- if(printJob.status == "printing" || printJob.status == "post_print")
+ if(printJob.state == "printing" || printJob.state == "post_print")
{
- return OutputDevice.getDateCompleted(printJob.time_total - printJob.time_elapsed)
+ return OutputDevice.getDateCompleted(printJob.timeTotal - printJob.timeElapsed)
}
}
return "";
diff --git a/plugins/UM3NetworkPrinting/PrinterVideoStream.qml b/plugins/UM3NetworkPrinting/PrinterVideoStream.qml
index 6793d74ac5..7f7b2ad546 100644
--- a/plugins/UM3NetworkPrinting/PrinterVideoStream.qml
+++ b/plugins/UM3NetworkPrinting/PrinterVideoStream.qml
@@ -17,7 +17,7 @@ Item
MouseArea
{
anchors.fill: parent
- onClicked: OutputDevice.selectAutomaticPrinter()
+ onClicked: OutputDevice.setActivePrinter(null)
z: 0
}
@@ -32,7 +32,7 @@ Item
width: 20 * screenScaleFactor
height: 20 * screenScaleFactor
- onClicked: OutputDevice.selectAutomaticPrinter()
+ onClicked: OutputDevice.setActivePrinter(null)
style: ButtonStyle
{
@@ -57,7 +57,7 @@ Item
{
id: cameraImage
width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth)
- height: Math.floor((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
+ height: Math.round((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
z: 1
@@ -65,17 +65,23 @@ Item
{
if(visible)
{
- OutputDevice.startCamera()
+ if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+ {
+ OutputDevice.activePrinter.camera.start()
+ }
} else
{
- OutputDevice.stopCamera()
+ if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
+ {
+ OutputDevice.activePrinter.camera.stop()
+ }
}
}
source:
{
- if(OutputDevice.cameraImage)
+ if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
{
- return OutputDevice.cameraImage;
+ return OutputDevice.activePrinter.camera.latestImage;
}
return "";
}
diff --git a/plugins/UM3NetworkPrinting/UM3InfoComponents.qml b/plugins/UM3NetworkPrinting/UM3InfoComponents.qml
index d0c95e1524..5a9cc096e7 100644
--- a/plugins/UM3NetworkPrinting/UM3InfoComponents.qml
+++ b/plugins/UM3NetworkPrinting/UM3InfoComponents.qml
@@ -10,10 +10,11 @@ Item
{
id: base
- property bool isUM3: Cura.MachineManager.activeQualityDefinitionId == "ultimaker3"
+ property string activeQualityDefinitionId: Cura.MachineManager.activeQualityDefinitionId
+ property bool isUM3: activeQualityDefinitionId == "ultimaker3" || activeQualityDefinitionId.match("ultimaker_") != null
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
- property bool authenticationRequested: printerConnected && Cura.MachineManager.printerOutputDevices[0].authenticationState == 2 // AuthState.AuthenticationRequested
+ property bool authenticationRequested: printerConnected && (Cura.MachineManager.printerOutputDevices[0].authenticationState == 2 || Cura.MachineManager.printerOutputDevices[0].authenticationState == 5) // AuthState.AuthenticationRequested or AuthenticationReceived.
Row
{
@@ -115,22 +116,8 @@ Item
{
tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura")
text: catalog.i18nc("@action:button", "Activate Configuration")
- visible: printerConnected && !isClusterPrinter()
+ visible: false // printerConnected && !isClusterPrinter()
onClicked: manager.loadConfigurationFromPrinter()
-
- function isClusterPrinter() {
- if(Cura.MachineManager.printerOutputDevices.length == 0)
- {
- return false;
- }
- var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize;
- // This is not a cluster printer or the cluster it is just one printer
- if(clusterSize == undefined || clusterSize == 1)
- {
- return false;
- }
- return true;
- }
}
}
diff --git a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py
new file mode 100644
index 0000000000..5ff5eb9e3e
--- /dev/null
+++ b/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py
@@ -0,0 +1,332 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
+from UM.Logger import Logger
+from UM.Application import Application
+from UM.Signal import Signal, signalemitter
+from UM.Preferences import Preferences
+from UM.Version import Version
+
+from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
+
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
+from PyQt5.QtCore import QUrl
+
+from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
+from queue import Queue
+from threading import Event, Thread
+from time import time
+
+import json
+
+
+## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
+# Zero-Conf is used to detect printers, which are saved in a dict.
+# If we discover a printer that has the same key as the active machine instance a connection is made.
+@signalemitter
+class UM3OutputDevicePlugin(OutputDevicePlugin):
+ addDeviceSignal = Signal()
+ removeDeviceSignal = Signal()
+ discoveredDevicesChanged = Signal()
+
+ def __init__(self):
+ super().__init__()
+ self._zero_conf = None
+ self._zero_conf_browser = None
+
+ # Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
+ self.addDeviceSignal.connect(self._onAddDevice)
+ self.removeDeviceSignal.connect(self._onRemoveDevice)
+
+ Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
+
+ self._discovered_devices = {}
+
+ self._network_manager = QNetworkAccessManager()
+ self._network_manager.finished.connect(self._onNetworkRequestFinished)
+
+ self._min_cluster_version = Version("4.0.0")
+
+ self._api_version = "1"
+ self._api_prefix = "/api/v" + self._api_version + "/"
+ self._cluster_api_version = "1"
+ self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
+
+ # Get list of manual instances from preferences
+ self._preferences = Preferences.getInstance()
+ self._preferences.addPreference("um3networkprinting/manual_instances",
+ "") # A comma-separated list of ip adresses or hostnames
+
+ self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",")
+
+ # The zero-conf service changed requests are handled in a separate thread, so we can re-schedule the requests
+ # which fail to get detailed service info.
+ # Any new or re-scheduled requests will be appended to the request queue, and the handling thread will pick
+ # them up and process them.
+ self._service_changed_request_queue = Queue()
+ self._service_changed_request_event = Event()
+ self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True)
+ self._service_changed_request_thread.start()
+
+ def getDiscoveredDevices(self):
+ return self._discovered_devices
+
+ ## Start looking for devices on network.
+ def start(self):
+ self.startDiscovery()
+
+ def startDiscovery(self):
+ self.stop()
+ if self._zero_conf_browser:
+ self._zero_conf_browser.cancel()
+ self._zero_conf_browser = None # Force the old ServiceBrowser to be destroyed.
+
+ self._zero_conf = Zeroconf()
+ self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.',
+ [self._appendServiceChangedRequest])
+
+ # Look for manual instances from preference
+ for address in self._manual_instances:
+ if address:
+ self.addManualDevice(address)
+
+ def reCheckConnections(self):
+ active_machine = Application.getInstance().getGlobalContainerStack()
+ if not active_machine:
+ return
+
+ um_network_key = active_machine.getMetaDataEntry("um_network_key")
+
+ for key in self._discovered_devices:
+ if key == um_network_key:
+ if not self._discovered_devices[key].isConnected():
+ Logger.log("d", "Attempting to connect with [%s]" % key)
+ self._discovered_devices[key].connect()
+ self._discovered_devices[key].connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
+ else:
+ if self._discovered_devices[key].isConnected():
+ Logger.log("d", "Attempting to close connection with [%s]" % key)
+ self._discovered_devices[key].close()
+ self._discovered_devices[key].connectionStateChanged.disconnect(self._onDeviceConnectionStateChanged)
+
+ def _onDeviceConnectionStateChanged(self, key):
+ if key not in self._discovered_devices:
+ return
+ if self._discovered_devices[key].isConnected():
+ self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key])
+ else:
+ self.getOutputDeviceManager().removeOutputDevice(key)
+
+ def stop(self):
+ if self._zero_conf is not None:
+ Logger.log("d", "zeroconf close...")
+ self._zero_conf.close()
+
+ def removeManualDevice(self, key, address = None):
+ if key in self._discovered_devices:
+ if not address:
+ address = self._discovered_devices[key].ipAddress
+ self._onRemoveDevice(key)
+
+ if address in self._manual_instances:
+ self._manual_instances.remove(address)
+ self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
+
+ def addManualDevice(self, address):
+ if address not in self._manual_instances:
+ self._manual_instances.append(address)
+ self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
+
+ instance_name = "manual:%s" % address
+ properties = {
+ b"name": address.encode("utf-8"),
+ b"address": address.encode("utf-8"),
+ b"manual": b"true",
+ b"incomplete": b"true"
+ }
+
+ if instance_name not in self._discovered_devices:
+ # Add a preliminary printer instance
+ self._onAddDevice(instance_name, address, properties)
+
+ self._checkManualDevice(address)
+
+ def _checkManualDevice(self, address):
+ # Check if a UM3 family device exists at this address.
+ # If a printer responds, it will replace the preliminary printer created above
+ # origin=manual is for tracking back the origin of the call
+ url = QUrl("http://" + address + self._api_prefix + "system")
+ name_request = QNetworkRequest(url)
+ self._network_manager.get(name_request)
+
+ def _onNetworkRequestFinished(self, reply):
+ reply_url = reply.url().toString()
+
+ if "system" in reply_url:
+ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
+ # Something went wrong with checking the firmware version!
+ return
+
+ try:
+ system_info = json.loads(bytes(reply.readAll()).decode("utf-8"))
+ except:
+ Logger.log("e", "Something went wrong converting the JSON.")
+ return
+
+ address = reply.url().host()
+ has_cluster_capable_firmware = Version(system_info["firmware"]) > self._min_cluster_version
+ instance_name = "manual:%s" % address
+ properties = {
+ b"name": system_info["name"].encode("utf-8"),
+ b"address": address.encode("utf-8"),
+ b"firmware_version": system_info["firmware"].encode("utf-8"),
+ b"manual": b"true",
+ b"machine": system_info["variant"].encode("utf-8")
+ }
+
+ if has_cluster_capable_firmware:
+ # Cluster needs an additional request, before it's completed.
+ properties[b"incomplete"] = b"true"
+
+ # Check if the device is still in the list & re-add it with the updated
+ # information.
+ if instance_name in self._discovered_devices:
+ self._onRemoveDevice(instance_name)
+ self._onAddDevice(instance_name, address, properties)
+
+ if has_cluster_capable_firmware:
+ # We need to request more info in order to figure out the size of the cluster.
+ cluster_url = QUrl("http://" + address + self._cluster_api_prefix + "printers/")
+ cluster_request = QNetworkRequest(cluster_url)
+ self._network_manager.get(cluster_request)
+
+ elif "printers" in reply_url:
+ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
+ # Something went wrong with checking the amount of printers the cluster has!
+ return
+ # So we confirmed that the device is in fact a cluster printer, and we should now know how big it is.
+ try:
+ cluster_printers_list = json.loads(bytes(reply.readAll()).decode("utf-8"))
+ except:
+ Logger.log("e", "Something went wrong converting the JSON.")
+ return
+ address = reply.url().host()
+ instance_name = "manual:%s" % address
+ if instance_name in self._discovered_devices:
+ device = self._discovered_devices[instance_name]
+ properties = device.getProperties().copy()
+ if b"incomplete" in properties:
+ del properties[b"incomplete"]
+ properties[b'cluster_size'] = len(cluster_printers_list)
+ self._onRemoveDevice(instance_name)
+ self._onAddDevice(instance_name, address, properties)
+
+ def _onRemoveDevice(self, device_id):
+ device = self._discovered_devices.pop(device_id, None)
+ if device:
+ if device.isConnected():
+ device.disconnect()
+ try:
+ device.connectionStateChanged.disconnect(self._onDeviceConnectionStateChanged)
+ except TypeError:
+ # Disconnect already happened.
+ pass
+
+ self.discoveredDevicesChanged.emit()
+
+ def _onAddDevice(self, name, address, properties):
+ # Check what kind of device we need to add; Depending on the firmware we either add a "Connect"/"Cluster"
+ # or "Legacy" UM3 device.
+ cluster_size = int(properties.get(b"cluster_size", -1))
+
+ if cluster_size >= 0:
+ device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties)
+ else:
+ device = LegacyUM3OutputDevice.LegacyUM3OutputDevice(name, address, properties)
+
+ self._discovered_devices[device.getId()] = device
+ self.discoveredDevicesChanged.emit()
+
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"):
+ device.connect()
+ device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
+
+ ## Appends a service changed request so later the handling thread will pick it up and processes it.
+ def _appendServiceChangedRequest(self, zeroconf, service_type, name, state_change):
+ # append the request and set the event so the event handling thread can pick it up
+ item = (zeroconf, service_type, name, state_change)
+ self._service_changed_request_queue.put(item)
+ self._service_changed_request_event.set()
+
+ def _handleOnServiceChangedRequests(self):
+ while True:
+ # Wait for the event to be set
+ self._service_changed_request_event.wait(timeout = 5.0)
+
+ # Stop if the application is shutting down
+ if Application.getInstance().isShuttingDown():
+ return
+
+ self._service_changed_request_event.clear()
+
+ # Handle all pending requests
+ reschedule_requests = [] # A list of requests that have failed so later they will get re-scheduled
+ while not self._service_changed_request_queue.empty():
+ request = self._service_changed_request_queue.get()
+ zeroconf, service_type, name, state_change = request
+ try:
+ result = self._onServiceChanged(zeroconf, service_type, name, state_change)
+ if not result:
+ reschedule_requests.append(request)
+ except Exception:
+ Logger.logException("e", "Failed to get service info for [%s] [%s], the request will be rescheduled",
+ service_type, name)
+ reschedule_requests.append(request)
+
+ # Re-schedule the failed requests if any
+ if reschedule_requests:
+ for request in reschedule_requests:
+ self._service_changed_request_queue.put(request)
+
+ ## Handler for zeroConf detection.
+ # Return True or False indicating if the process succeeded.
+ # Note that this function can take over 3 seconds to complete. Be carefull calling it from the main thread.
+ def _onServiceChanged(self, zero_conf, service_type, name, state_change):
+ if state_change == ServiceStateChange.Added:
+ Logger.log("d", "Bonjour service added: %s" % name)
+
+ # First try getting info from zero-conf cache
+ info = ServiceInfo(service_type, name, properties={})
+ for record in zero_conf.cache.entries_with_name(name.lower()):
+ info.update_record(zero_conf, time(), record)
+
+ for record in zero_conf.cache.entries_with_name(info.server):
+ info.update_record(zero_conf, time(), record)
+ if info.address:
+ break
+
+ # Request more data if info is not complete
+ if not info.address:
+ Logger.log("d", "Trying to get address of %s", name)
+ info = zero_conf.get_service_info(service_type, name)
+
+ if info:
+ type_of_device = info.properties.get(b"type", None)
+ if type_of_device:
+ if type_of_device == b"printer":
+ address = '.'.join(map(lambda n: str(n), info.address))
+ self.addDeviceSignal.emit(str(name), address, info.properties)
+ else:
+ Logger.log("w",
+ "The type of the found device is '%s', not 'printer'! Ignoring.." % type_of_device)
+ else:
+ Logger.log("w", "Could not get information about %s" % name)
+ return False
+
+ elif state_change == ServiceStateChange.Removed:
+ Logger.log("d", "Bonjour service removed: %s" % name)
+ self.removeDeviceSignal.emit(str(name))
+
+ return True
\ No newline at end of file
diff --git a/plugins/UM3NetworkPrinting/__init__.py b/plugins/UM3NetworkPrinting/__init__.py
index 37f863bd00..b68086cb75 100644
--- a/plugins/UM3NetworkPrinting/__init__.py
+++ b/plugins/UM3NetworkPrinting/__init__.py
@@ -1,12 +1,14 @@
-# Copyright (c) 2015 Ultimaker B.V.
+# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from . import NetworkPrinterOutputDevicePlugin
+
from . import DiscoverUM3Action
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
+from . import UM3OutputDevicePlugin
+
def getMetaData():
return {}
def register(app):
- return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}
\ No newline at end of file
+ return { "output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}
\ No newline at end of file
diff --git a/plugins/USBPrinting/AutoDetectBaudJob.py b/plugins/USBPrinting/AutoDetectBaudJob.py
new file mode 100644
index 0000000000..72f4f20262
--- /dev/null
+++ b/plugins/USBPrinting/AutoDetectBaudJob.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.Job import Job
+from UM.Logger import Logger
+
+from .avr_isp.stk500v2 import Stk500v2
+
+from time import time, sleep
+from serial import Serial, SerialException
+
+
+# An async job that attempts to find the correct baud rate for a USB printer.
+# It tries a pre-set list of baud rates. All these baud rates are validated by requesting the temperature a few times
+# and checking if the results make sense. If getResult() is not None, it was able to find a correct baud rate.
+class AutoDetectBaudJob(Job):
+ def __init__(self, serial_port):
+ super().__init__()
+ self._serial_port = serial_port
+ self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
+
+ def run(self):
+ Logger.log("d", "Auto detect baud rate started.")
+ timeout = 3
+
+ programmer = Stk500v2()
+ serial = None
+ try:
+ programmer.connect(self._serial_port)
+ serial = programmer.leaveISP()
+ except:
+ programmer.close()
+
+ for baud_rate in self._all_baud_rates:
+ Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate))
+
+ if serial is None:
+ try:
+ serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout)
+ except SerialException as e:
+ Logger.logException("w", "Unable to create serial")
+ continue
+ else:
+ # We already have a serial connection, just change the baud rate.
+ try:
+ serial.baudrate = baud_rate
+ except:
+ continue
+ sleep(1.5) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number
+ successful_responses = 0
+
+ serial.write(b"\n") # Ensure we clear out previous responses
+ serial.write(b"M105\n")
+
+ timeout_time = time() + timeout
+
+ while timeout_time > time():
+ line = serial.readline()
+ if b"ok T:" in line:
+ successful_responses += 1
+ if successful_responses >= 3:
+ self.setResult(baud_rate)
+ return
+
+ serial.write(b"M105\n")
+ self.setResult(None) # Unable to detect the correct baudrate.
diff --git a/plugins/USBPrinting/FirmwareUpdateWindow.qml b/plugins/USBPrinting/FirmwareUpdateWindow.qml
index 44218b61b1..e0f9de314e 100644
--- a/plugins/USBPrinting/FirmwareUpdateWindow.qml
+++ b/plugins/USBPrinting/FirmwareUpdateWindow.qml
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Ultimaker B.V.
+// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
@@ -34,44 +34,22 @@ UM.Dialog
}
text: {
- if (manager.errorCode == 0)
+ switch (manager.firmwareUpdateState)
{
- if (manager.firmwareUpdateCompleteStatus)
- {
- //: Firmware update status label
- return catalog.i18nc("@label","Firmware update completed.")
- }
- else if (manager.progress == 0)
- {
- //: Firmware update status label
- return catalog.i18nc("@label","Starting firmware update, this may take a while.")
- }
- else
- {
- //: Firmware update status label
+ case 0:
+ return "" //Not doing anything (eg; idling)
+ case 1:
return catalog.i18nc("@label","Updating firmware.")
- }
- }
- else
- {
- switch (manager.errorCode)
- {
- case 1:
- //: Firmware update status label
- return catalog.i18nc("@label","Firmware update failed due to an unknown error.")
- case 2:
- //: Firmware update status label
- return catalog.i18nc("@label","Firmware update failed due to an communication error.")
- case 3:
- //: Firmware update status label
- return catalog.i18nc("@label","Firmware update failed due to an input/output error.")
- case 4:
- //: Firmware update status label
- return catalog.i18nc("@label","Firmware update failed due to missing firmware.")
- default:
- //: Firmware update status label
- return catalog.i18nc("@label", "Unknown error code: %1").arg(manager.errorCode)
- }
+ case 2:
+ return catalog.i18nc("@label","Firmware update completed.")
+ case 3:
+ return catalog.i18nc("@label","Firmware update failed due to an unknown error.")
+ case 4:
+ return catalog.i18nc("@label","Firmware update failed due to an communication error.")
+ case 5:
+ return catalog.i18nc("@label","Firmware update failed due to an input/output error.")
+ case 6:
+ return catalog.i18nc("@label","Firmware update failed due to missing firmware.")
}
}
@@ -81,16 +59,15 @@ UM.Dialog
ProgressBar
{
id: prog
- value: manager.firmwareUpdateCompleteStatus ? 100 : manager.progress
+ value: manager.firmwareProgress
minimumValue: 0
maximumValue: 100
- indeterminate: (manager.progress < 1) && (!manager.firmwareUpdateCompleteStatus)
+ indeterminate: manager.firmwareProgress < 1 && manager.firmwareProgress > 0
anchors
{
left: parent.left;
right: parent.right;
}
-
}
SystemPalette
diff --git a/plugins/USBPrinting/USBPrinterOutputController.py b/plugins/USBPrinting/USBPrinterOutputController.py
new file mode 100644
index 0000000000..f189ed5876
--- /dev/null
+++ b/plugins/USBPrinting/USBPrinterOutputController.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+from PyQt5.QtCore import QTimer
+
+MYPY = False
+if MYPY:
+ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+
+
+class USBPrinterOutputController(PrinterOutputController):
+ def __init__(self, output_device):
+ super().__init__(output_device)
+
+ self._preheat_bed_timer = QTimer()
+ self._preheat_bed_timer.setSingleShot(True)
+ self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
+ self._preheat_printer = None
+
+ def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
+ self._output_device.sendCommand("G91")
+ self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
+ self._output_device.sendCommand("G90")
+
+ def homeHead(self, printer):
+ self._output_device.sendCommand("G28 X")
+ self._output_device.sendCommand("G28 Y")
+
+ def homeBed(self, printer):
+ self._output_device.sendCommand("G28 Z")
+
+ def setJobState(self, job: "PrintJobOutputModel", state: str):
+ if state == "pause":
+ self._output_device.pausePrint()
+ job.updateState("paused")
+ elif state == "print":
+ self._output_device.resumePrint()
+ job.updateState("printing")
+ elif state == "abort":
+ self._output_device.cancelPrint()
+ pass
+
+ def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
+ try:
+ temperature = round(temperature) # The API doesn't allow floating point.
+ duration = round(duration)
+ except ValueError:
+ return # Got invalid values, can't pre-heat.
+
+ self.setTargetBedTemperature(printer, temperature=temperature)
+ self._preheat_bed_timer.setInterval(duration * 1000)
+ self._preheat_bed_timer.start()
+ self._preheat_printer = printer
+ printer.updateIsPreheating(True)
+
+ def cancelPreheatBed(self, printer: "PrinterOutputModel"):
+ self.preheatBed(printer, temperature=0, duration=0)
+ self._preheat_bed_timer.stop()
+ printer.updateIsPreheating(False)
+
+ def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
+ self._output_device.sendCommand("M140 S%s" % temperature)
+
+ def _onPreheatBedTimerFinished(self):
+ self.setTargetBedTemperature(self._preheat_printer, 0)
+ self._preheat_printer.updateIsPreheating(False)
\ No newline at end of file
diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py
index 1930f5402b..6e2b5153db 100644
--- a/plugins/USBPrinting/USBPrinterOutputDevice.py
+++ b/plugins/USBPrinting/USBPrinterOutputDevice.py
@@ -1,472 +1,89 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from .avr_isp import stk500v2, ispBase, intelHex
-import serial # type: ignore
-import threading
-import time
-import queue
-import re
-import functools
-
-from UM.Application import Application
from UM.Logger import Logger
-from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
-from UM.Message import Message
-from UM.Qt.Duration import DurationFormat
-
-from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal, pyqtProperty
-
from UM.i18n import i18nCatalog
+from UM.Application import Application
+from UM.Qt.Duration import DurationFormat
+from UM.PluginRegistry import PluginRegistry
+
+from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
+from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+
+from .AutoDetectBaudJob import AutoDetectBaudJob
+from .USBPrinterOutputController import USBPrinterOutputController
+from .avr_isp import stk500v2, intelHex
+
+from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
+
+from serial import Serial, SerialException
+from threading import Thread
+from time import time, sleep
+from queue import Queue
+from enum import IntEnum
+from typing import Union, Optional, List
+
+import re
+import functools # Used for reduce
+import os
+
catalog = i18nCatalog("cura")
class USBPrinterOutputDevice(PrinterOutputDevice):
- def __init__(self, serial_port):
+ firmwareProgressChanged = pyqtSignal()
+ firmwareUpdateStateChanged = pyqtSignal()
+
+ def __init__(self, serial_port: str, baud_rate: Optional[int] = None):
super().__init__(serial_port)
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
self.setShortDescription(catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print via USB"))
self.setDescription(catalog.i18nc("@info:tooltip", "Print via USB"))
self.setIconName("print")
- self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
- self._serial = None
+ self._serial = None # type: Optional[Serial]
self._serial_port = serial_port
- self._error_state = None
+ self._address = serial_port
- self._connect_thread = threading.Thread(target = self._connect)
- self._connect_thread.daemon = True
-
- self._end_stop_thread = None
- self._poll_endstop = False
-
- # The baud checking is done by sending a number of m105 commands to the printer and waiting for a readable
- # response. If the baudrate is correct, this should make sense, else we get giberish.
- self._required_responses_auto_baud = 3
-
- self._listen_thread = threading.Thread(target=self._listen)
- self._listen_thread.daemon = True
-
- self._update_firmware_thread = threading.Thread(target= self._updateFirmware)
- self._update_firmware_thread.daemon = True
- self.firmwareUpdateComplete.connect(self._onFirmwareUpdateComplete)
-
- self._heatup_wait_start_time = time.time()
-
- self.jobStateChanged.connect(self._onJobStateChanged)
-
- ## Queue for commands that need to be send. Used when command is sent when a print is active.
- self._command_queue = queue.Queue()
-
- self._is_printing = False
- self._is_paused = False
-
- ## Set when print is started in order to check running time.
- self._print_start_time = None
- self._print_estimated_time = None
-
- ## Keep track where in the provided g-code the print is
- self._gcode_position = 0
+ self._timeout = 3
# List of gcode lines to be printed
- self._gcode = []
-
- # Check if endstops are ever pressed (used for first run)
- self._x_min_endstop_pressed = False
- self._y_min_endstop_pressed = False
- self._z_min_endstop_pressed = False
-
- self._x_max_endstop_pressed = False
- self._y_max_endstop_pressed = False
- self._z_max_endstop_pressed = False
-
- # In order to keep the connection alive we request the temperature every so often from a different extruder.
- # This index is the extruder we requested data from the last time.
- self._temperature_requested_extruder_index = 0
-
- self._current_z = 0
-
- self._updating_firmware = False
-
- self._firmware_file_name = None
- self._firmware_update_finished = False
-
- self._error_message = None
- self._error_code = 0
-
- onError = pyqtSignal()
-
- firmwareUpdateComplete = pyqtSignal()
- firmwareUpdateChange = pyqtSignal()
-
- endstopStateChanged = pyqtSignal(str ,bool, arguments = ["key","state"])
-
- def _setTargetBedTemperature(self, temperature):
- Logger.log("d", "Setting bed temperature to %s", temperature)
- self._sendCommand("M140 S%s" % temperature)
-
- def _setTargetHotendTemperature(self, index, temperature):
- Logger.log("d", "Setting hotend %s temperature to %s", index, temperature)
- self._sendCommand("M104 T%s S%s" % (index, temperature))
-
- def _setHeadPosition(self, x, y , z, speed):
- self._sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
-
- def _setHeadX(self, x, speed):
- self._sendCommand("G0 X%s F%s" % (x, speed))
-
- def _setHeadY(self, y, speed):
- self._sendCommand("G0 Y%s F%s" % (y, speed))
-
- def _setHeadZ(self, z, speed):
- self._sendCommand("G0 Y%s F%s" % (z, speed))
-
- def _homeHead(self):
- self._sendCommand("G28 X")
- self._sendCommand("G28 Y")
-
- def _homeBed(self):
- self._sendCommand("G28 Z")
-
- ## Updates the target bed temperature from the printer, and emit a signal if it was changed.
- #
- # /param temperature The new target temperature of the bed.
- # /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature
- def _updateTargetBedTemperature(self, temperature):
- if self._target_bed_temperature == temperature:
- return False
- self._target_bed_temperature = temperature
- self.targetBedTemperatureChanged.emit()
- return True
-
- ## Updates the target hotend temperature from the printer, and emit a signal if it was changed.
- #
- # /param index The index of the hotend.
- # /param temperature The new target temperature of the hotend.
- # /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature
- def _updateTargetHotendTemperature(self, index, temperature):
- if self._target_hotend_temperatures[index] == temperature:
- return False
- self._target_hotend_temperatures[index] = temperature
- self.targetHotendTemperaturesChanged.emit()
- return True
-
- ## A name for the device.
- @pyqtProperty(str, constant = True)
- def name(self):
- return self.getName()
-
- ## The address of the device.
- @pyqtProperty(str, constant = True)
- def address(self):
- return self._serial_port
-
- def startPrint(self):
- self.writeStarted.emit(self)
- gcode_list = getattr( Application.getInstance().getController().getScene(), "gcode_list")
- self._updateJobState("printing")
- self.printGCode(gcode_list)
-
- def _moveHead(self, x, y, z, speed):
- self._sendCommand("G91")
- self._sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
- self._sendCommand("G90")
-
- ## Start a print based on a g-code.
- # \param gcode_list List with gcode (strings).
- def printGCode(self, gcode_list):
- Logger.log("d", "Started printing g-code")
- if self._progress or self._connection_state != ConnectionState.connected:
- self._error_message = Message(catalog.i18nc("@info:status", "Unable to start a new job because the printer is busy or not connected."), title = catalog.i18nc("@info:title", "Printer Unavailable"))
- self._error_message.show()
- Logger.log("d", "Printer is busy or not connected, aborting print")
- self.writeError.emit(self)
- return
-
- self._gcode.clear()
- for layer in gcode_list:
- self._gcode.extend(layer.split("\n"))
-
- # Reset line number. If this is not done, first line is sometimes ignored
- self._gcode.insert(0, "M110")
+ self._gcode = [] # type: List[str]
self._gcode_position = 0
- self._is_printing = True
- self._print_start_time = time.time()
- for i in range(0, 4): # Push first 4 entries before accepting other inputs
- self._sendNextGcodeLine()
+ self._use_auto_detect = True
- self.writeFinished.emit(self)
+ self._baud_rate = baud_rate
- ## Get the serial port string of this connection.
- # \return serial port
- def getSerialPort(self):
- return self._serial_port
+ self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
- ## Try to connect the serial. This simply starts the thread, which runs _connect.
- def connect(self):
- if not self._updating_firmware and not self._connect_thread.isAlive():
- self._connect_thread.start()
+ # Instead of using a timer, we really need the update to be as a thread, as reading from serial can block.
+ self._update_thread = Thread(target=self._update, daemon = True)
- ## Private function (threaded) that actually uploads the firmware.
- def _updateFirmware(self):
- Logger.log("d", "Attempting to update firmware")
- self._error_code = 0
- self.setProgress(0, 100)
- self._firmware_update_finished = False
+ self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True)
- if self._connection_state != ConnectionState.closed:
- self.close()
- hex_file = intelHex.readHex(self._firmware_file_name)
+ self._last_temperature_request = None # type: Optional[int]
- if len(hex_file) == 0:
- Logger.log("e", "Unable to read provided hex file. Could not update firmware")
- self._updateFirmwareFailedMissingFirmware()
- return
+ self._is_printing = False # A print is being sent.
- programmer = stk500v2.Stk500v2()
- programmer.progress_callback = self.setProgress
+ ## Set when print is started in order to check running time.
+ self._print_start_time = None # type: Optional[int]
+ self._print_estimated_time = None # type: Optional[int]
- try:
- programmer.connect(self._serial_port)
- except Exception:
- programmer.close()
- pass
+ self._accepts_commands = True
- # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
- time.sleep(1)
+ self._paused = False
- if not programmer.isConnected():
- Logger.log("e", "Unable to connect with serial. Could not update firmware")
- self._updateFirmwareFailedCommunicationError()
- return
+ self._firmware_view = None
+ self._firmware_location = None
+ self._firmware_progress = 0
+ self._firmware_update_state = FirmwareUpdateState.idle
- self._updating_firmware = True
+ self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
- try:
- programmer.programChip(hex_file)
- self._updating_firmware = False
- except serial.SerialException as e:
- Logger.log("e", "SerialException while trying to update firmware: <%s>" %(repr(e)))
- self._updateFirmwareFailedIOError()
- return
- except Exception as e:
- Logger.log("e", "Exception while trying to update firmware: <%s>" %(repr(e)))
- self._updateFirmwareFailedUnknown()
- return
- programmer.close()
-
- self._updateFirmwareCompletedSucessfully()
- return
-
- ## Private function which makes sure that firmware update process has failed by missing firmware
- def _updateFirmwareFailedMissingFirmware(self):
- return self._updateFirmwareFailedCommon(4)
-
- ## Private function which makes sure that firmware update process has failed by an IO error
- def _updateFirmwareFailedIOError(self):
- return self._updateFirmwareFailedCommon(3)
-
- ## Private function which makes sure that firmware update process has failed by a communication problem
- def _updateFirmwareFailedCommunicationError(self):
- return self._updateFirmwareFailedCommon(2)
-
- ## Private function which makes sure that firmware update process has failed by an unknown error
- def _updateFirmwareFailedUnknown(self):
- return self._updateFirmwareFailedCommon(1)
-
- ## Private common function which makes sure that firmware update process has completed/ended with a set progress state
- def _updateFirmwareFailedCommon(self, code):
- if not code:
- raise Exception("Error code not set!")
-
- self._error_code = code
-
- self._firmware_update_finished = True
- self.resetFirmwareUpdate(update_has_finished = True)
- self.progressChanged.emit()
- self.firmwareUpdateComplete.emit()
-
- return
-
- ## Private function which makes sure that firmware update process has successfully completed
- def _updateFirmwareCompletedSucessfully(self):
- self.setProgress(100, 100)
- self._firmware_update_finished = True
- self.resetFirmwareUpdate(update_has_finished = True)
- self.firmwareUpdateComplete.emit()
-
- return
-
- ## Upload new firmware to machine
- # \param filename full path of firmware file to be uploaded
- def updateFirmware(self, file_name):
- Logger.log("i", "Updating firmware of %s using %s", self._serial_port, file_name)
- self._firmware_file_name = file_name
- self._update_firmware_thread.start()
-
- @property
- def firmwareUpdateFinished(self):
- return self._firmware_update_finished
-
- def resetFirmwareUpdate(self, update_has_finished = False):
- self._firmware_update_finished = update_has_finished
- self.firmwareUpdateChange.emit()
-
- @pyqtSlot()
- def startPollEndstop(self):
- if not self._poll_endstop:
- self._poll_endstop = True
- if self._end_stop_thread is None:
- self._end_stop_thread = threading.Thread(target=self._pollEndStop)
- self._end_stop_thread.daemon = True
- self._end_stop_thread.start()
-
- @pyqtSlot()
- def stopPollEndstop(self):
- self._poll_endstop = False
- self._end_stop_thread = None
-
- def _pollEndStop(self):
- while self._connection_state == ConnectionState.connected and self._poll_endstop:
- self.sendCommand("M119")
- time.sleep(0.5)
-
- ## Private connect function run by thread. Can be started by calling connect.
- def _connect(self):
- Logger.log("d", "Attempting to connect to %s", self._serial_port)
- self.setConnectionState(ConnectionState.connecting)
- programmer = stk500v2.Stk500v2()
- try:
- programmer.connect(self._serial_port) # Connect with the serial, if this succeeds, it's an arduino based usb device.
- self._serial = programmer.leaveISP()
- except ispBase.IspError as e:
- programmer.close()
- Logger.log("i", "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e)))
- except Exception as e:
- programmer.close()
- Logger.log("i", "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port)
-
- # If the programmer connected, we know its an atmega based version.
- # Not all that useful, but it does give some debugging information.
- for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect)
- Logger.log("d", "Attempting to connect to printer with serial %s on baud rate %s", self._serial_port, baud_rate)
- if self._serial is None:
- try:
- self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout = 3, writeTimeout = 10000)
- time.sleep(10)
- except serial.SerialException:
- Logger.log("d", "Could not open port %s" % self._serial_port)
- continue
- else:
- if not self.setBaudRate(baud_rate):
- continue # Could not set the baud rate, go to the next
-
- time.sleep(1.5) # Ensure that we are not talking to the bootloader. 1.5 seconds seems to be the magic number
- sucesfull_responses = 0
- timeout_time = time.time() + 5
- self._serial.write(b"\n")
- self._sendCommand("M105") # Request temperature, as this should (if baudrate is correct) result in a command with "T:" in it
- while timeout_time > time.time():
- line = self._readline()
- if line is None:
- Logger.log("d", "No response from serial connection received.")
- # Something went wrong with reading, could be that close was called.
- self.setConnectionState(ConnectionState.closed)
- return
-
- if b"T:" in line:
- Logger.log("d", "Correct response for auto-baudrate detection received.")
- self._serial.timeout = 0.5
- sucesfull_responses += 1
- if sucesfull_responses >= self._required_responses_auto_baud:
- self._serial.timeout = 2 # Reset serial timeout
- self.setConnectionState(ConnectionState.connected)
- self._listen_thread.start() # Start listening
- Logger.log("i", "Established printer connection on port %s" % self._serial_port)
- return
-
- self._sendCommand("M105") # Send M105 as long as we are listening, otherwise we end up in an undefined state
-
- Logger.log("e", "Baud rate detection for %s failed", self._serial_port)
- self.close() # Unable to connect, wrap up.
- self.setConnectionState(ConnectionState.closed)
-
- ## Set the baud rate of the serial. This can cause exceptions, but we simply want to ignore those.
- def setBaudRate(self, baud_rate):
- try:
- self._serial.baudrate = baud_rate
- return True
- except Exception as e:
- return False
-
- ## Close the printer connection
- def close(self):
- Logger.log("d", "Closing the USB printer connection.")
- if self._connect_thread.isAlive():
- try:
- self._connect_thread.join()
- except Exception as e:
- Logger.log("d", "PrinterConnection.close: %s (expected)", e)
- pass # This should work, but it does fail sometimes for some reason
-
- self._connect_thread = threading.Thread(target = self._connect)
- self._connect_thread.daemon = True
-
- self.setConnectionState(ConnectionState.closed)
- if self._serial is not None:
- try:
- self._listen_thread.join()
- except:
- pass
- if self._serial is not None: # Avoid a race condition when a thread can change the value of self._serial to None
- self._serial.close()
-
- self._listen_thread = threading.Thread(target = self._listen)
- self._listen_thread.daemon = True
- self._serial = None
-
- ## Directly send the command, withouth checking connection state (eg; printing).
- # \param cmd string with g-code
- def _sendCommand(self, cmd):
- if self._serial is None:
- return
-
- if "M109" in cmd or "M190" in cmd:
- self._heatup_wait_start_time = time.time()
-
- try:
- command = (cmd + "\n").encode()
- self._serial.write(b"\n")
- self._serial.write(command)
- except serial.SerialTimeoutException:
- Logger.log("w","Serial timeout while writing to serial port, trying again.")
- try:
- time.sleep(0.5)
- self._serial.write((cmd + "\n").encode())
- except Exception as e:
- Logger.log("e","Unexpected error while writing serial port %s " % e)
- self._setErrorState("Unexpected error while writing serial port %s " % e)
- self.close()
- except Exception as e:
- Logger.log("e","Unexpected error while writing serial port %s" % e)
- self._setErrorState("Unexpected error while writing serial port %s " % e)
- self.close()
-
- ## Send a command to printer.
- # \param cmd string with g-code
- def sendCommand(self, cmd):
- if self._progress:
- self._command_queue.put(cmd)
- elif self._connection_state == ConnectionState.connected:
- self._sendCommand(cmd)
-
- ## Set the error state with a message.
- # \param error String with the error message.
- def _setErrorState(self, error):
- self._updateJobState("error")
- self._error_state = error
- self.onError.emit()
+ # Queue for commands that need to be send. Used when command is sent when a print is active.
+ self._command_queue = Queue()
## Request the current scene to be sent to a USB-connected printer.
#
@@ -476,277 +93,295 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# is ignored.
# \param kwargs Keyword arguments.
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
- container_stack = Application.getInstance().getGlobalContainerStack()
-
- if container_stack.getProperty("machine_gcode_flavor", "value") == "UltiGCode":
- self._error_message = Message(catalog.i18nc("@info:status", "This printer does not support USB printing because it uses UltiGCode flavor."), title = catalog.i18nc("@info:title", "USB Printing"))
- self._error_message.show()
- return
- elif not container_stack.getMetaDataEntry("supports_usb_connection"):
- self._error_message = Message(catalog.i18nc("@info:status", "Unable to start a new job because the printer does not support usb printing."), title = catalog.i18nc("@info:title", "Warning"))
- self._error_message.show()
- return
-
- self.setJobName(file_name)
- self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
+ if self._is_printing:
+ return # Aleady printing
Application.getInstance().getController().setActiveStage("MonitorStage")
- self.startPrint()
- def _setEndstopState(self, endstop_key, value):
- if endstop_key == b"x_min":
- if self._x_min_endstop_pressed != value:
- self.endstopStateChanged.emit("x_min", value)
- self._x_min_endstop_pressed = value
- elif endstop_key == b"y_min":
- if self._y_min_endstop_pressed != value:
- self.endstopStateChanged.emit("y_min", value)
- self._y_min_endstop_pressed = value
- elif endstop_key == b"z_min":
- if self._z_min_endstop_pressed != value:
- self.endstopStateChanged.emit("z_min", value)
- self._z_min_endstop_pressed = value
+ # find the G-code for the active build plate to print
+ active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
+ gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict")
+ gcode_list = gcode_dict[active_build_plate_id]
- ## Listen thread function.
- def _listen(self):
- Logger.log("i", "Printer connection listen thread started for %s" % self._serial_port)
+ self._printGCode(gcode_list)
+
+ ## Show firmware interface.
+ # This will create the view if its not already created.
+ def showFirmwareInterface(self):
+ if self._firmware_view is None:
+ path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
+ self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self})
+
+ self._firmware_view.show()
+
+ @pyqtSlot(str)
+ def updateFirmware(self, file):
+ self._firmware_location = file
+ self.showFirmwareInterface()
+ self.setFirmwareUpdateState(FirmwareUpdateState.updating)
+ self._update_firmware_thread.start()
+
+ def _updateFirmware(self):
+ # Ensure that other connections are closed.
+ if self._connection_state != ConnectionState.closed:
+ self.close()
+
+ hex_file = intelHex.readHex(self._firmware_location)
+ if len(hex_file) == 0:
+ Logger.log("e", "Unable to read provided hex file. Could not update firmware")
+ self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
+ return
+
+ programmer = stk500v2.Stk500v2()
+ programmer.progress_callback = self._onFirmwareProgress
+
+ try:
+ programmer.connect(self._serial_port)
+ except:
+ programmer.close()
+ Logger.logException("e", "Failed to update firmware")
+ self.setFirmwareUpdateState(FirmwareUpdateState.communication_error)
+ return
+
+ # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
+ sleep(1)
+ if not programmer.isConnected():
+ Logger.log("e", "Unable to connect with serial. Could not update firmware")
+ self.setFirmwareUpdateState(FirmwareUpdateState.communication_error)
+ try:
+ programmer.programChip(hex_file)
+ except SerialException:
+ self.setFirmwareUpdateState(FirmwareUpdateState.io_error)
+ return
+ except:
+ self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
+ return
+
+ programmer.close()
+
+ # Clean up for next attempt.
+ self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
+ self._firmware_location = ""
+ self._onFirmwareProgress(100)
+ self.setFirmwareUpdateState(FirmwareUpdateState.completed)
+
+ # Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
+ Application.getInstance().callLater(self.connect)
+
+ @pyqtProperty(float, notify = firmwareProgressChanged)
+ def firmwareProgress(self):
+ return self._firmware_progress
+
+ @pyqtProperty(int, notify=firmwareUpdateStateChanged)
+ def firmwareUpdateState(self):
+ return self._firmware_update_state
+
+ def setFirmwareUpdateState(self, state):
+ if self._firmware_update_state != state:
+ self._firmware_update_state = state
+ self.firmwareUpdateStateChanged.emit()
+
+ # Callback function for firmware update progress.
+ def _onFirmwareProgress(self, progress, max_progress = 100):
+ self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100
+ self.firmwareProgressChanged.emit()
+
+ ## Start a print based on a g-code.
+ # \param gcode_list List with gcode (strings).
+ def _printGCode(self, gcode_list: List[str]):
+ self._gcode.clear()
+ self._paused = False
+
+ for layer in gcode_list:
+ self._gcode.extend(layer.split("\n"))
+
+ # Reset line number. If this is not done, first line is sometimes ignored
+ self._gcode.insert(0, "M110")
+ self._gcode_position = 0
+ self._is_printing = True
+ self._print_start_time = time()
+
+ self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
+
+ for i in range(0, 4): # Push first 4 entries before accepting other inputs
+ self._sendNextGcodeLine()
+
+ self.writeFinished.emit(self)
+
+ def _autoDetectFinished(self, job: AutoDetectBaudJob):
+ result = job.getResult()
+ if result is not None:
+ self.setBaudRate(result)
+ self.connect() # Try to connect (actually create serial, etc)
+
+ def setBaudRate(self, baud_rate: int):
+ if baud_rate not in self._all_baud_rates:
+ Logger.log("w", "Not updating baudrate to {baud_rate} as it's an unknown baudrate".format(baud_rate=baud_rate))
+ return
+
+ self._baud_rate = baud_rate
+
+ def connect(self):
+ if self._baud_rate is None:
+ if self._use_auto_detect:
+ auto_detect_job = AutoDetectBaudJob(self._serial_port)
+ auto_detect_job.start()
+ auto_detect_job.finished.connect(self._autoDetectFinished)
+ return
+ if self._serial is None:
+ try:
+ self._serial = Serial(str(self._serial_port), self._baud_rate, timeout=self._timeout, writeTimeout=self._timeout)
+ except SerialException:
+ Logger.log("w", "An exception occured while trying to create serial connection")
+ return
container_stack = Application.getInstance().getGlobalContainerStack()
- temperature_request_timeout = time.time()
- ok_timeout = time.time()
- while self._connection_state == ConnectionState.connected:
- line = self._readline()
- if line is None:
- break # None is only returned when something went wrong. Stop listening
+ num_extruders = container_stack.getProperty("machine_extruder_count", "value")
+ # Ensure that a printer is created.
+ self._printers = [PrinterOutputModel(output_controller=USBPrinterOutputController(self), number_of_extruders=num_extruders)]
+ self._printers[0].updateName(container_stack.getName())
+ self.setConnectionState(ConnectionState.connected)
+ self._update_thread.start()
- if time.time() > temperature_request_timeout:
- if self._num_extruders > 1:
- self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._num_extruders
- self.sendCommand("M105 T%d" % (self._temperature_requested_extruder_index))
- else:
- self.sendCommand("M105")
- temperature_request_timeout = time.time() + 5
+ def close(self):
+ super().close()
+ if self._serial is not None:
+ self._serial.close()
- if line.startswith(b"Error:"):
- # Oh YEAH, consistency.
- # Marlin reports a MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
- # But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!"
- # So we can have an extra newline in the most common case. Awesome work people.
- if re.match(b"Error:[0-9]\n", line):
- line = line.rstrip() + self._readline()
+ # Re-create the thread so it can be started again later.
+ self._update_thread = Thread(target=self._update, daemon=True)
+ self._serial = None
- # Skip the communication errors, as those get corrected.
- if b"Extruder switched off" in line or b"Temperature heated bed switched off" in line or b"Something is wrong, please turn off the printer." in line:
- if not self.hasError():
- self._setErrorState(line[6:])
+ ## Send a command to printer.
+ def sendCommand(self, command: Union[str, bytes]):
+ if self._is_printing:
+ self._command_queue.put(command)
+ elif self._connection_state == ConnectionState.connected:
+ self._sendCommand(command)
- elif b" T:" in line or line.startswith(b"T:"): # Temperature message
- temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
- temperature_set = False
- try:
- for match in temperature_matches:
- if match[0]:
- extruder_nr = int(match[0])
- if extruder_nr >= container_stack.getProperty("machine_extruder_count", "value"):
- continue
- if match[1]:
- self._setHotendTemperature(extruder_nr, float(match[1]))
- temperature_set = True
- if match[2]:
- self._updateTargetHotendTemperature(extruder_nr, float(match[2]))
- else:
- requested_temperatures = match
- if not temperature_set and requested_temperatures:
- if requested_temperatures[1]:
- self._setHotendTemperature(self._temperature_requested_extruder_index, float(requested_temperatures[1]))
- if requested_temperatures[2]:
- self._updateTargetHotendTemperature(self._temperature_requested_extruder_index, float(requested_temperatures[2]))
- except:
- Logger.log("w", "Could not parse hotend temperatures from response: %s", line)
- # Check if there's also a bed temperature
- temperature_matches = re.findall(b"B: ?([\d\.]+) ?\/?([\d\.]+)?", line)
- if container_stack.getProperty("machine_heated_bed", "value") and len(temperature_matches) > 0:
- match = temperature_matches[0]
- try:
- if match[0]:
- self._setBedTemperature(float(match[0]))
- if match[1]:
- self._updateTargetBedTemperature(float(match[1]))
- except:
- Logger.log("w", "Could not parse bed temperature from response: %s", line)
+ def _sendCommand(self, command: Union[str, bytes]):
+ if self._serial is None:
+ return
- elif b"_min" in line or b"_max" in line:
- tag, value = line.split(b":", 1)
- self._setEndstopState(tag,(b"H" in value or b"TRIGGERED" in value))
+ if type(command == str):
+ command = (command + "\n").encode()
+ if not command.endswith(b"\n"):
+ command += b"\n"
+ self._serial.write(b"\n")
+ self._serial.write(command)
+
+ def _update(self):
+ while self._connection_state == ConnectionState.connected and self._serial is not None:
+ try:
+ line = self._serial.readline()
+ except:
+ continue
+
+ if self._last_temperature_request is None or time() > self._last_temperature_request + self._timeout:
+ # Timeout, or no request has been sent at all.
+ self.sendCommand("M105")
+ self._last_temperature_request = time()
+
+ if b"ok T:" in line or line.startswith(b"T:"): # Temperature message
+ extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
+ # Update all temperature values
+ for match, extruder in zip(extruder_temperature_matches, self._printers[0].extruders):
+ if match[1]:
+ extruder.updateHotendTemperature(float(match[1]))
+ if match[2]:
+ extruder.updateTargetHotendTemperature(float(match[2]))
+
+ bed_temperature_matches = re.findall(b"B: ?([\d\.]+) ?\/?([\d\.]+)?", line)
+ if bed_temperature_matches:
+ match = bed_temperature_matches[0]
+ if match[0]:
+ self._printers[0].updateBedTemperature(float(match[0]))
+ if match[1]:
+ self._printers[0].updateTargetBedTemperature(float(match[1]))
if self._is_printing:
- if line == b"" and time.time() > ok_timeout:
- line = b"ok" # Force a timeout (basically, send next command)
-
if b"ok" in line:
- ok_timeout = time.time() + 5
if not self._command_queue.empty():
self._sendCommand(self._command_queue.get())
- elif self._is_paused:
- line = b"" # Force getting temperature as keep alive
+ elif self._paused:
+ pass # Nothing to do!
else:
self._sendNextGcodeLine()
- elif b"resend" in line.lower() or b"rs" in line: # Because a resend can be asked with "resend" and "rs"
+ elif b"resend" in line.lower() or b"rs" in line:
+ # A resend can be requested either by Resend, resend or rs.
try:
- Logger.log("d", "Got a resend response")
- self._gcode_position = int(line.replace(b"N:",b" ").replace(b"N",b" ").replace(b":",b" ").split()[-1])
+ self._gcode_position = int(line.replace(b"N:", b" ").replace(b"N", b" ").replace(b":", b" ").split()[-1])
except:
if b"rs" in line:
+ # In some cases of the RS command it needs to be handled differently.
self._gcode_position = int(line.split()[1])
- # Request the temperature on comm timeout (every 2 seconds) when we are not printing.)
- if line == b"":
- if self._num_extruders > 1:
- self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._num_extruders
- self.sendCommand("M105 T%d" % self._temperature_requested_extruder_index)
- else:
- self.sendCommand("M105")
+ def pausePrint(self):
+ self._paused = True
- Logger.log("i", "Printer connection listen thread stopped for %s" % self._serial_port)
+ def resumePrint(self):
+ self._paused = False
- ## Send next Gcode in the gcode list
- def _sendNextGcodeLine(self):
- if self._gcode_position >= len(self._gcode):
- return
- line = self._gcode[self._gcode_position]
-
- if ";" in line:
- line = line[:line.find(";")]
- line = line.strip()
-
- # Don't send empty lines. But we do have to send something, so send
- # m105 instead.
- # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as
- # an LCD menu pause.
- if line == "" or line == "M0" or line == "M1":
- line = "M105"
- try:
- if ("G0" in line or "G1" in line) and "Z" in line:
- z = float(re.search("Z([0-9\.]*)", line).group(1))
- if self._current_z != z:
- self._current_z = z
- except Exception as e:
- Logger.log("e", "Unexpected error with printer connection, could not parse current Z: %s: %s" % (e, line))
- self._setErrorState("Unexpected error: %s" %e)
- checksum = functools.reduce(lambda x,y: x^y, map(ord, "N%d%s" % (self._gcode_position, line)))
-
- self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum))
-
- progress = (self._gcode_position / len(self._gcode))
-
- elapsed_time = int(time.time() - self._print_start_time)
- self.setTimeElapsed(elapsed_time)
- estimated_time = self._print_estimated_time
- if progress > .1:
- estimated_time = self._print_estimated_time * (1-progress) + elapsed_time
- self.setTimeTotal(estimated_time)
-
- self._gcode_position += 1
- self.setProgress(progress * 100)
- self.progressChanged.emit()
-
- ## Set the state of the print.
- # Sent from the print monitor
- def _setJobState(self, job_state):
- if job_state == "pause":
- self._is_paused = True
- self._updateJobState("paused")
- elif job_state == "print":
- self._is_paused = False
- self._updateJobState("printing")
- elif job_state == "abort":
- self.cancelPrint()
-
- def _onJobStateChanged(self):
- # clear the job name & times when printing is done or aborted
- if self._job_state == "ready":
- self.setJobName("")
- self.setTimeElapsed(0)
- self.setTimeTotal(0)
-
- ## Set the progress of the print.
- # It will be normalized (based on max_progress) to range 0 - 100
- def setProgress(self, progress, max_progress = 100):
- self._progress = (progress / max_progress) * 100 # Convert to scale of 0-100
- if self._progress == 100:
- # Printing is done, reset progress
- self._gcode_position = 0
- self.setProgress(0)
- self._is_printing = False
- self._is_paused = False
- self._updateJobState("ready")
- self.progressChanged.emit()
-
- ## Cancel the current print. Printer connection wil continue to listen.
def cancelPrint(self):
self._gcode_position = 0
- self.setProgress(0)
- self._gcode = []
+ self._gcode.clear()
+ self._printers[0].updateActivePrintJob(None)
+ self._is_printing = False
+ self._is_paused = False
# Turn off temperatures, fan and steppers
self._sendCommand("M140 S0")
self._sendCommand("M104 S0")
self._sendCommand("M107")
+
# Home XY to prevent nozzle resting on aborted print
# Don't home bed because it may crash the printhead into the print on printers that home on the bottom
- self.homeHead()
+ self.printers[0].homeHead()
self._sendCommand("M84")
- self._is_printing = False
- self._is_paused = False
- self._updateJobState("ready")
- Application.getInstance().getController().setActiveStage("PrepareStage")
- ## Check if the process did not encounter an error yet.
- def hasError(self):
- return self._error_state is not None
+ def _sendNextGcodeLine(self):
+ if self._gcode_position >= len(self._gcode):
+ self._printers[0].updateActivePrintJob(None)
+ self._is_printing = False
+ return
+ line = self._gcode[self._gcode_position]
- ## private read line used by printer connection to listen for data on serial port.
- def _readline(self):
- if self._serial is None:
- return None
- try:
- ret = self._serial.readline()
- except Exception as e:
- Logger.log("e", "Unexpected error while reading serial port. %s" % e)
- self._setErrorState("Printer has been disconnected")
- self.close()
- return None
- return ret
+ if ";" in line:
+ line = line[:line.find(";")]
- ## Create a list of baud rates at which we can communicate.
- # \return list of int
- def _getBaudrateList(self):
- ret = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
- return ret
+ line = line.strip()
- def _onFirmwareUpdateComplete(self):
- self._update_firmware_thread.join()
- self._update_firmware_thread = threading.Thread(target = self._updateFirmware)
- self._update_firmware_thread.daemon = True
+ # Don't send empty lines. But we do have to send something, so send M105 instead.
+ # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
+ if line == "" or line == "M0" or line == "M1":
+ line = "M105"
- self.connect()
+ checksum = functools.reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (self._gcode_position, line)))
- ## Pre-heats the heated bed of the printer, if it has one.
- #
- # \param temperature The temperature to heat the bed to, in degrees
- # Celsius.
- # \param duration How long the bed should stay warm, in seconds. This is
- # ignored because there is no g-code to set this.
- @pyqtSlot(float, float)
- def preheatBed(self, temperature, duration):
- Logger.log("i", "Pre-heating the bed to %i degrees.", temperature)
- self._setTargetBedTemperature(temperature)
- self.preheatBedRemainingTimeChanged.emit()
+ self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum))
- ## Cancels pre-heating the heated bed of the printer.
- #
- # If the bed is not pre-heated, nothing happens.
- @pyqtSlot()
- def cancelPreheatBed(self):
- Logger.log("i", "Cancelling pre-heating of the bed.")
- self._setTargetBedTemperature(0)
- self.preheatBedRemainingTimeChanged.emit()
+ progress = (self._gcode_position / len(self._gcode))
+
+ elapsed_time = int(time() - self._print_start_time)
+ print_job = self._printers[0].activePrintJob
+ if print_job is None:
+ print_job = PrintJobOutputModel(output_controller = USBPrinterOutputController(self), name= Application.getInstance().getPrintInformation().jobName)
+ print_job.updateState("printing")
+ self._printers[0].updateActivePrintJob(print_job)
+
+ print_job.updateTimeElapsed(elapsed_time)
+ estimated_time = self._print_estimated_time
+ if progress > .1:
+ estimated_time = self._print_estimated_time * (1 - progress) + elapsed_time
+ print_job.updateTimeTotal(estimated_time)
+
+ self._gcode_position += 1
+
+
+class FirmwareUpdateState(IntEnum):
+ idle = 0
+ updating = 1
+ completed = 2
+ unknown_error = 3
+ communication_error = 4
+ io_error = 5
+ firmware_not_found_error = 6
\ No newline at end of file
diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py
index 62412bb521..58b6106fb0 100644
--- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py
+++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py
@@ -2,33 +2,32 @@
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Signal import Signal, signalemitter
-from . import USBPrinterOutputDevice
from UM.Application import Application
from UM.Resources import Resources
from UM.Logger import Logger
-from UM.PluginRegistry import PluginRegistry
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
-from cura.PrinterOutputDevice import ConnectionState
-from UM.Qt.ListModel import ListModel
-from UM.Message import Message
+from UM.i18n import i18nCatalog
+from cura.PrinterOutputDevice import ConnectionState
from cura.CuraApplication import CuraApplication
+from . import USBPrinterOutputDevice
+from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
+
import threading
import platform
import time
-import os.path
import serial.tools.list_ports
-from UM.Extension import Extension
-from PyQt5.QtCore import QUrl, QObject, pyqtSlot, pyqtProperty, pyqtSignal, Qt
-from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
-## Manager class that ensures that a usbPrinteroutput device is created for every connected USB printer.
+## Manager class that ensures that an USBPrinterOutput device is created for every connected USB printer.
@signalemitter
-class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
+class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
+ addUSBOutputDeviceSignal = Signal()
+ progressChanged = pyqtSignal()
+
def __init__(self, parent = None):
super().__init__(parent = parent)
self._serial_port_list = []
@@ -38,39 +37,10 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
self._update_thread.setDaemon(True)
self._check_updates = True
- self._firmware_view = None
Application.getInstance().applicationShuttingDown.connect(self.stop)
- self.addUSBOutputDeviceSignal.connect(self.addOutputDevice) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
-
- addUSBOutputDeviceSignal = Signal()
- connectionStateChanged = pyqtSignal()
-
- progressChanged = pyqtSignal()
- firmwareUpdateChange = pyqtSignal()
-
- @pyqtProperty(float, notify = progressChanged)
- def progress(self):
- progress = 0
- for printer_name, device in self._usb_output_devices.items(): # TODO: @UnusedVariable "printer_name"
- progress += device.progress
- return progress / len(self._usb_output_devices)
-
- @pyqtProperty(int, notify = progressChanged)
- def errorCode(self):
- for printer_name, device in self._usb_output_devices.items(): # TODO: @UnusedVariable "printer_name"
- if device._error_code:
- return device._error_code
- return 0
-
- ## Return True if all printers finished firmware update
- @pyqtProperty(float, notify = firmwareUpdateChange)
- def firmwareUpdateCompleteStatus(self):
- complete = True
- for printer_name, device in self._usb_output_devices.items(): # TODO: @UnusedVariable "printer_name"
- if not device.firmwareUpdateFinished:
- complete = False
- return complete
+ # Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
+ self.addUSBOutputDeviceSignal.connect(self.addOutputDevice)
def start(self):
self._check_updates = True
@@ -79,58 +49,28 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
def stop(self):
self._check_updates = False
- def _updateThread(self):
- while self._check_updates:
- result = self.getSerialPortList(only_list_usb = True)
- self._addRemovePorts(result)
- time.sleep(5)
-
- ## Show firmware interface.
- # This will create the view if its not already created.
- def spawnFirmwareInterface(self, serial_port):
- if self._firmware_view is None:
- path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
- self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self})
-
- self._firmware_view.show()
-
- @pyqtSlot(str)
- def updateAllFirmware(self, file_name):
- if file_name.startswith("file://"):
- file_name = QUrl(file_name).toLocalFile() # File dialogs prepend the path with file://, which we don't need / want
-
- if not self._usb_output_devices:
- Message(i18n_catalog.i18nc("@info", "Unable to update firmware because there are no printers connected."), title = i18n_catalog.i18nc("@info:title", "Warning")).show()
+ def _onConnectionStateChanged(self, serial_port):
+ if serial_port not in self._usb_output_devices:
return
- for printer_connection in self._usb_output_devices:
- self._usb_output_devices[printer_connection].resetFirmwareUpdate()
- self.spawnFirmwareInterface("")
- for printer_connection in self._usb_output_devices:
- try:
- self._usb_output_devices[printer_connection].updateFirmware(file_name)
- except FileNotFoundError:
- # Should only happen in dev environments where the resources/firmware folder is absent.
- self._usb_output_devices[printer_connection].setProgress(100, 100)
- Logger.log("w", "No firmware found for printer %s called '%s'", printer_connection, file_name)
- Message(i18n_catalog.i18nc("@info",
- "Could not find firmware required for the printer at %s.") % printer_connection, title = i18n_catalog.i18nc("@info:title", "Printer Firmware")).show()
- self._firmware_view.close()
+ changed_device = self._usb_output_devices[serial_port]
+ if changed_device.connectionState == ConnectionState.connected:
+ self.getOutputDeviceManager().addOutputDevice(changed_device)
+ else:
+ self.getOutputDeviceManager().removeOutputDevice(serial_port)
+ def _updateThread(self):
+ while self._check_updates:
+ container_stack = Application.getInstance().getGlobalContainerStack()
+ if container_stack is None:
+ time.sleep(5)
continue
-
- @pyqtSlot(str, str, result = bool)
- def updateFirmwareBySerial(self, serial_port, file_name):
- if serial_port in self._usb_output_devices:
- self.spawnFirmwareInterface(self._usb_output_devices[serial_port].getSerialPort())
- try:
- self._usb_output_devices[serial_port].updateFirmware(file_name)
- except FileNotFoundError:
- self._firmware_view.close()
- Logger.log("e", "Could not find firmware required for this machine called '%s'", file_name)
- return False
- return True
- return False
+ if container_stack.getMetaDataEntry("supports_usb_connection"):
+ port_list = self.getSerialPortList(only_list_usb=True)
+ else:
+ port_list = [] # Just use an empty list; all USB devices will be removed.
+ self._addRemovePorts(port_list)
+ time.sleep(5)
## Return the singleton instance of the USBPrinterManager
@classmethod
@@ -191,7 +131,11 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
Logger.log("w", "There is no firmware for machine %s.", machine_id)
if hex_file:
- return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
+ try:
+ return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
+ except FileNotFoundError:
+ Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
+ return ""
else:
Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
return ""
@@ -205,46 +149,16 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
continue
self._serial_port_list = list(serial_ports)
- devices_to_remove = []
for port, device in self._usb_output_devices.items():
if port not in self._serial_port_list:
device.close()
- devices_to_remove.append(port)
-
- for port in devices_to_remove:
- del self._usb_output_devices[port]
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
def addOutputDevice(self, serial_port):
device = USBPrinterOutputDevice.USBPrinterOutputDevice(serial_port)
device.connectionStateChanged.connect(self._onConnectionStateChanged)
- device.connect()
- device.progressChanged.connect(self.progressChanged)
- device.firmwareUpdateChange.connect(self.firmwareUpdateChange)
self._usb_output_devices[serial_port] = device
-
- ## If one of the states of the connected devices change, we might need to add / remove them from the global list.
- def _onConnectionStateChanged(self, serial_port):
- success = True
- try:
- if self._usb_output_devices[serial_port].connectionState == ConnectionState.connected:
- self.getOutputDeviceManager().addOutputDevice(self._usb_output_devices[serial_port])
- else:
- success = success and self.getOutputDeviceManager().removeOutputDevice(serial_port)
- if success:
- self.connectionStateChanged.emit()
- except KeyError:
- Logger.log("w", "Connection state of %s changed, but it was not found in the list")
-
- @pyqtProperty(QObject , notify = connectionStateChanged)
- def connectedPrinterList(self):
- self._usb_output_devices_model = ListModel()
- self._usb_output_devices_model.addRoleName(Qt.UserRole + 1, "name")
- self._usb_output_devices_model.addRoleName(Qt.UserRole + 2, "printer")
- for connection in self._usb_output_devices:
- if self._usb_output_devices[connection].connectionState == ConnectionState.connected:
- self._usb_output_devices_model.appendItem({"name": connection, "printer": self._usb_output_devices[connection]})
- return self._usb_output_devices_model
+ device.connect()
## Create a list of serial ports on the system.
# \param only_list_usb If true, only usb ports are listed
diff --git a/plugins/USBPrinting/__init__.py b/plugins/USBPrinting/__init__.py
index 1cc45c3c3b..7bf5853c10 100644
--- a/plugins/USBPrinting/__init__.py
+++ b/plugins/USBPrinting/__init__.py
@@ -1,17 +1,18 @@
-# Copyright (c) 2015 Ultimaker B.V.
+# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import USBPrinterOutputDeviceManager
-from PyQt5.QtQml import qmlRegisterType, qmlRegisterSingletonType
+from PyQt5.QtQml import qmlRegisterSingletonType
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
+
def getMetaData():
- return {
- }
+ return {}
+
def register(app):
# We are violating the QT API here (as we use a factory, which is technically not allowed).
# but we don't really have another means for doing this (and it seems to you know -work-)
qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
- return {"extension":USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance(), "output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance()}
+ return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance()}
diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py
index 1bf0b98217..e21256f6bd 100644
--- a/plugins/UltimakerMachineActions/UM2UpgradeSelection.py
+++ b/plugins/UltimakerMachineActions/UM2UpgradeSelection.py
@@ -1,8 +1,7 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.InstanceContainer import InstanceContainer
from cura.MachineAction import MachineAction
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
@@ -11,8 +10,6 @@ from UM.Application import Application
from UM.Util import parseBool
catalog = i18nCatalog("cura")
-import UM.Settings.InstanceContainer
-
## The Ultimaker 2 can have a few revisions & upgrades.
class UM2UpgradeSelection(MachineAction):
@@ -22,18 +19,28 @@ class UM2UpgradeSelection(MachineAction):
self._container_registry = ContainerRegistry.getInstance()
+ self._current_global_stack = None
+
+ Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
+ self._reset()
+
def _reset(self):
self.hasVariantsChanged.emit()
+ def _onGlobalStackChanged(self):
+ if self._current_global_stack:
+ self._current_global_stack.metaDataChanged.disconnect(self._onGlobalStackMetaDataChanged)
+
+ self._current_global_stack = Application.getInstance().getGlobalContainerStack()
+ if self._current_global_stack:
+ self._current_global_stack.metaDataChanged.connect(self._onGlobalStackMetaDataChanged)
+ self._reset()
+
+ def _onGlobalStackMetaDataChanged(self):
+ self._reset()
+
hasVariantsChanged = pyqtSignal()
- @pyqtProperty(bool, notify = hasVariantsChanged)
- def hasVariants(self):
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack:
- return parseBool(global_container_stack.getMetaDataEntry("has_variants", "false"))
-
- @pyqtSlot(bool)
def setHasVariants(self, has_variants = True):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
@@ -62,3 +69,9 @@ class UM2UpgradeSelection(MachineAction):
global_container_stack.extruders["0"].variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
Application.getInstance().globalContainerStackChanged.emit()
+ self._reset()
+
+ @pyqtProperty(bool, fset = setHasVariants, notify = hasVariantsChanged)
+ def hasVariants(self):
+ if self._current_global_stack:
+ return parseBool(self._current_global_stack.getMetaDataEntry("has_variants", "false"))
diff --git a/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml
index 988c9d6128..793f3f00a8 100644
--- a/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml
+++ b/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml
@@ -13,6 +13,7 @@ import Cura 1.0 as Cura
Cura.MachineAction
{
anchors.fill: parent;
+
Item
{
id: upgradeSelectionMachineAction
@@ -39,12 +40,19 @@ Cura.MachineAction
CheckBox
{
+ id: olssonBlockCheckBox
anchors.top: pageDescription.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@label", "Olsson Block")
checked: manager.hasVariants
- onClicked: manager.setHasVariants(checked)
+ onClicked: manager.hasVariants = checked
+
+ Connections
+ {
+ target: manager
+ onHasVariantsChanged: olssonBlockCheckBox.checked = manager.hasVariants
+ }
}
UM.I18nCatalog { id: catalog; name: "cura"; }
diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
index 5a1f8f26a7..b92638aa12 100644
--- a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
+++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
@@ -180,7 +180,7 @@ Cura.MachineAction
height: childrenRect.height
anchors.top: nozzleTempLabel.top
anchors.left: bedTempStatus.right
- anchors.leftMargin: UM.Theme.getSize("default_margin").width/2
+ anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
visible: checkupMachineAction.usbConnected
Button
{
@@ -241,7 +241,7 @@ Cura.MachineAction
height: childrenRect.height
anchors.top: bedTempLabel.top
anchors.left: bedTempStatus.right
- anchors.leftMargin: UM.Theme.getSize("default_margin").width/2
+ anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
Button
{
diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml
index 72a77e992d..f36788daa5 100644
--- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml
+++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml
@@ -14,6 +14,9 @@ import Cura 1.0 as Cura
Cura.MachineAction
{
anchors.fill: parent;
+ property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
+ property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
+
Item
{
id: upgradeFirmwareMachineAction
@@ -60,16 +63,17 @@ Cura.MachineAction
{
id: autoUpgradeButton
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
- enabled: parent.firmwareName != ""
+ enabled: parent.firmwareName != "" && activeOutputDevice
onClicked:
{
- Cura.USBPrinterManager.updateAllFirmware(parent.firmwareName)
+ activeOutputDevice.updateFirmware(parent.firmwareName)
}
}
Button
{
id: manualUpgradeButton
text: catalog.i18nc("@action:button", "Upload custom Firmware");
+ enabled: activeOutputDevice != null
onClicked:
{
customFirmwareDialog.open()
@@ -83,7 +87,7 @@ Cura.MachineAction
title: catalog.i18nc("@title:window", "Select custom firmware")
nameFilters: "Firmware image files (*.hex)"
selectExisting: true
- onAccepted: Cura.USBPrinterManager.updateAllFirmware(fileUrl)
+ onAccepted: activeOutputDevice.updateFirmware(fileUrl)
}
}
}
\ No newline at end of file
diff --git a/plugins/UltimakerMachineActions/__init__.py b/plugins/UltimakerMachineActions/__init__.py
index 864c501392..495f212736 100644
--- a/plugins/UltimakerMachineActions/__init__.py
+++ b/plugins/UltimakerMachineActions/__init__.py
@@ -3,7 +3,6 @@
from . import BedLevelMachineAction
from . import UpgradeFirmwareMachineAction
-from . import UMOCheckupMachineAction
from . import UMOUpgradeSelection
from . import UM2UpgradeSelection
@@ -18,7 +17,6 @@ def register(app):
return { "machine_action": [
BedLevelMachineAction.BedLevelMachineAction(),
UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(),
- UMOCheckupMachineAction.UMOCheckupMachineAction(),
UMOUpgradeSelection.UMOUpgradeSelection(),
UM2UpgradeSelection.UM2UpgradeSelection()
]}
diff --git a/plugins/UserAgreementPlugin/UserAgreement.qml b/plugins/UserAgreementPlugin/UserAgreement.qml
index c7f3f165e3..4ee03f4ad5 100644
--- a/plugins/UserAgreementPlugin/UserAgreement.qml
+++ b/plugins/UserAgreementPlugin/UserAgreement.qml
@@ -9,8 +9,8 @@ import UM 1.3 as UM
UM.Dialog
{
id: baseDialog
- minimumWidth: Math.floor(UM.Theme.getSize("modal_window_minimum").width * 0.75)
- minimumHeight: Math.floor(UM.Theme.getSize("modal_window_minimum").height * 0.5)
+ minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width * 0.75)
+ minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height * 0.5)
width: minimumWidth
height: minimumHeight
title: catalog.i18nc("@title:window", "User Agreement")
diff --git a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py
index 19f0563f10..7505911049 100644
--- a/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py
+++ b/plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py
@@ -74,7 +74,7 @@ class VersionUpgrade22to24(VersionUpgrade):
def __convertVariant(self, variant_path):
# Copy the variant to the machine_instances/*_settings.inst.cfg
variant_config = configparser.ConfigParser(interpolation=None)
- with open(variant_path, "r") as fhandle:
+ with open(variant_path, "r", encoding = "utf-8") as fhandle:
variant_config.read_file(fhandle)
config_name = "Unknown Variant"
diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py
index c01ff158b1..a88ff5ac1c 100644
--- a/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py
+++ b/plugins/VersionUpgrade/VersionUpgrade30to31/VersionUpgrade30to31.py
@@ -3,14 +3,9 @@
import configparser #To parse preference files.
import io #To serialise the preference files afterwards.
-import os
-from urllib.parse import quote_plus
-from UM.Resources import Resources
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
-from cura.CuraApplication import CuraApplication
-
# a list of all legacy "Not Supported" quality profiles
_OLD_NOT_SUPPORTED_PROFILES = [
@@ -59,6 +54,12 @@ _EMPTY_CONTAINER_DICT = {
}
+# Renamed definition files
+_RENAMED_DEFINITION_DICT = {
+ "jellybox": "imade3d_jellybox",
+}
+
+
class VersionUpgrade30to31(VersionUpgrade):
## Gets the version number from a CFG file in Uranium's 3.0 format.
#
@@ -111,31 +112,9 @@ class VersionUpgrade30to31(VersionUpgrade):
if not parser.has_section(each_section):
parser.add_section(each_section)
- # Copy global quality changes to extruder quality changes for single extrusion machines
- if parser["metadata"]["type"] == "quality_changes":
- all_quality_changes = self._getSingleExtrusionMachineQualityChanges(parser)
- # Note that DO NOT!!! use the quality_changes returned from _getSingleExtrusionMachineQualityChanges().
- # Those are loaded from the hard drive which are original files that haven't been upgraded yet.
- # NOTE 2: The number can be 0 or 1 depends on whether you are loading it from the qualities folder or
- # from a project file. When you load from a project file, the custom profile may not be in cura
- # yet, so you will get 0.
- if len(all_quality_changes) <= 1 and not parser.has_option("metadata", "extruder"):
- self._createExtruderQualityChangesForSingleExtrusionMachine(filename, parser)
-
- if parser["metadata"]["type"] == "definition_changes":
- if parser["general"]["definition"] == "custom":
-
- # We are only interested in machine_nozzle_size
- if parser.has_option("values", "machine_nozzle_size"):
- machine_nozzle_size = parser["values"]["machine_nozzle_size"]
-
- definition_name = parser["general"]["name"]
- machine_extruders = self._getSingleExtrusionMachineExtruders(definition_name)
-
- #For single extuder machine we nee only first extruder
- if len(machine_extruders) !=0:
- if self._updateSingleExtuderDefinitionFile(machine_extruders, machine_nozzle_size):
- parser.remove_option("values", "machine_nozzle_size")
+ # Check renamed definitions
+ if "definition" in parser["general"] and parser["general"]["definition"] in _RENAMED_DEFINITION_DICT:
+ parser["general"]["definition"] = _RENAMED_DEFINITION_DICT[parser["general"]["definition"]]
# Update version numbers
parser["general"]["version"] = "2"
@@ -146,7 +125,6 @@ class VersionUpgrade30to31(VersionUpgrade):
parser.write(output)
return [filename], [output.getvalue()]
-
## Upgrades a container stack from version 3.0 to 3.1.
#
# \param serialised The serialised form of a container stack.
@@ -171,6 +149,10 @@ class VersionUpgrade30to31(VersionUpgrade):
if parser.has_option("containers", key) and parser["containers"][key] == "empty":
parser["containers"][key] = specific_empty_container
+ # check renamed definition
+ if parser.has_option("containers", "6") and parser["containers"]["6"] in _RENAMED_DEFINITION_DICT:
+ parser["containers"]["6"] = _RENAMED_DEFINITION_DICT[parser["containers"]["6"]]
+
# Update version numbers
if "general" not in parser:
parser["general"] = {}
@@ -184,194 +166,3 @@ class VersionUpgrade30to31(VersionUpgrade):
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]
-
- def _getSingleExtrusionMachineQualityChanges(self, quality_changes_container):
- quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
- quality_changes_containers = []
-
- for item in os.listdir(quality_changes_dir):
- file_path = os.path.join(quality_changes_dir, item)
- if not os.path.isfile(file_path):
- continue
-
- parser = configparser.ConfigParser(interpolation = None)
- try:
- parser.read([file_path])
- except:
- # skip, it is not a valid stack file
- continue
-
- if not parser.has_option("metadata", "type"):
- continue
- if "quality_changes" != parser["metadata"]["type"]:
- continue
-
- if not parser.has_option("general", "name"):
- continue
- if quality_changes_container["general"]["name"] != parser["general"]["name"]:
- continue
-
- quality_changes_containers.append(parser)
-
- return quality_changes_containers
-
- def _getSingleExtrusionMachineExtruders(self, definition_name):
-
- machine_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.MachineStack)
-
- machine_instances = []
-
- #Find all machine instances
- for item in os.listdir(machine_instances_dir):
- file_path = os.path.join(machine_instances_dir, item)
- if not os.path.isfile(file_path):
- continue
-
- parser = configparser.ConfigParser(interpolation=None)
- try:
- parser.read([file_path])
- except:
- # skip, it is not a valid stack file
- continue
-
- if not parser.has_option("metadata", "type"):
- continue
- if "machine" != parser["metadata"]["type"]:
- continue
-
- if not parser.has_option("general", "id"):
- continue
-
- machine_instances.append(parser)
-
- #Find for extruders
- extruders_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack)
- #"machine",[extruders]
- extruder_instances_per_machine = {}
-
- #Find all custom extruders for founded machines
- for item in os.listdir(extruders_instances_dir):
- file_path = os.path.join(extruders_instances_dir, item)
- if not os.path.isfile(file_path):
- continue
-
- parser = configparser.ConfigParser(interpolation=None)
- try:
- parser.read([file_path])
- except:
- # skip, it is not a valid stack file
- continue
-
- if not parser.has_option("metadata", "type"):
- continue
- if "extruder_train" != parser["metadata"]["type"]:
- continue
-
- if not parser.has_option("metadata", "machine"):
- continue
- if not parser.has_option("metadata", "position"):
- continue
-
-
- for machine_instace in machine_instances:
-
- machine_id = machine_instace["general"]["id"]
- if machine_id != parser["metadata"]["machine"]:
- continue
-
- if machine_id + "_settings" != definition_name:
- continue
-
- if extruder_instances_per_machine.get(machine_id) is None:
- extruder_instances_per_machine.update({machine_id:[]})
-
- extruder_instances_per_machine.get(machine_id).append(parser)
- #the extruder can be related only to one machine
- break
-
- return extruder_instances_per_machine
-
- #Find extruder defition at index 0 and update its values
- def _updateSingleExtuderDefinitionFile(self, extruder_instances_per_machine, machine_nozzle_size):
-
- defintion_instances_dir = Resources.getPath(CuraApplication.ResourceTypes.DefinitionChangesContainer)
-
- for item in os.listdir(defintion_instances_dir):
- file_path = os.path.join(defintion_instances_dir, item)
- if not os.path.isfile(file_path):
- continue
-
- parser = configparser.ConfigParser(interpolation=None)
- try:
- parser.read([file_path])
- except:
- # skip, it is not a valid stack file
- continue
-
- if not parser.has_option("general", "name"):
- continue
- name = parser["general"]["name"]
- custom_extruder_at_0_position = None
- for machine_extruders in extruder_instances_per_machine:
- for extruder_instance in extruder_instances_per_machine[machine_extruders]:
-
- if extruder_instance["general"]["id"] + "_settings" == name:
- defition_position = extruder_instance["metadata"]["position"]
-
- if defition_position == "0":
- custom_extruder_at_0_position = extruder_instance
- break
- if custom_extruder_at_0_position is not None:
- break
-
- #If not null, then parsed file is for first extuder and then can be updated. I need to update only
- # first, because this update for single extuder machine
- if custom_extruder_at_0_position is not None:
-
- #Add new value
- parser["values"]["machine_nozzle_size"] = machine_nozzle_size
-
- definition_output = io.StringIO()
- parser.write(definition_output)
-
- with open(file_path, "w") as f:
- f.write(definition_output.getvalue())
-
- return True
-
- return False
-
-
- def _createExtruderQualityChangesForSingleExtrusionMachine(self, filename, global_quality_changes):
- suffix = "_" + quote_plus(global_quality_changes["general"]["name"].lower())
- machine_name = os.path.os.path.basename(filename).replace(".inst.cfg", "").replace(suffix, "")
-
- # Why is this here?!
- # When we load a .curaprofile file the deserialize will trigger a version upgrade, creating a dangling file.
- # This file can be recognized by it's lack of a machine name in the target filename.
- # So when we detect that situation here, we don't create the file and return.
- if machine_name == "":
- return
-
- new_filename = machine_name + "_" + "fdmextruder" + suffix
-
- extruder_quality_changes_parser = configparser.ConfigParser(interpolation = None)
- extruder_quality_changes_parser.add_section("general")
- extruder_quality_changes_parser["general"]["version"] = str(2)
- extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"]
- extruder_quality_changes_parser["general"]["definition"] = global_quality_changes["general"]["definition"]
-
- extruder_quality_changes_parser.add_section("metadata")
- extruder_quality_changes_parser["metadata"]["quality_type"] = global_quality_changes["metadata"]["quality_type"]
- extruder_quality_changes_parser["metadata"]["type"] = global_quality_changes["metadata"]["type"]
- extruder_quality_changes_parser["metadata"]["setting_version"] = str(4)
- extruder_quality_changes_parser["metadata"]["extruder"] = "fdmextruder"
-
- extruder_quality_changes_output = io.StringIO()
- extruder_quality_changes_parser.write(extruder_quality_changes_output)
- extruder_quality_changes_filename = quote_plus(new_filename) + ".inst.cfg"
-
- quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
-
- with open(os.path.join(quality_changes_dir, extruder_quality_changes_filename), "w") as f:
- f.write(extruder_quality_changes_output.getvalue())
diff --git a/plugins/X3DReader/X3DReader.py b/plugins/X3DReader/X3DReader.py
index e4a59dcdaa..b0b9e00a5b 100644
--- a/plugins/X3DReader/X3DReader.py
+++ b/plugins/X3DReader/X3DReader.py
@@ -11,7 +11,7 @@ from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader
-from UM.Scene.SceneNode import SceneNode
+from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
MYPY = False
try:
@@ -19,63 +19,63 @@ try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
-
+
# TODO: preserve the structure of scenes that contain several objects
-# Use CADPart, for example, to distinguish between separate objects
-
+# Use CADPart, for example, to distinguish between separate objects
+
DEFAULT_SUBDIV = 16 # Default subdivision factor for spheres, cones, and cylinders
EPSILON = 0.000001
class Shape:
-
+
# Expects verts in MeshBuilder-ready format, as a n by 3 mdarray
# with vertices stored in rows
def __init__(self, verts, faces, index_base, name):
self.verts = verts
self.faces = faces
# Those are here for debugging purposes only
- self.index_base = index_base
+ self.index_base = index_base
self.name = name
-
+
class X3DReader(MeshReader):
def __init__(self):
super().__init__()
self._supported_extensions = [".x3d"]
self._namespaces = {}
-
+
# Main entry point
# Reads the file, returns a SceneNode (possibly with nested ones), or None
def read(self, file_name):
try:
self.defs = {}
self.shapes = []
-
+
tree = ET.parse(file_name)
xml_root = tree.getroot()
-
+
if xml_root.tag != "X3D":
return None
- scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters
+ scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters
if xml_root[0].tag == "head":
for head_node in xml_root[0]:
if head_node.tag == "unit" and head_node.attrib.get("category") == "length":
scale *= float(head_node.attrib["conversionFactor"])
- break
+ break
xml_scene = xml_root[1]
else:
xml_scene = xml_root[0]
-
+
if xml_scene.tag != "Scene":
return None
-
+
self.transform = Matrix()
self.transform.setByScaleFactor(scale)
self.index_base = 0
-
+
# Traverse the scene tree, populate the shapes list
self.processChildNodes(xml_scene)
-
+
if self.shapes:
builder = MeshBuilder()
builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes]))
@@ -95,20 +95,20 @@ class X3DReader(MeshReader):
else:
return None
-
+
except Exception:
Logger.logException("e", "Exception in X3D reader")
return None
return node
-
+
# ------------------------- XML tree traversal
-
+
def processNode(self, xml_node):
xml_node = self.resolveDefUse(xml_node)
if xml_node is None:
return
-
+
tag = xml_node.tag
if tag in ("Group", "StaticGroup", "CADAssembly", "CADFace", "CADLayer", "Collision"):
self.processChildNodes(xml_node)
@@ -120,8 +120,8 @@ class X3DReader(MeshReader):
self.processTransform(xml_node)
elif tag == "Shape":
self.processShape(xml_node)
-
-
+
+
def processShape(self, xml_node):
# Find the geometry and the appearance inside the Shape
geometry = appearance = None
@@ -130,21 +130,21 @@ class X3DReader(MeshReader):
appearance = self.resolveDefUse(sub_node)
elif sub_node.tag in self.geometry_importers and not geometry:
geometry = self.resolveDefUse(sub_node)
-
- # TODO: appearance is completely ignored. At least apply the material color...
+
+ # TODO: appearance is completely ignored. At least apply the material color...
if not geometry is None:
try:
- self.verts = self.faces = [] # Safeguard
+ self.verts = self.faces = [] # Safeguard
self.geometry_importers[geometry.tag](self, geometry)
m = self.transform.getData()
verts = m.dot(self.verts)[:3].transpose()
-
+
self.shapes.append(Shape(verts, self.faces, self.index_base, geometry.tag))
self.index_base += len(verts)
-
+
except Exception:
Logger.logException("e", "Exception in X3D reader while reading %s", geometry.tag)
-
+
# Returns the referenced node if the node has USE, the same node otherwise.
# May return None is USE points at a nonexistent node
# In X3DOM, when both DEF and USE are in the same node, DEF is ignored.
@@ -155,36 +155,36 @@ class X3DReader(MeshReader):
if USE:
return self.defs.get(USE, None)
- DEF = node.attrib.get("DEF")
+ DEF = node.attrib.get("DEF")
if DEF:
- self.defs[DEF] = node
+ self.defs[DEF] = node
return node
-
+
def processChildNodes(self, node):
for c in node:
self.processNode(c)
Job.yieldThread()
-
+
# Since this is a grouping node, will recurse down the tree.
# According to the spec, the final transform matrix is:
# T * C * R * SR * S * -SR * -C
# Where SR corresponds to the rotation matrix to scaleOrientation
- # C and SR are rather exotic. S, slightly less so.
+ # C and SR are rather exotic. S, slightly less so.
def processTransform(self, node):
rot = readRotation(node, "rotation", (0, 0, 1, 0)) # (angle, axisVactor) tuple
trans = readVector(node, "translation", (0, 0, 0)) # Vector
scale = readVector(node, "scale", (1, 1, 1)) # Vector
center = readVector(node, "center", (0, 0, 0)) # Vector
scale_orient = readRotation(node, "scaleOrientation", (0, 0, 1, 0)) # (angle, axisVactor) tuple
-
- # Store the previous transform; in Cura, the default matrix multiplication is in place
+
+ # Store the previous transform; in Cura, the default matrix multiplication is in place
prev = Matrix(self.transform.getData()) # It's deep copy, I've checked
-
+
# The rest of transform manipulation will be applied in place
got_center = (center.x != 0 or center.y != 0 or center.z != 0)
-
+
T = self.transform
- if trans.x != 0 or trans.y != 0 or trans.z !=0:
+ if trans.x != 0 or trans.y != 0 or trans.z != 0:
T.translate(trans)
if got_center:
T.translate(center)
@@ -202,13 +202,13 @@ class X3DReader(MeshReader):
T.rotateByAxis(-scale_orient[0], scale_orient[1])
if got_center:
T.translate(-center)
-
+
self.processChildNodes(node)
self.transform = prev
-
+
# ------------------------- Geometry importers
# They are supposed to fill the self.verts and self.faces arrays, the caller will do the rest
-
+
# Primitives
def processGeometryBox(self, node):
@@ -228,14 +228,14 @@ class X3DReader(MeshReader):
self.addVertex(-dx, -dy, dz)
self.addVertex(-dx, -dy, -dz)
self.addVertex(dx, -dy, -dz)
-
+
self.addQuad(0, 1, 2, 3) # +y
self.addQuad(4, 0, 3, 7) # +x
self.addQuad(7, 3, 2, 6) # -z
self.addQuad(6, 2, 1, 5) # -x
self.addQuad(5, 1, 0, 4) # +z
self.addQuad(7, 6, 5, 4) # -y
-
+
# The sphere is subdivided into nr rings and ns segments
def processGeometrySphere(self, node):
r = readFloat(node, "radius", 0.5)
@@ -247,16 +247,16 @@ class X3DReader(MeshReader):
(nr, ns) = subdiv
else:
nr = ns = DEFAULT_SUBDIV
-
+
lau = pi / nr # Unit angle of latitude (rings) for the given tesselation
lou = 2 * pi / ns # Unit angle of longitude (segments)
-
+
self.reserveFaceAndVertexCount(ns*(nr*2 - 2), 2 + (nr - 1)*ns)
-
+
# +y and -y poles
self.addVertex(0, r, 0)
self.addVertex(0, -r, 0)
-
+
# The non-polar vertices go from x=0, negative z plane counterclockwise -
# to -x, to +z, to +x, back to -z
for ring in range(1, nr):
@@ -264,12 +264,12 @@ class X3DReader(MeshReader):
self.addVertex(-r*sin(lou * seg) * sin(lau * ring),
r*cos(lau * ring),
-r*cos(lou * seg) * sin(lau * ring))
-
+
vb = 2 + (nr - 2) * ns # First vertex index for the bottom cap
-
+
# Faces go in order: top cap, sides, bottom cap.
# Sides go by ring then by segment.
-
+
# Caps
# Top cap face vertices go in order: down right up
# (starting from +y pole)
@@ -277,7 +277,7 @@ class X3DReader(MeshReader):
for seg in range(ns):
self.addTri(0, seg + 2, (seg + 1) % ns + 2)
self.addTri(1, vb + (seg + 1) % ns, vb + seg)
-
+
# Sides
# Side face vertices go in order: down right upleft, downright up left
for ring in range(nr - 2):
@@ -288,24 +288,24 @@ class X3DReader(MeshReader):
for seg in range(ns):
nseg = (seg + 1) % ns
self.addQuad(tvb + seg, bvb + seg, bvb + nseg, tvb + nseg)
-
+
def processGeometryCone(self, node):
r = readFloat(node, "bottomRadius", 1)
height = readFloat(node, "height", 2)
bottom = readBoolean(node, "bottom", True)
side = readBoolean(node, "side", True)
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
-
+
d = height / 2
angle = 2 * pi / n
-
+
self.reserveFaceAndVertexCount((n if side else 0) + (n-2 if bottom else 0), n+1)
-
+
# Vertex 0 is the apex, vertices 1..n are the bottom
self.addVertex(0, d, 0)
for i in range(n):
self.addVertex(-r * sin(angle * i), -d, -r * cos(angle * i))
-
+
# Side face vertices go: up down right
if side:
for i in range(n):
@@ -313,7 +313,7 @@ class X3DReader(MeshReader):
if bottom:
for i in range(2, n):
self.addTri(1, i, i+1)
-
+
def processGeometryCylinder(self, node):
r = readFloat(node, "radius", 1)
height = readFloat(node, "height", 2)
@@ -321,13 +321,13 @@ class X3DReader(MeshReader):
side = readBoolean(node, "side", True)
top = readBoolean(node, "top", True)
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
-
+
nn = n * 2
angle = 2 * pi / n
hh = height/2
-
+
self.reserveFaceAndVertexCount((nn if side else 0) + (n - 2 if top else 0) + (n - 2 if bottom else 0), nn)
-
+
# The seam is at x=0, z=-r, vertices go ccw -
# to pos x, to neg z, to neg x, back to neg z
for i in range(n):
@@ -335,18 +335,18 @@ class X3DReader(MeshReader):
rc = -r * cos(angle * i)
self.addVertex(rs, hh, rc)
self.addVertex(rs, -hh, rc)
-
+
if side:
for i in range(n):
ni = (i + 1) % n
self.addQuad(ni * 2 + 1, ni * 2, i * 2, i * 2 + 1)
-
+
for i in range(2, nn-3, 2):
if top:
self.addTri(0, i, i+2)
if bottom:
self.addTri(1, i+1, i+3)
-
+
# Semi-primitives
def processGeometryElevationGrid(self, node):
@@ -356,21 +356,21 @@ class X3DReader(MeshReader):
nz = readInt(node, "zDimension", 0)
height = readFloatArray(node, "height", False)
ccw = readBoolean(node, "ccw", True)
-
+
if nx <= 0 or nz <= 0 or len(height) < nx*nz:
return # That's weird, the wording of the standard suggests grids with zero quads are somehow valid
-
+
self.reserveFaceAndVertexCount(2*(nx-1)*(nz-1), nx*nz)
-
+
for z in range(nz):
for x in range(nx):
self.addVertex(x * dx, height[z*nx + x], z * dz)
-
+
for z in range(1, nz):
for x in range(1, nx):
self.addTriFlip((z - 1)*nx + x - 1, z*nx + x, (z - 1)*nx + x, ccw)
self.addTriFlip((z - 1)*nx + x - 1, z*nx + x - 1, z*nx + x, ccw)
-
+
def processGeometryExtrusion(self, node):
ccw = readBoolean(node, "ccw", True)
begin_cap = readBoolean(node, "beginCap", True)
@@ -384,23 +384,23 @@ class X3DReader(MeshReader):
# This converts X3D's axis/angle rotation to a 3x3 numpy matrix
def toRotationMatrix(rot):
(x, y, z) = rot[:3]
- a = rot[3]
+ a = rot[3]
s = sin(a)
c = cos(a)
t = 1-c
return numpy.array((
(x * x * t + c, x * y * t - z*s, x * z * t + y * s),
(x * y * t + z*s, y * y * t + c, y * z * t - x * s),
- (x * z * t - y * s, y * z * t + x * s, z * z * t + c)))
-
+ (x * z * t - y * s, y * z * t + x * s, z * z * t + c)))
+
orient = [toRotationMatrix(orient[i:i+4]) if orient[i+3] != 0 else None for i in range(0, len(orient), 4)]
-
+
scale = readFloatArray(node, "scale", None)
if scale:
scale = [numpy.array(((scale[i], 0, 0), (0, 1, 0), (0, 0, scale[i+1])))
if scale[i] != 1 or scale[i+1] != 1 else None for i in range(0, len(scale), 2)]
-
-
+
+
# Special treatment for the closed spine and cross section.
# Let's save some memory by not creating identical but distinct vertices;
# later we'll introduce conditional logic to link the last vertex with
@@ -413,14 +413,14 @@ class X3DReader(MeshReader):
ncf = nc if crossClosed else nc - 1
# Face count along the cross; for closed cross, it's the same as the
# respective vertex count
-
+
spine_closed = spine[0] == spine[-1]
if spine_closed:
spine = spine[:-1]
ns = len(spine)
spine = [Vector(*s) for s in spine]
nsf = ns if spine_closed else ns - 1
-
+
# This will be used for fallback, where the current spine point joins
# two collinear spine segments. No need to recheck the case of the
# closed spine/last-to-first point juncture; if there's an angle there,
@@ -442,7 +442,7 @@ class X3DReader(MeshReader):
if v.cross(orig_y).length() > EPSILON:
# Spine at angle with global y - rotate the z accordingly
a = v.cross(orig_y) # Axis of rotation to get to the Z
- (x, y, z) = a.normalized().getData()
+ (x, y, z) = a.normalized().getData()
s = a.length()/v.length()
c = sqrt(1-s*s)
t = 1-c
@@ -452,7 +452,7 @@ class X3DReader(MeshReader):
(x * z * t + y * s, y * z * t - x * s, z * z * t + c)))
orig_z = Vector(*m.dot(orig_z.getData()))
return orig_z
-
+
self.reserveFaceAndVertexCount(2*nsf*ncf + (nc - 2 if begin_cap else 0) + (nc - 2 if end_cap else 0), ns*nc)
z = None
@@ -482,151 +482,151 @@ class X3DReader(MeshReader):
y = spt - sprev
# If there's more than one point in the spine, z is already set.
# One point in the spline is an error anyway.
-
+
z = z.normalized()
y = y.normalized()
x = y.cross(z) # Already normalized
m = numpy.array(((x.x, y.x, z.x), (x.y, y.y, z.y), (x.z, y.z, z.z)))
-
+
# Columns are the unit vectors for the xz plane for the cross-section
if orient:
mrot = orient[i] if len(orient) > 1 else orient[0]
if not mrot is None:
m = m.dot(mrot) # Tested against X3DOM, the result matches, still not sure :(
-
+
if scale:
mscale = scale[i] if len(scale) > 1 else scale[0]
if not mscale is None:
m = m.dot(mscale)
-
+
# First the cross-section 2-vector is scaled,
# then rotated (which may make it a 3-vector),
# then applied to the xz plane unit vectors
-
+
sptv3 = numpy.array(spt.getData()[:3])
for cpt in cross:
v = sptv3 + m.dot(cpt)
self.addVertex(*v)
-
+
if begin_cap:
self.addFace([x for x in range(nc - 1, -1, -1)], ccw)
-
+
# Order of edges in the face: forward along cross, forward along spine,
# backward along cross, backward along spine, flipped if now ccw.
# This order is assumed later in the texture coordinate assignment;
# please don't change without syncing.
-
+
for s in range(ns - 1):
for c in range(ncf):
self.addQuadFlip(s * nc + c, s * nc + (c + 1) % nc,
(s + 1) * nc + (c + 1) % nc, (s + 1) * nc + c, ccw)
-
+
if spine_closed:
# The faces between the last and the first spine points
b = (ns - 1) * nc
for c in range(ncf):
self.addQuadFlip(b + c, b + (c + 1) % nc,
(c + 1) % nc, c, ccw)
-
+
if end_cap:
self.addFace([(ns - 1) * nc + x for x in range(0, nc)], ccw)
-
+
# Triangle meshes
# Helper for numerous nodes with a Coordinate subnode holding vertices
# That all triangle meshes and IndexedFaceSet
- # num_faces can be a function, in case the face count is a function of vertex count
+ # num_faces can be a function, in case the face count is a function of vertex count
def startCoordMesh(self, node, num_faces):
ccw = readBoolean(node, "ccw", True)
self.readVertices(node) # This will allocate and fill the vertex array
if hasattr(num_faces, "__call__"):
num_faces = num_faces(self.getVertexCount())
self.reserveFaceCount(num_faces)
-
+
return ccw
-
+
def processGeometryIndexedTriangleSet(self, node):
index = readIntArray(node, "index", [])
num_faces = len(index) // 3
ccw = int(self.startCoordMesh(node, num_faces))
-
+
for i in range(0, num_faces*3, 3):
self.addTri(index[i + 1 - ccw], index[i + ccw], index[i+2])
-
+
def processGeometryIndexedTriangleStripSet(self, node):
strips = readIndex(node, "index")
ccw = int(self.startCoordMesh(node, sum([len(strip) - 2 for strip in strips])))
-
+
for strip in strips:
sccw = ccw # Running CCW value, reset for each strip
for i in range(len(strip) - 2):
self.addTri(strip[i + 1 - sccw], strip[i + sccw], strip[i+2])
sccw = 1 - sccw
-
+
def processGeometryIndexedTriangleFanSet(self, node):
fans = readIndex(node, "index")
ccw = int(self.startCoordMesh(node, sum([len(fan) - 2 for fan in fans])))
-
+
for fan in fans:
for i in range(1, len(fan) - 1):
self.addTri(fan[0], fan[i + 1 - ccw], fan[i + ccw])
-
+
def processGeometryTriangleSet(self, node):
ccw = int(self.startCoordMesh(node, lambda num_vert: num_vert // 3))
for i in range(0, self.getVertexCount(), 3):
self.addTri(i + 1 - ccw, i + ccw, i+2)
-
+
def processGeometryTriangleStripSet(self, node):
strips = readIntArray(node, "stripCount", [])
ccw = int(self.startCoordMesh(node, sum([n-2 for n in strips])))
-
+
vb = 0
for n in strips:
sccw = ccw
- for i in range(n-2):
+ for i in range(n-2):
self.addTri(vb + i + 1 - sccw, vb + i + sccw, vb + i + 2)
sccw = 1 - sccw
vb += n
-
+
def processGeometryTriangleFanSet(self, node):
fans = readIntArray(node, "fanCount", [])
ccw = int(self.startCoordMesh(node, sum([n-2 for n in fans])))
-
+
vb = 0
for n in fans:
- for i in range(1, n-1):
+ for i in range(1, n-1):
self.addTri(vb, vb + i + 1 - ccw, vb + i + ccw)
vb += n
-
+
# Quad geometries from the CAD module, might be relevant for printing
-
+
def processGeometryQuadSet(self, node):
ccw = self.startCoordMesh(node, lambda num_vert: 2*(num_vert // 4))
for i in range(0, self.getVertexCount(), 4):
self.addQuadFlip(i, i+1, i+2, i+3, ccw)
-
+
def processGeometryIndexedQuadSet(self, node):
index = readIntArray(node, "index", [])
num_quads = len(index) // 4
ccw = self.startCoordMesh(node, num_quads*2)
-
+
for i in range(0, num_quads*4, 4):
self.addQuadFlip(index[i], index[i+1], index[i+2], index[i+3], ccw)
-
+
# 2D polygon geometries
# Won't work for now, since Cura expects every mesh to have a nontrivial convex hull
# The only way around that is merging meshes.
-
+
def processGeometryDisk2D(self, node):
innerRadius = readFloat(node, "innerRadius", 0)
outerRadius = readFloat(node, "outerRadius", 1)
n = readInt(node, "subdivision", DEFAULT_SUBDIV)
-
+
angle = 2 * pi / n
-
+
self.reserveFaceAndVertexCount(n*4 if innerRadius else n-2, n*2 if innerRadius else n)
-
+
for i in range(n):
s = sin(angle * i)
c = cos(angle * i)
@@ -635,11 +635,11 @@ class X3DReader(MeshReader):
self.addVertex(innerRadius*c, innerRadius*s, 0)
ni = (i+1) % n
self.addQuad(2*i, 2*ni, 2*ni+1, 2*i+1)
-
+
if not innerRadius:
for i in range(2, n):
self.addTri(0, i-1, i)
-
+
def processGeometryRectangle2D(self, node):
(x, y) = readFloatArray(node, "size", (2, 2))
self.reserveFaceAndVertexCount(2, 4)
@@ -648,7 +648,7 @@ class X3DReader(MeshReader):
self.addVertex(x/2, y/2, 0)
self.addVertex(-x/2, y/2, 0)
self.addQuad(0, 1, 2, 3)
-
+
def processGeometryTriangleSet2D(self, node):
verts = readFloatArray(node, "vertices", ())
num_faces = len(verts) // 6;
@@ -656,25 +656,25 @@ class X3DReader(MeshReader):
self.reserveFaceAndVertexCount(num_faces, num_faces * 3)
for vert in verts:
self.addVertex(*vert)
-
+
# The front face is on the +Z side, so CCW is a variable
for i in range(0, num_faces*3, 3):
a = Vector(*verts[i+2]) - Vector(*verts[i])
b = Vector(*verts[i+1]) - Vector(*verts[i])
self.addTriFlip(i, i+1, i+2, a.x*b.y > a.y*b.x)
-
+
# General purpose polygon mesh
def processGeometryIndexedFaceSet(self, node):
faces = readIndex(node, "coordIndex")
ccw = self.startCoordMesh(node, sum([len(face) - 2 for face in faces]))
-
+
for face in faces:
if len(face) == 3:
self.addTriFlip(face[0], face[1], face[2], ccw)
elif len(face) > 3:
self.addFace(face, ccw)
-
+
geometry_importers = {
"IndexedFaceSet": processGeometryIndexedFaceSet,
"IndexedTriangleSet": processGeometryIndexedTriangleSet,
@@ -695,7 +695,7 @@ class X3DReader(MeshReader):
"Cylinder": processGeometryCylinder,
"Cone": processGeometryCone
}
-
+
# Parses the Coordinate.@point field, fills the verts array.
def readVertices(self, node):
for c in node:
@@ -704,9 +704,9 @@ class X3DReader(MeshReader):
if not c is None:
pt = c.attrib.get("point")
if pt:
- # allow the list of float values in 'point' attribute to
- # be separated by commas or whitespace as per spec of
- # XML encoding of X3D
+ # allow the list of float values in 'point' attribute to
+ # be separated by commas or whitespace as per spec of
+ # XML encoding of X3D
# Ref ISO/IEC 19776-1:2015 : Section 5.1.2
co = [float(x) for vec in pt.split(',') for x in vec.split()]
num_verts = len(co) // 3
@@ -715,57 +715,57 @@ class X3DReader(MeshReader):
# Group by three
for i in range(num_verts):
self.verts[:3,i] = co[3*i:3*i+3]
-
+
# Mesh builder helpers
-
+
def reserveFaceAndVertexCount(self, num_faces, num_verts):
# Unlike the Cura MeshBuilder, we use 4-vectors stored as columns for easier transform
self.verts = numpy.zeros((4, num_verts), dtype=numpy.float32)
self.verts[3,:] = numpy.ones((num_verts), dtype=numpy.float32)
self.num_verts = 0
self.reserveFaceCount(num_faces)
-
+
def reserveFaceCount(self, num_faces):
self.faces = numpy.zeros((num_faces, 3), dtype=numpy.int32)
self.num_faces = 0
-
+
def getVertexCount(self):
return self.verts.shape[1]
-
+
def addVertex(self, x, y, z):
self.verts[0, self.num_verts] = x
self.verts[1, self.num_verts] = y
self.verts[2, self.num_verts] = z
self.num_verts += 1
-
+
# Indices are 0-based for this shape, but they won't be zero-based in the merged mesh
def addTri(self, a, b, c):
self.faces[self.num_faces, 0] = self.index_base + a
self.faces[self.num_faces, 1] = self.index_base + b
self.faces[self.num_faces, 2] = self.index_base + c
self.num_faces += 1
-
+
def addTriFlip(self, a, b, c, ccw):
if ccw:
self.addTri(a, b, c)
else:
self.addTri(b, a, c)
-
+
# Needs to be convex, but not necessaily planar
# Assumed ccw, cut along the ac diagonal
def addQuad(self, a, b, c, d):
self.addTri(a, b, c)
self.addTri(c, d, a)
-
+
def addQuadFlip(self, a, b, c, d, ccw):
if ccw:
self.addTri(a, b, c)
self.addTri(c, d, a)
else:
self.addTri(a, c, b)
- self.addTri(c, a, d)
-
-
+ self.addTri(c, a, d)
+
+
# Arbitrary polygon triangulation.
# Doesn't assume convexity and doesn't check the "convex" flag in the file.
# Works by the "cutting of ears" algorithm:
@@ -776,13 +776,13 @@ class X3DReader(MeshReader):
def addFace(self, indices, ccw):
# Resolve indices to coordinates for faster math
face = [Vector(data=self.verts[0:3, i]) for i in indices]
-
+
# Need a normal to the plane so that we can know which vertices form inner angles
normal = findOuterNormal(face)
-
+
if not normal: # Couldn't find an outer edge, non-planar polygon maybe?
return
-
+
# Find the vertex with the smallest inner angle and no points inside, cut off. Repeat until done
n = len(face)
vi = [i for i in range(n)] # We'll be using this to kick vertices from the face
@@ -807,17 +807,17 @@ class X3DReader(MeshReader):
if pointInsideTriangle(vx, next, prev, nextXprev):
no_points_inside = False
break
-
+
if no_points_inside:
max_cos = cos
i_min = i
-
+
self.addTriFlip(indices[vi[(i_min + n - 1) % n]], indices[vi[i_min]], indices[vi[(i_min + 1) % n]], ccw)
vi.pop(i_min)
n -= 1
self.addTriFlip(indices[vi[0]], indices[vi[1]], indices[vi[2]], ccw)
-
+
# ------------------------------------------------------------
# X3D field parsers
# ------------------------------------------------------------
@@ -844,7 +844,7 @@ def readInt(node, attr, default):
if not s:
return default
return int(s, 0)
-
+
def readBoolean(node, attr, default):
s = node.attrib.get(attr)
if not s:
@@ -873,8 +873,8 @@ def readIndex(node, attr):
chunk.append(v[i])
if chunk:
chunks.append(chunk)
- return chunks
-
+ return chunks
+
# Given a face as a sequence of vectors, returns a normal to the polygon place that forms a right triple
# with a vector along the polygon sequence and a vector backwards
def findOuterNormal(face):
@@ -894,25 +894,25 @@ def findOuterNormal(face):
if rejection.dot(prev_rejection) < -EPSILON: # points on both sides of the edge - not an outer one
is_outer = False
break
- elif rejection.length() > prev_rejection.length(): # Pick a greater rejection for numeric stability
+ elif rejection.length() > prev_rejection.length(): # Pick a greater rejection for numeric stability
prev_rejection = rejection
-
+
if is_outer: # Found an outer edge, prev_rejection is the rejection inside the face. Generate a normal.
return edge.cross(prev_rejection)
return False
-# Given two *collinear* vectors a and b, returns the coefficient that takes b to a.
+# Given two *collinear* vectors a and b, returns the coefficient that takes b to a.
# No error handling.
-# For stability, taking the ration between the biggest coordinates would be better...
+# For stability, taking the ration between the biggest coordinates would be better...
def ratio(a, b):
if b.x > EPSILON or b.x < -EPSILON:
return a.x / b.x
elif b.y > EPSILON or b.y < -EPSILON:
return a.y / b.y
else:
- return a.z / b.z
-
+ return a.z / b.z
+
def pointInsideTriangle(vx, next, prev, nextXprev):
vxXprev = vx.cross(prev)
r = ratio(vxXprev, nextXprev)
@@ -921,4 +921,4 @@ def pointInsideTriangle(vx, next, prev, nextXprev):
vxXnext = vx.cross(next);
s = -ratio(vxXnext, nextXprev)
return s > 0 and (s + r) < 1
-
+
diff --git a/plugins/XRayView/XRayView.py b/plugins/XRayView/XRayView.py
index 35509a9715..0c4035c62d 100644
--- a/plugins/XRayView/XRayView.py
+++ b/plugins/XRayView/XRayView.py
@@ -2,10 +2,13 @@
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
+from PyQt5.QtGui import QOpenGLContext
from UM.Application import Application
+from UM.Logger import Logger
from UM.Math.Color import Color
from UM.PluginRegistry import PluginRegistry
+from UM.Platform import Platform
from UM.Event import Event
from UM.View.View import View
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
@@ -13,6 +16,8 @@ from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
+from cura.CuraApplication import CuraApplication
+
from . import XRayPass
## View used to display a see-through version of objects with errors highlighted.
@@ -52,6 +57,23 @@ class XRayView(View):
def event(self, event):
if event.type == Event.ViewActivateEvent:
+ # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
+ # This can happen when you do the following steps:
+ # 1. Start Cura
+ # 2. Load a model
+ # 3. Switch to Custom mode
+ # 4. Select the model and click on the per-object tool icon
+ # 5. Switch view to Layer view or X-Ray
+ # 6. Cura will very likely crash
+ # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why.
+ # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL
+ # context is None.
+ if Platform.isOSX():
+ if QOpenGLContext.currentContext() is None:
+ Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
+ CuraApplication.getInstance().callLater(lambda e = event: self.event(e))
+ return
+
if not self._xray_pass:
# Currently the RenderPass constructor requires a size > 0
# This should be fixed in RenderPass's constructor.
diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py
index 03fc3b5521..c580fd4458 100644
--- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py
+++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py
@@ -17,6 +17,8 @@ import UM.Dictionary
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
+from .XmlMaterialValidator import XmlMaterialValidator
+
## Handles serializing and deserializing material containers from an XML file
class XmlMaterialProfile(InstanceContainer):
CurrentFdmMaterialVersion = "1.3"
@@ -46,18 +48,35 @@ class XmlMaterialProfile(InstanceContainer):
## Overridden from InstanceContainer
# set the meta data for all machine / variant combinations
- def setMetaDataEntry(self, key, value):
+ #
+ # The "apply_to_all" flag indicates whether this piece of metadata should be applied to all material containers
+ # or just this specific container.
+ # For example, when you change the material name, you want to apply it to all its derived containers, but for
+ # some specific settings, they should only be applied to a machine/variant-specific container.
+ #
+ def setMetaDataEntry(self, key, value, apply_to_all = True):
registry = ContainerRegistry.getInstance()
if registry.isReadOnly(self.getId()):
return
- super().setMetaDataEntry(key, value)
+ # Prevent recursion
+ if not apply_to_all:
+ super().setMetaDataEntry(key, value)
+ return
- basefile = self.getMetaDataEntry("base_file", self.getId()) #if basefile is self.getId, this is a basefile.
- # Update all containers that share basefile
- for container in registry.findInstanceContainers(base_file = basefile):
- if container.getMetaDataEntry(key, None) != value: # Prevent recursion
- container.setMetaDataEntry(key, value)
+ # Get the MaterialGroup
+ material_manager = CuraApplication.getInstance().getMaterialManager()
+ root_material_id = self.getMetaDataEntry("base_file") #if basefile is self.getId, this is a basefile.
+ material_group = material_manager.getMaterialGroup(root_material_id)
+
+ # Update the root material container
+ root_material_container = material_group.root_material_node.getContainer()
+ root_material_container.setMetaDataEntry(key, value, apply_to_all = False)
+
+ # Update all containers derived from it
+ for node in material_group.derived_material_node_list:
+ container = node.getContainer()
+ container.setMetaDataEntry(key, value, apply_to_all = False)
## Overridden from InstanceContainer, similar to setMetaDataEntry.
# without this function the setName would only set the name of the specific nozzle / material / machine combination container
@@ -189,6 +208,8 @@ class XmlMaterialProfile(InstanceContainer):
machine_container_map = {}
machine_nozzle_map = {}
+ variant_manager = CuraApplication.getInstance()._variant_manager
+
all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId())
for container in all_containers:
definition_id = container.getDefinition().getId()
@@ -201,9 +222,10 @@ class XmlMaterialProfile(InstanceContainer):
if definition_id not in machine_nozzle_map:
machine_nozzle_map[definition_id] = {}
- variant = container.getMetaDataEntry("variant")
- if variant:
- machine_nozzle_map[definition_id][variant] = container
+ variant_name = container.getMetaDataEntry("variant_name")
+ if variant_name:
+ machine_nozzle_map[definition_id][variant_name] = variant_manager.getVariantNode(definition_id,
+ variant_name)
continue
machine_container_map[definition_id] = container
@@ -235,16 +257,12 @@ class XmlMaterialProfile(InstanceContainer):
self._addSettingElement(builder, instance)
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
- for hotend_id, hotend in machine_nozzle_map[definition_id].items():
- variant_containers = registry.findInstanceContainersMetadata(id = hotend.getMetaDataEntry("variant"))
- if not variant_containers:
- continue
-
+ for hotend_name, variant_node in machine_nozzle_map[definition_id].items():
# The hotend identifier is not the containers name, but its "name".
- builder.start("hotend", {"id": variant_containers[0]["name"]})
+ builder.start("hotend", {"id": hotend_name})
# Compatible is a special case, as it's added as a meta data entry (instead of an instance).
- compatible = hotend.getMetaDataEntry("compatible")
+ compatible = variant_node.metadata.get("compatible")
if compatible is not None:
builder.start("setting", {"key": "hardware compatible"})
if compatible:
@@ -253,7 +271,7 @@ class XmlMaterialProfile(InstanceContainer):
builder.data("no")
builder.end("setting")
- for instance in hotend.findInstances():
+ for instance in variant_node.getContainer().findInstances():
if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value:
# If the settings match that of the machine profile, just skip since we inherit the machine profile.
continue
@@ -481,6 +499,10 @@ class XmlMaterialProfile(InstanceContainer):
if "adhesion_info" not in meta_data:
meta_data["adhesion_info"] = ""
+ validation_message = XmlMaterialValidator.validateMaterialMetaData(meta_data)
+ if validation_message is not None:
+ raise Exception("Not valid material profile: %s" % (validation_message))
+
property_values = {}
properties = data.iterfind("./um:properties/*", self.__namespaces)
for entry in properties:
@@ -552,10 +574,8 @@ class XmlMaterialProfile(InstanceContainer):
for machine_id in machine_id_list:
definitions = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
if not definitions:
- Logger.log("w", "No definition found for machine ID %s", machine_id)
continue
- Logger.log("d", "Found definition for machine ID %s", machine_id)
definition = definitions[0]
machine_manufacturer = identifier.get("manufacturer", definition.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
@@ -590,18 +610,48 @@ class XmlMaterialProfile(InstanceContainer):
if is_new_material:
containers_to_add.append(new_material)
- hotends = machine.iterfind("./um:hotend", self.__namespaces)
- for hotend in hotends:
- hotend_id = hotend.get("id")
- if hotend_id is None:
+ # Find the buildplates compatibility
+ buildplates = machine.iterfind("./um:buildplate", self.__namespaces)
+ buildplate_map = {}
+ buildplate_map["buildplate_compatible"] = {}
+ buildplate_map["buildplate_recommended"] = {}
+ for buildplate in buildplates:
+ buildplate_id = buildplate.get("id")
+ if buildplate_id is None:
continue
- variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
- if not variant_containers:
- # It is not really properly defined what "ID" is so also search for variants by name.
- variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
+ from cura.Machines.VariantManager import VariantType
+ variant_manager = CuraApplication.getInstance().getVariantManager()
+ variant_node = variant_manager.getVariantNode(machine_id, buildplate_id)
+ if not variant_node:
+ continue
- if not variant_containers:
+ buildplate_compatibility = machine_compatibility
+ buildplate_recommended = machine_compatibility
+ settings = buildplate.iterfind("./um:setting", self.__namespaces)
+ for entry in settings:
+ key = entry.get("key")
+ if key in self.__unmapped_settings:
+ if key == "hardware compatible":
+ buildplate_compatibility = self._parseCompatibleValue(entry.text)
+ elif key == "hardware recommended":
+ buildplate_recommended = self._parseCompatibleValue(entry.text)
+ else:
+ Logger.log("d", "Unsupported material setting %s", key)
+
+ buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
+ buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended
+
+ hotends = machine.iterfind("./um:hotend", self.__namespaces)
+ for hotend in hotends:
+ # The "id" field for hotends in material profiles are actually
+ hotend_name = hotend.get("id")
+ if hotend_name is None:
+ continue
+
+ variant_manager = CuraApplication.getInstance().getVariantManager()
+ variant_node = variant_manager.getVariantNode(machine_id, hotend_name)
+ if not variant_node:
continue
hotend_compatibility = machine_compatibility
@@ -623,26 +673,28 @@ class XmlMaterialProfile(InstanceContainer):
key = entry.get("key")
hotend_setting_values[key] = entry.text
- new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
+ new_hotend_specific_material_id = self.getId() + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
- # Same as machine compatibility, keep the derived material containers consistent with the parent
- # material
- if ContainerRegistry.getInstance().isLoaded(new_hotend_id):
- new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0]
+ # Same as machine compatibility, keep the derived material containers consistent with the parent material
+ if ContainerRegistry.getInstance().isLoaded(new_hotend_specific_material_id):
+ new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_specific_material_id)[0]
is_new_material = False
else:
- new_hotend_material = XmlMaterialProfile(new_hotend_id)
+ new_hotend_material = XmlMaterialProfile(new_hotend_specific_material_id)
is_new_material = True
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
- new_hotend_material.getMetaData()["id"] = new_hotend_id
+ new_hotend_material.getMetaData()["id"] = new_hotend_specific_material_id
new_hotend_material.getMetaData()["name"] = self.getName()
- new_hotend_material.getMetaData()["variant"] = variant_containers[0]["id"]
+ new_hotend_material.getMetaData()["variant_name"] = hotend_name
new_hotend_material.setDefinition(machine_id)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_hotend_material.getMetaData()["definition"] = machine_id
+ if buildplate_map["buildplate_compatible"]:
+ new_hotend_material.getMetaData()["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
+ new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"]
cached_hotend_setting_properties = cached_machine_setting_properties.copy()
cached_hotend_setting_properties.update(hotend_setting_values)
@@ -754,10 +806,8 @@ class XmlMaterialProfile(InstanceContainer):
for machine_id in machine_id_list:
definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
if not definition_metadata:
- Logger.log("w", "No definition found for machine ID %s", machine_id)
continue
- Logger.log("d", "Found def for machine [%s]", machine_id)
definition_metadata = definition_metadata[0]
machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
@@ -784,15 +834,38 @@ class XmlMaterialProfile(InstanceContainer):
if len(found_materials) == 0: #This is a new material.
result_metadata.append(new_material_metadata)
- for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
- hotend_id = hotend.get("id")
- if hotend_id is None:
+ buildplates = machine.iterfind("./um:buildplate", cls.__namespaces)
+ buildplate_map = {}
+ buildplate_map["buildplate_compatible"] = {}
+ buildplate_map["buildplate_recommended"] = {}
+ for buildplate in buildplates:
+ buildplate_id = buildplate.get("id")
+ if buildplate_id is None:
continue
- variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
+ variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = buildplate_id)
if not variant_containers:
# It is not really properly defined what "ID" is so also search for variants by name.
- variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
+ variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = buildplate_id)
+
+ if not variant_containers:
+ continue
+
+ settings = buildplate.iterfind("./um:setting", cls.__namespaces)
+ for entry in settings:
+ key = entry.get("key")
+ if key == "hardware compatible":
+ buildplate_compatibility = cls._parseCompatibleValue(entry.text)
+ elif key == "hardware recommended":
+ buildplate_recommended = cls._parseCompatibleValue(entry.text)
+
+ buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_map["buildplate_compatible"]
+ buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_map["buildplate_recommended"]
+
+ for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
+ hotend_name = hotend.get("id")
+ if hotend_name is None:
+ continue
hotend_compatibility = machine_compatibility
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
@@ -800,26 +873,24 @@ class XmlMaterialProfile(InstanceContainer):
if key == "hardware compatible":
hotend_compatibility = cls._parseCompatibleValue(entry.text)
- new_hotend_id = container_id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
+ new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
- # Same as machine compatibility, keep the derived material containers consistent with the parent
- # material
- found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id)
+ # Same as machine compatibility, keep the derived material containers consistent with the parent material
+ found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_specific_material_id)
if found_materials:
new_hotend_material_metadata = found_materials[0]
else:
new_hotend_material_metadata = {}
new_hotend_material_metadata.update(base_metadata)
- if variant_containers:
- new_hotend_material_metadata["variant"] = variant_containers[0]["id"]
- else:
- new_hotend_material_metadata["variant"] = hotend_id
- _with_missing_variants.append(new_hotend_material_metadata)
+ new_hotend_material_metadata["variant_name"] = hotend_name
new_hotend_material_metadata["compatible"] = hotend_compatibility
new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer
- new_hotend_material_metadata["id"] = new_hotend_id
+ new_hotend_material_metadata["id"] = new_hotend_specific_material_id
new_hotend_material_metadata["definition"] = machine_id
+ if buildplate_map["buildplate_compatible"]:
+ new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
+ new_hotend_material_metadata["buildplate_recommended"] = buildplate_map["buildplate_recommended"]
if len(found_materials) == 0:
result_metadata.append(new_hotend_material_metadata)
@@ -871,11 +942,11 @@ class XmlMaterialProfile(InstanceContainer):
else:
merged_name_parts.append(part)
- id_list = [name.lower().replace(" ", ""), # simply removing all spaces
+ id_list = {name.lower().replace(" ", ""), # simply removing all spaces
name.lower().replace(" ", "_"), # simply replacing all spaces with underscores
"_".join(merged_name_parts),
- ]
-
+ }
+ id_list = list(id_list)
return id_list
## Gets a mapping from product names in the XML files to their definition
@@ -902,7 +973,7 @@ class XmlMaterialProfile(InstanceContainer):
# Map XML file setting names to internal names
__material_settings_setting_map = {
"print temperature": "default_material_print_temperature",
- "heated bed temperature": "material_bed_temperature",
+ "heated bed temperature": "default_material_bed_temperature",
"standby temperature": "material_standby_temperature",
"processing temperature graph": "material_flow_temp_graph",
"print cooling": "cool_fan_speed",
@@ -912,7 +983,8 @@ class XmlMaterialProfile(InstanceContainer):
"surface energy": "material_surface_energy"
}
__unmapped_settings = [
- "hardware compatible"
+ "hardware compatible",
+ "hardware recommended"
]
__material_properties_setting_map = {
"diameter": "material_diameter"
@@ -949,21 +1021,3 @@ def _indent(elem, level = 0):
# before the last }
def _tag_without_namespace(element):
return element.tag[element.tag.rfind("}") + 1:]
-
-#While loading XML profiles, some of these profiles don't know what variant
-#they belong to. We'd like to search by the machine ID and the variant's
-#name, but we don't know the variant's ID. Not all variants have been loaded
-#yet so we can't run a filter on the name and machine. The ID is unknown
-#so we can't lazily load the variant either. So we have to wait until all
-#the rest is loaded properly and then assign the correct variant to the
-#material files that were missing it.
-_with_missing_variants = []
-def _fillMissingVariants():
- registry = ContainerRegistry.getInstance()
- for variant_metadata in _with_missing_variants:
- variants = registry.findContainersMetadata(definition = variant_metadata["definition"], name = variant_metadata["variant"])
- if not variants:
- Logger.log("w", "Could not find variant for variant-specific material {material_id}.".format(material_id = variant_metadata["id"]))
- continue
- variant_metadata["variant"] = variants[0]["id"]
-ContainerRegistry.allMetadataLoaded.connect(_fillMissingVariants)
diff --git a/plugins/XmlMaterialProfile/XmlMaterialValidator.py b/plugins/XmlMaterialProfile/XmlMaterialValidator.py
new file mode 100644
index 0000000000..f11c8bea4b
--- /dev/null
+++ b/plugins/XmlMaterialProfile/XmlMaterialValidator.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+
+
+class XmlMaterialValidator():
+
+ @classmethod
+ def validateMaterialMetaData(cls, validation_metadata):
+
+ if validation_metadata.get("GUID") is None:
+ return "Missing GUID"
+
+ if validation_metadata.get("brand") is None:
+ return "Missing Brand"
+
+ if validation_metadata.get("material") is None:
+ return "Missing Material"
+
+ if validation_metadata.get("version") is None:
+ return "Missing Version"
+
+ return None
+
+
diff --git a/resources/definitions/3dator.def.json b/resources/definitions/3dator.def.json
index 2ec7119656..19307bfddd 100644
--- a/resources/definitions/3dator.def.json
+++ b/resources/definitions/3dator.def.json
@@ -47,9 +47,9 @@
"default_value": 30
},
"machine_start_gcode": {
- "default_value": ";Sliced at: {day} {date} {time}\nM104 S{material_print_temperature} ;set temperatures\nM140 S{material_bed_temperature}\nM109 S{material_print_temperature} ;wait for temperatures\nM190 S{material_bed_temperature}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 Z0 ;move Z to min endstops\nG28 X0 Y0 ;move X/Y to min endstops\nG29 ;Auto Level\nG1 Z0.6 F{travel_speed} ;move the Nozzle near the Bed\nG92 E0\nG1 Y0 ;zero the extruded length\nG1 X10 E30 F500 ;printing a Line from right to left\nG92 E0 ;zero the extruded length again\nG1 Z2\nG1 F{travel_speed}\nM117 Printing...;Put printing message on LCD screen\nM150 R255 U255 B255 P4 ;Change LED Color to white" },
+ "default_value": ";Sliced at: {day} {date} {time}\nM104 S{material_print_temperature} ;set temperatures\nM140 S{material_bed_temperature}\nM109 S{material_print_temperature} ;wait for temperatures\nM190 S{material_bed_temperature}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 Z0 ;move Z to min endstops\nG28 X0 Y0 ;move X/Y to min endstops\nG29 ;Auto Level\nG1 Z0.6 F{speed_travel} ;move the Nozzle near the Bed\nG92 E0\nG1 Y0 ;zero the extruded length\nG1 X10 E30 F500 ;printing a Line from right to left\nG92 E0 ;zero the extruded length again\nG1 Z2\nG1 F{speed_travel}\nM117 Printing...;Put printing message on LCD screen\nM150 R255 U255 B255 P4 ;Change LED Color to white" },
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
+ "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
}
}
}
diff --git a/resources/definitions/alya3dp.def.json b/resources/definitions/alya3dp.def.json
index 2fda102249..6bf5d89a95 100644
--- a/resources/definitions/alya3dp.def.json
+++ b/resources/definitions/alya3dp.def.json
@@ -40,10 +40,10 @@
"default_value": "RepRap"
},
"machine_start_gcode": {
- "default_value": ";Sliced at: {day} {date} {time}\n;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\n;Print time: {print_time}\n;Filament used: {filament_amount}m {filament_weight}g\n;Filament cost: {filament_cost}\n;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line\n;M109 S{print_temperature} ;Uncomment to add your own temperature line\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to max endstops\nG1 Z115.0 F{travel_speed} ;move th e platform up 20mm\nG28 Z0 ;move Z to max endstop\nG1 Z15.0 F{travel_speed} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM301 H1 P26.38 I2.57 D67.78\n;Put printing message on LCD screen\nM117 Printing..."
+ "default_value": ";Sliced at: {day} {date} {time}\n;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\n;Print time: {print_time}\n;Filament used: {filament_amount}m {filament_weight}g\n;Filament cost: {filament_cost}\n;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line\n;M109 S{print_temperature} ;Uncomment to add your own temperature line\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to max endstops\nG1 Z115.0 F{speed_travel} ;move th e platform up 20mm\nG28 Z0 ;move Z to max endstop\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM301 H1 P26.38 I2.57 D67.78\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_end_gcode": {
- "default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG28 Z0\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}"
+ "default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG28 Z0\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}"
}
}
}
\ No newline at end of file
diff --git a/resources/definitions/anycubic_i3_mega.def.json b/resources/definitions/anycubic_i3_mega.def.json
new file mode 100644
index 0000000000..7106038193
--- /dev/null
+++ b/resources/definitions/anycubic_i3_mega.def.json
@@ -0,0 +1,69 @@
+{
+ "version": 2,
+ "name": "Anycubic i3 Mega",
+ "inherits": "fdmprinter",
+ "metadata":
+ {
+ "visible": true,
+ "author": "TheTobby",
+ "manufacturer": "Anycubic",
+ "file_formats": "text/x-gcode",
+ "icon": "icon_ultimaker2",
+ "platform": "anycubic_i3_mega_platform.stl",
+ "has_materials": false,
+ "has_machine_quality": true,
+ "preferred_quality_type": "normal"
+ },
+
+ "overrides":
+ {
+ "machine_name":
+ {
+ "default_value": "Anycubic i3 Mega"
+ },
+ "machine_heated_bed":
+ {
+ "default_value": true
+ },
+ "machine_width":
+ {
+ "default_value": 210
+ },
+ "machine_height":
+ {
+ "default_value": 205
+ },
+ "machine_depth":
+ {
+ "default_value": 210
+ },
+ "machine_center_is_zero":
+ {
+ "default_value": false
+ },
+ "machine_nozzle_size":
+ {
+ "default_value": 0.4
+ },
+ "material_diameter":
+ {
+ "default_value": 1.75
+ },
+ "gantry_height":
+ {
+ "default_value": 0
+ },
+ "machine_gcode_flavor":
+ {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "machine_start_gcode":
+ {
+ "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing...\nG5"
+ },
+ "machine_end_gcode":
+ {
+ "default_value": "M104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nM84 ; disable motors\nM107\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle\nto release some of the pressure\nG1 Z+0.5 E-5 ;X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 ;Y0 ;move X/Y to min endstops\nso the head is out of the way\nG1 Y180 F2000\nM84 ;steppers off\nG90\nM300 P300 S4000"
+ }
+ }
+}
diff --git a/resources/definitions/builder_premium_large.def.json b/resources/definitions/builder_premium_large.def.json
index 5fc4b46c98..deb1539a9a 100644
--- a/resources/definitions/builder_premium_large.def.json
+++ b/resources/definitions/builder_premium_large.def.json
@@ -1,5 +1,4 @@
{
- "id": "builder_premium_large",
"version": 2,
"name": "Builder Premium Large",
"inherits": "fdmprinter",
@@ -13,16 +12,14 @@
"platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117],
"has_machine_quality": true,
- "preferred_quality": "*Normal*",
+ "preferred_quality_type": "normal",
"machine_extruder_trains":
{
"0": "builder_premium_large_rear",
"1": "builder_premium_large_front"
}
},
-
-
-
+
"overrides": {
"machine_name": { "default_value": "Builder Premium Large" },
"machine_heated_bed": { "default_value": true },
@@ -37,7 +34,7 @@
"default_material_print_temperature": { "value": "215" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_standby_temperature": { "value": "material_print_temperature" },
-
+
"switch_extruder_retraction_speeds": {"default_value": 15 },
"switch_extruder_retraction_speed": {"default_value": 15 },
"switch_extruder_prime_speed": {"default_value": 15 },
@@ -59,9 +56,9 @@
"prime_tower_wipe_enabled": { "default_value": false },
"prime_tower_min_volume": { "default_value": 50 },
"dual_pre_wipe": { "default_value": false },
-
+
"prime_blob_enable": { "enabled": true },
-
+
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
@@ -72,7 +69,7 @@
"acceleration_travel": { "value": "acceleration_print" },
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
-
+
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "default_value": 10 },
@@ -85,9 +82,9 @@
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
-
+
"wall_thickness": { "value": "1.2" },
-
+
"retraction_amount": { "default_value": 3 },
"retraction_speed": { "default_value": 15 },
"retraction_retract_speed": { "default_value": 15 },
@@ -114,4 +111,4 @@
},
"machine_extruder_count": { "default_value": 2 }
}
-}
\ No newline at end of file
+}
diff --git a/resources/definitions/builder_premium_medium.def.json b/resources/definitions/builder_premium_medium.def.json
index 56dab8f863..c28c7c5de6 100644
--- a/resources/definitions/builder_premium_medium.def.json
+++ b/resources/definitions/builder_premium_medium.def.json
@@ -1,5 +1,4 @@
{
- "id": "builder_premium_medium",
"version": 2,
"name": "Builder Premium Medium",
"inherits": "fdmprinter",
@@ -13,16 +12,14 @@
"platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117],
"has_machine_quality": true,
- "preferred_quality": "*Normal*",
+ "preferred_quality_type": "normal",
"machine_extruder_trains":
{
"0": "builder_premium_medium_rear",
"1": "builder_premium_medium_front"
}
},
-
-
-
+
"overrides": {
"machine_name": { "default_value": "Builder Premium Medium" },
"machine_heated_bed": { "default_value": true },
@@ -37,7 +34,7 @@
"default_material_print_temperature": { "value": "215" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_standby_temperature": { "value": "material_print_temperature" },
-
+
"switch_extruder_retraction_speeds": {"default_value": 15 },
"switch_extruder_retraction_speed": {"default_value": 15 },
"switch_extruder_prime_speed": {"default_value": 15 },
@@ -59,9 +56,9 @@
"prime_tower_wipe_enabled": { "default_value": false },
"prime_tower_min_volume": { "default_value": 50 },
"dual_pre_wipe": { "default_value": false },
-
+
"prime_blob_enable": { "enabled": true },
-
+
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
@@ -72,7 +69,7 @@
"acceleration_travel": { "value": "acceleration_print" },
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
-
+
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "default_value": 10 },
@@ -85,9 +82,9 @@
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
-
+
"wall_thickness": { "value": "1.2" },
-
+
"retraction_amount": { "default_value": 3 },
"retraction_speed": { "default_value": 15 },
"retraction_retract_speed": { "default_value": 15 },
@@ -114,4 +111,4 @@
},
"machine_extruder_count": { "default_value": 2 }
}
-}
\ No newline at end of file
+}
diff --git a/resources/definitions/builder_premium_small.def.json b/resources/definitions/builder_premium_small.def.json
index 65103ce1af..8e2fe44631 100644
--- a/resources/definitions/builder_premium_small.def.json
+++ b/resources/definitions/builder_premium_small.def.json
@@ -1,5 +1,4 @@
{
- "id": "builder_premium_small",
"version": 2,
"name": "Builder Premium Small",
"inherits": "fdmprinter",
@@ -12,16 +11,14 @@
"platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117],
"has_machine_quality": true,
- "preferred_quality": "*Normal*",
+ "preferred_quality_type": "normal",
"machine_extruder_trains":
{
"0": "builder_premium_small_rear",
"1": "builder_premium_small_front"
}
},
-
-
-
+
"overrides": {
"machine_name": { "default_value": "Builder Premium Small" },
"machine_heated_bed": { "default_value": true },
@@ -36,7 +33,7 @@
"default_material_print_temperature": { "value": "215" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_standby_temperature": { "value": "material_print_temperature" },
-
+
"switch_extruder_retraction_speeds": {"default_value": 15 },
"switch_extruder_retraction_speed": {"default_value": 15 },
"switch_extruder_prime_speed": {"default_value": 15 },
@@ -58,9 +55,9 @@
"prime_tower_wipe_enabled": { "default_value": false },
"prime_tower_min_volume": { "default_value": 50 },
"dual_pre_wipe": { "default_value": false },
-
+
"prime_blob_enable": { "enabled": true },
-
+
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
@@ -71,7 +68,7 @@
"acceleration_travel": { "value": "acceleration_print" },
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
-
+
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "default_value": 10 },
@@ -84,9 +81,9 @@
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
-
+
"wall_thickness": { "value": "1.2" },
-
+
"retraction_amount": { "default_value": 3 },
"retraction_speed": { "default_value": 15 },
"retraction_retract_speed": { "default_value": 15 },
@@ -113,4 +110,4 @@
},
"machine_extruder_count": { "default_value": 2 }
}
-}
\ No newline at end of file
+}
diff --git a/resources/definitions/cartesio.def.json b/resources/definitions/cartesio.def.json
index 44f3153015..5ca891d6c5 100644
--- a/resources/definitions/cartesio.def.json
+++ b/resources/definitions/cartesio.def.json
@@ -5,7 +5,7 @@
"metadata": {
"visible": true,
"author": "Scheepers",
- "manufacturer": "Cartesio bv",
+ "manufacturer": "MaukCC",
"file_formats": "text/x-gcode",
"has_machine_quality": true,
@@ -14,10 +14,10 @@
"has_variant_materials": true,
"has_variants": true,
- "variants_name": "Nozzle size",
- "preferred_variant": "*0.8*",
- "preferred_material": "*pla*",
- "preferred_quality": "*normal*",
+ "variants_name": "Tool",
+ "preferred_variant_name": "0.8 mm",
+ "preferred_material": "generic_pla",
+ "preferred_quality_type": "normal",
"machine_extruder_trains":
{
@@ -44,7 +44,7 @@
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"material_print_temp_wait": { "default_value": false },
"material_bed_temp_wait": { "default_value": false },
- "prime_tower_enable": { "default_value": true },
+ "prime_tower_enable": { "default_value": false },
"prime_tower_wall_thickness": { "resolve": 0.7 },
"prime_tower_size": { "value": 24.0 },
"prime_tower_position_x": { "value": 125 },
@@ -55,15 +55,16 @@
[[215, 135], [-215, 135], [-215, 75], [215, 75]]
]},
"machine_start_gcode": {
- "default_value": "\nM92 E159 ;2288 for V5 extruder\n\nM140 S{material_bed_temperature_layer_0}\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nG21\nG90\nM42 S255 P13 ;chamber lights\nM42 S255 P12 ;fume extraction\nM204 S300 ;default acceleration\nM205 X10 ;default jerk\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n"
+ "default_value": "\nM92 E159 ;2288 for V5 extruder\n\nM140 S{material_bed_temperature_layer_0}\nM104 T1 S120\nM104 T2 S120\nM104 T3 S120\n\nG21\nG90\nM42 S255 P13 ;chamber lights\nM42 S255 P12 ;fume extraction\nM204 S300 ;default acceleration\nM205 X10 ;default jerk\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 T1 S21\nM104 T2 S21\nM104 T3 S21\n\nM117 Printing .....\n"
},
"machine_end_gcode": {
- "default_value": "; -- END GCODE --\nM117 cooling down....\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\n\nG91\nG1 Z1 F900\nG90\n\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\nM117 Finished.\n; -- end of GCODE --"
+ "default_value": "; -- END GCODE --\nM117 cooling down....\nM106 S255\nM140 S5\nM104 T0 S5\nM104 T1 S5\nM104 T2 S5\nM104 T3 S5\n\nG91\nG1 Z1 F900\nG90\n\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\nM117 Finished.\n; -- end of GCODE --"
},
"layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
"layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
"retraction_extra_prime_amount": { "minimum_value_warning": "-2.0" },
"optimize_wall_printing_order": { "default_value": true },
+ "material_initial_print_temperature": {"maximum_value_warning": "material_print_temperature + 15" },
"machine_nozzle_heat_up_speed": {"default_value": 20},
"machine_nozzle_cool_down_speed": {"default_value": 20},
"machine_min_cool_heat_time_window": {"default_value": 5}
diff --git a/resources/definitions/creality_cr10.def.json b/resources/definitions/creality_cr10.def.json
index 7a58adcd4d..eb0b8c7306 100644
--- a/resources/definitions/creality_cr10.def.json
+++ b/resources/definitions/creality_cr10.def.json
@@ -7,7 +7,7 @@
"author": "Michael Wildermuth",
"manufacturer": "Creality3D",
"file_formats": "text/x-gcode",
- "preferred_quality": "*Draft*"
+ "preferred_quality_type": "draft"
},
"overrides": {
"machine_width": {
diff --git a/resources/definitions/dagoma_discoeasy200.def.json b/resources/definitions/dagoma_discoeasy200.def.json
index 9bcc2402f2..4f0fddc41d 100644
--- a/resources/definitions/dagoma_discoeasy200.def.json
+++ b/resources/definitions/dagoma_discoeasy200.def.json
@@ -7,7 +7,6 @@
"author": "Dagoma",
"manufacturer": "Dagoma",
"file_formats": "text/x-gcode",
- "icon": "icon_discoeasy200.png",
"platform": "discoeasy200.stl",
"platform_offset": [ 105, -59, 280]
},
@@ -39,13 +38,25 @@
"default_value": 10
},
"machine_start_gcode": {
- "default_value": ";Gcode by Cura\nG90 ;absolute positioning\nM106 S250 ;fan on for the palpeur\nG28 X Y\nG1 X50\nM109 S180\nG28\nM104 S{material_print_temperature_layer_0}\n;Activation palpeur\n;bloc palpeur\nG29 ;Auto level\nM107 ;start with the fan off\nG1 X100 Y20 F3000\nG1 Z0.5\nM109 S{material_print_temperature_layer_0}\nM82 ;set extruder to absolute mode\nG92 E0 ;zero the extruded length\nG1 F200 E10 ;extrude 10mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 Z3\nG1 F6000"
+ "default_value": ";Gcode by Cura\nG90\nM106 S250\nG28 X Y\nG1 X50\nM109 S180\nG28\nM104 S{material_print_temperature_layer_0}\nG29\nM107\nG1 X100 Y20 F3000\nG1 Z0.5\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E10\nG92 E0\nG1 Z3\nG1 F6000\n"
},
"machine_end_gcode": {
- "default_value": "M104 S0\nM106 S255 ;start fan full power\nM140 S0 ;heated bed heater off (if you have it)\n;Home machine\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+3 F3000 ;move Z up a bit and retract filament even more\nG90\nG28 X Y\n;Ventilation forcee\nM107 ;stop fan\n;Shut down motor\nM84 ;shut down motors"
+ "default_value": "\nM104 S0\nM106 S255\nM140 S0\nG91\nG1 E-1 F300\nG1 Z+3 F3000\nG90\nG28 X Y\nM107\nM84\n"
},
"material_diameter": {
"default_value": 1.75
+ },
+ "speed_print": {
+ "default_value": 60
+ },
+ "speed_travel": {
+ "value": "100"
+ },
+ "retraction_amount": {
+ "default_value": 3.5
+ },
+ "retraction_speed": {
+ "default_value": 50
}
}
}
diff --git a/resources/definitions/dagoma_neva.def.json b/resources/definitions/dagoma_neva.def.json
new file mode 100644
index 0000000000..21a557ac22
--- /dev/null
+++ b/resources/definitions/dagoma_neva.def.json
@@ -0,0 +1,69 @@
+{
+ "id": "Dagoma_neva",
+ "name": "Dagoma NEVA",
+ "version": 2,
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "Dagoma",
+ "manufacturer": "Dagoma",
+ "file_formats": "text/x-gcode",
+ "platform": "neva.stl",
+ "platform_offset": [ 0, 0, 0]
+ },
+ "overrides": {
+ "machine_width": {
+ "default_value": 195.55
+ },
+ "machine_height": {
+ "default_value": 205
+ },
+ "machine_depth": {
+ "default_value": 195.55
+ },
+ "machine_center_is_zero": {
+ "default_value": true
+ },
+ "machine_nozzle_size": {
+ "default_value": 0.4
+ },
+ "machine_head_with_fans_polygon": {
+ "default_value": [
+ [17, 40],
+ [17, -70],
+ [-17, -70],
+ [17, 40]
+ ]
+ },
+ "gantry_height": {
+ "default_value": 0
+ },
+ "machine_shape": {
+ "default_value": "elliptic"
+ },
+ "machine_gcode_flavor": {
+ "default_value": "RepRap (RepRap)"
+ },
+ "machine_start_gcode": {
+ "default_value": ";Gcode by Cura\nG90\nG28\nM109 S100\nG29\nM104 S{material_print_temperature_layer_0}\nG0 X0 Y-85\nG0 Z0.26\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E6\nG92 E0\nG1 F200 E-3.5\nG0 Z0.15\nG0 X10\nG0 Z3\nG1 F6000\n"
+ },
+ "machine_end_gcode": {
+ "default_value": "\nM104 S0\nM106 S255\nM140 S0\nG91\nG1 E-1 F300\nG1 Z+3 E-2 F9000\nG90\nG28\n"
+ },
+ "material_diameter": {
+ "default_value": 1.75
+ },
+ "speed_print": {
+ "default_value": 40
+ },
+ "speed_travel": {
+ "default_value": 120
+ },
+ "retraction_amount": {
+ "default_value": 3.8
+ },
+ "retraction_speed": {
+ "default_value": 60
+ }
+ }
+}
diff --git a/resources/definitions/deltacomb.def.json b/resources/definitions/deltacomb.def.json
index 031bd12156..7e6e956dbc 100644
--- a/resources/definitions/deltacomb.def.json
+++ b/resources/definitions/deltacomb.def.json
@@ -1,5 +1,4 @@
{
- "id": "deltacomb",
"version": 2,
"name": "Deltacomb 3D",
"inherits": "fdmprinter",
diff --git a/resources/definitions/easyarts_ares.def.json b/resources/definitions/easyarts_ares.def.json
index 982496de4c..689ac63625 100644
--- a/resources/definitions/easyarts_ares.def.json
+++ b/resources/definitions/easyarts_ares.def.json
@@ -10,7 +10,7 @@
},
"overrides": {
"machine_start_gcode": {
- "default_value": "; -- START GCODE --\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 \nG29 Z0.12 ;Auto-bedleveling with Z offset \nG92 E0 ;zero the extruded length \nG1 F2000 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM117 Printing...\n; -- end of START GCODE --"
+ "default_value": "; -- START GCODE --\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 \nG29 Z0.12 ;Auto-bedleveling with Z offset \nG92 E0 ;zero the extruded length \nG1 F2000 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing...\n; -- end of START GCODE --"
},
"machine_end_gcode": {
"default_value": "; -- START GCODE --\nG28 ; Home all axes\nM104 S0 ;extruder heater off\n;M140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n;M84 ;steppers off\nG90 ;absolute positioning\n; -- end of START GCODE --"
diff --git a/resources/definitions/fabtotum.def.json b/resources/definitions/fabtotum.def.json
index 87ce11a35c..d66de07c4a 100644
--- a/resources/definitions/fabtotum.def.json
+++ b/resources/definitions/fabtotum.def.json
@@ -13,18 +13,18 @@
"has_machine_quality": true,
"has_variants": true,
"variants_name": "Head",
- "preferred_variant": "*lite04*",
- "preferred_material": "*fabtotum_pla*",
+ "preferred_variant_name": "Lite 0.4 mm",
+ "preferred_material": "fabtotum_pla",
"supports_usb_connection": false
},
"overrides": {
"machine_name": { "default_value": "FABtotum Personal Fabricator" },
"machine_start_gcode": {
- "default_value": ";Layer height: {layer_height}\n;Walls: {wall_thickness}\n;Fill: {infill_sparse_density}\n;Top\\Bottom Thickness: {top_bottom_thickness}\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG4 S1 ;1 millisecond pause to buffer the bep bep \nM728 ;FAB bep bep (start the print, go check the oozing and skirt lines adesion) \nG4 S1 ;1 second pause to reach the printer (run fast)\nG92 E0 ;zero the extruded length \nG1 F200 E35 ;slowly extrude 35mm of filament to clean the nozzle and build up extrusion pressure \nG92 E0 ;zero the extruded length again \nG1 F{speed_travel} ;Set travel speed \n;print"
+ "default_value": ";Layer height: {layer_height}\n;Walls: {wall_thickness}\n;Fill: {infill_sparse_density}\n;Top\\Bottom Thickness: {top_bottom_thickness}\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG4 S1 ;1 millisecond pause to buffer the bep bep \nM300 S2 ;FAB bep bep (start the print, go check the oozing and skirt lines adesion) \nG4 S1 ;1 second pause to reach the printer (run fast)\nG92 E0 ;zero the extruded length \nG1 F200 E35 ;slowly extrude 35mm of filament to clean the nozzle and build up extrusion pressure \nG92 E0 ;zero the extruded length again \n;print"
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-3 X+5 Y+5 F5000 ;move Z up a bit and retract filament even more\n;end of the print\nM84 ;steppers off\nG90 ;absolute positioning\nM728 ;FAB bep bep (end print)"
+ "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-3 X+5 Y+5 F5000 ;move Z up a bit and retract filament even more\n;end of the print\nM84 ;steppers off\nG90 ;absolute positioning\nM300 S2 ;FAB bep bep (end print)"
},
"gantry_height": { "default_value": 55 },
"machine_width": { "default_value": 214 },
diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json
index 2b314cd6a5..3f84ed69a4 100644
--- a/resources/definitions/fdmextruder.def.json
+++ b/resources/definitions/fdmextruder.def.json
@@ -216,6 +216,30 @@
"enabled": false
}
}
+ },
+ "material":
+ {
+ "label": "Material",
+ "icon": "category_material",
+ "description": "Material",
+ "type": "category",
+ "children":
+ {
+ "material_diameter":
+ {
+ "label": "Diameter",
+ "description": "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 2.85,
+ "minimum_value": "0.0001",
+ "minimum_value_warning": "0.4",
+ "maximum_value_warning": "3.5",
+ "enabled": "machine_gcode_flavor != \"UltiGCode\"",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ }
+ }
}
}
}
diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json
index 24f7efe373..8c67462667 100644
--- a/resources/definitions/fdmprinter.def.json
+++ b/resources/definitions/fdmprinter.def.json
@@ -11,8 +11,8 @@
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
"visible": false,
"has_materials": true,
- "preferred_material": "*generic_pla*",
- "preferred_quality": "*normal*",
+ "preferred_material": "generic_pla",
+ "preferred_quality_type": "normal",
"machine_extruder_trains":
{
"0": "fdmextruder"
@@ -51,8 +51,8 @@
},
"machine_start_gcode":
{
- "label": "Start GCode",
- "description": "Gcode commands to be executed at the very start - separated by \\n.",
+ "label": "Start G-code",
+ "description": "G-code commands to be executed at the very start - separated by \\n.",
"default_value": "G28 ;Home\nG1 Z15.0 F6000 ;Move the platform down 15mm\n;Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0",
"type": "str",
"settable_per_mesh": false,
@@ -61,8 +61,8 @@
},
"machine_end_gcode":
{
- "label": "End GCode",
- "description": "Gcode commands to be executed at the very end - separated by \\n.",
+ "label": "End G-code",
+ "description": "G-code commands to be executed at the very end - separated by \\n.",
"default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG28 X0 Y0\nM84",
"type": "str",
"settable_per_mesh": false,
@@ -154,6 +154,21 @@
"settable_per_extruder": false,
"settable_per_meshgroup": false
},
+ "machine_buildplate_type":
+ {
+ "label": "Build Plate Material",
+ "description": "The material of the build plate installed on the printer.",
+ "default_value": "glass",
+ "type": "enum",
+ "options":
+ {
+ "glass": "Glass",
+ "aluminum": "Aluminum"
+ },
+ "settable_per_mesh": false,
+ "settable_per_extruder": false,
+ "settable_per_meshgroup": false
+ },
"machine_height":
{
"label": "Machine Height",
@@ -297,8 +312,8 @@
},
"machine_gcode_flavor":
{
- "label": "Gcode flavour",
- "description": "The type of gcode to be generated.",
+ "label": "G-code flavour",
+ "description": "The type of g-code to be generated.",
"type": "enum",
"options":
{
@@ -653,20 +668,6 @@
"settable_per_mesh": false,
"settable_per_extruder": false
},
- "slicing_tolerance":
- {
- "label": "Slicing Tolerance",
- "description": "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process.",
- "type": "enum",
- "options":
- {
- "middle": "Middle",
- "exclusive": "Exclusive",
- "inclusive": "Inclusive"
- },
- "default_value": "middle",
- "settable_per_mesh": true
- },
"line_width":
{
"label": "Line Width",
@@ -726,21 +727,6 @@
}
}
},
- "roofing_line_width":
- {
- "label": "Top Surface Skin Line Width",
- "description": "Width of a single line of the areas at the top of the print.",
- "unit": "mm",
- "minimum_value": "0.001",
- "minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
- "maximum_value_warning": "2 * machine_nozzle_size",
- "default_value": 0.4,
- "type": "float",
- "value": "skin_line_width",
- "limit_to_extruder": "roofing_extruder_nr",
- "settable_per_mesh": true,
- "enabled": "roofing_layer_count > 0 and top_layers > 0"
- },
"skin_line_width":
{
"label": "Top/Bottom Line Width",
@@ -999,34 +985,6 @@
"settable_per_mesh": true,
"enabled": "top_layers > 0"
},
- "roofing_pattern":
- {
- "label": "Top Surface Skin Pattern",
- "description": "The pattern of the top most layers.",
- "type": "enum",
- "options":
- {
- "lines": "Lines",
- "concentric": "Concentric",
- "zigzag": "Zig Zag"
- },
- "default_value": "lines",
- "value": "top_bottom_pattern",
- "limit_to_extruder": "roofing_extruder_nr",
- "settable_per_mesh": true,
- "enabled": "roofing_layer_count > 0 and top_layers > 0"
- },
- "roofing_angles":
- {
- "label": "Top Surface Skin Line Directions",
- "description": "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees).",
- "type": "[int]",
- "default_value": "[ ]",
- "value": "skin_angles",
- "enabled": "roofing_pattern != 'concentric' and roofing_layer_count > 0 and top_layers > 0",
- "limit_to_extruder": "roofing_extruder_nr",
- "settable_per_mesh": true
- },
"top_bottom_extruder_nr":
{
"label": "Top/Bottom Extruder",
@@ -1168,6 +1126,14 @@
"limit_to_extruder": "wall_0_extruder_nr",
"settable_per_mesh": true
},
+ "optimize_wall_printing_order":
+ {
+ "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.",
+ "type": "bool",
+ "default_value": false,
+ "settable_per_mesh": true
+ },
"outer_inset_first":
{
"label": "Outer Before Inner Walls",
@@ -1561,8 +1527,10 @@
"label": "Connect Infill Lines",
"description": "Connect the ends where the infill pattern meets the inner wall using a line which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduce the effects of infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used.",
"type": "bool",
- "default_value": true,
- "enabled": "infill_pattern == 'cross' or infill_pattern == 'cross_3d'",
+ "default_value": false,
+ "value": "infill_pattern == 'cross' or infill_pattern == 'cross_3d'",
+ "enabled": "infill_pattern == 'grid' or infill_pattern == 'triangles' or infill_pattern == 'trihexagon' or infill_pattern == 'cubic' or infill_pattern == 'tetrahedral' or infill_pattern == 'quarter_cubic' or infill_pattern == 'cross' or infill_pattern == 'cross_3d'",
+ "limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"infill_angles":
@@ -1614,7 +1582,7 @@
"infill_overlap":
{
"label": "Infill Overlap Percentage",
- "description": "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill.",
+ "description": "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill.",
"unit": "%",
"type": "float",
"default_value": 10,
@@ -1635,7 +1603,7 @@
"default_value": 0.04,
"minimum_value_warning": "-0.5 * machine_nozzle_size",
"maximum_value_warning": "machine_nozzle_size",
- "value": "0.5 * ( infill_line_width + (wall_line_width_x if wall_line_count > 1 else wall_line_width_0) ) * infill_overlap / 100 if infill_sparse_density < 95 and infill_pattern != 'concentric' else 0",
+ "value": "0.5 * (infill_line_width + (wall_line_width_x if wall_line_count > 1 else wall_line_width_0)) * infill_overlap / 100 if infill_sparse_density < 95 and infill_pattern != 'concentric' else 0",
"enabled": "infill_sparse_density > 0 and infill_pattern != 'concentric'",
"settable_per_mesh": true
}
@@ -1644,7 +1612,7 @@
"skin_overlap":
{
"label": "Skin Overlap Percentage",
- "description": "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall.",
+ "description": "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall.",
"unit": "%",
"type": "float",
"default_value": 5,
@@ -1665,7 +1633,7 @@
"default_value": 0.02,
"minimum_value_warning": "-0.5 * machine_nozzle_size",
"maximum_value_warning": "machine_nozzle_size",
- "value": "0.5 * ( skin_line_width + (wall_line_width_x if wall_line_count > 1 else wall_line_width_0) ) * skin_overlap / 100 if top_bottom_pattern != 'concentric' else 0",
+ "value": "0.5 * (skin_line_width + (wall_line_width_x if wall_line_count > 1 else wall_line_width_0)) * skin_overlap / 100 if top_bottom_pattern != 'concentric' else 0",
"enabled": "top_bottom_pattern != 'concentric'",
"settable_per_mesh": true
}
@@ -1864,16 +1832,6 @@
"type": "category",
"children":
{
- "material_flow_dependent_temperature":
- {
- "label": "Auto Temperature",
- "description": "Change the temperature for each layer automatically with the average flow speed of that layer.",
- "type": "bool",
- "default_value": false,
- "enabled": "machine_nozzle_temp_enabled and False",
- "settable_per_mesh": false,
- "settable_per_extruder": true
- },
"default_material_print_temperature":
{
"label": "Default Printing Temperature",
@@ -1948,17 +1906,6 @@
"settable_per_mesh": false,
"settable_per_extruder": true
},
- "material_flow_temp_graph":
- {
- "label": "Flow Temperature Graph",
- "description": "Data linking material flow (in mm3 per second) to temperature (degrees Celsius).",
- "unit": "[[mm³,°C]]",
- "type": "str",
- "default_value": "[[3.5,200],[7.0,240]]",
- "enabled": "False and machine_nozzle_temp_enabled and material_flow_dependent_temperature",
- "settable_per_mesh": false,
- "settable_per_extruder": true
- },
"material_extrusion_cool_down_speed":
{
"label": "Extrusion Cool Down Speed Modifier",
@@ -1973,14 +1920,30 @@
"settable_per_mesh": false,
"settable_per_extruder": true
},
+ "default_material_bed_temperature":
+ {
+ "label": "Default Build Plate Temperature",
+ "description": "The default temperature used for the heated build plate. This should be the \"base\" temperature of a build plate. All other print temperatures should use offsets based on this value",
+ "unit": "°C",
+ "type": "float",
+ "resolve": "max(extruderValues('default_material_bed_temperature'))",
+ "default_value": 60,
+ "minimum_value": "-273.15",
+ "minimum_value_warning": "0",
+ "maximum_value_warning": "130",
+ "enabled": "machine_heated_bed and machine_gcode_flavor != \"UltiGCode\"",
+ "settable_per_mesh": false,
+ "settable_per_extruder": false,
+ "settable_per_meshgroup": false
+ },
"material_bed_temperature":
{
"label": "Build Plate Temperature",
"description": "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted.",
"unit": "°C",
"type": "float",
- "resolve": "max(extruderValues('material_bed_temperature'))",
"default_value": 60,
+ "value": "default_material_bed_temperature",
"minimum_value": "-273.15",
"minimum_value_warning": "0",
"maximum_value_warning": "130",
@@ -2058,6 +2021,19 @@
"enabled": "machine_gcode_flavor != \"UltiGCode\"",
"settable_per_mesh": true
},
+ "material_flow_layer_0":
+ {
+ "label": "Initial Layer Flow",
+ "description": "Flow compensation for the first layer: the amount of material extruded on the initial layer is multiplied by this value.",
+ "unit": "%",
+ "default_value": 100,
+ "value": "material_flow",
+ "type": "float",
+ "minimum_value": "0.0001",
+ "minimum_value_warning": "50",
+ "maximum_value_warning": "150",
+ "settable_per_mesh": true
+ },
"retraction_enable":
{
"label": "Enable Retraction",
@@ -2067,7 +2043,8 @@
"settable_per_mesh": false,
"settable_per_extruder": true
},
- "retract_at_layer_change":{
+ "retract_at_layer_change":
+ {
"label": "Retract at Layer Change",
"description": "Retract the filament when the nozzle is moving to the next layer.",
"type": "bool",
@@ -2097,7 +2074,7 @@
"default_value": 25,
"minimum_value": "0.0001",
"minimum_value_warning": "1",
- "maximum_value": "machine_max_feedrate_e",
+ "maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value_warning": "70",
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
"settable_per_mesh": false,
@@ -2112,7 +2089,7 @@
"type": "float",
"default_value": 25,
"minimum_value": "0.0001",
- "maximum_value": "machine_max_feedrate_e",
+ "maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
"minimum_value_warning": "1",
"maximum_value_warning": "70",
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
@@ -2128,7 +2105,7 @@
"type": "float",
"default_value": 25,
"minimum_value": "0.0001",
- "maximum_value": "machine_max_feedrate_e",
+ "maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
"minimum_value_warning": "1",
"maximum_value_warning": "70",
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
@@ -2160,6 +2137,7 @@
"default_value": 1.5,
"value": "line_width * 2",
"minimum_value": "0",
+ "minimum_value_warning": "line_width * 1.5",
"maximum_value_warning": "10",
"enabled": "retraction_enable",
"settable_per_mesh": false,
@@ -2229,7 +2207,7 @@
"default_value": 20,
"minimum_value": "0.1",
"minimum_value_warning": "1",
- "maximum_value": "machine_max_feedrate_e",
+ "maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value_warning": "70",
"settable_per_mesh": false,
"settable_per_extruder": true,
@@ -2246,7 +2224,7 @@
"value": "switch_extruder_retraction_speeds",
"minimum_value": "0.1",
"minimum_value_warning": "1",
- "maximum_value": "machine_max_feedrate_e",
+ "maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value_warning": "70",
"settable_per_mesh": false,
"settable_per_extruder": true
@@ -2262,7 +2240,7 @@
"value": "switch_extruder_retraction_speeds",
"minimum_value": "0.1",
"minimum_value_warning": "1",
- "maximum_value": "machine_max_feedrate_e",
+ "maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value_warning": "70",
"settable_per_mesh": false,
"settable_per_extruder": true
@@ -3481,7 +3459,7 @@
"description": "The extruder train to use for printing the support. This is used in multi-extrusion.",
"type": "extruder",
"default_value": "0",
- "enabled": "support_enable and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false,
"children": {
@@ -3492,7 +3470,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_extruder_nr",
- "enabled": "support_enable and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -3503,7 +3481,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_extruder_nr",
- "enabled": "support_enable and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -3514,7 +3492,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_extruder_nr",
- "enabled": "support_enable and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false,
"children":
@@ -3526,7 +3504,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_interface_extruder_nr",
- "enabled": "support_enable and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -3537,7 +3515,7 @@
"type": "extruder",
"default_value": "0",
"value": "support_interface_extruder_nr",
- "enabled": "support_enable and machine_extruder_count > 1",
+ "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false
}
@@ -3557,7 +3535,7 @@
},
"default_value": "everywhere",
"resolve": "'everywhere' if 'everywhere' in extruderValues('support_type') else 'buildplate'",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@@ -3572,7 +3550,7 @@
"maximum_value_warning": "80",
"default_value": 50,
"limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true
},
"support_pattern":
@@ -3591,7 +3569,19 @@
"cross": "Cross"
},
"default_value": "zigzag",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "zig_zaggify_support":
+ {
+ "label": "Connect Support Lines",
+ "description": "Connect the ends of the support lines together. Enabling this setting can make your support more sturdy and reduce underextrusion, but it will cost more material.",
+ "type": "bool",
+ "default_value": false,
+ "value": "support_pattern == 'cross'",
+ "enabled": "support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'cross'",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
@@ -3602,7 +3592,7 @@
"description": "Connect the ZigZags. This will increase the strength of the zig zag support structure.",
"type": "bool",
"default_value": true,
- "enabled": "support_enable and (support_pattern == 'zigzag')",
+ "enabled": "(support_enable or support_tree_enable) and support_pattern == 'zigzag'",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
@@ -3616,7 +3606,8 @@
"minimum_value": "0",
"maximum_value_warning": "100",
"default_value": 15,
- "enabled": "support_enable",
+ "value": "15 if support_enable else 0 if support_tree_enable else 15",
+ "enabled": "support_enable or support_tree_enable",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true,
@@ -3631,7 +3622,7 @@
"minimum_value": "0",
"minimum_value_warning": "support_line_width",
"default_value": 2.66,
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"value": "0 if support_infill_rate == 0 else (support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false,
@@ -3649,7 +3640,7 @@
"maximum_value_warning": "machine_nozzle_size",
"default_value": 0.1,
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true,
"children":
{
@@ -3662,7 +3653,7 @@
"maximum_value_warning": "machine_nozzle_size",
"default_value": 0.1,
"type": "float",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"value": "extruderValue(support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr, 'support_z_distance')",
"limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr",
"settable_per_mesh": true
@@ -3678,7 +3669,7 @@
"value": "extruderValue(support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr, 'support_z_distance') if support_type == 'everywhere' else 0",
"limit_to_extruder": "support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr",
"type": "float",
- "enabled": "support_enable and resolveOrValue('support_type') == 'everywhere'",
+ "enabled": "(support_enable or support_tree_enable) and resolveOrValue('support_type') == 'everywhere'",
"settable_per_mesh": true
}
}
@@ -3693,7 +3684,7 @@
"maximum_value_warning": "1.5 * machine_nozzle_tip_outer_diameter",
"default_value": 0.7,
"limit_to_extruder": "support_infill_extruder_nr",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true
},
"support_xy_overrides_z":
@@ -3788,7 +3779,7 @@
"maximum_value_warning": "0.75 * machine_nozzle_size",
"maximum_value": "resolveOrValue('layer_height') * 8",
"value": "resolveOrValue('layer_height')",
- "enabled": "support_enable and support_infill_rate > 0",
+ "enabled": "(support_enable or support_tree_enable) and support_infill_rate > 0",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false
},
@@ -3801,7 +3792,7 @@
"minimum_value": "0",
"maximum_value_warning": "1 if (support_pattern == 'cross' or support_pattern == 'lines' or support_pattern == 'zigzag' or support_pattern == 'concentric' or support_pattern == 'concentric_3d') else 5",
"maximum_value": "999999 if support_line_distance == 0 else (20 - math.log(support_line_distance) / math.log(2))",
- "enabled": "support_enable and support_infill_rate > 0",
+ "enabled": "(support_enable or support_tree_enable) and support_infill_rate > 0",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false
},
@@ -3814,7 +3805,7 @@
"default_value": 1,
"minimum_value": "0.0001",
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
- "enabled": "support_enable and support_infill_rate > 0 and gradual_support_infill_steps > 0",
+ "enabled": "(support_enable or support_tree_enable) and support_infill_rate > 0 and gradual_support_infill_steps > 0",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false
},
@@ -3825,7 +3816,7 @@
"type": "bool",
"default_value": false,
"limit_to_extruder": "support_interface_extruder_nr",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true,
"children":
{
@@ -3837,7 +3828,7 @@
"default_value": false,
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_enable')",
"limit_to_extruder": "support_roof_extruder_nr",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true
},
"support_bottom_enable":
@@ -3848,7 +3839,7 @@
"default_value": false,
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_enable')",
"limit_to_extruder": "support_bottom_extruder_nr",
- "enabled": "support_enable",
+ "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true
}
}
@@ -3864,7 +3855,7 @@
"minimum_value_warning": "0.2 + layer_height",
"maximum_value_warning": "10",
"limit_to_extruder": "support_interface_extruder_nr",
- "enabled": "support_interface_enable and support_enable",
+ "enabled": "support_interface_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": true,
"children":
{
@@ -3880,7 +3871,7 @@
"maximum_value_warning": "10",
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')",
"limit_to_extruder": "support_roof_extruder_nr",
- "enabled": "support_roof_enable and support_enable",
+ "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": true
},
"support_bottom_height":
@@ -3895,7 +3886,7 @@
"minimum_value_warning": "min(0.2 + layer_height, support_bottom_stair_step_height)",
"maximum_value_warning": "10",
"limit_to_extruder": "support_bottom_extruder_nr",
- "enabled": "support_bottom_enable and support_enable",
+ "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": true
}
}
@@ -3922,7 +3913,7 @@
"minimum_value": "0",
"maximum_value_warning": "100",
"limit_to_extruder": "support_interface_extruder_nr",
- "enabled": "support_interface_enable and support_enable",
+ "enabled": "support_interface_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false,
"settable_per_extruder": true,
"children":
@@ -3937,7 +3928,7 @@
"minimum_value": "0",
"maximum_value": "100",
"limit_to_extruder": "support_roof_extruder_nr",
- "enabled": "support_roof_enable and support_enable",
+ "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_density')",
"settable_per_mesh": false,
"settable_per_extruder": true,
@@ -3954,7 +3945,7 @@
"minimum_value_warning": "support_roof_line_width - 0.0001",
"value": "0 if support_roof_density == 0 else (support_roof_line_width * 100) / support_roof_density * (2 if support_roof_pattern == 'grid' else (3 if support_roof_pattern == 'triangles' else 1))",
"limit_to_extruder": "support_roof_extruder_nr",
- "enabled": "support_roof_enable and support_enable",
+ "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false,
"settable_per_extruder": true
}
@@ -3970,7 +3961,7 @@
"minimum_value": "0",
"maximum_value": "100",
"limit_to_extruder": "support_bottom_extruder_nr",
- "enabled": "support_bottom_enable and support_enable",
+ "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_density')",
"settable_per_mesh": false,
"settable_per_extruder": true,
@@ -3987,7 +3978,7 @@
"minimum_value_warning": "support_bottom_line_width - 0.0001",
"value": "0 if support_bottom_density == 0 else (support_bottom_line_width * 100) / support_bottom_density * (2 if support_bottom_pattern == 'grid' else (3 if support_bottom_pattern == 'triangles' else 1))",
"limit_to_extruder": "support_bottom_extruder_nr",
- "enabled": "support_bottom_enable and support_enable",
+ "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false,
"settable_per_extruder": true
}
@@ -4011,7 +4002,7 @@
},
"default_value": "concentric",
"limit_to_extruder": "support_interface_extruder_nr",
- "enabled": "support_interface_enable and support_enable",
+ "enabled": "support_interface_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false,
"settable_per_extruder": true,
"children":
@@ -4033,7 +4024,7 @@
"default_value": "concentric",
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_pattern')",
"limit_to_extruder": "support_roof_extruder_nr",
- "enabled": "support_roof_enable and support_enable",
+ "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false,
"settable_per_extruder": true
},
@@ -4054,7 +4045,7 @@
"default_value": "concentric",
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_pattern')",
"limit_to_extruder": "support_bottom_extruder_nr",
- "enabled": "support_bottom_enable and support_enable",
+ "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false,
"settable_per_extruder": true
}
@@ -4111,6 +4102,18 @@
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_enable and support_use_towers",
"settable_per_mesh": true
+ },
+ "support_mesh_drop_down":
+ {
+ "label": "Drop Down Support Mesh",
+ "description": "Make support everywhere below the support mesh, so that there's no overhang in the support mesh.",
+ "type": "bool",
+ "default_value": true,
+ "enabled": "support_mesh",
+ "settable_per_mesh": true,
+ "settable_per_extruder": false,
+ "settable_per_meshgroup": false,
+ "settable_globally": false
}
}
},
@@ -4759,6 +4762,17 @@
"settable_per_mesh": false,
"settable_per_extruder": false
},
+ "prime_tower_circular":
+ {
+ "label": "Circular Prime Tower",
+ "description": "Make the prime tower as a circular shape.",
+ "type": "bool",
+ "enabled": "resolveOrValue('prime_tower_enable')",
+ "default_value": true,
+ "resolve": "any(extruderValues('prime_tower_circular'))",
+ "settable_per_mesh": false,
+ "settable_per_extruder": false
+ },
"prime_tower_size":
{
"label": "Prime Tower Size",
@@ -4782,8 +4796,9 @@
"unit": "mm³",
"type": "float",
"default_value": 10,
+ "value": "8.48 if prime_tower_circular else 10",
"minimum_value": "0",
- "maximum_value_warning": "resolveOrValue('prime_tower_size') ** 2 * resolveOrValue('layer_height')",
+ "maximum_value_warning": "round((resolveOrValue('prime_tower_size') * 0.5) ** 2 * 3.14159 * resolveOrValue('layer_height'), 2) if prime_tower_circular else resolveOrValue('prime_tower_size') ** 2 * resolveOrValue('layer_height')",
"enabled": "resolveOrValue('prime_tower_enable')",
"settable_per_mesh": false,
"settable_per_extruder": true,
@@ -4796,7 +4811,7 @@
"unit": "mm",
"type": "float",
"default_value": 2,
- "value": "round(max(2 * prime_tower_line_width, 0.5 * (prime_tower_size - math.sqrt(max(0, prime_tower_size ** 2 - prime_tower_min_volume / layer_height)))), 3)",
+ "value": "round(max(2 * prime_tower_line_width, (0.5 * (prime_tower_size - math.sqrt(max(0, prime_tower_size ** 2 - 4 * prime_tower_min_volume / (3.14159 * layer_height))))) if prime_tower_circular else (0.5 * (prime_tower_size - math.sqrt(max(0, prime_tower_size ** 2 - prime_tower_min_volume / layer_height))))), 3)",
"resolve": "max(extruderValues('prime_tower_wall_thickness'))",
"minimum_value": "0.001",
"minimum_value_warning": "2 * min(extruderValues('prime_tower_line_width')) - 0.0001",
@@ -4959,23 +4974,11 @@
"meshfix_keep_open_polygons":
{
"label": "Keep Disconnected Faces",
- "description": "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode.",
+ "description": "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper g-code.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true
},
- "meshfix_maximum_resolution":
- {
- "label": "Maximum Resolution",
- "description": "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway.",
- "type": "float",
- "unit": "mm",
- "default_value": 0.01,
- "minimum_value": "0.001",
- "minimum_value_warning": "0.005",
- "maximum_value_warning": "0.1",
- "settable_per_mesh": true
- },
"multiple_mesh_overlap":
{
"label": "Merged Meshes Overlap",
@@ -5139,18 +5142,6 @@
"settable_per_meshgroup": false,
"settable_globally": false
},
- "support_mesh_drop_down":
- {
- "label": "Drop Down Support Mesh",
- "description": "Make support everywhere below the support mesh, so that there's no overhang in the support mesh.",
- "type": "bool",
- "default_value": true,
- "enabled": "support_mesh",
- "settable_per_mesh": true,
- "settable_per_extruder": false,
- "settable_per_meshgroup": false,
- "settable_globally": false
- },
"anti_overhang_mesh":
{
"label": "Anti Overhang Mesh",
@@ -5198,7 +5189,7 @@
"relative_extrusion":
{
"label": "Relative Extrusion",
- "description": "Use relative extrusion rather than absolute extrusion. Using relative E-steps makes for easier post-processing of the Gcode. However, it's not supported by all printers and it may produce very slight deviations in the amount of deposited material compared to absolute E-steps. Irrespective of this setting, the extrusion mode will always be set to absolute before any Gcode script is output.",
+ "description": "Use relative extrusion rather than absolute extrusion. Using relative E-steps makes for easier post-processing of the g-code. However, it's not supported by all printers and it may produce very slight deviations in the amount of deposited material compared to absolute E-steps. Irrespective of this setting, the extrusion mode will always be set to absolute before any g-code script is output.",
"type": "bool",
"default_value": false,
"value": "machine_gcode_flavor==\"RepRap (RepRap)\"",
@@ -5216,12 +5207,215 @@
"description": "experimental!",
"children":
{
- "optimize_wall_printing_order":
+ "support_tree_enable":
{
- "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.",
+ "label": "Tree Support",
+ "description": "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time.",
"type": "bool",
"default_value": false,
+ "settable_per_mesh": true,
+ "settable_per_extruder": false
+ },
+ "support_tree_angle":
+ {
+ "label": "Tree Support Branch Angle",
+ "description": "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach.",
+ "unit": "°",
+ "type": "float",
+ "minimum_value": "0",
+ "maximum_value": "90",
+ "maximum_value_warning": "60",
+ "default_value": 40,
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "enabled": "support_tree_enable",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "support_tree_branch_distance":
+ {
+ "label": "Tree Support Branch Distance",
+ "description": "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove.",
+ "unit": "mm",
+ "type": "float",
+ "minimum_value": "0.001",
+ "default_value": 4,
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "enabled": "support_tree_enable",
+ "settable_per_mesh": true
+ },
+ "support_tree_branch_diameter":
+ {
+ "label": "Tree Support Branch Diameter",
+ "description": "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this.",
+ "unit": "mm",
+ "type": "float",
+ "minimum_value": "0.001",
+ "minimum_value_warning": "support_line_width * 2",
+ "default_value": 2,
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "enabled": "support_tree_enable",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "support_tree_branch_diameter_angle":
+ {
+ "label": "Tree Support Branch Diameter Angle",
+ "description": "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support.",
+ "unit": "°",
+ "type": "float",
+ "minimum_value": "0",
+ "maximum_value": "89.9999",
+ "maximum_value_warning": "15",
+ "default_value": 5,
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "enabled": "support_tree_enable",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "support_tree_collision_resolution":
+ {
+ "label": "Tree Support Collision Resolution",
+ "description": "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically.",
+ "unit": "mm",
+ "type": "float",
+ "minimum_value": "0.001",
+ "minimum_value_warning": "support_line_width / 4",
+ "maximum_value_warning": "support_line_width * 2",
+ "default_value": 0.4,
+ "value": "support_line_width / 2",
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "enabled": "support_tree_enable and support_tree_branch_diameter_angle > 0",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "support_tree_wall_thickness":
+ {
+ "label": "Tree Support Wall Thickness",
+ "description": "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily.",
+ "unit": "mm",
+ "type": "float",
+ "minimum_value": "0",
+ "minimum_value_warning": "wall_line_width",
+ "default_value": 0.8,
+ "value": "support_line_width",
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "enabled": "support_tree_enable",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true,
+ "children":
+ {
+ "support_tree_wall_count":
+ {
+ "label": "Tree Support Wall Line Count",
+ "description": "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily.",
+ "type": "int",
+ "minimum_value": "0",
+ "minimum_value_warning": "1",
+ "default_value": 1,
+ "value": "round(support_tree_wall_thickness / support_line_width)",
+ "limit_to_extruder": "support_infill_extruder_nr",
+ "enabled": "support_tree_enable",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ }
+ }
+ },
+ "slicing_tolerance":
+ {
+ "label": "Slicing Tolerance",
+ "description": "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process.",
+ "type": "enum",
+ "options":
+ {
+ "middle": "Middle",
+ "exclusive": "Exclusive",
+ "inclusive": "Inclusive"
+ },
+ "default_value": "middle",
+ "settable_per_mesh": true
+ },
+ "roofing_line_width":
+ {
+ "label": "Top Surface Skin Line Width",
+ "description": "Width of a single line of the areas at the top of the print.",
+ "unit": "mm",
+ "minimum_value": "0.001",
+ "minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
+ "maximum_value_warning": "2 * machine_nozzle_size",
+ "default_value": 0.4,
+ "type": "float",
+ "value": "skin_line_width",
+ "limit_to_extruder": "roofing_extruder_nr",
+ "settable_per_mesh": true,
+ "enabled": "roofing_layer_count > 0 and top_layers > 0"
+ },
+ "roofing_pattern":
+ {
+ "label": "Top Surface Skin Pattern",
+ "description": "The pattern of the top most layers.",
+ "type": "enum",
+ "options":
+ {
+ "lines": "Lines",
+ "concentric": "Concentric",
+ "zigzag": "Zig Zag"
+ },
+ "default_value": "lines",
+ "value": "top_bottom_pattern",
+ "limit_to_extruder": "roofing_extruder_nr",
+ "settable_per_mesh": true,
+ "enabled": "roofing_layer_count > 0 and top_layers > 0"
+ },
+ "roofing_angles":
+ {
+ "label": "Top Surface Skin Line Directions",
+ "description": "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees).",
+ "type": "[int]",
+ "default_value": "[ ]",
+ "value": "skin_angles",
+ "enabled": "roofing_pattern != 'concentric' and roofing_layer_count > 0 and top_layers > 0",
+ "limit_to_extruder": "roofing_extruder_nr",
+ "settable_per_mesh": true
+ },
+ "infill_enable_travel_optimization":
+ {
+ "label": "Infill Travel Optimization",
+ "description": "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased.",
+ "type": "bool",
+ "default_value": false,
+ "settable_per_mesh": true
+ },
+ "material_flow_dependent_temperature":
+ {
+ "label": "Auto Temperature",
+ "description": "Change the temperature for each layer automatically with the average flow speed of that layer.",
+ "type": "bool",
+ "default_value": false,
+ "enabled": "machine_nozzle_temp_enabled and False",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "material_flow_temp_graph":
+ {
+ "label": "Flow Temperature Graph",
+ "description": "Data linking material flow (in mm3 per second) to temperature (degrees Celsius).",
+ "unit": "[[mm³,°C]]",
+ "type": "str",
+ "default_value": "[[3.5,200],[7.0,240]]",
+ "enabled": "False and machine_nozzle_temp_enabled and material_flow_dependent_temperature",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "meshfix_maximum_resolution":
+ {
+ "label": "Maximum Resolution",
+ "description": "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway.",
+ "type": "float",
+ "unit": "mm",
+ "default_value": 0.01,
+ "minimum_value": "0.001",
+ "minimum_value_warning": "0.005",
+ "maximum_value_warning": "0.1",
"settable_per_mesh": true
},
"support_skip_some_zags":
diff --git a/resources/definitions/gmax15plus.def.json b/resources/definitions/gmax15plus.def.json
new file mode 100644
index 0000000000..897d492bb2
--- /dev/null
+++ b/resources/definitions/gmax15plus.def.json
@@ -0,0 +1,52 @@
+{
+ "id": "gmax15plus",
+ "version": 2,
+ "name": "gMax 1.5 Plus",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "gcreate",
+ "manufacturer": "gcreate",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
+ "has_machine_quality": true,
+ "has_variants": true,
+ "variants_name": "Hotend",
+ "preferred_variant_name": "0.5mm E3D (Default)"
+ },
+
+ "overrides": {
+ "machine_extruder_count": { "default_value": 1 },
+ "machine_name": { "default_value": "gMax 1.5 Plus" },
+ "machine_heated_bed": { "default_value": false },
+ "machine_width": { "default_value": 406 },
+ "machine_depth": { "default_value": 406 },
+ "machine_height": { "default_value": 533 },
+ "machine_center_is_zero": { "default_value": false },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_nozzle_size": { "default_value": 0.5 },
+ "layer_height": { "default_value": 0.2 },
+ "layer_height_0": { "default_value": 0.3 },
+ "retraction_amount": { "default_value": 1 },
+ "retraction_speed": { "default_value": 70},
+ "adhesion_type": { "default_value": "skirt" },
+ "gantry_height": { "default_value": 50 },
+ "speed_print": { "default_value": 50 },
+ "speed_travel": { "default_value": 70 },
+ "machine_max_acceleration_x": { "default_value": 600 },
+ "machine_max_acceleration_y": { "default_value": 600 },
+ "machine_max_acceleration_z": { "default_value": 30 },
+ "machine_max_acceleration_e": { "default_value": 10000 },
+ "machine_max_jerk_xy": { "default_value": 8 },
+ "machine_max_jerk_z": { "default_value": 0.4 },
+ "machine_max_jerk_e": { "default_value": 5.0 },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+ "machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 ;Home X/Y/Z\nG29 ; Bed level\nM104 S{material_print_temperature} ; Preheat\nM109 S{material_print_temperature} ; Preheat\nG91 ;relative positioning\nG90 ;absolute positioning\nG1 Z25.0 F9000 ;raise nozzle 25mm\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." },
+ "machine_end_gcode": { "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning" },
+ "material_print_temperature": { "default_value": 202 },
+ "wall_thickness": { "default_value": 1 },
+ "top_bottom_thickness": { "default_value": 1 },
+ "bottom_thickness": { "default_value": 1 }
+ }
+}
diff --git a/resources/definitions/gmax15plus_dual.def.json b/resources/definitions/gmax15plus_dual.def.json
new file mode 100644
index 0000000000..8c57c8af63
--- /dev/null
+++ b/resources/definitions/gmax15plus_dual.def.json
@@ -0,0 +1,56 @@
+{
+ "id": "gmax15plus_dual",
+ "version": 2,
+ "name": "gMax 1.5 Plus Dual Extruder",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "GTL_180109",
+ "manufacturer": "gCreate",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
+ "has_variants": true,
+ "has_machine_quality": true,
+ "variants_name": "Hotend",
+ "preferred_variant_name": "0.5mm E3D (Default)",
+ "machine_extruder_trains": {
+ "0": "gmax15plus_dual_extruder_0",
+ "1": "gmax15plus_dual_extruder_1"
+ }
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "gMax 1.5 Plus Dual Extruder" },
+ "machine_extruder_count": { "default_value": 2 },
+ "machine_heated_bed": { "default_value": false },
+ "machine_width": { "default_value": 406 },
+ "machine_depth": { "default_value": 406 },
+ "machine_height": { "default_value": 533 },
+ "machine_center_is_zero": { "default_value": false },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_nozzle_size": { "default_value": 0.5 },
+ "layer_height": { "default_value": 0.2 },
+ "layer_height_0": { "default_value": 0.3 },
+ "retraction_amount": { "default_value": 1 },
+ "retraction_speed": { "default_value": 70},
+ "adhesion_type": { "default_value": "skirt" },
+ "gantry_height": { "default_value": 50 },
+ "speed_print": { "default_value": 50 },
+ "speed_travel": { "default_value": 70 },
+ "machine_max_acceleration_x": { "default_value": 600 },
+ "machine_max_acceleration_y": { "default_value": 600 },
+ "machine_max_acceleration_z": { "default_value": 30 },
+ "machine_max_acceleration_e": { "default_value": 10000 },
+ "machine_max_jerk_xy": { "default_value": 8 },
+ "machine_max_jerk_z": { "default_value": 0.4 },
+ "machine_max_jerk_e": { "default_value": 5.0 },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+ "machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 ;Home X/Y/Z\nG29 ; Bed level\nM104 S{material_print_temperature} T0 ; Preheat Left Extruder\nM104 S{material_print_temperature} T1 ; Preheat Right Extruder\nM109 S{material_print_temperature} T0 ; Preheat Left Extruder\nM109 S{material_print_temperature} T1 ; Preheat Right Extruder\nG91 ;relative positioning\nG90 ;absolute positioning\nM218 T1 X34.3 Y0; Set 2nd extruder offset. This can be changed later if needed\nG1 Z25.0 F9000 ;raise nozzle 25mm\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." },
+ "machine_end_gcode": { "default_value": "M104 S0 T0;Left extruder off\nM104 S0 T1; Right extruder off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning" },
+ "material_print_temperature": { "default_value": 202 },
+ "wall_thickness": { "default_value": 1 },
+ "top_bottom_thickness": { "default_value": 1 },
+ "bottom_thickness": { "default_value": 1 }
+ }
+}
diff --git a/resources/definitions/imade3d_jellybox.def.json b/resources/definitions/imade3d_jellybox.def.json
index 11df730408..b234e4b2cd 100644
--- a/resources/definitions/imade3d_jellybox.def.json
+++ b/resources/definitions/imade3d_jellybox.def.json
@@ -9,9 +9,8 @@
"platform": "imade3d_jellybox_platform.stl",
"platform_offset": [ 0, -0.3, 0],
"file_formats": "text/x-gcode",
- "preferred_variant": "*0.4*",
- "preferred_material": "*generic_pla*",
- "preferred_quality": "*fast*",
+ "preferred_variant_name": "0.4 mm",
+ "preferred_quality_type": "fast",
"has_materials": true,
"has_variants": true,
"has_machine_materials": true,
diff --git a/resources/definitions/m180.def.json b/resources/definitions/malyan_m180.def.json
similarity index 98%
rename from resources/definitions/m180.def.json
rename to resources/definitions/malyan_m180.def.json
index 71aa729b7e..5e0a6038dd 100644
--- a/resources/definitions/m180.def.json
+++ b/resources/definitions/malyan_m180.def.json
@@ -1,4 +1,5 @@
{
+ "id": "malyan_m180",
"version": 2,
"name": "Malyan M180",
"inherits": "fdmprinter",
diff --git a/resources/definitions/malyan_m200.def.json b/resources/definitions/malyan_m200.def.json
new file mode 100644
index 0000000000..a3f4f81ecf
--- /dev/null
+++ b/resources/definitions/malyan_m200.def.json
@@ -0,0 +1,85 @@
+{
+ "id": "malyan_m200",
+ "version": 2,
+ "name": "Malyan M200",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "author": "Brian Corbino, Tyler Gibson",
+ "manufacturer": "Malyan",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "platform": "malyan_m200_platform.stl",
+ "has_machine_quality": true,
+ "has_materials": true,
+ "preferred_quality_type": "normal",
+ "supports_usb_connection": true,
+ "visible": true,
+ "first_start_actions": ["MachineSettingsAction"],
+ "supported_actions": ["MachineSettingsAction"]
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Malyan M200" },
+ "speed_print": { "default_value": 50 },
+ "speed_wall_0": { "value": "round(speed_print * 0.75, 2)" },
+ "speed_wall_x": { "value": "speed_print" },
+ "speed_support": { "value": "speed_wall_0" },
+ "speed_layer_0": { "value": "round(speed_print / 2.0, 2)" },
+ "speed_travel": { "default_value": 50 },
+ "speed_travel_layer_0": { "default_value": 40 },
+ "speed_infill": { "value": "speed_print" },
+ "speed_topbottom": {"value": "speed_print / 2"},
+
+ "layer_height": { "minimum_value": "0.04375", "maximum_value": "machine_nozzle_size * 0.875", "maximum_value_warning": "machine_nozzle_size * 0.48125 + 0.0875", "default_value": 0.13125 },
+ "line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" },
+
+ "material_print_temperature": { "minimum_value": "0" },
+ "material_print_temperature_layer_0": { "value": "min(material_print_temperature + 5, 245)" },
+ "material_bed_temperature": { "minimum_value": "0" },
+ "material_bed_temperature_layer_0": { "value": "min(material_bed_temperature + 5, 70)" },
+ "material_standby_temperature": { "minimum_value": "0" },
+ "machine_show_variants": { "default_value": true },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+ "machine_start_gcode" : {
+ "default_value": "G21;(metric values)\nG90;(absolute positioning)\nM82;(set extruder to absolute mode)\nM107;(start with the fan off)\nG28;(Home the printer)\nG92 E0;(Reset the extruder to 0)\nG0 Z5 E5 F500;(Move up and prime the nozzle)\nG0 X-1 Z0;(Move outside the printable area)\nG1 Y60 E8 F500;(Draw a priming/wiping line to the rear)\nG1 X-1;(Move a little closer to the print area)\nG1 Y10 E16 F500;(draw more priming/wiping)\nG1 E15 F250;(Small retract)\nG92 E0;(Zero the extruder)"
+ },
+ "machine_end_gcode" : {
+ "default_value": "G0 X0 Y127;(Stick out the part)\nM190 S0;(Turn off heat bed, don't wait.)\nG92 E10;(Set extruder to 10)\nG1 E7 F200;(retract 3mm)\nM104 S0;(Turn off nozzle, don't wait)\nG4 S300;(Delay 5 minutes)\nM107;(Turn off part fan)\nM84;(Turn off stepper motors.)"
+ },
+ "machine_width": { "default_value": 120 },
+ "machine_depth": { "default_value": 120 },
+ "machine_height": { "default_value": 120 },
+ "machine_heated_bed": { "default_value": true },
+ "machine_center_is_zero": { "default_value": false },
+ "material_diameter": { "default_value": 1.75 },
+ "machine_nozzle_size": {
+ "default_value": 0.4,
+ "minimum_value": 0.15
+ },
+ "machine_max_feedrate_x": { "default_value": 150 },
+ "machine_max_feedrate_y": { "default_value": 150 },
+ "machine_max_feedrate_z": { "default_value": 1.5 },
+ "machine_max_feedrate_e": { "default_value": 100 },
+ "machine_max_acceleration_x": { "default_value": 800 },
+ "machine_max_acceleration_y": { "default_value": 800 },
+ "machine_max_acceleration_z": { "default_value": 20 },
+ "machine_max_acceleration_e": { "default_value": 10000 },
+ "machine_max_jerk_xy": { "default_value": 20 },
+ "machine_max_jerk_z": { "default_value": 0.4 },
+ "machine_max_jerk_e": { "default_value": 5},
+ "adhesion_type": { "default_value": "raft" },
+ "raft_margin": { "default_value": 5 },
+ "raft_airgap": { "default_value": 0.2625 },
+ "raft_base_thickness": { "value": "0.30625" },
+ "raft_interface_thickness": { "value": "0.21875" },
+ "raft_surface_layers": { "default_value": 1 },
+ "skirt_line_count": { "default_value": 2},
+ "brim_width" : { "default_value": 5},
+ "start_layers_at_same_position": { "default_value": true},
+ "retraction_combing": { "default_value": "noskin" },
+ "retraction_amount" : { "default_value": 4.5},
+ "retraction_speed" : { "default_value": 40},
+ "coasting_enable": { "default_value": true },
+ "prime_tower_enable": { "default_value": false}
+ }
+}
diff --git a/resources/definitions/monoprice_select_mini_v1.def.json b/resources/definitions/monoprice_select_mini_v1.def.json
new file mode 100644
index 0000000000..7264f0a6fc
--- /dev/null
+++ b/resources/definitions/monoprice_select_mini_v1.def.json
@@ -0,0 +1,18 @@
+{
+ "id": "monoprice_select_mini_v1",
+ "version": 2,
+ "name": "Monoprice Select Mini V1",
+ "inherits": "malyan_m200",
+ "metadata": {
+ "author": "Brian Corbino, Tyler Gibson",
+ "manufacturer": "Monoprice",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "quality_definition": "malyan_m200",
+ "visible": true
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Monoprice Select Mini V1" }
+ }
+}
diff --git a/resources/definitions/monoprice_select_mini_v2.def.json b/resources/definitions/monoprice_select_mini_v2.def.json
new file mode 100644
index 0000000000..99bb7ef50a
--- /dev/null
+++ b/resources/definitions/monoprice_select_mini_v2.def.json
@@ -0,0 +1,25 @@
+{
+ "id": "monoprice_select_mini_v2",
+ "version": 2,
+ "name": "Monoprice Select Mini V2 (E3D)",
+ "inherits": "malyan_m200",
+ "metadata": {
+ "author": "Tyler Gibson",
+ "manufacturer": "Monoprice",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "has_machine_quality": true,
+ "has_materials": true,
+ "preferred_quality_type": "normal",
+ "visible": true
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Monoprice Select Mini V2" },
+ "adhesion_type": { "default_value": "brim" },
+ "retraction_combing": { "default_value": "noskin" },
+ "retraction_amount" : { "default_value": 2.5},
+ "retraction_speed" : { "default_value": 40},
+ "material_print_temperature_layer_0": { "value": "material_print_temperature + 5" }
+ }
+}
diff --git a/resources/definitions/peopoly_moai.def.json b/resources/definitions/peopoly_moai.def.json
index 78aca31dae..5a3cbddd14 100644
--- a/resources/definitions/peopoly_moai.def.json
+++ b/resources/definitions/peopoly_moai.def.json
@@ -136,7 +136,7 @@
},
"material_diameter": {
"enabled": false,
- "value": "1.75"
+ "default_value": 1.75
},
"cool_fan_enabled": {
"enabled": false,
diff --git a/resources/definitions/seemecnc_artemis.def.json b/resources/definitions/seemecnc_artemis.def.json
new file mode 100644
index 0000000000..88a1b28de6
--- /dev/null
+++ b/resources/definitions/seemecnc_artemis.def.json
@@ -0,0 +1,43 @@
+{
+ "version": 2,
+ "name": "SeeMeCNC Artemis",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "PouncingIguana, JJ",
+ "manufacturer": "SeeMeCNC",
+ "file_formats": "text/x-gcode",
+ "icon": "icon_ultimaker2",
+ "platform": "artemis_platform.stl",
+ "has_materials": true
+ },
+
+ "overrides": {
+ "layer_height": { "default_value": 0.1618 },
+ "layer_height_0": { "default_value": 0.2 },
+ "machine_center_is_zero": { "default_value": true },
+ "machine_depth": { "default_value": 290 },
+ "machine_gcode_flavor": { "default_value": "RepRap (RepRap)" },
+ "machine_heated_bed": { "default_value": true },
+ "machine_height": { "default_value": 530 },
+ "machine_max_feedrate_z": { "default_value": 400 },
+ "machine_name": { "default_value": "Artemis" },
+ "machine_nozzle_size": { "default_value": 0.5 },
+ "machine_shape": { "default_value": "elliptic" },
+ "machine_width": { "default_value": 290 },
+ "relative_extrusion": { "default_value": false },
+ "retraction_amount": { "default_value": 3.2 },
+ "retraction_combing": { "default_value": "off" },
+ "retraction_hop_enabled": { "default_value": true },
+ "retraction_hop_only_when_collides": { "default_value": false },
+ "retraction_prime_speed": { "default_value": 45 },
+ "retraction_retract_speed": { "default_value": 45 },
+ "retraction_speed": { "default_value": 45 },
+ "machine_start_gcode": {
+ "default_value": "G28\nG1 Z15.0 F10000\nG92 E0"
+ },
+ "machine_end_gcode": {
+ "default_value": "M203 Z24000\nM104 S0\nM140 S0\nM107\nG28\nM84"
+ }
+ }
+}
diff --git a/resources/definitions/seemecnc_v32.def.json b/resources/definitions/seemecnc_v32.def.json
new file mode 100644
index 0000000000..5932403bc5
--- /dev/null
+++ b/resources/definitions/seemecnc_v32.def.json
@@ -0,0 +1,43 @@
+{
+ "version": 2,
+ "name": "SeeMeCNC Rostock Max V3.2",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "PouncingIguana, JJ",
+ "manufacturer": "SeeMeCNC",
+ "file_formats": "text/x-gcode",
+ "icon": "icon_ultimaker2",
+ "platform": "rostock_platform.stl",
+ "has_materials": true
+ },
+
+ "overrides": {
+ "layer_height": { "default_value": 0.1618 },
+ "layer_height_0": { "default_value": 0.2 },
+ "machine_center_is_zero": { "default_value": true },
+ "machine_depth": { "default_value": 265 },
+ "machine_gcode_flavor": { "default_value": "RepRap (RepRap)" },
+ "machine_heated_bed": { "default_value": true },
+ "machine_height": { "default_value": 395 },
+ "machine_max_feedrate_z": { "default_value": 300 },
+ "machine_name": { "default_value": "Rostock Max V3.2" },
+ "machine_nozzle_size": { "default_value": 0.5 },
+ "machine_shape": { "default_value": "elliptic" },
+ "machine_width": { "default_value": 265 },
+ "relative_extrusion": { "default_value": false },
+ "retraction_amount": { "default_value": 3.2 },
+ "retraction_combing": { "default_value": "off" },
+ "retraction_hop_enabled": { "default_value": true },
+ "retraction_hop_only_when_collides": { "default_value": false },
+ "retraction_prime_speed": { "default_value": 45 },
+ "retraction_retract_speed": { "default_value": 45 },
+ "retraction_speed": { "default_value": 45 },
+ "machine_start_gcode": {
+ "default_value": "G28\nG1 Z15.0 F10000\nG92 E0"
+ },
+ "machine_end_gcode": {
+ "default_value": "M203 Z24000\nM104 S0\nM140 S0\nM107\nG28\nM84"
+ }
+ }
+}
diff --git a/resources/definitions/tevo_blackwidow.def.json b/resources/definitions/tevo_blackwidow.def.json
new file mode 100644
index 0000000000..22f7095e17
--- /dev/null
+++ b/resources/definitions/tevo_blackwidow.def.json
@@ -0,0 +1,67 @@
+{
+ "version": 2,
+ "name": "Tevo Black Widow",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "TheTobby",
+ "manufacturer": "Tevo",
+ "file_formats": "text/x-gcode",
+ "icon": "icon_ultimaker2",
+ "has_materials": false,
+ "has_machine_quality": true,
+ "platform": "tevo_blackwidow.stl",
+ "preferred_quality_type": "normal"
+ },
+ "overrides":
+ {
+ "machine_name":
+ {
+ "default_value": "Tevo Black Widow"
+ },
+ "machine_heated_bed":
+ {
+ "default_value": true
+ },
+ "machine_width":
+ {
+ "default_value": 350
+ },
+ "machine_height":
+ {
+ "default_value": 250
+ },
+ "machine_depth":
+ {
+ "default_value": 250
+ },
+ "machine_center_is_zero":
+ {
+ "default_value": false
+ },
+ "machine_nozzle_size":
+ {
+ "default_value": 0.4
+ },
+ "material_diameter":
+ {
+ "default_value": 1.75
+ },
+ "gantry_height":
+ {
+ "default_value": 0
+ },
+ "machine_gcode_flavor":
+ {
+ "default_value": "RepRap (Marlin/Sprinter)"
+ },
+ "machine_start_gcode":
+ {
+ "default_value": "M280 P0 S160 ; release BLTouch alarm (OK to send for Non BLTouch)\nM420 Z2 ; set fade leveling at 2mm for BLTouch (OK to send for Non BLTouch)\nG28 ; home all\nG29 ; probe bed\nG92 E0 ;zero the extruded length\nG1 X0.0 Y50.0 Z10.0 F3600\n; perform wipe and prime\nG1 Z0.0 F1000\nG1 Z0.2 Y70.0 E9.0 F1000.0 ; prime\nG1 Y100.0 E12.5 F1000.0 ; prime\nG92 E0 ; zero extruder again\nM117 Printing..."
+ },
+ "machine_end_gcode":
+ {
+ "default_value": "G92 E0 ; zero the extruded length again\nG1 E-1.5 F500 ; retract the filament to release some of the pressure\nM104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nG28 X0 ; home X axis\nG1 Y245 ; move Y axis to end position\nM84 ; disable motors\nM107 ; turn off fan"
+ }
+ }
+}
diff --git a/resources/definitions/tevo_tarantula.def.json b/resources/definitions/tevo_tarantula.def.json
index 097281aaeb..a9f9cefff2 100644
--- a/resources/definitions/tevo_tarantula.def.json
+++ b/resources/definitions/tevo_tarantula.def.json
@@ -66,7 +66,7 @@
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_end_gcode": {
- "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nG1 Y200 F3600 ;move baseplate to front for easier access to printed object"
+ "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG1 X0 Y200 F3600 ;move extruder out of the way by moving the baseplate to the front for easier access to printed object\nM84 ;steppers off"
}
}
}
diff --git a/resources/definitions/ubuild-3d_mr_bot_280.def.json b/resources/definitions/ubuild-3d_mr_bot_280.def.json
new file mode 100644
index 0000000000..7f9069370c
--- /dev/null
+++ b/resources/definitions/ubuild-3d_mr_bot_280.def.json
@@ -0,0 +1,49 @@
+{
+ "id": "ubuild-3d_mr_bot_280",
+ "version": 2,
+ "name": "uBuild-3D Mr Bot 280",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "visible": true,
+ "author": "uBuild-3D",
+ "manufacturer": "uBuild-3D",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "icon": "icon_uBuild-3D",
+ "platform": "mr_bot_280_platform.stl",
+ "has_materials": true
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Mr Bot 280" },
+ "machine_heated_bed": { "default_value": true },
+ "machine_width": { "default_value": 275 },
+ "machine_height": { "default_value": 275 },
+ "machine_depth": { "default_value": 275 },
+ "machine_center_is_zero": { "default_value": false },
+ "material_diameter": { "default_value": 1.75 },
+ "material_bed_temperature": { "default_value": 70 },
+ "machine_nozzle_size": { "default_value": 0.4 },
+ "layer_height": { "default_value": 0.2 },
+ "layer_height_0": { "default_value": 0.1 },
+ "retraction_amount": { "default_value": 2 },
+ "retraction_speed": { "default_value": 50 },
+ "retraction_retract_speed": { "default_value": 50 },
+ "retraction_prime_speed": { "default_value": 30 },
+ "adhesion_type": { "default_value": "skirt" },
+ "machine_nozzle_heat_up_speed": { "default_value": 2 },
+ "machine_nozzle_cool_down_speed": { "default_value": 2 },
+ "machine_head_with_fans_polygon": { "default_value": [[-20,20],[10,10],[10,10],[10,10]] },
+ "gantry_height": { "default_value": 275 },
+ "machine_max_feedrate_z": { "default_value": 15 },
+ "machine_max_feedrate_e": { "default_value": 60 },
+ "machine_max_acceleration_z": { "default_value": 1000 },
+ "machine_acceleration": { "default_value": 2000 },
+ "machine_max_jerk_xy": { "default_value": 20 },
+ "machine_max_jerk_z": { "default_value": 0.4 },
+ "machine_max_jerk_e": { "default_value": 5 },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+ "machine_start_gcode": { "default_value": "G21 ; set units to millimeters\nG90 ; use absolute positioning\nM82 ; absolute extrusion mode\nM140 S{material_bed_temperature} ; set bed temp\nM104 S{material_print_temperature} ; set extruder temp\nG28 ; home X, Y and Z\nG29 ; probe sequence (for auto-leveling)\nG1 Z15 F600 ; go to Z15 position\nG1 X0 Y-20 F10000 ; go to X0 Y-20 position\nM190 S{material_bed_temperature} ; wait for bed temp\nM109 S{material_print_temperature} ; wait for extruder temp\nG92 E0 ; reset extruder distance position\nG1 E25 F100 ; extrude 25mm of material\nG92 E0 ; reset extruder distance position\nM117 Printing..." },
+ "machine_end_gcode": { "default_value": "M400 ; wait for moves to finish\nG92 Z0 E0 ; reset Z position\nG1 E-2 F9000 ; retract material\nG1 Z2 ; get extruder out of the way\nM104 S0 ; turn off extruder\nG1 Y285 F3000 ; present finished print\nM140 S0 ; turn off bed\nM84 ; disable motors\nM117 Print complete" }
+ }
+}
diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json
index 038071574f..33b48116be 100644
--- a/resources/definitions/ultimaker2.def.json
+++ b/resources/definitions/ultimaker2.def.json
@@ -20,12 +20,10 @@
"overrides": {
"machine_name": { "default_value": "Ultimaker 2" },
"machine_start_gcode" : {
- "default_value": "",
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \"G21 ;metric values\\nG90 ;absolute positioning\\nM82 ;set extruder to absolute mode\\nM107 ;start with the fan off\\nG28 Z0 ;move Z to bottom endstops\\nG28 X0 Y0 ;move X/Y to endstops\\nG1 X15 Y0 F4000 ;move X/Y to front of printer\\nG1 Z15.0 F9000 ;move the platform to 15mm\\nG92 E0 ;zero the extruded length\\nG1 F200 E10 ;extrude 10 mm of feed stock\\nG92 E0 ;zero the extruded length again\\nG1 F9000\\n;Put printing message on LCD screen\\nM117 Printing...\""
},
"machine_end_gcode" : {
- "default_value": "",
- "value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \"M104 S0 ;extruder heater off\\nM140 S0 ;heated bed heater off (if you have it)\\nG91 ;relative positioning\\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\\nM84 ;steppers off\\nG90 ;absolute positioning\""
+ "value": "\";Version _2.6 of the firmware can abort the print too early if the file ends\\n;too soon. However if the file hasn't ended yet because there are comments at\\n;the end of the file, it won't abort yet. Therefore we have to put at least 512\\n;bytes at the end of the g-code so that the file is not yet finished by the\\n;time that the motion planner gets flushed. With firmware version _3.3 this\\n;should be fixed, so this comment wouldn't be necessary any more. Now we have\\n;to pad this text to make precisely 512 bytes.\" if machine_gcode_flavor == \"UltiGCode\" else \"M104 S0 ;extruder heater off\\nM140 S0 ;heated bed heater off (if you have it)\\nG91 ;relative positioning\\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\\nM84 ;steppers off\\nG90 ;absolute positioning\\n;Version _2.6 of the firmware can abort the print too early if the file ends\\n;too soon. However if the file hasn't ended yet because there are comments at\\n;the end of the file, it won't abort yet. Therefore we have to put at least 512\\n;bytes at the end of the g-code so that the file is not yet finished by the\\n;time that the motion planner gets flushed. With firmware version _3.3 this\\n;should be fixed, so this comment wouldn't be necessary any more. Now we have\\n;to pad this text to make precisely 512 bytes.\""
},
"machine_width": {
"default_value": 223
diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json
index 58833904d2..935bf5b6c0 100644
--- a/resources/definitions/ultimaker2_plus.def.json
+++ b/resources/definitions/ultimaker2_plus.def.json
@@ -9,7 +9,7 @@
"file_formats": "text/x-gcode",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2Plusbackplate.png",
- "preferred_variant": "*0.4*",
+ "preferred_variant_name": "0.4 mm",
"has_variants": true,
"has_materials": true,
"has_machine_materials": true,
diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json
index e7b9b6255e..dcf6b167c0 100644
--- a/resources/definitions/ultimaker3.def.json
+++ b/resources/definitions/ultimaker3.def.json
@@ -15,8 +15,8 @@
"has_machine_materials": true,
"has_variant_materials": true,
"has_variants": true,
- "preferred_variant": "*aa04*",
- "preferred_quality": "*Normal*",
+ "preferred_variant_name": "AA 0.4",
+ "preferred_quality_type": "normal",
"variants_name": "Print core",
"machine_extruder_trains":
{
@@ -90,6 +90,7 @@
"infill_overlap": { "value": "0" },
"infill_pattern": { "value": "'triangles'" },
"infill_wipe_dist": { "value": "0" },
+ "initial_layer_line_width_factor": { "value": "120" },
"jerk_enabled": { "value": "True" },
"jerk_layer_0": { "value": "jerk_topbottom" },
"jerk_prime_tower": { "value": "math.ceil(jerk_print * 15 / 25)" },
@@ -109,7 +110,9 @@
"material_bed_temperature": { "maximum_value": "115" },
"material_bed_temperature_layer_0": { "maximum_value": "115" },
"material_standby_temperature": { "value": "100" },
+ "meshfix_maximum_resolution": { "value": "0.04" },
"multiple_mesh_overlap": { "value": "0" },
+ "optimize_wall_printing_order": { "value": "True" },
"prime_tower_enable": { "default_value": true },
"raft_airgap": { "value": "0" },
"raft_base_thickness": { "value": "0.3" },
@@ -150,6 +153,7 @@
"travel_avoid_distance": { "value": "3" },
"wall_0_inset": { "value": "0" },
"wall_line_width_x": { "value": "round(wall_line_width * 0.3 / 0.35, 2)" },
- "wall_thickness": { "value": "1" }
+ "wall_thickness": { "value": "1" },
+ "zig_zaggify_infill": { "value": "True" }
}
}
diff --git a/resources/definitions/ultimaker3_extended.def.json b/resources/definitions/ultimaker3_extended.def.json
index 385199f4f1..3a1be3a303 100644
--- a/resources/definitions/ultimaker3_extended.def.json
+++ b/resources/definitions/ultimaker3_extended.def.json
@@ -16,7 +16,7 @@
"has_variant_materials": true,
"has_materials": true,
"has_variants": true,
- "preferred_variant": "*aa04*",
+ "preferred_variant_name": "AA 0.4",
"variants_name": "Print core",
"machine_extruder_trains":
{
diff --git a/resources/definitions/vertex_delta_k8800.def.json b/resources/definitions/vertex_delta_k8800.def.json
index 736d17e17a..495fd5a5bc 100644
--- a/resources/definitions/vertex_delta_k8800.def.json
+++ b/resources/definitions/vertex_delta_k8800.def.json
@@ -6,7 +6,9 @@
"manufacturer": "Velleman nv",
"file_formats": "text/x-gcode",
"visible": true,
- "author": "Velleman"
+ "author": "Velleman",
+ "has_machine_quality": true,
+ "has_materials": true
},
"overrides": {
"material_diameter": {
@@ -59,6 +61,99 @@
},
"machine_end_gcode": {
"default_value": "; Vertex Delta end code\nM107 ; Turn off fan\nG91 ; Relative positioning\nT0\nG1 E-1 F1500; Reduce filament pressure\nM104 T0 S0\nG90 ; Absolute positioning\nG92 E0 ; Reset extruder position\nM300 S4000 P500\nM300 S3000 P500\nM300 S2000 P800\nG28\nM84 ; Turn steppers off"
+ },
+ "line_width": {
+ "value": 0.35
+ },
+ "infill_line_width": {
+ "value": 0.35
+ },
+ "wall_thickness": {
+ "value": 0.7
+ },
+ "top_bottom_thickness": {
+ "value": 0.6
+ },
+ "infill_sparse_density": {
+ "value": 40
+ },
+ "infill_overlap": {
+ "value": 5
+ },
+ "min_infill_area": {
+ "value": 0.1
+ },
+ "retract_at_layer_change": {
+ "value": true
+ },
+ "retraction_min_travel": {
+ "value": 1
+ },
+ "retraction_count_max": {
+ "value": 15
+ },
+ "retraction_extrusion_window": {
+ "value": 1
+ },
+ "speed_print": {
+ "value": 35
+ },
+ "speed_infill": {
+ "value": 40
+ },
+ "speed_wall": {
+ "value": 35
+ },
+ "speed_wall_x": {
+ "value": 35
+ },
+ "speed_topbottom": {
+ "value": 35
+ },
+ "speed_travel": {
+ "value": 190
+ },
+ "speed_layer_0": {
+ "value": 20
+ },
+ "speed_print_layer_0": {
+ "value": 20
+ },
+ "skirt_brim_speed": {
+ "value": 20
+ },
+ "travel_retract_before_outer_wall": {
+ "value": false
+ },
+ "retraction_hop_enabled": {
+ "value": true
+ },
+ "retraction_hop": {
+ "value": 0.1
+ },
+ "cool_fan_full_at_height": {
+ "value": 2
+ },
+ "cool_fan_full_layer": {
+ "value": 11
+ },
+ "cool_min_layer_time": {
+ "value": 8
+ },
+ "support_z_distance": {
+ "value": 0.4
+ },
+ "support_xy_distance": {
+ "value": 1
+ },
+ "brim_width": {
+ "value": 6
+ },
+ "skirt_line_count": {
+ "value": 2
+ },
+ "skirt_brim_minimal_length": {
+ "value": 50
}
}
}
\ No newline at end of file
diff --git a/resources/extruders/cartesio_extruder_0.def.json b/resources/extruders/cartesio_extruder_0.def.json
index 5558d9325e..47b5b5abf5 100644
--- a/resources/extruders/cartesio_extruder_0.def.json
+++ b/resources/extruders/cartesio_extruder_0.def.json
@@ -16,17 +16,10 @@
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_code": {
- "default_value": "\n;start extruder_0\n\nM117 printing...\n"
+ "default_value": "\n;start T0\n\nM104 T0 S{material_print_temperature_layer_0}\nG1 X65 Y35 F9000 ; go to wipe position\nM109 T0 S{material_print_temperature_layer_0}; wait for temp\nG1 E10 F300; prime\nG92 E0\nG1 X45 Y15 F3000; wipe\nG1 X55 F9000\nG1 Y35 F6000; wipe again\n\nM117 printing...\n"
},
"machine_extruder_end_code": {
- "default_value": "\nM104 T0 S120\n;end extruder_0\nM117 temp is {material_print_temp}\n"
- },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
- "machine_extruder_start_pos_x": { "default_value": 24 },
- "machine_extruder_start_pos_y": { "default_value": 16 },
- "machine_extruder_end_pos_abs": { "default_value": true },
- "machine_extruder_end_pos_x": { "default_value": 48 },
- "machine_extruder_end_pos_y": { "default_value": 16 }
+ "default_value": "\nM104 T0 S{material_standby_temperature}\nG1 X65 Y35 F9000 ; go to wipe position\nM109 T0 R{material_standby_temperature}; wait for temp\nG1 X45 Y15 F3000; wipe\nG1 X55 F9000\nG1 Y35 F6000; wipe again\n\n;end T0\n\n"
+ }
}
}
diff --git a/resources/extruders/cartesio_extruder_1.def.json b/resources/extruders/cartesio_extruder_1.def.json
index f8350f8091..78bcccd12a 100644
--- a/resources/extruders/cartesio_extruder_1.def.json
+++ b/resources/extruders/cartesio_extruder_1.def.json
@@ -16,17 +16,10 @@
"machine_nozzle_offset_x": { "default_value": 24.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_code": {
- "default_value": "\n;start extruder_1\n\nM117 printing...\n"
+ "default_value": "\n;start T1\n\nM104 T1 S{material_print_temperature_layer_0}\nG1 X41 Y35 F9000 ; go to wipe position\nM109 T1 S{material_print_temperature_layer_0}; wait for temp\nG1 E10 F300; prime\nG92 E0\nG1 X21 Y15 F3000; wipe\nG1 X34 F9000\nG1 Y35 F6000; wipe again\n\nM117 printing...\n"
},
"machine_extruder_end_code": {
- "default_value": "\nM104 T1 S120\n;end extruder_1\n"
- },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
- "machine_extruder_start_pos_x": { "default_value": 48 },
- "machine_extruder_start_pos_y": { "default_value": 16 },
- "machine_extruder_end_pos_abs": { "default_value": true },
- "machine_extruder_end_pos_x": { "default_value": 24 },
- "machine_extruder_end_pos_y": { "default_value": 16 }
+ "default_value": "\nM104 T1 S{material_standby_temperature}\nG1 X41 Y35 F9000 ; go to wipe position\nM109 T1 R{material_standby_temperature}; wait for temp\nG1 X21 Y15 F3000; wipe\nG1 X31 F9000\nG1 Y35 F6000; wipe again\n\n;end T1\n\n"
+ }
}
}
diff --git a/resources/extruders/cartesio_extruder_2.def.json b/resources/extruders/cartesio_extruder_2.def.json
index bfc10e75c3..dbd6643bfe 100644
--- a/resources/extruders/cartesio_extruder_2.def.json
+++ b/resources/extruders/cartesio_extruder_2.def.json
@@ -13,20 +13,13 @@
"default_value": 2,
"maximum_value": "3"
},
- "machine_nozzle_offset_x": { "default_value": 0.0 },
- "machine_nozzle_offset_y": { "default_value": 0.0 },
+ "machine_nozzle_offset_x": { "default_value": 24.0 },
+ "machine_nozzle_offset_y": { "default_value": -100.0 },
"machine_extruder_start_code": {
- "default_value": "\n;start extruder_2\n\nM117 printing...\n"
+ "default_value": "\n;start T2\n\nM104 T2 S{material_print_temperature_layer_0}\nG1 X41 Y215 F9000 ; go to wipe position\nM109 T2 S{material_print_temperature_layer_0}; wait for temp\nG1 E10 F300; prime\nG92 E0\nG1 X21 Y235 F3000; wipe\nG1 X31 F9000\nG1 Y215 F6000; wipe again\n\nM117 printing...\n"
},
"machine_extruder_end_code": {
- "default_value": "\nM104 T2 S120\n;end extruder_2\n"
- },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
- "machine_extruder_start_pos_x": { "default_value": 24 },
- "machine_extruder_start_pos_y": { "default_value": 309 },
- "machine_extruder_end_pos_abs": { "default_value": true },
- "machine_extruder_end_pos_x": { "default_value": 48 },
- "machine_extruder_end_pos_y": { "default_value": 309 }
+ "default_value": "\nM104 T2 S{material_standby_temperature}\nG1 X41 Y215 F9000 ; go to wipe position\nM109 T2 R{material_standby_temperature}; wait for temp\nG1 X21 Y235 F3000; wipe\nG1 X31 F9000\nG1 Y215 F6000; wipe again\n\n;end T2\n\n"
+ }
}
}
diff --git a/resources/extruders/cartesio_extruder_3.def.json b/resources/extruders/cartesio_extruder_3.def.json
index f0be53e564..beed117abe 100644
--- a/resources/extruders/cartesio_extruder_3.def.json
+++ b/resources/extruders/cartesio_extruder_3.def.json
@@ -14,19 +14,12 @@
"maximum_value": "3"
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
- "machine_nozzle_offset_y": { "default_value": 0.0 },
+ "machine_nozzle_offset_y": { "default_value": -100.0 },
"machine_extruder_start_code": {
- "default_value": "\n;start extruder_3\n\nM117 printing...\n"
+ "default_value": "\n;start T3\n\nM104 T3 S{material_print_temperature_layer_0}\nG1 X65 Y215 F9000 ; go to wipe position\nM109 T3 S{material_print_temperature_layer_0}; wait for temp\nG1 E10 F300; prime\nG92 E0\nG1 X45 Y235 F3000; wipe\nG1 X55 F9000\nG1 Y215 F6000; wipe again\n\nM117 printing...\n"
},
"machine_extruder_end_code": {
- "default_value": "\nM104 T3 S120\n;end extruder_3\n"
- },
-
- "machine_extruder_start_pos_abs": { "default_value": true },
- "machine_extruder_start_pos_x": { "default_value": 48 },
- "machine_extruder_start_pos_y": { "default_value": 309 },
- "machine_extruder_end_pos_abs": { "default_value": true },
- "machine_extruder_end_pos_x": { "default_value": 24 },
- "machine_extruder_end_pos_y": { "default_value": 309}
+ "default_value": "\nM104 T3 S{material_standby_temperature}\nG1 X65 Y215 F9000 ; go to wipe position\nM109 T3 R{material_standby_temperature}; wait for temp\nG1 X45 Y235 F3000; wipe\nG1 X55 F9000\nG1 Y215 F6000; wipe again\n\n;end T3\n\n"
+ }
}
}
diff --git a/resources/extruders/gmax15plus_dual_extruder_0.def.json b/resources/extruders/gmax15plus_dual_extruder_0.def.json
new file mode 100644
index 0000000000..0037b75a1c
--- /dev/null
+++ b/resources/extruders/gmax15plus_dual_extruder_0.def.json
@@ -0,0 +1,28 @@
+{
+ "id": "gmax15plus_dual_extruder_0",
+ "version": 2,
+ "name": "Left Extruder",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "gmax15plus_dual",
+ "position": "0"
+ },
+
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 0,
+ "maximum_value": "1"
+ },
+ "machine_nozzle_offset_x": { "default_value": 0.0 },
+ "machine_nozzle_offset_y": { "default_value": 0.0 },
+ "machine_nozzle_size": { "default_value": 0.5 },
+
+ "machine_extruder_start_pos_abs": { "default_value": true },
+ "machine_extruder_start_pos_x": { "value": 40 },
+ "machine_extruder_start_pos_y": { "value": 210 },
+ "machine_extruder_end_pos_abs": { "default_value": true },
+ "machine_extruder_end_pos_x": { "value": 40 },
+ "machine_extruder_end_pos_y": { "value": 210 }
+
+ }
+}
diff --git a/resources/extruders/gmax15plus_dual_extruder_1.def.json b/resources/extruders/gmax15plus_dual_extruder_1.def.json
new file mode 100644
index 0000000000..2db9aef3ee
--- /dev/null
+++ b/resources/extruders/gmax15plus_dual_extruder_1.def.json
@@ -0,0 +1,30 @@
+{
+ "id": "gmax15plus_dual_extruder_1",
+ "version": 2,
+ "name": "Right Extruder",
+ "inherits": "fdmextruder",
+ "metadata": {
+ "machine": "gmax15plus_dual",
+ "position": "1"
+ },
+
+ "overrides": {
+ "extruder_nr": {
+ "default_value": 1,
+ "maximum_value": "1"
+ },
+ "machine_nozzle_offset_x": { "default_value": 0.0 },
+ "machine_nozzle_offset_y": { "default_value": 0.0 },
+ "machine_nozzle_size": { "default_value": 0.5 },
+
+ "machine_extruder_start_pos_abs": { "default_value": true },
+ "machine_extruder_start_pos_x": { "value": 40 },
+ "machine_extruder_start_pos_y": { "value": 210 },
+ "machine_extruder_end_pos_abs": { "default_value": true },
+ "machine_extruder_end_pos_x": { "value": 40 },
+ "machine_extruder_end_pos_y": { "value": 210 }
+
+ }
+}
+
+
diff --git a/resources/extruders/punchtec_connect_xl_extruder_left.def.json b/resources/extruders/punchtec_connect_xl_extruder_0.def.json
similarity index 100%
rename from resources/extruders/punchtec_connect_xl_extruder_left.def.json
rename to resources/extruders/punchtec_connect_xl_extruder_0.def.json
diff --git a/resources/extruders/punchtec_connect_xl_extruder_right.def.json b/resources/extruders/punchtec_connect_xl_extruder_1.def.json
similarity index 100%
rename from resources/extruders/punchtec_connect_xl_extruder_right.def.json
rename to resources/extruders/punchtec_connect_xl_extruder_1.def.json
diff --git a/resources/i18n/cura.pot b/resources/i18n/cura.pot
index be6613ebf5..1620e11d81 100644
--- a/resources/i18n/cura.pot
+++ b/resources/i18n/cura.pot
@@ -1,14 +1,14 @@
# Cura
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
+# Ruben Dulek , 2018.
#
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.1\n"
+"Project-Id-Version: Cura 3.2\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2017-08-02 16:53+0000\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: TEAM\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:29
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
msgctxt "@action"
msgid "Machine Settings"
msgstr ""
@@ -55,12 +55,11 @@ msgstr ""
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:646
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:875
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:659
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:370
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:78
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:104
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:99
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:355
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:376
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:139
@@ -100,7 +99,7 @@ msgctxt "@info:tooltip"
msgid "Open the Doodle3D Connect web interface"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:34
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr ""
@@ -115,80 +114,85 @@ msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:26
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:29
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:31
msgctxt "@info:status"
msgid "Connected via USB"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:status"
msgid "Unable to start a new job because the printer is busy or not connected."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:title"
msgid "Printer Unavailable"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:status"
msgid ""
"This printer does not support USB printing because it uses UltiGCode flavor."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:title"
msgid "USB Printing"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
msgctxt "@info:status"
msgid ""
"Unable to start a new job because the printer does not support usb printing."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1349
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:946
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1418
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1496
msgctxt "@info:title"
msgid "Warning"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
msgctxt "@info"
msgid "Unable to update firmware because there are no printers connected."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
#, python-format
msgctxt "@info"
msgid "Could not find firmware required for the printer at %s."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
msgctxt "@info:title"
msgid "Printer Firmware"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Prepare"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Save to Removable Drive"
@@ -232,11 +236,11 @@ msgid "Could not save to removable drive {0}: {1}"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:146
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:693
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:701
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:153
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1358
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:160
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1427
msgctxt "@info:title"
msgid "Error"
msgstr ""
@@ -286,7 +290,7 @@ msgid "Removable Drive"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:53
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:51
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr ""
@@ -406,37 +410,37 @@ msgctxt "@info:title"
msgid "Printer Status"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:691
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No Printcore loaded in slot {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:699
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No material loaded in slot {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:709
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:710
#, python-brace-format
msgctxt "@label"
msgid "Not enough material for spool {0}."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:719
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:720
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:733
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:734
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:741
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:742
#, python-brace-format
msgctxt "@label"
msgid ""
@@ -444,12 +448,12 @@ msgid ""
"performed on the printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:746
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:748
msgctxt "@label"
msgid ""
"There is a mismatch between the configuration or calibration of the printer "
@@ -457,65 +461,65 @@ msgid ""
"that are inserted in your printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:753
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:754
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:864
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:865
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:258
msgctxt "@info:status"
msgid ""
"Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:title"
msgid "Sending Data"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:944
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
msgctxt "@info:status"
msgid "Unable to send data to printer. Is another job still active?"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1085
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1087
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:196
msgctxt "@label:MonitorStatus"
msgid "Aborting print..."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1091
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1093
msgctxt "@label:MonitorStatus"
msgid "Print aborted. Please check the printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1097
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
msgctxt "@label:MonitorStatus"
msgid "Pausing print..."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1101
msgctxt "@label:MonitorStatus"
msgid "Resuming print..."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1289
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1295
msgctxt "@label"
msgid ""
"The PrintCores and/or materials on your printer differ from those within "
@@ -544,80 +548,84 @@ msgid ""
msgstr ""
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:115
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:520
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:533
#, python-brace-format
msgid ""
"{printer_name} is reserved to print '{job_name}'. Please change the "
"printer's configuration to match the job, for it to start printing."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:277
msgctxt "@info:status"
msgid ""
"Unable to send new print job: this 3D printer is not (yet) set up to host a "
"group of connected Ultimaker 3 printers."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:410
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to send print job to group {cluster_name}."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:418
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:431
#, python-brace-format
msgctxt "@info:status"
msgid "Sent {file_name} to group {cluster_name}."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:436
msgctxt "@action:button"
msgid "Show print jobs"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:424
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:437
msgctxt "@info:tooltip"
msgid "Opens the print jobs interface in your browser."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:502
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Unknown"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:492
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:494
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:497
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:507
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:510
msgctxt "@info:status"
msgid "Print finished"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:522
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:525
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:535
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:538
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:282
msgctxt "@label:status"
msgid "Action required"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:643
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:656
#, python-brace-format
msgctxt "@info:progress"
msgid "Sending {file_name} to group {cluster_name}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:19
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:17
msgctxt "@action"
msgid "Connect via Network"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:64
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Monitor"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
#, python-brace-format
msgctxt ""
"@info Don't translate {machine_name}, since it gets replaced by a printer "
@@ -627,74 +635,118 @@ msgid ""
"update the firmware on your printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:65
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:67
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:68
msgctxt "@action:button"
msgid "How to update"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:77
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:79
msgctxt "@info"
msgid "Could not access update information."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:199
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
msgctxt "@info:status"
msgid ""
-"Errors appeared while opening your SolidWorks file! Please "
-"check, whether it is possible to open your file in SolidWorks itself without "
-"any problems as well!"
+"SolidWorks reported errors, while opening your file. We recommend to solve "
+"these issues inside SolidWorks itself."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:31
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
+msgctxt "@info:status"
+msgid ""
+"Found no models inside your drawing. Could you please check it's content "
+"again and make sure one part or assembly is inside?\n"
+"\n"
+" Thanks!."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
+msgctxt "@info:status"
+msgid ""
+"Found more then one part or assembly inside your drawing. We currently only "
+"support drawings with exactly one part or assembly inside.\n"
+"\n"
+"Sorry!"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
msgctxt "@item:inlistbox"
msgid "SolidWorks part file"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:35
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:29
msgctxt "@item:inlistbox"
msgid "SolidWorks assembly file"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.py:21
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
+msgctxt "@item:inlistbox"
+msgid "SolidWorks drawing file"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"We could not find a valid installation of SolidWorks on your system. That "
+"means that either SolidWorks is not installed or you don't own an valid "
+"license. Please make sure that running SolidWorks itself works without "
+"issues and/or contact your ICT.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"You are currently running this plugin on an operating system other than "
+"Windows. This plugin will only work on Windows with SolidWorks installed, "
+"including an valid license. Please install this plugin on a Windows machine "
+"with SolidWorks installed.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
msgid "Configure"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/CommonComReader.py:135
-#, python-format
-msgctxt "@info:status"
-msgid "Error while starting %s!"
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
+msgid "Installation guide for SolidWorks macro"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
-msgid "Simulation view"
+msgid "Layer view"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:101
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
msgctxt "@info:title"
msgid "Simulation View"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:26
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:25
msgid "Modify G-Code"
msgstr ""
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
msgctxt "@info"
-msgid ""
-"Cura collects anonymised slicing statistics. You can disable this in the "
-"preferences."
+msgid "Cura collects anonymized usage statistics."
msgstr ""
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
@@ -704,7 +756,27 @@ msgstr ""
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
msgctxt "@action:button"
-msgid "Dismiss"
+msgid "Allow"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+msgctxt "@action:tooltip"
+msgid ""
+"Allow Cura to send anonymized usage statistics to help prioritize future "
+"improvements to Cura. Some of your preferences and settings are sent, the "
+"Cura version and a hash of the models you're slicing."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+msgctxt "@action:button"
+msgid "Disable"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
+msgctxt "@action:tooltip"
+msgid ""
+"Don't allow Cura to send anonymized usage statistics. You can enable it "
+"again in the preferences."
msgstr ""
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
@@ -712,6 +784,18 @@ msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
+msgctxt "@item:inlistbox"
+msgid "Blender file"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
+msgctxt "@info:status"
+msgid ""
+"Could not export using \"{}\" quality!\n"
+"Felt back to \"{}\"."
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
msgctxt "@item:inlistbox"
@@ -743,23 +827,23 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
msgctxt "@info:status"
msgid ""
"Unable to slice with the current material as it is incompatible with the "
"selected machine or configuration."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:297
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:319
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:327
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:336
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:349
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:357
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
msgctxt "@info:title"
msgid "Unable to slice"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:296
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
#, python-brace-format
msgctxt "@info:status"
msgid ""
@@ -767,7 +851,7 @@ msgid ""
"errors: {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:318
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:348
#, python-brace-format
msgctxt "@info:status"
msgid ""
@@ -775,13 +859,13 @@ msgid ""
"errors on one or more models: {error_labels}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:356
msgctxt "@info:status"
msgid ""
"Unable to slice because the prime tower or prime position(s) are invalid."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:335
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:365
msgctxt "@info:status"
msgid ""
"Nothing to slice because none of the models fit the build volume. Please "
@@ -789,12 +873,12 @@ msgid ""
msgstr ""
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:status"
msgid "Processing Layers"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:title"
msgid "Information"
msgstr ""
@@ -836,14 +920,14 @@ msgid ""
"UGII_USER_DIR for Siemens NX."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:585
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:165
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
msgctxt "@title:tab"
msgid "Recommended"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:169
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:595
msgctxt "@title:tab"
msgid "Custom"
msgstr ""
@@ -854,24 +938,24 @@ msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:126
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1142
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:159
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1185
msgctxt "@label"
msgid "Nozzle"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:152
#, python-brace-format
msgctxt "@info:status"
msgid "Failed to get plugin ID from {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:165
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:153
msgctxt "@info:tile"
msgid "Warning"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:203
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:191
msgctxt "@window:title"
msgid "Plugin browser"
msgstr ""
@@ -886,18 +970,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:314
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:321
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:316
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:426
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:323
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:464
msgctxt "@info:title"
msgid "G-code Details"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:424
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:462
msgctxt "@info:generic"
msgid ""
"Make sure the g-code is suitable for your printer and printer configuration "
@@ -910,6 +994,16 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr ""
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Profile Assistant"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
+msgctxt "@item:inlistbox"
+msgid "Profile Assistant"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
msgctxt "@item:inlistbox"
msgid "3MF file"
@@ -941,111 +1035,90 @@ msgctxt "@action"
msgid "Level build plate"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
msgctxt "@tooltip"
msgid "Skin"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
msgctxt "@tooltip"
msgid "Infill"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:93
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
msgctxt "@tooltip"
msgid "Support Infill"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:94
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
msgctxt "@tooltip"
msgid "Support Interface"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:95
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
msgctxt "@tooltip"
msgid "Support"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:96
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
msgctxt "@tooltip"
msgid "Skirt"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:97
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
msgctxt "@tooltip"
msgid "Travel"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
msgctxt "@tooltip"
msgid "Retractions"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
msgctxt "@tooltip"
msgid "Other"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:199
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:231
msgctxt "@label unknown material"
msgid "Unknown"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:284
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:318
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:469
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:440
msgctxt "@item:material"
msgid "No material loaded"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:476
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:447
msgctxt "@item:material"
msgid "Unknown material"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:30
-msgctxt "@info:status"
-msgid "Finding new location for objects"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:34
-msgctxt "@info:title"
-msgid "Finding Location"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:89
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
-msgctxt "@info:status"
-msgid "Unable to find a location within the build volume for all objects"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:90
-msgctxt "@info:title"
-msgid "Can't Find Location"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:431
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:113
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:437
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:120
msgctxt "@title:window"
msgid "File Already Exists"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:432
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:114
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:438
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
msgid ""
@@ -1053,34 +1126,29 @@ msgid ""
"overwrite it?"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:872
msgctxt "@label"
msgid "Custom"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:819
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:876
msgctxt "@label"
msgid "Custom Material"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:182
-msgctxt "@menuitem"
-msgid "Global"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:229
+#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:205
msgctxt "@menuitem"
msgid "Not overridden"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:124
msgctxt "@info:status"
msgid ""
"The selected material is incompatible with the selected machine or "
"configuration."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:125
#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24
msgctxt "@info:title"
msgid "Incompatible Material"
@@ -1103,14 +1171,14 @@ msgctxt "@action"
msgid "Undo changing the material diameter."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:144
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid ""
"Failed to export profile to {0}: {1}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:158
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid ""
@@ -1118,21 +1186,20 @@ msgid ""
"failure."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:156
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:163
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Exported profile to {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:157
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:164
msgctxt "@info:title"
msgid "Export succeeded"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:183
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:205
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:214
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:248
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:211
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:271
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid ""
@@ -1140,35 +1207,62 @@ msgid ""
"message>"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:216
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:252
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:230
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid ""
+"This profile {0} contains incorrect data, could not "
+"import it."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid ""
+"The machine defined in profile {0} doesn't match with "
+"your current machine, could not import it."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:255
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:277
+#, python-brace-format
+msgctxt "@info:status"
+msgid "File {0} does not contain any valid profile."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:298
msgctxt "@label"
msgid "Custom profile"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:285
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:349
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr ""
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:46
+#, python-brace-format
+msgctxt "@label"
+msgid "Group #{group_nr}"
+msgstr ""
+
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
msgctxt "@info:status"
msgid ""
@@ -1181,126 +1275,151 @@ msgctxt "@info:title"
msgid "Build Volume"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:34
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:25
msgctxt "@info:status"
msgid "Multiplying and placing objects"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:35
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:26
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
msgctxt "@info:title"
msgid "Placing Object"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:80
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:88
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:152
+msgctxt "@info:status"
+msgid "Unable to find a location within the build volume for all objects"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:29
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:64
+msgctxt "@info:status"
+msgid "Finding new location for objects"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:33
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:68
+msgctxt "@info:title"
+msgid "Finding Location"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:89
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:153
+msgctxt "@info:title"
+msgid "Can't Find Location"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:81
msgctxt "@title:window"
msgid "Crash Report"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:93
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:94
msgctxt "@label crash message"
msgid ""
-"
A fatal exception has occurred. Please send us this Crash Report to "
-"fix the problem
\n"
+"
A fatal error has occurred. Please send us this Crash Report to fix "
+"the problem
\n"
"
Please use the \"Send report\" button to post a bug report "
"automatically to our servers
"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:141
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:147
msgctxt "@title:groupbox"
-msgid "Exception traceback"
-msgstr "Ausnahme-Rückverfolgung"
+msgid "Error traceback"
+msgstr "Fehler-Rückverfolgung"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:208
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:214
msgctxt "@title:groupbox"
msgid "Logs"
msgstr "Protokolle"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:231
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:237
msgctxt "@title:groupbox"
msgid "User description"
msgstr "Benutzerbeschreibung"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:246
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:252
msgctxt "@action:button"
msgid "Send report"
msgstr "Bericht senden"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:256
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:274
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Geräte werden geladen..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:661
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:660
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Die Szene wird eingerichtet..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:703
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:702
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Die Benutzeroberfläche wird geladen..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:874
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:899
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1348
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "Es kann nur jeweils ein G-Code gleichzeitig geladen werden. Wichtige {0} werden übersprungen."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1357
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1426
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "Wenn G-Code geladen wird, kann keine weitere Datei geöffnet werden. Wichtige {0} werden übersprungen."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1416
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1495
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "Das gewählte Modell war zu klein zum Laden."
@@ -1279,12 +1407,11 @@ msgstr "X (Breite)"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:288
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:300
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:391
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:413
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:840
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:394
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:424
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:849
msgctxt "@label"
msgid "mm"
msgstr "mm"
@@ -1374,68 +1501,67 @@ msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "Der Höhenunterschied zwischen der Düsenspitze und dem Brückensystem (X- und Y-Achsen). Wird verwendet, um Kollisionen zwischen vorherigen Drucken und der Brücke zu verhindern, wenn im Modus „Nacheinander“ gedruckt wird."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:254
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Anzahl Extruder"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:289
-msgctxt "@tooltip"
-msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
-msgstr "Der Nenndurchmesser des durch den Drucker unterstützten Filaments. Der exakte Durchmesser wird durch das Material und/oder das Profil überschrieben."
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:291
-msgctxt "@label"
-msgid "Material diameter"
-msgstr "Materialdurchmesser"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:299
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:390
-msgctxt "@label"
-msgid "Nozzle size"
-msgstr "Düsengröße"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:317
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:310
msgctxt "@label"
msgid "Start Gcode"
msgstr "G-Code starten"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:327
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:320
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very start."
msgstr "G-Code-Befehle, die zum Start ausgeführt werden sollen."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:336
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:329
msgctxt "@label"
msgid "End Gcode"
msgstr "G-Code beenden"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:346
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:339
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very end."
msgstr "G-Code-Befehle, die am Ende ausgeführt werden sollen."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:378
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:370
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Düseneinstellungen"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:382
+msgctxt "@label"
+msgid "Nozzle size"
+msgstr "Düsengröße"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:393
+msgctxt "@label"
+msgid "Compatible material diameter"
+msgstr "Kompatibler Materialdurchmesser"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:395
+msgctxt "@tooltip"
+msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
+msgstr "Der Nenndurchmesser des durch den Drucker unterstützten Filaments. Der exakte Durchmesser wird durch das Material und/oder das Profil überschrieben."
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:411
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "X-Versatz Düse"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:423
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Y-Versatz Düse"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:433
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:444
msgctxt "@label"
msgid "Extruder Start Gcode"
msgstr "G-Code Extruder-Start"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:462
msgctxt "@label"
msgid "Extruder End Gcode"
msgstr "G-Code Extruder-Ende"
@@ -1448,8 +1574,9 @@ msgstr "Änderungsprotokoll"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:107
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:55
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:445
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:357
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:306
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:456
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:492
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:80
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:123
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
@@ -1506,7 +1633,7 @@ msgstr "Unbekannter Fehlercode: %1"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:55
msgctxt "@title:window"
msgid "Connect to Networked Printer"
-msgstr "Anschluss an vernetzten Drucker"
+msgstr "Anschluss an Netzwerk Drucker"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:65
msgctxt "@label"
@@ -1514,7 +1641,10 @@ msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
"\n"
"Select your printer from the list below:"
-msgstr "Um über das Netzwerk direkt auf Ihrem Drucker zu drucken, stellen Sie bitte sicher, dass der Drucker mit dem Netzwerkkabel verbunden ist oder verbinden Sie Ihren Drucker mit Ihrem WLAN-Netzwerk. Wenn Sie Cura nicht mit Ihrem Drucker verbinden, können Sie dennoch ein USB-Laufwerk für die Übertragung von G-Code-Dateien auf Ihren Drucker verwenden.\n\nWählen Sie Ihren Drucker aus der folgenden Liste:"
+msgstr ""
+"Um über das Netzwerk direkt auf Ihrem Drucker zu drucken, stellen Sie bitte sicher, dass der Drucker mit dem Netzwerkkabel verbunden ist oder verbinden Sie Ihren Drucker mit Ihrem WLAN-Netzwerk. Wenn Sie Cura nicht mit Ihrem Drucker verbinden, können Sie dennoch ein USB-Laufwerk für die Übertragung von G-Code-Dateien auf Ihren Drucker verwenden.\n"
+"\n"
+"Wählen Sie Ihren Drucker aus der folgenden Liste:"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:75
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:44
@@ -1530,7 +1660,7 @@ msgstr "Bearbeiten"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:50
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:95
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:171
msgctxt "@action:button"
msgid "Remove"
msgstr "Entfernen"
@@ -1552,12 +1682,12 @@ msgid "Type"
msgstr "Typ"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:233
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3"
msgstr "Ultimaker 3"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:236
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3 Extended"
msgstr "Ultimaker 3 Extended"
@@ -1603,8 +1733,6 @@ msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Geben Sie die IP-Adresse oder den Hostnamen Ihres Druckers auf dem Netzwerk ein."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:379
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:92
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:88
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
@@ -1625,6 +1753,11 @@ msgctxt "@label: arg 1 is group name"
msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
msgstr "%1 ist nicht für das Hosten einer Gruppe verbundener Ultimaker 3-Drucker eingerichtet"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
+msgctxt "@label link to connect manager"
+msgid "Add/Remove printers"
+msgstr "Drucker hinzufügen/entfernen"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
msgctxt "@info:tooltip"
msgid "Opens the print jobs page with your default web browser."
@@ -1655,11 +1788,16 @@ msgid "Available"
msgstr "Verfügbar"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:100
msgctxt "@label:MonitorStatus"
msgid "Lost connection with the printer"
msgstr "Verbindung zum Drucker wurde unterbrochen"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
+msgctxt "@label Printer status"
+msgid "Unknown"
+msgstr "Unbekannt"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:257
msgctxt "@label:status"
msgid "Disabled"
@@ -1751,138 +1889,252 @@ msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Konfiguration aktivieren"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:20
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:21
msgctxt "@title:window"
-msgid "Cura SolidWorks Plugin Configuration"
-msgstr "Cura SolidWorks Plugin-Konfiguration"
+msgid "SolidWorks: Export wizard"
+msgstr "SolidWorks: Exportassistent"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:44
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:45
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:140
msgctxt "@action:label"
-msgid "Default quality of the exported STL:"
-msgstr "Standardqualität des exportierten STL:"
+msgid "Quality:"
+msgstr "Qualität:"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:78
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:179
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always ask"
-msgstr "Immer nachfragen"
+msgid "Fine (3D-printing)"
+msgstr "Fein (3D-Druck)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:180
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Fine quality"
-msgstr "Immer Qualität „Fein“ verwenden"
+msgid "Coarse (3D-printing)"
+msgstr "Grob (3D-Druck)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:181
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Coarse quality"
-msgstr "Immer Qualität „Grob“ verwenden"
+msgid "Fine (SolidWorks)"
+msgstr "Fein (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:20
-msgctxt "@title:window"
-msgid "Import SolidWorks File as STL..."
-msgstr "SolidWorks-Datei importieren als STL ..."
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:43
-msgctxt "@info:tooltip"
-msgid "Quality of the Exported STL"
-msgstr "Qualität des exportierten STL"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:48
-msgctxt "@action:label"
-msgid "Quality"
-msgstr "Qualität"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:62
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:182
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Coarse"
-msgstr "Grob"
+msgid "Coarse (SolidWorks)"
+msgstr "Grob (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:63
-msgctxt "@option:curaSolidworksStlQuality"
-msgid "Fine"
-msgstr "Fein"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:78
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:82
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:94
msgctxt "@text:window"
-msgid "Remember my choice"
-msgstr "Meine Auswahl merken"
+msgid "Show this dialog again"
+msgstr "Diesen Dialog erneut anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:104
+msgctxt "@action:button"
+msgid "Continue"
+msgstr "Weiter"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:116
+msgctxt "@action:button"
+msgid "Abort"
+msgstr "Abbrechen"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:21
+msgctxt "@title:window"
+msgid "How to install Cura SolidWorks macro"
+msgstr "Installieren von Cura SolidWorks Makro"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:62
+msgctxt "@description:label"
+msgid "Steps:"
+msgstr "Schritte:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:140
+msgctxt "@action:button"
+msgid ""
+"Open the directory\n"
+"with macro and icon"
+msgstr ""
+"Verzeichnis\n"
+"mit Makro und Symbol öffnen"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:160
+msgctxt "@description:label"
+msgid "Instructions:"
+msgstr "Anweisungen:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:202
+msgctxt "@action:playpause"
+msgid "Play"
+msgstr "Wiedergeben"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:206
+msgctxt "@action:playpause"
+msgid "Pause"
+msgstr "Pausieren"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:268
+msgctxt "@action:button"
+msgid "Previous Step"
+msgstr "Vorheriger Schritt"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:283
+msgctxt "@action:button"
+msgid "Done"
+msgstr "Fertig"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:287
+msgctxt "@action:button"
+msgid "Next Step"
+msgstr "Nächster Schritt"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:21
+msgctxt "@title:window"
+msgid "SolidWorks plugin: Configuration"
+msgstr "SolidWorks Plugin: Konfiguration"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:39
+msgctxt "@title:tab"
+msgid "Conversion settings"
+msgstr "Konvertierungseinstellungen"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:66
+msgctxt "@label"
+msgid "First choice:"
+msgstr "Erste Wahl:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:86
+msgctxt "@text:menu"
+msgid "Latest installed version (Recommended)"
+msgstr "Aktuelle installierte Version (Empfohlen)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:95
+msgctxt "@text:menu"
+msgid "Default version"
+msgstr "Standardversion"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:193
+msgctxt "@label"
+msgid "Show wizard before opening SolidWorks files"
+msgstr "Assistent vor dem Öffnen von SolidWorks-Dateien anzeigen"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:203
+msgctxt "@label"
+msgid "Automatically rotate opened file into normed orientation"
+msgstr "Geöffnete Datei automatisch in normale Ausrichtung drehen"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:210
+msgctxt "@title:tab"
+msgid "Installation(s)"
+msgstr "Installation(en)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:284
+msgctxt "@label"
+msgid "COM service found"
+msgstr "COM-Service gefunden"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:295
+msgctxt "@label"
+msgid "Executable found"
+msgstr "Ausführbare Datei gefunden"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:306
+msgctxt "@label"
+msgid "COM starting"
+msgstr "COM wird gestartet"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:317
+msgctxt "@label"
+msgid "Revision number"
+msgstr "Revisionsnummer"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:328
+msgctxt "@label"
+msgid "Functions available"
+msgstr "Verfügbare Funktionen"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+msgctxt "@action:button"
+msgid "Save"
+msgstr "Speichern"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
msgctxt "@label"
msgid "Color scheme"
msgstr "Farbschema"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:96
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Materialfarbe"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Linientyp"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "Vorschub"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:108
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr "Schichtdicke"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:148
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Kompatibilitätsmodus"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:230
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
msgctxt "@label"
msgid "Show Travels"
msgstr "Bewegungen anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:236
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
msgctxt "@label"
msgid "Show Helpers"
msgstr "Helfer anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:242
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
msgctxt "@label"
msgid "Show Shell"
msgstr "Gehäuse anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:248
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
msgctxt "@label"
msgid "Show Infill"
msgstr "Füllung anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:297
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Nur obere Schichten anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:306
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "5 detaillierte Schichten oben anzeigen"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:317
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Oben/Unten"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:321
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
msgctxt "@label"
msgid "Inner Wall"
msgstr "Innenwand"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:378
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
msgctxt "@label"
msgid "min"
msgstr "min."
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:420
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
msgctxt "@label"
msgid "max"
msgstr "max."
@@ -1907,7 +2159,7 @@ msgctxt "@label"
msgid "Settings"
msgstr "Einstellungen"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:455
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:466
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Aktive Skripts Nachbearbeitung ändern"
@@ -1982,23 +2234,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Glättung"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:208
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+msgctxt "@label"
+msgid "Mesh Type"
+msgstr "Mesh-Typ"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+msgctxt "@label"
+msgid "Normal model"
+msgstr "Normales Modell"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+msgctxt "@label"
+msgid "Print as support"
+msgstr "Als Stützstruktur drucken"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+msgctxt "@label"
+msgid "Don't support overlap with other models"
+msgstr "Keine Überlappung mit anderen Modellen unterstützen"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+msgctxt "@label"
+msgid "Modify settings for overlap with other models"
+msgstr "Einstellungen für Überlappung mit anderen Modellen bearbeiten"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+msgctxt "@label"
+msgid "Modify settings for infill of other models"
+msgstr "Einstellungen für Füllung von anderen Modellen bearbeiten"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:333
msgctxt "@action:button"
msgid "Select settings"
msgstr "Einstellungen wählen"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:248
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:375
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Einstellungen für die benutzerdefinierte Anpassung dieses Modells wählen"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:272
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:402
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:91
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Filtern..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:296
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:426
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Alle anzeigen"
@@ -2166,7 +2448,10 @@ msgid ""
"This plugin contains a license.\n"
"You need to accept this license to install this plugin.\n"
"Do you agree with the terms below?"
-msgstr "Dieses Plugin enthält eine Lizenz.\nSie müssen diese Lizenz akzeptieren, um das Plugin zu installieren.\nStimmen Sie den nachfolgenden Bedingungen zu?"
+msgstr ""
+"Dieses Plugin enthält eine Lizenz.\n"
+"Sie müssen diese Lizenz akzeptieren, um das Plugin zu installieren.\n"
+"Stimmen Sie den nachfolgenden Bedingungen zu?"
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:242
msgctxt "@action:button"
@@ -2357,66 +2642,66 @@ msgctxt "@label"
msgid "Everything is in order! You're done with your CheckUp."
msgstr "Alles ist in Ordnung! Der Check-up ist abgeschlossen."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:88
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:87
msgctxt "@label:MonitorStatus"
msgid "Not connected to a printer"
msgstr "Nicht mit einem Drucker verbunden"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:90
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:89
msgctxt "@label:MonitorStatus"
msgid "Printer does not accept commands"
msgstr "Drucker nimmt keine Befehle an"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:96
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:95
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:194
msgctxt "@label:MonitorStatus"
msgid "In maintenance. Please check the printer"
msgstr "In Wartung. Den Drucker überprüfen"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:102
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:184
msgctxt "@label:MonitorStatus"
msgid "Printing..."
msgstr "Es wird gedruckt..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:105
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:186
msgctxt "@label:MonitorStatus"
msgid "Paused"
msgstr "Pausiert"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:109
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:108
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:188
msgctxt "@label:MonitorStatus"
msgid "Preparing..."
msgstr "Vorbereitung läuft..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:111
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:110
msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "Bitte den Ausdruck entfernen"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:237
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
msgctxt "@label:"
msgid "Resume"
msgstr "Zurückkehren"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:245
msgctxt "@label:"
msgid "Pause"
msgstr "Pausieren"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:274
msgctxt "@label:"
msgid "Abort Print"
msgstr "Drucken abbrechen"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:280
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
msgctxt "@window:title"
msgid "Abort print"
msgstr "Drucken abbrechen"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:282
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:286
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
msgstr "Soll das Drucken wirklich abgebrochen werden?"
@@ -2431,7 +2716,9 @@ msgctxt "@text:window"
msgid ""
"You have customized some profile settings.\n"
"Would you like to keep or discard those settings?"
-msgstr "Sie haben einige Profileinstellungen angepasst.\nMöchten Sie diese Einstellungen übernehmen oder verwerfen?"
+msgstr ""
+"Sie haben einige Profileinstellungen angepasst.\n"
+"Möchten Sie diese Einstellungen übernehmen oder verwerfen?"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:110
msgctxt "@title:column"
@@ -2449,19 +2736,19 @@ msgid "Customized"
msgstr "Angepasst"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:593
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Stets nachfragen"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:594
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:596
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Verwerfen und zukünftig nicht mehr nachfragen"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:597
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Übernehmen und zukünftig nicht mehr nachfragen"
@@ -2474,7 +2761,7 @@ msgstr "Verwerfen"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:209
msgctxt "@action:button"
msgid "Keep"
-msgstr "Übernehmen"
+msgstr "Beibehalten"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:222
msgctxt "@action:button"
@@ -2496,72 +2783,72 @@ msgctxt "@label"
msgid "Brand"
msgstr "Marke"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:88
msgctxt "@label"
msgid "Material Type"
msgstr "Materialtyp"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:97
msgctxt "@label"
msgid "Color"
msgstr "Farbe"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
msgctxt "@label"
msgid "Properties"
msgstr "Eigenschaften"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:143
msgctxt "@label"
msgid "Density"
msgstr "Dichte"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:156
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:158
msgctxt "@label"
msgid "Diameter"
msgstr "Durchmesser"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:187
msgctxt "@label"
msgid "Filament Cost"
msgstr "Filamentkosten"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
msgctxt "@label"
msgid "Filament weight"
msgstr "Filamentgewicht"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:220
msgctxt "@label"
msgid "Filament length"
msgstr "Filamentlänge"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:227
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:229
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Kosten pro Meter"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:243
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Dieses Material ist mit %1 verknüpft und teilt sich damit einige seiner Eigenschaften"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:250
msgctxt "@label"
msgid "Unlink Material"
msgstr "Material trennen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:259
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:261
msgctxt "@label"
msgid "Description"
msgstr "Beschreibung"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:272
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:274
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Haftungsinformationen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:300
msgctxt "@label"
msgid "Print settings"
msgstr "Druckeinstellungen"
@@ -2602,7 +2889,7 @@ msgid "Unit"
msgstr "Einheit"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:14
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:439
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:509
msgctxt "@title:tab"
msgid "General"
msgstr "Allgemein"
@@ -2617,230 +2904,255 @@ msgctxt "@label"
msgid "Language:"
msgstr "Sprache:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:207
msgctxt "@label"
msgid "Currency:"
msgstr "Währung:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:219
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Theme:"
msgstr "Thema:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:279
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:281
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Die Anwendung muss neu gestartet werden, um die Änderungen zu übernehmen."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:296
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:298
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Bei Änderung der Einstellungen automatisch schneiden."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:306
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Automatisch schneiden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:318
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:320
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Viewport-Verhalten"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:326
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:328
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Nicht gestützte Bereiche des Modells in rot hervorheben. Ohne Support werden diese Bereiche nicht korrekt gedruckt."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:337
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Überhang anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:342
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:344
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
msgstr "Bewegt die Kamera, bis sich das Modell im Mittelpunkt der Ansicht befindet, wenn ein Modell ausgewählt wurde"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:347
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:349
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Zentrieren Sie die Kamera, wenn das Element ausgewählt wurde"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:356
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:358
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "Soll das standardmäßige Zoom-Verhalten von Cura umgekehrt werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:363
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Kehren Sie die Richtung des Kamera-Zooms um."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:370
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:372
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "Soll das Zoomen in Richtung der Maus erfolgen?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:377
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "In Mausrichtung zoomen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "Sollen Modelle auf der Plattform so verschoben werden, dass sie sich nicht länger überschneiden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Stellen Sie sicher, dass die Modelle getrennt gehalten werden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:397
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "Sollen Modelle auf der Plattform so nach unten verschoben werden, dass sie die Druckplatte berühren?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:402
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Setzt Modelle automatisch auf der Druckplatte ab"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:414
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:416
msgctxt "@info:tooltip"
msgid "Show caution message in gcode reader."
msgstr "Warnmeldung im G-Code-Reader anzeigen."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:423
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:425
msgctxt "@option:check"
msgid "Caution message in gcode reader"
msgstr "Warnmeldung in G-Code-Reader"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "Soll die Schicht in den Kompatibilitätsmodus gezwungen werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:435
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:437
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Schichtenansicht Kompatibilitätsmodus erzwingen (Neustart erforderlich)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:451
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:453
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Dateien öffnen und speichern"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:457
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:459
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "Sollen Modelle an das Erstellungsvolumen angepasst werden, wenn sie zu groß sind?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:462
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:464
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Große Modelle anpassen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:471
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:473
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Ein Modell kann extrem klein erscheinen, wenn seine Maßeinheit z. B. in Metern anstelle von Millimetern angegeben ist. Sollen diese Modelle hoch skaliert werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:476
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:478
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Extrem kleine Modelle skalieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:485
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:487
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "Soll ein Präfix anhand des Druckernamens automatisch zum Namen des Druckauftrags hinzugefügt werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Geräte-Präfix zu Auftragsnamen hinzufügen."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:501
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Soll beim Speichern einer Projektdatei eine Zusammenfassung angezeigt werden?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Dialog Zusammenfassung beim Speichern eines Projekts anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:514
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Standardverhalten beim Öffnen einer Projektdatei"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Standardverhalten beim Öffnen einer Projektdatei: "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:533
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@option:openProject"
msgid "Always ask"
msgstr "Immer nachfragen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:536
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Immer als Projekt öffnen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Modelle immer importieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Wenn Sie Änderungen für ein Profil vorgenommen haben und zu einem anderen Profil gewechselt sind, wird ein Dialog angezeigt, der hinterfragt, ob Sie Ihre Änderungen beibehalten möchten oder nicht; optional können Sie ein Standardverhalten wählen, sodass dieser Dialog nicht erneut angezeigt wird."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:582
msgctxt "@label"
msgid "Override Profile"
msgstr "Profil überschreiben"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:629
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:631
msgctxt "@label"
msgid "Privacy"
msgstr "Privatsphäre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Soll Cura bei Programmstart nach Updates suchen?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:641
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:643
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Bei Start nach Updates suchen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:651
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:653
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "Sollen anonyme Daten über Ihren Druck an Ultimaker gesendet werden? Beachten Sie, dass keine Modelle, IP-Adressen oder andere personenbezogene Daten gesendet oder gespeichert werden."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:656
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:658
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "(Anonyme) Druckinformationen senden"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:674
+msgctxt "@label"
+msgid "Experimental"
+msgstr "Experimentell"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:680
+msgctxt "@info:tooltip"
+msgid "Use multi build plate functionality"
+msgstr "Mehrfach-Druckplattenfunktion verwenden"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:685
+msgctxt "@option:check"
+msgid "Use multi build plate functionality (restart required)"
+msgstr "Mehrfach-Druckplattenfunktion verwenden (Neustart erforderlich)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+msgctxt "@info:tooltip"
+msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+msgstr "Sollen neu geladene Modelle auf der Druckplatte angeordnet werden? In Verbindung mit Mehrfach-Druckplatte verwenden (EXPERIMENTELL)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+msgctxt "@option:check"
+msgid "Do not arrange objects on load"
+msgstr "Keine Objekte beim Laden anordnen"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:514
msgctxt "@title:tab"
msgid "Printers"
msgstr "Drucker"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:37
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:51
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:138
msgctxt "@action:button"
msgid "Activate"
msgstr "Aktivieren"
@@ -2883,7 +3195,7 @@ msgid "Waiting for a printjob"
msgstr "Warten auf einen Druckauftrag"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:448
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:518
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Profile"
@@ -2909,13 +3221,13 @@ msgid "Duplicate"
msgstr "Duplizieren"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:113
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:182
msgctxt "@action:button"
msgid "Import"
msgstr "Import"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:119
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:212
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:193
msgctxt "@action:button"
msgid "Export"
msgstr "Export"
@@ -2981,7 +3293,7 @@ msgid "Export Profile"
msgstr "Profil exportieren"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:516
msgctxt "@title:tab"
msgid "Materials"
msgstr "Materialien"
@@ -2996,60 +3308,60 @@ msgctxt "@action:label %1 is printer name"
msgid "Printer: %1"
msgstr "Drucker: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:149
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:150
msgctxt "@action:button"
msgid "Create"
msgstr "Erstellen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:160
msgctxt "@action:button"
msgid "Duplicate"
msgstr "Duplizieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:306
msgctxt "@title:window"
msgid "Import Material"
msgstr "Material importieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:320
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:307
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Could not import material %1: %2"
msgstr "Material konnte nicht importiert werden %1: %2"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully imported material %1"
msgstr "Material wurde erfolgreich importiert %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:343
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:329
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:344
msgctxt "@title:window"
msgid "Export Material"
msgstr "Material exportieren"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:348
msgctxt "@info:status Don't translate the XML tags and !"
msgid "Failed to export material to %1: %2"
msgstr "Exportieren des Materials nach %1: %2 schlug fehl"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:368
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:354
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully exported material to %1"
msgstr "Material erfolgreich nach %1 exportiert"
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:793
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:869
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Drucker hinzufügen"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
msgctxt "@label"
msgid "Printer Name:"
msgstr "Druckername:"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Drucker hinzufügen"
@@ -3074,7 +3386,9 @@ msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
"Cura proudly uses the following open source projects:"
-msgstr "Cura wurde von Ultimaker B.V. in Zusammenarbeit mit der Community entwickelt.\nCura verwendet mit Stolz die folgenden Open Source-Projekte:"
+msgstr ""
+"Cura wurde von Ultimaker B.V. in Zusammenarbeit mit der Community entwickelt.\n"
+"Cura verwendet mit Stolz die folgenden Open Source-Projekte:"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
msgctxt "@label"
@@ -3171,163 +3485,177 @@ msgctxt "@label"
msgid "SVG icons"
msgstr "SVG-Symbole"
+#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:139
+msgctxt "@label"
+msgid "Linux cross-distribution application deployment"
+msgstr "Distributionsunabhängiges Format für Linux-Anwendungen"
+
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:41
msgctxt "@label"
msgid "Profile:"
msgstr "Profil:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:66
-msgctxt "@"
-msgid "No Profile Available"
-msgstr "Kein Profil verfügbar"
-
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:102
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
"\n"
"Click to open the profile manager."
-msgstr "Einige Einstellungs-/Überschreibungswerte unterscheiden sich von den im Profil gespeicherten Werten.\n\nKlicken Sie, um den Profilmanager zu öffnen."
+msgstr ""
+"Einige Einstellungs-/Überschreibungswerte unterscheiden sich von den im Profil gespeicherten Werten.\n"
+"\n"
+"Klicken Sie, um den Profilmanager zu öffnen."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:150
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Suchen..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:482
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Werte für alle Extruder kopieren"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:497
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Diese Einstellung ausblenden"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:508
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:507
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "Diese Einstellung ausblenden"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:511
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Diese Einstellung weiterhin anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:530
msgctxt "@action:menu"
msgid "Configure setting visiblity..."
msgstr "Sichtbarkeit der Einstellung wird konfiguriert..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:250
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
"\n"
"Click to make these settings visible."
-msgstr "Einige ausgeblendete Einstellungen verwenden Werte, die von ihren normalen, berechneten Werten abweichen.\n\nKlicken Sie, um diese Einstellungen sichtbar zu machen."
+msgstr ""
+"Einige ausgeblendete Einstellungen verwenden Werte, die von ihren normalen, berechneten Werten abweichen.\n"
+"\n"
+"Klicken Sie, um diese Einstellungen sichtbar zu machen."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:62
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
msgctxt "@label Header for list of settings."
msgid "Affects"
msgstr "Hat Einfluss auf"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:67
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:66
msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Wird beeinflusst von"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:156
msgctxt "@label"
-msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
-msgstr "Diese Einstellung wird stets zwischen allen Extrudern geteilt. Eine Änderung ändert den Wert für alle Extruder"
+msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
+msgstr "Diese Einstellung wird stets zwischen allen Extrudern geteilt. Eine Änderung ändert den Wert für alle Extruder."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:160
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:159
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "Der Wert wird von Pro-Extruder-Werten gelöst "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:186
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:190
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
"\n"
"Click to restore the value of the profile."
-msgstr "Diese Einstellung hat einen vom Profil abweichenden Wert.\n\nKlicken Sie, um den Wert des Profils wiederherzustellen."
+msgstr ""
+"Diese Einstellung hat einen vom Profil abweichenden Wert.\n"
+"\n"
+"Klicken Sie, um den Wert des Profils wiederherzustellen."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:288
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
"\n"
"Click to restore the calculated value."
-msgstr "Diese Einstellung wird normalerweise berechnet; aktuell ist jedoch ein Absolutwert eingestellt.\n\nKlicken Sie, um den berechneten Wert wiederherzustellen."
+msgstr ""
+"Diese Einstellung wird normalerweise berechnet; aktuell ist jedoch ein Absolutwert eingestellt.\n"
+"\n"
+"Klicken Sie, um den berechneten Wert wiederherzustellen."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid "Print Setup"
msgstr "Druckeinrichtung"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid ""
"Print Setup disabled\n"
"G-code files cannot be modified"
-msgstr "Druckeinrichtung deaktiviert\nG-Code-Dateien können nicht geändert werden"
+msgstr ""
+"Druckeinrichtung deaktiviert\n"
+"G-Code-Dateien können nicht geändert werden"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:336
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:342
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00 Stunden 00 Minuten"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:354
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:359
msgctxt "@tooltip"
-msgid "Time specification
"
-msgstr "Zeitangabe
"
+msgid "Time specification"
+msgstr "Zeitangabe"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:429
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:441
msgctxt "@label"
msgid "Cost specification"
msgstr "Kostenangabe"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:434
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:455
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1 m"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:435
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:447
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:456
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1 g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:454
msgctxt "@label"
msgid "Total:"
msgstr "Insgesamt:"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:504
msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
msgid "%1m / ~ %2g / ~ %4 %3"
msgstr "%1m / ~ %2g / ~ %4 %3"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:509
msgctxt "@label Print estimates: m for meters, g for grams"
msgid "%1m / ~ %2g"
msgstr "%1m / ~ %2g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Empfohlene Druckeinrichtung
Drucken mit den empfohlenen Einstellungen für den gewählten Drucker, das gewählte Material und die gewählte Qualität."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:596
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Benutzerdefinierte Druckeinrichtung
Druck mit Feineinstellung über jedem einzelnen Bereich des Schneidvorgangs."
-#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:49
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:50
msgctxt "@title:menuitem %1 is the automatically selected material"
msgid "Automatic: %1"
msgstr "Automatisch: %1"
@@ -3337,6 +3665,16 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Ansicht"
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:37
+msgctxt "@action:inmenu menubar:view"
+msgid "&Camera position"
+msgstr "&Kameraposition"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:52
+msgctxt "@action:inmenu menubar:view"
+msgid "&Build plate"
+msgstr "&Druckplatte"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:40
msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer"
msgid "Automatic: %1"
@@ -3349,14 +3687,14 @@ msgid_plural "Print Selected Models With:"
msgstr[0] "Ausgewähltes Modell drucken mit:"
msgstr[1] "Ausgewählte Modelle drucken mit:"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:83
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:114
msgctxt "@title:window"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Ausgewähltes Modell multiplizieren"
msgstr[1] "Ausgewählte Modelle multiplizieren"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:108
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:139
msgctxt "@label"
msgid "Number of Copies"
msgstr "Anzahl Kopien"
@@ -3372,7 +3710,7 @@ msgid "No printer connected"
msgstr "Es ist kein Drucker verbunden"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:90
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:139
msgctxt "@label"
msgid "Extruder"
msgstr "Extruder"
@@ -3482,254 +3820,294 @@ msgctxt "@label"
msgid "Estimated time left"
msgstr "Geschätzte verbleibende Zeit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
msgid "Toggle Fu&ll Screen"
msgstr "Umschalten auf Vo&llbild-Modus"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "&Rückgängig machen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:89
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "&Wiederholen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:99
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "&Beenden"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:107
-msgctxt "@action:inmenu menubar:view"
-msgid "&Reset camera position"
-msgstr "&Kameraposition zurücksetzen"
-
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
+msgctxt "@action:inmenu menubar:view"
+msgid "&3D View"
+msgstr "&3D-Ansicht"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+msgctxt "@action:inmenu menubar:view"
+msgid "&Front View"
+msgstr "&Vorderansicht"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
+msgctxt "@action:inmenu menubar:view"
+msgid "&Top View"
+msgstr "&Draufsicht"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
+msgctxt "@action:inmenu menubar:view"
+msgid "&Left Side View"
+msgstr "&Ansicht von links"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+msgctxt "@action:inmenu menubar:view"
+msgid "&Right Side View"
+msgstr "&Ansicht von rechts"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Cura konfigurieren..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "&Drucker hinzufügen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "Dr&ucker verwalten..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Materialien werden verwaltet..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Profil mit aktuellen Einstellungen/Überschreibungen aktualisieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:150
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Aktuelle Änderungen verwerfen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Profil von aktuellen Einstellungen/Überschreibungen erstellen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Profile verwalten..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Online-&Dokumentation anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "&Fehler melden"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:191
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
msgid "&About..."
msgstr "&Über..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
msgid "Delete &Selected Model"
msgid_plural "Delete &Selected Models"
msgstr[0] "&Ausgewähltes Modell löschen"
msgstr[1] "&Ausgewählte Modelle löschen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Ausgewähltes Modell zentrieren"
msgstr[1] "Ausgewählte Modelle zentrieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Ausgewähltes Modell multiplizieren"
msgstr[1] "Ausgewählte Modelle multiplizieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Modell löschen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "Modell auf Druckplatte ze&ntrieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "Modelle &gruppieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:250
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Gruppierung für Modelle aufheben"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:260
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "Modelle &zusammenführen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "Modell &multiplizieren"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
msgid "&Select All Models"
msgstr "Alle Modelle &wählen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:287
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
msgid "&Clear Build Plate"
msgstr "Druckplatte &reinigen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:297
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
msgctxt "@action:inmenu menubar:file"
msgid "Re&load All Models"
msgstr "Alle Modelle neu &laden"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:306
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
+msgctxt "@action:inmenu menubar:edit"
+msgid "Arrange All Models To All Build Plates"
+msgstr "Alle Modelle an allen Druckplatten anordnen"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Alle Modelle anordnen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Anordnung auswählen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:321
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Alle Modellpositionen zurücksetzen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:328
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model &Transformations"
msgstr "Alle Modell&transformationen zurücksetzen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Datei(en) öffnen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:343
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Neues Projekt..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "Engine-&Protokoll anzeigen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Konfigurationsordner anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:365
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Sichtbarkeit einstellen wird konfiguriert..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse plugins..."
msgstr "Plugins durchsuchen..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:379
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:menu"
msgid "Installed plugins..."
msgstr "Installierte plugins..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:28
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:438
+msgctxt "@action:inmenu menubar:view"
+msgid "Expand/Collapse Sidebar"
+msgstr "Seitenleiste vergrößern/verkleinern"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Bitte laden Sie ein 3D-Modell"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:34
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Bereit zum Slicen (Schneiden)"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Das Slicing läuft..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Bereit zum %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
msgstr "Slicing nicht möglich"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "Slicing ist nicht verfügbar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Slice current printjob"
+msgstr "Aktuellen Druckauftrag slicen"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Cancel slicing process"
+msgstr "Slicing-Vorgang abbrechen"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Vorbereiten"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Abbrechen"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:302
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Wählen Sie das aktive Ausgabegerät"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:696
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Datei(en) öffnen"
@@ -3749,114 +4127,114 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:81
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&Datei"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:98
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
msgctxt "@action:inmenu menubar:file"
msgid "&Save Selection to File"
msgstr "Auswahl als Datei &speichern"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
msgctxt "@title:menu menubar:file"
msgid "Save &As..."
msgstr "Speichern &Als"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
msgctxt "@title:menu menubar:file"
-msgid "Save project"
-msgstr "Projekt speichern"
+msgid "Save &Project..."
+msgstr "&Projekt speichern..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Bearbeiten"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:158
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
msgctxt "@title:menu"
msgid "&View"
msgstr "&Ansicht"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
msgctxt "@title:menu"
msgid "&Settings"
msgstr "&Einstellungen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
msgctxt "@title:menu menubar:toplevel"
msgid "&Printer"
msgstr "Dr&ucker"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:175
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:187
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:208
msgctxt "@title:menu"
msgid "&Material"
msgstr "&Material"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:176
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:197
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:209
msgctxt "@title:menu"
msgid "&Profile"
msgstr "&Profil"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:180
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:201
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Als aktiven Extruder festlegen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:219
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "Er&weiterungen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:232
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:253
msgctxt "@title:menu menubar:toplevel"
msgid "P&lugins"
msgstr "&Plugins"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:261
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "E&instellungen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:269
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "&Hilfe"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:330
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:351
msgctxt "@action:button"
msgid "Open File"
msgstr "Datei öffnen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:442
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:512
msgctxt "@title:tab"
msgid "Settings"
msgstr "Einstellungen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:478
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:554
msgctxt "@title:window"
msgid "New project"
msgstr "Neues Projekt"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:479
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:555
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "Möchten Sie wirklich ein neues Projekt beginnen? Damit werden das Druckbett und alle nicht gespeicherten Einstellungen gelöscht."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:721
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:797
msgctxt "@window:title"
msgid "Install Plugin"
msgstr "Plugin installieren"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:728
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:804
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Datei(en) öffnen"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:731
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:807
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Es wurden eine oder mehrere G-Code-Datei(en) innerhalb der von Ihnen gewählten Dateien gefunden. Sie können nur eine G-Code-Datei auf einmal öffnen. Wenn Sie eine G-Code-Datei öffnen möchten wählen Sie bitte nur eine Datei."
@@ -3881,97 +4259,82 @@ msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "Projektzusammenfassung beim Speichern nicht erneut anzeigen"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
-msgctxt "@action:button"
-msgid "Save"
-msgstr "Speichern"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:74
-msgctxt "@title:tab"
-msgid "Prepare"
-msgstr "Vorbereiten"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:100
-msgctxt "@title:tab"
-msgid "Monitor"
-msgstr "Überwachen"
-
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
msgctxt "@label"
msgid "Layer Height"
msgstr "Schichtdicke"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:323
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:345
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr "Ein benutzerdefiniertes Profil ist derzeit aktiv. Wählen Sie ein voreingestelltes Qualitätsprofil aus der Registerkarte „Benutzerdefiniert“, um den Schieberegler für Qualität zu aktivieren."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:362
msgctxt "@label"
msgid "Print Speed"
msgstr "Druckgeschwindigkeit"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:374
msgctxt "@label"
msgid "Slower"
msgstr "Langsamer"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:385
msgctxt "@label"
msgid "Faster"
msgstr "Schneller"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:388
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:423
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "Sie haben einige Profileinstellungen geändert. Wenn Sie diese ändern möchten, wechseln Sie in den Modus „Benutzerdefiniert“."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:413
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:446
msgctxt "@label"
msgid "Infill"
msgstr "Füllung"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:633
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:668
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Die graduelle Füllung steigert die Menge der Füllung nach oben hin schrittweise."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:645
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:680
msgctxt "@label"
msgid "Enable gradual"
msgstr "Graduell aktivieren"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:712
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:747
msgctxt "@label"
msgid "Generate Support"
msgstr "Stützstruktur generieren"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:746
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:781
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Damit werden Strukturen zur Unterstützung von Modellteilen mit Überhängen generiert. Ohne diese Strukturen würden solche Teile während des Druckvorgangs zusammenfallen."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:764
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:799
msgctxt "@label"
msgid "Support Extruder"
msgstr "Extruder für Stützstruktur"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:816
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:851
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Wählen Sie, welcher Extruder für die Unterstützung verwendet wird. Dient zum Konstruieren von Stützstrukturen unter dem Modell, damit dieses nicht absinkt oder frei schwebend gedruckt wird."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:839
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:874
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Druckplattenhaftung"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:894
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:929
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "Drucken eines Brim- oder Raft-Elements aktivieren. Es wird ein flacher Bereich rund um oder unter Ihrem Objekt hinzugefügt, das im Anschluss leicht abgeschnitten werden kann. "
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:934
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:969
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "Sie benötigen Hilfe für Ihre Drucke? Lesen Sie die Ultimaker Anleitungen für Fehlerbehebung>"
@@ -3988,17 +4351,22 @@ msgctxt "@title:window"
msgid "Open project file"
msgstr "Projektdatei öffnen"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:93
msgctxt "@text:window"
msgid "This is a Cura project file. Would you like to open it as a project or import the models from it?"
msgstr "Dies ist eine Cura-Projektdatei. Möchten Sie diese als Projekt öffnen oder die Modelle hieraus importieren?"
#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:103
+msgctxt "@text:window"
+msgid "Remember my choice"
+msgstr "Meine Auswahl merken"
+
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
msgctxt "@action:button"
msgid "Open as project"
msgstr "Als Projekt öffnen"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:131
msgctxt "@action:button"
msgid "Import models"
msgstr "Modelle importieren"
@@ -4008,21 +4376,36 @@ msgctxt "@title:window"
msgid "Engine Log"
msgstr "Engine-Protokoll"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:245
msgctxt "@label"
msgid "Material"
msgstr "Material"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:349
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:352
msgctxt "@label"
-msgid "Check compatibility"
-msgstr "Kompatibilität prüfen"
+msgid "Check compatibility"
+msgstr "Kompatibilität prüfen"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:369
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Klicken Sie, um die Materialkompatibilität auf Ultimaker.com zu prüfen."
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:211
+msgctxt "@option:check"
+msgid "See only current build plate"
+msgstr "Nur aktuelle Druckplatte anzeigen"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:227
+msgctxt "@action:button"
+msgid "Arrange to all build plates"
+msgstr "An allen Druckplatten ausrichten"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:247
+msgctxt "@action:button"
+msgid "Arrange current build plate"
+msgstr "An aktueller Druckplatte ausrichten"
+
#: MachineSettingsAction/plugin.json
msgctxt "description"
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc)"
@@ -4113,6 +4496,26 @@ msgctxt "name"
msgid "USB printing"
msgstr "USB-Drucken"
+#: PrepareStage/plugin.json
+msgctxt "description"
+msgid "Provides a prepare stage in Cura."
+msgstr "Bietet eine Vorbereitungsstufe in Cura."
+
+#: PrepareStage/plugin.json
+msgctxt "name"
+msgid "Prepare Stage"
+msgstr "Vorbereitungsstufe"
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "description"
+msgid "Provides an edit window for direct script editing."
+msgstr "Bietet ein Bearbeitungsfenster für direkte Skriptbearbeitung."
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "name"
+msgid "Live scripting tool"
+msgstr "Live-Scripting-Tool"
+
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4133,6 +4536,16 @@ msgctxt "name"
msgid "UM3 Network Connection"
msgstr "UM3-Netzwerkverbindung"
+#: MonitorStage/plugin.json
+msgctxt "description"
+msgid "Provides a monitor stage in Cura."
+msgstr "Bietet eine Überwachungsstufe in Cura."
+
+#: MonitorStage/plugin.json
+msgctxt "name"
+msgid "Monitor Stage"
+msgstr "Überwachungsstufe"
+
#: FirmwareUpdateChecker/plugin.json
msgctxt "description"
msgid "Checks for firmware updates."
@@ -4145,8 +4558,8 @@ msgstr "Firmware-Update-Prüfer"
#: CuraSolidWorksPlugin/plugin.json
msgctxt "description"
-msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
-msgstr "Bietet Ihnen die Möglichkeit, bestimmte Dateien über SolidWorks selbst zu öffnen. Diese werden anschließend konvertiert und in Cura geladen."
+msgid "Gives you the possibility to open certain files using SolidWorks itself. Conversion is done by this plugin and additional optimizations."
+msgstr "Bietet Ihnen die Möglichkeit, bestimmte Dateien über SolidWorks selbst zu öffnen. Die Konvertierung erfolgt über dieses Plugin und zusätzliche Optimierungen."
#: CuraSolidWorksPlugin/plugin.json
msgctxt "name"
@@ -4213,6 +4626,16 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Cura-Vorgängerprofil-Reader"
+#: CuraBlenderPlugin/plugin.json
+msgctxt "description"
+msgid "Helps to open Blender files directly in Cura."
+msgstr "Unterstützt das Öffnen der Blender-Dateien direkt in Cura."
+
+#: CuraBlenderPlugin/plugin.json
+msgctxt "name"
+msgid "Blender Integration (experimental)"
+msgstr "Blender-Integration (experimentell)"
+
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4373,6 +4796,16 @@ msgctxt "name"
msgid "Cura Profile Writer"
msgstr "Cura-Profil-Writer"
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "description"
+msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+msgstr "Ermöglichen Sie Materialherstellern die Erstellung neuer Material- und Qualitätsprofile, indem Sie eine Drop-In-Benutzerschnittstelle verwenden."
+
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "name"
+msgid "Print Profile Assistant"
+msgstr "Druckprofil-Assistent"
+
#: 3MFWriter/plugin.json
msgctxt "description"
msgid "Provides support for writing 3MF files."
@@ -4413,6 +4846,156 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Cura-Profil-Reader"
+#~ msgctxt "@label"
+#~ msgid "Unknown"
+#~ msgstr "Unbekannt"
+
+#~ msgctxt "@info:status"
+#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
+#~ msgstr "Beim Öffnen Ihrer SolidWorks Datei trat ein Fehler auf! Überprüfen Sie bitte, ob sich Ihre Datei in SolidWorks ohne Probleme öffnen lässt!"
+
+#~ msgctxt "@info:status"
+#~ msgid "Error while starting %s!"
+#~ msgstr "Fehler beim Starten %s!"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Simulation view"
+#~ msgstr "Simulationsansicht"
+
+#~ msgctxt "@info"
+#~ msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
+#~ msgstr "Cura erfasst anonymisierte Slice-Informationen. Sie können dies in den Einstellungen deaktivieren."
+
+#~ msgctxt "@action:button"
+#~ msgid "Dismiss"
+#~ msgstr "Verwerfen"
+
+#~ msgctxt "@menuitem"
+#~ msgid "Global"
+#~ msgstr "Global"
+
+#~ msgctxt "@label crash message"
+#~ msgid ""
+#~ "
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+#~ "
Please use the \"Send report\" button to post a bug report automatically to our servers
\n"
+#~ " "
+#~ msgstr ""
+#~ "
Ein schwerer Ausnahmefehler ist aufgetreten. Senden Sie uns diesen Absturzbericht, um das Problem zu beheben
\n"
+#~ "
Verwenden Sie bitte die Schaltfläche „Bericht senden“, um den Fehlerbericht automatisch an unsere Server zu senden
"
+
+#~ msgctxt "@action:inmenu menubar:view"
+#~ msgid "&Reset camera position"
+#~ msgstr "&Kameraposition zurücksetzen"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save project"
+#~ msgstr "Projekt speichern"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Prepare"
+#~ msgstr "Vorbereiten"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Monitor"
+#~ msgstr "Überwachen"
+
+#~ msgctxt "@label"
+#~ msgid "Check compatibility"
+#~ msgstr "Kompatibilität prüfen"
+
+#~ msgctxt "description"
+#~ msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
+#~ msgstr "Bietet Ihnen die Möglichkeit, bestimmte Dateien über SolidWorks selbst zu öffnen. Diese werden anschließend konvertiert und in Cura geladen."
+
#~ msgctxt "@label:status"
#~ msgid "Blocked"
#~ msgstr "Blockiert"
@@ -4433,13 +5016,9 @@ msgstr "Cura-Profil-Reader"
#~ msgid "To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware regularly. This can be done on the {machine_name} (when connected to the network) or via USB."
#~ msgstr "Um sicherzustellen, dass Ihr {machine_name} mit den neuesten Funktionen ausgestattet ist, wird empfohlen, die Firmware regelmäßig zu aktualisieren Dies kann auf dem {machine_name} (bei Anschluss an ein Netzwerk) oder über USB erfolgen."
-msgctxt "@item:inlistbox"
-msgid "Layer view"
-msgstr "Schichtenansicht"
-
-msgctxt "@info:title"
-msgid "Layer View"
-msgstr "Schichtenansicht"
+#~ msgctxt "@info:title"
+#~ msgid "Layer View"
+#~ msgstr "Schichtenansicht"
#~ msgctxt "@menuitem"
#~ msgid "Browse plugins"
@@ -4541,9 +5120,9 @@ msgstr "Schichtenansicht"
#~ msgid "Provides the Layer view."
#~ msgstr "Bietet eine Schichtenansicht."
-msgctxt "name"
-msgid "Layer View"
-msgstr "Schichtenansicht"
+#~ msgctxt "name"
+#~ msgid "Layer View"
+#~ msgstr "Schichtenansicht"
#~ msgctxt "@item:inlistbox"
#~ msgid "X-Ray"
@@ -4864,9 +5443,9 @@ msgstr "Schichtenansicht"
#~ msgid "Provides support for importing profiles from g-code files."
#~ msgstr "Ermöglicht das Importieren von Profilen aus G-Code-Dateien."
-msgctxt "@label"
-msgid "Layer View"
-msgstr "Schichtenansicht"
+#~ msgctxt "@label"
+#~ msgid "Layer View"
+#~ msgstr "Schichtenansicht"
#~ msgctxt "@info:whatsthis"
#~ msgid "Provides the Layer view."
diff --git a/resources/i18n/de_DE/fdmextruder.def.json.po b/resources/i18n/de_DE/fdmextruder.def.json.po
index 1108f1a541..4c81c54c35 100644
--- a/resources/i18n/de_DE/fdmextruder.def.json.po
+++ b/resources/i18n/de_DE/fdmextruder.def.json.po
@@ -1,13 +1,13 @@
-# Cura JSON setting files
-# Copyright (C) 2017 Ultimaker
+# Cura
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: 2017-11-30 13:05+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: German\n"
diff --git a/resources/i18n/de_DE/fdmprinter.def.json.po b/resources/i18n/de_DE/fdmprinter.def.json.po
index 337df8bff2..95422bb589 100644
--- a/resources/i18n/de_DE/fdmprinter.def.json.po
+++ b/resources/i18n/de_DE/fdmprinter.def.json.po
@@ -1,13 +1,13 @@
-# Cura JSON setting files
-# Copyright (C) 2017 Ultimaker
+# Cura
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
"PO-Revision-Date: 2017-11-30 13:05+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: German\n"
@@ -345,6 +345,16 @@ msgctxt "machine_gcode_flavor option Repetier"
msgid "Repetier"
msgstr "Repetier"
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract label"
+msgid "Firmware Retraction"
+msgstr "Firmware-Einzug"
+
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract description"
+msgid "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material."
+msgstr "Definiert, ob Firmware-Einzugsbefehle (G10/G11) anstelle der E-Eigenschaft in G1-Befehlen verwendet wird, um das Material einzuziehen."
+
#: fdmprinter.def.json
msgctxt "machine_disallowed_areas label"
msgid "Disallowed areas"
@@ -605,31 +615,6 @@ msgctxt "layer_height_0 description"
msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier."
msgstr "Die Dicke der ersten Schicht in mm. Eine dicke erste Schicht erleichtert die Haftung am Druckbett."
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance label"
-msgid "Slicing Tolerance"
-msgstr "Slicing-Toleranz"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance description"
-msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
-msgstr "Slicen von Schichten mit diagonalen Flächen. Die Bereiche einer Schicht können anhand der Position generiert werden, an der die Mitte einer Schicht die Oberfläche kreuzt (Mitte). Optional kann jede Schicht die Bereiche enthalten, die in das Volumen entlang der Höhe der Schicht (Exklusiv) fallen oder eine Schicht enthält die Bereiche, die irgendwo innerhalb der Schicht positioniert sind (Inklusiv). Exklusiv bewahrt die meisten Details, Inklusiv ermöglicht die beste Passform und Mitte erfordert die kürzeste Bearbeitungszeit."
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option middle"
-msgid "Middle"
-msgstr "Mitte"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option exclusive"
-msgid "Exclusive"
-msgstr "Exklusiv"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option inclusive"
-msgid "Inclusive"
-msgstr "Inklusiv"
-
#: fdmprinter.def.json
msgctxt "line_width label"
msgid "Line Width"
@@ -670,16 +655,6 @@ msgctxt "wall_line_width_x description"
msgid "Width of a single wall line for all wall lines except the outermost one."
msgstr "Die Breite einer einzelnen Wandlinie für alle Wandlinien, außer der äußersten."
-#: fdmprinter.def.json
-msgctxt "roofing_line_width label"
-msgid "Top Surface Skin Line Width"
-msgstr "Oberfläche Außenhaut Linienbreite"
-
-#: fdmprinter.def.json
-msgctxt "roofing_line_width description"
-msgid "Width of a single line of the areas at the top of the print."
-msgstr "Die Breite einer einzelnen Linie der oberen Druckbereiche."
-
#: fdmprinter.def.json
msgctxt "skin_line_width label"
msgid "Top/Bottom Line Width"
@@ -860,41 +835,6 @@ msgctxt "roofing_layer_count description"
msgid "The number of top most skin layers. Usually only one top most layer is sufficient to generate higher quality top surfaces."
msgstr "Die Anzahl der obersten Außenhautschichten. Üblicherweise reicht eine einzige oberste Schicht aus, um höherwertige Oberflächen zu generieren."
-#: fdmprinter.def.json
-msgctxt "roofing_pattern label"
-msgid "Top Surface Skin Pattern"
-msgstr "Oberfläche Außenhaut Muster"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern description"
-msgid "The pattern of the top most layers."
-msgstr "Das Muster der obersten Schichten."
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option lines"
-msgid "Lines"
-msgstr "Linien"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option concentric"
-msgid "Concentric"
-msgstr "Konzentrisch"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option zigzag"
-msgid "Zig Zag"
-msgstr "Zickzack"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles label"
-msgid "Top Surface Skin Line Directions"
-msgstr "Linienrichtungen der Oberfläche Außenhaut"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles description"
-msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
-msgstr "Eine Liste von Ganzzahl-Linienrichtungen für den Fall, wenn die oberen Außenhautschichten die Linien- oder Zickzack-Muster verwenden. Elemente aus der Liste werden während des Aufbaus der Schichten sequentiell verwendet und wenn das Listenende erreicht wird, beginnt die Liste von vorne. Die Listenobjekte werden durch Kommas getrennt und die gesamte Liste ist in eckige Klammern gesetzt. Standardmäßig ist eine leere Liste vorhanden, was bedeutet, dass herkömmliche Standardwinkel (45- und 135-Grad) verwendet werden."
-
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr label"
msgid "Top/Bottom Extruder"
@@ -1025,6 +965,16 @@ msgctxt "wall_0_inset description"
msgid "Inset applied to the path of the outer wall. If the outer wall is smaller than the nozzle, and printed after the inner walls, use this offset to get the hole in the nozzle to overlap with the inner walls instead of the outside of the model."
msgstr "Verwendete Einfügung am Pfad zur Außenwand. Wenn die Außenwand kleiner als die Düse ist und nach den Innenwänden gedruckt wird, verwenden Sie diesen Versatz, damit die Öffnung in der Düse mit den Innenwänden überlappt, anstelle mit der Außenseite des Modells."
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order label"
+msgid "Optimize Wall Printing Order"
+msgstr "Reihenfolge des Wanddrucks optimieren"
+
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order description"
+msgid "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."
+msgstr "Optimieren Sie die Reihenfolge, in der die Wände gedruckt werden, um die Anzahl der Einzüge und die zurückgelegten Distanzen zu reduzieren. Dieser Schritt bringt für die meisten Teile Vorteile, allerdings werden einige möglicherweise länger benötigen. Vergleichen Sie deshalb bitte die Schätzung der Druckzeiten mit und ohne Optimierung."
+
#: fdmprinter.def.json
msgctxt "outer_inset_first label"
msgid "Outer Before Inner Walls"
@@ -1095,6 +1045,16 @@ msgctxt "fill_perimeter_gaps option everywhere"
msgid "Everywhere"
msgstr "Überall"
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps label"
+msgid "Filter Out Tiny Gaps"
+msgstr "Kleine Lücken ausfiltern"
+
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps description"
+msgid "Filter out tiny gaps to reduce blobs on outside of model."
+msgstr "Kleine Lücken ausfiltern, um Tropfen an der Außenseite des Modells zu reduzieren."
+
#: fdmprinter.def.json
msgctxt "fill_outline_gaps label"
msgid "Print Thin Walls"
@@ -1477,8 +1437,8 @@ msgstr "X-Versatz Füllung"
#: fdmprinter.def.json
msgctxt "infill_offset_x description"
-msgid "The infill pattern is offset this distance along the X axis."
-msgstr "Das Füllmuster wird um diese Distanz entlang der X-Achse versetzt."
+msgid "The infill pattern is moved this distance along the X axis."
+msgstr "Das Füllmuster wird um diese Distanz entlang der X-Achse verschoben."
#: fdmprinter.def.json
msgctxt "infill_offset_y label"
@@ -1487,8 +1447,8 @@ msgstr "Y-Versatz Füllung"
#: fdmprinter.def.json
msgctxt "infill_offset_y description"
-msgid "The infill pattern is offset this distance along the Y axis."
-msgstr "Das Füllmuster wird um diese Distanz entlang der Y-Achse versetzt."
+msgid "The infill pattern is moved this distance along the Y axis."
+msgstr "Das Füllmuster wird um diese Distanz entlang der Y-Achse verschoben."
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
@@ -1507,8 +1467,8 @@ msgstr "Prozentsatz Füllung überlappen"
#: fdmprinter.def.json
msgctxt "infill_overlap description"
-msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
-msgstr "Das Ausmaß des Überlappens zwischen der Füllung und den Wänden. Ein leichtes Überlappen ermöglicht es den Wänden, eine solide Verbindung mit der Füllung herzustellen."
+msgid "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill."
+msgstr "Das Ausmaß des Überlappens zwischen der Füllung und den Wänden als Prozentwert der Füllungslinienbreite. Ein leichtes Überlappen ermöglicht es den Wänden, eine solide Verbindung mit der Füllung herzustellen."
#: fdmprinter.def.json
msgctxt "infill_overlap_mm label"
@@ -1527,8 +1487,8 @@ msgstr "Prozentsatz Außenhaut überlappen"
#: fdmprinter.def.json
msgctxt "skin_overlap description"
-msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
-msgstr "Das Ausmaß des Überlappens zwischen der Außenhaut und den Wänden als Prozentwert der Linienbreite. Ein leichtes Überlappen ermöglicht es den Wänden, eine solide Verbindung mit der Außenhaut herzustellen. Dies ist ein Prozentwert der durchschnittlichen Linienbreiten der Außenhautlinien und der innersten Wand."
+msgid "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+msgstr "Das Ausmaß des Überlappens zwischen der Außenhaut und den Wänden als Prozentwert der Außenhaut-Linienbreite. Ein leichtes Überlappen ermöglicht es den Wänden, eine solide Verbindung mit der Außenhaut herzustellen. Dies ist ein Prozentwert der durchschnittlichen Linienbreiten der Außenhautlinien und der innersten Wand."
#: fdmprinter.def.json
msgctxt "skin_overlap_mm label"
@@ -1690,16 +1650,6 @@ msgctxt "material description"
msgid "Material"
msgstr "Material"
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature label"
-msgid "Auto Temperature"
-msgstr "Automatische Temperatur"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature description"
-msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
-msgstr "Die Temperatur wird für jede Schicht automatisch anhand der durchschnittlichen Fließgeschwindigkeit dieser Schicht geändert."
-
#: fdmprinter.def.json
msgctxt "default_material_print_temperature label"
msgid "Default Printing Temperature"
@@ -1750,16 +1700,6 @@ msgctxt "material_final_print_temperature description"
msgid "The temperature to which to already start cooling down just before the end of printing."
msgstr "Die Temperatur, bei der das Abkühlen bereits beginnen kann, bevor der Druck beendet wird."
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph label"
-msgid "Flow Temperature Graph"
-msgstr "Fließtemperaturgraf"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph description"
-msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
-msgstr "Der Materialfluss (in mm3 pro Sekunde) in Bezug zur Temperatur (Grad Celsius)."
-
#: fdmprinter.def.json
msgctxt "material_extrusion_cool_down_speed label"
msgid "Extrusion Cool Down Speed Modifier"
@@ -1777,8 +1717,8 @@ msgstr "Temperatur Druckplatte"
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
-msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
-msgstr "Die Temperatur, die für die erhitzte Druckplatte verwendet wird. Wenn dieser Wert 0 beträgt, wird das Bett für diesen Druck nicht erhitzt."
+msgid "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted."
+msgstr "Die Temperatur, die für die erhitzte Druckplatte verwendet wird. Wenn dieser Wert 0 beträgt, wird die Betttemperatur nicht angepasst."
#: fdmprinter.def.json
msgctxt "material_bed_temperature_layer_0 label"
@@ -3450,6 +3390,16 @@ msgctxt "support_tower_roof_angle description"
msgid "The angle of a rooftop of a tower. A higher value results in pointed tower roofs, a lower value results in flattened tower roofs."
msgstr "Der Winkel eines Pfeilerdachs. Ein höherer Wert hat spitze Pfeilerdächer zur Folge, ein niedrigerer Wert führt zu flacheren Pfeilerdächern."
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down label"
+msgid "Drop Down Support Mesh"
+msgstr "Stütznetz ablegen"
+
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down description"
+msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
+msgstr "Sorgt für Unterstützung überall unterhalb des Stütznetzes, sodass kein Überhang im Stütznetz vorhanden ist."
+
#: fdmprinter.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
@@ -4082,16 +4032,6 @@ msgctxt "meshfix_keep_open_polygons description"
msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode."
msgstr "Normalerweise versucht Cura kleine Löcher im Netz abzudecken und Teile von Schichten, die große Löcher aufweisen, zu entfernen. Die Aktivierung dieser Option erhält jene Teile, die nicht abgedeckt werden können. Diese Option sollte nur als letzter Ausweg verwendet werden, wenn es ansonsten nicht möglich ist, einen korrekten G-Code zu berechnen."
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution label"
-msgid "Maximum Resolution"
-msgstr "Maximale Auflösung"
-
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution description"
-msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
-msgstr "Die Mindestgröße eines Linienabschnitts nach dem Slicen. Wenn Sie diesen Wert erhöhen, führt dies zu einer niedrigeren Auslösung des Mesh. Damit kann der Drucker die erforderliche Geschwindigkeit für die Verarbeitung des G-Codes beibehalten; außerdem wird die Slice-Geschwindigkeit erhöht, indem Details des Mesh entfernt werden, die ohnehin nicht verarbeitet werden können."
-
#: fdmprinter.def.json
msgctxt "multiple_mesh_overlap label"
msgid "Merged Meshes Overlap"
@@ -4242,16 +4182,6 @@ msgctxt "support_mesh description"
msgid "Use this mesh to specify support areas. This can be used to generate support structure."
msgstr "Dieses Netz wird verwendet, um festzulegen, welche Bereiche gestützt werden sollen. Dies kann verwendet werden, um eine Stützstruktur zu errichten."
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down label"
-msgid "Drop Down Support Mesh"
-msgstr "Stütznetz ablegen"
-
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down description"
-msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
-msgstr "Sorgt für Unterstützung überall unterhalb des Stütznetzes, sodass kein Überhang im Stütznetz vorhanden ist."
-
#: fdmprinter.def.json
msgctxt "anti_overhang_mesh label"
msgid "Anti Overhang Mesh"
@@ -4328,14 +4258,194 @@ msgid "experimental!"
msgstr "experimentell!"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order label"
-msgid "Optimize Wall Printing Order"
-msgstr "Reihenfolge des Wanddrucks optimieren"
+msgctxt "support_tree_enable label"
+msgid "Tree Support"
+msgstr "Baumstruktur"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order description"
-msgid "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."
-msgstr "Optimieren Sie die Reihenfolge, in der die Wände gedruckt werden, um die Anzahl der Einzüge und die zurückgelegten Distanzen zu reduzieren. Dieser Schritt bringt für die meisten Teile Vorteile, allerdings werden einige möglicherweise länger benötigen. Vergleichen Sie deshalb bitte die Schätzung der Druckzeiten mit und ohne Optimierung."
+msgctxt "support_tree_enable description"
+msgid "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time."
+msgstr "Erstellen Sie eine baumähnliche Stützstruktur mit Ästen, die Ihren Druck stützen. Das reduziert möglicherweise den Materialverbrauch und die Druckdauer, erhöht jedoch die Slicing-Dauer erheblich."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle label"
+msgid "Tree Support Branch Angle"
+msgstr "Astwinkel der Baumstützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle description"
+msgid "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."
+msgstr "Dies bezeichnet den Winkel der Äste. Verwenden Sie einen geringeren Winkel, um sie vertikaler und stabiler zu gestalten. Verwenden Sie einen stärkeren Winkel, um mehr Reichweite zu erhalten."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance label"
+msgid "Tree Support Branch Distance"
+msgstr "Astabstand der Baumstützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance description"
+msgid "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove."
+msgstr "Dies beschreibt, wie weit die Äste weg sein müssen, wenn sie das Modell berühren. Eine geringe Entfernung lässt die Baumstützstruktur das Modell an mehreren Punkten berühren, und führt zu einem besseren Überhang, allerdings lässt sich die Stützstruktur auch schwieriger entfernen."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter label"
+msgid "Tree Support Branch Diameter"
+msgstr "Astdurchmesser der Baumstützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter description"
+msgid "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this."
+msgstr "Dies beschreibt den Durchmesser der dünnsten Äste der Baumstützstruktur. Dickere Äste sind stabiler. Äste zur Basis hin werden dicker als diese sein."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle label"
+msgid "Tree Support Branch Diameter Angle"
+msgstr "Winkel Astdurchmesser der Baumstützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle description"
+msgid "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support."
+msgstr "Dies beschreibt den Winkel der Astdurchmesser, da sie stufenweise zum Boden hin dicker werden. Ein Winkel von 0 lässt die Äste über die gesamte Länge hinweg eine gleiche Dicke haben. Ein geringer Winkel kann die Stabilität der Baumstützstruktur erhöhen."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution label"
+msgid "Tree Support Collision Resolution"
+msgstr "Kollisionsauflösung der Baumstützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution description"
+msgid "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically."
+msgstr "Dies ist die Auflösung für die Berechnung von Kollisionen, um ein Anschlagen des Modells zu verhindern. Eine niedrigere Einstellung sorgt für akkuratere Bäume, die weniger häufig fehlschlagen, erhöht jedoch die Slicing-Zeit erheblich."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness label"
+msgid "Tree Support Wall Thickness"
+msgstr "Wanddicke der Baumstützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness description"
+msgid "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "Das ist die Dicke der Astwände der Baumstützstruktur. Dickere Wände benötigen eine längere Druckdauer, fallen jedoch nicht so leicht um."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count label"
+msgid "Tree Support Wall Line Count"
+msgstr "Anzahl der Wandlinien der Baumstützstruktur"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count description"
+msgid "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "Das ist die Anzahl der Astwände der Baumstützstruktur. Dickere Wände benötigen eine längere Druckdauer, fallen jedoch nicht so leicht um."
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance label"
+msgid "Slicing Tolerance"
+msgstr "Slicing-Toleranz"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance description"
+msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
+msgstr "Slicen von Schichten mit diagonalen Flächen. Die Bereiche einer Schicht können anhand der Position generiert werden, an der die Mitte einer Schicht die Oberfläche kreuzt (Mitte). Optional kann jede Schicht die Bereiche enthalten, die in das Volumen entlang der Höhe der Schicht (Exklusiv) fallen oder eine Schicht enthält die Bereiche, die irgendwo innerhalb der Schicht positioniert sind (Inklusiv). Exklusiv bewahrt die meisten Details, Inklusiv ermöglicht die beste Passform und Mitte erfordert die kürzeste Bearbeitungszeit."
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option middle"
+msgid "Middle"
+msgstr "Mitte"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option exclusive"
+msgid "Exclusive"
+msgstr "Exklusiv"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option inclusive"
+msgid "Inclusive"
+msgstr "Inklusiv"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width label"
+msgid "Top Surface Skin Line Width"
+msgstr "Oberfläche Außenhaut Linienbreite"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width description"
+msgid "Width of a single line of the areas at the top of the print."
+msgstr "Die Breite einer einzelnen Linie der oberen Druckbereiche."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern label"
+msgid "Top Surface Skin Pattern"
+msgstr "Oberfläche Außenhaut Muster"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern description"
+msgid "The pattern of the top most layers."
+msgstr "Das Muster der obersten Schichten."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option lines"
+msgid "Lines"
+msgstr "Linien"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option concentric"
+msgid "Concentric"
+msgstr "Konzentrisch"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option zigzag"
+msgid "Zig Zag"
+msgstr "Zickzack"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles label"
+msgid "Top Surface Skin Line Directions"
+msgstr "Linienrichtungen der Oberfläche Außenhaut"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles description"
+msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
+msgstr "Eine Liste von Ganzzahl-Linienrichtungen für den Fall, wenn die oberen Außenhautschichten die Linien- oder Zickzack-Muster verwenden. Elemente aus der Liste werden während des Aufbaus der Schichten sequentiell verwendet und wenn das Listenende erreicht wird, beginnt die Liste von vorne. Die Listenobjekte werden durch Kommas getrennt und die gesamte Liste ist in eckige Klammern gesetzt. Standardmäßig ist eine leere Liste vorhanden, was bedeutet, dass herkömmliche Standardwinkel (45- und 135-Grad) verwendet werden."
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization label"
+msgid "Infill Travel Optimization"
+msgstr "Bewegungsoptimierung Füllung"
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization description"
+msgid "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased."
+msgstr "Bei Aktivierung wird die Reihenfolge, in der die Fülllinien gedruckt werden, optimiert, um die gefahrene Distanz zu reduzieren. Diese erzielte Reduzierung der Bewegung ist sehr stark von dem zu slicenden Modell, dem Füllmuster, der Dichte usw. abhängig. Beachten Sie, dass die Dauer für das Slicen bei einigen Modellen mit vielen kleinen Füllbereichen erheblich länger ausfallen kann."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature label"
+msgid "Auto Temperature"
+msgstr "Automatische Temperatur"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature description"
+msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
+msgstr "Die Temperatur wird für jede Schicht automatisch anhand der durchschnittlichen Fließgeschwindigkeit dieser Schicht geändert."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph label"
+msgid "Flow Temperature Graph"
+msgstr "Fließtemperaturgraf"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph description"
+msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
+msgstr "Der Materialfluss (in mm3 pro Sekunde) in Bezug zur Temperatur (Grad Celsius)."
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution label"
+msgid "Maximum Resolution"
+msgstr "Maximale Auflösung"
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution description"
+msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
+msgstr "Die Mindestgröße eines Linienabschnitts nach dem Slicen. Wenn Sie diesen Wert erhöhen, führt dies zu einer niedrigeren Auslösung des Mesh. Damit kann der Drucker die erforderliche Geschwindigkeit für die Verarbeitung des G-Codes beibehalten; außerdem wird die Slice-Geschwindigkeit erhöht, indem Details des Mesh entfernt werden, die ohnehin nicht verarbeitet werden können."
#: fdmprinter.def.json
msgctxt "support_skip_some_zags label"
@@ -4934,6 +5044,46 @@ msgctxt "wireframe_nozzle_clearance description"
msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing."
msgstr "Der Abstand zwischen der Düse und den horizontalen Abwärtslinien. Bei einem größeren Abstand haben die diagonalen Abwärtslinien einen weniger spitzen Winkel, was wiederum weniger Aufwärtsverbindungen zur nächsten Schicht zur Folge hat. Dies gilt nur für das Drucken mit Drahtstruktur."
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled label"
+msgid "Use adaptive layers"
+msgstr "Anpassschichten verwenden"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled description"
+msgid "Adaptive layers computes the layer heights depending on the shape of the model."
+msgstr "Die Funktion Anpassschichten berechnet die Schichthöhe je nach Form des Modells."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation label"
+msgid "Adaptive layers maximum variation"
+msgstr "Maximale Abweichung für Anpassschichten"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation description"
+msgid "The maximum allowed height different from the base layer height in mm."
+msgstr "Das ist die maximal zulässige Höhendifferenz von der Basisschichthöhe in mm."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step label"
+msgid "Adaptive layers variation step size"
+msgstr "Abweichung Schrittgröße für Anpassschichten"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step description"
+msgid "The difference in height of the next layer height compared to the previous one."
+msgstr "Der Höhenunterscheid der nächsten Schichthöhe im Vergleich zur vorherigen."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold label"
+msgid "Adaptive layers threshold"
+msgstr "Schwellenwert Anpassschichten"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold description"
+msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
+msgstr "Das ist der Schwellenwert, der definiert, ob eine kleinere Schicht verwendet wird oder nicht. Dieser Wert wird mit dem der stärksten Neigung in einer Schicht verglichen."
+
#: fdmprinter.def.json
msgctxt "command_line_settings label"
msgid "Command Line Settings"
@@ -4994,6 +5144,26 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Transformationsmatrix, die beim Laden aus der Datei auf das Modell angewandt wird."
+#~ msgctxt "infill_offset_x description"
+#~ msgid "The infill pattern is offset this distance along the X axis."
+#~ msgstr "Das Füllmuster wird um diese Distanz entlang der X-Achse versetzt."
+
+#~ msgctxt "infill_offset_y description"
+#~ msgid "The infill pattern is offset this distance along the Y axis."
+#~ msgstr "Das Füllmuster wird um diese Distanz entlang der Y-Achse versetzt."
+
+#~ msgctxt "infill_overlap description"
+#~ msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
+#~ msgstr "Das Ausmaß des Überlappens zwischen der Füllung und den Wänden. Ein leichtes Überlappen ermöglicht es den Wänden, eine solide Verbindung mit der Füllung herzustellen."
+
+#~ msgctxt "skin_overlap description"
+#~ msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+#~ msgstr "Das Ausmaß des Überlappens zwischen der Außenhaut und den Wänden als Prozentwert der Linienbreite. Ein leichtes Überlappen ermöglicht es den Wänden, eine solide Verbindung mit der Außenhaut herzustellen. Dies ist ein Prozentwert der durchschnittlichen Linienbreiten der Außenhautlinien und der innersten Wand."
+
+#~ msgctxt "material_bed_temperature description"
+#~ msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
+#~ msgstr "Die Temperatur, die für die erhitzte Druckplatte verwendet wird. Wenn dieser Wert 0 beträgt, wird das Bett für diesen Druck nicht erhitzt."
+
#~ msgctxt "wall_x_extruder_nr label"
#~ msgid "Inner Walls Extruder"
#~ msgstr "Extruder Innenwände"
diff --git a/resources/i18n/es_ES/cura.po b/resources/i18n/es_ES/cura.po
index 3a35477fc9..fa8cf61985 100644
--- a/resources/i18n/es_ES/cura.po
+++ b/resources/i18n/es_ES/cura.po
@@ -1,22 +1,24 @@
# Cura
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-11-21 16:58+0100\n"
-"PO-Revision-Date: 2017-11-30 13:05+0100\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
+"PO-Revision-Date: 2018-02-12 13:40+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 2.0.6\n"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:29
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
msgctxt "@action"
msgid "Machine Settings"
msgstr "Ajustes de la máquina"
@@ -53,12 +55,11 @@ msgstr "Conectar con Doodle3D Connect"
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:646
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:875
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:659
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:370
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:78
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:104
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:99
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:355
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:376
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:139
@@ -98,7 +99,7 @@ msgctxt "@info:tooltip"
msgid "Open the Doodle3D Connect web interface"
msgstr "Abrir la interfaz web de Doodle3D Connect"
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:34
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Mostrar registro de cambios"
@@ -113,78 +114,83 @@ msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "El perfil se ha aplanado y activado."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:26
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "Impresión USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Imprimir mediante USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:29
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Imprimir mediante USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:31
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Conectado mediante USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:status"
msgid "Unable to start a new job because the printer is busy or not connected."
msgstr "No se puede iniciar un trabajo nuevo porque la impresora está ocupada o no está conectada."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:title"
msgid "Printer Unavailable"
msgstr "Impresora no disponible"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:status"
msgid "This printer does not support USB printing because it uses UltiGCode flavor."
msgstr "Esta impresora no es compatible con la impresión USB porque utiliza el tipo UltiGCode."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:title"
msgid "USB Printing"
msgstr "Impresión USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
msgctxt "@info:status"
msgid "Unable to start a new job because the printer does not support usb printing."
msgstr "No se puede iniciar un trabajo nuevo porque la impresora no es compatible con la impresión USB."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1349
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:946
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1418
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1496
msgctxt "@info:title"
msgid "Warning"
msgstr "Advertencia"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
msgctxt "@info"
msgid "Unable to update firmware because there are no printers connected."
msgstr "No se puede actualizar el firmware porque no hay impresoras conectadas."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
#, python-format
msgctxt "@info"
msgid "Could not find firmware required for the printer at %s."
msgstr "No se pudo encontrar el firmware necesario para la impresora en %s."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
msgctxt "@info:title"
msgid "Printer Firmware"
msgstr "Firmware de la impresora"
+#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Prepare"
+msgstr "Preparar"
+
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Save to Removable Drive"
@@ -228,11 +234,11 @@ msgid "Could not save to removable drive {0}: {1}"
msgstr "No se pudo guardar en unidad extraíble {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:146
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:693
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:701
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:153
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1358
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:160
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1427
msgctxt "@info:title"
msgid "Error"
msgstr "Error"
@@ -282,7 +288,7 @@ msgid "Removable Drive"
msgstr "Unidad extraíble"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:53
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:51
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Imprimir a través de la red"
@@ -396,110 +402,110 @@ msgctxt "@info:title"
msgid "Printer Status"
msgstr "Estado de la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:691
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No Printcore loaded in slot {0}"
msgstr "No se puede iniciar un trabajo nuevo de impresión. No se ha cargado ningún PrintCore en la ranura {0}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:699
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No material loaded in slot {0}"
msgstr "No se puede iniciar un trabajo nuevo de impresión. No se ha cargado material en la ranura {0}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:709
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:710
#, python-brace-format
msgctxt "@label"
msgid "Not enough material for spool {0}."
msgstr "No hay suficiente material para la bobina {0}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:719
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:720
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "PrintCore distinto (Cura: {0}, impresora: {1}) seleccionado para extrusor {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:733
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:734
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Material distinto (Cura: {0}, impresora: {1}) seleccionado para extrusor {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:741
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:742
#, python-brace-format
msgctxt "@label"
msgid "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer."
msgstr "El PrintCore {0} no está calibrado correctamente. Debe llevarse a cabo una calibración XY de la impresora."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:746
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "¿Seguro que desea imprimir con la configuración seleccionada?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:748
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "La configuración o calibración de la impresora y de Cura no coinciden. Para obtener el mejor resultado, segmente siempre los PrintCores y los materiales que se insertan en la impresora."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:753
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:754
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Configuración desajustada"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:864
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:865
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:258
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Envío de nuevos trabajos (temporalmente) bloqueado; se sigue enviando el trabajo de impresión previo."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Enviando datos a la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:title"
msgid "Sending Data"
msgstr "Enviando datos"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:944
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
msgctxt "@info:status"
msgid "Unable to send data to printer. Is another job still active?"
msgstr "No se puede enviar datos a la impresora. ¿Hay otro trabajo que todavía esté activo?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1085
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1087
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:196
msgctxt "@label:MonitorStatus"
msgid "Aborting print..."
msgstr "Cancelando impresión..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1091
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1093
msgctxt "@label:MonitorStatus"
msgid "Print aborted. Please check the printer"
msgstr "Impresión cancelada. Compruebe la impresora."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1097
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
msgctxt "@label:MonitorStatus"
msgid "Pausing print..."
msgstr "Pausando impresión..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1101
msgctxt "@label:MonitorStatus"
msgid "Resuming print..."
msgstr "Reanudando impresión..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1289
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Sincronizar con la impresora"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "¿Desea utilizar la configuración actual de su impresora en Cura?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1295
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Los PrintCores o los materiales de la impresora difieren de los del proyecto actual. Para obtener el mejor resultado, segmente siempre los PrintCores y materiales que se hayan insertado en la impresora."
@@ -520,145 +526,204 @@ msgid "{printer_name} has finished printing '{job_name}'. Please collect the pri
msgstr "{printer_name} ha terminado de imprimir «{job_name}». Recoja el impreso y confirme que ha borrado la placa de impresión."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:115
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:520
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:533
#, python-brace-format
msgid "{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing."
msgstr "{printer_name} está reservada para imprimir «{job_name}». Modifique la configuración de la impresora de modo que se adapte al trabajo para comenzar la impresión."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:277
msgctxt "@info:status"
msgid "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."
msgstr "No se pudo enviar el nuevo trabajo de impresión: esta impresora 3D (todavía) no está configurada para alojar un grupo de impresoras de Ultimaker 3 conectadas."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:410
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to send print job to group {cluster_name}."
msgstr "No se puede enviar el trabajo de impresión al grupo {cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:418
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:431
#, python-brace-format
msgctxt "@info:status"
msgid "Sent {file_name} to group {cluster_name}."
msgstr "Enviar {file_name} al grupo {cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:436
msgctxt "@action:button"
msgid "Show print jobs"
msgstr "Mostrar trabajos de impresión"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:424
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:437
msgctxt "@info:tooltip"
msgid "Opens the print jobs interface in your browser."
msgstr "Abre la interfaz de trabajos de impresión en el navegador."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:502
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Unknown"
msgstr "Desconocido"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:492
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "{printer_name} ha terminado de imprimir «{job_name}»."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:494
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:497
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:507
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:510
msgctxt "@info:status"
msgid "Print finished"
msgstr "Impresión terminada"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:522
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:525
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:535
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:538
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:282
msgctxt "@label:status"
msgid "Action required"
msgstr "Acción requerida"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:643
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:656
#, python-brace-format
msgctxt "@info:progress"
msgid "Sending {file_name} to group {cluster_name}"
msgstr "Enviando {file_name} al grupo {cluster_name}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:19
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:17
msgctxt "@action"
msgid "Connect via Network"
msgstr "Conectar a través de la red"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:64
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Monitor"
+msgstr "Supervisar"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr "Hay nuevas funciones disponibles para {machine_name}. Se recomienda actualizar el firmware de la impresora."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:65
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:67
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Nuevo firmware de %s disponible"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:68
msgctxt "@action:button"
msgid "How to update"
msgstr "Cómo actualizar"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:77
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:79
msgctxt "@info"
msgid "Could not access update information."
msgstr "No se pudo acceder a la información actualizada."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:199
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
msgctxt "@info:status"
-msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
-msgstr "Se han producido varios errores al abrir el archivo de SolidWorks. Compruebe que el archivo se puede abrir correctamente en SolidWorks."
+msgid "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself."
+msgstr "SolidWorks ha informado de errores al abrir el archivo. Le recomendamos que solucione estos problemas dentro del propio SolidWorks."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:31
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
+msgctxt "@info:status"
+msgid ""
+"Found no models inside your drawing. Could you please check it's content again and make sure one part or assembly is inside?\n"
+"\n"
+" Thanks!."
+msgstr ""
+"No se han encontrado modelos en el dibujo. ¿Puede comprobar el contenido de nuevo y asegurarse de que hay una parte o un ensamblado dentro?\n"
+"\n"
+" Gracias."
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
+msgctxt "@info:status"
+msgid ""
+"Found more then one part or assembly inside your drawing. We currently only support drawings with exactly one part or assembly inside.\n"
+"\n"
+"Sorry!"
+msgstr ""
+"Se ha encontrado más de una parte o ensamblado en el dibujo. Actualmente únicamente son compatibles dibujos con una sola parte o ensamblado.\n"
+"\n"
+" Disculpe."
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
msgctxt "@item:inlistbox"
msgid "SolidWorks part file"
msgstr "Archivo de elementos de SolidWorks"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:35
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:29
msgctxt "@item:inlistbox"
msgid "SolidWorks assembly file"
msgstr "Archivo de ensamblado de SolidWorks"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.py:21
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
+msgctxt "@item:inlistbox"
+msgid "SolidWorks drawing file"
+msgstr "Archivo de dibujo de SolidWorks"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"We could not find a valid installation of SolidWorks on your system. That means that either SolidWorks is not installed or you don't own an valid license. Please make sure that running SolidWorks itself works without issues and/or contact your ICT.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+"Estimado cliente:\n"
+"No hemos encontrado una instalación válida de SolidWorks en el sistema. Esto significa que SolidWorks no está instalado o que no dispone de una licencia válida. Asegúrese de que la ejecución del propio SolidWorks funciona sin problemas o póngase en contacto con su CDTI.\n"
+"\n"
+"Atentamente\n"
+" - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"You are currently running this plugin on an operating system other than Windows. This plugin will only work on Windows with SolidWorks installed, including an valid license. Please install this plugin on a Windows machine with SolidWorks installed.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+"Estimado cliente:\n"
+"Actualmente está ejecutando este complemento en un sistema operativo diferente a Windows. Este complemento solo funcionará en Windows con SolidWorks instalado, siempre que se disponga de una licencia válida. Instale este complemento en un equipo Windows con SolidWorks instalado.\n"
+"\n"
+"Atentamente\n"
+" - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
msgid "Configure"
msgstr "Configurar"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/CommonComReader.py:135
-#, python-format
-msgctxt "@info:status"
-msgid "Error while starting %s!"
-msgstr "Error al iniciar %s"
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
+msgid "Installation guide for SolidWorks macro"
+msgstr "Guía de instalación para la macro de SolidWorks"
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
-msgid "Simulation view"
-msgstr "Vista de simulación"
+msgid "Layer view"
+msgstr "Vista de capas"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "Cura no muestra correctamente las capas si la impresión de alambre está habilitada."
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:101
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
msgctxt "@info:title"
msgid "Simulation View"
msgstr "Vista de simulación"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:26
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:25
msgid "Modify G-Code"
msgstr "Modificar GCode"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
msgctxt "@info"
-msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
-msgstr "Cura recopila de forma anónima información de la segmentación. Puede desactivar esta opción en las preferencias."
+msgid "Cura collects anonymized usage statistics."
+msgstr "Cura recopila estadísticas de uso de forma anónima."
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
msgctxt "@info:title"
@@ -667,14 +732,43 @@ msgstr "Recopilando datos"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
msgctxt "@action:button"
-msgid "Dismiss"
-msgstr "Descartar"
+msgid "Allow"
+msgstr "Permitir"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+msgctxt "@action:tooltip"
+msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
+msgstr "Permitir a Cura enviar estadísticas de uso de forma anónima para ayudar a priorizar mejoras futuras para Cura. Se envían algunas de sus preferencias y ajustes, la versión de Cura y un resumen de los modelos que está fragmentando."
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+msgctxt "@action:button"
+msgid "Disable"
+msgstr "Deshabilitar"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
+msgctxt "@action:tooltip"
+msgid "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."
+msgstr "No permitir a Cura enviar estadísticas de uso de forma anónima. Puede habilitarlo de nuevo en las preferencias."
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Perfiles de Cura 15.04"
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
+msgctxt "@item:inlistbox"
+msgid "Blender file"
+msgstr "Archivo Blender"
+
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
+msgctxt "@info:status"
+msgid ""
+"Could not export using \"{}\" quality!\n"
+"Felt back to \"{}\"."
+msgstr ""
+"No ha podido exportarse con la calidad \"{}\"\n"
+"Retroceder a \"{}»."
+
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
msgctxt "@item:inlistbox"
@@ -706,49 +800,49 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "Imagen GIF"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "No se puede segmentar con el material actual, ya que es incompatible con el dispositivo o la configuración seleccionados."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:297
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:319
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:327
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:336
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:349
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:357
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "No se puede segmentar"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:296
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "Los ajustes actuales no permiten la segmentación. Los siguientes ajustes contienen errores: {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:318
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:348
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr "Los ajustes de algunos modelos no permiten la segmentación. Los siguientes ajustes contienen errores en uno o más modelos: {error_labels}."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:356
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "No se puede segmentar porque la torre auxiliar o la posición o posiciones de preparación no son válidas."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:335
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:365
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "No hay nada que segmentar porque ninguno de los modelos se adapta al volumen de impresión. Escale o rote los modelos para que se adapten."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Procesando capas"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:title"
msgid "Information"
msgstr "Información"
@@ -785,14 +879,14 @@ msgstr "Se ha producido un error al copiar los archivos de complemento de Siemen
msgid "Failed to install Siemens NX plugin. Could not set environment variable UGII_USER_DIR for Siemens NX."
msgstr "Se ha producido un error al instalar el complemento de Siemens NX. No se pudo definir la variable de entorno UGII_USER_DIR de Siemens NX."
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:585
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:165
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Recomendado"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:169
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:595
msgctxt "@title:tab"
msgid "Custom"
msgstr "Personalizado"
@@ -803,24 +897,24 @@ msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "Archivo 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:126
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1142
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:159
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1185
msgctxt "@label"
msgid "Nozzle"
msgstr "Tobera"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:152
#, python-brace-format
msgctxt "@info:status"
msgid "Failed to get plugin ID from {0}"
msgstr "No se pudo obtener la ID del complemento de {0}"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:165
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:153
msgctxt "@info:tile"
msgid "Warning"
msgstr "Advertencia"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:203
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:191
msgctxt "@window:title"
msgid "Plugin browser"
msgstr "Explorador de complementos"
@@ -835,18 +929,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "Archivo G"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:314
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:321
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "Analizar GCode"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:316
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:426
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:323
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:464
msgctxt "@info:title"
msgid "G-code Details"
msgstr "Datos de GCode"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:424
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:462
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "Asegúrese de que el GCode es adecuado para la impresora y para su configuración antes de enviar el archivo a la misma. Es posible que la representación del GCode no sea precisa."
@@ -857,6 +951,16 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Perfil de cura"
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Profile Assistant"
+msgstr "Asistente del perfil"
+
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
+msgctxt "@item:inlistbox"
+msgid "Profile Assistant"
+msgstr "Asistente del perfil"
+
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
msgctxt "@item:inlistbox"
msgid "3MF file"
@@ -888,142 +992,116 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "Nivelar placa de impresión"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Pared exterior"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Paredes interiores"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
msgctxt "@tooltip"
msgid "Skin"
msgstr "Forro"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
msgctxt "@tooltip"
msgid "Infill"
msgstr "Relleno"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:93
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
msgctxt "@tooltip"
msgid "Support Infill"
-msgstr "Relleno de soporte"
+msgstr "Relleno del soporte"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:94
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
msgctxt "@tooltip"
msgid "Support Interface"
-msgstr "Interfaz de soporte"
+msgstr "Interfaz del soporte"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:95
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
msgctxt "@tooltip"
msgid "Support"
msgstr "Soporte"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:96
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
msgctxt "@tooltip"
msgid "Skirt"
msgstr "Falda"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:97
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
msgctxt "@tooltip"
msgid "Travel"
msgstr "Desplazamiento"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Retracciones"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
msgctxt "@tooltip"
msgid "Other"
msgstr "Otro"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:199
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:231
msgctxt "@label unknown material"
msgid "Unknown"
msgstr "Desconocido"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:284
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:318
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "Archivo {0} presegmentado"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:469
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:440
msgctxt "@item:material"
msgid "No material loaded"
msgstr "No se ha cargado material."
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:476
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:447
msgctxt "@item:material"
msgid "Unknown material"
msgstr "Material desconocido"
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:30
-msgctxt "@info:status"
-msgid "Finding new location for objects"
-msgstr "Buscando nueva ubicación para los objetos"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:34
-msgctxt "@info:title"
-msgid "Finding Location"
-msgstr "Buscando ubicación"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:89
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
-msgctxt "@info:status"
-msgid "Unable to find a location within the build volume for all objects"
-msgstr "No se puede encontrar una ubicación dentro del volumen de impresión para todos los objetos"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:90
-msgctxt "@info:title"
-msgid "Can't Find Location"
-msgstr "No se puede encontrar la ubicación"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:431
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:113
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:437
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:120
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "El archivo ya existe"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:432
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:114
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:438
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
msgid "The file {0} already exists. Are you sure you want to overwrite it?"
msgstr "El archivo {0} ya existe. ¿Está seguro de que desea sobrescribirlo?"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:872
msgctxt "@label"
msgid "Custom"
msgstr "Personalizado"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:819
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:876
msgctxt "@label"
msgid "Custom Material"
msgstr "Material personalizado"
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:182
-msgctxt "@menuitem"
-msgid "Global"
-msgstr "Global"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:229
+#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:205
msgctxt "@menuitem"
msgid "Not overridden"
msgstr "No reemplazado"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:124
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "El material seleccionado no es compatible con la máquina o la configuración seleccionada."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:125
#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24
msgctxt "@info:title"
msgid "Incompatible Material"
@@ -1044,67 +1122,89 @@ msgctxt "@action"
msgid "Undo changing the material diameter."
msgstr "Deshacer cambio del diámetro del material."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:144
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to export profile to {0}: {1}"
msgstr "Error al exportar el perfil a {0}: {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:158
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Failed to export profile to {0}: Writer plugin reported failure."
msgstr "Error al exportar el perfil a {0}: Error en el complemento de escritura."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:156
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:163
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Exported profile to {0}"
msgstr "Perfil exportado a {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:157
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:164
msgctxt "@info:title"
msgid "Export succeeded"
msgstr "Exportación correcta"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:183
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:205
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:214
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:248
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:211
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:271
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
msgstr "Error al importar el perfil de {0}: {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:216
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:252
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:230
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "This profile {0} contains incorrect data, could not import it."
+msgstr "Este perfil {0} contiene datos incorrectos, no se han podido importar."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "The machine defined in profile {0} doesn't match with your current machine, could not import it."
+msgstr "El equipo definido en el perfil {0} no coincide con su equipo actual, no se ha podido importar."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Perfil {0} importado correctamente"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:255
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:277
+#, python-brace-format
+msgctxt "@info:status"
+msgid "File {0} does not contain any valid profile."
+msgstr "El archivo {0} no contiene ningún perfil válido."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "El perfil {0} tiene un tipo de archivo desconocido o está corrupto."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:298
msgctxt "@label"
msgid "Custom profile"
msgstr "Perfil personalizado"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:285
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Al perfil le falta un tipo de calidad."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:349
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "No se ha podido encontrar un tipo de calidad {0} para la configuración actual."
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:46
+#, python-brace-format
+msgctxt "@label"
+msgid "Group #{group_nr}"
+msgstr "N.º de grupo {group_nr}"
+
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
@@ -1115,142 +1215,170 @@ msgctxt "@info:title"
msgid "Build Volume"
msgstr "Volumen de impresión"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:34
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:25
msgctxt "@info:status"
msgid "Multiplying and placing objects"
msgstr "Multiplicar y colocar objetos"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:35
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:26
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
msgctxt "@info:title"
msgid "Placing Object"
msgstr "Colocando objeto"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:80
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:88
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:152
+msgctxt "@info:status"
+msgid "Unable to find a location within the build volume for all objects"
+msgstr "No se puede encontrar una ubicación dentro del volumen de impresión para todos los objetos"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:29
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:64
+msgctxt "@info:status"
+msgid "Finding new location for objects"
+msgstr "Buscando nueva ubicación para los objetos"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:33
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:68
+msgctxt "@info:title"
+msgid "Finding Location"
+msgstr "Buscando ubicación"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:89
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:153
+msgctxt "@info:title"
+msgid "Can't Find Location"
+msgstr "No se puede encontrar la ubicación"
+
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:81
msgctxt "@title:window"
msgid "Crash Report"
msgstr "Informe del accidente"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:93
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:94
msgctxt "@label crash message"
msgid ""
-"
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+"
A fatal error has occurred. Please send us this Crash Report to fix the problem
\n"
"
Please use the \"Send report\" button to post a bug report automatically to our servers
\n"
" "
-msgstr "
Se ha producido una excepción fatal. Envíenos este informe de errores para que podamos solucionar el problema.
\n
Utilice el botón «Enviar informe» para publicar automáticamente un informe de errores en nuestros servidores.
\n "
+msgstr ""
+"
Se ha producido un error grave. Envíenos este informe de incidencias para que podamos solucionar el problema.
\n"
+"
Utilice el botón «Enviar informe» para publicar automáticamente un informe de errores en nuestros servidores.
"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:141
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:147
msgctxt "@title:groupbox"
-msgid "Exception traceback"
-msgstr "Rastreabilidad de excepciones"
+msgid "Error traceback"
+msgstr "Rastreabilidad de errores"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:208
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:214
msgctxt "@title:groupbox"
msgid "Logs"
msgstr "Registros"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:231
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:237
msgctxt "@title:groupbox"
msgid "User description"
msgstr "Descripción del usuario"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:246
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:252
msgctxt "@action:button"
msgid "Send report"
msgstr "Enviar informe"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:256
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:274
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Cargando máquinas..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:661
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:660
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Configurando escena..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:703
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:702
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Cargando interfaz..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:874
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:899
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1348
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "Solo se puede cargar un archivo GCode a la vez. Se omitió la importación de {0}"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1357
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1426
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "No se puede abrir ningún archivo si se está cargando un archivo GCode. Se omitió la importación de {0}"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1416
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1495
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "No se puede cargar el modelo seleccionado, es demasiado pequeño."
@@ -1279,12 +1407,11 @@ msgstr "X (anchura)"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:288
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:300
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:391
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:413
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:840
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:394
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:424
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:849
msgctxt "@label"
msgid "mm"
msgstr "mm"
@@ -1374,68 +1501,67 @@ msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "Diferencia de altura entre la punta de la tobera y el sistema del puente (ejes X e Y). Se usa para evitar que colisionen la impresión anterior con el caballete al imprimir «de uno en uno»."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:254
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Número de extrusores"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:289
-msgctxt "@tooltip"
-msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
-msgstr "El diámetro nominal del filamento compatible con la impresora. El diámetro exacto se sobrescribirá según el material o el perfil."
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:291
-msgctxt "@label"
-msgid "Material diameter"
-msgstr "Diámetro del material"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:299
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:390
-msgctxt "@label"
-msgid "Nozzle size"
-msgstr "Tamaño de la tobera"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:317
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:310
msgctxt "@label"
msgid "Start Gcode"
msgstr "Iniciar GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:327
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:320
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very start."
msgstr "Los comandos de Gcode que se ejecutarán justo al inicio."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:336
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:329
msgctxt "@label"
msgid "End Gcode"
msgstr "Finalizar GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:346
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:339
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very end."
msgstr "Los comandos de Gcode que se ejecutarán justo al final."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:378
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:370
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Ajustes de la tobera"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:382
+msgctxt "@label"
+msgid "Nozzle size"
+msgstr "Tamaño de la tobera"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:393
+msgctxt "@label"
+msgid "Compatible material diameter"
+msgstr "Diámetro del material compatible"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:395
+msgctxt "@tooltip"
+msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
+msgstr "El diámetro nominal del filamento compatible con la impresora. El diámetro exacto se sobrescribirá según el material o el perfil."
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:411
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "Desplazamiento de la tobera sobre el eje X"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:423
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Desplazamiento de la tobera sobre el eje Y"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:433
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:444
msgctxt "@label"
msgid "Extruder Start Gcode"
msgstr "GCode inicial del extrusor"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:462
msgctxt "@label"
msgid "Extruder End Gcode"
msgstr "GCode final del extrusor"
@@ -1448,8 +1574,9 @@ msgstr "Registro de cambios"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:107
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:55
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:445
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:357
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:306
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:456
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:492
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:80
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:123
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
@@ -1514,7 +1641,10 @@ msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
"\n"
"Select your printer from the list below:"
-msgstr "Para imprimir directamente en la impresora a través de la red, asegúrese de que esta está conectada a la red utilizando un cable de red o conéctela a la red wifi. Si no conecta Cura con la impresora, también puede utilizar una unidad USB para transferir archivos GCode a la impresora.\n\nSeleccione la impresora de la siguiente lista:"
+msgstr ""
+"Para imprimir directamente en la impresora a través de la red, asegúrese de que esta está conectada a la red utilizando un cable de red o conéctela a la red wifi. Si no conecta Cura con la impresora, también puede utilizar una unidad USB para transferir archivos GCode a la impresora.\n"
+"\n"
+"Seleccione la impresora de la siguiente lista:"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:75
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:44
@@ -1530,7 +1660,7 @@ msgstr "Editar"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:50
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:95
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:171
msgctxt "@action:button"
msgid "Remove"
msgstr "Eliminar"
@@ -1552,12 +1682,12 @@ msgid "Type"
msgstr "Tipo"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:233
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3"
msgstr "Ultimaker 3"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:236
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3 Extended"
msgstr "Ultimaker 3 Extended"
@@ -1603,8 +1733,6 @@ msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Introduzca la dirección IP o el nombre de host de la impresora en red."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:379
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:92
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:88
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
@@ -1625,6 +1753,11 @@ msgctxt "@label: arg 1 is group name"
msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
msgstr "%1 no está configurada para alojar un grupo de impresoras conectadas de Ultimaker 3"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
+msgctxt "@label link to connect manager"
+msgid "Add/Remove printers"
+msgstr "Agregar/eliminar impresoras"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
msgctxt "@info:tooltip"
msgid "Opens the print jobs page with your default web browser."
@@ -1655,11 +1788,16 @@ msgid "Available"
msgstr "Disponible"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:100
msgctxt "@label:MonitorStatus"
msgid "Lost connection with the printer"
msgstr "Se ha perdido la conexión con la impresora."
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
+msgctxt "@label Printer status"
+msgid "Unknown"
+msgstr "Desconocido"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:257
msgctxt "@label:status"
msgid "Disabled"
@@ -1751,138 +1889,252 @@ msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Activar configuración"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:20
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:21
msgctxt "@title:window"
-msgid "Cura SolidWorks Plugin Configuration"
-msgstr "Configuración de complementos Cura SolidWorks"
+msgid "SolidWorks: Export wizard"
+msgstr "SolidWorks: exportar asistente"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:44
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:45
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:140
msgctxt "@action:label"
-msgid "Default quality of the exported STL:"
-msgstr "Calidad predeterminada del STL exportado:"
+msgid "Quality:"
+msgstr "Calidad:"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:78
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:179
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always ask"
-msgstr "Preguntar siempre"
+msgid "Fine (3D-printing)"
+msgstr "Fina (impresión en 3D)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:180
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Fine quality"
-msgstr "Usar siempre calidad fina"
+msgid "Coarse (3D-printing)"
+msgstr "Gruesa (impresión en 3D)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:181
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Coarse quality"
-msgstr "Usar siempre calidad gruesa"
+msgid "Fine (SolidWorks)"
+msgstr "Fina (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:20
-msgctxt "@title:window"
-msgid "Import SolidWorks File as STL..."
-msgstr "Importar el archivo SolidWorks como STL..."
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:43
-msgctxt "@info:tooltip"
-msgid "Quality of the Exported STL"
-msgstr "Calidad del STL exportado"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:48
-msgctxt "@action:label"
-msgid "Quality"
-msgstr "Calidad"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:62
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:182
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Coarse"
-msgstr "Gruesa"
+msgid "Coarse (SolidWorks)"
+msgstr "Gruesa (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:63
-msgctxt "@option:curaSolidworksStlQuality"
-msgid "Fine"
-msgstr "Fina"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:78
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:82
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:94
msgctxt "@text:window"
-msgid "Remember my choice"
-msgstr "Recordar mi selección"
+msgid "Show this dialog again"
+msgstr "Mostrar este cuadro de diálogo de nuevo"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:104
+msgctxt "@action:button"
+msgid "Continue"
+msgstr "Continuar"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:116
+msgctxt "@action:button"
+msgid "Abort"
+msgstr "Cancelar"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:21
+msgctxt "@title:window"
+msgid "How to install Cura SolidWorks macro"
+msgstr "Cómo instalar la macro SolidWorks de Cura"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:62
+msgctxt "@description:label"
+msgid "Steps:"
+msgstr "Pasos:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:140
+msgctxt "@action:button"
+msgid ""
+"Open the directory\n"
+"with macro and icon"
+msgstr ""
+"Abra el directorio\n"
+"con la macro y el icono"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:160
+msgctxt "@description:label"
+msgid "Instructions:"
+msgstr "Instrucciones:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:202
+msgctxt "@action:playpause"
+msgid "Play"
+msgstr "Reproducir"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:206
+msgctxt "@action:playpause"
+msgid "Pause"
+msgstr "Pausar"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:268
+msgctxt "@action:button"
+msgid "Previous Step"
+msgstr "Paso anterior"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:283
+msgctxt "@action:button"
+msgid "Done"
+msgstr "Realizado"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:287
+msgctxt "@action:button"
+msgid "Next Step"
+msgstr "Paso siguiente"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:21
+msgctxt "@title:window"
+msgid "SolidWorks plugin: Configuration"
+msgstr "Complementos de SolidWorks: configuración"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:39
+msgctxt "@title:tab"
+msgid "Conversion settings"
+msgstr "Ajustes de la conversión"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:66
+msgctxt "@label"
+msgid "First choice:"
+msgstr "Primera opción:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:86
+msgctxt "@text:menu"
+msgid "Latest installed version (Recommended)"
+msgstr "Última versión instalada (recomendada)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:95
+msgctxt "@text:menu"
+msgid "Default version"
+msgstr "Versión predeterminada"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:193
+msgctxt "@label"
+msgid "Show wizard before opening SolidWorks files"
+msgstr "Mostrar asistente antes de abrir los archivos de SolidWorks"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:203
+msgctxt "@label"
+msgid "Automatically rotate opened file into normed orientation"
+msgstr "Girar automáticamente el archivo abierto a la orientación normal"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:210
+msgctxt "@title:tab"
+msgid "Installation(s)"
+msgstr "Instalación(es)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:284
+msgctxt "@label"
+msgid "COM service found"
+msgstr "Servicio COM encontrado"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:295
+msgctxt "@label"
+msgid "Executable found"
+msgstr "Ejecutable encontrado"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:306
+msgctxt "@label"
+msgid "COM starting"
+msgstr "Iniciando COM"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:317
+msgctxt "@label"
+msgid "Revision number"
+msgstr "Número de revisión"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:328
+msgctxt "@label"
+msgid "Functions available"
+msgstr "Funciones disponibles"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+msgctxt "@action:button"
+msgid "Save"
+msgstr "Guardar"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
msgctxt "@label"
msgid "Color scheme"
msgstr "Combinación de colores"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:96
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Color del material"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Tipo de línea"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "Velocidad"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:108
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr "Grosor de la capa"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:148
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Modo de compatibilidad"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:230
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
msgctxt "@label"
msgid "Show Travels"
msgstr "Mostrar desplazamientos"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:236
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
msgctxt "@label"
msgid "Show Helpers"
msgstr "Mostrar asistentes"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:242
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
msgctxt "@label"
msgid "Show Shell"
msgstr "Mostrar perímetro"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:248
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
msgctxt "@label"
msgid "Show Infill"
msgstr "Mostrar relleno"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:297
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Mostrar solo capas superiores"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:306
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "Mostrar cinco capas detalladas en la parte superior"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:317
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Superior o inferior"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:321
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
msgctxt "@label"
msgid "Inner Wall"
msgstr "Pared interior"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:378
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
msgctxt "@label"
msgid "min"
msgstr "mín."
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:420
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
msgctxt "@label"
msgid "max"
msgstr "máx."
@@ -1907,7 +2159,7 @@ msgctxt "@label"
msgid "Settings"
msgstr "Ajustes"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:455
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:466
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Cambia las secuencias de comandos de posprocesamiento."
@@ -1982,23 +2234,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Suavizado"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:208
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+msgctxt "@label"
+msgid "Mesh Type"
+msgstr "Tipo de malla"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+msgctxt "@label"
+msgid "Normal model"
+msgstr "Modelo normal"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+msgctxt "@label"
+msgid "Print as support"
+msgstr "Imprimir según compatibilidad"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+msgctxt "@label"
+msgid "Don't support overlap with other models"
+msgstr "No es compatible la superposición con otros modelos"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+msgctxt "@label"
+msgid "Modify settings for overlap with other models"
+msgstr "Modificar ajustes para superponer con otros modelos"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+msgctxt "@label"
+msgid "Modify settings for infill of other models"
+msgstr "Modificar ajustes para rellenar con otros modelos"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:333
msgctxt "@action:button"
msgid "Select settings"
msgstr "Seleccionar ajustes"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:248
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:375
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Seleccionar ajustes o personalizar este modelo"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:272
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:402
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:91
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Filtrar..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:296
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:426
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Mostrar todo"
@@ -2166,7 +2448,10 @@ msgid ""
"This plugin contains a license.\n"
"You need to accept this license to install this plugin.\n"
"Do you agree with the terms below?"
-msgstr "Este complemento incluye una licencia.\nDebe aceptar dicha licencia para instalar el complemento.\n¿Acepta las condiciones que aparecen a continuación?"
+msgstr ""
+"Este complemento incluye una licencia.\n"
+"Debe aceptar dicha licencia para instalar el complemento.\n"
+"¿Acepta las condiciones que aparecen a continuación?"
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:242
msgctxt "@action:button"
@@ -2357,66 +2642,66 @@ msgctxt "@label"
msgid "Everything is in order! You're done with your CheckUp."
msgstr "¡Todo correcto! Ha terminado con la comprobación."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:88
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:87
msgctxt "@label:MonitorStatus"
msgid "Not connected to a printer"
msgstr "No está conectado a ninguna impresora."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:90
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:89
msgctxt "@label:MonitorStatus"
msgid "Printer does not accept commands"
msgstr "La impresora no acepta comandos."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:96
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:95
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:194
msgctxt "@label:MonitorStatus"
msgid "In maintenance. Please check the printer"
msgstr "En mantenimiento. Compruebe la impresora."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:102
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:184
msgctxt "@label:MonitorStatus"
msgid "Printing..."
msgstr "Imprimiendo..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:105
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:186
msgctxt "@label:MonitorStatus"
msgid "Paused"
msgstr "En pausa"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:109
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:108
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:188
msgctxt "@label:MonitorStatus"
msgid "Preparing..."
msgstr "Preparando..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:111
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:110
msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "Retire la impresión."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:237
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
msgctxt "@label:"
msgid "Resume"
msgstr "Reanudar"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:245
msgctxt "@label:"
msgid "Pause"
msgstr "Pausar"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:274
msgctxt "@label:"
msgid "Abort Print"
msgstr "Cancelar impresión"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:280
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
msgctxt "@window:title"
msgid "Abort print"
msgstr "Cancela la impresión"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:282
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:286
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
msgstr "¿Está seguro de que desea cancelar la impresión?"
@@ -2431,7 +2716,9 @@ msgctxt "@text:window"
msgid ""
"You have customized some profile settings.\n"
"Would you like to keep or discard those settings?"
-msgstr "Ha personalizado parte de los ajustes del perfil.\n¿Desea descartar los cambios o guardarlos?"
+msgstr ""
+"Ha personalizado parte de los ajustes del perfil.\n"
+"¿Desea descartar los cambios o guardarlos?"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:110
msgctxt "@title:column"
@@ -2449,19 +2736,19 @@ msgid "Customized"
msgstr "Valor personalizado"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:593
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Preguntar siempre"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:594
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:596
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Descartar y no volver a preguntar"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:597
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Guardar y no volver a preguntar"
@@ -2496,72 +2783,72 @@ msgctxt "@label"
msgid "Brand"
msgstr "Marca"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:88
msgctxt "@label"
msgid "Material Type"
msgstr "Tipo de material"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:97
msgctxt "@label"
msgid "Color"
msgstr "Color"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
msgctxt "@label"
msgid "Properties"
msgstr "Propiedades"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:143
msgctxt "@label"
msgid "Density"
msgstr "Densidad"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:156
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:158
msgctxt "@label"
msgid "Diameter"
msgstr "Diámetro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:187
msgctxt "@label"
msgid "Filament Cost"
msgstr "Coste del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
msgctxt "@label"
msgid "Filament weight"
-msgstr "Anchura del filamento"
+msgstr "Peso del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:220
msgctxt "@label"
msgid "Filament length"
msgstr "Longitud del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:227
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:229
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Coste por metro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:243
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Este material está vinculado a %1 y comparte alguna de sus propiedades."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:250
msgctxt "@label"
msgid "Unlink Material"
msgstr "Desvincular material"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:259
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:261
msgctxt "@label"
msgid "Description"
msgstr "Descripción"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:272
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:274
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Información sobre adherencia"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:300
msgctxt "@label"
msgid "Print settings"
msgstr "Ajustes de impresión"
@@ -2602,7 +2889,7 @@ msgid "Unit"
msgstr "Unidad"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:14
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:439
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:509
msgctxt "@title:tab"
msgid "General"
msgstr "General"
@@ -2617,230 +2904,255 @@ msgctxt "@label"
msgid "Language:"
msgstr "Idioma:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:207
msgctxt "@label"
msgid "Currency:"
msgstr "Moneda:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:219
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Theme:"
msgstr "Tema:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:279
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:281
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Tendrá que reiniciar la aplicación para que estos cambios tengan efecto."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:296
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:298
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Segmentar automáticamente al cambiar los ajustes."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:306
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Segmentar automáticamente"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:318
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:320
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Comportamiento de la ventanilla"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:326
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:328
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Resaltar en rojo las áreas del modelo sin soporte. Sin soporte, estas áreas no se imprimirán correctamente."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:337
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Mostrar voladizos"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:342
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:344
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
msgstr "Mueve la cámara de manera que el modelo se encuentre en el centro de la vista cuando se selecciona un modelo."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:347
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:349
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Centrar cámara cuando se selecciona elemento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:356
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:358
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "¿Se debería invertir el comportamiento predeterminado del zoom de cura?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:363
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Invertir la dirección del zoom de la cámara."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:370
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:372
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "¿Debería moverse el zoom en la dirección del ratón?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:377
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "Hacer zoom en la dirección del ratón"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "¿Deben moverse los modelos en la plataforma de modo que no se crucen?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Asegúrese de que lo modelos están separados."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:397
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "¿Deben moverse los modelos del área de impresión de modo que no toquen la placa de impresión?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:402
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Arrastrar modelos a la placa de impresión de forma automática"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:414
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:416
msgctxt "@info:tooltip"
msgid "Show caution message in gcode reader."
msgstr "Mostrar mensaje de advertencia en el lector de GCode."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:423
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:425
msgctxt "@option:check"
msgid "Caution message in gcode reader"
msgstr "Mensaje de advertencia en el lector de GCode"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "¿Debe forzarse el modo de compatibilidad de la capa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:435
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:437
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Forzar modo de compatibilidad de la vista de capas (necesario reiniciar)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:451
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:453
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Abrir y guardar archivos"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:457
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:459
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "¿Deben ajustarse los modelos al volumen de impresión si son demasiado grandes?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:462
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:464
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Escalar modelos de gran tamaño"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:471
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:473
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Un modelo puede mostrarse demasiado pequeño si su unidad son metros en lugar de milímetros, por ejemplo. ¿Deben escalarse estos modelos?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:476
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:478
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Escalar modelos demasiado pequeños"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:485
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:487
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "¿Debe añadirse automáticamente un prefijo basado en el nombre de la impresora al nombre del trabajo de impresión?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Agregar prefijo de la máquina al nombre del trabajo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:501
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "¿Mostrar un resumen al guardar un archivo de proyecto?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Mostrar un cuadro de diálogo de resumen al guardar el proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:514
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Comportamiento predeterminado al abrir un archivo del proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Comportamiento predeterminado al abrir un archivo del proyecto: "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:533
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@option:openProject"
msgid "Always ask"
msgstr "Preguntar siempre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:536
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Abrir siempre como un proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Importar modelos siempre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Si ha realizado cambios en un perfil y, a continuación, ha cambiado a otro, aparecerá un cuadro de diálogo que le preguntará si desea guardar o descartar los cambios. También puede elegir el comportamiento predeterminado, así ese cuadro de diálogo no volverá a aparecer."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:582
msgctxt "@label"
msgid "Override Profile"
msgstr "Anular perfil"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:629
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:631
msgctxt "@label"
msgid "Privacy"
msgstr "Privacidad"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "¿Debe Cura buscar actualizaciones cuando se abre el programa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:641
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:643
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Buscar actualizaciones al iniciar"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:651
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:653
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "¿Deben enviarse datos anónimos sobre la impresión a Ultimaker? Tenga en cuenta que no se envían ni almacenan modelos, direcciones IP ni otra información de identificación personal."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:656
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:658
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "Enviar información (anónima) de impresión"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:674
+msgctxt "@label"
+msgid "Experimental"
+msgstr "Experimental"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:680
+msgctxt "@info:tooltip"
+msgid "Use multi build plate functionality"
+msgstr "Utilizar funcionalidad de placa de impresión múltiple"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:685
+msgctxt "@option:check"
+msgid "Use multi build plate functionality (restart required)"
+msgstr "Utilizar funcionalidad de placa de impresión múltiple (reinicio requerido)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+msgctxt "@info:tooltip"
+msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+msgstr "¿Los modelos recién cargados se deben organizar en la placa de impresión? Se han usado junto con la placa de impresión múltiple (EXPERIMENTAL)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+msgctxt "@option:check"
+msgid "Do not arrange objects on load"
+msgstr "No organizar objetos al cargar"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:514
msgctxt "@title:tab"
msgid "Printers"
msgstr "Impresoras"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:37
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:51
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:138
msgctxt "@action:button"
msgid "Activate"
msgstr "Activar"
@@ -2883,7 +3195,7 @@ msgid "Waiting for a printjob"
msgstr "Esperando un trabajo de impresión..."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:448
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:518
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Perfiles"
@@ -2909,13 +3221,13 @@ msgid "Duplicate"
msgstr "Duplicado"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:113
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:182
msgctxt "@action:button"
msgid "Import"
msgstr "Importar"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:119
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:212
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:193
msgctxt "@action:button"
msgid "Export"
msgstr "Exportar"
@@ -2981,7 +3293,7 @@ msgid "Export Profile"
msgstr "Exportar perfil"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:516
msgctxt "@title:tab"
msgid "Materials"
msgstr "Materiales"
@@ -2996,60 +3308,60 @@ msgctxt "@action:label %1 is printer name"
msgid "Printer: %1"
msgstr "Impresora: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:149
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:150
msgctxt "@action:button"
msgid "Create"
msgstr "Crear"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:160
msgctxt "@action:button"
msgid "Duplicate"
msgstr "Duplicado"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:306
msgctxt "@title:window"
msgid "Import Material"
msgstr "Importar material"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:320
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:307
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Could not import material %1: %2"
msgstr "No se pudo importar el material en %1: %2."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully imported material %1"
msgstr "El material se ha importado correctamente en %1."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:343
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:329
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:344
msgctxt "@title:window"
msgid "Export Material"
msgstr "Exportar material"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:348
msgctxt "@info:status Don't translate the XML tags and !"
msgid "Failed to export material to %1: %2"
msgstr "Se ha producido un error al exportar el material a %1: %2."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:368
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:354
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully exported material to %1"
msgstr "El material se ha exportado correctamente a %1."
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:793
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:869
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Agregar impresora"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
msgctxt "@label"
msgid "Printer Name:"
msgstr "Nombre de la impresora:"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Agregar impresora"
@@ -3074,7 +3386,9 @@ msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
"Cura proudly uses the following open source projects:"
-msgstr "Ultimaker B.V. ha desarrollado Cura en cooperación con la comunidad.\nCura se enorgullece de utilizar los siguientes proyectos de código abierto:"
+msgstr ""
+"Ultimaker B.V. ha desarrollado Cura en cooperación con la comunidad.\n"
+"Cura se enorgullece de utilizar los siguientes proyectos de código abierto:"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
msgctxt "@label"
@@ -3176,158 +3490,167 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Perfil:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:66
-msgctxt "@"
-msgid "No Profile Available"
-msgstr "No hay perfiles disponibles."
-
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:102
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
"\n"
"Click to open the profile manager."
-msgstr "Algunos valores de los ajustes o sobrescrituras son distintos a los valores almacenados en el perfil.\n\nHaga clic para abrir el administrador de perfiles."
+msgstr ""
+"Algunos valores de los ajustes o sobrescrituras son distintos a los valores almacenados en el perfil.\n"
+"\n"
+"Haga clic para abrir el administrador de perfiles."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:150
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Buscar..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:482
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Copiar valor en todos los extrusores"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:497
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Ocultar este ajuste"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:508
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:507
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "No mostrar este ajuste"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:511
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Mostrar este ajuste"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:530
msgctxt "@action:menu"
msgid "Configure setting visiblity..."
msgstr "Configurar la visibilidad de los ajustes..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:250
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
"\n"
"Click to make these settings visible."
-msgstr "Algunos ajustes ocultos utilizan valores diferentes de los valores normales calculados.\n\nHaga clic para mostrar estos ajustes."
+msgstr ""
+"Algunos ajustes ocultos utilizan valores diferentes de los valores normales calculados.\n"
+"\n"
+"Haga clic para mostrar estos ajustes."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:62
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
msgctxt "@label Header for list of settings."
msgid "Affects"
msgstr "Afecta a"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:67
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:66
msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Afectado por"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:156
msgctxt "@label"
-msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
+msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
msgstr "Este ajuste siempre se comparte entre extrusores. Si lo modifica, modificará el valor de todos los extrusores."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:160
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:159
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "El valor se resuelve según los valores de los extrusores. "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:186
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:190
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
"\n"
"Click to restore the value of the profile."
-msgstr "Este ajuste tiene un valor distinto del perfil.\n\nHaga clic para restaurar el valor del perfil."
+msgstr ""
+"Este ajuste tiene un valor distinto del perfil.\n"
+"\n"
+"Haga clic para restaurar el valor del perfil."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:288
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
"\n"
"Click to restore the calculated value."
-msgstr "Este ajuste se calcula normalmente pero actualmente tiene un valor absoluto establecido.\n\nHaga clic para restaurar el valor calculado."
+msgstr ""
+"Este ajuste se calcula normalmente pero actualmente tiene un valor absoluto establecido.\n"
+"\n"
+"Haga clic para restaurar el valor calculado."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid "Print Setup"
msgstr "Configuración de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid ""
"Print Setup disabled\n"
"G-code files cannot be modified"
-msgstr "Ajustes de impresión deshabilitados\nNo se pueden modificar los archivos GCode"
+msgstr ""
+"Ajustes de impresión deshabilitados\n"
+"No se pueden modificar los archivos GCode"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:336
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:342
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00 h 00 min"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:354
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:359
msgctxt "@tooltip"
-msgid "Time specification
"
-msgstr "Especificación de tiempo
"
+msgid "Time specification"
+msgstr "Especificación de tiempos"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:429
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:441
msgctxt "@label"
msgid "Cost specification"
msgstr "Especificación de costes"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:434
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:455
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1 m"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:435
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:447
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:456
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1 g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:454
msgctxt "@label"
msgid "Total:"
msgstr "Total:"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:504
msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
msgid "%1m / ~ %2g / ~ %4 %3"
msgstr "%1 m/~ %2 g/~ %4 %3"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:509
msgctxt "@label Print estimates: m for meters, g for grams"
msgid "%1m / ~ %2g"
msgstr "%1 m/~ %2 g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Configuración de impresión recomendada
Imprimir con los ajustes recomendados para la impresora, el material y la calidad seleccionados."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:596
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Configuración de impresión personalizada
Imprimir con un control muy detallado del proceso de segmentación."
-#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:49
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:50
msgctxt "@title:menuitem %1 is the automatically selected material"
msgid "Automatic: %1"
msgstr "Automático: %1"
@@ -3337,6 +3660,16 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Ver"
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:37
+msgctxt "@action:inmenu menubar:view"
+msgid "&Camera position"
+msgstr "&Posición de la cámara"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:52
+msgctxt "@action:inmenu menubar:view"
+msgid "&Build plate"
+msgstr "&Placa de impresión"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:40
msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer"
msgid "Automatic: %1"
@@ -3349,14 +3682,14 @@ msgid_plural "Print Selected Models With:"
msgstr[0] "Imprimir modelo seleccionado con:"
msgstr[1] "Imprimir modelos seleccionados con:"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:83
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:114
msgctxt "@title:window"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Multiplicar modelo seleccionado"
msgstr[1] "Multiplicar modelos seleccionados"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:108
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:139
msgctxt "@label"
msgid "Number of Copies"
msgstr "Número de copias"
@@ -3372,7 +3705,7 @@ msgid "No printer connected"
msgstr "No hay ninguna impresora conectada"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:90
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:139
msgctxt "@label"
msgid "Extruder"
msgstr "Extrusor"
@@ -3482,254 +3815,294 @@ msgctxt "@label"
msgid "Estimated time left"
msgstr "Tiempo restante estimado"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
msgid "Toggle Fu&ll Screen"
msgstr "A<ernar pantalla completa"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "Des&hacer"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:89
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "&Rehacer"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:99
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "&Salir"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:107
-msgctxt "@action:inmenu menubar:view"
-msgid "&Reset camera position"
-msgstr "&Restablecer posición de la cámara"
-
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
+msgctxt "@action:inmenu menubar:view"
+msgid "&3D View"
+msgstr "&Vista en 3D"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+msgctxt "@action:inmenu menubar:view"
+msgid "&Front View"
+msgstr "&Vista frontal"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
+msgctxt "@action:inmenu menubar:view"
+msgid "&Top View"
+msgstr "&Vista superior"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
+msgctxt "@action:inmenu menubar:view"
+msgid "&Left Side View"
+msgstr "&Vista del lado izquierdo"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+msgctxt "@action:inmenu menubar:view"
+msgid "&Right Side View"
+msgstr "&Vista del lado derecho"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Configurar Cura..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "&Agregar impresora..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
-msgstr "Adm&inistrar impresoras ..."
+msgstr "Adm&inistrar impresoras..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Administrar materiales..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Actualizar perfil con ajustes o sobrescrituras actuales"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:150
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Descartar cambios actuales"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Crear perfil a partir de ajustes o sobrescrituras actuales..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Administrar perfiles..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Mostrar &documentación en línea"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "Informar de un &error"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:191
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
msgid "&About..."
msgstr "&Acerca de..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
msgid "Delete &Selected Model"
msgid_plural "Delete &Selected Models"
msgstr[0] "Eliminar modelo &seleccionado"
msgstr[1] "Eliminar modelos &seleccionados"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Centrar modelo seleccionado"
msgstr[1] "Centrar modelos seleccionados"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Multiplicar modelo seleccionado"
msgstr[1] "Multiplicar modelos seleccionados"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Eliminar modelo"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "Ce&ntrar modelo en plataforma"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "A&grupar modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:250
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Desagrupar modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:260
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "Co&mbinar modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "&Multiplicar modelo..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
msgid "&Select All Models"
msgstr "&Seleccionar todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:287
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
msgid "&Clear Build Plate"
msgstr "&Borrar placa de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:297
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
msgctxt "@action:inmenu menubar:file"
msgid "Re&load All Models"
msgstr "&Recargar todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:306
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
+msgctxt "@action:inmenu menubar:edit"
+msgid "Arrange All Models To All Build Plates"
+msgstr "Organizar todos los modelos en todas las placas de impresión"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Organizar todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Organizar selección"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:321
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Restablecer las posiciones de todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:328
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model &Transformations"
msgstr "Restablecer las &transformaciones de todos los modelos"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Abrir archivo(s)..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:343
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Nuevo proyecto..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "&Mostrar registro del motor..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Mostrar carpeta de configuración"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:365
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Configurar visibilidad de los ajustes..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse plugins..."
msgstr "Examinar complementos..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:379
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:menu"
msgid "Installed plugins..."
msgstr "Complementos instalados..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:28
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:438
+msgctxt "@action:inmenu menubar:view"
+msgid "Expand/Collapse Sidebar"
+msgstr "Expandir/contraer barra lateral"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Cargue un modelo en 3D"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:34
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Preparado para segmentar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Segmentando..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Listo para %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
-msgctxt "@label:PrintjobStatus"
-msgid "Unable to Slice"
-msgstr "No se puede segmentar."
-
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
msgctxt "@label:PrintjobStatus"
-msgid "Slicing unavailable"
+msgid "Unable to Slice"
msgstr "No se puede segmentar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
+msgctxt "@label:PrintjobStatus"
+msgid "Slicing unavailable"
+msgstr "Segmentación no disponible"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Slice current printjob"
+msgstr "Fragmentar trabajo de impresión actual"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Cancel slicing process"
+msgstr "Cancelar proceso de fragmentación"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Preparar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Cancelar"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:302
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Seleccione el dispositivo de salida activo"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:696
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Abrir archivo(s)"
@@ -3749,114 +4122,114 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:81
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&Archivo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:98
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
msgctxt "@action:inmenu menubar:file"
msgid "&Save Selection to File"
msgstr "Guardar &selección en archivo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
msgctxt "@title:menu menubar:file"
msgid "Save &As..."
msgstr "Guardar &como..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
msgctxt "@title:menu menubar:file"
-msgid "Save project"
-msgstr "Guardar proyecto"
+msgid "Save &Project..."
+msgstr "Guardar &proyecto..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Edición"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:158
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
msgctxt "@title:menu"
msgid "&View"
msgstr "&Ver"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
msgctxt "@title:menu"
msgid "&Settings"
msgstr "A&justes"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
msgctxt "@title:menu menubar:toplevel"
msgid "&Printer"
msgstr "&Impresora"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:175
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:187
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:208
msgctxt "@title:menu"
msgid "&Material"
msgstr "&Material"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:176
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:197
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:209
msgctxt "@title:menu"
msgid "&Profile"
msgstr "&Perfil"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:180
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:201
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Definir como extrusor activo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:219
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "E&xtensiones"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:232
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:253
msgctxt "@title:menu menubar:toplevel"
msgid "P&lugins"
msgstr "&Complementos"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:261
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "Pre&ferencias"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:269
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "A&yuda"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:330
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:351
msgctxt "@action:button"
msgid "Open File"
msgstr "Abrir archivo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:442
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:512
msgctxt "@title:tab"
msgid "Settings"
msgstr "Ajustes"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:478
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:554
msgctxt "@title:window"
msgid "New project"
msgstr "Nuevo proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:479
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:555
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "¿Está seguro de que desea iniciar un nuevo proyecto? Esto borrará la placa de impresión y cualquier ajuste no guardado."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:721
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:797
msgctxt "@window:title"
msgid "Install Plugin"
msgstr "Instalar complemento"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:728
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:804
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Abrir archivo(s)"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:731
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:807
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Hemos encontrado uno o más archivos de GCode entre los archivos que ha seleccionado. Solo puede abrir los archivos GCode de uno en uno. Si desea abrir un archivo GCode, seleccione solo uno."
@@ -3881,97 +4254,82 @@ msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "No mostrar resumen de proyecto al guardar de nuevo"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
-msgctxt "@action:button"
-msgid "Save"
-msgstr "Guardar"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:74
-msgctxt "@title:tab"
-msgid "Prepare"
-msgstr "Preparar"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:100
-msgctxt "@title:tab"
-msgid "Monitor"
-msgstr "Supervisar"
-
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
msgctxt "@label"
msgid "Layer Height"
msgstr "Altura de capa"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:323
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:345
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr "Hay un perfil personalizado activado en este momento. Para habilitar el control deslizante de calidad, seleccione un perfil de calidad predeterminado en la pestaña Personalizado."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:362
msgctxt "@label"
msgid "Print Speed"
msgstr "Velocidad de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:374
msgctxt "@label"
msgid "Slower"
msgstr "Más lento"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:385
msgctxt "@label"
msgid "Faster"
msgstr "Más rápido"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:388
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:423
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "Ha modificado algunos ajustes del perfil. Si desea cambiarlos, hágalo en el modo personalizado."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:413
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:446
msgctxt "@label"
msgid "Infill"
msgstr "Relleno"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:633
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:668
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Un relleno gradual aumentará gradualmente la cantidad de relleno hacia arriba."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:645
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:680
msgctxt "@label"
msgid "Enable gradual"
msgstr "Habilitar gradual"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:712
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:747
msgctxt "@label"
msgid "Generate Support"
msgstr "Generar soporte"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:746
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:781
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Generar estructuras para soportar piezas del modelo que tengan voladizos. Sin estas estructuras, estas piezas se romperían durante la impresión."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:764
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:799
msgctxt "@label"
msgid "Support Extruder"
msgstr "Extrusor del soporte"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:816
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:851
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Seleccione qué extrusor se utilizará como soporte. Esta opción formará estructuras de soporte por debajo del modelo para evitar que éste se combe o la impresión se haga en el aire."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:839
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:874
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Adherencia de la placa de impresión"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:894
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:929
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "Habilita la impresión de un borde o una balsa. Esta opción agregará un área plana alrededor del objeto, que es fácil de cortar después."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:934
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:969
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "¿Necesita ayuda para mejorar sus impresiones? Lea las Guías de solución de problemas de Ultimaker"
@@ -3988,17 +4346,22 @@ msgctxt "@title:window"
msgid "Open project file"
msgstr "Abrir archivo de proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:93
msgctxt "@text:window"
msgid "This is a Cura project file. Would you like to open it as a project or import the models from it?"
msgstr "Este es un archivo de proyecto Cura. ¿Le gustaría abrirlo como un proyecto o importar sus modelos?"
#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:103
+msgctxt "@text:window"
+msgid "Remember my choice"
+msgstr "Recordar mi selección"
+
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
msgctxt "@action:button"
msgid "Open as project"
msgstr "Abrir como proyecto"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:131
msgctxt "@action:button"
msgid "Import models"
msgstr "Importar modelos"
@@ -4008,21 +4371,36 @@ msgctxt "@title:window"
msgid "Engine Log"
msgstr "Registro del motor"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:245
msgctxt "@label"
msgid "Material"
msgstr "Material"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:349
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:352
msgctxt "@label"
-msgid "Check compatibility"
-msgstr "Comprobar compatibilidad"
+msgid "Check compatibility"
+msgstr "Comprobar compatibilidad"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:369
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Haga clic para comprobar la compatibilidad de los materiales en Utimaker.com."
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:211
+msgctxt "@option:check"
+msgid "See only current build plate"
+msgstr "Ver solo placa de impresión actual"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:227
+msgctxt "@action:button"
+msgid "Arrange to all build plates"
+msgstr "Organizar todas las placas de impresión"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:247
+msgctxt "@action:button"
+msgid "Arrange current build plate"
+msgstr "Organizar placa de impresión actual"
+
#: MachineSettingsAction/plugin.json
msgctxt "description"
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc)"
@@ -4113,6 +4491,26 @@ msgctxt "name"
msgid "USB printing"
msgstr "Impresión USB"
+#: PrepareStage/plugin.json
+msgctxt "description"
+msgid "Provides a prepare stage in Cura."
+msgstr "Proporciona una fase de preparación en Cura."
+
+#: PrepareStage/plugin.json
+msgctxt "name"
+msgid "Prepare Stage"
+msgstr "Fase de preparación"
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "description"
+msgid "Provides an edit window for direct script editing."
+msgstr "Proporciona una ventana de edición para la edición directa de secuencias de comandos."
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "name"
+msgid "Live scripting tool"
+msgstr "Herramienta de secuencia de comandos en directo"
+
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4133,6 +4531,16 @@ msgctxt "name"
msgid "UM3 Network Connection"
msgstr "Conexión de red UM3"
+#: MonitorStage/plugin.json
+msgctxt "description"
+msgid "Provides a monitor stage in Cura."
+msgstr "Proporciona una fase de supervisión en Cura."
+
+#: MonitorStage/plugin.json
+msgctxt "name"
+msgid "Monitor Stage"
+msgstr "Fase de supervisión"
+
#: FirmwareUpdateChecker/plugin.json
msgctxt "description"
msgid "Checks for firmware updates."
@@ -4145,8 +4553,8 @@ msgstr "Buscador de actualizaciones de firmware"
#: CuraSolidWorksPlugin/plugin.json
msgctxt "description"
-msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
-msgstr "Permite abrir ciertos archivos con el propio SolidWorks que, a continuación, puede convertirse y cargarse en Cura."
+msgid "Gives you the possibility to open certain files using SolidWorks itself. Conversion is done by this plugin and additional optimizations."
+msgstr "Permite abrir determinados archivos con el propio SolidWorks. La conversión se lleva a cabo mediante este complemento y optimizaciones adicionales."
#: CuraSolidWorksPlugin/plugin.json
msgctxt "name"
@@ -4213,6 +4621,16 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Lector de perfiles antiguos de Cura"
+#: CuraBlenderPlugin/plugin.json
+msgctxt "description"
+msgid "Helps to open Blender files directly in Cura."
+msgstr "Ayuda a abrir archivos de Blender directamente en Cura."
+
+#: CuraBlenderPlugin/plugin.json
+msgctxt "name"
+msgid "Blender Integration (experimental)"
+msgstr "Integración de Blender (experimental)"
+
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4316,7 +4734,7 @@ msgstr "Herramienta de ajustes por modelo"
#: cura-siemensnx-plugin/plugin.json
msgctxt "description"
msgid "Helps you to install an 'export to Cura' button in Siemens NX."
-msgstr "Ayuda a instalar el botón para exportar a Cura en in Siemens NX."
+msgstr "Ayuda a instalar el botón para exportar a Cura en Siemens NX."
#: cura-siemensnx-plugin/plugin.json
msgctxt "name"
@@ -4373,6 +4791,16 @@ msgctxt "name"
msgid "Cura Profile Writer"
msgstr "Escritor de perfiles de Cura"
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "description"
+msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+msgstr "Permite a los fabricantes de material crear nuevos perfiles de material y calidad mediante una IU integrada."
+
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "name"
+msgid "Print Profile Assistant"
+msgstr "Imprimir asistente del perfil"
+
#: 3MFWriter/plugin.json
msgctxt "description"
msgid "Provides support for writing 3MF files."
@@ -4413,6 +4841,156 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Lector de perfiles de Cura"
+#~ msgctxt "@label"
+#~ msgid "Unknown"
+#~ msgstr "Desconocido"
+
+#~ msgctxt "@info:status"
+#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
+#~ msgstr "Se han producido varios errores al abrir el archivo de SolidWorks. Compruebe que el archivo se puede abrir correctamente en SolidWorks."
+
+#~ msgctxt "@info:status"
+#~ msgid "Error while starting %s!"
+#~ msgstr "Error al iniciar %s"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Simulation view"
+#~ msgstr "Vista de simulación"
+
+#~ msgctxt "@info"
+#~ msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
+#~ msgstr "Cura recopila de forma anónima información de la segmentación. Puede desactivar esta opción en las preferencias."
+
+#~ msgctxt "@action:button"
+#~ msgid "Dismiss"
+#~ msgstr "Descartar"
+
+#~ msgctxt "@menuitem"
+#~ msgid "Global"
+#~ msgstr "Global"
+
+#~ msgctxt "@label crash message"
+#~ msgid ""
+#~ "
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+#~ "
Please use the \"Send report\" button to post a bug report automatically to our servers
\n"
+#~ " "
+#~ msgstr ""
+#~ "
Se ha producido una excepción fatal. Envíenos este informe de errores para que podamos solucionar el problema.
\n"
+#~ "
Utilice el botón «Enviar informe» para publicar automáticamente un informe de errores en nuestros servidores.
\n"
+#~ " "
+
+#~ msgctxt "@label Cura version"
+#~ msgid "Cura version: {version} "
+#~ msgstr "Versión de Cura: {version} "
+
+#~ msgctxt "@label Platform"
+#~ msgid "Platform: {platform} "
+#~ msgstr "Platforma: {platform} "
+
+#~ msgctxt "@label Qt version"
+#~ msgid "Qt version: {qt} "
+#~ msgstr "Versión de Qt: {qt} "
+
+#~ msgctxt "@label PyQt version"
+#~ msgid "PyQt version: {pyqt} "
+#~ msgstr "Versión de PyQt: {pyqt} "
+
+#~ msgctxt "@label OpenGL"
+#~ msgid "OpenGL: {opengl} "
+#~ msgstr "OpenGL: {opengl} "
+
+#~ msgctxt "@title:groupbox"
+#~ msgid "Exception traceback"
+#~ msgstr "Rastreabilidad de excepciones"
+
+#~ msgctxt "@label"
+#~ msgid "Material diameter"
+#~ msgstr "Diámetro del material"
+
+#~ msgctxt "@label"
+#~ msgid "Ultimaker 3"
+#~ msgstr "Ultimaker 3"
+
+#~ msgctxt "@label"
+#~ msgid "Ultimaker 3 Extended"
+#~ msgstr "Ultimaker 3 Extended"
+
+#~ msgctxt "@title:window"
+#~ msgid "Cura SolidWorks Plugin Configuration"
+#~ msgstr "Configuración de complementos Cura SolidWorks"
+
+#~ msgctxt "@action:label"
+#~ msgid "Default quality of the exported STL:"
+#~ msgstr "Calidad predeterminada del STL exportado:"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always ask"
+#~ msgstr "Preguntar siempre"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always use Fine quality"
+#~ msgstr "Usar siempre calidad fina"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always use Coarse quality"
+#~ msgstr "Usar siempre calidad gruesa"
+
+#~ msgctxt "@title:window"
+#~ msgid "Import SolidWorks File as STL..."
+#~ msgstr "Importar el archivo SolidWorks como STL..."
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Quality of the Exported STL"
+#~ msgstr "Calidad del STL exportado"
+
+#~ msgctxt "@action:label"
+#~ msgid "Quality"
+#~ msgstr "Calidad"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Coarse"
+#~ msgstr "Gruesa"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Fine"
+#~ msgstr "Fina"
+
+#~ msgctxt "@"
+#~ msgid "No Profile Available"
+#~ msgstr "No hay perfiles disponibles."
+
+#~ msgctxt "@label"
+#~ msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
+#~ msgstr "Este ajuste siempre se comparte entre extrusores. Si lo modifica, modificará el valor de todos los extrusores."
+
+#~ msgctxt "@tooltip"
+#~ msgid "Time specification
"
+#~ msgstr "Especificación de tiempo
"
+
+#~ msgctxt "@action:inmenu menubar:view"
+#~ msgid "&Reset camera position"
+#~ msgstr "&Restablecer posición de la cámara"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save project"
+#~ msgstr "Guardar proyecto"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Prepare"
+#~ msgstr "Preparar"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Monitor"
+#~ msgstr "Supervisar"
+
+#~ msgctxt "@label"
+#~ msgid "Check compatibility"
+#~ msgstr "Comprobar compatibilidad"
+
+#~ msgctxt "description"
+#~ msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
+#~ msgstr "Permite abrir ciertos archivos con el propio SolidWorks que, a continuación, puede convertirse y cargarse en Cura."
+
#~ msgctxt "@label:status"
#~ msgid "Blocked"
#~ msgstr "Deshabilitada"
@@ -4433,13 +5011,9 @@ msgstr "Lector de perfiles de Cura"
#~ msgid "To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware regularly. This can be done on the {machine_name} (when connected to the network) or via USB."
#~ msgstr "Para garantizar que su {machine_name} disponga de las prestaciones más recientes, se recomienda actualizar el firmware con regularidad. Esto se puede hacer en la {machine_name} (cuando esté conectada a la red) o vía USB."
-msgctxt "@item:inlistbox"
-msgid "Layer view"
-msgstr "Vista de capas"
-
-msgctxt "@info:title"
-msgid "Layer View"
-msgstr "Vista de capas"
+#~ msgctxt "@info:title"
+#~ msgid "Layer View"
+#~ msgstr "Vista de capas"
#~ msgctxt "@menuitem"
#~ msgid "Browse plugins"
@@ -4541,9 +5115,9 @@ msgstr "Vista de capas"
#~ msgid "Provides the Layer view."
#~ msgstr "Proporciona la vista de capas."
-msgctxt "name"
-msgid "Layer View"
-msgstr "Vista de capas"
+#~ msgctxt "name"
+#~ msgid "Layer View"
+#~ msgstr "Vista de capas"
#~ msgctxt "@item:inlistbox"
#~ msgid "X-Ray"
@@ -4864,9 +5438,9 @@ msgstr "Vista de capas"
#~ msgid "Provides support for importing profiles from g-code files."
#~ msgstr "Proporciona asistencia para la importación de perfiles de archivos GCode."
-msgctxt "@label"
-msgid "Layer View"
-msgstr "Vista de capas"
+#~ msgctxt "@label"
+#~ msgid "Layer View"
+#~ msgstr "Vista de capas"
#~ msgctxt "@info:whatsthis"
#~ msgid "Provides the Layer view."
diff --git a/resources/i18n/es_ES/fdmextruder.def.json.po b/resources/i18n/es_ES/fdmextruder.def.json.po
index 1d0986f4e1..e895c053e3 100644
--- a/resources/i18n/es_ES/fdmextruder.def.json.po
+++ b/resources/i18n/es_ES/fdmextruder.def.json.po
@@ -2,12 +2,12 @@
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek , 2017.
-#
+#
msgid ""
msgstr ""
"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: 2017-11-30 13:05+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: Spanish\n"
diff --git a/resources/i18n/es_ES/fdmprinter.def.json.po b/resources/i18n/es_ES/fdmprinter.def.json.po
index d3515844e3..98c2d1da7f 100644
--- a/resources/i18n/es_ES/fdmprinter.def.json.po
+++ b/resources/i18n/es_ES/fdmprinter.def.json.po
@@ -1,20 +1,21 @@
# Cura JSON setting files
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
-"PO-Revision-Date: 2017-11-30 13:05+0100\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
+"PO-Revision-Date: 2018-02-12 13:40+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.6\n"
#: fdmprinter.def.json
msgctxt "machine_settings label"
@@ -56,7 +57,9 @@ msgctxt "machine_start_gcode description"
msgid ""
"Gcode commands to be executed at the very start - separated by \n"
"."
-msgstr "Los comandos de Gcode que se ejecutarán justo al inicio - separados por \n."
+msgstr ""
+"Los comandos de Gcode que se ejecutarán justo al inicio - separados por \n"
+"."
#: fdmprinter.def.json
msgctxt "machine_end_gcode label"
@@ -68,7 +71,9 @@ msgctxt "machine_end_gcode description"
msgid ""
"Gcode commands to be executed at the very end - separated by \n"
"."
-msgstr "Los comandos de Gcode que se ejecutarán justo al final - separados por \n."
+msgstr ""
+"Los comandos de Gcode que se ejecutarán justo al final - separados por \n"
+"."
#: fdmprinter.def.json
msgctxt "material_guid label"
@@ -93,7 +98,7 @@ msgstr "Elija si desea escribir un comando para esperar a que la temperatura de
#: fdmprinter.def.json
msgctxt "material_print_temp_wait label"
msgid "Wait for Nozzle Heatup"
-msgstr "Esperar a la que la tobera se caliente"
+msgstr "Esperar a que la tobera se caliente"
#: fdmprinter.def.json
msgctxt "material_print_temp_wait description"
@@ -345,6 +350,16 @@ msgctxt "machine_gcode_flavor option Repetier"
msgid "Repetier"
msgstr "Repetier"
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract label"
+msgid "Firmware Retraction"
+msgstr "Retracción de firmware"
+
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract description"
+msgid "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material."
+msgstr "Utilizar o no los comandos de retracción de firmware (G10/G11) en lugar de utilizar la propiedad E en comandos G1 para retraer el material."
+
#: fdmprinter.def.json
msgctxt "machine_disallowed_areas label"
msgid "Disallowed areas"
@@ -398,12 +413,12 @@ msgstr "Diferencia de altura entre la punta de la tobera y el sistema del puente
#: fdmprinter.def.json
msgctxt "machine_nozzle_id label"
msgid "Nozzle ID"
-msgstr "Id. de la tobera"
+msgstr "ID de la tobera"
#: fdmprinter.def.json
msgctxt "machine_nozzle_id description"
msgid "The nozzle ID for an extruder train, such as \"AA 0.4\" and \"BB 0.8\"."
-msgstr "Id. de la tobera de un tren extrusor, como \"AA 0.4\" y \"BB 0.8\"."
+msgstr "ID de la tobera de un tren extrusor, como \"AA 0.4\" y \"BB 0.8\"."
#: fdmprinter.def.json
msgctxt "machine_nozzle_size label"
@@ -605,31 +620,6 @@ msgctxt "layer_height_0 description"
msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier."
msgstr "Altura de capa inicial en mm. Una capa inicial más gruesa se adhiere a la placa de impresión con mayor facilidad."
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance label"
-msgid "Slicing Tolerance"
-msgstr "Tolerancia de segmentación"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance description"
-msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
-msgstr "Cómo segmentar capas con superficies diagonales. Las áreas de una capa se pueden crear según el punto en el que el centro de esta intersecta con la superficie (Media). Las capas también pueden tener áreas comprendidas en el volumen a lo largo de la altura de la capa (Exclusiva) o una capa puede tener áreas comprendidas en cualquier lugar de la capa (Inclusiva). Las capas exclusivas tienen un mayor nivel de detalle, mientras que las inclusivas son las que mejor se ajustan y las medias las que tardan menos en procesarse."
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option middle"
-msgid "Middle"
-msgstr "Media"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option exclusive"
-msgid "Exclusive"
-msgstr "Exclusiva"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option inclusive"
-msgid "Inclusive"
-msgstr "Inclusiva"
-
#: fdmprinter.def.json
msgctxt "line_width label"
msgid "Line Width"
@@ -670,16 +660,6 @@ msgctxt "wall_line_width_x description"
msgid "Width of a single wall line for all wall lines except the outermost one."
msgstr "Ancho de una sola línea de pared para todas las líneas de pared excepto la más externa."
-#: fdmprinter.def.json
-msgctxt "roofing_line_width label"
-msgid "Top Surface Skin Line Width"
-msgstr "Ancho de línea de la superficie superior del forro"
-
-#: fdmprinter.def.json
-msgctxt "roofing_line_width description"
-msgid "Width of a single line of the areas at the top of the print."
-msgstr "Ancho de una sola línea de las áreas superiores de la impresión."
-
#: fdmprinter.def.json
msgctxt "skin_line_width label"
msgid "Top/Bottom Line Width"
@@ -860,41 +840,6 @@ msgctxt "roofing_layer_count description"
msgid "The number of top most skin layers. Usually only one top most layer is sufficient to generate higher quality top surfaces."
msgstr "El número de capas del nivel superior del forro. Normalmente es suficiente con una sola capa para generar superficies superiores con mayor calidad."
-#: fdmprinter.def.json
-msgctxt "roofing_pattern label"
-msgid "Top Surface Skin Pattern"
-msgstr "Patrón de la superficie superior del forro"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern description"
-msgid "The pattern of the top most layers."
-msgstr "El patrón de las capas de nivel superior."
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option lines"
-msgid "Lines"
-msgstr "Líneas"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option concentric"
-msgid "Concentric"
-msgstr "Concéntrico"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option zigzag"
-msgid "Zig Zag"
-msgstr "Zigzag"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles label"
-msgid "Top Surface Skin Line Directions"
-msgstr "Direcciones de línea de la superficie superior del forro"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles description"
-msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
-msgstr "Una lista de los valores enteros de las direcciones de línea si las capas de la superficie superior del forro utilizan líneas o el patrón en zigzag. Los elementos de esta lista se utilizan de forma secuencial a medida que las capas se utilizan y, cuando se alcanza el final, la lista vuelve a comenzar desde el principio. Los elementos de la lista están separados por comas y toda la lista aparece entre corchetes. El valor predeterminado es una lista vacía que utiliza los ángulos predeterminados típicos (45 y 135 grados)."
-
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr label"
msgid "Top/Bottom Extruder"
@@ -1025,6 +970,16 @@ msgctxt "wall_0_inset description"
msgid "Inset applied to the path of the outer wall. If the outer wall is smaller than the nozzle, and printed after the inner walls, use this offset to get the hole in the nozzle to overlap with the inner walls instead of the outside of the model."
msgstr "Entrante aplicado a la trayectoria de la pared exterior. Si la pared exterior es más pequeña que la tobera y se imprime a continuación de las paredes interiores, utilice este valor de desplazamiento para hacer que el agujero de la tobera se superponga a las paredes interiores del modelo en lugar de a las exteriores."
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order label"
+msgid "Optimize Wall Printing Order"
+msgstr "Optimizar el orden de impresión de paredes"
+
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order description"
+msgid "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."
+msgstr "Optimizar el orden en el que se imprimen las paredes a fin de reducir el número de retracciones y la distancia recorrida. La mayoría de los componentes se beneficiarán si este ajuste está habilitado pero, en algunos casos, se puede tardar más, por lo que deben compararse las previsiones de tiempo de impresión con y sin optimización."
+
#: fdmprinter.def.json
msgctxt "outer_inset_first label"
msgid "Outer Before Inner Walls"
@@ -1053,7 +1008,7 @@ msgstr "Compensar superposiciones de pared"
#: fdmprinter.def.json
msgctxt "travel_compensate_overlapping_walls_enabled description"
msgid "Compensate the flow for parts of a wall being printed where there is already a wall in place."
-msgstr "Compensa el flujo en partes de una pared que se están imprimiendo dónde ya hay una pared."
+msgstr "Compensa el flujo en partes de una pared que se están imprimiendo donde ya hay una pared."
#: fdmprinter.def.json
msgctxt "travel_compensate_overlapping_walls_0_enabled label"
@@ -1095,6 +1050,16 @@ msgctxt "fill_perimeter_gaps option everywhere"
msgid "Everywhere"
msgstr "En todas partes"
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps label"
+msgid "Filter Out Tiny Gaps"
+msgstr "Filtrar pequeños huecos"
+
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps description"
+msgid "Filter out tiny gaps to reduce blobs on outside of model."
+msgstr "Filtrar pequeños huecos para reducir las gotas en la parte externa del modelo."
+
#: fdmprinter.def.json
msgctxt "fill_outline_gaps label"
msgid "Print Thin Walls"
@@ -1383,7 +1348,7 @@ msgstr "Patrón de relleno"
#: fdmprinter.def.json
msgctxt "infill_pattern description"
msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
-msgstr "Patrón del material de relleno de la impresión. El relleno de línea y zigzag cambia de dirección en capas alternas, con lo que se reduce el coste del material. Los patrones de rejilla, triángulo, trihexagonal, cúbico, de octeto, cúbico bitruncado y transversal y concéntrico se imprimen en todas las capas por completo. El relleno cúbico, cúbico bitruncado y de octeto cambian en cada capa para proporcionar una distribución de fuerza equitativa en cada dirección."
+msgstr "Patrón del material de relleno de la impresión. El relleno de línea y zigzag cambia de dirección en capas alternas, con lo que se reduce el coste del material. Los patrones de rejilla, triángulo, trihexagonal, cúbico, octeto, cúbico bitruncado y transversal y concéntrico se imprimen en todas las capas por completo. El relleno cúbico, cúbico bitruncado y octeto cambian en cada capa para proporcionar una distribución de fuerza equitativa en cada dirección."
#: fdmprinter.def.json
msgctxt "infill_pattern option grid"
@@ -1477,8 +1442,8 @@ msgstr "Desplazamiento del relleno sobre el eje X"
#: fdmprinter.def.json
msgctxt "infill_offset_x description"
-msgid "The infill pattern is offset this distance along the X axis."
-msgstr "El patrón de relleno se desplaza esta distancia a lo largo del eje X."
+msgid "The infill pattern is moved this distance along the X axis."
+msgstr "El patrón de relleno se mueve esta distancia a lo largo del eje X."
#: fdmprinter.def.json
msgctxt "infill_offset_y label"
@@ -1487,8 +1452,8 @@ msgstr "Desplazamiento del relleno sobre el eje X"
#: fdmprinter.def.json
msgctxt "infill_offset_y description"
-msgid "The infill pattern is offset this distance along the Y axis."
-msgstr "El patrón de relleno se desplaza esta distancia a lo largo del eje Y."
+msgid "The infill pattern is moved this distance along the Y axis."
+msgstr "El patrón de relleno se mueve esta distancia a lo largo del eje Y."
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
@@ -1507,8 +1472,8 @@ msgstr "Porcentaje de superposición del relleno"
#: fdmprinter.def.json
msgctxt "infill_overlap description"
-msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
-msgstr "Cantidad de superposición entre el relleno y las paredes. Una ligera superposición permite que las paredes conecten firmemente con el relleno."
+msgid "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill."
+msgstr "La cantidad de superposición entre el relleno y las paredes son un porcentaje del ancho de la línea de relleno. Una ligera superposición permite que las paredes estén firmemente unidas al relleno."
#: fdmprinter.def.json
msgctxt "infill_overlap_mm label"
@@ -1527,8 +1492,8 @@ msgstr "Porcentaje de superposición del forro"
#: fdmprinter.def.json
msgctxt "skin_overlap description"
-msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
-msgstr "Cantidad de superposición entre el forro y las paredes como porcentaje del ancho de línea. Una ligera superposición permite que las paredes conecten firmemente con el forro. Este es el porcentaje de la media de los anchos de las líneas del forro y la pared más profunda."
+msgid "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+msgstr "La cantidad de superposición entre el forro y las paredes son un porcentaje del ancho de la línea de forro. Una ligera superposición permite que las paredes estén firmemente unidas al forro. Este es el porcentaje de la media de los anchos de las líneas del forro y la pared más profunda."
#: fdmprinter.def.json
msgctxt "skin_overlap_mm label"
@@ -1690,16 +1655,6 @@ msgctxt "material description"
msgid "Material"
msgstr "Material"
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature label"
-msgid "Auto Temperature"
-msgstr "Temperatura automática"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature description"
-msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
-msgstr "Cambia automáticamente la temperatura para cada capa con la velocidad media de flujo de esa capa."
-
#: fdmprinter.def.json
msgctxt "default_material_print_temperature label"
msgid "Default Printing Temperature"
@@ -1750,16 +1705,6 @@ msgctxt "material_final_print_temperature description"
msgid "The temperature to which to already start cooling down just before the end of printing."
msgstr "La temperatura a la que se puede empezar a enfriar justo antes de finalizar la impresión."
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph label"
-msgid "Flow Temperature Graph"
-msgstr "Gráfico de flujo y temperatura"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph description"
-msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
-msgstr "Datos que vinculan el flujo de materiales (en 3 mm por segundo) a la temperatura (grados centígrados)."
-
#: fdmprinter.def.json
msgctxt "material_extrusion_cool_down_speed label"
msgid "Extrusion Cool Down Speed Modifier"
@@ -1777,8 +1722,8 @@ msgstr "Temperatura de la placa de impresión"
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
-msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
-msgstr "Temperatura de la placa de impresión una vez caliente. Si el valor es 0, la plataforma no se calentará en esta impresión."
+msgid "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted."
+msgstr "La temperatura utilizada para la placa de impresión caliente. Si el valor es 0, la temperatura de la plataforma no se ajustará."
#: fdmprinter.def.json
msgctxt "material_bed_temperature_layer_0 label"
@@ -1848,7 +1793,7 @@ msgstr "Retracción en el cambio de capa"
#: fdmprinter.def.json
msgctxt "retract_at_layer_change description"
msgid "Retract the filament when the nozzle is moving to the next layer."
-msgstr "Retrae el filamento cuando la tobera se mueve a la siguiente capa. "
+msgstr "Retrae el filamento cuando la tobera se mueve a la siguiente capa."
#: fdmprinter.def.json
msgctxt "retraction_amount label"
@@ -3450,6 +3395,16 @@ msgctxt "support_tower_roof_angle description"
msgid "The angle of a rooftop of a tower. A higher value results in pointed tower roofs, a lower value results in flattened tower roofs."
msgstr "Ángulo del techo superior de una torre. Un valor más alto da como resultado techos de torre en punta, un valor más bajo da como resultado techos de torre planos."
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down label"
+msgid "Drop Down Support Mesh"
+msgstr "Malla de soporte desplegable"
+
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down description"
+msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
+msgstr "Disponga un soporte en todas partes por debajo de la malla de soporte, para que no haya voladizo en la malla de soporte."
+
#: fdmprinter.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
@@ -3550,7 +3505,9 @@ msgctxt "skirt_gap description"
msgid ""
"The horizontal distance between the skirt and the first layer of the print.\n"
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
-msgstr "La distancia horizontal entre la falda y la primera capa de la impresión.\nSe trata de la distancia mínima. Múltiples líneas de falda se extenderán hacia el exterior a partir de esta distancia."
+msgstr ""
+"La distancia horizontal entre la falda y la primera capa de la impresión.\n"
+"Se trata de la distancia mínima. Múltiples líneas de falda se extenderán hacia el exterior a partir de esta distancia."
#: fdmprinter.def.json
msgctxt "skirt_brim_minimal_length label"
@@ -4082,16 +4039,6 @@ msgctxt "meshfix_keep_open_polygons description"
msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode."
msgstr "Normalmente, Cura intenta coser los pequeños agujeros de la malla y eliminar las partes de una capa con grandes agujeros. Al habilitar esta opción se mantienen aquellas partes que no puedan coserse. Esta opción se debe utilizar como una opción de último recurso cuando todo lo demás falla para producir un GCode adecuado."
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution label"
-msgid "Maximum Resolution"
-msgstr "Resolución máxima"
-
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution description"
-msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
-msgstr "El tamaño mínimo de un segmento de línea tras la segmentación. Si se aumenta, la resolución de la malla será menor. Esto puede permitir a la impresora mantener la velocidad que necesita para procesar GCode y aumentará la velocidad de segmentación al eliminar detalles de la malla que, de todas formas, no puede procesar."
-
#: fdmprinter.def.json
msgctxt "multiple_mesh_overlap label"
msgid "Merged Meshes Overlap"
@@ -4242,16 +4189,6 @@ msgctxt "support_mesh description"
msgid "Use this mesh to specify support areas. This can be used to generate support structure."
msgstr "Utilice esta malla para especificar las áreas de soporte. Esta opción puede utilizarse para generar estructuras de soporte."
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down label"
-msgid "Drop Down Support Mesh"
-msgstr "Malla de soporte desplegable"
-
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down description"
-msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
-msgstr "Disponga un soporte en todas partes por debajo de la malla de soporte, para que no haya voladizo en la malla de soporte."
-
#: fdmprinter.def.json
msgctxt "anti_overhang_mesh label"
msgid "Anti Overhang Mesh"
@@ -4328,14 +4265,194 @@ msgid "experimental!"
msgstr "Experimental"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order label"
-msgid "Optimize Wall Printing Order"
-msgstr "Optimizar el orden de impresión de paredes"
+msgctxt "support_tree_enable label"
+msgid "Tree Support"
+msgstr "Soporte en árbol"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order description"
-msgid "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."
-msgstr "Optimizar el orden en el que se imprimen las paredes a fin de reducir el número de retracciones y la distancia recorrida. La mayoría de los componentes se beneficiarán si este ajuste está habilitado pero, en algunos casos, se puede tardar más, por lo que deben compararse las previsiones de tiempo de impresión con y sin optimización."
+msgctxt "support_tree_enable description"
+msgid "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time."
+msgstr "Generar un soporte en forma de árbol con ramas que apoyan la impresión. Esto puede reducir el uso de material y el tiempo de impresión, pero aumenta considerablemente el tiempo de fragmentación."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle label"
+msgid "Tree Support Branch Angle"
+msgstr "Ángulo de las ramas del soporte en árbol"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle description"
+msgid "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."
+msgstr "El ángulo de las ramas. Utilice un ángulo inferior para que sean más verticales y estables. Utilice un ángulo superior para poder tener más alcance."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance label"
+msgid "Tree Support Branch Distance"
+msgstr "Distancia de las ramas del soporte en árbol"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance description"
+msgid "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove."
+msgstr "Qué separación deben tener las ramas cuando tocan el modelo. Reducir esta distancia ocasionará que el soporte en árbol toque el modelo en más puntos, produciendo mejor cobertura pero dificultando la tarea de retirar el soporte."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter label"
+msgid "Tree Support Branch Diameter"
+msgstr "Diámetro de las ramas del soporte en árbol"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter description"
+msgid "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this."
+msgstr "El diámetro de las ramas más finas del soporte en árbol. Cuanto más gruesas sean las ramas, más robustas serán. Las ramas que estén cerca de la base serán más gruesas que esto."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle label"
+msgid "Tree Support Branch Diameter Angle"
+msgstr "Ángulo de diámetro de las ramas del soporte en árbol"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle description"
+msgid "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support."
+msgstr "El ángulo del diámetro de las ramas es gradualmente más alto según se acercan a la base. Un ángulo de 0 ocasionará que las ramas tengan un grosor uniforme en toda su longitud. Un poco de ángulo puede aumentar la estabilidad del soporte en árbol."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution label"
+msgid "Tree Support Collision Resolution"
+msgstr "Resolución de colisión del soporte en árbol"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution description"
+msgid "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically."
+msgstr "Resolución para computar colisiones para evitar golpear el modelo. Establecer un ajuste bajo producirá árboles más precisos que producen fallos con menor frecuencia, pero aumenta significativamente el tiempo de fragmentación."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness label"
+msgid "Tree Support Wall Thickness"
+msgstr "Grosor de las paredes del soporte en árbol"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness description"
+msgid "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "El grosor de las paredes de las ramas del soporte en árbol. Imprimir paredes más gruesas llevará más tiempo pero no se caerán tan fácilmente."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count label"
+msgid "Tree Support Wall Line Count"
+msgstr "Recuento de líneas de pared del soporte en árbol"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count description"
+msgid "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "El número de las paredes de las ramas del soporte en árbol. Imprimir paredes más gruesas llevará más tiempo pero no se caerán tan fácilmente."
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance label"
+msgid "Slicing Tolerance"
+msgstr "Tolerancia de segmentación"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance description"
+msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
+msgstr "Cómo segmentar capas con superficies diagonales. Las áreas de una capa se pueden crear según el punto en el que el centro de esta intersecta con la superficie (Media). Las capas también pueden tener áreas comprendidas en el volumen a lo largo de la altura de la capa (Exclusiva) o una capa puede tener áreas comprendidas en cualquier lugar de la capa (Inclusiva). Las capas exclusivas tienen un mayor nivel de detalle, mientras que las inclusivas son las que mejor se ajustan y las medias las que tardan menos en procesarse."
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option middle"
+msgid "Middle"
+msgstr "Media"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option exclusive"
+msgid "Exclusive"
+msgstr "Exclusiva"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option inclusive"
+msgid "Inclusive"
+msgstr "Inclusiva"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width label"
+msgid "Top Surface Skin Line Width"
+msgstr "Ancho de línea de la superficie superior del forro"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width description"
+msgid "Width of a single line of the areas at the top of the print."
+msgstr "Ancho de una sola línea de las áreas superiores de la impresión."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern label"
+msgid "Top Surface Skin Pattern"
+msgstr "Patrón de la superficie superior del forro"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern description"
+msgid "The pattern of the top most layers."
+msgstr "El patrón de las capas de nivel superior."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option lines"
+msgid "Lines"
+msgstr "Líneas"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option concentric"
+msgid "Concentric"
+msgstr "Concéntrico"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option zigzag"
+msgid "Zig Zag"
+msgstr "Zigzag"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles label"
+msgid "Top Surface Skin Line Directions"
+msgstr "Direcciones de línea de la superficie superior del forro"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles description"
+msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
+msgstr "Una lista de los valores enteros de las direcciones de línea si las capas de la superficie superior del forro utilizan líneas o el patrón en zigzag. Los elementos de esta lista se utilizan de forma secuencial a medida que las capas se utilizan y, cuando se alcanza el final, la lista vuelve a comenzar desde el principio. Los elementos de la lista están separados por comas y toda la lista aparece entre corchetes. El valor predeterminado es una lista vacía que utiliza los ángulos predeterminados típicos (45 y 135 grados)."
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization label"
+msgid "Infill Travel Optimization"
+msgstr "Optimización del desplazamiento del relleno"
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization description"
+msgid "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased."
+msgstr "Cuando está habilitado, se optimiza el orden en el que se imprimen las líneas de relleno para reducir la distancia de desplazamiento. La reducción del tiempo de desplazamiento obtenido depende en gran parte del modelo que se está fragmentando, el patrón de relleno, la densidad, etc. Tenga en cuenta que, para algunos modelos que tienen pequeñas áreas de relleno, el tiempo para fragmentar el modelo se puede aumentar en gran medida."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature label"
+msgid "Auto Temperature"
+msgstr "Temperatura automática"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature description"
+msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
+msgstr "Cambia automáticamente la temperatura para cada capa con la velocidad media de flujo de esa capa."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph label"
+msgid "Flow Temperature Graph"
+msgstr "Gráfico de flujo y temperatura"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph description"
+msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
+msgstr "Datos que vinculan el flujo de materiales (en 3 mm por segundo) a la temperatura (grados centígrados)."
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution label"
+msgid "Maximum Resolution"
+msgstr "Resolución máxima"
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution description"
+msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
+msgstr "El tamaño mínimo de un segmento de línea tras la segmentación. Si se aumenta, la resolución de la malla será menor. Esto puede permitir a la impresora mantener la velocidad que necesita para procesar GCode y aumentará la velocidad de segmentación al eliminar detalles de la malla que, de todas formas, no puede procesar."
#: fdmprinter.def.json
msgctxt "support_skip_some_zags label"
@@ -4827,7 +4944,9 @@ msgctxt "wireframe_up_half_speed description"
msgid ""
"Distance of an upward move which is extruded with half speed.\n"
"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing."
-msgstr "Distancia de un movimiento ascendente que se extrude a media velocidad.\nEsto puede causar una mejor adherencia a las capas anteriores, aunque no calienta demasiado el material en esas capas. Solo se aplica a la impresión de alambre."
+msgstr ""
+"Distancia de un movimiento ascendente que se extrude a media velocidad.\n"
+"Esto puede causar una mejor adherencia a las capas anteriores, aunque no calienta demasiado el material en esas capas. Solo se aplica a la impresión de alambre."
#: fdmprinter.def.json
msgctxt "wireframe_top_jump label"
@@ -4934,6 +5053,46 @@ msgctxt "wireframe_nozzle_clearance description"
msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing."
msgstr "Distancia entre la tobera y líneas descendentes en horizontal. Cuanto mayor sea la holgura, menos pronunciado será el ángulo de las líneas descendentes en diagonal, lo que a su vez se traduce en menos conexiones ascendentes con la siguiente capa. Solo se aplica a la impresión de alambre."
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled label"
+msgid "Use adaptive layers"
+msgstr "Utilizar capas de adaptación"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled description"
+msgid "Adaptive layers computes the layer heights depending on the shape of the model."
+msgstr "Las capas de adaptación calculan las alturas de las capas dependiendo de la forma del modelo."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation label"
+msgid "Adaptive layers maximum variation"
+msgstr "Variación máxima de las capas de adaptación"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation description"
+msgid "The maximum allowed height different from the base layer height in mm."
+msgstr "La diferencia de altura máxima permitida en comparación con la altura de la capa base en mm."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step label"
+msgid "Adaptive layers variation step size"
+msgstr "Tamaño de pasos de variación de las capas de adaptación"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step description"
+msgid "The difference in height of the next layer height compared to the previous one."
+msgstr "La diferencia de altura de la siguiente altura de capa en comparación con la anterior."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold label"
+msgid "Adaptive layers threshold"
+msgstr "Umbral de las capas de adaptación"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold description"
+msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
+msgstr "Umbral para usar o no una capa más pequeña. Este número se compara con el curtido de la pendiente más empinada de una capa."
+
#: fdmprinter.def.json
msgctxt "command_line_settings label"
msgid "Command Line Settings"
@@ -4994,6 +5153,26 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Matriz de transformación que se aplicará al modelo cuando se cargue desde el archivo."
+#~ msgctxt "infill_offset_x description"
+#~ msgid "The infill pattern is offset this distance along the X axis."
+#~ msgstr "El patrón de relleno se desplaza esta distancia a lo largo del eje X."
+
+#~ msgctxt "infill_offset_y description"
+#~ msgid "The infill pattern is offset this distance along the Y axis."
+#~ msgstr "El patrón de relleno se desplaza esta distancia a lo largo del eje Y."
+
+#~ msgctxt "infill_overlap description"
+#~ msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
+#~ msgstr "Cantidad de superposición entre el relleno y las paredes. Una ligera superposición permite que las paredes conecten firmemente con el relleno."
+
+#~ msgctxt "skin_overlap description"
+#~ msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+#~ msgstr "Cantidad de superposición entre el forro y las paredes como porcentaje del ancho de línea. Una ligera superposición permite que las paredes conecten firmemente con el forro. Este es el porcentaje de la media de los anchos de las líneas del forro y la pared más profunda."
+
+#~ msgctxt "material_bed_temperature description"
+#~ msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
+#~ msgstr "Temperatura de la placa de impresión una vez caliente. Si el valor es 0, la plataforma no se calentará en esta impresión."
+
#~ msgctxt "wall_x_extruder_nr label"
#~ msgid "Inner Walls Extruder"
#~ msgstr "Extrusor de paredes interiores"
diff --git a/resources/i18n/fdmextruder.def.json.pot b/resources/i18n/fdmextruder.def.json.pot
index af77729254..454d324874 100644
--- a/resources/i18n/fdmextruder.def.json.pot
+++ b/resources/i18n/fdmextruder.def.json.pot
@@ -1,11 +1,11 @@
# Cura JSON setting files
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
+# Ruben Dulek , 2018.
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.1\n"
+"Project-Id-Version: Cura 3.2\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
diff --git a/resources/i18n/fdmprinter.def.json.pot b/resources/i18n/fdmprinter.def.json.pot
index 9d5ef93fc3..126c02a207 100644
--- a/resources/i18n/fdmprinter.def.json.pot
+++ b/resources/i18n/fdmprinter.def.json.pot
@@ -1,11 +1,11 @@
# Cura JSON setting files
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
+# Ruben Dulek , 2018.
#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.1\n"
+"Project-Id-Version: Cura 3.2\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
@@ -377,6 +377,18 @@ msgctxt "machine_gcode_flavor option Repetier"
msgid "Repetier"
msgstr ""
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract label"
+msgid "Firmware Retraction"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract description"
+msgid ""
+"Whether to use firmware retract commands (G10/G11) instead of using the E "
+"property in G1 commands to retract the material."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "machine_disallowed_areas label"
msgid "Disallowed areas"
@@ -651,38 +663,6 @@ msgid ""
"adhesion to the build plate easier."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance label"
-msgid "Slicing Tolerance"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance description"
-msgid ""
-"How to slice layers with diagonal surfaces. The areas of a layer can be "
-"generated based on where the middle of the layer intersects the surface "
-"(Middle). Alternatively each layer can have the areas which fall inside of "
-"the volume throughout the height of the layer (Exclusive) or a layer has the "
-"areas which fall inside anywhere within the layer (Inclusive). Exclusive "
-"retains the most details, Inclusive makes for the best fit and Middle takes "
-"the least time to process."
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option middle"
-msgid "Middle"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option exclusive"
-msgid "Exclusive"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option inclusive"
-msgid "Inclusive"
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "line_width label"
msgid "Line Width"
@@ -729,16 +709,6 @@ msgid ""
"Width of a single wall line for all wall lines except the outermost one."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "roofing_line_width label"
-msgid "Top Surface Skin Line Width"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "roofing_line_width description"
-msgid "Width of a single line of the areas at the top of the print."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "skin_line_width label"
msgid "Top/Bottom Line Width"
@@ -937,47 +907,6 @@ msgid ""
"sufficient to generate higher quality top surfaces."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "roofing_pattern label"
-msgid "Top Surface Skin Pattern"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern description"
-msgid "The pattern of the top most layers."
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option lines"
-msgid "Lines"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option concentric"
-msgid "Concentric"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option zigzag"
-msgid "Zig Zag"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles label"
-msgid "Top Surface Skin Line Directions"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles description"
-msgid ""
-"A list of integer line directions to use when the top surface skin layers "
-"use the lines or zig zag pattern. Elements from the list are used "
-"sequentially as the layers progress and when the end of the list is reached, "
-"it starts at the beginning again. The list items are separated by commas and "
-"the whole list is contained in square brackets. Default is an empty list "
-"which means use the traditional default angles (45 and 135 degrees)."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr label"
msgid "Top/Bottom Extruder"
@@ -1130,6 +1059,20 @@ msgid ""
"outside of the model."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order label"
+msgid "Optimize Wall Printing Order"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order description"
+msgid ""
+"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."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "outer_inset_first label"
msgid "Outer Before Inner Walls"
@@ -1212,6 +1155,16 @@ msgctxt "fill_perimeter_gaps option everywhere"
msgid "Everywhere"
msgstr ""
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps label"
+msgid "Filter Out Tiny Gaps"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps description"
+msgid "Filter out tiny gaps to reduce blobs on outside of model."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "fill_outline_gaps label"
msgid "Print Thin Walls"
@@ -1656,7 +1609,7 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "infill_offset_x description"
-msgid "The infill pattern is offset this distance along the X axis."
+msgid "The infill pattern is moved this distance along the X axis."
msgstr ""
#: fdmprinter.def.json
@@ -1666,7 +1619,7 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "infill_offset_y description"
-msgid "The infill pattern is offset this distance along the Y axis."
+msgid "The infill pattern is moved this distance along the Y axis."
msgstr ""
#: fdmprinter.def.json
@@ -1691,8 +1644,9 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "infill_overlap description"
msgid ""
-"The amount of overlap between the infill and the walls. A slight overlap "
-"allows the walls to connect firmly to the infill."
+"The amount of overlap between the infill and the walls as a percentage of "
+"the infill line width. A slight overlap allows the walls to connect firmly "
+"to the infill."
msgstr ""
#: fdmprinter.def.json
@@ -1716,9 +1670,9 @@ msgstr ""
msgctxt "skin_overlap description"
msgid ""
"The amount of overlap between the skin and the walls as a percentage of the "
-"line width. A slight overlap allows the walls to connect firmly to the skin. "
-"This is a percentage of the average line widths of the skin lines and the "
-"innermost wall."
+"skin line width. A slight overlap allows the walls to connect firmly to the "
+"skin. This is a percentage of the average line widths of the skin lines and "
+"the innermost wall."
msgstr ""
#: fdmprinter.def.json
@@ -1927,18 +1881,6 @@ msgctxt "material description"
msgid "Material"
msgstr ""
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature label"
-msgid "Auto Temperature"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature description"
-msgid ""
-"Change the temperature for each layer automatically with the average flow "
-"speed of that layer."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "default_material_print_temperature label"
msgid "Default Printing Temperature"
@@ -1998,18 +1940,6 @@ msgid ""
"of printing."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph label"
-msgid "Flow Temperature Graph"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph description"
-msgid ""
-"Data linking material flow (in mm3 per second) to temperature (degrees "
-"Celsius)."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "material_extrusion_cool_down_speed label"
msgid "Extrusion Cool Down Speed Modifier"
@@ -2030,8 +1960,8 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
msgid ""
-"The temperature used for the heated build plate. If this is 0, the bed will "
-"not heat up for this print."
+"The temperature used for the heated build plate. If this is 0, the bed "
+"temperature will not be adjusted."
msgstr ""
#: fdmprinter.def.json
@@ -3962,6 +3892,18 @@ msgid ""
"roofs, a lower value results in flattened tower roofs."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down label"
+msgid "Drop Down Support Mesh"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down description"
+msgid ""
+"Make support everywhere below the support mesh, so that there's no overhang "
+"in the support mesh."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
@@ -4699,20 +4641,6 @@ msgid ""
"everything else fails to produce proper GCode."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution label"
-msgid "Maximum Resolution"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution description"
-msgid ""
-"The minimum size of a line segment after slicing. If you increase this, the "
-"mesh will have a lower resolution. This may allow the printer to keep up "
-"with the speed it has to process g-code and will increase slice speed by "
-"removing details of the mesh that it can't process anyway."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "multiple_mesh_overlap label"
msgid "Merged Meshes Overlap"
@@ -4897,18 +4825,6 @@ msgid ""
"structure."
msgstr ""
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down label"
-msgid "Drop Down Support Mesh"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down description"
-msgid ""
-"Make support everywhere below the support mesh, so that there's no overhang "
-"in the support mesh."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "anti_overhang_mesh label"
msgid "Anti Overhang Mesh"
@@ -5005,17 +4921,239 @@ msgid "experimental!"
msgstr ""
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order label"
-msgid "Optimize Wall Printing Order"
+msgctxt "support_tree_enable label"
+msgid "Tree Support"
msgstr ""
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order description"
+msgctxt "support_tree_enable description"
msgid ""
-"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."
+"Generate a tree-like support with branches that support your print. This may "
+"reduce material usage and print time, but greatly increases slicing time."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle label"
+msgid "Tree Support Branch Angle"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle description"
+msgid ""
+"The angle of the branches. Use a lower angle to make them more vertical and "
+"more stable. Use a higher angle to be able to have more reach."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance label"
+msgid "Tree Support Branch Distance"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance description"
+msgid ""
+"How far apart the branches need to be when they touch the model. Making this "
+"distance small will cause the tree support to touch the model at more "
+"points, causing better overhang but making support harder to remove."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter label"
+msgid "Tree Support Branch Diameter"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter description"
+msgid ""
+"The diameter of the thinnest branches of tree support. Thicker branches are "
+"more sturdy. Branches towards the base will be thicker than this."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle label"
+msgid "Tree Support Branch Diameter Angle"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle description"
+msgid ""
+"The angle of the branches' diameter as they gradually become thicker towards "
+"the bottom. An angle of 0 will cause the branches to have uniform thickness "
+"over their length. A bit of an angle can increase stability of the tree "
+"support."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution label"
+msgid "Tree Support Collision Resolution"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution description"
+msgid ""
+"Resolution to compute collisions with to avoid hitting the model. Setting "
+"this lower will produce more accurate trees that fail less often, but "
+"increases slicing time dramatically."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness label"
+msgid "Tree Support Wall Thickness"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness description"
+msgid ""
+"The thickness of the walls of the branches of tree support. Thicker walls "
+"take longer to print but don't fall over as easily."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count label"
+msgid "Tree Support Wall Line Count"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count description"
+msgid ""
+"The number of walls of the branches of tree support. Thicker walls take "
+"longer to print but don't fall over as easily."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance label"
+msgid "Slicing Tolerance"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance description"
+msgid ""
+"How to slice layers with diagonal surfaces. The areas of a layer can be "
+"generated based on where the middle of the layer intersects the surface "
+"(Middle). Alternatively each layer can have the areas which fall inside of "
+"the volume throughout the height of the layer (Exclusive) or a layer has the "
+"areas which fall inside anywhere within the layer (Inclusive). Exclusive "
+"retains the most details, Inclusive makes for the best fit and Middle takes "
+"the least time to process."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option middle"
+msgid "Middle"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option exclusive"
+msgid "Exclusive"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option inclusive"
+msgid "Inclusive"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width label"
+msgid "Top Surface Skin Line Width"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width description"
+msgid "Width of a single line of the areas at the top of the print."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern label"
+msgid "Top Surface Skin Pattern"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern description"
+msgid "The pattern of the top most layers."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option lines"
+msgid "Lines"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option concentric"
+msgid "Concentric"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option zigzag"
+msgid "Zig Zag"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles label"
+msgid "Top Surface Skin Line Directions"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles description"
+msgid ""
+"A list of integer line directions to use when the top surface skin layers "
+"use the lines or zig zag pattern. Elements from the list are used "
+"sequentially as the layers progress and when the end of the list is reached, "
+"it starts at the beginning again. The list items are separated by commas and "
+"the whole list is contained in square brackets. Default is an empty list "
+"which means use the traditional default angles (45 and 135 degrees)."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization label"
+msgid "Infill Travel Optimization"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization description"
+msgid ""
+"When enabled, the order in which the infill lines are printed is optimized "
+"to reduce the distance travelled. The reduction in travel time achieved very "
+"much depends on the model being sliced, infill pattern, density, etc. Note "
+"that, for some models that have many small areas of infill, the time to "
+"slice the model may be greatly increased."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature label"
+msgid "Auto Temperature"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature description"
+msgid ""
+"Change the temperature for each layer automatically with the average flow "
+"speed of that layer."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph label"
+msgid "Flow Temperature Graph"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph description"
+msgid ""
+"Data linking material flow (in mm3 per second) to temperature (degrees "
+"Celsius)."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution label"
+msgid "Maximum Resolution"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution description"
+msgid ""
+"The minimum size of a line segment after slicing. If you increase this, the "
+"mesh will have a lower resolution. This may allow the printer to keep up "
+"with the speed it has to process g-code and will increase slice speed by "
+"removing details of the mesh that it can't process anyway."
msgstr ""
#: fdmprinter.def.json
@@ -5745,6 +5883,52 @@ msgid ""
"applies to Wire Printing."
msgstr ""
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled label"
+msgid "Use adaptive layers"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled description"
+msgid ""
+"Adaptive layers computes the layer heights depending on the shape of the "
+"model."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation label"
+msgid "Adaptive layers maximum variation"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation description"
+msgid "The maximum allowed height different from the base layer height in mm."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step label"
+msgid "Adaptive layers variation step size"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step description"
+msgid ""
+"The difference in height of the next layer height compared to the previous "
+"one."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold label"
+msgid "Adaptive layers threshold"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold description"
+msgid ""
+"Threshold whether to use a smaller layer or not. This number is compared to "
+"the tan of the steepest slope in a layer."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "command_line_settings label"
msgid "Command Line Settings"
diff --git a/resources/i18n/fi_FI/cura.po b/resources/i18n/fi_FI/cura.po
index 461bfd63db..5b16d0b323 100644
--- a/resources/i18n/fi_FI/cura.po
+++ b/resources/i18n/fi_FI/cura.po
@@ -6,8 +6,8 @@
msgid ""
msgstr ""
"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-11-21 16:58+0100\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: 2017-09-27 12:27+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Finnish\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:29
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
msgctxt "@action"
msgid "Machine Settings"
msgstr "Laitteen asetukset"
@@ -53,12 +53,11 @@ msgstr "Yhteyden muodostaminen Doodle3D Connectiin"
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:646
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:875
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:659
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:370
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:78
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:104
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:99
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:355
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:376
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:139
@@ -98,7 +97,7 @@ msgctxt "@info:tooltip"
msgid "Open the Doodle3D Connect web interface"
msgstr "Avaa Doodle3D Connect -verkkoliittymä"
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:34
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Näytä muutosloki"
@@ -113,78 +112,83 @@ msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "Profiili on tasoitettu ja aktivoitu."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:26
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "USB-tulostus"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Tulosta USB:n kautta"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:29
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Tulosta USB:n kautta"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:31
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Yhdistetty USB:n kautta"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:status"
msgid "Unable to start a new job because the printer is busy or not connected."
msgstr "Uuden työn aloittaminen ei onnistu, koska tulostin on varattu tai sitä ei ole yhdistetty."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:title"
msgid "Printer Unavailable"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:status"
msgid "This printer does not support USB printing because it uses UltiGCode flavor."
msgstr "Tämä tulostin ei tue USB-tulostusta, koska se käyttää UltiGCode-tyyppiä."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:title"
msgid "USB Printing"
msgstr "USB-tulostus"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
msgctxt "@info:status"
msgid "Unable to start a new job because the printer does not support usb printing."
msgstr "Uuden työn aloittaminen ei onnistu, koska tulostin ei tue USB-tulostusta."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1349
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:946
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1418
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1496
msgctxt "@info:title"
msgid "Warning"
msgstr "Varoitus"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
msgctxt "@info"
msgid "Unable to update firmware because there are no printers connected."
msgstr "Laiteohjelmistoa ei voida päivittää, koska yhtään tulostinta ei ole yhdistetty."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
#, python-format
msgctxt "@info"
msgid "Could not find firmware required for the printer at %s."
msgstr "Tulostimelle ei löydetty laiteohjelmistoa (%s)."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
msgctxt "@info:title"
msgid "Printer Firmware"
msgstr "Tulostimen laiteohjelmisto"
+#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Prepare"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Save to Removable Drive"
@@ -228,11 +232,11 @@ msgid "Could not save to removable drive {0}: {1}"
msgstr "Ei voitu tallentaa siirrettävälle asemalle {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:146
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:693
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:701
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:153
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1358
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:160
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1427
msgctxt "@info:title"
msgid "Error"
msgstr "Virhe"
@@ -282,7 +286,7 @@ msgid "Removable Drive"
msgstr "Siirrettävä asema"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:53
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:51
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Tulosta verkon kautta"
@@ -396,110 +400,110 @@ msgctxt "@info:title"
msgid "Printer Status"
msgstr "Tulostimen tila"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:691
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No Printcore loaded in slot {0}"
msgstr "Uuden tulostustyön aloittaminen ei onnistu. PrintCorea ei ole ladattu aukkoon {0}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:699
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No material loaded in slot {0}"
msgstr "Uuden tulostustyön aloittaminen ei onnistu. Materiaalia ei ole ladattu aukkoon {0}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:709
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:710
#, python-brace-format
msgctxt "@label"
msgid "Not enough material for spool {0}."
msgstr "Kelalle {0} ei ole tarpeeksi materiaalia."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:719
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:720
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Eri PrintCore (Cura: {0}, tulostin: {1}) valittu suulakkeelle {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:733
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:734
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Eri materiaali (Cura: {0}, tulostin: {1}) valittu suulakkeelle {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:741
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:742
#, python-brace-format
msgctxt "@label"
msgid "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer."
msgstr "Print Core -tulostusydintä {0} ei ole kalibroitu oikein. Tulostimen XY-kalibrointi tulee suorittaa."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:746
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "Haluatko varmasti tulostaa valitulla määrityksellä?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:748
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Tulostimen ja Curan määrityksen tai kalibroinnin välillä on ristiriita. Parhaat tulokset saavutetaan viipaloimalla aina tulostimeen asetetuille PrintCoreille ja materiaaleille."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:753
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:754
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Ristiriitainen määritys"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:864
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:865
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:258
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Uusien töiden lähettäminen (tilapäisesti) estetty, edellistä tulostustyötä lähetetään vielä."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Lähetetään tietoja tulostimeen"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:title"
msgid "Sending Data"
msgstr "Lähetetään tietoja"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:944
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
msgctxt "@info:status"
msgid "Unable to send data to printer. Is another job still active?"
msgstr "Tietojen lähetys tulostimeen ei onnistu. Onko toinen työ yhä aktiivinen?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1085
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1087
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:196
msgctxt "@label:MonitorStatus"
msgid "Aborting print..."
msgstr "Keskeytetään tulostus..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1091
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1093
msgctxt "@label:MonitorStatus"
msgid "Print aborted. Please check the printer"
msgstr "Tulostus keskeytetty. Tarkista tulostin"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1097
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
msgctxt "@label:MonitorStatus"
msgid "Pausing print..."
msgstr "Tulostus pysäytetään..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1101
msgctxt "@label:MonitorStatus"
msgid "Resuming print..."
msgstr "Tulostusta jatketaan..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1289
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Synkronoi tulostimen kanssa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "Haluatko käyttää nykyistä tulostimen määritystä Curassa?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1295
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "Tulostimen PrintCoret tai materiaalit eivät vastaa tulostettavan projektin asetuksia. Parhaat tulokset saavutetaan viipaloimalla aina tulostimeen asetetuille PrintCoreille ja materiaaleille."
@@ -520,145 +524,188 @@ msgid "{printer_name} has finished printing '{job_name}'. Please collect the pri
msgstr "{printer_name} on tulostanut työn '{job_name}'. Nouda työ ja vahvista alustan tyhjennys."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:115
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:520
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:533
#, python-brace-format
msgid "{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing."
msgstr "{printer_name} on varattu työn {job_name} tulostamiseen. Muuta tulostimen määritys vastaamaan työtä, jotta tulostus alkaa."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:277
msgctxt "@info:status"
msgid "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."
msgstr "Uuden tulostustyön lähetys ei onnistu: tätä 3D-tulostinta ei ole (vielä) määritetty yhdistetyn Ultimaker 3 -tulostinryhmän isännäksi."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:410
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to send print job to group {cluster_name}."
msgstr "Tulostustyön lähetys ryhmään {cluster_name} ei onnistu."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:418
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:431
#, python-brace-format
msgctxt "@info:status"
msgid "Sent {file_name} to group {cluster_name}."
msgstr "Lähetettiin {file_name} ryhmään {cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:436
msgctxt "@action:button"
msgid "Show print jobs"
msgstr "Näytä tulostustyöt"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:424
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:437
msgctxt "@info:tooltip"
msgid "Opens the print jobs interface in your browser."
msgstr "Avaa tulostustöiden käyttöliittymän selaimessa."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:502
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Unknown"
-msgstr "Tuntematon"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:492
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "{printer_name} on tulostanut työn '{job_name}'."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:494
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:497
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:507
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:510
msgctxt "@info:status"
msgid "Print finished"
msgstr "Tulosta valmis"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:522
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:525
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:535
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:538
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:282
msgctxt "@label:status"
msgid "Action required"
msgstr "Vaatii toimenpiteitä"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:643
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:656
#, python-brace-format
msgctxt "@info:progress"
msgid "Sending {file_name} to group {cluster_name}"
msgstr "Lähetetään tiedostoa {file_name} ryhmään {cluster_name}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:19
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:17
msgctxt "@action"
msgid "Connect via Network"
msgstr "Yhdistä verkon kautta"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:64
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Monitor"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:65
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:67
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Uusi tulostimen %s laiteohjelmisto saatavilla"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:68
msgctxt "@action:button"
msgid "How to update"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:77
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:79
msgctxt "@info"
msgid "Could not access update information."
msgstr "Päivitystietoja ei löytynyt."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:199
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
msgctxt "@info:status"
-msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
-msgstr "SolidWorks-tiedostoa avattaessa ilmeni virheitä! Tarkista, voiko tiedoston avata SolidWorks-ohjelmistossa ilman ongelmia."
+msgid "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself."
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:31
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
+msgctxt "@info:status"
+msgid ""
+"Found no models inside your drawing. Could you please check it's content again and make sure one part or assembly is inside?\n"
+"\n"
+" Thanks!."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
+msgctxt "@info:status"
+msgid ""
+"Found more then one part or assembly inside your drawing. We currently only support drawings with exactly one part or assembly inside.\n"
+"\n"
+"Sorry!"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
msgctxt "@item:inlistbox"
msgid "SolidWorks part file"
msgstr "SolidWorks-osatiedosto"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:35
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:29
msgctxt "@item:inlistbox"
msgid "SolidWorks assembly file"
msgstr "SolidWorks-kokoonpanotiedosto"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.py:21
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
+msgctxt "@item:inlistbox"
+msgid "SolidWorks drawing file"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"We could not find a valid installation of SolidWorks on your system. That means that either SolidWorks is not installed or you don't own an valid license. Please make sure that running SolidWorks itself works without issues and/or contact your ICT.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"You are currently running this plugin on an operating system other than Windows. This plugin will only work on Windows with SolidWorks installed, including an valid license. Please install this plugin on a Windows machine with SolidWorks installed.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
msgid "Configure"
msgstr "Määritä"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/CommonComReader.py:135
-#, python-format
-msgctxt "@info:status"
-msgid "Error while starting %s!"
-msgstr "%s:n käynnistyksen aikana ilmeni virhe!"
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
+msgid "Installation guide for SolidWorks macro"
+msgstr ""
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
-msgid "Simulation view"
-msgstr ""
+msgid "Layer view"
+msgstr "Kerrosnäkymä"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "Cura ei näytä kerroksia täsmällisesti, kun rautalankatulostus on käytössä"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:101
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
msgctxt "@info:title"
msgid "Simulation View"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:26
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:25
msgid "Modify G-Code"
msgstr "Muokkaa GCode-arvoa"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
msgctxt "@info"
-msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
-msgstr "Cura kerää anonyymejä viipalointiin liittyviä tilastotietoja. Tämän voi poistaa käytöstä asetuksien kautta."
+msgid "Cura collects anonymized usage statistics."
+msgstr ""
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
msgctxt "@info:title"
@@ -667,14 +714,41 @@ msgstr "Kerätään tietoja"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
msgctxt "@action:button"
-msgid "Dismiss"
-msgstr "Ohita"
+msgid "Allow"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+msgctxt "@action:tooltip"
+msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+msgctxt "@action:button"
+msgid "Disable"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
+msgctxt "@action:tooltip"
+msgid "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."
+msgstr ""
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Cura 15.04 -profiilit"
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
+msgctxt "@item:inlistbox"
+msgid "Blender file"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
+msgctxt "@info:status"
+msgid ""
+"Could not export using \"{}\" quality!\n"
+"Felt back to \"{}\"."
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
msgctxt "@item:inlistbox"
@@ -706,49 +780,49 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "GIF-kuva"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "Viipalointi ei onnistu nykyisellä materiaalilla, sillä se ei sovellu käytettäväksi valitun laitteen tai kokoonpanon kanssa."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:297
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:319
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:327
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:336
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:349
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:357
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "Viipalointi ei onnistu"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:296
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "Viipalointi ei onnistu nykyisten asetuksien ollessa voimassa. Seuraavissa asetuksissa on virheitä: {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:318
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:348
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:356
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "Viipalointi ei onnistu, koska esitäyttötorni tai esitäytön sijainti tai sijainnit eivät kelpaa."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:335
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:365
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "Ei viipaloitavaa, koska mikään malleista ei sovellu tulostustilavuuteen. Skaalaa tai pyöritä mallia, kunnes se on sopiva."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Käsitellään kerroksia"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:title"
msgid "Information"
msgstr "Tiedot"
@@ -785,14 +859,14 @@ msgstr ""
msgid "Failed to install Siemens NX plugin. Could not set environment variable UGII_USER_DIR for Siemens NX."
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:585
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:165
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Suositeltu"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:169
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:595
msgctxt "@title:tab"
msgid "Custom"
msgstr "Mukautettu"
@@ -803,24 +877,24 @@ msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "3MF-tiedosto"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:126
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1142
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:159
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1185
msgctxt "@label"
msgid "Nozzle"
msgstr "Suutin"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:152
#, python-brace-format
msgctxt "@info:status"
msgid "Failed to get plugin ID from {0}"
msgstr "Lisäosan tunnuksen hankkiminen epäonnistui tiedostosta {0}"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:165
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:153
msgctxt "@info:tile"
msgid "Warning"
msgstr "Varoitus"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:203
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:191
msgctxt "@window:title"
msgid "Plugin browser"
msgstr "Lisäosien selain"
@@ -835,18 +909,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "G File -tiedosto"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:314
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:321
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "G-coden jäsennys"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:316
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:426
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:323
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:464
msgctxt "@info:title"
msgid "G-code Details"
msgstr "G-coden tiedot"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:424
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:462
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "Varmista, että G-code on tulostimelle ja sen tulostusasetuksille soveltuva, ennen kuin lähetät tiedoston siihen. G-coden esitys ei välttämättä ole tarkka."
@@ -857,6 +931,16 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Cura-profiili"
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Profile Assistant"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
+msgctxt "@item:inlistbox"
+msgid "Profile Assistant"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
msgctxt "@item:inlistbox"
msgid "3MF file"
@@ -888,142 +972,116 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "Tasaa alusta"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Ulkoseinämä"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Sisäseinämät"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
msgctxt "@tooltip"
msgid "Skin"
msgstr "Pintakalvo"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
msgctxt "@tooltip"
msgid "Infill"
msgstr "Täyttö"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:93
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "Tuen täyttö"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:94
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "Tukiliittymä"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:95
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
msgctxt "@tooltip"
msgid "Support"
msgstr "Tuki"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:96
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
msgctxt "@tooltip"
msgid "Skirt"
msgstr "Helma"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:97
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
msgctxt "@tooltip"
msgid "Travel"
msgstr "Siirtoliike"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Takaisinvedot"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
msgctxt "@tooltip"
msgid "Other"
msgstr "Muu"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:199
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:231
msgctxt "@label unknown material"
msgid "Unknown"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:284
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:318
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "Esiviipaloitu tiedosto {0}"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:469
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:440
msgctxt "@item:material"
msgid "No material loaded"
msgstr "Ei ladattua materiaalia"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:476
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:447
msgctxt "@item:material"
msgid "Unknown material"
msgstr "Tuntematon materiaali"
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:30
-msgctxt "@info:status"
-msgid "Finding new location for objects"
-msgstr "Uusien paikkojen etsiminen kappaleille"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:34
-msgctxt "@info:title"
-msgid "Finding Location"
-msgstr "Etsitään paikkaa"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:89
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
-msgctxt "@info:status"
-msgid "Unable to find a location within the build volume for all objects"
-msgstr "Kaikille kappaleille ei löydy paikkaa tulostustilavuudessa."
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:90
-msgctxt "@info:title"
-msgid "Can't Find Location"
-msgstr "Paikkaa ei löydy"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:431
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:113
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:437
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:120
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "Tiedosto on jo olemassa"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:432
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:114
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:438
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
msgid "The file {0} already exists. Are you sure you want to overwrite it?"
msgstr "Tiedosto {0} on jo olemassa. Haluatko varmasti kirjoittaa sen päälle?"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:872
msgctxt "@label"
msgid "Custom"
msgstr "Mukautettu"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:819
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:876
msgctxt "@label"
msgid "Custom Material"
msgstr "Mukautettu materiaali"
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:182
-msgctxt "@menuitem"
-msgid "Global"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:229
+#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:205
msgctxt "@menuitem"
msgid "Not overridden"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:124
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "Valittu materiaali ei sovellu käytettäväksi valitun laitteen tai kokoonpanon kanssa."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:125
#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24
msgctxt "@info:title"
msgid "Incompatible Material"
@@ -1044,67 +1102,89 @@ msgctxt "@action"
msgid "Undo changing the material diameter."
msgstr "Kumoa materiaalin halkaisijan muutokset."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:144
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to export profile to {0}: {1}"
msgstr "Profiilin vienti epäonnistui tiedostoon {0}: {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:158
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Failed to export profile to {0}: Writer plugin reported failure."
msgstr "Profiilin vienti epäonnistui tiedostoon {0}: Kirjoitin-lisäosa ilmoitti virheestä."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:156
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:163
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Exported profile to {0}"
msgstr "Profiili viety tiedostoon {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:157
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:164
msgctxt "@info:title"
msgid "Export succeeded"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:183
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:205
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:214
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:248
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:211
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:271
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
msgstr "Profiilin tuonti epäonnistui tiedostosta {0}: {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:216
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:252
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:230
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "This profile {0} contains incorrect data, could not import it."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "The machine defined in profile {0} doesn't match with your current machine, could not import it."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Onnistuneesti tuotu profiili {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:255
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:277
+#, python-brace-format
+msgctxt "@info:status"
+msgid "File {0} does not contain any valid profile."
+msgstr ""
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "Profiililla {0} on tuntematon tiedostotyyppi tai se on vioittunut."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:298
msgctxt "@label"
msgid "Custom profile"
msgstr "Mukautettu profiili"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:285
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Profiilista puuttuu laatutyyppi."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:349
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "Laatutyyppiä {0} ei löydy nykyiselle kokoonpanolle."
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:46
+#, python-brace-format
+msgctxt "@label"
+msgid "Group #{group_nr}"
+msgstr ""
+
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
@@ -1115,142 +1195,167 @@ msgctxt "@info:title"
msgid "Build Volume"
msgstr "Tulostustilavuus"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:34
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:25
msgctxt "@info:status"
msgid "Multiplying and placing objects"
msgstr "Kappaleiden kertominen ja sijoittelu"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:35
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:26
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
msgctxt "@info:title"
msgid "Placing Object"
msgstr "Sijoitetaan kappaletta"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:80
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:88
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:152
+msgctxt "@info:status"
+msgid "Unable to find a location within the build volume for all objects"
+msgstr "Kaikille kappaleille ei löydy paikkaa tulostustilavuudessa."
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:29
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:64
+msgctxt "@info:status"
+msgid "Finding new location for objects"
+msgstr "Uusien paikkojen etsiminen kappaleille"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:33
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:68
+msgctxt "@info:title"
+msgid "Finding Location"
+msgstr "Etsitään paikkaa"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:89
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:153
+msgctxt "@info:title"
+msgid "Can't Find Location"
+msgstr "Paikkaa ei löydy"
+
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:81
msgctxt "@title:window"
msgid "Crash Report"
msgstr "Kaatumisraportti"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:93
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:94
msgctxt "@label crash message"
msgid ""
-"
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+"
A fatal error has occurred. Please send us this Crash Report to fix the problem
\n"
"
Please use the \"Send report\" button to post a bug report automatically to our servers
"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:141
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:147
msgctxt "@title:groupbox"
-msgid "Exception traceback"
+msgid "Error traceback"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:208
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:214
msgctxt "@title:groupbox"
msgid "Logs"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:231
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:237
msgctxt "@title:groupbox"
msgid "User description"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:246
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:252
msgctxt "@action:button"
msgid "Send report"
msgstr ""
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:256
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:274
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Ladataan laitteita..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:661
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:660
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Asetetaan näkymää..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:703
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:702
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Ladataan käyttöliittymää..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:874
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:899
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1348
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "Vain yksi G-code-tiedosto voidaan ladata kerralla. Tiedoston {0} tuonti ohitettiin."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1357
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1426
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "Muita tiedostoja ei voida ladata, kun G-code latautuu. Tiedoston {0} tuonti ohitettiin."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1416
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1495
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr ""
@@ -1279,12 +1384,11 @@ msgstr "X (leveys)"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:288
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:300
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:391
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:413
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:840
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:394
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:424
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:849
msgctxt "@label"
msgid "mm"
msgstr "mm"
@@ -1374,68 +1478,67 @@ msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "Suuttimen kärjen ja korokejärjestelmän (X- ja Y-akselit) välinen korkeusero. Käytetään estämään aiempien tulosteiden ja korokkeen yhteentörmäyksiä, kun tulostetaan yksi kerrallaan."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:254
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Suulakkeiden määrä"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:289
-msgctxt "@tooltip"
-msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
-msgstr "Tulostimen tukema tulostuslangan nimellinen halkaisija. Materiaali ja/tai profiili korvaa tarkan halkaisijan."
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:291
-msgctxt "@label"
-msgid "Material diameter"
-msgstr "Materiaalin halkaisija"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:299
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:390
-msgctxt "@label"
-msgid "Nozzle size"
-msgstr "Suuttimen koko"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:317
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:310
msgctxt "@label"
msgid "Start Gcode"
msgstr "Aloita GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:327
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:320
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very start."
msgstr "GCode-komennot, jotka suoritetaan aivan alussa."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:336
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:329
msgctxt "@label"
msgid "End Gcode"
msgstr "Lopeta GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:346
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:339
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very end."
msgstr "GCode-komennot, jotka suoritetaan aivan lopussa."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:378
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:370
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Suutinasetukset"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:382
+msgctxt "@label"
+msgid "Nozzle size"
+msgstr "Suuttimen koko"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:393
+msgctxt "@label"
+msgid "Compatible material diameter"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:395
+msgctxt "@tooltip"
+msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
+msgstr "Tulostimen tukema tulostuslangan nimellinen halkaisija. Materiaali ja/tai profiili korvaa tarkan halkaisijan."
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:411
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "Suuttimen X-siirtymä"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:423
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Suuttimen Y-siirtymä"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:433
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:444
msgctxt "@label"
msgid "Extruder Start Gcode"
msgstr "Suulake – aloita Gcode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:462
msgctxt "@label"
msgid "Extruder End Gcode"
msgstr "Suulake – lopeta Gcode"
@@ -1448,8 +1551,9 @@ msgstr "Muutosloki"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:107
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:55
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:445
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:357
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:306
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:456
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:492
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:80
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:123
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
@@ -1533,7 +1637,7 @@ msgstr "Muokkaa"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:50
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:95
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:171
msgctxt "@action:button"
msgid "Remove"
msgstr "Poista"
@@ -1555,14 +1659,14 @@ msgid "Type"
msgstr "Tyyppi"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:233
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3"
-msgstr "Ultimaker 3"
+msgstr ""
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:236
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3 Extended"
-msgstr "Ultimaker 3 Extended"
+msgstr ""
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:252
msgctxt "@label"
@@ -1606,8 +1710,6 @@ msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Anna verkon tulostimen IP-osoite tai isäntänimi."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:379
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:92
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:88
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
@@ -1628,6 +1730,11 @@ msgctxt "@label: arg 1 is group name"
msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
msgstr "%1 ei ole määritetty yhdistetyn Ultimaker 3 -tulostinryhmän isännäksi"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
+msgctxt "@label link to connect manager"
+msgid "Add/Remove printers"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
msgctxt "@info:tooltip"
msgid "Opens the print jobs page with your default web browser."
@@ -1658,11 +1765,16 @@ msgid "Available"
msgstr "Saatavilla"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:100
msgctxt "@label:MonitorStatus"
msgid "Lost connection with the printer"
msgstr "Yhteys tulostimeen menetetty"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
+msgctxt "@label Printer status"
+msgid "Unknown"
+msgstr ""
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:257
msgctxt "@label:status"
msgid "Disabled"
@@ -1754,138 +1866,250 @@ msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Aktivoi määritys"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:20
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:21
msgctxt "@title:window"
-msgid "Cura SolidWorks Plugin Configuration"
-msgstr "Cura SolidWorks -laajennuksen määritys"
+msgid "SolidWorks: Export wizard"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:44
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:45
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:140
msgctxt "@action:label"
-msgid "Default quality of the exported STL:"
-msgstr "Viedyn STL:n oletuslaatu:"
+msgid "Quality:"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:78
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:179
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always ask"
-msgstr "Kysy aina"
+msgid "Fine (3D-printing)"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:180
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Fine quality"
-msgstr "Käytä aina hienoa laatua"
+msgid "Coarse (3D-printing)"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:181
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Coarse quality"
-msgstr "Käytä aina karkeaa laatua"
+msgid "Fine (SolidWorks)"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:20
-msgctxt "@title:window"
-msgid "Import SolidWorks File as STL..."
-msgstr "Tuo SolidWorks-tiedosto STL-muodossa..."
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:43
-msgctxt "@info:tooltip"
-msgid "Quality of the Exported STL"
-msgstr "Viedyn STL:n laatu"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:48
-msgctxt "@action:label"
-msgid "Quality"
-msgstr "Laatu"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:62
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:182
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Coarse"
-msgstr "Karkea"
+msgid "Coarse (SolidWorks)"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:63
-msgctxt "@option:curaSolidworksStlQuality"
-msgid "Fine"
-msgstr "Hieno"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:78
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:82
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:94
msgctxt "@text:window"
-msgid "Remember my choice"
-msgstr "Muista valintani"
+msgid "Show this dialog again"
+msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:104
+msgctxt "@action:button"
+msgid "Continue"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:116
+msgctxt "@action:button"
+msgid "Abort"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:21
+msgctxt "@title:window"
+msgid "How to install Cura SolidWorks macro"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:62
+msgctxt "@description:label"
+msgid "Steps:"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:140
+msgctxt "@action:button"
+msgid ""
+"Open the directory\n"
+"with macro and icon"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:160
+msgctxt "@description:label"
+msgid "Instructions:"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:202
+msgctxt "@action:playpause"
+msgid "Play"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:206
+msgctxt "@action:playpause"
+msgid "Pause"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:268
+msgctxt "@action:button"
+msgid "Previous Step"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:283
+msgctxt "@action:button"
+msgid "Done"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:287
+msgctxt "@action:button"
+msgid "Next Step"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:21
+msgctxt "@title:window"
+msgid "SolidWorks plugin: Configuration"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:39
+msgctxt "@title:tab"
+msgid "Conversion settings"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:66
+msgctxt "@label"
+msgid "First choice:"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:86
+msgctxt "@text:menu"
+msgid "Latest installed version (Recommended)"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:95
+msgctxt "@text:menu"
+msgid "Default version"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:193
+msgctxt "@label"
+msgid "Show wizard before opening SolidWorks files"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:203
+msgctxt "@label"
+msgid "Automatically rotate opened file into normed orientation"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:210
+msgctxt "@title:tab"
+msgid "Installation(s)"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:284
+msgctxt "@label"
+msgid "COM service found"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:295
+msgctxt "@label"
+msgid "Executable found"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:306
+msgctxt "@label"
+msgid "COM starting"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:317
+msgctxt "@label"
+msgid "Revision number"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:328
+msgctxt "@label"
+msgid "Functions available"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+msgctxt "@action:button"
+msgid "Save"
+msgstr "Tallenna"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
msgctxt "@label"
msgid "Color scheme"
msgstr "Värimalli"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:96
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Materiaalin väri"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Linjojen tyyppi"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:108
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:148
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Yhteensopivuustila"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:230
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
msgctxt "@label"
msgid "Show Travels"
msgstr "Näytä siirtoliikkeet"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:236
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
msgctxt "@label"
msgid "Show Helpers"
msgstr "Näytä avustimet"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:242
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
msgctxt "@label"
msgid "Show Shell"
msgstr "Näytä kuori"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:248
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
msgctxt "@label"
msgid "Show Infill"
msgstr "Näytä täyttö"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:297
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Näytä vain yläkerrokset"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:306
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "Näytä 5 yksityiskohtaista kerrosta ylhäällä"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:317
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Yläosa/alaosa"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:321
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
msgctxt "@label"
msgid "Inner Wall"
msgstr "Sisäseinämä"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:378
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
msgctxt "@label"
msgid "min"
msgstr ""
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:420
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
msgctxt "@label"
msgid "max"
msgstr ""
@@ -1910,7 +2134,7 @@ msgctxt "@label"
msgid "Settings"
msgstr "Asetukset"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:455
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:466
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Muuta aktiivisia jälkikäsittelykomentosarjoja"
@@ -1985,23 +2209,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Tasoitus"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:208
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+msgctxt "@label"
+msgid "Mesh Type"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+msgctxt "@label"
+msgid "Normal model"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+msgctxt "@label"
+msgid "Print as support"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+msgctxt "@label"
+msgid "Don't support overlap with other models"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+msgctxt "@label"
+msgid "Modify settings for overlap with other models"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+msgctxt "@label"
+msgid "Modify settings for infill of other models"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:333
msgctxt "@action:button"
msgid "Select settings"
msgstr "Valitse asetukset"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:248
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:375
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Valitse tätä mallia varten mukautettavat asetukset"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:272
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:402
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:91
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Suodatin..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:296
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:426
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Näytä kaikki"
@@ -2360,66 +2614,66 @@ msgctxt "@label"
msgid "Everything is in order! You're done with your CheckUp."
msgstr "Kaikki on kunnossa! CheckUp on valmis."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:88
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:87
msgctxt "@label:MonitorStatus"
msgid "Not connected to a printer"
msgstr "Ei yhteyttä tulostimeen"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:90
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:89
msgctxt "@label:MonitorStatus"
msgid "Printer does not accept commands"
msgstr "Tulostin ei hyväksy komentoja"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:96
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:95
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:194
msgctxt "@label:MonitorStatus"
msgid "In maintenance. Please check the printer"
msgstr "Huolletaan. Tarkista tulostin"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:102
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:184
msgctxt "@label:MonitorStatus"
msgid "Printing..."
msgstr "Tulostetaan..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:105
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:186
msgctxt "@label:MonitorStatus"
msgid "Paused"
msgstr "Keskeytetty"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:109
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:108
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:188
msgctxt "@label:MonitorStatus"
msgid "Preparing..."
msgstr "Valmistellaan..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:111
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:110
msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "Poista tuloste"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:237
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
msgctxt "@label:"
msgid "Resume"
msgstr "Jatka"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:245
msgctxt "@label:"
msgid "Pause"
msgstr "Keskeytä"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:274
msgctxt "@label:"
msgid "Abort Print"
msgstr "Keskeytä tulostus"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:280
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
msgctxt "@window:title"
msgid "Abort print"
msgstr "Keskeytä tulostus"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:282
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:286
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
msgstr "Haluatko varmasti keskeyttää tulostuksen?"
@@ -2454,19 +2708,19 @@ msgid "Customized"
msgstr "Mukautettu"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:593
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Kysy aina"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:594
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:596
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Hylkää äläkä kysy uudelleen"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:597
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Säilytä äläkä kysy uudelleen"
@@ -2501,72 +2755,72 @@ msgctxt "@label"
msgid "Brand"
msgstr "Merkki"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:88
msgctxt "@label"
msgid "Material Type"
msgstr "Materiaalin tyyppi"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:97
msgctxt "@label"
msgid "Color"
msgstr "Väri"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
msgctxt "@label"
msgid "Properties"
msgstr "Ominaisuudet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:143
msgctxt "@label"
msgid "Density"
msgstr "Tiheys"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:156
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:158
msgctxt "@label"
msgid "Diameter"
msgstr "Läpimitta"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:187
msgctxt "@label"
msgid "Filament Cost"
msgstr "Tulostuslangan hinta"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
msgctxt "@label"
msgid "Filament weight"
msgstr "Tulostuslangan paino"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:220
msgctxt "@label"
msgid "Filament length"
msgstr "Tulostuslangan pituus"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:227
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:229
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Hinta metriä kohden"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:243
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Materiaali on linkitetty kohteeseen %1 ja niillä on joitain samoja ominaisuuksia."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:250
msgctxt "@label"
msgid "Unlink Material"
msgstr "Poista materiaalin linkitys"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:259
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:261
msgctxt "@label"
msgid "Description"
msgstr "Kuvaus"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:272
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:274
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Tarttuvuustiedot"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:300
msgctxt "@label"
msgid "Print settings"
msgstr "Tulostusasetukset"
@@ -2607,7 +2861,7 @@ msgid "Unit"
msgstr "Yksikkö"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:14
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:439
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:509
msgctxt "@title:tab"
msgid "General"
msgstr "Yleiset"
@@ -2622,230 +2876,255 @@ msgctxt "@label"
msgid "Language:"
msgstr "Kieli:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:207
msgctxt "@label"
msgid "Currency:"
msgstr "Valuutta:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:219
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Theme:"
msgstr "Teema:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:279
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:281
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Sovellus on käynnistettävä uudelleen, jotta nämä muutokset tulevat voimaan."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:296
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:298
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Viipaloi automaattisesti, kun asetuksia muutetaan."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:306
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Viipaloi automaattisesti"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:318
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:320
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Näyttöikkunan käyttäytyminen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:326
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:328
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Korosta mallin vailla tukea olevat alueet punaisella. Ilman tukea nämä alueet eivät tulostu kunnolla."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:337
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Näytä uloke"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:342
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:344
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
msgstr "Siirtää kameraa siten, että valittuna oleva malli on näkymän keskellä."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:347
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:349
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Keskitä kamera kun kohde on valittu"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:356
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:358
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "Pitääkö Curan oletusarvoinen zoom-toimintatapa muuttaa päinvastaiseksi?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:363
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Käännä kameran zoomin suunta päinvastaiseksi."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:370
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:372
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "Tuleeko zoomauksen siirtyä hiiren suuntaan?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:377
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "Zoomaa hiiren suuntaan"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "Pitäisikö alustalla olevia malleja siirtää niin, etteivät ne enää leikkaa toisiaan?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Varmista, että mallit ovat erillään"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:397
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "Pitäisikö tulostusalueella olevia malleja siirtää alas niin, että ne koskettavat tulostusalustaa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:402
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Pudota mallit automaattisesti alustalle"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:414
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:416
msgctxt "@info:tooltip"
msgid "Show caution message in gcode reader."
msgstr "Näytä varoitusviesti gcode-lukijassa."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:423
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:425
msgctxt "@option:check"
msgid "Caution message in gcode reader"
msgstr "Gcode-lukijan varoitusviesti"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "Pakotetaanko kerros yhteensopivuustilaan?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:435
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:437
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Pakota kerrosnäkymän yhteensopivuustila (vaatii uudelleenkäynnistyksen)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:451
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:453
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Tiedostojen avaaminen ja tallentaminen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:457
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:459
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "Pitäisikö mallit skaalata tulostustilavuuteen, jos ne ovat liian isoja?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:462
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:464
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Skaalaa suuret mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:471
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:473
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Malli voi vaikuttaa erittäin pieneltä, jos sen koko on ilmoitettu esimerkiksi metreissä eikä millimetreissä. Pitäisikö nämä mallit suurentaa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:476
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:478
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Skaalaa erittäin pienet mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:485
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:487
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "Pitäisikö tulostustyön nimeen lisätä automaattisesti tulostimen nimeen perustuva etuliite?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Lisää laitteen etuliite työn nimeen"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:501
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Näytetäänkö yhteenveto, kun projektitiedosto tallennetaan?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Näytä yhteenvetoikkuna, kun projekti tallennetaan"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:514
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Projektitiedoston avaamisen oletustoimintatapa"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Projektitiedoston avaamisen oletustoimintatapa: "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:533
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@option:openProject"
msgid "Always ask"
msgstr "Kysy aina"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:536
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Avaa aina projektina"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Tuo mallit aina"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Kun olet tehnyt muutokset profiiliin ja vaihtanut toiseen, näytetään valintaikkuna, jossa kysytään, haluatko säilyttää vai hylätä muutokset. Tässä voit myös valita oletuskäytöksen, jolloin valintaikkunaa ei näytetä uudelleen."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:582
msgctxt "@label"
msgid "Override Profile"
msgstr "Kumoa profiili"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:629
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:631
msgctxt "@label"
msgid "Privacy"
msgstr "Tietosuoja"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Pitäisikö Curan tarkistaa saatavilla olevat päivitykset, kun ohjelma käynnistetään?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:641
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:643
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Tarkista päivitykset käynnistettäessä"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:651
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:653
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "Pitäisikö anonyymejä tietoja tulosteesta lähettää Ultimakerille? Huomaa, että malleja, IP-osoitteita tai muita henkilökohtaisia tietoja ei lähetetä eikä tallenneta."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:656
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:658
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "Lähetä (anonyymit) tulostustiedot"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:674
+msgctxt "@label"
+msgid "Experimental"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:680
+msgctxt "@info:tooltip"
+msgid "Use multi build plate functionality"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:685
+msgctxt "@option:check"
+msgid "Use multi build plate functionality (restart required)"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+msgctxt "@info:tooltip"
+msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+msgctxt "@option:check"
+msgid "Do not arrange objects on load"
+msgstr ""
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:514
msgctxt "@title:tab"
msgid "Printers"
msgstr "Tulostimet"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:37
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:51
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:138
msgctxt "@action:button"
msgid "Activate"
msgstr "Aktivoi"
@@ -2888,7 +3167,7 @@ msgid "Waiting for a printjob"
msgstr "Odotetaan tulostustyötä"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:448
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:518
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Profiilit"
@@ -2914,13 +3193,13 @@ msgid "Duplicate"
msgstr "Jäljennös"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:113
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:182
msgctxt "@action:button"
msgid "Import"
msgstr "Tuo"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:119
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:212
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:193
msgctxt "@action:button"
msgid "Export"
msgstr "Vie"
@@ -2986,7 +3265,7 @@ msgid "Export Profile"
msgstr "Profiilin vienti"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:516
msgctxt "@title:tab"
msgid "Materials"
msgstr "Materiaalit"
@@ -3001,60 +3280,60 @@ msgctxt "@action:label %1 is printer name"
msgid "Printer: %1"
msgstr "Tulostin: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:149
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:150
msgctxt "@action:button"
msgid "Create"
msgstr "Luo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:160
msgctxt "@action:button"
msgid "Duplicate"
msgstr "Jäljennös"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:306
msgctxt "@title:window"
msgid "Import Material"
msgstr "Tuo materiaali"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:320
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:307
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Could not import material %1: %2"
msgstr "Materiaalin tuominen epäonnistui: %1: %2"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully imported material %1"
msgstr "Materiaalin tuominen onnistui: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:343
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:329
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:344
msgctxt "@title:window"
msgid "Export Material"
msgstr "Vie materiaali"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:348
msgctxt "@info:status Don't translate the XML tags and !"
msgid "Failed to export material to %1: %2"
msgstr "Materiaalin vieminen epäonnistui kohteeseen %1: %2"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:368
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:354
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully exported material to %1"
msgstr "Materiaalin vieminen onnistui kohteeseen %1"
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:793
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:869
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Lisää tulostin"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
msgctxt "@label"
msgid "Printer Name:"
msgstr "Tulostimen nimi:"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Lisää tulostin"
@@ -3183,12 +3462,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Profiili:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:66
-msgctxt "@"
-msgid "No Profile Available"
-msgstr ""
-
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:102
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
@@ -3199,37 +3473,37 @@ msgstr ""
"\n"
"Avaa profiilin hallinta napsauttamalla."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:150
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Haku…"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:482
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Kopioi arvo kaikkiin suulakepuristimiin"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:497
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Piilota tämä asetus"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:508
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:507
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "Älä näytä tätä asetusta"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:511
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Pidä tämä asetus näkyvissä"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:530
msgctxt "@action:menu"
msgid "Configure setting visiblity..."
msgstr "Määritä asetusten näkyvyys..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:250
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
@@ -3240,27 +3514,27 @@ msgstr ""
"\n"
"Tee asetuksista näkyviä napsauttamalla."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:62
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
msgctxt "@label Header for list of settings."
msgid "Affects"
msgstr "Koskee seuraavia:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:67
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:66
msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Riippuu seuraavista:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:156
msgctxt "@label"
-msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
-msgstr "Tämä asetus koskee aina kaikkia suulakepuristimia. Jos se vaihdetaan tässä, kaikkien suulakepuristimien arvo muuttuu"
+msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
+msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:160
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:159
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "Arvo perustuu suulakepuristimien arvoihin "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:186
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:190
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3271,7 +3545,7 @@ msgstr ""
"\n"
"Palauta profiilin arvo napsauttamalla."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:288
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
@@ -3282,12 +3556,12 @@ msgstr ""
"\n"
"Palauta laskettu arvo napsauttamalla."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid "Print Setup"
msgstr "Tulostuksen asennus"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid ""
"Print Setup disabled\n"
@@ -3296,59 +3570,59 @@ msgstr ""
"Tulostuksen asennus ei käytössä\n"
"G-code-tiedostoja ei voida muokata"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:336
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:342
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:354
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:359
msgctxt "@tooltip"
-msgid "Time specification
"
+msgid "Time specification"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:429
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:441
msgctxt "@label"
msgid "Cost specification"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:434
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:455
msgctxt "@label m for meter"
msgid "%1m"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:435
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:447
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:456
msgctxt "@label g for grams"
msgid "%1g"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:454
msgctxt "@label"
msgid "Total:"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:504
msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
msgid "%1m / ~ %2g / ~ %4 %3"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:509
msgctxt "@label Print estimates: m for meters, g for grams"
msgid "%1m / ~ %2g"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Suositeltu tulostuksen asennus
Tulosta valitun tulostimen, materiaalin ja laadun suositelluilla asetuksilla."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:596
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Mukautettu tulostuksen asennus
Tulosta hallitsemalla täysin kaikkia viipalointiprosessin vaiheita."
-#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:49
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:50
msgctxt "@title:menuitem %1 is the automatically selected material"
msgid "Automatic: %1"
msgstr "Automaattinen: %1"
@@ -3358,6 +3632,16 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Näytä"
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:37
+msgctxt "@action:inmenu menubar:view"
+msgid "&Camera position"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:52
+msgctxt "@action:inmenu menubar:view"
+msgid "&Build plate"
+msgstr ""
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:40
msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer"
msgid "Automatic: %1"
@@ -3370,14 +3654,14 @@ msgid_plural "Print Selected Models With:"
msgstr[0] "Tulosta valittu malli asetuksella:"
msgstr[1] "Tulosta valitut mallit asetuksella:"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:83
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:114
msgctxt "@title:window"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Kerro valittu malli"
msgstr[1] "Kerro valitut mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:108
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:139
msgctxt "@label"
msgid "Number of Copies"
msgstr "Kopioiden määrä"
@@ -3393,7 +3677,7 @@ msgid "No printer connected"
msgstr "Ei tulostinta yhdistettynä"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:90
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:139
msgctxt "@label"
msgid "Extruder"
msgstr "Suulake"
@@ -3503,254 +3787,294 @@ msgctxt "@label"
msgid "Estimated time left"
msgstr "Aikaa jäljellä arviolta"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
msgid "Toggle Fu&ll Screen"
msgstr "Vaihda &koko näyttöön"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "&Kumoa"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:89
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "Tee &uudelleen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:99
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "&Lopeta"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
msgctxt "@action:inmenu menubar:view"
-msgid "&Reset camera position"
+msgid "&3D View"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+msgctxt "@action:inmenu menubar:view"
+msgid "&Front View"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
+msgctxt "@action:inmenu menubar:view"
+msgid "&Top View"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
+msgctxt "@action:inmenu menubar:view"
+msgid "&Left Side View"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+msgctxt "@action:inmenu menubar:view"
+msgid "&Right Side View"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Määritä Curan asetukset..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "L&isää tulostin..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "Tulostinten &hallinta..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Hallitse materiaaleja..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Päivitä nykyiset asetukset tai ohitukset profiiliin"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:150
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Hylkää tehdyt muutokset"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Luo profiili nykyisten asetusten tai ohitusten perusteella..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Profiilien hallinta..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Näytä sähköinen &dokumentaatio"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "Ilmoita &virheestä"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:191
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
msgid "&About..."
msgstr "Ti&etoja..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
msgid "Delete &Selected Model"
msgid_plural "Delete &Selected Models"
msgstr[0] "Poista &valittu malli"
msgstr[1] "Poista &valitut mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Keskitä valittu malli"
msgstr[1] "Keskitä valitut mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Kerro valittu malli"
msgstr[1] "Kerro valitut mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Poista malli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "Ke&skitä malli alustalle"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "&Ryhmittele mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:250
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Poista mallien ryhmitys"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:260
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "&Yhdistä mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "&Kerro malli..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
msgid "&Select All Models"
msgstr "&Valitse kaikki mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:287
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
msgid "&Clear Build Plate"
msgstr "&Tyhjennä tulostusalusta"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:297
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
msgctxt "@action:inmenu menubar:file"
msgid "Re&load All Models"
msgstr "&Lataa kaikki mallit uudelleen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:306
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
+msgctxt "@action:inmenu menubar:edit"
+msgid "Arrange All Models To All Build Plates"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Järjestä kaikki mallit"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Järjestä valinta"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:321
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Määritä kaikkien mallien positiot uudelleen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:328
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model &Transformations"
msgstr "Määritä kaikkien mallien &muutokset uudelleen"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Avaa tiedosto(t)..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:343
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Uusi projekti..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "Näytä moottorin l&oki"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Näytä määrityskansio"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:365
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Määritä asetusten näkyvyys..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse plugins..."
msgstr "Selaa lisäosia..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:379
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:menu"
msgid "Installed plugins..."
msgstr "Asennetut lisäoset..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:28
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:438
+msgctxt "@action:inmenu menubar:view"
+msgid "Expand/Collapse Sidebar"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Lataa 3D-malli"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:34
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Valmiina viipaloimaan"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Viipaloidaan..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Valmis: %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
msgstr "Viipalointi ei onnistu"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "Viipalointi ei käytettävissä"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Slice current printjob"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Cancel slicing process"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Valmistele"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Peruuta"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:302
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Valitse aktiivinen tulostusväline"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:696
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Avaa tiedosto(t)"
@@ -3770,114 +4094,114 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:81
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&Tiedosto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:98
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
msgctxt "@action:inmenu menubar:file"
msgid "&Save Selection to File"
msgstr "&Tallenna valinta tiedostoon"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
msgctxt "@title:menu menubar:file"
msgid "Save &As..."
msgstr "Tallenna &nimellä…"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
msgctxt "@title:menu menubar:file"
-msgid "Save project"
-msgstr "Tallenna projekti"
+msgid "Save &Project..."
+msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Muokkaa"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:158
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
msgctxt "@title:menu"
msgid "&View"
msgstr "&Näytä"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
msgctxt "@title:menu"
msgid "&Settings"
msgstr "&Asetukset"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
msgctxt "@title:menu menubar:toplevel"
msgid "&Printer"
msgstr "&Tulostin"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:175
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:187
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:208
msgctxt "@title:menu"
msgid "&Material"
msgstr "&Materiaali"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:176
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:197
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:209
msgctxt "@title:menu"
msgid "&Profile"
msgstr "&Profiili"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:180
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:201
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Aseta aktiiviseksi suulakepuristimeksi"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:219
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "Laa&jennukset"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:232
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:253
msgctxt "@title:menu menubar:toplevel"
msgid "P&lugins"
msgstr "&Lisäosat"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:261
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "L&isäasetukset"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:269
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "&Ohje"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:330
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:351
msgctxt "@action:button"
msgid "Open File"
msgstr "Avaa tiedosto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:442
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:512
msgctxt "@title:tab"
msgid "Settings"
msgstr "Asetukset"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:478
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:554
msgctxt "@title:window"
msgid "New project"
msgstr "Uusi projekti"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:479
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:555
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "Haluatko varmasti aloittaa uuden projektin? Se tyhjentää alustan ja kaikki tallentamattomat asetukset."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:721
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:797
msgctxt "@window:title"
msgid "Install Plugin"
msgstr "Asenna laajennus"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:728
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:804
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Avaa tiedosto(t)"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:731
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:807
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Löysimme vähintään yhden Gcode-tiedoston valitsemiesi tiedostojen joukosta. Voit avata vain yhden Gcode-tiedoston kerrallaan. Jos haluat avata Gcode-tiedoston, valitse vain yksi."
@@ -3902,97 +4226,82 @@ msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "Älä näytä projektin yhteenvetoa tallennettaessa"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
-msgctxt "@action:button"
-msgid "Save"
-msgstr "Tallenna"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:74
-msgctxt "@title:tab"
-msgid "Prepare"
-msgstr "Valmistele"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:100
-msgctxt "@title:tab"
-msgid "Monitor"
-msgstr "Valvo"
-
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
msgctxt "@label"
msgid "Layer Height"
msgstr "Kerroksen korkeus"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:323
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:345
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:362
msgctxt "@label"
msgid "Print Speed"
msgstr "Tulostusnopeus"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:374
msgctxt "@label"
msgid "Slower"
msgstr "Hitaammin"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:385
msgctxt "@label"
msgid "Faster"
msgstr "Nopeammin"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:388
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:423
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:413
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:446
msgctxt "@label"
msgid "Infill"
msgstr "Täyttö"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:633
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:668
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Asteittainen täyttö lisää täytön tiheyttä vähitellen yläosaa kohti."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:645
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:680
msgctxt "@label"
msgid "Enable gradual"
msgstr "Ota asteittainen käyttöön"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:712
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:747
msgctxt "@label"
msgid "Generate Support"
msgstr "Muodosta tuki"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:746
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:781
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Muodosta rakenteita, jotka tukevat mallin ulokkeita sisältäviä osia. Ilman tukirakenteita kyseiset osat luhistuvat tulostuksen aikana."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:764
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:799
msgctxt "@label"
msgid "Support Extruder"
msgstr "Tuen suulake"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:816
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:851
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Valitse tukena käytettävä suulakepuristin. Näin mallin alle rakennetaan tukirakenteita estämään mallin painuminen tai tulostuminen ilmaan."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:839
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:874
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Alustan tarttuvuus"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:894
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:929
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "Ota reunuksen tai pohjaristikon tulostus käyttöön. Tämä lisää kappaleen ympärille tai alle tasaisen alueen, joka on helppo leikata pois myöhemmin."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:934
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:969
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "Tarvitsetko apua tulosteiden parantamiseen? Lue Ultimakerin vianmääritysoppaat"
@@ -4009,17 +4318,22 @@ msgctxt "@title:window"
msgid "Open project file"
msgstr "Avaa projektitiedosto"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:93
msgctxt "@text:window"
msgid "This is a Cura project file. Would you like to open it as a project or import the models from it?"
msgstr "Tämä on Cura-projektitiedosto. Haluatko avata sen projektina vai tuoda siinä olevat mallit?"
#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:103
+msgctxt "@text:window"
+msgid "Remember my choice"
+msgstr "Muista valintani"
+
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
msgctxt "@action:button"
msgid "Open as project"
msgstr "Avaa projektina"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:131
msgctxt "@action:button"
msgid "Import models"
msgstr "Tuo mallit"
@@ -4029,21 +4343,36 @@ msgctxt "@title:window"
msgid "Engine Log"
msgstr "Moottorin loki"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:245
msgctxt "@label"
msgid "Material"
msgstr "Materiaali"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:349
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:352
msgctxt "@label"
-msgid "Check compatibility"
+msgid "Check compatibility"
msgstr ""
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:369
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Napsauta ja tarkista materiaalin yhteensopivuus sivustolla Ultimaker.com."
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:211
+msgctxt "@option:check"
+msgid "See only current build plate"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:227
+msgctxt "@action:button"
+msgid "Arrange to all build plates"
+msgstr ""
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:247
+msgctxt "@action:button"
+msgid "Arrange current build plate"
+msgstr ""
+
#: MachineSettingsAction/plugin.json
msgctxt "description"
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc)"
@@ -4134,6 +4463,26 @@ msgctxt "name"
msgid "USB printing"
msgstr "USB-tulostus"
+#: PrepareStage/plugin.json
+msgctxt "description"
+msgid "Provides a prepare stage in Cura."
+msgstr ""
+
+#: PrepareStage/plugin.json
+msgctxt "name"
+msgid "Prepare Stage"
+msgstr ""
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "description"
+msgid "Provides an edit window for direct script editing."
+msgstr ""
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "name"
+msgid "Live scripting tool"
+msgstr ""
+
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4154,6 +4503,16 @@ msgctxt "name"
msgid "UM3 Network Connection"
msgstr "UM3-verkkoyhteys"
+#: MonitorStage/plugin.json
+msgctxt "description"
+msgid "Provides a monitor stage in Cura."
+msgstr ""
+
+#: MonitorStage/plugin.json
+msgctxt "name"
+msgid "Monitor Stage"
+msgstr ""
+
#: FirmwareUpdateChecker/plugin.json
msgctxt "description"
msgid "Checks for firmware updates."
@@ -4166,8 +4525,8 @@ msgstr "Laiteohjelmiston päivitysten tarkistus"
#: CuraSolidWorksPlugin/plugin.json
msgctxt "description"
-msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
-msgstr "Mahdollistaa tiettyjen tiedostojen avaamisen SolidWorks-ohjelmiston kautta. Tämän jälkeen tiedostot muunnetaan ja ladataan Curaan."
+msgid "Gives you the possibility to open certain files using SolidWorks itself. Conversion is done by this plugin and additional optimizations."
+msgstr ""
#: CuraSolidWorksPlugin/plugin.json
msgctxt "name"
@@ -4234,6 +4593,16 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Aikaisempien Cura-profiilien lukija"
+#: CuraBlenderPlugin/plugin.json
+msgctxt "description"
+msgid "Helps to open Blender files directly in Cura."
+msgstr ""
+
+#: CuraBlenderPlugin/plugin.json
+msgctxt "name"
+msgid "Blender Integration (experimental)"
+msgstr ""
+
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4394,6 +4763,16 @@ msgctxt "name"
msgid "Cura Profile Writer"
msgstr "Cura-profiilin kirjoitin"
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "description"
+msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+msgstr ""
+
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "name"
+msgid "Print Profile Assistant"
+msgstr ""
+
#: 3MFWriter/plugin.json
msgctxt "description"
msgid "Provides support for writing 3MF files."
@@ -4434,6 +4813,98 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Cura-profiilin lukija"
+#~ msgctxt "@label"
+#~ msgid "Unknown"
+#~ msgstr "Tuntematon"
+
+#~ msgctxt "@info:status"
+#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
+#~ msgstr "SolidWorks-tiedostoa avattaessa ilmeni virheitä! Tarkista, voiko tiedoston avata SolidWorks-ohjelmistossa ilman ongelmia."
+
+#~ msgctxt "@info:status"
+#~ msgid "Error while starting %s!"
+#~ msgstr "%s:n käynnistyksen aikana ilmeni virhe!"
+
+#~ msgctxt "@info"
+#~ msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
+#~ msgstr "Cura kerää anonyymejä viipalointiin liittyviä tilastotietoja. Tämän voi poistaa käytöstä asetuksien kautta."
+
+#~ msgctxt "@action:button"
+#~ msgid "Dismiss"
+#~ msgstr "Ohita"
+
+#~ msgctxt "@label"
+#~ msgid "Material diameter"
+#~ msgstr "Materiaalin halkaisija"
+
+#~ msgctxt "@label"
+#~ msgid "Ultimaker 3"
+#~ msgstr "Ultimaker 3"
+
+#~ msgctxt "@label"
+#~ msgid "Ultimaker 3 Extended"
+#~ msgstr "Ultimaker 3 Extended"
+
+#~ msgctxt "@title:window"
+#~ msgid "Cura SolidWorks Plugin Configuration"
+#~ msgstr "Cura SolidWorks -laajennuksen määritys"
+
+#~ msgctxt "@action:label"
+#~ msgid "Default quality of the exported STL:"
+#~ msgstr "Viedyn STL:n oletuslaatu:"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always ask"
+#~ msgstr "Kysy aina"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always use Fine quality"
+#~ msgstr "Käytä aina hienoa laatua"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always use Coarse quality"
+#~ msgstr "Käytä aina karkeaa laatua"
+
+#~ msgctxt "@title:window"
+#~ msgid "Import SolidWorks File as STL..."
+#~ msgstr "Tuo SolidWorks-tiedosto STL-muodossa..."
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Quality of the Exported STL"
+#~ msgstr "Viedyn STL:n laatu"
+
+#~ msgctxt "@action:label"
+#~ msgid "Quality"
+#~ msgstr "Laatu"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Coarse"
+#~ msgstr "Karkea"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Fine"
+#~ msgstr "Hieno"
+
+#~ msgctxt "@label"
+#~ msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
+#~ msgstr "Tämä asetus koskee aina kaikkia suulakepuristimia. Jos se vaihdetaan tässä, kaikkien suulakepuristimien arvo muuttuu"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save project"
+#~ msgstr "Tallenna projekti"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Prepare"
+#~ msgstr "Valmistele"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Monitor"
+#~ msgstr "Valvo"
+
+#~ msgctxt "description"
+#~ msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
+#~ msgstr "Mahdollistaa tiettyjen tiedostojen avaamisen SolidWorks-ohjelmiston kautta. Tämän jälkeen tiedostot muunnetaan ja ladataan Curaan."
+
#~ msgctxt "@label:status"
#~ msgid "Blocked"
#~ msgstr "Tukossa"
@@ -4454,10 +4925,6 @@ msgstr "Cura-profiilin lukija"
#~ msgid "To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware regularly. This can be done on the {machine_name} (when connected to the network) or via USB."
#~ msgstr "On suositeltavaa päivittää laiteohjelmisto säännöllisesti, jotta voidaan varmistaa, että laitteessa {machine_name} on viimeisimmät ominaisuudet. Tämä voidaan tehdä laitteessa {machine_name} (verkkoon yhdistettynä) tai USB:n kautta."
-#~ msgctxt "@item:inlistbox"
-#~ msgid "Layer view"
-#~ msgstr "Kerrosnäkymä"
-
#~ msgctxt "@info:title"
#~ msgid "Layer View"
#~ msgstr "Kerrosnäkymä"
diff --git a/resources/i18n/fi_FI/fdmextruder.def.json.po b/resources/i18n/fi_FI/fdmextruder.def.json.po
index a76ee285e0..ea50325417 100644
--- a/resources/i18n/fi_FI/fdmextruder.def.json.po
+++ b/resources/i18n/fi_FI/fdmextruder.def.json.po
@@ -6,8 +6,8 @@
msgid ""
msgstr ""
"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: 2017-08-11 14:31+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Finnish\n"
diff --git a/resources/i18n/fi_FI/fdmprinter.def.json.po b/resources/i18n/fi_FI/fdmprinter.def.json.po
index 23ed6b0da4..678d6afcec 100644
--- a/resources/i18n/fi_FI/fdmprinter.def.json.po
+++ b/resources/i18n/fi_FI/fdmprinter.def.json.po
@@ -6,8 +6,8 @@
msgid ""
msgstr ""
"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: 2017-09-27 12:27+0200\n"
"Last-Translator: Bothof \n"
"Language-Team: Finnish\n"
@@ -349,6 +349,16 @@ msgctxt "machine_gcode_flavor option Repetier"
msgid "Repetier"
msgstr "Repetier"
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract label"
+msgid "Firmware Retraction"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract description"
+msgid "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "machine_disallowed_areas label"
msgid "Disallowed areas"
@@ -609,31 +619,6 @@ msgctxt "layer_height_0 description"
msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier."
msgstr "Alkukerroksen korkeus milleinä. Paksumpi alkukerros helpottaa alustaan kiinnittymistä."
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance label"
-msgid "Slicing Tolerance"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance description"
-msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option middle"
-msgid "Middle"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option exclusive"
-msgid "Exclusive"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option inclusive"
-msgid "Inclusive"
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "line_width label"
msgid "Line Width"
@@ -674,16 +659,6 @@ msgctxt "wall_line_width_x description"
msgid "Width of a single wall line for all wall lines except the outermost one."
msgstr "Yhden seinämälinjan leveys. Koskee kaikkia muita paitsi ulommaista seinämää."
-#: fdmprinter.def.json
-msgctxt "roofing_line_width label"
-msgid "Top Surface Skin Line Width"
-msgstr "Yläpinnan pintakalvon linjan leveys"
-
-#: fdmprinter.def.json
-msgctxt "roofing_line_width description"
-msgid "Width of a single line of the areas at the top of the print."
-msgstr "Tulosteen yläosan alueiden yhden linjan leveys."
-
#: fdmprinter.def.json
msgctxt "skin_line_width label"
msgid "Top/Bottom Line Width"
@@ -864,41 +839,6 @@ msgctxt "roofing_layer_count description"
msgid "The number of top most skin layers. Usually only one top most layer is sufficient to generate higher quality top surfaces."
msgstr "Ylimpien pintakalvokerrosten määrä. Yleensä vain yksi ylin kerros riittää tuottamaan korkeampilaatuisia yläpintoja."
-#: fdmprinter.def.json
-msgctxt "roofing_pattern label"
-msgid "Top Surface Skin Pattern"
-msgstr "Yläpinnan pintakalvokuvio"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern description"
-msgid "The pattern of the top most layers."
-msgstr "Ylimpien kerrosten kuvio."
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option lines"
-msgid "Lines"
-msgstr "Linjat"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option concentric"
-msgid "Concentric"
-msgstr "Samankeskinen"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option zigzag"
-msgid "Zig Zag"
-msgstr "Siksak"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles label"
-msgid "Top Surface Skin Line Directions"
-msgstr "Yläpinnan pintakalvon linjojen suunnat"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles description"
-msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
-msgstr "Luettelo käytettävistä linjojen kokonaislukusuunnista, kun yläpinnan pintakalvokerroksilla käytetään linja- tai siksak-kuviota. Tämän luettelon elementtejä käytetään järjestyksessä kerrosten edetessä, ja kun luettelon loppu saavutetaan, aloitetaan taas alusta. Luettelon kohteet on erotettu pilkuilla, ja koko luettelo on hakasulkeiden sisällä. Oletusarvo on tyhjä luettelo, jolloin käytetään perinteisiä oletuskulmia (45 ja 135 astetta)."
-
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr label"
msgid "Top/Bottom Extruder"
@@ -1029,6 +969,16 @@ msgctxt "wall_0_inset description"
msgid "Inset applied to the path of the outer wall. If the outer wall is smaller than the nozzle, and printed after the inner walls, use this offset to get the hole in the nozzle to overlap with the inner walls instead of the outside of the model."
msgstr "Ulkoseinämän reitille asetettu liitos. Jos ulkoseinämä on pienempi kuin suutin ja se tulostetaan sisäseinämien jälkeen, tällä siirtymällä saadaan suuttimen reikä limittymään sisäseinämiin mallin ulkopuolen sijaan."
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order label"
+msgid "Optimize Wall Printing Order"
+msgstr "Optimoi seinämien tulostusjärjestys"
+
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order description"
+msgid "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."
+msgstr "Optimoi seinämien tulostusjärjestys takaisinvetojen ja kuljetun etäisyyden vähentämiseksi. Useimmat osat hyötyvät tämän asetuksen käytöstä, mutta joissakin saattaa kestää kauemmin, joten vertaa tulostusajan arvioita optimointia käytettäessä ja ilman sitä."
+
#: fdmprinter.def.json
msgctxt "outer_inset_first label"
msgid "Outer Before Inner Walls"
@@ -1099,6 +1049,16 @@ msgctxt "fill_perimeter_gaps option everywhere"
msgid "Everywhere"
msgstr "Kaikkialla"
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps label"
+msgid "Filter Out Tiny Gaps"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps description"
+msgid "Filter out tiny gaps to reduce blobs on outside of model."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "fill_outline_gaps label"
msgid "Print Thin Walls"
@@ -1481,7 +1441,7 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "infill_offset_x description"
-msgid "The infill pattern is offset this distance along the X axis."
+msgid "The infill pattern is moved this distance along the X axis."
msgstr ""
#: fdmprinter.def.json
@@ -1491,7 +1451,7 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "infill_offset_y description"
-msgid "The infill pattern is offset this distance along the Y axis."
+msgid "The infill pattern is moved this distance along the Y axis."
msgstr ""
#: fdmprinter.def.json
@@ -1511,8 +1471,8 @@ msgstr "Täytön limityksen prosentti"
#: fdmprinter.def.json
msgctxt "infill_overlap description"
-msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
-msgstr "Limityksen määrä täytön ja seinämien välillä. Pienellä limityksellä seinämät liittyvät tukevasti täyttöön."
+msgid "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill."
+msgstr ""
#: fdmprinter.def.json
msgctxt "infill_overlap_mm label"
@@ -1531,8 +1491,8 @@ msgstr "Pintakalvon limityksen prosentti"
#: fdmprinter.def.json
msgctxt "skin_overlap description"
-msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
-msgstr "Limityksen määrä pintakalvon ja seinämien välillä linjaleveyden prosenttina. Pienellä limityksellä seinämät liittyvät tukevasti pintakalvoon. Tämä on pintakalvon linjojen ja sisimmän seinämän keskimääräisten linjaleveyksien prosenttiluku."
+msgid "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+msgstr ""
#: fdmprinter.def.json
msgctxt "skin_overlap_mm label"
@@ -1694,16 +1654,6 @@ msgctxt "material description"
msgid "Material"
msgstr "Materiaali"
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature label"
-msgid "Auto Temperature"
-msgstr "Automaattinen lämpötila"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature description"
-msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
-msgstr "Muuta kunkin kerroksen lämpötilaa automaattisesti kyseisen kerroksen keskimääräisen virtausnopeuden mukaan."
-
#: fdmprinter.def.json
msgctxt "default_material_print_temperature label"
msgid "Default Printing Temperature"
@@ -1754,16 +1704,6 @@ msgctxt "material_final_print_temperature description"
msgid "The temperature to which to already start cooling down just before the end of printing."
msgstr "Lämpötila, johon jäähdytetään jo ennen tulostuksen loppumista."
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph label"
-msgid "Flow Temperature Graph"
-msgstr "Virtauksen lämpötilakaavio"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph description"
-msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
-msgstr "Tiedot, jotka yhdistävät materiaalivirran (mm3 sekunnissa) lämpötilaan (celsiusastetta)."
-
#: fdmprinter.def.json
msgctxt "material_extrusion_cool_down_speed label"
msgid "Extrusion Cool Down Speed Modifier"
@@ -1781,8 +1721,8 @@ msgstr "Alustan lämpötila"
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
-msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
-msgstr "Lämmitettävän alustan lämpötila. Jos tämä on 0, pöytä ei lämpene tätä tulostusta varten."
+msgid "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted."
+msgstr ""
#: fdmprinter.def.json
msgctxt "material_bed_temperature_layer_0 label"
@@ -3454,6 +3394,16 @@ msgctxt "support_tower_roof_angle description"
msgid "The angle of a rooftop of a tower. A higher value results in pointed tower roofs, a lower value results in flattened tower roofs."
msgstr "Tornin katon kulma. Korkeampi arvo johtaa teräväkärkisiin tornien kattoihin, matalampi arvo litteämpiin tornien kattoihin."
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down label"
+msgid "Drop Down Support Mesh"
+msgstr "Tukiverkon pudottaminen alaspäin"
+
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down description"
+msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
+msgstr "Muodosta tukea kaikkialle tukiverkon alla, niin ettei tukiverkossa ole ulokkeita."
+
#: fdmprinter.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
@@ -4086,16 +4036,6 @@ msgctxt "meshfix_keep_open_polygons description"
msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode."
msgstr "Normaalisti Cura yrittää silmukoida umpeen pieniä reikiä verkosta ja poistaa kerroksesta osat, joissa on isoja reikiä. Tämän vaihtoehdon käyttöönotto pitää ne osat, joita ei voida silmukoida. Tätä tulisi pitää viimeisenä vaihtoehtona, kun millään muulla ei saada aikaan kunnollista GCodea."
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution label"
-msgid "Maximum Resolution"
-msgstr ""
-
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution description"
-msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
-msgstr ""
-
#: fdmprinter.def.json
msgctxt "multiple_mesh_overlap label"
msgid "Merged Meshes Overlap"
@@ -4246,16 +4186,6 @@ msgctxt "support_mesh description"
msgid "Use this mesh to specify support areas. This can be used to generate support structure."
msgstr "Käytä tätä verkkoa tukialueiden valintaan. Sen avulla voidaan luoda tukirakenne."
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down label"
-msgid "Drop Down Support Mesh"
-msgstr "Tukiverkon pudottaminen alaspäin"
-
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down description"
-msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
-msgstr "Muodosta tukea kaikkialle tukiverkon alla, niin ettei tukiverkossa ole ulokkeita."
-
#: fdmprinter.def.json
msgctxt "anti_overhang_mesh label"
msgid "Anti Overhang Mesh"
@@ -4332,14 +4262,194 @@ msgid "experimental!"
msgstr "kokeellinen!"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order label"
-msgid "Optimize Wall Printing Order"
-msgstr "Optimoi seinämien tulostusjärjestys"
+msgctxt "support_tree_enable label"
+msgid "Tree Support"
+msgstr ""
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order description"
-msgid "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."
-msgstr "Optimoi seinämien tulostusjärjestys takaisinvetojen ja kuljetun etäisyyden vähentämiseksi. Useimmat osat hyötyvät tämän asetuksen käytöstä, mutta joissakin saattaa kestää kauemmin, joten vertaa tulostusajan arvioita optimointia käytettäessä ja ilman sitä."
+msgctxt "support_tree_enable description"
+msgid "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle label"
+msgid "Tree Support Branch Angle"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle description"
+msgid "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance label"
+msgid "Tree Support Branch Distance"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance description"
+msgid "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter label"
+msgid "Tree Support Branch Diameter"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter description"
+msgid "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle label"
+msgid "Tree Support Branch Diameter Angle"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle description"
+msgid "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution label"
+msgid "Tree Support Collision Resolution"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution description"
+msgid "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness label"
+msgid "Tree Support Wall Thickness"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness description"
+msgid "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count label"
+msgid "Tree Support Wall Line Count"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count description"
+msgid "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance label"
+msgid "Slicing Tolerance"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance description"
+msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option middle"
+msgid "Middle"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option exclusive"
+msgid "Exclusive"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option inclusive"
+msgid "Inclusive"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width label"
+msgid "Top Surface Skin Line Width"
+msgstr "Yläpinnan pintakalvon linjan leveys"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width description"
+msgid "Width of a single line of the areas at the top of the print."
+msgstr "Tulosteen yläosan alueiden yhden linjan leveys."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern label"
+msgid "Top Surface Skin Pattern"
+msgstr "Yläpinnan pintakalvokuvio"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern description"
+msgid "The pattern of the top most layers."
+msgstr "Ylimpien kerrosten kuvio."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option lines"
+msgid "Lines"
+msgstr "Linjat"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option concentric"
+msgid "Concentric"
+msgstr "Samankeskinen"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option zigzag"
+msgid "Zig Zag"
+msgstr "Siksak"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles label"
+msgid "Top Surface Skin Line Directions"
+msgstr "Yläpinnan pintakalvon linjojen suunnat"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles description"
+msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
+msgstr "Luettelo käytettävistä linjojen kokonaislukusuunnista, kun yläpinnan pintakalvokerroksilla käytetään linja- tai siksak-kuviota. Tämän luettelon elementtejä käytetään järjestyksessä kerrosten edetessä, ja kun luettelon loppu saavutetaan, aloitetaan taas alusta. Luettelon kohteet on erotettu pilkuilla, ja koko luettelo on hakasulkeiden sisällä. Oletusarvo on tyhjä luettelo, jolloin käytetään perinteisiä oletuskulmia (45 ja 135 astetta)."
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization label"
+msgid "Infill Travel Optimization"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization description"
+msgid "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature label"
+msgid "Auto Temperature"
+msgstr "Automaattinen lämpötila"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature description"
+msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
+msgstr "Muuta kunkin kerroksen lämpötilaa automaattisesti kyseisen kerroksen keskimääräisen virtausnopeuden mukaan."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph label"
+msgid "Flow Temperature Graph"
+msgstr "Virtauksen lämpötilakaavio"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph description"
+msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
+msgstr "Tiedot, jotka yhdistävät materiaalivirran (mm3 sekunnissa) lämpötilaan (celsiusastetta)."
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution label"
+msgid "Maximum Resolution"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution description"
+msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
+msgstr ""
#: fdmprinter.def.json
msgctxt "support_skip_some_zags label"
@@ -4940,6 +5050,46 @@ msgctxt "wireframe_nozzle_clearance description"
msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing."
msgstr "Suuttimen ja vaakasuoraan laskevien linjojen välinen etäisyys. Suurempi väli aiheuttaa vähemmän jyrkän kulman diagonaalisesti laskeviin linjoihin, mikä puolestaan johtaa harvempiin yläliitoksiin seuraavan kerroksen kanssa. Koskee vain rautalankamallin tulostusta."
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled label"
+msgid "Use adaptive layers"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled description"
+msgid "Adaptive layers computes the layer heights depending on the shape of the model."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation label"
+msgid "Adaptive layers maximum variation"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation description"
+msgid "The maximum allowed height different from the base layer height in mm."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step label"
+msgid "Adaptive layers variation step size"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step description"
+msgid "The difference in height of the next layer height compared to the previous one."
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold label"
+msgid "Adaptive layers threshold"
+msgstr ""
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold description"
+msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
+msgstr ""
+
#: fdmprinter.def.json
msgctxt "command_line_settings label"
msgid "Command Line Settings"
@@ -5000,6 +5150,18 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Mallissa käytettävä muunnosmatriisi, kun malli ladataan tiedostosta."
+#~ msgctxt "infill_overlap description"
+#~ msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
+#~ msgstr "Limityksen määrä täytön ja seinämien välillä. Pienellä limityksellä seinämät liittyvät tukevasti täyttöön."
+
+#~ msgctxt "skin_overlap description"
+#~ msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+#~ msgstr "Limityksen määrä pintakalvon ja seinämien välillä linjaleveyden prosenttina. Pienellä limityksellä seinämät liittyvät tukevasti pintakalvoon. Tämä on pintakalvon linjojen ja sisimmän seinämän keskimääräisten linjaleveyksien prosenttiluku."
+
+#~ msgctxt "material_bed_temperature description"
+#~ msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
+#~ msgstr "Lämmitettävän alustan lämpötila. Jos tämä on 0, pöytä ei lämpene tätä tulostusta varten."
+
#~ msgctxt "wall_x_extruder_nr label"
#~ msgid "Inner Walls Extruder"
#~ msgstr "Sisäseinämien suulake"
diff --git a/resources/i18n/fr_FR/cura.po b/resources/i18n/fr_FR/cura.po
index 4db845190f..ee4e175f8a 100644
--- a/resources/i18n/fr_FR/cura.po
+++ b/resources/i18n/fr_FR/cura.po
@@ -1,22 +1,24 @@
# Cura
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-11-21 16:58+0100\n"
-"PO-Revision-Date: 2017-11-30 13:05+0100\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
+"PO-Revision-Date: 2018-02-13 17:26+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: French\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 2.0.6\n"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:29
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
msgctxt "@action"
msgid "Machine Settings"
msgstr "Paramètres de la machine"
@@ -53,12 +55,11 @@ msgstr "Connexion avec Doodle3D Connecter..."
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:646
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:875
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:659
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:370
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:78
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:104
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:99
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:355
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:376
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:139
@@ -98,7 +99,7 @@ msgctxt "@info:tooltip"
msgid "Open the Doodle3D Connect web interface"
msgstr "Ouvrir l'interface web Doodle3D Connecter"
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:34
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Afficher le récapitulatif des changements"
@@ -106,85 +107,90 @@ msgstr "Afficher le récapitulatif des changements"
#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20
msgctxt "@item:inmenu"
msgid "Flatten active settings"
-msgstr "Aplatir les paramètres actifs"
+msgstr "Réduire les paramètres actifs"
#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32
msgctxt "@info:status"
msgid "Profile has been flattened & activated."
-msgstr "Le profil a été aplati et activé."
+msgstr "Le profil a été réduit et activé."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:26
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "Impression par USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Imprimer via USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:29
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Imprimer via USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:31
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Connecté via USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:status"
msgid "Unable to start a new job because the printer is busy or not connected."
msgstr "Impossible de démarrer une nouvelle tâche car l'imprimante est occupée ou n'est pas connectée."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:title"
msgid "Printer Unavailable"
msgstr "Imprimante indisponible"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:status"
msgid "This printer does not support USB printing because it uses UltiGCode flavor."
-msgstr "L'imprimante ne prend pas en charge l'impression par USB car elle utilise UltiGCode parfum."
+msgstr "L'imprimante ne prend pas en charge l'impression par USB car elle utilise UltiGCode."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:title"
msgid "USB Printing"
msgstr "Impression par USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
msgctxt "@info:status"
msgid "Unable to start a new job because the printer does not support usb printing."
msgstr "Impossible de démarrer une nouvelle tâche car l'imprimante ne prend pas en charge l'impression par USB."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1349
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:946
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1418
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1496
msgctxt "@info:title"
msgid "Warning"
msgstr "Avertissement"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
msgctxt "@info"
msgid "Unable to update firmware because there are no printers connected."
msgstr "Impossible de mettre à jour le firmware car il n'y a aucune imprimante connectée."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
#, python-format
msgctxt "@info"
msgid "Could not find firmware required for the printer at %s."
msgstr "Impossible de trouver le firmware requis pour l'imprimante sur %s."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
msgctxt "@info:title"
msgid "Printer Firmware"
msgstr "Firmware de l'imprimante"
+#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Prepare"
+msgstr "Préparer"
+
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Save to Removable Drive"
@@ -205,7 +211,7 @@ msgstr "Enregistrement sur le lecteur amovible {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:89
msgctxt "@info:title"
msgid "Saving"
-msgstr "Enregistrement..."
+msgstr "Enregistrement"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:99
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:102
@@ -228,11 +234,11 @@ msgid "Could not save to removable drive {0}: {1}"
msgstr "Impossible d'enregistrer sur le lecteur {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:146
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:693
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:701
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:153
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1358
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:160
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1427
msgctxt "@info:title"
msgid "Error"
msgstr "Erreur"
@@ -263,7 +269,7 @@ msgstr "Ejecter le lecteur amovible {0}"
#, python-brace-format
msgctxt "@info:status"
msgid "Ejected {0}. You can now safely remove the drive."
-msgstr "Lecteur {0} éjecté. Vous pouvez maintenant le retirer en tout sécurité."
+msgstr "Lecteur {0} éjecté. Vous pouvez maintenant le retirer en toute sécurité."
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:156
msgctxt "@info:title"
@@ -282,7 +288,7 @@ msgid "Removable Drive"
msgstr "Lecteur amovible"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:53
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:51
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Imprimer sur le réseau"
@@ -368,22 +374,22 @@ msgstr "Connecté sur le réseau. Pas d'accès pour commander l'imprimante."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:410
msgctxt "@info:status"
msgid "Access request was denied on the printer."
-msgstr "La demande d'accès a été refusée sur l'imprimante."
+msgstr "La demande d'accès à l'imprimante a été refusée."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:413
msgctxt "@info:status"
msgid "Access request failed due to a timeout."
-msgstr "Échec de la demande d'accès à cause de la durée limite."
+msgstr "Durée d'attente dépassée. Échec de la demande d'accès."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:478
msgctxt "@info:status"
msgid "The connection with the network was lost."
-msgstr "La connexion avec le réseau a été perdue."
+msgstr "Interruption de connexion au le réseau."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:501
msgctxt "@info:status"
msgid "The connection with the printer was lost. Check your printer to see if it is connected."
-msgstr "La connexion avec l'imprimante a été perdue. Vérifiez que votre imprimante est connectée."
+msgstr "La connexion avec l'imprimante est interrompue. Vérifiez que votre imprimante est connectée."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:666
#, python-format
@@ -396,113 +402,113 @@ msgctxt "@info:title"
msgid "Printer Status"
msgstr "Statut de l'imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:691
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No Printcore loaded in slot {0}"
msgstr "Impossible de démarrer une nouvelle tâche d'impression. Pas de PrintCore inséré dans la fente {0}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:699
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No material loaded in slot {0}"
msgstr "Impossible de démarrer une nouvelle tâche d'impression car l'imprimante est occupée. Pas de matériau chargé dans la fente {0}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:709
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:710
#, python-brace-format
msgctxt "@label"
msgid "Not enough material for spool {0}."
-msgstr "Pas suffisamment de matériau pour bobine {0}."
+msgstr "Pas suffisamment de matériau pour la bobine {0}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:719
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:720
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}"
-msgstr "PrintCore différent (Cura : {0}, Imprimante : {1}) sélectionné pour l'extrudeuse {2}"
+msgstr "PrintCore différent (Cura : {0}, Imprimante : {1}) sélectionné pour l'extrudeur {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:733
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:734
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
-msgstr "Matériau différent (Cura : {0}, Imprimante : {1}) sélectionné pour l'extrudeuse {2}"
+msgstr "Matériau différent (Cura : {0}, Imprimante : {1}) sélectionné pour l'extrudeur {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:741
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:742
#, python-brace-format
msgctxt "@label"
msgid "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer."
msgstr "Le PrintCore {0} n'est pas correctement calibré. Le calibrage XY doit être effectué sur l'imprimante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:746
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "Êtes-vous sûr(e) de vouloir imprimer avec la configuration sélectionnée ?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:748
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
-msgstr "Problème de compatibilité entre la configuration ou l'étalonnage de l'imprimante et Cura. Pour un résultat optimal, découpez toujours pour les PrintCores et matériaux insérés dans votre imprimante."
+msgstr "Problème de compatibilité entre la configuration ou la calibration de l'imprimante et Cura. Pour un résultat optimal, découpez toujours les PrintCores et matériaux insérés dans votre imprimante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:753
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:754
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Configuration différente"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:864
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:865
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:258
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Envoi de nouvelles tâches (temporairement) bloqué, envoi de la tâche d'impression précédente en cours."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Envoi des données à l'imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:title"
msgid "Sending Data"
-msgstr "Envoi des données..."
+msgstr "Envoi des données"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:944
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
msgctxt "@info:status"
msgid "Unable to send data to printer. Is another job still active?"
msgstr "Impossible d'envoyer les données à l'imprimante. Une autre tâche est-elle toujours active ?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1085
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1087
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:196
msgctxt "@label:MonitorStatus"
msgid "Aborting print..."
msgstr "Abandon de l'impression..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1091
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1093
msgctxt "@label:MonitorStatus"
msgid "Print aborted. Please check the printer"
msgstr "Abandon de l'impression. Vérifiez l'imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1097
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
msgctxt "@label:MonitorStatus"
msgid "Pausing print..."
msgstr "Mise en pause de l'impression..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1101
msgctxt "@label:MonitorStatus"
msgid "Resuming print..."
msgstr "Reprise de l'impression..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1289
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Synchroniser avec votre imprimante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
-msgctxt "@label"
-msgid "Would you like to use your current printer configuration in Cura?"
-msgstr "Voulez-vous utiliser votre configuration d'imprimante actuelle dans Cura ?"
-
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
msgctxt "@label"
+msgid "Would you like to use your current printer configuration in Cura?"
+msgstr "Voulez-vous utiliser votre configuration actuelle d'imprimante dans Cura ?"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1295
+msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
-msgstr "Les PrintCores et / ou matériaux sur votre imprimante diffèrent de ceux de votre projet actuel. Pour un résultat optimal, découpez toujours pour les PrintCores et matériaux insérés dans votre imprimante."
+msgstr "Les PrintCores et / ou les matériaux sur votre imprimante sont différents de ceux de votre projet actuel. Pour un résultat optimal, découpez toujours les PrintCores et matériaux insérés dans votre imprimante."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:112
msgid "This printer is not set up to host a group of connected Ultimaker 3 printers."
@@ -517,164 +523,252 @@ msgstr "L'imprimante n'est pas configurée pour héberger un groupe de {count} i
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:114
#, python-brace-format
msgid "{printer_name} has finished printing '{job_name}'. Please collect the print and confirm clearing the build plate."
-msgstr "{printer_name} a terminé d'imprimer '{job_name}'. Veuillez enlever l'impression et confirmer avoir débarrassé le plateau."
+msgstr "{printer_name} a terminé d'imprimer '{job_name}'. Veuillez enlever l'impression et confirmer avoir nettoyé le plateau."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:115
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:520
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:533
#, python-brace-format
msgid "{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing."
msgstr "{printer_name} est réservé pour imprimer '{job_name}'. Veuillez modifier la configuration de l'imprimante pour qu'elle corresponde à la tâche et commence l'impression."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:277
msgctxt "@info:status"
msgid "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."
msgstr "Impossible d'envoyer une nouvelle tâche d'impression : cette imprimante 3D n'est pas (encore) configurée pour héberger un groupe d'imprimantes connectées Ultimaker 3."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:410
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to send print job to group {cluster_name}."
msgstr "Impossible d'envoyer la tâche d'impression vers le groupe {cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:418
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:431
#, python-brace-format
msgctxt "@info:status"
msgid "Sent {file_name} to group {cluster_name}."
msgstr "{file_name} envoyé vers le groupe {cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:436
msgctxt "@action:button"
msgid "Show print jobs"
msgstr "Afficher les tâches d'impression"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:424
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:437
msgctxt "@info:tooltip"
msgid "Opens the print jobs interface in your browser."
msgstr "Ouvre l'interface d'impression des tâches dans votre navigateur."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:502
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Unknown"
msgstr "Inconnu"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:492
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "{printer_name} a terminé d'imprimer '{job_name}'."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:494
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:497
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:507
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:510
msgctxt "@info:status"
msgid "Print finished"
msgstr "Impression terminée"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:522
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:525
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:535
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:538
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:282
msgctxt "@label:status"
msgid "Action required"
msgstr "Action requise"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:643
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:656
#, python-brace-format
msgctxt "@info:progress"
msgid "Sending {file_name} to group {cluster_name}"
msgstr "Envoi de {file_name} vers le groupe {cluster_name}..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:19
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:17
msgctxt "@action"
msgid "Connect via Network"
msgstr "Connecter via le réseau"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:64
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Monitor"
+msgstr "Surveiller"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
-msgstr "De nouvelles fonctionnalités sont disponibles pour votre {machine_name} ! Il est recommandé de mettre à jour le firmware sur votre imprimante."
+msgstr "De nouvelles fonctionnalités sont disponibles pour votre {machine_name} ! Il est recommandé de mettre à jour le firmware de votre imprimante."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:65
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:67
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Nouveau firmware %s disponible"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:68
msgctxt "@action:button"
msgid "How to update"
msgstr "Comment effectuer la mise à jour"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:77
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:79
msgctxt "@info"
msgid "Could not access update information."
msgstr "Impossible d'accéder aux informations de mise à jour."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:199
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
msgctxt "@info:status"
-msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
-msgstr "Des erreurs sont apparues lors de l'ouverture de votre fichier SolidWorks ! Veuillez vérifier s'il est possible d'ouvrir votre fichier dans SolidWorks sans que cela ne cause de problèmes."
+msgid "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself."
+msgstr "SolidWorks a signalé des erreurs lors de l'ouverture de votre fichier. Nous vous recommandons de résoudre ces problèmes dans SolidWorks lui-même."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:31
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
+msgctxt "@info:status"
+msgid ""
+"Found no models inside your drawing. Could you please check it's content again and make sure one part or assembly is inside?\n"
+"\n"
+" Thanks!."
+msgstr ""
+"Aucun modèle n'a été trouvé à l'intérieur de votre dessin. Pouvez-vous vérifier son contenu de nouveau et vous assurer qu'une pièce ou un assemblage est présent ?\n"
+"\n"
+" Merci !"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
+msgctxt "@info:status"
+msgid ""
+"Found more then one part or assembly inside your drawing. We currently only support drawings with exactly one part or assembly inside.\n"
+"\n"
+"Sorry!"
+msgstr ""
+"Plus d'une pièce ou d'un ensemble de pièces ont été trouvés dans votre dessin. Nous ne prenons actuellement en charge que les dessins comptant exactement une pièce ou un ensemble de pièces.\n"
+"\n"
+"Désolé !"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
msgctxt "@item:inlistbox"
msgid "SolidWorks part file"
msgstr "Fichier de composant SolidWorks"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:35
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:29
msgctxt "@item:inlistbox"
msgid "SolidWorks assembly file"
msgstr "Fichier d'assemblage SolidWorks"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.py:21
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
+msgctxt "@item:inlistbox"
+msgid "SolidWorks drawing file"
+msgstr "Fichier de dessin SolidWorks"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"We could not find a valid installation of SolidWorks on your system. That means that either SolidWorks is not installed or you don't own an valid license. Please make sure that running SolidWorks itself works without issues and/or contact your ICT.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+"Cher client,\n"
+"Nous n'avons pas pu trouver une installation valide de SolidWorks sur votre système. Cela signifie soit que SolidWorks n'est pas installé, soit que vous ne possédez pas de licence valide. Veuillez vous assurer que l'exécution de SolidWorks lui-même fonctionne sans problèmes et / ou contactez votre service IT.\n"
+"\n"
+"Cordialement,\n"
+" - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"You are currently running this plugin on an operating system other than Windows. This plugin will only work on Windows with SolidWorks installed, including an valid license. Please install this plugin on a Windows machine with SolidWorks installed.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+"Cher client,\n"
+"Vous exécutez actuellement ce plug-in sur un système d'exploitation autre que Windows. Ce plug-in fonctionne uniquement sous Windows et lorsque SolidWorks est installé avec une licence valide. Veuillez installer ce plug-in sur un poste Windows où SolidWorks est installé.\n"
+"\n"
+"Cordialement,\n"
+" - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
msgid "Configure"
msgstr "Configurer"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/CommonComReader.py:135
-#, python-format
-msgctxt "@info:status"
-msgid "Error while starting %s!"
-msgstr "Erreur lors du lancement de %s !"
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
+msgid "Installation guide for SolidWorks macro"
+msgstr "Guide d'installation SolidWorks macro"
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
-msgid "Simulation view"
-msgstr "Vue simulation"
+msgid "Layer view"
+msgstr "Vue en couches"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "Cura n'affiche pas les couches avec précision lorsque l'impression filaire est activée"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:101
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
msgctxt "@info:title"
msgid "Simulation View"
msgstr "Vue simulation"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:26
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:25
msgid "Modify G-Code"
msgstr "Modifier le G-Code"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
msgctxt "@info"
-msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
-msgstr "Cura collecte des statistiques anonymes sur le découpage. Vous pouvez désactiver cette fonctionnalité dans les préférences."
+msgid "Cura collects anonymized usage statistics."
+msgstr "Cura recueille des statistiques d'utilisation anonymes."
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
msgctxt "@info:title"
msgid "Collecting Data"
-msgstr "Collecte des données..."
+msgstr "Collecte de données"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
msgctxt "@action:button"
-msgid "Dismiss"
-msgstr "Ignorer"
+msgid "Allow"
+msgstr "Autoriser"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+msgctxt "@action:tooltip"
+msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
+msgstr "Autoriser Cura à envoyer des statistiques d'utilisation anonymes pour mieux prioriser les améliorations futures apportées à Cura. Certaines de vos préférences et paramètres sont envoyés, ainsi que la version du logiciel Cura et un hachage des modèles que vous découpez."
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+msgctxt "@action:button"
+msgid "Disable"
+msgstr "Désactiver"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
+msgctxt "@action:tooltip"
+msgid "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."
+msgstr "Ne pas autoriser Cura à envoyer des statistiques d'utilisation anonymes. Vous pouvez modifier ce paramètre dans les préférences."
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Profils Cura 15.04"
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
+msgctxt "@item:inlistbox"
+msgid "Blender file"
+msgstr "Fichier Blender"
+
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
+msgctxt "@info:status"
+msgid ""
+"Could not export using \"{}\" quality!\n"
+"Felt back to \"{}\"."
+msgstr ""
+"Impossible d'exporter avec la qualité \"{}\" !\n"
+"Qualité redéfinie sur \"{}\"."
+
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
msgctxt "@item:inlistbox"
@@ -706,49 +800,49 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "Image GIF"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "Impossible de découper le matériau actuel, car celui-ci est incompatible avec la machine ou la configuration sélectionnée."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:297
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:319
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:327
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:336
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:349
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:357
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "Impossible de découper"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:296
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "Impossible de couper avec les paramètres actuels. Les paramètres suivants contiennent des erreurs : {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:318
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:348
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr "Impossible de couper en raison de certains paramètres par modèle. Les paramètres suivants contiennent des erreurs sur un ou plusieurs modèles : {error_labels}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:356
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "Impossible de couper car la tour primaire ou la (les) position(s) d'amorçage ne sont pas valides."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:335
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:365
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "Rien à couper car aucun des modèles ne convient au volume d'impression. Mettez à l'échelle ou faites pivoter les modèles pour les faire correspondre."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Traitement des couches"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:title"
msgid "Information"
msgstr "Informations"
@@ -785,14 +879,14 @@ msgstr "Échec de la copie des fichiers plug-ins Siemens NX. Veuillez vérifier
msgid "Failed to install Siemens NX plugin. Could not set environment variable UGII_USER_DIR for Siemens NX."
msgstr "Échec de l'installation du plug-in Siemens NX. Impossible de définir la variable d'environnement UGII_USER_DIR pour Siemens NX."
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:585
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:165
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Recommandé"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:169
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:595
msgctxt "@title:tab"
msgid "Custom"
msgstr "Personnalisé"
@@ -803,24 +897,24 @@ msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "Fichier 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:126
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1142
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:159
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1185
msgctxt "@label"
msgid "Nozzle"
msgstr "Buse"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:152
#, python-brace-format
msgctxt "@info:status"
msgid "Failed to get plugin ID from {0}"
msgstr "Échec de l'obtention de l'identifiant du plug-in au départ de {0}"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:165
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:153
msgctxt "@info:tile"
msgid "Warning"
msgstr "Avertissement"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:203
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:191
msgctxt "@window:title"
msgid "Plugin browser"
msgstr "Navigateur de plug-ins"
@@ -835,18 +929,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "Fichier G"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:314
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:321
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "Analyse du G-Code"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:316
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:426
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:323
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:464
msgctxt "@info:title"
msgid "G-code Details"
msgstr "Détails G-Code"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:424
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:462
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "Assurez-vous que le g-code est adapté à votre imprimante et à la configuration de l'imprimante avant d'y envoyer le fichier. La représentation du g-code peut ne pas être exacte."
@@ -857,6 +951,16 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Profil Cura"
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Profile Assistant"
+msgstr "Assistant de profil"
+
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
+msgctxt "@item:inlistbox"
+msgid "Profile Assistant"
+msgstr "Assistant de profil"
+
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
msgctxt "@item:inlistbox"
msgid "3MF file"
@@ -881,149 +985,123 @@ msgstr "Mise à niveau du firmware"
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14
msgctxt "@action"
msgid "Checkup"
-msgstr "Check-up"
+msgstr "Vérification"
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.py:15
msgctxt "@action"
msgid "Level build plate"
-msgstr "Nivellement du plateau"
+msgstr "Paramétrage du plateau de fabrication"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Paroi externe"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Parois internes"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
msgctxt "@tooltip"
msgid "Skin"
msgstr "Couche extérieure"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
msgctxt "@tooltip"
msgid "Infill"
msgstr "Remplissage"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:93
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "Remplissage du support"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:94
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "Interface du support"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:95
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
msgctxt "@tooltip"
msgid "Support"
msgstr "Support"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:96
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
msgctxt "@tooltip"
msgid "Skirt"
-msgstr "Jupe"
+msgstr "Contourner"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:97
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
msgctxt "@tooltip"
msgid "Travel"
msgstr "Déplacement"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Rétractions"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
msgctxt "@tooltip"
msgid "Other"
msgstr "Autre"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:199
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:231
msgctxt "@label unknown material"
msgid "Unknown"
msgstr "Inconnu"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:284
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:318
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "Fichier {0} prédécoupé"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:469
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:440
msgctxt "@item:material"
msgid "No material loaded"
msgstr "Pas de matériau chargé"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:476
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:447
msgctxt "@item:material"
msgid "Unknown material"
msgstr "Matériau inconnu"
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:30
-msgctxt "@info:status"
-msgid "Finding new location for objects"
-msgstr "Recherche d'un nouvel emplacement pour les objets"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:34
-msgctxt "@info:title"
-msgid "Finding Location"
-msgstr "Recherche d'emplacement..."
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:89
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
-msgctxt "@info:status"
-msgid "Unable to find a location within the build volume for all objects"
-msgstr "Impossible de trouver un emplacement dans le volume d'impression pour tous les objets"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:90
-msgctxt "@info:title"
-msgid "Can't Find Location"
-msgstr "Impossible de trouver un emplacement"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:431
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:113
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:437
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:120
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "Le fichier existe déjà"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:432
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:114
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:438
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
msgid "The file {0} already exists. Are you sure you want to overwrite it?"
msgstr "Le fichier {0} existe déjà. Êtes-vous sûr de vouloir le remplacer ?"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:872
msgctxt "@label"
msgid "Custom"
msgstr "Personnalisé"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:819
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:876
msgctxt "@label"
msgid "Custom Material"
msgstr "Matériau personnalisé"
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:182
-msgctxt "@menuitem"
-msgid "Global"
-msgstr "Global"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:229
+#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:205
msgctxt "@menuitem"
msgid "Not overridden"
-msgstr "Pas écrasé"
+msgstr "Pas pris en compte"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:124
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "Le matériau sélectionné est incompatible avec la machine ou la configuration sélectionnée."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:125
#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24
msgctxt "@info:title"
msgid "Incompatible Material"
@@ -1044,67 +1122,89 @@ msgctxt "@action"
msgid "Undo changing the material diameter."
msgstr "Annuler la modification du diamètre du matériau."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:144
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to export profile to {0}: {1}"
msgstr "Échec de l'exportation du profil vers {0} : {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:158
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Failed to export profile to {0}: Writer plugin reported failure."
msgstr "Échec de l'exportation du profil vers {0} : le plug-in du générateur a rapporté une erreur."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:156
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:163
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Exported profile to {0}"
msgstr "Profil exporté vers {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:157
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:164
msgctxt "@info:title"
msgid "Export succeeded"
msgstr "L'exportation a réussi"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:183
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:205
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:214
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:248
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:211
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:271
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
msgstr "Échec de l'importation du profil depuis le fichier {0} : {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:216
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:252
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:230
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "This profile {0} contains incorrect data, could not import it."
+msgstr "Le profil {0} contient des données incorrectes ; échec de l'importation."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "The machine defined in profile {0} doesn't match with your current machine, could not import it."
+msgstr "La machine définie dans le profil {0} ne correspond pas à votre machine actuelle ; échec de l'importation."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Importation du profil {0} réussie"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:255
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:277
+#, python-brace-format
+msgctxt "@info:status"
+msgid "File {0} does not contain any valid profile."
+msgstr "Le fichier {0} ne contient pas de profil valide."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "Le profil {0} est un type de fichier inconnu ou est corrompu."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:298
msgctxt "@label"
msgid "Custom profile"
msgstr "Personnaliser le profil"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:285
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Il manque un type de qualité au profil."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:349
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "Impossible de trouver un type de qualité {0} pour la configuration actuelle."
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:46
+#, python-brace-format
+msgctxt "@label"
+msgid "Group #{group_nr}"
+msgstr "Groupe nº {group_nr}"
+
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
@@ -1115,142 +1215,170 @@ msgctxt "@info:title"
msgid "Build Volume"
msgstr "Volume d'impression"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:34
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:25
msgctxt "@info:status"
msgid "Multiplying and placing objects"
msgstr "Multiplication et placement d'objets"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:35
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:26
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
msgctxt "@info:title"
msgid "Placing Object"
-msgstr "Placement de l'objet..."
+msgstr "Placement de l'objet"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:80
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:88
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:152
+msgctxt "@info:status"
+msgid "Unable to find a location within the build volume for all objects"
+msgstr "Impossible de trouver un emplacement dans le volume d'impression pour tous les objets"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:29
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:64
+msgctxt "@info:status"
+msgid "Finding new location for objects"
+msgstr "Recherche d'un nouvel emplacement pour les objets"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:33
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:68
+msgctxt "@info:title"
+msgid "Finding Location"
+msgstr "Recherche d'emplacement..."
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:89
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:153
+msgctxt "@info:title"
+msgid "Can't Find Location"
+msgstr "Impossible de trouver un emplacement"
+
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:81
msgctxt "@title:window"
msgid "Crash Report"
msgstr "Rapport d'incident"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:93
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:94
msgctxt "@label crash message"
msgid ""
-"
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+"
A fatal error has occurred. Please send us this Crash Report to fix the problem
\n"
"
Please use the \"Send report\" button to post a bug report automatically to our servers
\n"
" "
-msgstr "
Une exception fatale s'est produite. Veuillez nous envoyer ce Rapport d'incident pour résoudre le problème
\n
Veuillez utiliser le bouton « Envoyer rapport » pour publier automatiquement un rapport d'erreur sur nos serveurs
\n "
+msgstr ""
+"
Une erreur fatale s'est produite. Veuillez nous envoyer ce Rapport d'incident pour résoudre le problème
\n"
+"
Veuillez utiliser le bouton « Envoyer rapport » pour publier automatiquement un rapport d'erreur sur nos serveurs
"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:141
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:147
msgctxt "@title:groupbox"
-msgid "Exception traceback"
-msgstr "Retraçage de l'exception"
+msgid "Error traceback"
+msgstr "Retraçage de l'erreur"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:208
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:214
msgctxt "@title:groupbox"
msgid "Logs"
-msgstr "Journaux"
+msgstr "Registres"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:231
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:237
msgctxt "@title:groupbox"
msgid "User description"
msgstr "Description de l'utilisateur"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:246
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:252
msgctxt "@action:button"
msgid "Send report"
msgstr "Envoyer rapport"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:256
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:274
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Chargement des machines..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:661
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:660
msgctxt "@info:progress"
msgid "Setting up scene..."
-msgstr "Préparation de la scène..."
+msgstr "Préparation de la tâche..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:703
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:702
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Chargement de l'interface..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:874
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:899
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1348
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "Un seul fichier G-Code peut être chargé à la fois. Importation de {0} sautée"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1357
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1426
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "Impossible d'ouvrir un autre fichier si le G-Code est en cours de chargement. Importation de {0} sautée"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1416
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1495
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "Le modèle sélectionné était trop petit pour être chargé."
@@ -1279,12 +1407,11 @@ msgstr "X (Largeur)"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:288
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:300
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:391
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:413
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:840
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:394
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:424
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:849
msgctxt "@label"
msgid "mm"
msgstr "mm"
@@ -1374,71 +1501,70 @@ msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "La différence de hauteur entre la pointe de la buse et le système de portique (axes X et Y). Permet d'empêcher les collisions entre les impressions précédentes et le portique lors d'une impression « Un à la fois »."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:254
msgctxt "@label"
msgid "Number of Extruders"
-msgstr "Nombre d'extrudeuses"
+msgstr "Nombre d'extrudeurs"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:289
-msgctxt "@tooltip"
-msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
-msgstr "Le diamètre nominal de filament pris en charge par l'imprimante. Le diamètre exact sera remplacé par le matériau et / ou le profil."
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:291
-msgctxt "@label"
-msgid "Material diameter"
-msgstr "Diamètre du matériau"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:299
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:390
-msgctxt "@label"
-msgid "Nozzle size"
-msgstr "Taille de la buse"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:317
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:310
msgctxt "@label"
msgid "Start Gcode"
msgstr "Début Gcode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:327
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:320
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very start."
msgstr "Commandes Gcode à exécuter au tout début."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:336
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:329
msgctxt "@label"
msgid "End Gcode"
msgstr "Fin Gcode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:346
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:339
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very end."
msgstr "Commandes Gcode à exécuter tout à la fin."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:378
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:370
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Paramètres de la buse"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:382
+msgctxt "@label"
+msgid "Nozzle size"
+msgstr "Taille de la buse"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:393
+msgctxt "@label"
+msgid "Compatible material diameter"
+msgstr "Diamètre du matériau compatible"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:395
+msgctxt "@tooltip"
+msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
+msgstr "Le diamètre nominal de filament pris en charge par l'imprimante. Le diamètre exact sera remplacé par le matériau et / ou le profil."
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:411
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "Décalage buse X"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:423
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Décalage buse Y"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:433
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:444
msgctxt "@label"
msgid "Extruder Start Gcode"
-msgstr "Extrudeuse Gcode de démarrage"
+msgstr "Extrudeur Gcode de démarrage"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:462
msgctxt "@label"
msgid "Extruder End Gcode"
-msgstr "Extrudeuse Gcode de fin"
+msgstr "Extrudeur Gcode de fin"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:18
msgctxt "@label"
@@ -1448,8 +1574,9 @@ msgstr "Récapitulatif des changements"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:107
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:55
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:445
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:357
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:306
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:456
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:492
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:80
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:123
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
@@ -1514,7 +1641,10 @@ msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
"\n"
"Select your printer from the list below:"
-msgstr "Pour imprimer directement sur votre imprimante sur le réseau, assurez-vous que votre imprimante est connectée au réseau via un câble réseau ou en connectant votre imprimante à votre réseau Wi-Fi. Si vous ne connectez pas Cura avec votre imprimante, vous pouvez utiliser une clé USB pour transférer les fichiers g-code sur votre imprimante.\n\nSélectionnez votre imprimante dans la liste ci-dessous :"
+msgstr ""
+"Pour imprimer directement sur votre imprimante sur le réseau, assurez-vous que votre imprimante est connectée au réseau via un câble réseau ou en connectant votre imprimante à votre réseau Wi-Fi. Si vous ne connectez pas Cura avec votre imprimante, vous pouvez utiliser une clé USB pour transférer les fichiers g-code sur votre imprimante.\n"
+"\n"
+"Sélectionnez votre imprimante dans la liste ci-dessous :"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:75
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:44
@@ -1530,7 +1660,7 @@ msgstr "Modifier"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:50
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:95
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:171
msgctxt "@action:button"
msgid "Remove"
msgstr "Supprimer"
@@ -1552,14 +1682,14 @@ msgid "Type"
msgstr "Type"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:233
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3"
-msgstr "Ultimaker 3"
+msgstr "Ultimaker 3"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:236
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3 Extended"
-msgstr "Ultimaker 3 Extended"
+msgstr "Ultimaker 3 Extended"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:252
msgctxt "@label"
@@ -1603,8 +1733,6 @@ msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Saisissez l'adresse IP ou le nom d'hôte de votre imprimante sur le réseau."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:379
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:92
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:88
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
@@ -1623,7 +1751,12 @@ msgstr "Imprimer"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:36
msgctxt "@label: arg 1 is group name"
msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
-msgstr "%1 n'est pas configurée pour héberger un groupe d'imprimantes connectées Ultimaker 3."
+msgstr "%1 n'est pas configurée pour héberger un groupe d'imprimantes connectées Ultimaker 3"
+
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
+msgctxt "@label link to connect manager"
+msgid "Add/Remove printers"
+msgstr "Ajouter / supprimer une imprimante"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
msgctxt "@info:tooltip"
@@ -1641,7 +1774,7 @@ msgstr "Afficher les tâches d'impression"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:408
msgctxt "@label"
msgid "Preparing to print"
-msgstr "Préparation de l'impression..."
+msgstr "Préparation de l'impression"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:39
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:271
@@ -1655,11 +1788,16 @@ msgid "Available"
msgstr "Disponible"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:100
msgctxt "@label:MonitorStatus"
msgid "Lost connection with the printer"
msgstr "Connexion avec l'imprimante perdue"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
+msgctxt "@label Printer status"
+msgid "Unknown"
+msgstr "Inconnu"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:257
msgctxt "@label:status"
msgid "Disabled"
@@ -1751,141 +1889,255 @@ msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Activer la configuration"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:20
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:21
msgctxt "@title:window"
-msgid "Cura SolidWorks Plugin Configuration"
-msgstr "Configuration du plug-in Cura SolidWorks"
+msgid "SolidWorks: Export wizard"
+msgstr "SolidWorks : assistant d'exportation"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:44
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:45
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:140
msgctxt "@action:label"
-msgid "Default quality of the exported STL:"
-msgstr "Qualité par défaut du STL exporté :"
+msgid "Quality:"
+msgstr "Qualité :"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:78
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:179
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always ask"
-msgstr "Toujours demander"
+msgid "Fine (3D-printing)"
+msgstr "Fine (impression 3D)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:180
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Fine quality"
-msgstr "Toujours utiliser la qualité Fine"
+msgid "Coarse (3D-printing)"
+msgstr "Grossière (impression 3D)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:181
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Coarse quality"
-msgstr "Toujours utiliser la qualité grossière"
+msgid "Fine (SolidWorks)"
+msgstr "Fine (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:20
-msgctxt "@title:window"
-msgid "Import SolidWorks File as STL..."
-msgstr "Importer le fichier SolidWorks comme STL..."
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:43
-msgctxt "@info:tooltip"
-msgid "Quality of the Exported STL"
-msgstr "Qualité du STL exporté"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:48
-msgctxt "@action:label"
-msgid "Quality"
-msgstr "Qualité"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:62
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:182
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Coarse"
-msgstr "Grossière"
+msgid "Coarse (SolidWorks)"
+msgstr "Grossière (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:63
-msgctxt "@option:curaSolidworksStlQuality"
-msgid "Fine"
-msgstr "Fine"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:78
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:82
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:94
msgctxt "@text:window"
-msgid "Remember my choice"
-msgstr "Se souvenir de mon choix"
+msgid "Show this dialog again"
+msgstr "Afficher de nouveau cette boîte de dialogue"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:104
+msgctxt "@action:button"
+msgid "Continue"
+msgstr "Continuer"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:116
+msgctxt "@action:button"
+msgid "Abort"
+msgstr "Abandonner"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:21
+msgctxt "@title:window"
+msgid "How to install Cura SolidWorks macro"
+msgstr "Modalités d’installation de Cura SolidWorks macro"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:62
+msgctxt "@description:label"
+msgid "Steps:"
+msgstr "Procédure :"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:140
+msgctxt "@action:button"
+msgid ""
+"Open the directory\n"
+"with macro and icon"
+msgstr ""
+"Ouvrez le répertoire\n"
+"contenant la macro et l'icône"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:160
+msgctxt "@description:label"
+msgid "Instructions:"
+msgstr "Instructions :"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:202
+msgctxt "@action:playpause"
+msgid "Play"
+msgstr "Jouer"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:206
+msgctxt "@action:playpause"
+msgid "Pause"
+msgstr "Pause"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:268
+msgctxt "@action:button"
+msgid "Previous Step"
+msgstr "Étape précédente"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:283
+msgctxt "@action:button"
+msgid "Done"
+msgstr "Terminé"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:287
+msgctxt "@action:button"
+msgid "Next Step"
+msgstr "Étape suivante"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:21
+msgctxt "@title:window"
+msgid "SolidWorks plugin: Configuration"
+msgstr "Plug-in SolidWorks : configuration"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:39
+msgctxt "@title:tab"
+msgid "Conversion settings"
+msgstr "Paramètres de conversion"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:66
+msgctxt "@label"
+msgid "First choice:"
+msgstr "Premier choix :"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:86
+msgctxt "@text:menu"
+msgid "Latest installed version (Recommended)"
+msgstr "Dernière version installée (recommandé)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:95
+msgctxt "@text:menu"
+msgid "Default version"
+msgstr "Version par défaut"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:193
+msgctxt "@label"
+msgid "Show wizard before opening SolidWorks files"
+msgstr "Afficher l'Assistant avant d'ouvrir les fichiers SolidWorks"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:203
+msgctxt "@label"
+msgid "Automatically rotate opened file into normed orientation"
+msgstr "Rotation automatique du fichier ouvert en orientation normalisée"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:210
+msgctxt "@title:tab"
+msgid "Installation(s)"
+msgstr "Installation(s)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:284
+msgctxt "@label"
+msgid "COM service found"
+msgstr "Service COM trouvé"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:295
+msgctxt "@label"
+msgid "Executable found"
+msgstr "Fichier exécutable trouvé"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:306
+msgctxt "@label"
+msgid "COM starting"
+msgstr "Lancement de COM"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:317
+msgctxt "@label"
+msgid "Revision number"
+msgstr "Numéro de révision"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:328
+msgctxt "@label"
+msgid "Functions available"
+msgstr "Fonctions disponibles"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+msgctxt "@action:button"
+msgid "Save"
+msgstr "Enregistrer"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
msgctxt "@label"
msgid "Color scheme"
msgstr "Modèle de couleurs"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:96
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Couleur du matériau"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Type de ligne"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "Taux d'alimentation"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:108
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr "Épaisseur de la couche"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:148
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Mode de compatibilité"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:230
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
msgctxt "@label"
msgid "Show Travels"
msgstr "Afficher les déplacements"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:236
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
msgctxt "@label"
msgid "Show Helpers"
msgstr "Afficher les aides"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:242
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
msgctxt "@label"
msgid "Show Shell"
msgstr "Afficher la coque"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:248
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
msgctxt "@label"
msgid "Show Infill"
msgstr "Afficher le remplissage"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:297
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Afficher uniquement les couches supérieures"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:306
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "Afficher 5 niveaux détaillés en haut"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:317
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Haut / bas"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:321
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
msgctxt "@label"
msgid "Inner Wall"
msgstr "Paroi interne"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:378
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
msgctxt "@label"
msgid "min"
-msgstr "min."
+msgstr "min"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:420
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
msgctxt "@label"
msgid "max"
-msgstr "max."
+msgstr "max"
#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:18
msgctxt "@title:window"
@@ -1907,7 +2159,7 @@ msgctxt "@label"
msgid "Settings"
msgstr "Paramètres"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:455
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:466
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Modifier les scripts de post-traitement actifs"
@@ -1982,23 +2234,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Lissage"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:208
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+msgctxt "@label"
+msgid "Mesh Type"
+msgstr "Type de maille"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+msgctxt "@label"
+msgid "Normal model"
+msgstr "Modèle normal"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+msgctxt "@label"
+msgid "Print as support"
+msgstr "Imprimer comme support"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+msgctxt "@label"
+msgid "Don't support overlap with other models"
+msgstr "Ne pas prendre en charge le chevauchement avec d'autres modèles"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+msgctxt "@label"
+msgid "Modify settings for overlap with other models"
+msgstr "Modifier les paramètres de chevauchement avec d'autres modèles"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+msgctxt "@label"
+msgid "Modify settings for infill of other models"
+msgstr "Modifier les paramètres de remplissage d'autres modèles"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:333
msgctxt "@action:button"
msgid "Select settings"
msgstr "Sélectionner les paramètres"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:248
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:375
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Sélectionner les paramètres pour personnaliser ce modèle"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:272
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:402
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:91
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Filtrer..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:296
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:426
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Afficher tout"
@@ -2022,7 +2304,7 @@ msgstr "Créer"
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:72
msgctxt "@action:title"
msgid "Summary - Cura Project"
-msgstr "Résumé - Projet Cura"
+msgstr "Sommaire - Projet Cura"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:92
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:90
@@ -2033,7 +2315,7 @@ msgstr "Paramètres de l'imprimante"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:108
msgctxt "@info:tooltip"
msgid "How should the conflict in the machine be resolved?"
-msgstr "Comment le conflit de la machine doit-il être résolu ?"
+msgstr "Comment le problème de la machine doit-il être résolu ?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:128
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:99
@@ -2059,7 +2341,7 @@ msgstr "Paramètres de profil"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:181
msgctxt "@info:tooltip"
msgid "How should the conflict in the profile be resolved?"
-msgstr "Comment le conflit du profil doit-il être résolu ?"
+msgstr "Comment le problème du profil doit-il être résolu ?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:216
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:174
@@ -2095,7 +2377,7 @@ msgstr "Paramètres du matériau"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:269
msgctxt "@info:tooltip"
msgid "How should the conflict in the material be resolved?"
-msgstr "Comment le conflit du matériau doit-il être résolu ?"
+msgstr "Comment le problème du matériau doit-il être résolu ?"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:312
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:209
@@ -2158,7 +2440,7 @@ msgstr "Télécharger"
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:199
msgctxt "@title:window"
msgid "Plugin License Agreement"
-msgstr "Plug-in d'accord de licence"
+msgstr "Plug-in de l'accord de licence"
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:220
msgctxt "@label"
@@ -2166,7 +2448,10 @@ msgid ""
"This plugin contains a license.\n"
"You need to accept this license to install this plugin.\n"
"Do you agree with the terms below?"
-msgstr "Ce plug-in contient une licence.\nVous devez approuver cette licence pour installer ce plug-in.\nAcceptez-vous les clauses ci-dessous ?"
+msgstr ""
+"Ce plug-in contient une licence.\n"
+"Vous devez approuver cette licence pour installer ce plug-in.\n"
+"Acceptez-vous les clauses ci-dessous ?"
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:242
msgctxt "@action:button"
@@ -2355,68 +2640,68 @@ msgstr "Contrôlée"
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml:284
msgctxt "@label"
msgid "Everything is in order! You're done with your CheckUp."
-msgstr "Tout est en ordre ! Vous avez terminé votre check-up."
+msgstr "Tout est en ordre ! Vous avez terminé votre vérification."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:88
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:87
msgctxt "@label:MonitorStatus"
msgid "Not connected to a printer"
msgstr "Non connecté à une imprimante"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:90
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:89
msgctxt "@label:MonitorStatus"
msgid "Printer does not accept commands"
msgstr "L'imprimante n'accepte pas les commandes"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:96
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:95
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:194
msgctxt "@label:MonitorStatus"
msgid "In maintenance. Please check the printer"
-msgstr "En maintenance. Vérifiez l'imprimante"
+msgstr "En maintenance. Veuillez vérifier l'imprimante"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:102
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:184
msgctxt "@label:MonitorStatus"
msgid "Printing..."
msgstr "Impression..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:105
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:186
msgctxt "@label:MonitorStatus"
msgid "Paused"
msgstr "En pause"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:109
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:108
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:188
msgctxt "@label:MonitorStatus"
msgid "Preparing..."
msgstr "Préparation..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:111
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:110
msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
-msgstr "Supprimez l'imprimante"
+msgstr "Veuillez supprimer l'imprimante"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:237
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
msgctxt "@label:"
msgid "Resume"
msgstr "Reprendre"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:245
msgctxt "@label:"
msgid "Pause"
msgstr "Pause"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:274
msgctxt "@label:"
msgid "Abort Print"
msgstr "Abandonner l'impression"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:280
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
msgctxt "@window:title"
msgid "Abort print"
msgstr "Abandonner l'impression"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:282
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:286
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
msgstr "Êtes-vous sûr(e) de vouloir abandonner l'impression ?"
@@ -2431,7 +2716,9 @@ msgctxt "@text:window"
msgid ""
"You have customized some profile settings.\n"
"Would you like to keep or discard those settings?"
-msgstr "Vous avez personnalisé certains paramètres du profil.\nSouhaitez-vous conserver ces changements, ou les annuler ?"
+msgstr ""
+"Vous avez personnalisé certains paramètres du profil.\n"
+"Souhaitez-vous conserver ces changements, ou les annuler ?"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:110
msgctxt "@title:column"
@@ -2449,19 +2736,19 @@ msgid "Customized"
msgstr "Personnalisé"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:593
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Toujours me demander"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:594
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:596
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Annuler et ne plus me demander"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:597
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Conserver et ne plus me demander"
@@ -2496,72 +2783,72 @@ msgctxt "@label"
msgid "Brand"
msgstr "Marque"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:88
msgctxt "@label"
msgid "Material Type"
msgstr "Type de matériau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:97
msgctxt "@label"
msgid "Color"
msgstr "Couleur"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
msgctxt "@label"
msgid "Properties"
msgstr "Propriétés"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:143
msgctxt "@label"
msgid "Density"
msgstr "Densité"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:156
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:158
msgctxt "@label"
msgid "Diameter"
msgstr "Diamètre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:187
msgctxt "@label"
msgid "Filament Cost"
msgstr "Coût du filament"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
msgctxt "@label"
msgid "Filament weight"
msgstr "Poids du filament"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:220
msgctxt "@label"
msgid "Filament length"
msgstr "Longueur du filament"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:227
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:229
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Coût au mètre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:243
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Ce matériau est lié à %1 et partage certaines de ses propriétés."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:250
msgctxt "@label"
msgid "Unlink Material"
msgstr "Délier le matériau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:259
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:261
msgctxt "@label"
msgid "Description"
msgstr "Description"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:272
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:274
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Informations d'adhérence"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:300
msgctxt "@label"
msgid "Print settings"
msgstr "Paramètres d'impression"
@@ -2602,7 +2889,7 @@ msgid "Unit"
msgstr "Unité"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:14
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:439
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:509
msgctxt "@title:tab"
msgid "General"
msgstr "Général"
@@ -2617,230 +2904,255 @@ msgctxt "@label"
msgid "Language:"
msgstr "Langue :"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:207
msgctxt "@label"
msgid "Currency:"
msgstr "Devise :"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:219
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Theme:"
msgstr "Thème :"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:279
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:281
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Vous devez redémarrer l'application pour que ces changements prennent effet."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:296
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:298
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Découper automatiquement si les paramètres sont modifiés."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:306
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Découper automatiquement"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:318
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:320
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Comportement Viewport"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:326
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:328
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Surligne les parties non supportées du modèle en rouge. Sans ajouter de support, ces zones ne s'imprimeront pas correctement."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:337
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Mettre en surbrillance les porte-à-faux"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:342
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:344
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
msgstr "Déplace la caméra afin que le modèle sélectionné se trouve au centre de la vue."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:347
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:349
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "Centrer la caméra lorsqu'un élément est sélectionné"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:356
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:358
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "Le comportement de zoom par défaut de Cura doit-il être inversé ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:363
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "Inverser la direction du zoom de la caméra."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:370
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:372
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "Le zoom doit-il se faire dans la direction de la souris ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:377
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "Zoomer vers la direction de la souris"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "Les modèles dans la zone d'impression doivent-ils être déplacés afin de ne plus se croiser ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Veillez à ce que les modèles restent séparés"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:397
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "Les modèles dans la zone d'impression doivent-ils être abaissés afin de toucher le plateau ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:402
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Abaisser automatiquement les modèles sur le plateau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:414
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:416
msgctxt "@info:tooltip"
msgid "Show caution message in gcode reader."
msgstr "Afficher le message d'avertissement dans le lecteur gcode."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:423
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:425
msgctxt "@option:check"
msgid "Caution message in gcode reader"
msgstr "Message d'avertissement dans lecteur gcode."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "La couche doit-elle être forcée en mode de compatibilité ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:435
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:437
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "Forcer l'affichage de la couche en mode de compatibilité (redémarrage requis)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:451
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:453
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Ouvrir et enregistrer des fichiers"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:457
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:459
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "Les modèles doivent-ils être mis à l'échelle du volume d'impression s'ils sont trop grands ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:462
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:464
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Réduire la taille des modèles trop grands"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:471
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:473
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Un modèle peut apparaître en tout petit si son unité est par exemple en mètres plutôt qu'en millimètres. Ces modèles doivent-ils être agrandis ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:476
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:478
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Mettre à l'échelle les modèles extrêmement petits"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:485
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:487
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "Un préfixe basé sur le nom de l'imprimante doit-il être automatiquement ajouté au nom de la tâche d'impression ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Ajouter le préfixe de la machine au nom de la tâche"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:501
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Un résumé doit-il être affiché lors de l'enregistrement d'un fichier de projet ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Afficher la boîte de dialogue du résumé lors de l'enregistrement du projet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:514
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Comportement par défaut lors de l'ouverture d'un fichier de projet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Comportement par défaut lors de l'ouverture d'un fichier de projet : "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:533
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@option:openProject"
msgid "Always ask"
msgstr "Toujours demander"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:536
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Toujours ouvrir comme projet"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Toujours importer les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Lorsque vous apportez des modifications à un profil puis passez à un autre profil, une boîte de dialogue apparaît, vous demandant si vous souhaitez conserver les modifications. Vous pouvez aussi choisir une option par défaut, et le dialogue ne s'affichera plus."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:582
msgctxt "@label"
msgid "Override Profile"
msgstr "Écraser le profil"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:629
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:631
msgctxt "@label"
msgid "Privacy"
msgstr "Confidentialité"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Cura doit-il vérifier les mises à jour au démarrage du programme ?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:641
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:643
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Vérifier les mises à jour au démarrage"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:651
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:653
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "Les données anonymes de votre impression doivent-elles être envoyées à Ultimaker ? Notez qu'aucun modèle, aucune adresse IP ni aucune autre information permettant de vous identifier personnellement ne seront envoyés ou stockés."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:656
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:658
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "Envoyer des informations (anonymes) sur l'impression"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:674
+msgctxt "@label"
+msgid "Experimental"
+msgstr "Expérimental"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:680
+msgctxt "@info:tooltip"
+msgid "Use multi build plate functionality"
+msgstr "Utiliser la fonctionnalité multi-plateau"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:685
+msgctxt "@option:check"
+msgid "Use multi build plate functionality (restart required)"
+msgstr "Utiliser la fonctionnalité multi-plateau (redémarrage requis)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+msgctxt "@info:tooltip"
+msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+msgstr "Les modèles nouvellement chargés doivent-ils être disposés sur le plateau ? Utilisé en conjonction avec le multi-plateau (EXPÉRIMENTAL)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+msgctxt "@option:check"
+msgid "Do not arrange objects on load"
+msgstr "Ne pas réorganiser les objets lors du chargement"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:514
msgctxt "@title:tab"
msgid "Printers"
msgstr "Imprimantes"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:37
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:51
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:138
msgctxt "@action:button"
msgid "Activate"
msgstr "Activer"
@@ -2883,7 +3195,7 @@ msgid "Waiting for a printjob"
msgstr "En attente d'une tâche d'impression"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:448
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:518
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Profils"
@@ -2909,13 +3221,13 @@ msgid "Duplicate"
msgstr "Dupliquer"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:113
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:182
msgctxt "@action:button"
msgid "Import"
msgstr "Importer"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:119
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:212
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:193
msgctxt "@action:button"
msgid "Export"
msgstr "Exporter"
@@ -2981,7 +3293,7 @@ msgid "Export Profile"
msgstr "Exporter un profil"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:516
msgctxt "@title:tab"
msgid "Materials"
msgstr "Matériaux"
@@ -2996,60 +3308,60 @@ msgctxt "@action:label %1 is printer name"
msgid "Printer: %1"
msgstr "Imprimante : %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:149
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:150
msgctxt "@action:button"
msgid "Create"
msgstr "Créer"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:160
msgctxt "@action:button"
msgid "Duplicate"
msgstr "Dupliquer"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:306
msgctxt "@title:window"
msgid "Import Material"
msgstr "Importer un matériau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:320
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:307
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Could not import material %1: %2"
msgstr "Impossible d'importer le matériau %1 : %2"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully imported material %1"
msgstr "Matériau %1 importé avec succès"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:343
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:329
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:344
msgctxt "@title:window"
msgid "Export Material"
msgstr "Exporter un matériau"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:348
msgctxt "@info:status Don't translate the XML tags and !"
msgid "Failed to export material to %1: %2"
msgstr "Échec de l'exportation de matériau vers %1 : %2"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:368
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:354
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully exported material to %1"
msgstr "Matériau exporté avec succès vers %1"
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:793
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:869
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Ajouter une imprimante"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
msgctxt "@label"
msgid "Printer Name:"
msgstr "Nom de l'imprimante :"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Ajouter une imprimante"
@@ -3074,7 +3386,9 @@ msgctxt "@info:credit"
msgid ""
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
"Cura proudly uses the following open source projects:"
-msgstr "Cura a été développé par Ultimaker B.V. en coopération avec la communauté Ultimaker.\nCura est fier d'utiliser les projets open source suivants :"
+msgstr ""
+"Cura a été développé par Ultimaker B.V. en coopération avec la communauté Ultimaker.\n"
+"Cura est fier d'utiliser les projets open source suivants :"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
msgctxt "@label"
@@ -3094,7 +3408,7 @@ msgstr "Générateur GCode"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121
msgctxt "@label"
msgid "Interprocess communication library"
-msgstr "Bibliothèque de communication interprocess"
+msgstr "Bibliothèque de communication inter-process"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:123
msgctxt "@label"
@@ -3176,158 +3490,167 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Profil :"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:66
-msgctxt "@"
-msgid "No Profile Available"
-msgstr "Aucun profil disponible"
-
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:102
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
"\n"
"Click to open the profile manager."
-msgstr "Certaines valeurs de paramètre / forçage sont différentes des valeurs enregistrées dans le profil. \n\nCliquez pour ouvrir le gestionnaire de profils."
+msgstr ""
+"Certaines valeurs de paramètre / forçage sont différentes des valeurs enregistrées dans le profil. \n"
+"\n"
+"Cliquez pour ouvrir le gestionnaire de profils."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:150
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Rechercher..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:482
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Copier la valeur vers tous les extrudeurs"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:497
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Masquer ce paramètre"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:508
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:507
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "Masquer ce paramètre"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:511
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Afficher ce paramètre"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:530
msgctxt "@action:menu"
msgid "Configure setting visiblity..."
msgstr "Configurer la visibilité des paramètres..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:250
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
"\n"
"Click to make these settings visible."
-msgstr "Certains paramètres masqués utilisent des valeurs différentes de leur valeur normalement calculée.\n\nCliquez pour rendre ces paramètres visibles."
+msgstr ""
+"Certains paramètres masqués utilisent des valeurs différentes de leur valeur normalement calculée.\n"
+"\n"
+"Cliquez pour rendre ces paramètres visibles."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:62
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
msgctxt "@label Header for list of settings."
msgid "Affects"
msgstr "Touche"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:67
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:66
msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Touché par"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:156
msgctxt "@label"
-msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
+msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
msgstr "Ce paramètre est toujours partagé par tous les extrudeurs. Le modifier ici entraînera la modification de la valeur pour tous les extrudeurs."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:160
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:159
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "La valeur est résolue à partir des valeurs par extrudeur "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:186
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:190
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
"\n"
"Click to restore the value of the profile."
-msgstr "Ce paramètre possède une valeur qui est différente du profil.\n\nCliquez pour restaurer la valeur du profil."
+msgstr ""
+"Ce paramètre possède une valeur qui est différente du profil.\n"
+"\n"
+"Cliquez pour restaurer la valeur du profil."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:288
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
"\n"
"Click to restore the calculated value."
-msgstr "Ce paramètre est normalement calculé mais il possède actuellement une valeur absolue définie.\n\nCliquez pour restaurer la valeur calculée."
+msgstr ""
+"Ce paramètre est normalement calculé mais il possède actuellement une valeur absolue définie.\n"
+"\n"
+"Cliquez pour restaurer la valeur calculée."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid "Print Setup"
msgstr "Configuration de l'impression"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid ""
"Print Setup disabled\n"
"G-code files cannot be modified"
-msgstr "Configuration de l'impression désactivée\nLes fichiers G-Code ne peuvent pas être modifiés"
+msgstr ""
+"Configuration de l'impression désactivée\n"
+"Les fichiers G-Code ne peuvent pas être modifiés"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:336
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:342
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00h 00min"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:354
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:359
msgctxt "@tooltip"
-msgid "Time specification
"
-msgstr "Spécification de temps
"
+msgid "Time specification"
+msgstr "Spécification de temps"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:429
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:441
msgctxt "@label"
msgid "Cost specification"
msgstr "Spécification de coût"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:434
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:455
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1m"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:435
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:447
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:456
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:454
msgctxt "@label"
msgid "Total:"
msgstr "Total :"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:504
msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
msgid "%1m / ~ %2g / ~ %4 %3"
msgstr "%1m / ~ %2g / ~ %4 %3"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:509
msgctxt "@label Print estimates: m for meters, g for grams"
msgid "%1m / ~ %2g"
msgstr "%1m / ~ %2g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Configuration de l'impression recommandée
Imprimer avec les paramètres recommandés pour l'imprimante, le matériau et la qualité sélectionnés."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:596
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "Configuration de l'impression personnalisée
Imprimer avec un contrôle fin de chaque élément du processus de découpe."
-#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:49
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:50
msgctxt "@title:menuitem %1 is the automatically selected material"
msgid "Automatic: %1"
msgstr "Automatique : %1"
@@ -3337,6 +3660,16 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Visualisation"
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:37
+msgctxt "@action:inmenu menubar:view"
+msgid "&Camera position"
+msgstr "Position de la &caméra"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:52
+msgctxt "@action:inmenu menubar:view"
+msgid "&Build plate"
+msgstr "&Plateau"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:40
msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer"
msgid "Automatic: %1"
@@ -3349,14 +3682,14 @@ msgid_plural "Print Selected Models With:"
msgstr[0] "Imprimer le modèle sélectionné avec :"
msgstr[1] "Imprimer les modèles sélectionnés avec :"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:83
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:114
msgctxt "@title:window"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Multiplier le modèle sélectionné"
msgstr[1] "Multiplier les modèles sélectionnés"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:108
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:139
msgctxt "@label"
msgid "Number of Copies"
msgstr "Nombre de copies"
@@ -3372,10 +3705,10 @@ msgid "No printer connected"
msgstr "Aucune imprimante n'est connectée"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:90
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:139
msgctxt "@label"
msgid "Extruder"
-msgstr "Extrudeuse"
+msgstr "Extrudeur"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:120
msgctxt "@tooltip"
@@ -3482,254 +3815,294 @@ msgctxt "@label"
msgid "Estimated time left"
msgstr "Durée restante estimée"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
msgid "Toggle Fu&ll Screen"
msgstr "Passer en P&lein écran"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "&Annuler"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:89
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "&Rétablir"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:99
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "&Quitter"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:107
-msgctxt "@action:inmenu menubar:view"
-msgid "&Reset camera position"
-msgstr "&Réinitialiser la position de la caméra"
-
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
+msgctxt "@action:inmenu menubar:view"
+msgid "&3D View"
+msgstr "Vue &3D"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+msgctxt "@action:inmenu menubar:view"
+msgid "&Front View"
+msgstr "Vue de &face"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
+msgctxt "@action:inmenu menubar:view"
+msgid "&Top View"
+msgstr "Vue du dess&us"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
+msgctxt "@action:inmenu menubar:view"
+msgid "&Left Side View"
+msgstr "Vue latérale &gauche"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+msgctxt "@action:inmenu menubar:view"
+msgid "&Right Side View"
+msgstr "Vue latérale &droite"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Configurer Cura..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "&Ajouter une imprimante..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "Gérer les &imprimantes..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Gérer les matériaux..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Mettre à jour le profil à l'aide des paramètres / forçages actuels"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:150
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Ignorer les modifications actuelles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Créer un profil à partir des paramètres / forçages actuels..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Gérer les profils..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Afficher la &documentation en ligne"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "Notifier un &bug"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:191
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
msgid "&About..."
msgstr "&À propos de..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
msgid "Delete &Selected Model"
msgid_plural "Delete &Selected Models"
msgstr[0] "Supprimer le modèle &sélectionné"
msgstr[1] "Supprimer les modèles &sélectionnés"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Centrer le modèle sélectionné"
msgstr[1] "Centrer les modèles sélectionnés"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Multiplier le modèle sélectionné"
msgstr[1] "Multiplier les modèles sélectionnés"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Supprimer le modèle"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "Ce&ntrer le modèle sur le plateau"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "&Grouper les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:250
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Dégrouper les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:260
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "&Fusionner les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "&Multiplier le modèle..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
msgid "&Select All Models"
msgstr "&Sélectionner tous les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:287
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
msgid "&Clear Build Plate"
msgstr "&Supprimer les objets du plateau"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:297
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
msgctxt "@action:inmenu menubar:file"
msgid "Re&load All Models"
msgstr "Rechar&ger tous les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:306
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
+msgctxt "@action:inmenu menubar:edit"
+msgid "Arrange All Models To All Build Plates"
+msgstr "Réorganiser tous les modèles sur tous les plateaux"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Réorganiser tous les modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Réorganiser la sélection"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:321
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Réinitialiser toutes les positions des modèles"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:328
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model &Transformations"
msgstr "Réinitialiser tous les modèles et transformations"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Ouvrir le(s) fichier(s)..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:343
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Nouveau projet..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "Afficher le &journal du moteur..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Afficher le dossier de configuration"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:365
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Configurer la visibilité des paramètres..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse plugins..."
msgstr "Parcourir les plug-ins..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:379
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:menu"
msgid "Installed plugins..."
msgstr "Plug-ins installés..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:28
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:438
+msgctxt "@action:inmenu menubar:view"
+msgid "Expand/Collapse Sidebar"
+msgstr "Déplier / replier la barre latérale"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Veuillez charger un modèle 3D"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:34
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Prêt à découper"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "Découpe en cours..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Prêt à %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
msgstr "Impossible de découper"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "Découpe indisponible"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Slice current printjob"
+msgstr "Découper la tâche d'impression en cours"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Cancel slicing process"
+msgstr "Annuler le processus de découpe"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Préparer"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Annuler"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:302
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "Sélectionner le périphérique de sortie actif"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:696
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Ouvrir le(s) fichier(s)"
@@ -3749,114 +4122,114 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:81
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&Fichier"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:98
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
msgctxt "@action:inmenu menubar:file"
msgid "&Save Selection to File"
msgstr "Enregi&strer la sélection dans un fichier"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
msgctxt "@title:menu menubar:file"
msgid "Save &As..."
msgstr "Enregistrer &sous..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
msgctxt "@title:menu menubar:file"
-msgid "Save project"
-msgstr "Enregistrer le projet"
+msgid "Save &Project..."
+msgstr "Enregistrer le &projet..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Modifier"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:158
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
msgctxt "@title:menu"
msgid "&View"
msgstr "&Visualisation"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
msgctxt "@title:menu"
msgid "&Settings"
msgstr "&Paramètres"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
msgctxt "@title:menu menubar:toplevel"
msgid "&Printer"
msgstr "Im&primante"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:175
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:187
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:208
msgctxt "@title:menu"
msgid "&Material"
msgstr "&Matériau"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:176
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:197
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:209
msgctxt "@title:menu"
msgid "&Profile"
msgstr "&Profil"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:180
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:201
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Définir comme extrudeur actif"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:219
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "E&xtensions"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:232
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:253
msgctxt "@title:menu menubar:toplevel"
msgid "P&lugins"
msgstr "&Plug-ins"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:261
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "P&références"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:269
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "&Aide"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:330
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:351
msgctxt "@action:button"
msgid "Open File"
msgstr "Ouvrir un fichier"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:442
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:512
msgctxt "@title:tab"
msgid "Settings"
msgstr "Paramètres"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:478
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:554
msgctxt "@title:window"
msgid "New project"
msgstr "Nouveau projet"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:479
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:555
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
-msgstr "Êtes-vous sûr(e) de souhaiter lancer un nouveau projet ? Cela supprimera les objets du plateau ainsi que tous paramètres non enregistrés."
+msgstr "Êtes-vous sûr(e) de vouloir commencer un nouveau projet ? Cela supprimera les objets du plateau ainsi que tous paramètres non enregistrés."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:721
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:797
msgctxt "@window:title"
msgid "Install Plugin"
msgstr "Installer plug-in"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:728
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:804
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Ouvrir le(s) fichier(s)"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:731
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:807
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Nous avons trouvé au moins un fichier G-Code parmi les fichiers que vous avez sélectionné. Vous ne pouvez ouvrir qu'un seul fichier G-Code à la fois. Si vous souhaitez ouvrir un fichier G-Code, veuillez ne sélectionner qu'un seul fichier de ce type."
@@ -3869,7 +4242,7 @@ msgstr "Enregistrer le projet"
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:136
msgctxt "@action:label"
msgid "Extruder %1"
-msgstr "Extrudeuse %1"
+msgstr "Extrudeur %1"
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:146
msgctxt "@action:label"
@@ -3881,97 +4254,82 @@ msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "Ne pas afficher à nouveau le résumé du projet lors de l'enregistrement"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
-msgctxt "@action:button"
-msgid "Save"
-msgstr "Enregistrer"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:74
-msgctxt "@title:tab"
-msgid "Prepare"
-msgstr "Préparer"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:100
-msgctxt "@title:tab"
-msgid "Monitor"
-msgstr "Surveiller"
-
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
msgctxt "@label"
msgid "Layer Height"
msgstr "Hauteur de la couche"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:323
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:345
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr "Un profil personnalisé est actuellement actif. Pour activer le curseur de qualité, choisissez un profil de qualité par défaut dans l'onglet Personnaliser"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:362
msgctxt "@label"
msgid "Print Speed"
msgstr "Vitesse d’impression"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:374
msgctxt "@label"
msgid "Slower"
msgstr "Ralentir"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:385
msgctxt "@label"
msgid "Faster"
msgstr "Accélérer"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:388
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:423
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "Vous avez modifié certains paramètres du profil. Si vous souhaitez les modifier, allez dans le mode Personnaliser."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:413
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:446
msgctxt "@label"
msgid "Infill"
msgstr "Remplissage"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:633
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:668
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Un remplissage graduel augmentera la quantité de remplissage vers le haut."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:645
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:680
msgctxt "@label"
msgid "Enable gradual"
msgstr "Permettre le remplissage graduel"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:712
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:747
msgctxt "@label"
msgid "Generate Support"
msgstr "Générer les supports"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:746
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:781
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
-msgstr "Générer des structures pour soutenir les parties du modèle qui possèdent des porte-à-faux. Sans ces structures, ces parties s'effondreront durant l'impression."
+msgstr "Générer des supports pour soutenir les parties du modèle qui possèdent des porte-à-faux. Sans ces supports, ces parties s'effondreront durant l'impression."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:764
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:799
msgctxt "@label"
msgid "Support Extruder"
-msgstr "Extrudeuse de soutien"
+msgstr "Extrudeur pour matériau support"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:816
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:851
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Sélectionnez l'extrudeur à utiliser comme support. Cela créera des structures de support sous le modèle afin de l'empêcher de s'affaisser ou de s'imprimer dans les airs."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:839
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:874
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Adhérence au plateau"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:894
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:929
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
-msgstr "Activez l'impression d'une bordure ou plaquette (Brim/Raft). Cela ajoutera une zone plate autour de ou sous votre objet qui est facile à découper par la suite."
+msgstr "Activez l'impression du Brim ou Raft. Cela ajoutera une zone plate autour de ou sous votre objet qui est facile à retirer par la suite."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:934
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:969
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "Besoin d'aide pour améliorer vos impressions ? Lisez les Guides de dépannage Ultimaker"
@@ -3988,17 +4346,22 @@ msgctxt "@title:window"
msgid "Open project file"
msgstr "Ouvrir un fichier de projet"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:93
msgctxt "@text:window"
msgid "This is a Cura project file. Would you like to open it as a project or import the models from it?"
msgstr "Ceci est un fichier de projet Cura. Souhaitez-vous l'ouvrir comme projet ou en importer les modèles ?"
#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:103
+msgctxt "@text:window"
+msgid "Remember my choice"
+msgstr "Se souvenir de mon choix"
+
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
msgctxt "@action:button"
msgid "Open as project"
msgstr "Ouvrir comme projet"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:131
msgctxt "@action:button"
msgid "Import models"
msgstr "Importer les modèles"
@@ -4008,21 +4371,36 @@ msgctxt "@title:window"
msgid "Engine Log"
msgstr "Journal du moteur"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:245
msgctxt "@label"
msgid "Material"
msgstr "Matériau"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:349
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:352
msgctxt "@label"
-msgid "Check compatibility"
-msgstr "Vérifier la compatibilité"
+msgid "Check compatibility"
+msgstr "Vérifier la compatibilité"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:369
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Cliquez ici pour vérifier la compatibilité des matériaux sur Ultimaker.com."
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:211
+msgctxt "@option:check"
+msgid "See only current build plate"
+msgstr "Afficher uniquement le plateau actuel"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:227
+msgctxt "@action:button"
+msgid "Arrange to all build plates"
+msgstr "Réorganiser sur tous les plateaux"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:247
+msgctxt "@action:button"
+msgid "Arrange current build plate"
+msgstr "Réorganiser le plateau actuel"
+
#: MachineSettingsAction/plugin.json
msgctxt "description"
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc)"
@@ -4113,6 +4491,26 @@ msgctxt "name"
msgid "USB printing"
msgstr "Impression par USB"
+#: PrepareStage/plugin.json
+msgctxt "description"
+msgid "Provides a prepare stage in Cura."
+msgstr "Fournit une étape de préparation dans Cura."
+
+#: PrepareStage/plugin.json
+msgctxt "name"
+msgid "Prepare Stage"
+msgstr "Étape de préparation"
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "description"
+msgid "Provides an edit window for direct script editing."
+msgstr "Fournit une fenêtre d'édition pour l'édition directe de script."
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "name"
+msgid "Live scripting tool"
+msgstr "Outil de scripting en direct"
+
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4133,6 +4531,16 @@ msgctxt "name"
msgid "UM3 Network Connection"
msgstr "Connexion au réseau UM3"
+#: MonitorStage/plugin.json
+msgctxt "description"
+msgid "Provides a monitor stage in Cura."
+msgstr "Fournit une étape de surveillance dans Cura."
+
+#: MonitorStage/plugin.json
+msgctxt "name"
+msgid "Monitor Stage"
+msgstr "Étape de surveillance"
+
#: FirmwareUpdateChecker/plugin.json
msgctxt "description"
msgid "Checks for firmware updates."
@@ -4141,12 +4549,12 @@ msgstr "Vérifie les mises à jour du firmware."
#: FirmwareUpdateChecker/plugin.json
msgctxt "name"
msgid "Firmware Update Checker"
-msgstr "Vérificateur des mises à jour du firmware"
+msgstr "Vérification des mises à jour du firmware"
#: CuraSolidWorksPlugin/plugin.json
msgctxt "description"
-msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
-msgstr "Donne la possibilité d'ouvrir certains fichiers via SolidWorks. Ces fichiers sont ensuite convertis et chargés dans Cura."
+msgid "Gives you the possibility to open certain files using SolidWorks itself. Conversion is done by this plugin and additional optimizations."
+msgstr "Donne la possibilité d'ouvrir certains fichiers via SolidWorks. La conversion est ensuite effectuée par ce plug-in et des optimisations supplémentaires."
#: CuraSolidWorksPlugin/plugin.json
msgctxt "name"
@@ -4201,7 +4609,7 @@ msgstr "Offre la possibilité de lire et d'écrire des profils matériels basés
#: XmlMaterialProfile/plugin.json
msgctxt "name"
msgid "Material Profiles"
-msgstr "Profils matériels"
+msgstr "Profils matériaux"
#: LegacyProfileReader/plugin.json
msgctxt "description"
@@ -4213,6 +4621,16 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Lecteur de profil Cura antérieur"
+#: CuraBlenderPlugin/plugin.json
+msgctxt "description"
+msgid "Helps to open Blender files directly in Cura."
+msgstr "Aide à ouvrir les fichiers Blender directement dans Cura."
+
+#: CuraBlenderPlugin/plugin.json
+msgctxt "name"
+msgid "Blender Integration (experimental)"
+msgstr "Intégration Blender (expérimental)"
+
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4321,7 +4739,7 @@ msgstr "Vous aide à installer un bouton « exporter vers Cura » dans Siemens
#: cura-siemensnx-plugin/plugin.json
msgctxt "name"
msgid "Siemens NX Integration"
-msgstr "Siemens NX Integration"
+msgstr "Intégration Siemens NX"
#: 3MFReader/plugin.json
msgctxt "description"
@@ -4373,6 +4791,16 @@ msgctxt "name"
msgid "Cura Profile Writer"
msgstr "Générateur de profil Cura"
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "description"
+msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+msgstr "Permet aux fabricants de matériaux de créer de nouveaux matériaux et profils de qualité à l'aide d'une interface utilisateur ad hoc."
+
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "name"
+msgid "Print Profile Assistant"
+msgstr "Assistant de profil d'impression"
+
#: 3MFWriter/plugin.json
msgctxt "description"
msgid "Provides support for writing 3MF files."
@@ -4386,17 +4814,17 @@ msgstr "Générateur 3MF"
#: UserAgreementPlugin/plugin.json
msgctxt "description"
msgid "Ask the user once if he/she agrees with our license"
-msgstr "Demander à l'utilisateur une fois s'il appose son accord à notre licence"
+msgstr "Demander à l'utilisateur une fois s'il est d'accord avec les termes de notre licence"
#: UserAgreementPlugin/plugin.json
msgctxt "name"
msgid "UserAgreement"
-msgstr "UserAgreement"
+msgstr "Accord de l'utilisateur"
#: UltimakerMachineActions/plugin.json
msgctxt "description"
msgid "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc)"
-msgstr "Fournit les actions de la machine pour les machines Ultimaker (telles que l'assistant de calibration du plateau, sélection des mises à niveau, etc.)"
+msgstr "Fournit les actions de la machine pour les machines Ultimaker (tels que l'assistant de calibration du plateau, sélection des mises à niveau, etc.)"
#: UltimakerMachineActions/plugin.json
msgctxt "name"
@@ -4413,6 +4841,156 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Lecteur de profil Cura"
+#~ msgctxt "@label"
+#~ msgid "Unknown"
+#~ msgstr "Inconnu"
+
+#~ msgctxt "@info:status"
+#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
+#~ msgstr "Des erreurs sont apparues lors de l'ouverture de votre fichier SolidWorks ! Veuillez vérifier s'il est possible d'ouvrir votre fichier dans SolidWorks sans que cela ne cause de problèmes."
+
+#~ msgctxt "@info:status"
+#~ msgid "Error while starting %s!"
+#~ msgstr "Erreur lors du lancement de %s !"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Simulation view"
+#~ msgstr "Vue simulation"
+
+#~ msgctxt "@info"
+#~ msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
+#~ msgstr "Cura collecte des statistiques anonymes sur le découpage. Vous pouvez désactiver cette fonctionnalité dans les préférences."
+
+#~ msgctxt "@action:button"
+#~ msgid "Dismiss"
+#~ msgstr "Ignorer"
+
+#~ msgctxt "@menuitem"
+#~ msgid "Global"
+#~ msgstr "Global"
+
+#~ msgctxt "@label crash message"
+#~ msgid ""
+#~ "
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+#~ "
Please use the \"Send report\" button to post a bug report automatically to our servers
\n"
+#~ " "
+#~ msgstr ""
+#~ "
Une exception fatale s'est produite. Veuillez nous envoyer ce Rapport d'incident pour résoudre le problème
\n"
+#~ "
Veuillez utiliser le bouton « Envoyer rapport » pour publier automatiquement un rapport d'erreur sur nos serveurs
\n"
+#~ " "
+
+#~ msgctxt "@label Cura version"
+#~ msgid "Cura version: {version} "
+#~ msgstr "Version Cura : {version} "
+
+#~ msgctxt "@label Platform"
+#~ msgid "Platform: {platform} "
+#~ msgstr "Plateforme : {platform} "
+
+#~ msgctxt "@label Qt version"
+#~ msgid "Qt version: {qt} "
+#~ msgstr "Version Qt : {qt} "
+
+#~ msgctxt "@label PyQt version"
+#~ msgid "PyQt version: {pyqt} "
+#~ msgstr "Version PyQt : {pyqt} "
+
+#~ msgctxt "@label OpenGL"
+#~ msgid "OpenGL: {opengl} "
+#~ msgstr "OpenGL : {opengl} "
+
+#~ msgctxt "@title:groupbox"
+#~ msgid "Exception traceback"
+#~ msgstr "Retraçage de l'exception"
+
+#~ msgctxt "@label"
+#~ msgid "Material diameter"
+#~ msgstr "Diamètre du matériau"
+
+#~ msgctxt "@label"
+#~ msgid "Ultimaker 3"
+#~ msgstr "Ultimaker 3"
+
+#~ msgctxt "@label"
+#~ msgid "Ultimaker 3 Extended"
+#~ msgstr "Ultimaker 3 Extended"
+
+#~ msgctxt "@title:window"
+#~ msgid "Cura SolidWorks Plugin Configuration"
+#~ msgstr "Configuration du plug-in Cura SolidWorks"
+
+#~ msgctxt "@action:label"
+#~ msgid "Default quality of the exported STL:"
+#~ msgstr "Qualité par défaut du STL exporté :"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always ask"
+#~ msgstr "Toujours demander"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always use Fine quality"
+#~ msgstr "Toujours utiliser la qualité Fine"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always use Coarse quality"
+#~ msgstr "Toujours utiliser la qualité grossière"
+
+#~ msgctxt "@title:window"
+#~ msgid "Import SolidWorks File as STL..."
+#~ msgstr "Importer le fichier SolidWorks comme STL..."
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Quality of the Exported STL"
+#~ msgstr "Qualité du STL exporté"
+
+#~ msgctxt "@action:label"
+#~ msgid "Quality"
+#~ msgstr "Qualité"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Coarse"
+#~ msgstr "Grossière"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Fine"
+#~ msgstr "Fine"
+
+#~ msgctxt "@"
+#~ msgid "No Profile Available"
+#~ msgstr "Aucun profil disponible"
+
+#~ msgctxt "@label"
+#~ msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
+#~ msgstr "Ce paramètre est toujours partagé par tous les extrudeurs. Le modifier ici entraînera la modification de la valeur pour tous les extrudeurs."
+
+#~ msgctxt "@tooltip"
+#~ msgid "Time specification
"
+#~ msgstr "Spécification de temps
"
+
+#~ msgctxt "@action:inmenu menubar:view"
+#~ msgid "&Reset camera position"
+#~ msgstr "&Réinitialiser la position de la caméra"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save project"
+#~ msgstr "Enregistrer le projet"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Prepare"
+#~ msgstr "Préparer"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Monitor"
+#~ msgstr "Surveiller"
+
+#~ msgctxt "@label"
+#~ msgid "Check compatibility"
+#~ msgstr "Vérifier la compatibilité"
+
+#~ msgctxt "description"
+#~ msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
+#~ msgstr "Donne la possibilité d'ouvrir certains fichiers via SolidWorks. Ces fichiers sont ensuite convertis et chargés dans Cura."
+
#~ msgctxt "@label:status"
#~ msgid "Blocked"
#~ msgstr "Bloqué"
@@ -4433,13 +5011,9 @@ msgstr "Lecteur de profil Cura"
#~ msgid "To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware regularly. This can be done on the {machine_name} (when connected to the network) or via USB."
#~ msgstr "Pour s'assurer que votre {machine_name} est pourvue des dernières fonctionnalités, il est recommandé de mettre régulièrement à jour le firmware. Cela peut se faire sur la {machine_name} (lorsque connectée au réseau) ou via USB."
-msgctxt "@item:inlistbox"
-msgid "Layer view"
-msgstr "Vue en couches"
-
-msgctxt "@info:title"
-msgid "Layer View"
-msgstr "Vue en couches"
+#~ msgctxt "@info:title"
+#~ msgid "Layer View"
+#~ msgstr "Vue en couches"
#~ msgctxt "@menuitem"
#~ msgid "Browse plugins"
@@ -4541,9 +5115,9 @@ msgstr "Vue en couches"
#~ msgid "Provides the Layer view."
#~ msgstr "Permet la vue en couches."
-msgctxt "name"
-msgid "Layer View"
-msgstr "Vue en couches"
+#~ msgctxt "name"
+#~ msgid "Layer View"
+#~ msgstr "Vue en couches"
#~ msgctxt "@item:inlistbox"
#~ msgid "X-Ray"
@@ -4864,9 +5438,9 @@ msgstr "Vue en couches"
#~ msgid "Provides support for importing profiles from g-code files."
#~ msgstr "Fournit la prise en charge de l'importation de profils à partir de fichiers g-code."
-msgctxt "@label"
-msgid "Layer View"
-msgstr "Vue en couches"
+#~ msgctxt "@label"
+#~ msgid "Layer View"
+#~ msgstr "Vue en couches"
#~ msgctxt "@info:whatsthis"
#~ msgid "Provides the Layer view."
diff --git a/resources/i18n/fr_FR/fdmextruder.def.json.po b/resources/i18n/fr_FR/fdmextruder.def.json.po
index fdec72de32..ead5e0a87b 100644
--- a/resources/i18n/fr_FR/fdmextruder.def.json.po
+++ b/resources/i18n/fr_FR/fdmextruder.def.json.po
@@ -2,12 +2,12 @@
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek , 2017.
-#
+#
msgid ""
msgstr ""
"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: 2017-11-30 13:05+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: French\n"
diff --git a/resources/i18n/fr_FR/fdmprinter.def.json.po b/resources/i18n/fr_FR/fdmprinter.def.json.po
index 0e768253dc..35731b8312 100644
--- a/resources/i18n/fr_FR/fdmprinter.def.json.po
+++ b/resources/i18n/fr_FR/fdmprinter.def.json.po
@@ -1,20 +1,21 @@
-# Cura JSON setting files
-# Copyright (C) 2017 Ultimaker
+# Cura
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
-"PO-Revision-Date: 2017-11-30 13:05+0100\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
+"PO-Revision-Date: 2018-02-13 15:31+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: French\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.6\n"
#: fdmprinter.def.json
msgctxt "machine_settings label"
@@ -56,7 +57,9 @@ msgctxt "machine_start_gcode description"
msgid ""
"Gcode commands to be executed at the very start - separated by \n"
"."
-msgstr "Commandes Gcode à exécuter au tout début, séparées par \n."
+msgstr ""
+"Commandes Gcode à exécuter au tout début, séparées par \n"
+"."
#: fdmprinter.def.json
msgctxt "machine_end_gcode label"
@@ -68,17 +71,19 @@ msgctxt "machine_end_gcode description"
msgid ""
"Gcode commands to be executed at the very end - separated by \n"
"."
-msgstr "Commandes Gcode à exécuter à la toute fin, séparées par \n."
+msgstr ""
+"Commandes Gcode à exécuter à la toute fin, séparées par \n"
+"."
#: fdmprinter.def.json
msgctxt "material_guid label"
msgid "Material GUID"
-msgstr "GUID matériau"
+msgstr "Identification GUID du matériau"
#: fdmprinter.def.json
msgctxt "material_guid description"
msgid "GUID of the material. This is set automatically. "
-msgstr "GUID du matériau. Cela est configuré automatiquement. "
+msgstr "Identification GUID du matériau. Cela est configuré automatiquement. "
#: fdmprinter.def.json
msgctxt "material_bed_temp_wait label"
@@ -148,7 +153,7 @@ msgstr "Forme du plateau"
#: fdmprinter.def.json
msgctxt "machine_shape description"
msgid "The shape of the build plate without taking unprintable areas into account."
-msgstr "La forme du plateau sans prendre les zones non imprimables en compte."
+msgstr "La forme du plateau sans prendre en compte les zones non imprimables."
#: fdmprinter.def.json
msgctxt "machine_shape option rectangular"
@@ -173,12 +178,12 @@ msgstr "La hauteur (sens Z) de la zone imprimable."
#: fdmprinter.def.json
msgctxt "machine_heated_bed label"
msgid "Has Heated Build Plate"
-msgstr "A un plateau chauffé"
+msgstr "A un plateau chauffant"
#: fdmprinter.def.json
msgctxt "machine_heated_bed description"
msgid "Whether the machine has a heated build plate present."
-msgstr "Si la machine a un plateau chauffé présent."
+msgstr "Si la machine a un plateau chauffant existant."
#: fdmprinter.def.json
msgctxt "machine_center_is_zero label"
@@ -198,7 +203,7 @@ msgstr "Nombre d'extrudeuses"
#: fdmprinter.def.json
msgctxt "machine_extruder_count description"
msgid "Number of extruder trains. An extruder train is the combination of a feeder, bowden tube, and nozzle."
-msgstr "Nombre de trains d'extrudeuse. Un train d'extrudeuse est la combinaison d'un chargeur, d'un tube bowden et d'une buse."
+msgstr "Nombre de systèmes d'extrusion. Un système d'extrusion est la combinaison d'un feeder, d'un tube bowden et d'une buse."
#: fdmprinter.def.json
msgctxt "machine_nozzle_tip_outer_diameter label"
@@ -238,7 +243,7 @@ msgstr "Longueur de la zone chauffée"
#: fdmprinter.def.json
msgctxt "machine_heat_zone_length description"
msgid "The distance from the tip of the nozzle in which heat from the nozzle is transferred to the filament."
-msgstr "Distance depuis la pointe du bec d'impression sur laquelle la chaleur du bec d'impression est transférée au filament."
+msgstr "Distance depuis la pointe de la buse sur laquelle la chaleur de la buse est transférée au filament."
#: fdmprinter.def.json
msgctxt "machine_filament_park_distance label"
@@ -248,7 +253,7 @@ msgstr "Distance de stationnement du filament"
#: fdmprinter.def.json
msgctxt "machine_filament_park_distance description"
msgid "The distance from the tip of the nozzle where to park the filament when an extruder is no longer used."
-msgstr "Distance depuis la pointe du bec sur laquelle stationner le filament lorsqu'une extrudeuse n'est plus utilisée."
+msgstr "Distance depuis la pointe de la buse sur laquelle stationne le filament lorsqu'une extrudeuse n'est plus utilisée."
#: fdmprinter.def.json
msgctxt "machine_nozzle_temp_enabled label"
@@ -263,7 +268,7 @@ msgstr "Contrôler ou non la température depuis Cura. Désactivez cette option
#: fdmprinter.def.json
msgctxt "machine_nozzle_heat_up_speed label"
msgid "Heat up speed"
-msgstr "Vitesse de chauffage"
+msgstr "Vitesse de chauffe"
#: fdmprinter.def.json
msgctxt "machine_nozzle_heat_up_speed description"
@@ -283,7 +288,7 @@ msgstr "La vitesse (°C/s) à laquelle la buse refroidit, sur une moyenne de la
#: fdmprinter.def.json
msgctxt "machine_min_cool_heat_time_window label"
msgid "Minimal Time Standby Temperature"
-msgstr "Durée minimale température de veille"
+msgstr "Température minimale de veille"
#: fdmprinter.def.json
msgctxt "machine_min_cool_heat_time_window description"
@@ -345,6 +350,16 @@ msgctxt "machine_gcode_flavor option Repetier"
msgid "Repetier"
msgstr "Repetier"
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract label"
+msgid "Firmware Retraction"
+msgstr "Rétraction du firmware"
+
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract description"
+msgid "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material."
+msgstr "S'il faut utiliser les commandes de rétraction du firmware (G10 / G11) au lieu d'utiliser la propriété E dans les commandes G1 pour rétracter le matériau."
+
#: fdmprinter.def.json
msgctxt "machine_disallowed_areas label"
msgid "Disallowed areas"
@@ -358,12 +373,12 @@ msgstr "Une liste de polygones comportant les zones dans lesquelles la tête d'i
#: fdmprinter.def.json
msgctxt "nozzle_disallowed_areas label"
msgid "Nozzle Disallowed Areas"
-msgstr "Zones interdites au bec d'impression"
+msgstr "Zones interdites à la buse"
#: fdmprinter.def.json
msgctxt "nozzle_disallowed_areas description"
msgid "A list of polygons with areas the nozzle is not allowed to enter."
-msgstr "Une liste de polygones comportant les zones dans lesquelles le bec n'a pas le droit de pénétrer."
+msgstr "Une liste de polygones comportant les zones dans lesquelles la buse n'a pas le droit de pénétrer."
#: fdmprinter.def.json
msgctxt "machine_head_polygon label"
@@ -373,7 +388,7 @@ msgstr "Polygone de la tête de machine"
#: fdmprinter.def.json
msgctxt "machine_head_polygon description"
msgid "A 2D silhouette of the print head (fan caps excluded)."
-msgstr "Une silhouette 2D de la tête d'impression (sans les capuchons du ventilateur)."
+msgstr "Une silhouette 2D de la tête d'impression (sans les carter du ventilateur)."
#: fdmprinter.def.json
msgctxt "machine_head_with_fans_polygon label"
@@ -383,7 +398,7 @@ msgstr "Tête de la machine et polygone du ventilateur"
#: fdmprinter.def.json
msgctxt "machine_head_with_fans_polygon description"
msgid "A 2D silhouette of the print head (fan caps included)."
-msgstr "Une silhouette 2D de la tête d'impression (avec les capuchons du ventilateur)."
+msgstr "Une silhouette 2D de la tête d'impression (avec les carters du ventilateur)."
#: fdmprinter.def.json
msgctxt "gantry_height label"
@@ -398,12 +413,12 @@ msgstr "La différence de hauteur entre la pointe de la buse et le système de p
#: fdmprinter.def.json
msgctxt "machine_nozzle_id label"
msgid "Nozzle ID"
-msgstr "ID buse"
+msgstr "ID de la buse"
#: fdmprinter.def.json
msgctxt "machine_nozzle_id description"
msgid "The nozzle ID for an extruder train, such as \"AA 0.4\" and \"BB 0.8\"."
-msgstr "ID buse pour un train d'extrudeuse, comme « AA 0.4 » et « BB 0.8 »."
+msgstr "ID de la buse pour un système d'extrusion, comme « AA 0.4 » et « BB 0.8 »."
#: fdmprinter.def.json
msgctxt "machine_nozzle_size label"
@@ -418,17 +433,17 @@ msgstr "Le diamètre intérieur de la buse. Modifiez ce paramètre si vous utili
#: fdmprinter.def.json
msgctxt "machine_use_extruder_offset_to_offset_coords label"
msgid "Offset With Extruder"
-msgstr "Décalage avec extrudeuse"
+msgstr "Offset avec extrudeuse"
#: fdmprinter.def.json
msgctxt "machine_use_extruder_offset_to_offset_coords description"
msgid "Apply the extruder offset to the coordinate system."
-msgstr "Appliquer le décalage de l'extrudeuse au système de coordonnées."
+msgstr "Appliquer l'offset de l'extrudeuse au système de coordonnées."
#: fdmprinter.def.json
msgctxt "extruder_prime_pos_z label"
msgid "Extruder Prime Z Position"
-msgstr "Extrudeuse Position d'amorçage Z"
+msgstr "Position d'amorçage en Z de l'extrudeuse"
#: fdmprinter.def.json
msgctxt "extruder_prime_pos_z description"
@@ -588,7 +603,7 @@ msgstr "Tous les paramètres qui influent sur la résolution de l'impression. Ce
#: fdmprinter.def.json
msgctxt "layer_height label"
msgid "Layer Height"
-msgstr "Hauteur de la couche"
+msgstr "Hauteur de couche"
#: fdmprinter.def.json
msgctxt "layer_height description"
@@ -598,38 +613,13 @@ msgstr "La hauteur de chaque couche en mm. Des valeurs plus élevées créent de
#: fdmprinter.def.json
msgctxt "layer_height_0 label"
msgid "Initial Layer Height"
-msgstr "Hauteur de la couche initiale"
+msgstr "Hauteur de couche initiale"
#: fdmprinter.def.json
msgctxt "layer_height_0 description"
msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier."
msgstr "La hauteur de la couche initiale en mm. Une couche initiale plus épaisse adhère plus facilement au plateau."
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance label"
-msgid "Slicing Tolerance"
-msgstr "Tolérance à la découpe"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance description"
-msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
-msgstr "Comment découper des couches avec des surfaces diagonales. Les zones d'une couche peuvent être générées en fonction de l'endroit où le milieu de la couche croise la surface (Milieu). Alternativement, chaque couche peut posséder des zones situées à l'intérieur du volume à travers toute la hauteur de la couche (Exclusif), ou une couche peut avoir des zones situées à l'intérieur à tout endroit dans la couche (Inclusif). L'option Exclusif permet de retenir le plus de détails, Inclusif permet d'obtenir une adaptation optimale et Milieu demande le moins de temps de traitement."
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option middle"
-msgid "Middle"
-msgstr "Milieu"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option exclusive"
-msgid "Exclusive"
-msgstr "Exclusif"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option inclusive"
-msgid "Inclusive"
-msgstr "Inclusif"
-
#: fdmprinter.def.json
msgctxt "line_width label"
msgid "Line Width"
@@ -643,12 +633,12 @@ msgstr "Largeur d'une ligne. Généralement, la largeur de chaque ligne doit cor
#: fdmprinter.def.json
msgctxt "wall_line_width label"
msgid "Wall Line Width"
-msgstr "Largeur de ligne de la paroi"
+msgstr "Largeur de ligne de la coque"
#: fdmprinter.def.json
msgctxt "wall_line_width description"
msgid "Width of a single wall line."
-msgstr "Largeur d'une seule ligne de la paroi."
+msgstr "Largeur d'une seule ligne de la paroie."
#: fdmprinter.def.json
msgctxt "wall_line_width_0 label"
@@ -670,16 +660,6 @@ msgctxt "wall_line_width_x description"
msgid "Width of a single wall line for all wall lines except the outermost one."
msgstr "Largeur d'une seule ligne de la paroi pour toutes les lignes de paroi, à l’exception de la ligne la plus externe."
-#: fdmprinter.def.json
-msgctxt "roofing_line_width label"
-msgid "Top Surface Skin Line Width"
-msgstr "Largeur de ligne de couche extérieure de la surface supérieure"
-
-#: fdmprinter.def.json
-msgctxt "roofing_line_width description"
-msgid "Width of a single line of the areas at the top of the print."
-msgstr "Largeur d'une seule ligne de la zone en haut de l'impression."
-
#: fdmprinter.def.json
msgctxt "skin_line_width label"
msgid "Top/Bottom Line Width"
@@ -713,7 +693,7 @@ msgstr "Largeur d'une seule ligne de jupe ou de bordure."
#: fdmprinter.def.json
msgctxt "support_line_width label"
msgid "Support Line Width"
-msgstr "Largeur de ligne de support"
+msgstr "Largeur de ligne des supports"
#: fdmprinter.def.json
msgctxt "support_line_width description"
@@ -733,12 +713,12 @@ msgstr "Largeur d'une seule ligne de plafond ou de bas de support."
#: fdmprinter.def.json
msgctxt "support_roof_line_width label"
msgid "Support Roof Line Width"
-msgstr "Largeur de ligne de plafond de support"
+msgstr "Largeur de ligne du toit de support"
#: fdmprinter.def.json
msgctxt "support_roof_line_width description"
msgid "Width of a single support roof line."
-msgstr "Largeur d'une seule ligne de plafond de support."
+msgstr "Largeur d'une seule ligne de toit de support."
#: fdmprinter.def.json
msgctxt "support_bottom_line_width label"
@@ -788,7 +768,7 @@ msgstr "Extrudeuse de paroi"
#: fdmprinter.def.json
msgctxt "wall_extruder_nr description"
msgid "The extruder train used for printing the walls. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse utilisé pour l'impression des parois. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion utilisé pour l'impression des parois. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "wall_0_extruder_nr label"
@@ -798,7 +778,7 @@ msgstr "Extrudeuse de paroi externe"
#: fdmprinter.def.json
msgctxt "wall_0_extruder_nr description"
msgid "The extruder train used for printing the outer wall. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse utilisé pour l'impression des parois externes. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion utilisé pour l'impression des parois externes. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "wall_x_extruder_nr label"
@@ -808,7 +788,7 @@ msgstr "Extrudeuse de paroi interne"
#: fdmprinter.def.json
msgctxt "wall_x_extruder_nr description"
msgid "The extruder train used for printing the inner walls. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse utilisé pour l'impression des parois internes. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion utilisé pour l'impression des parois internes. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "wall_thickness label"
@@ -848,7 +828,7 @@ msgstr "Extrudeuse de couche extérieure de la surface supérieure"
#: fdmprinter.def.json
msgctxt "roofing_extruder_nr description"
msgid "The extruder train used for printing the top most skin. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse utilisé pour l'impression de la couche extérieure supérieure. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion utilisé pour l'impression de la couche extérieure supérieure. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "roofing_layer_count label"
@@ -860,41 +840,6 @@ msgctxt "roofing_layer_count description"
msgid "The number of top most skin layers. Usually only one top most layer is sufficient to generate higher quality top surfaces."
msgstr "Nombre de couches extérieures supérieures. En général, une seule couche supérieure est suffisante pour générer des surfaces supérieures de qualité."
-#: fdmprinter.def.json
-msgctxt "roofing_pattern label"
-msgid "Top Surface Skin Pattern"
-msgstr "Motif de couche extérieure de surface supérieure"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern description"
-msgid "The pattern of the top most layers."
-msgstr "Le motif des couches supérieures."
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option lines"
-msgid "Lines"
-msgstr "Lignes"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option concentric"
-msgid "Concentric"
-msgstr "Concentrique"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option zigzag"
-msgid "Zig Zag"
-msgstr "Zig Zag"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles label"
-msgid "Top Surface Skin Line Directions"
-msgstr "Sens de lignes de couche extérieure de surface supérieure"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles description"
-msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
-msgstr "Une liste de sens de ligne (exprimés en nombres entiers) à utiliser lorsque les couches extérieures de la surface supérieure utilisent le motif en lignes ou en zig zag. Les éléments de la liste sont utilisés de manière séquentielle à mesure de l'avancement des couches. La liste reprend depuis le début lorsque la fin est atteinte. Les éléments de la liste sont séparés par des virgules et la liste entière est encadrée entre crochets. La valeur par défaut est une liste vide, ce qui signifie que les angles traditionnels par défaut seront utilisés (45 et 135 degrés)."
-
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr label"
msgid "Top/Bottom Extruder"
@@ -903,7 +848,7 @@ msgstr "Extrudeuse du dessus/dessous"
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr description"
msgid "The extruder train used for printing the top and bottom skin. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse utilisé pour l'impression de la couche extérieure du haut et du bas. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion utilisé pour l'impression de la couche extérieure du haut et du bas. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "top_bottom_thickness label"
@@ -1018,12 +963,22 @@ msgstr "Une liste de sens de ligne (exprimés en nombres entiers) à utiliser lo
#: fdmprinter.def.json
msgctxt "wall_0_inset label"
msgid "Outer Wall Inset"
-msgstr "Insert de paroi externe"
+msgstr "Enchevêtrement de la paroi externe"
#: fdmprinter.def.json
msgctxt "wall_0_inset description"
msgid "Inset applied to the path of the outer wall. If the outer wall is smaller than the nozzle, and printed after the inner walls, use this offset to get the hole in the nozzle to overlap with the inner walls instead of the outside of the model."
-msgstr "Insert appliqué sur le passage de la paroi externe. Si la paroi externe est plus petite que la buse et imprimée après les parois intérieures, utiliser ce décalage pour que le trou dans la buse chevauche les parois internes et non l'extérieur du modèle."
+msgstr "Enchevêtrement appliqué sur le passage de la paroi externe. Si la paroi externe est plus petite que la buse et imprimée après les parois intérieures, utiliser cet Offset pour que le trou dans la buse chevauche les parois internes et non l'extérieur du modèle."
+
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order label"
+msgid "Optimize Wall Printing Order"
+msgstr "Optimiser l'ordre d'impression des parois"
+
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order description"
+msgid "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."
+msgstr "Optimiser l'ordre dans lequel des parois sont imprimées de manière à réduire le nombre de retraits et les distances parcourues. La plupart des pièces bénéficieront de cette possibilité, mais certaines peuvent en fait prendre plus de temps à l'impression ; veuillez dès lors comparer les estimations de durée d'impression avec et sans optimisation."
#: fdmprinter.def.json
msgctxt "outer_inset_first label"
@@ -1078,12 +1033,12 @@ msgstr "Compenser le débit pour les parties d'une paroi intérieure imprimées
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps label"
msgid "Fill Gaps Between Walls"
-msgstr "Remplir les trous entre les parois"
+msgstr "Remplir l'espace entre les parois"
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps description"
msgid "Fills the gaps between walls where no walls fit."
-msgstr "Imprime les remplissages entre les parois lorsqu'aucune paroi ne convient."
+msgstr "Rempli l'espace entre les parois lorsqu'aucune paroi ne convient."
#: fdmprinter.def.json
msgctxt "fill_perimeter_gaps option nowhere"
@@ -1095,6 +1050,16 @@ msgctxt "fill_perimeter_gaps option everywhere"
msgid "Everywhere"
msgstr "Partout"
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps label"
+msgid "Filter Out Tiny Gaps"
+msgstr "Filtrer les petits espaces"
+
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps description"
+msgid "Filter out tiny gaps to reduce blobs on outside of model."
+msgstr "Filtrer les petits espaces pour réduire la présence de gouttes à l'extérieur du modèle."
+
#: fdmprinter.def.json
msgctxt "fill_outline_gaps label"
msgid "Print Thin Walls"
@@ -1108,12 +1073,12 @@ msgstr "Imprimer les parties du modèle qui sont horizontalement plus fines que
#: fdmprinter.def.json
msgctxt "xy_offset label"
msgid "Horizontal Expansion"
-msgstr "Vitesse d’impression horizontale"
+msgstr "Expansion horizontale"
#: fdmprinter.def.json
msgctxt "xy_offset description"
msgid "Amount of offset applied to all polygons in each layer. Positive values can compensate for too big holes; negative values can compensate for too small holes."
-msgstr "Le décalage appliqué à tous les polygones dans chaque couche. Une valeur positive peut compenser les trous trop gros ; une valeur négative peut compenser les trous trop petits."
+msgstr "L'offset appliqué à tous les polygones dans chaque couche. Une valeur positive peut compenser les trous trop gros ; une valeur négative peut compenser les trous trop petits."
#: fdmprinter.def.json
msgctxt "xy_offset_layer_0 label"
@@ -1123,7 +1088,7 @@ msgstr "Expansion horizontale de la couche initiale"
#: fdmprinter.def.json
msgctxt "xy_offset_layer_0 description"
msgid "Amount of offset applied to all polygons in the first layer. A negative value can compensate for squishing of the first layer known as \"elephant's foot\"."
-msgstr "Le décalage appliqué à tous les polygones dans la première couche. Une valeur négative peut compenser l'écrasement de la première couche, appelé « patte d'éléphant »."
+msgstr "L'offset appliqué à tous les polygones dans la première couche. Une valeur négative peut compenser l'écrasement de la première couche, appelé « patte d'éléphant »."
#: fdmprinter.def.json
msgctxt "z_seam_type label"
@@ -1133,7 +1098,7 @@ msgstr "Alignement de la jointure en Z"
#: fdmprinter.def.json
msgctxt "z_seam_type description"
msgid "Starting point of each path in a layer. When paths in consecutive layers start at the same point a vertical seam may show on the print. When aligning these near a user specified location, the seam is easiest to remove. When placed randomly the inaccuracies at the paths' start will be less noticeable. When taking the shortest path the print will be quicker."
-msgstr "Point de départ de chaque voie dans une couche. Quand les voies dans les couches consécutives démarrent au même endroit, une jointure verticale peut apparaître sur l'impression. En alignant les points de départ près d'un emplacement défini par l'utilisateur, la jointure sera plus facile à faire disparaître. Lorsqu'elles sont disposées de manière aléatoire, les imprécisions de départ des voies seront moins visibles. En choisissant la voie la plus courte, l'impression se fera plus rapidement."
+msgstr "Point de départ de chaque chemin dans une couche. Quand les chemins dans les couches consécutives démarrent au même endroit, une jointure verticale peut apparaître sur l'impression. En alignant les points de départ près d'un emplacement défini par l'utilisateur, la jointure sera plus facile à faire disparaître. Lorsqu'elles sont disposées de manière aléatoire, les imprécisions de départ des chemins seront moins visibles. En choisissant le chemin le plus court, l'impression se fera plus rapidement."
#: fdmprinter.def.json
msgctxt "z_seam_type option back"
@@ -1218,12 +1183,12 @@ msgstr "Si cette option est activée, les coordonnées de la jointure z sont rel
#: fdmprinter.def.json
msgctxt "skin_no_small_gaps_heuristic label"
msgid "Ignore Small Z Gaps"
-msgstr "Ignorer les petits trous en Z"
+msgstr "Ignorer les petits espaces en Z"
#: fdmprinter.def.json
msgctxt "skin_no_small_gaps_heuristic description"
msgid "When the model has small vertical gaps, about 5% extra computation time can be spent on generating top and bottom skin in these narrow spaces. In such case, disable the setting."
-msgstr "Quand le modèle présente de petits trous verticaux, environ 5 % de temps de calcul supplémentaire peut être alloué à la génération de couches du dessus et du dessous dans ces espaces étroits. Dans ce cas, désactivez ce paramètre."
+msgstr "Quand le modèle présente de petits espaces verticaux, environ 5 % de temps de calcul supplémentaire peut être alloué à la génération de couches du dessus et du dessous dans ces espaces étroits. Dans ce cas, désactivez ce paramètre."
#: fdmprinter.def.json
msgctxt "skin_outline_count label"
@@ -1238,32 +1203,32 @@ msgstr "Remplace la partie la plus externe du motif du dessus/dessous par un cer
#: fdmprinter.def.json
msgctxt "ironing_enabled label"
msgid "Enable Ironing"
-msgstr "Activer l'étirage"
+msgstr "Activer le lissage"
#: fdmprinter.def.json
msgctxt "ironing_enabled description"
msgid "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface."
-msgstr "Aller au-dessus de la surface supérieure une fois supplémentaire, mais sans extruder de matériau. Cela signifie de faire fondre le plastique en haut un peu plus, pour créer une surface lisse."
+msgstr "Aller au-dessus de la surface supérieure une fois supplémentaire, mais sans extruder de matériau. Cela signifie de faire fondre le plastique sur les couches supérieures, pour créer une surface lisse."
#: fdmprinter.def.json
msgctxt "ironing_only_highest_layer label"
msgid "Iron Only Highest Layer"
-msgstr "N'étirer que la couche supérieure"
+msgstr "Ne lisser que la couche supérieure"
#: fdmprinter.def.json
msgctxt "ironing_only_highest_layer description"
msgid "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish."
-msgstr "N'exécute un étirage que sur l'ultime couche du maillage. Ceci économise du temps si les couches inférieures ne nécessitent pas de fini lisse de surface."
+msgstr "N'exécute un lissage que sur la dernière couche du maillage. Ceci économise du temps si les couches inférieures ne nécessitent pas de finition lissée."
#: fdmprinter.def.json
msgctxt "ironing_pattern label"
msgid "Ironing Pattern"
-msgstr "Motif d'étirage"
+msgstr "Motif de lissage"
#: fdmprinter.def.json
msgctxt "ironing_pattern description"
msgid "The pattern to use for ironing top surfaces."
-msgstr "Le motif à utiliser pour étirer les surfaces supérieures."
+msgstr "Le motif à utiliser pour lisser les surfaces supérieures."
#: fdmprinter.def.json
msgctxt "ironing_pattern option concentric"
@@ -1278,37 +1243,37 @@ msgstr "Zig Zag"
#: fdmprinter.def.json
msgctxt "ironing_line_spacing label"
msgid "Ironing Line Spacing"
-msgstr "Interligne de l'étirage"
+msgstr "Interligne de lissage"
#: fdmprinter.def.json
msgctxt "ironing_line_spacing description"
msgid "The distance between the lines of ironing."
-msgstr "La distance entre les lignes d'étirage."
+msgstr "La distance entre les lignes de lissage"
#: fdmprinter.def.json
msgctxt "ironing_flow label"
msgid "Ironing Flow"
-msgstr "Flux d'étirage"
+msgstr "Flux de lissage"
#: fdmprinter.def.json
msgctxt "ironing_flow description"
msgid "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface."
-msgstr "La quantité de matériau, relative à une ligne de couche extérieure normale, à extruder pendant l'étirage. Le fait de garder la buse pleine aide à remplir certaines des crevasses de la surface supérieure ; mais si la quantité est trop importante, cela entraînera une surextrusion et l'apparition de coupures sur le côté de la surface."
+msgstr "La quantité de matériau, relative à une ligne de couche extérieure normale, à extruder pendant le lissage. Le fait de garder la buse pleine aide à remplir certaines des crevasses de la surface supérieure ; mais si la quantité est trop importante, cela entraînera une surextrusion et l'apparition de coupures sur le côté de la surface."
#: fdmprinter.def.json
msgctxt "ironing_inset label"
msgid "Ironing Inset"
-msgstr "Insert d'étirage"
+msgstr "Chevauchement du lissage"
#: fdmprinter.def.json
msgctxt "ironing_inset description"
msgid "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print."
-msgstr "Distance à garder à partir des bords du modèle. Étirer jusqu'au bord de la maille peut entraîner l'apparition d'un bord denté sur votre impression."
+msgstr "Distance à garder à partir des bords du modèle. Lisser jusqu'au bord de la maille peut entraîner l'apparition d'un bord denté sur votre impression."
#: fdmprinter.def.json
msgctxt "speed_ironing label"
msgid "Ironing Speed"
-msgstr "Vitesse d'étirage"
+msgstr "Vitesse de lissage"
#: fdmprinter.def.json
msgctxt "speed_ironing description"
@@ -1318,22 +1283,22 @@ msgstr "La vitesse à laquelle passer sur la surface supérieure."
#: fdmprinter.def.json
msgctxt "acceleration_ironing label"
msgid "Ironing Acceleration"
-msgstr "Accélération d'étirage"
+msgstr "Accélération du lissage"
#: fdmprinter.def.json
msgctxt "acceleration_ironing description"
msgid "The acceleration with which ironing is performed."
-msgstr "L'accélération selon laquelle l'étirage est effectué."
+msgstr "L'accélération selon laquelle le lissage est effectué."
#: fdmprinter.def.json
msgctxt "jerk_ironing label"
msgid "Ironing Jerk"
-msgstr "Saccade d'étirage"
+msgstr "Saccade du lissage"
#: fdmprinter.def.json
msgctxt "jerk_ironing description"
msgid "The maximum instantaneous velocity change while performing ironing."
-msgstr "Le changement instantané maximal de vitesse lors de l'étirage."
+msgstr "Le changement instantané maximal de vitesse lors du lissage."
#: fdmprinter.def.json
msgctxt "infill label"
@@ -1353,7 +1318,7 @@ msgstr "Extrudeuse de remplissage"
#: fdmprinter.def.json
msgctxt "infill_extruder_nr description"
msgid "The extruder train used for printing infill. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse utilisé pour l'impression du remplissage. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion utilisé pour l'impression du remplissage. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "infill_sparse_density label"
@@ -1378,12 +1343,12 @@ msgstr "Distance entre les lignes de remplissage imprimées. Ce paramètre est c
#: fdmprinter.def.json
msgctxt "infill_pattern label"
msgid "Infill Pattern"
-msgstr "Motif de remplissage"
+msgstr "Motif du remplissage"
#: fdmprinter.def.json
msgctxt "infill_pattern description"
msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
-msgstr "Motif du matériau de remplissage de l'impression. La ligne et le remplissage en zigzag changent de sens à chaque alternance de couche, réduisant ainsi les coûts matériels. Les motifs en grille, en triangle, trihexagonaux, cubiques, octaédriques, quart cubiques et concentriques sont entièrement imprimés sur chaque couche. Les remplissages cubique, quart cubique et octaédrique changent à chaque couche afin d'offrir une répartition plus égale de la solidité dans chaque direction."
+msgstr "Le motif du remplissage de l'impression. La ligne et le remplissage en zigzag changent de sens à chaque alternance de couche, réduisant ainsi les coûts matériels. Les motifs en grille, en triangle, trihexagonaux, cubiques, octaédriques, quart cubiques et concentriques sont entièrement imprimés sur chaque couche. Les remplissages cubique, quart cubique et octaédrique changent à chaque couche afin d'offrir une répartition plus égale de la solidité dans chaque direction."
#: fdmprinter.def.json
msgctxt "infill_pattern option grid"
@@ -1473,22 +1438,22 @@ msgstr "Une liste de sens de ligne (exprimés en nombres entiers) à utiliser. L
#: fdmprinter.def.json
msgctxt "infill_offset_x label"
msgid "Infill X Offset"
-msgstr "Remplissage Décalage X"
+msgstr "Offset Remplissage X"
#: fdmprinter.def.json
msgctxt "infill_offset_x description"
-msgid "The infill pattern is offset this distance along the X axis."
-msgstr "Le motif de remplissage est décalé de cette distance sur l'axe X."
+msgid "The infill pattern is moved this distance along the X axis."
+msgstr "Le motif de remplissage est décalé de cette distance sur l'axe X."
#: fdmprinter.def.json
msgctxt "infill_offset_y label"
msgid "Infill Y Offset"
-msgstr "Remplissage Décalage Y"
+msgstr "Remplissage Offset Y"
#: fdmprinter.def.json
msgctxt "infill_offset_y description"
-msgid "The infill pattern is offset this distance along the Y axis."
-msgstr "Le motif de remplissage est décalé de cette distance sur l'axe Y."
+msgid "The infill pattern is moved this distance along the Y axis."
+msgstr "Le motif de remplissage est décalé de cette distance sur l'axe Y."
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
@@ -1507,8 +1472,8 @@ msgstr "Pourcentage de chevauchement du remplissage"
#: fdmprinter.def.json
msgctxt "infill_overlap description"
-msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
-msgstr "Le degré de chevauchement entre le remplissage et les parois. Un léger chevauchement permet de lier fermement les parois au remplissage."
+msgid "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill."
+msgstr "Le degré de chevauchement entre le remplissage et les parois exprimé en pourcentage de la largeur de ligne de remplissage. Un chevauchement faible permet aux parois de se connecter fermement au remplissage."
#: fdmprinter.def.json
msgctxt "infill_overlap_mm label"
@@ -1527,8 +1492,8 @@ msgstr "Pourcentage de chevauchement de la couche extérieure"
#: fdmprinter.def.json
msgctxt "skin_overlap description"
-msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
-msgstr "Le montant de chevauchement entre la couche extérieure et les parois en pourcentage de la largeur de ligne. Un chevauchement faible permet aux parois de se connecter fermement à la couche extérieure. Ce montant est un pourcentage des largeurs moyennes des lignes de la couche extérieure et de la paroi la plus intérieure."
+msgid "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+msgstr "Le montant de chevauchement entre la couche extérieure et les parois en pourcentage de la largeur de ligne de couche extérieure. Un chevauchement faible permet aux parois de se connecter fermement à la couche extérieure. Ce montant est un pourcentage des largeurs moyennes des lignes de la couche extérieure et de la paroi la plus intérieure."
#: fdmprinter.def.json
msgctxt "skin_overlap_mm label"
@@ -1690,16 +1655,6 @@ msgctxt "material description"
msgid "Material"
msgstr "Matériau"
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature label"
-msgid "Auto Temperature"
-msgstr "Température auto"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature description"
-msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
-msgstr "Modifie automatiquement la température pour chaque couche en fonction de la vitesse de flux moyenne pour cette couche."
-
#: fdmprinter.def.json
msgctxt "default_material_print_temperature label"
msgid "Default Printing Temperature"
@@ -1708,7 +1663,7 @@ msgstr "Température d’impression par défaut"
#: fdmprinter.def.json
msgctxt "default_material_print_temperature description"
msgid "The default temperature used for printing. This should be the \"base\" temperature of a material. All other print temperatures should use offsets based on this value"
-msgstr "La température par défaut utilisée pour l'impression. Il doit s'agir de la température de « base » d'un matériau. Toutes les autres températures d'impression doivent utiliser des décalages basés sur cette valeur."
+msgstr "La température par défaut utilisée pour l'impression. Il doit s'agir de la température de « base » d'un matériau. Toutes les autres températures d'impression doivent utiliser des offset basés sur cette valeur."
#: fdmprinter.def.json
msgctxt "material_print_temperature label"
@@ -1750,16 +1705,6 @@ msgctxt "material_final_print_temperature description"
msgid "The temperature to which to already start cooling down just before the end of printing."
msgstr "La température à laquelle le refroidissement commence juste avant la fin de l'impression."
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph label"
-msgid "Flow Temperature Graph"
-msgstr "Graphique de la température du flux"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph description"
-msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
-msgstr "Données reliant le flux de matériau (en mm3 par seconde) à la température (degrés Celsius)."
-
#: fdmprinter.def.json
msgctxt "material_extrusion_cool_down_speed label"
msgid "Extrusion Cool Down Speed Modifier"
@@ -1777,8 +1722,8 @@ msgstr "Température du plateau"
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
-msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
-msgstr "Température utilisée pour le plateau chauffant. Si elle est définie sur 0, le plateau ne sera pas chauffé pour cette impression."
+msgid "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted."
+msgstr "Température utilisée pour le plateau chauffant. Si elle est définie sur 0, la température du plateau ne sera pas ajustée."
#: fdmprinter.def.json
msgctxt "material_bed_temperature_layer_0 label"
@@ -1848,7 +1793,7 @@ msgstr "Rétracter au changement de couche"
#: fdmprinter.def.json
msgctxt "retract_at_layer_change description"
msgid "Retract the filament when the nozzle is moving to the next layer."
-msgstr "Rétracter le filament quand le bec se déplace vers la prochaine couche. "
+msgstr "Rétracter le filament quand la buse se déplace vers la prochaine couche."
#: fdmprinter.def.json
msgctxt "retraction_amount label"
@@ -2603,12 +2548,12 @@ msgstr "déplacement"
#: fdmprinter.def.json
msgctxt "retraction_combing label"
msgid "Combing Mode"
-msgstr "Mode de détours"
+msgstr "Mode detour"
#: fdmprinter.def.json
msgctxt "retraction_combing description"
msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
-msgstr "Les détours (le 'combing') maintiennent le bec dans les zones déjà imprimées lors des déplacements. Cela résulte en des déplacements légèrement plus longs mais réduit le recours aux rétractions. Si les détours sont désactivés, le matériau se rétractera et le bec se déplacera en ligne droite jusqu'au point suivant. Il est également possible d'éviter les détours sur les zones de la couche du dessus / dessous en effectuant les détours uniquement dans le remplissage."
+msgstr "Les détours (le 'combing') maintiennent la buse dans les zones déjà imprimées lors des déplacements. Cela résulte en des déplacements légèrement plus longs mais réduit le recours aux rétractions. Si les détours sont désactivés, le matériau se rétractera et la buze se déplacera en ligne droite jusqu'au point suivant. Il est également possible d'éviter les détours sur les zones de la couche du dessus / dessous en effectuant les détours uniquement dans le remplissage."
#: fdmprinter.def.json
msgctxt "retraction_combing option off"
@@ -2788,7 +2733,7 @@ msgstr "La durée de couche qui définit la limite entre la vitesse régulière
#: fdmprinter.def.json
msgctxt "cool_fan_speed_0 label"
msgid "Initial Fan Speed"
-msgstr "Vitesse des ventilateurs initiale"
+msgstr "Vitesse initiale des ventilateurs"
#: fdmprinter.def.json
msgctxt "cool_fan_speed_0 description"
@@ -2873,7 +2818,7 @@ msgstr "Extrudeuse de support"
#: fdmprinter.def.json
msgctxt "support_extruder_nr description"
msgid "The extruder train to use for printing the support. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse à utiliser pour l'impression du support. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion à utiliser pour l'impression du support. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "support_infill_extruder_nr label"
@@ -2883,7 +2828,7 @@ msgstr "Extrudeuse de remplissage du support"
#: fdmprinter.def.json
msgctxt "support_infill_extruder_nr description"
msgid "The extruder train to use for printing the infill of the support. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse à utiliser pour l'impression du remplissage du support. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion à utiliser pour l'impression du remplissage du support. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "support_extruder_nr_layer_0 label"
@@ -2893,7 +2838,7 @@ msgstr "Extrudeuse de support de la première couche"
#: fdmprinter.def.json
msgctxt "support_extruder_nr_layer_0 description"
msgid "The extruder train to use for printing the first layer of support infill. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse à utiliser pour l'impression de la première couche de remplissage du support. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion à utiliser pour l'impression de la première couche de remplissage du support. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "support_interface_extruder_nr label"
@@ -2903,7 +2848,7 @@ msgstr "Extrudeuse de l'interface du support"
#: fdmprinter.def.json
msgctxt "support_interface_extruder_nr description"
msgid "The extruder train to use for printing the roofs and floors of the support. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse à utiliser pour l'impression des plafonds et bas du support. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion à utiliser pour l'impression des plafonds et bas du support. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "support_roof_extruder_nr label"
@@ -2913,7 +2858,7 @@ msgstr "Extrudeuse des plafonds de support"
#: fdmprinter.def.json
msgctxt "support_roof_extruder_nr description"
msgid "The extruder train to use for printing the roofs of the support. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse à utiliser pour l'impression des plafonds du support. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion à utiliser pour l'impression des plafonds du support. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "support_bottom_extruder_nr label"
@@ -2923,7 +2868,7 @@ msgstr "Extrudeuse des bas de support"
#: fdmprinter.def.json
msgctxt "support_bottom_extruder_nr description"
msgid "The extruder train to use for printing the floors of the support. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse à utiliser pour l'impression des bas du support. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion à utiliser pour l'impression des bas du support. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "support_type label"
@@ -3138,7 +3083,7 @@ msgstr "Expansion horizontale des supports"
#: fdmprinter.def.json
msgctxt "support_offset description"
msgid "Amount of offset applied to all support polygons in each layer. Positive values can smooth out the support areas and result in more sturdy support."
-msgstr "Le décalage appliqué à tous les polygones pour chaque couche. Une valeur positive peut lisser les zones de support et rendre le support plus solide."
+msgstr "L'offset appliqué à tous les polygones pour chaque couche. Une valeur positive peut lisser les zones de support et rendre le support plus solide."
#: fdmprinter.def.json
msgctxt "support_infill_sparse_thickness label"
@@ -3450,6 +3395,16 @@ msgctxt "support_tower_roof_angle description"
msgid "The angle of a rooftop of a tower. A higher value results in pointed tower roofs, a lower value results in flattened tower roofs."
msgstr "L'angle du toit d'une tour. Une valeur plus élevée entraîne des toits de tour pointus, tandis qu'une valeur plus basse résulte en des toits plats."
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down label"
+msgid "Drop Down Support Mesh"
+msgstr "Maillage de support descendant"
+
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down description"
+msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
+msgstr "Inclure du support à tout emplacement sous le maillage de support, de sorte à ce qu'il n'y ait pas de porte-à-faux dans le maillage de support."
+
#: fdmprinter.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
@@ -3528,7 +3483,7 @@ msgstr "Extrudeuse d'adhérence du plateau"
#: fdmprinter.def.json
msgctxt "adhesion_extruder_nr description"
msgid "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion."
-msgstr "Le train d'extrudeuse à utiliser pour l'impression de la jupe/la bordure/du radeau. Cela est utilisé en multi-extrusion."
+msgstr "Le système d'extrusion à utiliser pour l'impression de la jupe/la bordure/du radeau. Cela est utilisé en multi-extrusion."
#: fdmprinter.def.json
msgctxt "skirt_line_count label"
@@ -3550,7 +3505,9 @@ msgctxt "skirt_gap description"
msgid ""
"The horizontal distance between the skirt and the first layer of the print.\n"
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
-msgstr "La distance horizontale entre la jupe et la première couche de l’impression.\nIl s’agit de la distance minimale séparant la jupe de l’objet. Si la jupe a d’autres lignes, celles-ci s’étendront vers l’extérieur."
+msgstr ""
+"La distance horizontale entre la jupe et la première couche de l’impression.\n"
+"Il s’agit de la distance minimale séparant la jupe de l’objet. Si la jupe a d’autres lignes, celles-ci s’étendront vers l’extérieur."
#: fdmprinter.def.json
msgctxt "skirt_brim_minimal_length label"
@@ -3975,7 +3932,7 @@ msgstr "Compensation du débit : la quantité de matériau extrudée est multip
#: fdmprinter.def.json
msgctxt "prime_tower_wipe_enabled label"
msgid "Wipe Inactive Nozzle on Prime Tower"
-msgstr "Essuyer le bec d'impression inactif sur la tour primaire"
+msgstr "Essuyer la buse d'impression inactif sur la tour primaire"
#: fdmprinter.def.json
msgctxt "prime_tower_wipe_enabled description"
@@ -4082,16 +4039,6 @@ msgctxt "meshfix_keep_open_polygons description"
msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode."
msgstr "Normalement, Cura essaye de raccommoder les petits trous dans le maillage et supprime les parties des couches contenant de gros trous. Activer cette option pousse Cura à garder les parties qui ne peuvent être raccommodées. Cette option doit être utilisée en dernier recours quand tout le reste échoue à produire un GCode correct."
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution label"
-msgid "Maximum Resolution"
-msgstr "Résolution maximum"
-
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution description"
-msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
-msgstr "Taille minimum d'un segment de ligne après découpage. Si vous augmentez cette valeur, la maille aura une résolution plus faible. Cela peut permettre à l'imprimante de suivre la vitesse à laquelle elle doit traiter le G-Code et augmentera la vitesse de découpe en enlevant des détails de la maille que l'imprimante ne peut pas traiter de toute manière."
-
#: fdmprinter.def.json
msgctxt "multiple_mesh_overlap label"
msgid "Merged Meshes Overlap"
@@ -4242,16 +4189,6 @@ msgctxt "support_mesh description"
msgid "Use this mesh to specify support areas. This can be used to generate support structure."
msgstr "Utiliser ce maillage pour spécifier des zones de support. Cela peut être utilisé pour générer une structure de support."
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down label"
-msgid "Drop Down Support Mesh"
-msgstr "Maillage de support descendant"
-
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down description"
-msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
-msgstr "Inclure du support à tout emplacement sous le maillage de support, de sorte à ce qu'il n'y ait pas de porte-à-faux dans le maillage de support."
-
#: fdmprinter.def.json
msgctxt "anti_overhang_mesh label"
msgid "Anti Overhang Mesh"
@@ -4328,14 +4265,194 @@ msgid "experimental!"
msgstr "expérimental !"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order label"
-msgid "Optimize Wall Printing Order"
-msgstr "Optimiser l'ordre d'impression des parois"
+msgctxt "support_tree_enable label"
+msgid "Tree Support"
+msgstr "Support arborescent"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order description"
-msgid "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."
-msgstr "Optimiser l'ordre dans lequel des parois sont imprimées de manière à réduire le nombre de retraits et les distances parcourues. La plupart des pièces bénéficieront de cette possibilité, mais certaines peuvent en fait prendre plus de temps à l'impression ; veuillez dès lors comparer les estimations de durée d'impression avec et sans optimisation."
+msgctxt "support_tree_enable description"
+msgid "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time."
+msgstr "Générer un support arborescent avec des branches qui soutiennent votre impression. Cela peut réduire l'utilisation de matériau et le temps d'impression, mais augmente considérablement le temps de découpage."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle label"
+msgid "Tree Support Branch Angle"
+msgstr "Angle des branches de support arborescent"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle description"
+msgid "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."
+msgstr "Angle des branches. Utilisez un angle plus faible pour les rendre plus verticales et plus stables ; utilisez un angle plus élevé pour avoir plus de portée."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance label"
+msgid "Tree Support Branch Distance"
+msgstr "Distance des branches de support arborescent"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance description"
+msgid "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove."
+msgstr "Distance à laquelle doivent se trouver les branches lorsqu'elles touchent le modèle. Si vous réduisez cette distance, le support arborescent touchera le modèle à plus d'endroits, ce qui causera un meilleur porte-à-faux mais rendra le support plus difficile à enlever."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter label"
+msgid "Tree Support Branch Diameter"
+msgstr "Diamètre des branches de support arborescent"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter description"
+msgid "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this."
+msgstr "Diamètre des branches les plus minces du support arborescent. Plus les branches sont épaisses, plus elles sont robustes ; les branches proches de la base seront plus épaisses que cette valeur."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle label"
+msgid "Tree Support Branch Diameter Angle"
+msgstr "Angle de diamètre des branches de support arborescent"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle description"
+msgid "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support."
+msgstr "Angle du diamètre des branches au fur et à mesure qu'elles s'épaississent lorsqu'elles sont proches du fond. Avec un angle de 0°, les branches auront une épaisseur uniforme sur toute leur longueur. Donner un peu d'angle permet d'augmenter la stabilité du support arborescent."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution label"
+msgid "Tree Support Collision Resolution"
+msgstr "Résolution de collision du support arborescent"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution description"
+msgid "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically."
+msgstr "Résolution servant à calculer les collisions afin d'éviter de heurter le modèle. Plus ce paramètre est faible, plus les arborescences seront précises et stables, mais cela augmente considérablement le temps de découpage."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness label"
+msgid "Tree Support Wall Thickness"
+msgstr "Épaisseur de la paroi du support arborescent"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness description"
+msgid "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "Épaisseur des parois des branches du support arborescent. Les parois plus épaisses prennent plus de temps à imprimer, mais ne tombent pas aussi facilement."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count label"
+msgid "Tree Support Wall Line Count"
+msgstr "Nombre de lignes de la paroi du support arborescent"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count description"
+msgid "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "Nombre de parois des branches du support arborescent. Les parois plus épaisses prennent plus de temps à imprimer, mais ne tombent pas aussi facilement."
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance label"
+msgid "Slicing Tolerance"
+msgstr "Tolérance à la découpe"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance description"
+msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
+msgstr "Comment découper des couches avec des surfaces diagonales. Les zones d'une couche peuvent être générées en fonction de l'endroit où le milieu de la couche croise la surface (Milieu). Alternativement, chaque couche peut posséder des zones situées à l'intérieur du volume à travers toute la hauteur de la couche (Exclusif), ou une couche peut avoir des zones situées à l'intérieur à tout endroit dans la couche (Inclusif). L'option Exclusif permet de retenir le plus de détails, Inclusif permet d'obtenir une adaptation optimale et Milieu demande le moins de temps de traitement."
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option middle"
+msgid "Middle"
+msgstr "Milieu"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option exclusive"
+msgid "Exclusive"
+msgstr "Exclusif"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option inclusive"
+msgid "Inclusive"
+msgstr "Inclusif"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width label"
+msgid "Top Surface Skin Line Width"
+msgstr "Largeur de ligne de couche extérieure de la surface supérieure"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width description"
+msgid "Width of a single line of the areas at the top of the print."
+msgstr "Largeur d'une seule ligne de la zone en haut de l'impression."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern label"
+msgid "Top Surface Skin Pattern"
+msgstr "Motif de couche extérieure de surface supérieure"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern description"
+msgid "The pattern of the top most layers."
+msgstr "Le motif des couches supérieures."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option lines"
+msgid "Lines"
+msgstr "Lignes"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option concentric"
+msgid "Concentric"
+msgstr "Concentrique"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option zigzag"
+msgid "Zig Zag"
+msgstr "Zig Zag"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles label"
+msgid "Top Surface Skin Line Directions"
+msgstr "Sens de lignes de couche extérieure de surface supérieure"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles description"
+msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
+msgstr "Une liste de sens de ligne (exprimés en nombres entiers) à utiliser lorsque les couches extérieures de la surface supérieure utilisent le motif en lignes ou en zig zag. Les éléments de la liste sont utilisés de manière séquentielle à mesure de l'avancement des couches. La liste reprend depuis le début lorsque la fin est atteinte. Les éléments de la liste sont séparés par des virgules et la liste entière est encadrée entre crochets. La valeur par défaut est une liste vide, ce qui signifie que les angles traditionnels par défaut seront utilisés (45 et 135 degrés)."
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization label"
+msgid "Infill Travel Optimization"
+msgstr "Optimisation du déplacement de remplissage"
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization description"
+msgid "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased."
+msgstr "Lorsque cette option est activée, l'ordre dans lequel les lignes de remplissage sont imprimées est optimisé pour réduire la distance parcourue. La réduction du temps de parcours dépend en grande partie du modèle à découper, du type de remplissage, de la densité, etc. Remarque : pour certains modèles possédant beaucoup de petites zones de remplissage, le temps de découpe du modèle peut en être considérablement augmenté."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature label"
+msgid "Auto Temperature"
+msgstr "Température auto"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature description"
+msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
+msgstr "Modifie automatiquement la température pour chaque couche en fonction de la vitesse de flux moyenne pour cette couche."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph label"
+msgid "Flow Temperature Graph"
+msgstr "Graphique de la température du flux"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph description"
+msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
+msgstr "Données reliant le flux de matériau (en mm3 par seconde) à la température (degrés Celsius)."
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution label"
+msgid "Maximum Resolution"
+msgstr "Résolution maximum"
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution description"
+msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
+msgstr "Taille minimum d'un segment de ligne après découpage. Si vous augmentez cette valeur, la maille aura une résolution plus faible. Cela peut permettre à l'imprimante de suivre la vitesse à laquelle elle doit traiter le G-Code et augmentera la vitesse de découpe en enlevant des détails de la maille que l'imprimante ne peut pas traiter de toute manière."
#: fdmprinter.def.json
msgctxt "support_skip_some_zags label"
@@ -4555,7 +4672,7 @@ msgstr "Insert en spaghettis"
#: fdmprinter.def.json
msgctxt "spaghetti_inset description"
msgid "The offset from the walls from where the spaghetti infill will be printed."
-msgstr "Le décalage à partir des parois depuis lesquelles le remplissage en spaghettis sera imprimé."
+msgstr "L'offset à partir des parois depuis lesquelles le remplissage en spaghettis sera imprimé."
#: fdmprinter.def.json
msgctxt "spaghetti_flow label"
@@ -4660,7 +4777,7 @@ msgstr "Distance moyenne entre les points ajoutés aléatoirement sur chaque seg
#: fdmprinter.def.json
msgctxt "flow_rate_max_extrusion_offset label"
msgid "Flow rate compensation max extrusion offset"
-msgstr "Décalage d'extrusion max. pour compensation du débit"
+msgstr "Offset d'extrusion max. pour compensation du débit"
#: fdmprinter.def.json
msgctxt "flow_rate_max_extrusion_offset description"
@@ -4827,7 +4944,9 @@ msgctxt "wireframe_up_half_speed description"
msgid ""
"Distance of an upward move which is extruded with half speed.\n"
"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing."
-msgstr "Distance d’un déplacement ascendant qui est extrudé à mi-vitesse.\nCela peut permettre une meilleure adhérence aux couches précédentes sans surchauffer le matériau dans ces couches. Uniquement applicable à l'impression filaire."
+msgstr ""
+"Distance d’un déplacement ascendant qui est extrudé à mi-vitesse.\n"
+"Cela peut permettre une meilleure adhérence aux couches précédentes sans surchauffer le matériau dans ces couches. Uniquement applicable à l'impression filaire."
#: fdmprinter.def.json
msgctxt "wireframe_top_jump label"
@@ -4934,6 +5053,46 @@ msgctxt "wireframe_nozzle_clearance description"
msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing."
msgstr "Distance entre la buse et les lignes descendantes horizontalement. Un espacement plus important génère des lignes diagonalement descendantes avec un angle moins abrupt, qui génère alors des connexions moins ascendantes avec la couche suivante. Uniquement applicable à l'impression filaire."
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled label"
+msgid "Use adaptive layers"
+msgstr "Utiliser des couches adaptatives"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled description"
+msgid "Adaptive layers computes the layer heights depending on the shape of the model."
+msgstr "Cette option calcule la hauteur des couches en fonction de la forme du modèle."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation label"
+msgid "Adaptive layers maximum variation"
+msgstr "Variation maximale des couches adaptatives"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation description"
+msgid "The maximum allowed height different from the base layer height in mm."
+msgstr "Hauteur maximale autorisée par rapport à la couche de base, en mm."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step label"
+msgid "Adaptive layers variation step size"
+msgstr "Taille des étapes de variation des couches adaptatives"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step description"
+msgid "The difference in height of the next layer height compared to the previous one."
+msgstr "Différence de hauteur de la couche suivante par rapport à la précédente."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold label"
+msgid "Adaptive layers threshold"
+msgstr "Limite des couches adaptatives"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold description"
+msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
+msgstr "Limite indiquant d'utiliser ou non une couche plus petite. Ce nombre est comparé à la tangente de la pente la plus raide d'une couche."
+
#: fdmprinter.def.json
msgctxt "command_line_settings label"
msgid "Command Line Settings"
@@ -4982,7 +5141,7 @@ msgstr "Position z de la maille"
#: fdmprinter.def.json
msgctxt "mesh_position_z description"
msgid "Offset applied to the object in the z direction. With this you can perform what was used to be called 'Object Sink'."
-msgstr "Décalage appliqué à l'objet dans le sens z. Cela vous permet d'exécuter ce que l'on appelait « Affaissement de l'objet »."
+msgstr "Offset appliqué à l'objet dans le sens z. Cela vous permet d'exécuter ce que l'on appelait « Affaissement de l'objet »."
#: fdmprinter.def.json
msgctxt "mesh_rotation_matrix label"
@@ -4994,6 +5153,26 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Matrice de transformation à appliquer au modèle lors de son chargement depuis le fichier."
+#~ msgctxt "infill_offset_x description"
+#~ msgid "The infill pattern is offset this distance along the X axis."
+#~ msgstr "Le motif de remplissage est décalé de cette distance sur l'axe X."
+
+#~ msgctxt "infill_offset_y description"
+#~ msgid "The infill pattern is offset this distance along the Y axis."
+#~ msgstr "Le motif de remplissage est décalé de cette distance sur l'axe Y."
+
+#~ msgctxt "infill_overlap description"
+#~ msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
+#~ msgstr "Le degré de chevauchement entre le remplissage et les parois. Un léger chevauchement permet de lier fermement les parois au remplissage."
+
+#~ msgctxt "skin_overlap description"
+#~ msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+#~ msgstr "Le montant de chevauchement entre la couche extérieure et les parois en pourcentage de la largeur de ligne. Un chevauchement faible permet aux parois de se connecter fermement à la couche extérieure. Ce montant est un pourcentage des largeurs moyennes des lignes de la couche extérieure et de la paroi la plus intérieure."
+
+#~ msgctxt "material_bed_temperature description"
+#~ msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
+#~ msgstr "Température utilisée pour le plateau chauffant. Si elle est définie sur 0, le plateau ne sera pas chauffé pour cette impression."
+
#~ msgctxt "wall_x_extruder_nr label"
#~ msgid "Inner Walls Extruder"
#~ msgstr "Extrudeuse de parois internes"
diff --git a/resources/i18n/it_IT/cura.po b/resources/i18n/it_IT/cura.po
index a4c9ef40f8..d6f066aa22 100644
--- a/resources/i18n/it_IT/cura.po
+++ b/resources/i18n/it_IT/cura.po
@@ -1,22 +1,22 @@
# Cura
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-11-21 16:58+0100\n"
-"PO-Revision-Date: 2017-11-30 13:05+0100\n"
-"Last-Translator: Bothof \n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
+"PO-Revision-Date: 2018-02-13 13:15+0100\n"
+"Last-Translator: Crea-3D \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:29
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
msgctxt "@action"
msgid "Machine Settings"
msgstr "Impostazioni macchina"
@@ -53,12 +53,11 @@ msgstr "Collegamento a Doodle3D Connect"
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:646
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:875
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:659
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:370
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:78
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:104
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:99
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:355
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:376
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:139
@@ -98,7 +97,7 @@ msgctxt "@info:tooltip"
msgid "Open the Doodle3D Connect web interface"
msgstr "Apri interfaccia web Doodle3D Connect"
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:34
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Visualizza registro modifiche"
@@ -106,85 +105,90 @@ msgstr "Visualizza registro modifiche"
#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:20
msgctxt "@item:inmenu"
msgid "Flatten active settings"
-msgstr "Impostazioni attive profilo appiattito"
+msgstr "Resetta impostazioni attive"
#: /home/ruben/Projects/Cura/plugins/ProfileFlattener/ProfileFlattener.py:32
msgctxt "@info:status"
msgid "Profile has been flattened & activated."
-msgstr "Il profilo è stato appiattito e attivato."
+msgstr "Il profilo è stato resettato e attivato."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:26
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "Stampa USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "Stampa tramite USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:29
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "Stampa tramite USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:31
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Connesso tramite USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:status"
msgid "Unable to start a new job because the printer is busy or not connected."
msgstr "Impossibile avviare un nuovo processo di stampa perché la stampante è occupata o non collegata."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:title"
msgid "Printer Unavailable"
msgstr "Stampante non disponibile"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:status"
msgid "This printer does not support USB printing because it uses UltiGCode flavor."
msgstr "Questa stampante non supporta la stampa tramite USB in quanto utilizza la versione UltiGCode."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:title"
msgid "USB Printing"
msgstr "Stampa USB"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
msgctxt "@info:status"
msgid "Unable to start a new job because the printer does not support usb printing."
msgstr "Impossibile avviare un nuovo processo di stampa perché la stampante non supporta la stampa tramite USB."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1349
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:946
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1418
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1496
msgctxt "@info:title"
msgid "Warning"
-msgstr "Avvertenza"
+msgstr "Attenzione"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
msgctxt "@info"
msgid "Unable to update firmware because there are no printers connected."
msgstr "Impossibile aggiornare il firmware perché non ci sono stampanti collegate."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
#, python-format
msgctxt "@info"
msgid "Could not find firmware required for the printer at %s."
msgstr "Impossibile trovare il firmware richiesto per la stampante a %s."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
msgctxt "@info:title"
msgid "Printer Firmware"
msgstr "Firmware stampante"
+#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Prepare"
+msgstr "Prepara"
+
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Save to Removable Drive"
@@ -228,11 +232,11 @@ msgid "Could not save to removable drive {0}: {1}"
msgstr "Impossibile salvare su unità rimovibile {0}: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:146
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:693
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:701
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:153
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1358
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:160
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1427
msgctxt "@info:title"
msgid "Error"
msgstr "Errore"
@@ -257,7 +261,7 @@ msgstr "Rimuovi"
#, python-brace-format
msgctxt "@action"
msgid "Eject removable device {0}"
-msgstr "Rimuovi il dispositivo rimovibile {0}"
+msgstr "Espelli il dispositivo rimovibile {0}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:156
#, python-brace-format
@@ -282,7 +286,7 @@ msgid "Removable Drive"
msgstr "Unità rimovibile"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:53
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:51
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "Stampa sulla rete"
@@ -396,113 +400,113 @@ msgctxt "@info:title"
msgid "Printer Status"
msgstr "Stato stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:691
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No Printcore loaded in slot {0}"
msgstr "Impossibile avviare un nuovo processo di stampa. Nessun Printcore caricato nello slot {0}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:699
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No material loaded in slot {0}"
msgstr "Impossibile avviare un nuovo processo di stampa. Nessun materiale caricato nello slot {0}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:709
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:710
#, python-brace-format
msgctxt "@label"
msgid "Not enough material for spool {0}."
msgstr "Materiale per la bobina insufficiente {0}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:719
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:720
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "PrintCore diverso (Cura: {0}, Stampante: {1}) selezionata per estrusore {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:733
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:734
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "Materiale diverso (Cura: {0}, Stampante: {1}) selezionato per l’estrusore {2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:741
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:742
#, python-brace-format
msgctxt "@label"
msgid "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer."
msgstr "PrintCore {0} non correttamente calibrato. Necessario eseguire calibrazione XY sulla stampante."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:746
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "Sei sicuro di voler stampare con la configurazione selezionata?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:748
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
-msgstr "Le configurazioni o la calibrazione della stampante e di Cura non corrispondono. Per ottenere i migliori risultati, sezionare sempre per i PrintCore e i materiali inseriti nella stampante utilizzata."
+msgstr "Le configurazioni o la calibrazione della stampante e di Cura non corrispondono. Per risultati ottimali, sezionare sempre i PrintCore e i materiali inseriti nella stampante utilizzata."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:753
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:754
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "Mancata corrispondenza della configurazione"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:864
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:865
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:258
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "Invio nuovi processi (temporaneamente) bloccato, invio in corso precedente processo di stampa."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "Invio dati alla stampante in corso"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:title"
msgid "Sending Data"
msgstr "Invio dati"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:944
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
msgctxt "@info:status"
msgid "Unable to send data to printer. Is another job still active?"
msgstr "Impossibile inviare i dati alla stampante. Altro processo ancora attivo?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1085
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1087
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:196
msgctxt "@label:MonitorStatus"
msgid "Aborting print..."
msgstr "Interruzione stampa in corso..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1091
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1093
msgctxt "@label:MonitorStatus"
msgid "Print aborted. Please check the printer"
msgstr "Stampa interrotta. Controllare la stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1097
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
msgctxt "@label:MonitorStatus"
msgid "Pausing print..."
msgstr "Messa in pausa stampa..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1101
msgctxt "@label:MonitorStatus"
msgid "Resuming print..."
msgstr "Ripresa stampa..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1289
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "Sincronizzazione con la stampante"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "Desideri utilizzare la configurazione corrente della tua stampante in Cura?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1295
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
-msgstr "I PrintCore e/o i materiali sulla stampante differiscono da quelli contenuti nel tuo attuale progetto. Per ottenere i risultati migliori, sezionare sempre per i PrintCore e i materiali inseriti nella stampante utilizzata."
+msgstr "I PrintCore e/o i materiali sulla stampante differiscono da quelli contenuti nel tuo attuale progetto. Per risultati ottimali, sezionare sempre i PrintCore e i materiali inseriti nella stampante utilizzata."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:112
msgid "This printer is not set up to host a group of connected Ultimaker 3 printers."
@@ -517,148 +521,191 @@ msgstr "Questa stampante fa da host per un gruppo di {count} stampanti Ultimaker
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:114
#, python-brace-format
msgid "{printer_name} has finished printing '{job_name}'. Please collect the print and confirm clearing the build plate."
-msgstr "{printer_name} ha terminato la stampa '{job_name}'. Raccogliere la stampa e confermare la liberazione del piano di stampa."
+msgstr "{printer_name} ha terminato la stampa '{job_name}'. Rimuovere la stampa e confermare la pulizia del piano di stampa."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:115
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:520
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:533
#, python-brace-format
msgid "{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing."
msgstr "{printer_name} è riservata per la stampa di '{job_name}'. Modificare la configurazione della stampante in modo che corrisponda al lavoro da eseguire per avviare il processo di stampa."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:277
msgctxt "@info:status"
msgid "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."
msgstr "Impossibile inviare nuovo processo di stampa: questa stampante 3D non è (ancora) configurata per supportare la connessione di un gruppo di stampanti Ultimaker 3."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:410
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to send print job to group {cluster_name}."
msgstr "Impossibile inviare processo di stampa a gruppo {cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:418
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:431
#, python-brace-format
msgctxt "@info:status"
msgid "Sent {file_name} to group {cluster_name}."
msgstr "Inviato {file_name} a gruppo {cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:436
msgctxt "@action:button"
msgid "Show print jobs"
msgstr "Mostra processi di stampa"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:424
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:437
msgctxt "@info:tooltip"
msgid "Opens the print jobs interface in your browser."
msgstr "Apre l'interfaccia processi di stampa sul browser."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:502
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Unknown"
msgstr "Sconosciuto"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:492
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "La stampante '{printer_name}' ha finito di stampare '{job_name}'."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:494
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:497
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:507
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:510
msgctxt "@info:status"
msgid "Print finished"
msgstr "Stampa finita"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:522
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:525
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:535
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:538
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:282
msgctxt "@label:status"
msgid "Action required"
-msgstr "Richiede un'azione"
+msgstr "Azione richiesta"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:643
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:656
#, python-brace-format
msgctxt "@info:progress"
msgid "Sending {file_name} to group {cluster_name}"
msgstr "Invio {file_name} a gruppo {cluster_name}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:19
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:17
msgctxt "@action"
msgid "Connect via Network"
msgstr "Collega tramite rete"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:64
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Monitor"
+msgstr "Controlla"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr "Sono disponibili nuove funzioni per la {machine_name}! Si consiglia di aggiornare il firmware sulla stampante."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:65
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:67
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "Nuovo firmware %s disponibile"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:68
msgctxt "@action:button"
msgid "How to update"
msgstr "Modalità di aggiornamento"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:77
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:79
msgctxt "@info"
msgid "Could not access update information."
msgstr "Non è possibile accedere alle informazioni di aggiornamento."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:199
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
msgctxt "@info:status"
-msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
-msgstr "Rilevati errori all'apertura del file SolidWorks! Controllare se è possibile aprire il file in SolidWorks senza che si verifichino problemi!"
+msgid "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself."
+msgstr "SolidWorks ha segnalato errori all’apertura del file. Si consiglia di risolvere queste problematiche all’interno di SolidWorks stesso."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:31
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
+msgctxt "@info:status"
+msgid ""
+"Found no models inside your drawing. Could you please check it's content again and make sure one part or assembly is inside?\n"
+"\n"
+" Thanks!."
+msgstr "Nessun modello trovato nel disegno. Si prega di controllare nuovamente il contenuto e accertarsi che all’interno vi sia un componente o gruppo.\n\n Grazie."
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
+msgctxt "@info:status"
+msgid ""
+"Found more then one part or assembly inside your drawing. We currently only support drawings with exactly one part or assembly inside.\n"
+"\n"
+"Sorry!"
+msgstr "Trovato più di un componente o gruppo all’interno del disegno. Attualmente sono supportati solo i disegni con esattamente un componente o gruppo all’interno.\n\n Spiacenti."
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
msgctxt "@item:inlistbox"
msgid "SolidWorks part file"
msgstr "File part SolidWorks"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:35
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:29
msgctxt "@item:inlistbox"
msgid "SolidWorks assembly file"
msgstr "File gruppo SolidWorks"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.py:21
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
+msgctxt "@item:inlistbox"
+msgid "SolidWorks drawing file"
+msgstr "File disegno SolidWorks"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"We could not find a valid installation of SolidWorks on your system. That means that either SolidWorks is not installed or you don't own an valid license. Please make sure that running SolidWorks itself works without issues and/or contact your ICT.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr "Gentile cliente,\nnon abbiamo trovato un’installazione valida di SolidWorks nel suo sistema. Questo significa che SolidWorks non è installato o che non possiede una licenza valida. La invitiamo a verificare che l’esecuzione di SolidWorks avvenga senza problemi e/o a contattare il suo ICT.\n\nCordiali saluti\n - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"You are currently running this plugin on an operating system other than Windows. This plugin will only work on Windows with SolidWorks installed, including an valid license. Please install this plugin on a Windows machine with SolidWorks installed.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr "Gentile cliente,\nattualmente ha in esecuzione questo plugin su un sistema operativo diverso da Windows. Questo plugin funziona solo su Windows con SolidWorks installato, con inclusa una licenza valida. Si prega di installare questo plugin su una macchina Windows con SolidWorks installato.\n\nCordiali saluti\n - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
msgid "Configure"
msgstr "Configura"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/CommonComReader.py:135
-#, python-format
-msgctxt "@info:status"
-msgid "Error while starting %s!"
-msgstr "Errore durante l'avvio di %s!"
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
+msgid "Installation guide for SolidWorks macro"
+msgstr "Guida per l’installazione di macro SolidWorks"
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
-msgid "Simulation view"
-msgstr "Vista simulazione"
+msgid "Layer view"
+msgstr "Visualizzazione layer"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "Cura non visualizza in modo accurato gli strati se la funzione Wire Printing è abilitata"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:101
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
msgctxt "@info:title"
msgid "Simulation View"
msgstr "Vista simulazione"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:26
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:25
msgid "Modify G-Code"
msgstr "Modifica G-code"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
msgctxt "@info"
-msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
-msgstr "Cura acquisisce dati statistici elaborati in forma anonima. L'acquisizione può essere disabilitata nelle preferenze."
+msgid "Cura collects anonymized usage statistics."
+msgstr "Cura raccoglie statistiche di utilizzo in forma anonima."
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
msgctxt "@info:title"
@@ -667,14 +714,41 @@ msgstr "Acquisizione dati"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
msgctxt "@action:button"
-msgid "Dismiss"
-msgstr "Ignora"
+msgid "Allow"
+msgstr "Consenti"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+msgctxt "@action:tooltip"
+msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
+msgstr "Consente a Cura di inviare in forma anonima statistiche d’uso, riguardanti alcune delle preferenze e impostazioni, la versione cura e una serie di modelli in sezionamento, per aiutare a dare priorità a miglioramenti futuri in Cura."
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+msgctxt "@action:button"
+msgid "Disable"
+msgstr "Disabilita"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
+msgctxt "@action:tooltip"
+msgid "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."
+msgstr "Non consente a Cura di inviare statistiche di utilizzo in forma anonima. È possibile riabilitare nelle preferenze."
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Profili Cura 15.04"
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
+msgctxt "@item:inlistbox"
+msgid "Blender file"
+msgstr "File Blender"
+
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
+msgctxt "@info:status"
+msgid ""
+"Could not export using \"{}\" quality!\n"
+"Felt back to \"{}\"."
+msgstr "Impossibile esportare utilizzando qualità \"{}\" quality!\nTornato a \"{}\"."
+
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
msgctxt "@item:inlistbox"
@@ -706,49 +780,49 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "Immagine GIF"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
-msgstr "Impossibile eseguire il sezionamento con il materiale corrente in quanto incompatibile con la macchina o la configurazione selezionata."
+msgstr "Impossibile eseguire lo slicing con il materiale corrente in quanto incompatibile con la macchina o la configurazione selezionata."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:297
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:319
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:327
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:336
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:349
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:357
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
msgctxt "@info:title"
msgid "Unable to slice"
-msgstr "Sezionamento impossibile"
+msgstr "Slicing impossibile"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:296
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
-msgstr "Impossibile eseguire il sezionamento con le impostazioni attuali. Le seguenti impostazioni presentano errori: {0}"
+msgstr "Impossibile eseguire lo slicing con le impostazioni attuali. Le seguenti impostazioni presentano errori: {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:318
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:348
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
-msgstr "Impossibile eseguire il sezionamento a causa di alcune impostazioni per modello. Le seguenti impostazioni presentano errori su uno o più modelli: {error_labels}"
+msgstr "Impossibile eseguire lo slicing a causa di alcune impostazioni del modello. Le seguenti impostazioni presentano errori su uno o più modelli: {error_labels}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:356
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
-msgstr "Impossibile eseguire il sezionamento perché la torre di innesco o la posizione di innesco non sono valide."
+msgstr "Impossibile eseguire lo slicing perché la prime tower o la prime position non sono valide."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:335
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:365
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "Nulla da sezionare in quanto nessuno dei modelli corrisponde al volume di stampa. Ridimensionare o ruotare i modelli secondo necessità."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "Elaborazione dei livelli"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:title"
msgid "Information"
msgstr "Informazioni"
@@ -769,30 +843,30 @@ msgstr "Installazione"
#: /home/ruben/Projects/Cura/plugins/cura-siemensnx-plugin/Installer.py:43
msgid "Failed to copy Siemens NX plugins files. Please check your UGII_USER_DIR. It is not set to a directory."
-msgstr "Impossibile copiare i file di plugin Siemens NX. Controllare UGII_USER_DIR. Non è assegnato ad alcuna directory."
+msgstr "Impossibile copiare i file dei plugin Siemens NX. Controllare UGII_USER_DIR. Non è assegnato ad alcuna directory."
#: /home/ruben/Projects/Cura/plugins/cura-siemensnx-plugin/Installer.py:50
#: /home/ruben/Projects/Cura/plugins/cura-siemensnx-plugin/Installer.py:59
#: /home/ruben/Projects/Cura/plugins/cura-siemensnx-plugin/Installer.py:81
msgid "Successfully installed Siemens NX Cura plugin."
-msgstr "Installato correttamente plugin Siemens NX Cura."
+msgstr "Siemens NX Cura plugin installato correttamente."
#: /home/ruben/Projects/Cura/plugins/cura-siemensnx-plugin/Installer.py:65
msgid "Failed to copy Siemens NX plugins files. Please check your UGII_USER_DIR."
-msgstr "Impossibile copiare i file di plugin Siemens NX. Controllare UGII_USER_DIR."
+msgstr "Impossibile copiare i file dei plugin Siemens NX. Controllare UGII_USER_DIR."
#: /home/ruben/Projects/Cura/plugins/cura-siemensnx-plugin/Installer.py:85
msgid "Failed to install Siemens NX plugin. Could not set environment variable UGII_USER_DIR for Siemens NX."
-msgstr "Impossibile installare plugin Siemens NX. Impossibile impostare la variabile di ambiente UGII_USER_DIR per Siemens NX."
+msgstr "Impossibile installare il plugin Siemens NX. Impossibile impostare la variabile di ambiente UGII_USER_DIR per Siemens NX."
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:585
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:165
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
msgctxt "@title:tab"
msgid "Recommended"
msgstr "Consigliata"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:169
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:595
msgctxt "@title:tab"
msgid "Custom"
msgstr "Personalizzata"
@@ -803,24 +877,24 @@ msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "File 3MF"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:126
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1142
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:159
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1185
msgctxt "@label"
msgid "Nozzle"
msgstr "Ugello"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:152
#, python-brace-format
msgctxt "@info:status"
msgid "Failed to get plugin ID from {0}"
-msgstr "Impossibile ottenere ID plugin da {0}"
+msgstr "Impossibile ottenere ID del plugin da {0}"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:165
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:153
msgctxt "@info:tile"
msgid "Warning"
-msgstr "Avvertenza"
+msgstr "Attenzione"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:203
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:191
msgctxt "@window:title"
msgid "Plugin browser"
msgstr "Browser plugin"
@@ -835,21 +909,21 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "File G"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:314
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:321
msgctxt "@info:status"
msgid "Parsing G-code"
-msgstr "Parsing codice G"
+msgstr "Analisi G-code"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:316
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:426
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:323
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:464
msgctxt "@info:title"
msgid "G-code Details"
-msgstr "Dettagli codice G"
+msgstr "Dettagli G-code"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:424
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:462
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
-msgstr "Verifica che il codice G sia idoneo alla tua stampante e alla sua configurazione prima di trasmettere il file. La rappresentazione del codice G potrebbe non essere accurata."
+msgstr "Verifica che il G-code sia idoneo alla tua stampante e alla sua configurazione prima di trasmettere il file. La rappresentazione del G-code potrebbe non essere accurata."
#: /home/ruben/Projects/Cura/plugins/CuraProfileWriter/__init__.py:14
#: /home/ruben/Projects/Cura/plugins/CuraProfileReader/__init__.py:14
@@ -857,6 +931,16 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Profilo Cura"
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Profile Assistant"
+msgstr "Assistente profilo"
+
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
+msgctxt "@item:inlistbox"
+msgid "Profile Assistant"
+msgstr "Assistente profilo"
+
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
msgctxt "@item:inlistbox"
msgid "3MF file"
@@ -888,142 +972,116 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "Livella piano di stampa"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "Parete esterna"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "Pareti interne"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
msgctxt "@tooltip"
msgid "Skin"
msgstr "Rivestimento esterno"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
msgctxt "@tooltip"
msgid "Infill"
msgstr "Riempimento"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:93
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "Riempimento del supporto"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:94
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "Interfaccia supporto"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:95
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
msgctxt "@tooltip"
msgid "Support"
msgstr "Supporto"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:96
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
msgctxt "@tooltip"
msgid "Skirt"
msgstr "Skirt"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:97
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
msgctxt "@tooltip"
msgid "Travel"
msgstr "Spostamenti"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
msgctxt "@tooltip"
msgid "Retractions"
msgstr "Retrazioni"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
msgctxt "@tooltip"
msgid "Other"
msgstr "Altro"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:199
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:231
msgctxt "@label unknown material"
msgid "Unknown"
msgstr "Sconosciuto"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:284
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:318
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "File pre-sezionato {0}"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:469
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:440
msgctxt "@item:material"
msgid "No material loaded"
msgstr "Nessun materiale caricato"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:476
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:447
msgctxt "@item:material"
msgid "Unknown material"
msgstr "Materiale sconosciuto"
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:30
-msgctxt "@info:status"
-msgid "Finding new location for objects"
-msgstr "Ricerca nuova posizione per gli oggetti"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:34
-msgctxt "@info:title"
-msgid "Finding Location"
-msgstr "Ricerca posizione"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:89
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
-msgctxt "@info:status"
-msgid "Unable to find a location within the build volume for all objects"
-msgstr "Impossibile individuare una posizione nel volume di stampa per tutti gli oggetti"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:90
-msgctxt "@info:title"
-msgid "Can't Find Location"
-msgstr "Impossibile individuare posizione"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:431
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:113
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:437
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:120
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "Il file esiste già"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:432
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:114
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:438
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
msgid "The file {0} already exists. Are you sure you want to overwrite it?"
msgstr "Il file {0} esiste già. Sei sicuro di volerlo sovrascrivere?"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:872
msgctxt "@label"
msgid "Custom"
msgstr "Personalizzata"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:819
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:876
msgctxt "@label"
msgid "Custom Material"
msgstr "Materiale personalizzato"
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:182
-msgctxt "@menuitem"
-msgid "Global"
-msgstr "Globale"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:229
+#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:205
msgctxt "@menuitem"
msgid "Not overridden"
msgstr "Non sottoposto a override"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:124
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "Il materiale selezionato è incompatibile con la macchina o la configurazione selezionata."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:125
#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24
msgctxt "@info:title"
msgid "Incompatible Material"
@@ -1044,67 +1102,89 @@ msgctxt "@action"
msgid "Undo changing the material diameter."
msgstr "Annulla modifica del diametro del materiale."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:144
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to export profile to {0}: {1}"
msgstr "Impossibile esportare il profilo su {0}: {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:158
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Failed to export profile to {0}: Writer plugin reported failure."
msgstr "Impossibile esportare il profilo su {0}: Rilevata anomalia durante scrittura plugin."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:156
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:163
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Exported profile to {0}"
msgstr "Profilo esportato su {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:157
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:164
msgctxt "@info:title"
msgid "Export succeeded"
msgstr "Esportazione riuscita"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:183
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:205
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:214
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:248
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:211
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:271
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
msgstr "Impossibile importare il profilo da {0}: {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:216
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:252
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:230
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "This profile {0} contains incorrect data, could not import it."
+msgstr "Questo profilo {0} contiene dati errati, impossibile importarlo."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "The machine defined in profile {0} doesn't match with your current machine, could not import it."
+msgstr "La macchina definita nel profilo {0} non corrisponde alla macchina corrente, impossibile importarlo."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "Profilo importato correttamente {0}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:255
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:277
+#, python-brace-format
+msgctxt "@info:status"
+msgid "File {0} does not contain any valid profile."
+msgstr "Il file {0} non contiene nessun profilo valido."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "Il profilo {0} ha un tipo di file sconosciuto o corrotto."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:298
msgctxt "@label"
msgid "Custom profile"
msgstr "Profilo personalizzato"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:285
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "Il profilo è privo del tipo di qualità."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:349
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "Impossibile trovare un tipo qualità {0} per la configurazione corrente."
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:46
+#, python-brace-format
+msgctxt "@label"
+msgid "Group #{group_nr}"
+msgstr "Gruppo #{group_nr}"
+
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
@@ -1115,142 +1195,167 @@ msgctxt "@info:title"
msgid "Build Volume"
msgstr "Volume di stampa"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:34
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:25
msgctxt "@info:status"
msgid "Multiplying and placing objects"
msgstr "Moltiplicazione e collocazione degli oggetti"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:35
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:26
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
msgctxt "@info:title"
msgid "Placing Object"
msgstr "Sistemazione oggetto"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:80
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:88
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:152
+msgctxt "@info:status"
+msgid "Unable to find a location within the build volume for all objects"
+msgstr "Impossibile individuare una posizione nel volume di stampa per tutti gli oggetti"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:29
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:64
+msgctxt "@info:status"
+msgid "Finding new location for objects"
+msgstr "Ricerca nuova posizione per gli oggetti"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:33
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:68
+msgctxt "@info:title"
+msgid "Finding Location"
+msgstr "Ricerca posizione"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:89
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:153
+msgctxt "@info:title"
+msgid "Can't Find Location"
+msgstr "Impossibile individuare posizione"
+
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:81
msgctxt "@title:window"
msgid "Crash Report"
-msgstr "Rapporto su crash"
+msgstr "Rapporto sul crash"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:93
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:94
msgctxt "@label crash message"
msgid ""
-"
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+"
A fatal error has occurred. Please send us this Crash Report to fix the problem
\n"
"
Please use the \"Send report\" button to post a bug report automatically to our servers
\n"
" "
-msgstr "
Si è verificata un'eccezione irreversibile. Si prega di inviarci questo crash report per risolvere il problema
\n
Utilizzare il pulsante \"Invia report\" per inviare un report sui bug automaticamente ai nostri server
\n "
+msgstr "
Si è verificato un errore fatale. Si prega di inviare questo Report su crash per correggere il problema
\n
Usare il pulsante “Invia report\" per inviare automaticamente una segnalazione errore ai nostri server
"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:141
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:147
msgctxt "@title:groupbox"
-msgid "Exception traceback"
-msgstr "Analisi eccezione"
+msgid "Error traceback"
+msgstr "Analisi errori"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:208
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:214
msgctxt "@title:groupbox"
msgid "Logs"
msgstr "Registri"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:231
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:237
msgctxt "@title:groupbox"
msgid "User description"
msgstr "Descrizione utente"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:246
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:252
msgctxt "@action:button"
msgid "Send report"
msgstr "Invia report"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:256
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:274
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "Caricamento macchine in corso..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:661
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:660
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "Impostazione scena in corso..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:703
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:702
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "Caricamento interfaccia in corso..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:874
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:899
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1348
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
-msgstr "È possibile caricare un solo file codice G per volta. Importazione saltata {0}"
+msgstr "È possibile caricare un solo file G-code per volta. Importazione saltata {0}"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1357
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1426
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
-msgstr "Impossibile aprire altri file durante il caricamento del codice G. Importazione saltata {0}"
+msgstr "Impossibile aprire altri file durante il caricamento del G-code. Importazione saltata {0}"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1416
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1495
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "Il modello selezionato è troppo piccolo per il caricamento."
@@ -1279,12 +1384,11 @@ msgstr "X (Larghezza)"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:288
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:300
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:391
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:413
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:840
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:394
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:424
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:849
msgctxt "@label"
msgid "mm"
msgstr "mm"
@@ -1322,7 +1426,7 @@ msgstr "Versione GCode"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:181
msgctxt "@label"
msgid "Printhead Settings"
-msgstr "Impostazioni della testina di stampa"
+msgstr "Impostazioni della testa di stampa"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:191
msgctxt "@label"
@@ -1332,7 +1436,7 @@ msgstr "X min"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:192
msgctxt "@tooltip"
msgid "Distance from the left of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
-msgstr "Distanza tra il lato sinistro della testina di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testina di stampa durante la stampa \"Uno alla volta\"."
+msgstr "Distanza tra il lato sinistro della testa di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testa di stampa durante la stampa \"Uno alla volta\"."
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:201
msgctxt "@label"
@@ -1342,7 +1446,7 @@ msgstr "Y min"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:202
msgctxt "@tooltip"
msgid "Distance from the front of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
-msgstr "Distanza tra il lato anteriore della testina di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testina di stampa durante la stampa \"Uno alla volta\"."
+msgstr "Distanza tra il lato anteriore della testa di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testa di stampa durante la stampa \"Uno alla volta\"."
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:211
msgctxt "@label"
@@ -1352,7 +1456,7 @@ msgstr "X max"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:212
msgctxt "@tooltip"
msgid "Distance from the right of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
-msgstr "Distanza tra il lato destro della testina di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testina di stampa durante la stampa \"Uno alla volta\"."
+msgstr "Distanza tra il lato destro della testa di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testa di stampa durante la stampa \"Uno alla volta\"."
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:221
msgctxt "@label"
@@ -1362,7 +1466,7 @@ msgstr "Y max"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:222
msgctxt "@tooltip"
msgid "Distance from the rear of the printhead to the center of the nozzle. Used to prevent colissions between previous prints and the printhead when printing \"One at a Time\"."
-msgstr "Distanza tra il lato posteriore della testina di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testina di stampa durante la stampa \"Uno alla volta\"."
+msgstr "Distanza tra il lato posteriore della testa di stampa e il centro dell'ugello. Utilizzata per evitare collisioni tra le stampe precedenti e la testa di stampa durante la stampa \"Uno alla volta\"."
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:234
msgctxt "@label"
@@ -1374,71 +1478,70 @@ msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "La differenza di altezza tra la punta dell’ugello e il sistema gantry (assi X e Y). Utilizzata per evitare collisioni tra le stampe precedenti e il gantry durante la stampa \"Uno alla volta\"."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:254
msgctxt "@label"
msgid "Number of Extruders"
msgstr "Numero di estrusori"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:289
-msgctxt "@tooltip"
-msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
-msgstr "Diametro nominale del filamento supportato dalla stampante. Il diametro esatto verrà sovrapposto dal materiale e/o dal profilo."
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:291
-msgctxt "@label"
-msgid "Material diameter"
-msgstr "Diametro materiale"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:299
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:390
-msgctxt "@label"
-msgid "Nozzle size"
-msgstr "Dimensione ugello"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:317
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:310
msgctxt "@label"
msgid "Start Gcode"
msgstr "Avvio GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:327
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:320
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very start."
msgstr "Comandi Gcode da eseguire all’avvio."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:336
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:329
msgctxt "@label"
msgid "End Gcode"
msgstr "Fine GCode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:346
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:339
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very end."
msgstr "Comandi Gcode da eseguire alla fine."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:378
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:370
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "Impostazioni ugello"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:382
+msgctxt "@label"
+msgid "Nozzle size"
+msgstr "Dimensione ugello"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:393
+msgctxt "@label"
+msgid "Compatible material diameter"
+msgstr "Diametro del materiale compatibile"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:395
+msgctxt "@tooltip"
+msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
+msgstr "Diametro nominale del filamento supportato dalla stampante. Il diametro esatto verrà sovrascritto dal materiale e/o dal profilo."
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:411
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "Scostamento X ugello"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:423
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "Scostamento Y ugello"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:433
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:444
msgctxt "@label"
msgid "Extruder Start Gcode"
-msgstr "Codice G avvio estrusore"
+msgstr "Gcode avvio estrusore"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:462
msgctxt "@label"
msgid "Extruder End Gcode"
-msgstr "Codice G fine estrusore"
+msgstr "Gcode fine estrusore"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:18
msgctxt "@label"
@@ -1448,8 +1551,9 @@ msgstr "Registro modifiche"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:107
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:55
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:445
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:357
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:306
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:456
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:492
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:80
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:123
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
@@ -1514,7 +1618,7 @@ msgid ""
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
"\n"
"Select your printer from the list below:"
-msgstr "Per stampare direttamente sulla stampante in rete, verificare che la stampante desiderata sia collegata alla rete mediante un cavo di rete o mediante collegamento alla rete WIFI. Se si collega Cura alla stampante, è comunque possibile utilizzare una chiavetta USB per trasferire i file codice G alla stampante.\n\nSelezionare la stampante dall’elenco seguente:"
+msgstr "Per stampare direttamente sulla stampante in rete, verificare che la stampante desiderata sia collegata alla rete mediante un cavo di rete o mediante collegamento alla rete WIFI. Se si collega Cura alla stampante, è comunque possibile utilizzare una chiavetta USB per trasferire i file Gcode alla stampante.\n\nSelezionare la stampante dall’elenco seguente:"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:75
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:44
@@ -1530,7 +1634,7 @@ msgstr "Modifica"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:50
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:95
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:171
msgctxt "@action:button"
msgid "Remove"
msgstr "Rimuovi"
@@ -1552,14 +1656,14 @@ msgid "Type"
msgstr "Tipo"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:233
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3"
msgstr "Ultimaker 3"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:236
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3 Extended"
-msgstr "Ultimaker3 Extended"
+msgstr "Ultimaker 3 Extended"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:252
msgctxt "@label"
@@ -1603,8 +1707,6 @@ msgid "Enter the IP address or hostname of your printer on the network."
msgstr "Inserire l’indirizzo IP o l’hostname della stampante sulla rete."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:379
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:92
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:88
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
@@ -1613,7 +1715,7 @@ msgstr "OK"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:24
msgctxt "@title:window"
msgid "Print over network"
-msgstr "Stampa sulla rete"
+msgstr "Stampa tramite rete"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:92
msgctxt "@action:button"
@@ -1625,6 +1727,11 @@ msgctxt "@label: arg 1 is group name"
msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
msgstr "%1 non è configurata per supportare la connessione di un gruppo di stampanti Ultimaker 3"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
+msgctxt "@label link to connect manager"
+msgid "Add/Remove printers"
+msgstr "Aggiungi/Rimuovi stampanti"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
msgctxt "@info:tooltip"
msgid "Opens the print jobs page with your default web browser."
@@ -1655,11 +1762,16 @@ msgid "Available"
msgstr "Disponibile"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:100
msgctxt "@label:MonitorStatus"
msgid "Lost connection with the printer"
msgstr "Persa connessione con la stampante"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
+msgctxt "@label Printer status"
+msgid "Unknown"
+msgstr "Sconosciuto"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:257
msgctxt "@label:status"
msgid "Disabled"
@@ -1704,7 +1816,7 @@ msgstr "Finisce alle: "
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:405
msgctxt "@label"
msgid "Clear build plate"
-msgstr "Cancellare piano di stampa"
+msgstr "Pulire piano di stampa"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:414
msgctxt "@label"
@@ -1751,138 +1863,250 @@ msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "Attiva la configurazione"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:20
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:21
msgctxt "@title:window"
-msgid "Cura SolidWorks Plugin Configuration"
-msgstr "Configurazione plugin Cura SolidWorks"
+msgid "SolidWorks: Export wizard"
+msgstr "SolidWorks: procedura guidata per l’esportazione"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:44
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:45
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:140
msgctxt "@action:label"
-msgid "Default quality of the exported STL:"
-msgstr "Qualità predefinita STL esportato:"
+msgid "Quality:"
+msgstr "Qualità:"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:78
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:179
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always ask"
-msgstr "Chiedi sempre"
+msgid "Fine (3D-printing)"
+msgstr "Fine (stampa 3D)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:180
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Fine quality"
-msgstr "Utilizza sempre la qualità Fine"
+msgid "Coarse (3D-printing)"
+msgstr "Grossolana (stampa 3D)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:181
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Coarse quality"
-msgstr "Utilizza sempre la qualità Grossolana"
+msgid "Fine (SolidWorks)"
+msgstr "Fine (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:20
-msgctxt "@title:window"
-msgid "Import SolidWorks File as STL..."
-msgstr "Importa file SolidWorks come STL..."
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:43
-msgctxt "@info:tooltip"
-msgid "Quality of the Exported STL"
-msgstr "Qualità STL esportato"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:48
-msgctxt "@action:label"
-msgid "Quality"
-msgstr "Qualità"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:62
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:182
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Coarse"
-msgstr "Grossolana"
+msgid "Coarse (SolidWorks)"
+msgstr "Grossolana (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:63
-msgctxt "@option:curaSolidworksStlQuality"
-msgid "Fine"
-msgstr "Fine"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:78
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:82
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:94
msgctxt "@text:window"
-msgid "Remember my choice"
-msgstr "Ricorda la scelta"
+msgid "Show this dialog again"
+msgstr "Mostra nuovamente questa finestra di dialogo"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:104
+msgctxt "@action:button"
+msgid "Continue"
+msgstr "Continua"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:116
+msgctxt "@action:button"
+msgid "Abort"
+msgstr "Interrompi"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:21
+msgctxt "@title:window"
+msgid "How to install Cura SolidWorks macro"
+msgstr "Come installare la macro Cura SolidWorks"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:62
+msgctxt "@description:label"
+msgid "Steps:"
+msgstr "Fasi:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:140
+msgctxt "@action:button"
+msgid ""
+"Open the directory\n"
+"with macro and icon"
+msgstr "Aprire la directory\ncon macro e icona"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:160
+msgctxt "@description:label"
+msgid "Instructions:"
+msgstr "Istruzioni:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:202
+msgctxt "@action:playpause"
+msgid "Play"
+msgstr "Riproduci"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:206
+msgctxt "@action:playpause"
+msgid "Pause"
+msgstr "Pausa"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:268
+msgctxt "@action:button"
+msgid "Previous Step"
+msgstr "Fase precedente"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:283
+msgctxt "@action:button"
+msgid "Done"
+msgstr "Eseguito"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:287
+msgctxt "@action:button"
+msgid "Next Step"
+msgstr "Fase successiva"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:21
+msgctxt "@title:window"
+msgid "SolidWorks plugin: Configuration"
+msgstr "Plugin SolidWorks: configurazione"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:39
+msgctxt "@title:tab"
+msgid "Conversion settings"
+msgstr "Impostazioni di conversione"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:66
+msgctxt "@label"
+msgid "First choice:"
+msgstr "Prima scelta:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:86
+msgctxt "@text:menu"
+msgid "Latest installed version (Recommended)"
+msgstr "Ultima versione installata (consigliata)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:95
+msgctxt "@text:menu"
+msgid "Default version"
+msgstr "Versione predefinita"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:193
+msgctxt "@label"
+msgid "Show wizard before opening SolidWorks files"
+msgstr "Mostra la procedura guidata prima di aprire i file SolidWorks"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:203
+msgctxt "@label"
+msgid "Automatically rotate opened file into normed orientation"
+msgstr "Ruota automaticamente il file aperto nell’orientamento corretto"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:210
+msgctxt "@title:tab"
+msgid "Installation(s)"
+msgstr "Installazione(i)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:284
+msgctxt "@label"
+msgid "COM service found"
+msgstr "Servizio COM trovato"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:295
+msgctxt "@label"
+msgid "Executable found"
+msgstr "Eseguibile trovato"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:306
+msgctxt "@label"
+msgid "COM starting"
+msgstr "COM in avvio"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:317
+msgctxt "@label"
+msgid "Revision number"
+msgstr "Numero di revisione"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:328
+msgctxt "@label"
+msgid "Functions available"
+msgstr "Funzioni disponibili"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+msgctxt "@action:button"
+msgid "Save"
+msgstr "Salva"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
msgctxt "@label"
msgid "Color scheme"
msgstr "Schema colori"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:96
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "Colore materiale"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "Tipo di linea"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "Velocità"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:108
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
msgctxt "@label:listbox"
msgid "Layer thickness"
-msgstr "Spessore strato"
+msgstr "Spessore layer"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:148
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "Modalità di compatibilità"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:230
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
msgctxt "@label"
msgid "Show Travels"
msgstr "Mostra spostamenti"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:236
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
msgctxt "@label"
msgid "Show Helpers"
msgstr "Mostra helper"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:242
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
msgctxt "@label"
msgid "Show Shell"
msgstr "Mostra guscio"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:248
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
msgctxt "@label"
msgid "Show Infill"
msgstr "Mostra riempimento"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:297
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "Mostra solo strati superiori"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:306
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
-msgstr "Mostra 5 strati superiori in dettaglio"
+msgstr "Mostra 5 layer superiori in dettaglio"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:317
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
msgctxt "@label"
msgid "Top / Bottom"
msgstr "Superiore / Inferiore"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:321
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
msgctxt "@label"
msgid "Inner Wall"
msgstr "Parete interna"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:378
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
msgctxt "@label"
msgid "min"
msgstr "min."
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:420
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
msgctxt "@label"
msgid "max"
msgstr "max."
@@ -1907,7 +2131,7 @@ msgctxt "@label"
msgid "Settings"
msgstr "Impostazioni"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:455
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:466
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "Modifica script di post-elaborazione attivi"
@@ -1982,23 +2206,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "Smoothing"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:208
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+msgctxt "@label"
+msgid "Mesh Type"
+msgstr "Tipo di maglia"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+msgctxt "@label"
+msgid "Normal model"
+msgstr "Modello normale"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+msgctxt "@label"
+msgid "Print as support"
+msgstr "Stampa come supporto"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+msgctxt "@label"
+msgid "Don't support overlap with other models"
+msgstr "Non supporta sovrapposizione con altri modelli"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+msgctxt "@label"
+msgid "Modify settings for overlap with other models"
+msgstr "Modifica impostazioni per sovrapposizione con altri modelli"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+msgctxt "@label"
+msgid "Modify settings for infill of other models"
+msgstr "Modifica impostazioni per riempimento di altri modelli"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:333
msgctxt "@action:button"
msgid "Select settings"
msgstr "Seleziona impostazioni"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:248
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:375
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "Seleziona impostazioni di personalizzazione per questo modello"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:272
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:402
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:91
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "Filtro..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:296
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:426
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "Mostra tutto"
@@ -2197,7 +2451,7 @@ msgstr "Seleziona qualsiasi aggiornamento realizzato per questa Ultimaker 2."
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UM2UpgradeSelectionMachineAction.qml:45
msgctxt "@label"
msgid "Olsson Block"
-msgstr "Blocco Olsson"
+msgstr "Olsson Block"
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.qml:27
msgctxt "@title"
@@ -2212,7 +2466,7 @@ msgstr "Per assicurarsi stampe di alta qualità, è ora possibile regolare il pi
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.qml:47
msgctxt "@label"
msgid "For every position; insert a piece of paper under the nozzle and adjust the print build plate height. The print build plate height is right when the paper is slightly gripped by the tip of the nozzle."
-msgstr "Per ciascuna posizione: inserire un pezzo di carta sotto l'ugello e regolare la stampa dell'altezza del piano di stampa. L'altezza del piano di stampa è corretta quando la carta sfiora la punta dell'ugello."
+msgstr "Per ciascuna posizione: inserire un pezzo di carta sotto l'ugello e regolare l'altezza del piano di stampa. L'altezza del piano di stampa è corretta quando la carta sfiora la punta dell'ugello."
#: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.qml:62
msgctxt "@action:button"
@@ -2357,66 +2611,66 @@ msgctxt "@label"
msgid "Everything is in order! You're done with your CheckUp."
msgstr "È tutto in ordine! Controllo terminato."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:88
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:87
msgctxt "@label:MonitorStatus"
msgid "Not connected to a printer"
msgstr "Non collegato ad una stampante"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:90
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:89
msgctxt "@label:MonitorStatus"
msgid "Printer does not accept commands"
msgstr "La stampante non accetta comandi"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:96
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:95
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:194
msgctxt "@label:MonitorStatus"
msgid "In maintenance. Please check the printer"
msgstr "In manutenzione. Controllare la stampante"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:102
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:184
msgctxt "@label:MonitorStatus"
msgid "Printing..."
msgstr "Stampa in corso..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:105
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:186
msgctxt "@label:MonitorStatus"
msgid "Paused"
msgstr "In pausa"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:109
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:108
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:188
msgctxt "@label:MonitorStatus"
msgid "Preparing..."
msgstr "Preparazione in corso..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:111
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:110
msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "Rimuovere la stampa"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:237
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
msgctxt "@label:"
msgid "Resume"
msgstr "Riprendi"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:245
msgctxt "@label:"
msgid "Pause"
msgstr "Pausa"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:274
msgctxt "@label:"
msgid "Abort Print"
msgstr "Interrompi la stampa"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:280
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
msgctxt "@window:title"
msgid "Abort print"
msgstr "Interrompi la stampa"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:282
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:286
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
msgstr "Sei sicuro di voler interrompere la stampa?"
@@ -2449,19 +2703,19 @@ msgid "Customized"
msgstr "Valore personalizzato"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:593
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "Chiedi sempre"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:594
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:596
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "Elimina e non chiedere nuovamente"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:597
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "Mantieni e non chiedere nuovamente"
@@ -2496,72 +2750,72 @@ msgctxt "@label"
msgid "Brand"
msgstr "Marchio"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:88
msgctxt "@label"
msgid "Material Type"
msgstr "Tipo di materiale"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:97
msgctxt "@label"
msgid "Color"
msgstr "Colore"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
msgctxt "@label"
msgid "Properties"
msgstr "Proprietà"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:143
msgctxt "@label"
msgid "Density"
msgstr "Densità"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:156
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:158
msgctxt "@label"
msgid "Diameter"
msgstr "Diametro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:187
msgctxt "@label"
msgid "Filament Cost"
msgstr "Costo del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
msgctxt "@label"
msgid "Filament weight"
msgstr "Peso del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:220
msgctxt "@label"
msgid "Filament length"
msgstr "Lunghezza del filamento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:227
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:229
msgctxt "@label"
msgid "Cost per Meter"
msgstr "Costo al metro"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:243
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "Questo materiale è collegato a %1 e condivide alcune delle sue proprietà."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:250
msgctxt "@label"
msgid "Unlink Material"
msgstr "Scollega materiale"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:259
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:261
msgctxt "@label"
msgid "Description"
msgstr "Descrizione"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:272
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:274
msgctxt "@label"
msgid "Adhesion Information"
msgstr "Informazioni sull’aderenza"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:300
msgctxt "@label"
msgid "Print settings"
msgstr "Impostazioni di stampa"
@@ -2602,7 +2856,7 @@ msgid "Unit"
msgstr "Unità"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:14
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:439
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:509
msgctxt "@title:tab"
msgid "General"
msgstr "Generale"
@@ -2617,230 +2871,255 @@ msgctxt "@label"
msgid "Language:"
msgstr "Lingua:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:207
msgctxt "@label"
msgid "Currency:"
msgstr "Valuta:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:219
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Theme:"
msgstr "Tema:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:279
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:281
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "Riavviare l'applicazione per rendere effettive le modifiche."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:296
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:298
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "Seziona automaticamente alla modifica delle impostazioni."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:306
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "Seziona automaticamente"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:318
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:320
msgctxt "@label"
msgid "Viewport behavior"
msgstr "Comportamento del riquadro di visualizzazione"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:326
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:328
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "Evidenzia in rosso le zone non supportate del modello. In assenza di supporto, queste aree non saranno stampate in modo corretto."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:337
msgctxt "@option:check"
msgid "Display overhang"
msgstr "Visualizza sbalzo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:342
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:344
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
-msgstr "Sposta la fotocamera in modo che il modello si trovi al centro della visualizzazione quando è selezionato"
+msgstr "Sposta la camera in modo che il modello si trovi al centro della visualizzazione quando è selezionato"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:347
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:349
msgctxt "@action:button"
msgid "Center camera when item is selected"
-msgstr "Centratura fotocamera alla selezione dell'elemento"
+msgstr "Centratura camera alla selezione dell'elemento"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:356
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:358
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "Il comportamento dello zoom predefinito di Cura dovrebbe essere invertito?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:363
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
-msgstr "Inverti la direzione dello zoom della fotocamera."
+msgstr "Inverti la direzione dello zoom della camera."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:370
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:372
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "Lo zoom si muove nella direzione del mouse?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:377
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "Zoom verso la direzione del mouse"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "I modelli sull’area di stampa devono essere spostati per evitare intersezioni?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "Assicurarsi che i modelli siano mantenuti separati"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:397
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "I modelli sull’area di stampa devono essere portati a contatto del piano di stampa?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:402
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "Rilascia automaticamente i modelli sul piano di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:414
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:416
msgctxt "@info:tooltip"
msgid "Show caution message in gcode reader."
-msgstr "Visualizza il messaggio di avvertimento sul lettore codice G."
+msgstr "Visualizza il messaggio di avvertimento sul lettore gcode."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:423
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:425
msgctxt "@option:check"
msgid "Caution message in gcode reader"
-msgstr "Messaggio di avvertimento sul lettore codice G"
+msgstr "Messaggio di avvertimento sul lettore gcode"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
-msgstr "Lo strato deve essere forzato in modalità di compatibilità?"
+msgstr "Il layer deve essere forzato in modalità di compatibilità?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:435
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:437
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
-msgstr "Forzare la modalità di compatibilità visualizzazione strato (riavvio necessario)"
+msgstr "Forzare la modalità di compatibilità visualizzazione layer (riavvio necessario)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:451
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:453
msgctxt "@label"
msgid "Opening and saving files"
msgstr "Apertura e salvataggio file"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:457
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:459
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "I modelli devono essere ridimensionati al volume di stampa, se troppo grandi?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:462
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:464
msgctxt "@option:check"
msgid "Scale large models"
msgstr "Ridimensiona i modelli troppo grandi"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:471
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:473
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "Un modello può apparire eccessivamente piccolo se la sua unità di misura è espressa in metri anziché in millimetri. Questi modelli devono essere aumentati?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:476
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:478
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "Ridimensiona i modelli eccessivamente piccoli"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:485
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:487
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "Al nome del processo di stampa deve essere aggiunto automaticamente un prefisso basato sul nome della stampante?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "Aggiungi al nome del processo un prefisso macchina"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:501
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Quando si salva un file di progetto deve essere visualizzato un riepilogo?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "Visualizza una finestra di riepilogo quando si salva un progetto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:514
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "Comportamento predefinito all'apertura di un file progetto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "Comportamento predefinito all'apertura di un file progetto: "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:533
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@option:openProject"
msgid "Always ask"
msgstr "Chiedi sempre"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:536
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "Apri sempre come progetto"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "Importa sempre i modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "Dopo aver modificato un profilo ed essere passati a un altro, si apre una finestra di dialogo che chiede se mantenere o eliminare le modifiche oppure se scegliere un comportamento predefinito e non visualizzare più tale finestra di dialogo."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:582
msgctxt "@label"
msgid "Override Profile"
msgstr "Override profilo"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:629
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:631
msgctxt "@label"
msgid "Privacy"
msgstr "Privacy"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Cura deve verificare la presenza di eventuali aggiornamenti all’avvio del programma?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:641
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:643
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "Controlla aggiornamenti all’avvio"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:651
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:653
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "I dati anonimi sulla stampa devono essere inviati a Ultimaker? Nota, non sono trasmessi o memorizzati modelli, indirizzi IP o altre informazioni personali."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:656
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:658
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "Invia informazioni di stampa (anonime)"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:674
+msgctxt "@label"
+msgid "Experimental"
+msgstr "Sperimentale"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:680
+msgctxt "@info:tooltip"
+msgid "Use multi build plate functionality"
+msgstr "Utilizzare la funzionalità piano di stampa multiplo"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:685
+msgctxt "@option:check"
+msgid "Use multi build plate functionality (restart required)"
+msgstr "Utilizzare la funzionalità piano di stampa multiplo (necessario riavvio)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+msgctxt "@info:tooltip"
+msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+msgstr "I modelli appena caricati devono essere sistemati sul piano di stampa? Utilizzato in abbinamento al piano di stampa multiplo (SPERIMENTALE)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+msgctxt "@option:check"
+msgid "Do not arrange objects on load"
+msgstr "Non posizionare oggetti dopo il caricamento"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:514
msgctxt "@title:tab"
msgid "Printers"
msgstr "Stampanti"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:37
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:51
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:138
msgctxt "@action:button"
msgid "Activate"
msgstr "Attiva"
@@ -2875,7 +3154,7 @@ msgstr "Stato:"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:190
msgctxt "@label:MonitorStatus"
msgid "Waiting for someone to clear the build plate"
-msgstr "In attesa di qualcuno che cancelli il piano di stampa"
+msgstr "In attesa che qualcuno liberi il piano di stampa"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:199
msgctxt "@label:MonitorStatus"
@@ -2883,7 +3162,7 @@ msgid "Waiting for a printjob"
msgstr "In attesa di un processo di stampa"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:448
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:518
msgctxt "@title:tab"
msgid "Profiles"
msgstr "Profili"
@@ -2909,13 +3188,13 @@ msgid "Duplicate"
msgstr "Duplica"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:113
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:182
msgctxt "@action:button"
msgid "Import"
msgstr "Importa"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:119
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:212
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:193
msgctxt "@action:button"
msgid "Export"
msgstr "Esporta"
@@ -2981,7 +3260,7 @@ msgid "Export Profile"
msgstr "Esporta profilo"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:516
msgctxt "@title:tab"
msgid "Materials"
msgstr "Materiali"
@@ -2996,60 +3275,60 @@ msgctxt "@action:label %1 is printer name"
msgid "Printer: %1"
msgstr "Stampante: %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:149
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:150
msgctxt "@action:button"
msgid "Create"
msgstr "Crea"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:160
msgctxt "@action:button"
msgid "Duplicate"
msgstr "Duplica"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:306
msgctxt "@title:window"
msgid "Import Material"
msgstr "Importa materiale"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:320
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:307
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Could not import material %1: %2"
msgstr "Impossibile importare materiale {1}: %2"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully imported material %1"
msgstr "Materiale importato correttamente %1"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:343
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:329
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:344
msgctxt "@title:window"
msgid "Export Material"
msgstr "Esporta materiale"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:348
msgctxt "@info:status Don't translate the XML tags and !"
msgid "Failed to export material to %1: %2"
msgstr "Impossibile esportare il materiale su %1: %2"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:368
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:354
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully exported material to %1"
msgstr "Materiale esportato correttamente su %1"
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:793
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:869
msgctxt "@title:window"
msgid "Add Printer"
msgstr "Aggiungi stampante"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
msgctxt "@label"
msgid "Printer Name:"
msgstr "Nome stampante:"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
msgctxt "@action:button"
msgid "Add Printer"
msgstr "Aggiungi stampante"
@@ -3176,12 +3455,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr "Profilo:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:66
-msgctxt "@"
-msgid "No Profile Available"
-msgstr "Nessun profilo disponibile"
-
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:102
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
@@ -3189,37 +3463,37 @@ msgid ""
"Click to open the profile manager."
msgstr "Alcuni valori di impostazione/esclusione sono diversi dai valori memorizzati nel profilo.\n\nFare clic per aprire la gestione profili."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:150
msgctxt "@label:textbox"
msgid "Search..."
msgstr "Ricerca..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:482
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "Copia valore su tutti gli estrusori"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:497
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "Nascondi questa impostazione"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:508
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:507
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "Nascondi questa impostazione"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:511
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "Mantieni visibile questa impostazione"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:530
msgctxt "@action:menu"
msgid "Configure setting visiblity..."
msgstr "Configurazione visibilità delle impostazioni in corso..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:250
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
@@ -3227,27 +3501,27 @@ msgid ""
"Click to make these settings visible."
msgstr "Alcune impostazioni nascoste utilizzano valori diversi dal proprio valore normale calcolato.\n\nFare clic per rendere visibili queste impostazioni."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:62
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
msgctxt "@label Header for list of settings."
msgid "Affects"
msgstr "Influisce su"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:67
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:66
msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "Influenzato da"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:156
msgctxt "@label"
-msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
-msgstr "Questa impostazione è sempre condivisa tra tutti gli estrusori. La sua modifica varierà il valore per tutti gli estrusori"
+msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
+msgstr "Questa impostazione è sempre condivisa tra tutti gli estrusori. La sua modifica varierà il valore per tutti gli estrusori."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:160
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:159
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "Questo valore è risolto da valori per estrusore "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:186
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:190
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3255,7 +3529,7 @@ msgid ""
"Click to restore the value of the profile."
msgstr "Questa impostazione ha un valore diverso dal profilo.\n\nFare clic per ripristinare il valore del profilo."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:288
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
@@ -3263,71 +3537,71 @@ msgid ""
"Click to restore the calculated value."
msgstr "Questa impostazione normalmente viene calcolata, ma attualmente ha impostato un valore assoluto.\n\nFare clic per ripristinare il valore calcolato."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid "Print Setup"
msgstr "Impostazione di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid ""
"Print Setup disabled\n"
"G-code files cannot be modified"
-msgstr "Impostazione di stampa disabilitata\nI file codice G non possono essere modificati"
+msgstr "Impostazione di stampa disabilitata\nI file G-code non possono essere modificati"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:336
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:342
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00h 00min"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:354
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:359
msgctxt "@tooltip"
-msgid "Time specification
"
-msgstr "Indicazione del tempo
"
+msgid "Time specification"
+msgstr "Indicazioni di tempo"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:429
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:441
msgctxt "@label"
msgid "Cost specification"
msgstr "Indicazione di costo"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:434
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:455
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1m"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:435
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:447
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:456
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:454
msgctxt "@label"
msgid "Total:"
msgstr "Totale:"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:504
msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
msgid "%1m / ~ %2g / ~ %4 %3"
msgstr "%1m / ~ %2g / ~ %4 %3"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:509
msgctxt "@label Print estimates: m for meters, g for grams"
msgid "%1m / ~ %2g"
msgstr "%1m / ~ %2g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "Impostazione di stampa consigliata
Stampa con le impostazioni consigliate per la stampante, il materiale e la qualità selezionati."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:596
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
-msgstr "Impostazione di stampa personalizzata
Stampa con il controllo grana fine su ogni sezione finale del processo di sezionamento."
+msgstr "Impostazione di stampa personalizzata
Stampa con il controllo grana fine su ogni sezione finale del processo di slicing."
-#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:49
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:50
msgctxt "@title:menuitem %1 is the automatically selected material"
msgid "Automatic: %1"
msgstr "Automatico: %1"
@@ -3337,6 +3611,16 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "&Visualizza"
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:37
+msgctxt "@action:inmenu menubar:view"
+msgid "&Camera position"
+msgstr "&Posizione fotocamera"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:52
+msgctxt "@action:inmenu menubar:view"
+msgid "&Build plate"
+msgstr "&Piano di stampa"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:40
msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer"
msgid "Automatic: %1"
@@ -3349,14 +3633,14 @@ msgid_plural "Print Selected Models With:"
msgstr[0] "Stampa modello selezionato con:"
msgstr[1] "Stampa modelli selezionati con:"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:83
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:114
msgctxt "@title:window"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Moltiplica modello selezionato"
msgstr[1] "Moltiplica modelli selezionati"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:108
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:139
msgctxt "@label"
msgid "Number of Copies"
msgstr "Numero di copie"
@@ -3372,7 +3656,7 @@ msgid "No printer connected"
msgstr "Nessuna stampante collegata"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:90
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:139
msgctxt "@label"
msgid "Extruder"
msgstr "Estrusore"
@@ -3420,7 +3704,7 @@ msgstr "La temperatura corrente del piano riscaldato."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:423
msgctxt "@tooltip of temperature input"
msgid "The temperature to pre-heat the bed to."
-msgstr "La temperatura di preriscaldo del piano."
+msgstr "La temperatura di preriscaldamento del piano."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:623
msgctxt "@button Cancel pre-heating"
@@ -3430,7 +3714,7 @@ msgstr "Annulla"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:623
msgctxt "@button"
msgid "Pre-heat"
-msgstr "Pre-riscaldo"
+msgstr "Pre-riscaldamento"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:650
msgctxt "@tooltip of pre-heat"
@@ -3482,254 +3766,294 @@ msgctxt "@label"
msgid "Estimated time left"
msgstr "Tempo residuo stimato"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
msgid "Toggle Fu&ll Screen"
msgstr "Att&iva/disattiva schermo intero"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "&Annulla"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:89
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "Ri&peti"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:99
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "E&sci"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:107
-msgctxt "@action:inmenu menubar:view"
-msgid "&Reset camera position"
-msgstr "&Ripristina la posizione della telecamera"
-
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
+msgctxt "@action:inmenu menubar:view"
+msgid "&3D View"
+msgstr "&Visualizzazione 3D"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+msgctxt "@action:inmenu menubar:view"
+msgid "&Front View"
+msgstr "&Visualizzazione frontale"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
+msgctxt "@action:inmenu menubar:view"
+msgid "&Top View"
+msgstr "&Visualizzazione superiore"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
+msgctxt "@action:inmenu menubar:view"
+msgid "&Left Side View"
+msgstr "&Visualizzazione lato sinistro"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+msgctxt "@action:inmenu menubar:view"
+msgid "&Right Side View"
+msgstr "&Visualizzazione lato destro"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Configura Cura..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "A&ggiungi stampante..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "&Gestione stampanti..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "Gestione materiali..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Aggiorna il profilo con le impostazioni/esclusioni correnti"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:150
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "&Elimina le modifiche correnti"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "&Crea profilo dalle impostazioni/esclusioni correnti..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "Gestione profili..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "Mostra documentazione &online"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "Se&gnala un errore"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:191
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
msgid "&About..."
msgstr "I&nformazioni..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
msgid "Delete &Selected Model"
msgid_plural "Delete &Selected Models"
msgstr[0] "Cancella &modello selezionato"
msgstr[1] "Cancella modelli &selezionati"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "Centra modello selezionato"
msgstr[1] "Centra modelli selezionati"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "Moltiplica modello selezionato"
msgstr[1] "Moltiplica modelli selezionati"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "Elimina modello"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "C&entra modello su piattaforma"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "&Raggruppa modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:250
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "Separa modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:260
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "&Unisci modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "Mo<iplica modello"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
msgid "&Select All Models"
msgstr "Sel&eziona tutti i modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:287
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
msgid "&Clear Build Plate"
-msgstr "&Cancellare piano di stampa"
+msgstr "&Pulire piano di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:297
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
msgctxt "@action:inmenu menubar:file"
msgid "Re&load All Models"
msgstr "R&icarica tutti i modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:306
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
+msgctxt "@action:inmenu menubar:edit"
+msgid "Arrange All Models To All Build Plates"
+msgstr "Sistema tutti i modelli su tutti i piani di stampa"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "Sistema tutti i modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "Sistema selezione"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:321
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "Reimposta tutte le posizioni dei modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:328
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model &Transformations"
msgstr "Reimposta tutte le &trasformazioni dei modelli"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "&Apri file..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:343
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "&Nuovo Progetto..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
-msgstr "M&ostra log motore..."
+msgstr "M&ostra motore log..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "Mostra cartella di configurazione"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:365
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "Configura visibilità delle impostazioni..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse plugins..."
msgstr "Sfoglia plugin..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:379
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:menu"
msgid "Installed plugins..."
msgstr "Plugin installati..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:28
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:438
+msgctxt "@action:inmenu menubar:view"
+msgid "Expand/Collapse Sidebar"
+msgstr "Espandi/Riduci barra laterale"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "Caricare un modello 3D"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:34
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "Pronto per il sezionamento"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
-msgstr "Sezionamento in corso..."
+msgstr "Slicing in corso..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "Pronto a %1"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
-msgstr "Sezionamento impossibile"
+msgstr "Slicing impossibile"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "Sezionamento non disponibile"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Slice current printjob"
+msgstr "Seziona processo di stampa corrente"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Cancel slicing process"
+msgstr "Annulla processo di slicing"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "Prepara"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "Annulla"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:302
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
msgctxt "@info:tooltip"
msgid "Select the active output device"
-msgstr "Seleziona l'unità di uscita attiva"
+msgstr "Seleziona l'unità output attiva"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:696
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "Apri file"
@@ -3749,114 +4073,114 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:81
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "&File"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:98
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
msgctxt "@action:inmenu menubar:file"
msgid "&Save Selection to File"
msgstr "&Salva selezione su file"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
msgctxt "@title:menu menubar:file"
msgid "Save &As..."
msgstr "Salva &come..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
msgctxt "@title:menu menubar:file"
-msgid "Save project"
-msgstr "Salva progetto"
+msgid "Save &Project..."
+msgstr "Salva &progetto..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "&Modifica"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:158
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
msgctxt "@title:menu"
msgid "&View"
msgstr "&Visualizza"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
msgctxt "@title:menu"
msgid "&Settings"
msgstr "&Impostazioni"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
msgctxt "@title:menu menubar:toplevel"
msgid "&Printer"
msgstr "S&tampante"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:175
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:187
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:208
msgctxt "@title:menu"
msgid "&Material"
msgstr "Ma&teriale"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:176
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:197
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:209
msgctxt "@title:menu"
msgid "&Profile"
msgstr "&Profilo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:180
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:201
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "Imposta come estrusore attivo"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:219
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "Es&tensioni"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:232
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:253
msgctxt "@title:menu menubar:toplevel"
msgid "P&lugins"
msgstr "&Plugin"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:261
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "P&referenze"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:269
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "&Help"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:330
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:351
msgctxt "@action:button"
msgid "Open File"
msgstr "Apri file"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:442
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:512
msgctxt "@title:tab"
msgid "Settings"
msgstr "Impostazioni"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:478
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:554
msgctxt "@title:window"
msgid "New project"
msgstr "Nuovo progetto"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:479
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:555
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "Sei sicuro di voler aprire un nuovo progetto? Questo cancellerà il piano di stampa e tutte le impostazioni non salvate."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:721
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:797
msgctxt "@window:title"
msgid "Install Plugin"
msgstr "Installa plugin"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:728
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:804
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "Apri file"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:731
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:807
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "Rilevata la presenza di uno o più file codice G tra i file selezionati. È possibile aprire solo un file codice G alla volta. Se desideri aprire un file codice G, selezionane uno solo. "
@@ -3881,97 +4205,82 @@ msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "Non mostrare il riepilogo di progetto alla ripetizione di salva"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
-msgctxt "@action:button"
-msgid "Save"
-msgstr "Salva"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:74
-msgctxt "@title:tab"
-msgid "Prepare"
-msgstr "Prepara"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:100
-msgctxt "@title:tab"
-msgid "Monitor"
-msgstr "Controlla"
-
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
msgctxt "@label"
msgid "Layer Height"
msgstr "Altezza dello strato"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:323
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:345
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr "Un profilo personalizzato è attualmente attivo. Per attivare il cursore qualità, selezionare un profilo di qualità predefinito nella scheda Personalizza"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:362
msgctxt "@label"
msgid "Print Speed"
msgstr "Velocità di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:374
msgctxt "@label"
msgid "Slower"
msgstr "Più lenta"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:385
msgctxt "@label"
msgid "Faster"
msgstr "Più veloce"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:388
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:423
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "Sono state modificate alcune impostazioni del profilo. Per modificarle, andare alla modalità personalizzata."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:413
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:446
msgctxt "@label"
msgid "Infill"
msgstr "Riempimento"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:633
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:668
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "Un riempimento graduale aumenterà gradualmente la quantità di riempimento verso l'alto."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:645
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:680
msgctxt "@label"
msgid "Enable gradual"
msgstr "Consenti variazione graduale"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:712
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:747
msgctxt "@label"
msgid "Generate Support"
msgstr "Generazione supporto"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:746
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:781
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "Genera strutture per supportare le parti del modello a sbalzo. Senza queste strutture, queste parti collasserebbero durante la stampa."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:764
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:799
msgctxt "@label"
msgid "Support Extruder"
msgstr "Estrusore del supporto"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:816
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:851
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "Seleziona l’estrusore da utilizzare per la stampa di strutture di supporto. Ciò consentirà di costruire strutture di supporto sotto il modello per evitare cedimenti del modello o di stampare a mezz'aria."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:839
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:874
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "Adesione piano di stampa"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:894
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:929
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "Abilita stampa di brim o raft. Questa funzione aggiunge un’area piana attorno o sotto l’oggetto, facile da tagliare successivamente."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:934
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:969
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "Serve aiuto per migliorare le tue stampe? Leggi la Guida alla ricerca e riparazione guasti Ultimaker"
@@ -3988,17 +4297,22 @@ msgctxt "@title:window"
msgid "Open project file"
msgstr "Apri file progetto"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:93
msgctxt "@text:window"
msgid "This is a Cura project file. Would you like to open it as a project or import the models from it?"
msgstr "Questo è un file progetto Cura. Vuoi aprirlo come progetto o importarne i modelli?"
#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:103
+msgctxt "@text:window"
+msgid "Remember my choice"
+msgstr "Ricorda la scelta"
+
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
msgctxt "@action:button"
msgid "Open as project"
msgstr "Apri come progetto"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:131
msgctxt "@action:button"
msgid "Import models"
msgstr "Importa i modelli"
@@ -4006,23 +4320,38 @@ msgstr "Importa i modelli"
#: /home/ruben/Projects/Cura/resources/qml/EngineLog.qml:15
msgctxt "@title:window"
msgid "Engine Log"
-msgstr "Log motore"
+msgstr "Motore Log"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:245
msgctxt "@label"
msgid "Material"
msgstr "Materiale"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:349
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:352
msgctxt "@label"
-msgid "Check compatibility"
-msgstr "Controllo compatibilità"
+msgid "Check compatibility"
+msgstr "Controlla compatibilità"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:369
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Fai clic per verificare la compatibilità del materiale su Ultimaker.com."
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:211
+msgctxt "@option:check"
+msgid "See only current build plate"
+msgstr "Vedi solo il piano di stampa corrente"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:227
+msgctxt "@action:button"
+msgid "Arrange to all build plates"
+msgstr "Sistema su tutti i piani di stampa"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:247
+msgctxt "@action:button"
+msgid "Arrange current build plate"
+msgstr "Sistema il piano di stampa corrente"
+
#: MachineSettingsAction/plugin.json
msgctxt "description"
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc)"
@@ -4113,6 +4442,26 @@ msgctxt "name"
msgid "USB printing"
msgstr "Stampa USB"
+#: PrepareStage/plugin.json
+msgctxt "description"
+msgid "Provides a prepare stage in Cura."
+msgstr "Fornisce una fase di preparazione in Cura."
+
+#: PrepareStage/plugin.json
+msgctxt "name"
+msgid "Prepare Stage"
+msgstr "Fase di preparazione"
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "description"
+msgid "Provides an edit window for direct script editing."
+msgstr "Fornisce una finestra di modifica per la modifica script diretta."
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "name"
+msgid "Live scripting tool"
+msgstr "Strumento di script diretto"
+
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4133,6 +4482,16 @@ msgctxt "name"
msgid "UM3 Network Connection"
msgstr "Connessione di rete UM3"
+#: MonitorStage/plugin.json
+msgctxt "description"
+msgid "Provides a monitor stage in Cura."
+msgstr "Fornisce una fase di controllo in Cura."
+
+#: MonitorStage/plugin.json
+msgctxt "name"
+msgid "Monitor Stage"
+msgstr "Fase di controllo"
+
#: FirmwareUpdateChecker/plugin.json
msgctxt "description"
msgid "Checks for firmware updates."
@@ -4145,8 +4504,8 @@ msgstr "Controllo aggiornamento firmware"
#: CuraSolidWorksPlugin/plugin.json
msgctxt "description"
-msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
-msgstr "Offre la possibilità di aprire alcuni file tramite SolidWorks stessa. Questi vengono quindi convertiti e caricati in Cura"
+msgid "Gives you the possibility to open certain files using SolidWorks itself. Conversion is done by this plugin and additional optimizations."
+msgstr "Offre la possibilità di aprire alcuni file utilizzando SolidWorks. La conversione viene effettuata da questo plugin e ottimizzazioni addizionali."
#: CuraSolidWorksPlugin/plugin.json
msgctxt "name"
@@ -4213,6 +4572,16 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "Lettore legacy profilo Cura"
+#: CuraBlenderPlugin/plugin.json
+msgctxt "description"
+msgid "Helps to open Blender files directly in Cura."
+msgstr "Aiuta ad aprire i file Blender direttamente in Cura."
+
+#: CuraBlenderPlugin/plugin.json
+msgctxt "name"
+msgid "Blender Integration (experimental)"
+msgstr "Integrazione Blender (sperimentale)"
+
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4296,7 +4665,7 @@ msgstr "Lettore di immagine"
#: CuraEngineBackend/plugin.json
msgctxt "description"
msgid "Provides the link to the CuraEngine slicing backend."
-msgstr "Fornisce il collegamento al back-end di sezionamento CuraEngine."
+msgstr "Fornisce il collegamento al back-end di slicing di CuraEngine."
#: CuraEngineBackend/plugin.json
msgctxt "name"
@@ -4356,12 +4725,12 @@ msgstr "Visualizzazione compatta"
#: GCodeReader/plugin.json
msgctxt "description"
msgid "Allows loading and displaying G-code files."
-msgstr "Consente il caricamento e la visualizzazione dei file codice G."
+msgstr "Consente il caricamento e la visualizzazione dei file G-code."
#: GCodeReader/plugin.json
msgctxt "name"
msgid "G-code Reader"
-msgstr "Lettore codice G"
+msgstr "Lettore G-code"
#: CuraProfileWriter/plugin.json
msgctxt "description"
@@ -4373,6 +4742,16 @@ msgctxt "name"
msgid "Cura Profile Writer"
msgstr "Writer profilo Cura"
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "description"
+msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+msgstr "Consente ai produttori di materiali di creare nuovi profili materiale e di qualità utilizzando una UI drop-in."
+
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "name"
+msgid "Print Profile Assistant"
+msgstr "Assistente profilo di stampa"
+
#: 3MFWriter/plugin.json
msgctxt "description"
msgid "Provides support for writing 3MF files."
@@ -4413,9 +4792,159 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Lettore profilo Cura"
+#~ msgctxt "@label"
+#~ msgid "Unknown"
+#~ msgstr "Sconosciuto"
+
+#~ msgctxt "@info:status"
+#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
+#~ msgstr "Rilevati errori all'apertura del file SolidWorks! Controllare se è possibile aprire il file in SolidWorks senza che si verifichino problemi!"
+
+#~ msgctxt "@info:status"
+#~ msgid "Error while starting %s!"
+#~ msgstr "Errore durante l'avvio di %s!"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Simulation view"
+#~ msgstr "Vista simulazione"
+
+#~ msgctxt "@info"
+#~ msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
+#~ msgstr "Cura acquisisce dati statistici elaborati in forma anonima. L'acquisizione può essere disabilitata nelle preferenze."
+
+#~ msgctxt "@action:button"
+#~ msgid "Dismiss"
+#~ msgstr "Ignora"
+
+#~ msgctxt "@menuitem"
+#~ msgid "Global"
+#~ msgstr "Globale"
+
+#~ msgctxt "@label crash message"
+#~ msgid ""
+#~ "
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+#~ "
Please use the \"Send report\" button to post a bug report automatically to our servers
\n"
+#~ " "
+#~ msgstr ""
+#~ "
Si è verificata un'eccezione irreversibile. Si prega di inviarci questo crash report per risolvere il problema
\n"
+#~ "
Utilizzare il pulsante \"Invia report\" per inviare un report sui bug automaticamente ai nostri server
\n"
+#~ " "
+
+#~ msgctxt "@label Cura version"
+#~ msgid "Cura version: {version} "
+#~ msgstr "Versione Cura: {version} "
+
+#~ msgctxt "@label Platform"
+#~ msgid "Platform: {platform} "
+#~ msgstr "Piattaforma: {platform} "
+
+#~ msgctxt "@label Qt version"
+#~ msgid "Qt version: {qt} "
+#~ msgstr "Versione Qt: {qt} "
+
+#~ msgctxt "@label PyQt version"
+#~ msgid "PyQt version: {pyqt} "
+#~ msgstr "Versione PyQt: {pyqt} "
+
+#~ msgctxt "@label OpenGL"
+#~ msgid "OpenGL: {opengl} "
+#~ msgstr "OpenGL: {opengl} "
+
+#~ msgctxt "@title:groupbox"
+#~ msgid "Exception traceback"
+#~ msgstr "Analisi eccezione"
+
+#~ msgctxt "@label"
+#~ msgid "Material diameter"
+#~ msgstr "Diametro materiale"
+
+#~ msgctxt "@label"
+#~ msgid "Ultimaker 3"
+#~ msgstr "Ultimaker 3"
+
+#~ msgctxt "@label"
+#~ msgid "Ultimaker 3 Extended"
+#~ msgstr "Ultimaker3 Extended"
+
+#~ msgctxt "@title:window"
+#~ msgid "Cura SolidWorks Plugin Configuration"
+#~ msgstr "Configurazione plugin Cura SolidWorks"
+
+#~ msgctxt "@action:label"
+#~ msgid "Default quality of the exported STL:"
+#~ msgstr "Qualità predefinita STL esportato:"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always ask"
+#~ msgstr "Chiedi sempre"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always use Fine quality"
+#~ msgstr "Utilizza sempre la qualità Fine"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Always use Coarse quality"
+#~ msgstr "Utilizza sempre la qualità Grossolana"
+
+#~ msgctxt "@title:window"
+#~ msgid "Import SolidWorks File as STL..."
+#~ msgstr "Importa file SolidWorks come STL..."
+
+#~ msgctxt "@info:tooltip"
+#~ msgid "Quality of the Exported STL"
+#~ msgstr "Qualità STL esportato"
+
+#~ msgctxt "@action:label"
+#~ msgid "Quality"
+#~ msgstr "Qualità"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Coarse"
+#~ msgstr "Grossolana"
+
+#~ msgctxt "@option:curaSolidworksStlQuality"
+#~ msgid "Fine"
+#~ msgstr "Fine"
+
+#~ msgctxt "@"
+#~ msgid "No Profile Available"
+#~ msgstr "Nessun profilo disponibile"
+
+#~ msgctxt "@label"
+#~ msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
+#~ msgstr "Questa impostazione è sempre condivisa tra tutti gli estrusori. La sua modifica varierà il valore per tutti gli estrusori"
+
+#~ msgctxt "@tooltip"
+#~ msgid "Time specification
"
+#~ msgstr "Indicazione del tempo
"
+
+#~ msgctxt "@action:inmenu menubar:view"
+#~ msgid "&Reset camera position"
+#~ msgstr "&Ripristina la posizione della telecamera"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save project"
+#~ msgstr "Salva progetto"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Prepare"
+#~ msgstr "Prepara"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Monitor"
+#~ msgstr "Controlla"
+
+#~ msgctxt "@label"
+#~ msgid "Check compatibility"
+#~ msgstr "Controllo compatibilità"
+
+#~ msgctxt "description"
+#~ msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
+#~ msgstr "Offre la possibilità di aprire alcuni file tramite SolidWorks stessa. Questi vengono quindi convertiti e caricati in Cura"
+
#~ msgctxt "@label:status"
#~ msgid "Blocked"
-#~ msgstr "Ostacolato"
+#~ msgstr "Bloccato"
#~ msgctxt "@label:status"
#~ msgid "Can't start print"
@@ -4433,13 +4962,9 @@ msgstr "Lettore profilo Cura"
#~ msgid "To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware regularly. This can be done on the {machine_name} (when connected to the network) or via USB."
#~ msgstr "Per verificare che la vostra {machine_name} sia dotata delle funzionalità più recenti, si consiglia di aggiornare periodicamente il firmware. Questo può essere fatto sulla {machine_name} (quando connessa alla rete) o via USB."
-msgctxt "@item:inlistbox"
-msgid "Layer view"
-msgstr "Visualizzazione strato"
-
-msgctxt "@info:title"
-msgid "Layer View"
-msgstr "Visualizzazione strato"
+#~ msgctxt "@info:title"
+#~ msgid "Layer View"
+#~ msgstr "Visualizzazione layer"
#~ msgctxt "@menuitem"
#~ msgid "Browse plugins"
@@ -4541,9 +5066,9 @@ msgstr "Visualizzazione strato"
#~ msgid "Provides the Layer view."
#~ msgstr "Fornisce la visualizzazione degli strati."
-msgctxt "name"
-msgid "Layer View"
-msgstr "Visualizzazione strato"
+#~ msgctxt "name"
+#~ msgid "Layer View"
+#~ msgstr "Visualizzazione layer"
#~ msgctxt "@item:inlistbox"
#~ msgid "X-Ray"
@@ -4674,7 +5199,7 @@ msgstr "Visualizzazione strato"
#~ msgctxt "@label"
#~ msgid "Hotend"
-#~ msgstr "Estremità calda"
+#~ msgstr "Hotend"
#~ msgctxt "@action:button"
#~ msgid "View Mode"
@@ -4810,7 +5335,7 @@ msgstr "Visualizzazione strato"
#~ msgctxt "@label"
#~ msgid "The print cores and/or materials on your printer differ from those within your current project. For the best result, always slice for the print cores and materials that are inserted in your printer."
-#~ msgstr "I PrintCore e/o i materiali della stampante sono diversi da quelli del progetto corrente. Per ottenere i migliori risultati, sezionare sempre per i PrintCore e i materiali inseriti nella stampante utilizzata."
+#~ msgstr "I PrintCore e/o i materiali della stampante sono diversi da quelli del progetto corrente. Per risultati ottimali, sezionare sempre i PrintCore e i materiali inseriti nella stampante utilizzata."
#~ msgctxt "@label"
#~ msgid "Post Processing"
@@ -4864,13 +5389,13 @@ msgstr "Visualizzazione strato"
#~ msgid "Provides support for importing profiles from g-code files."
#~ msgstr "Fornisce supporto per l'importazione di profili da file G-Code."
-msgctxt "@label"
-msgid "Layer View"
-msgstr "Visualizzazione strato"
+#~ msgctxt "@label"
+#~ msgid "Layer View"
+#~ msgstr "Visualizzazione layer"
#~ msgctxt "@info:whatsthis"
#~ msgid "Provides the Layer view."
-#~ msgstr "Fornisce la visualizzazione degli strati."
+#~ msgstr "Fornisce la visualizzazione dei layer."
#~ msgctxt "@label"
#~ msgid "Version Upgrade 2.5 to 2.6"
@@ -4910,7 +5435,7 @@ msgstr "Visualizzazione strato"
#~ msgctxt "@info:whatsthis"
#~ msgid "Provides the link to the CuraEngine slicing backend."
-#~ msgstr "Fornisce il collegamento al back-end di sezionamento CuraEngine."
+#~ msgstr "Fornisce il collegamento al back-end di slicing di CuraEngine."
#~ msgctxt "@label"
#~ msgid "Per Model Settings Tool"
@@ -4938,11 +5463,11 @@ msgstr "Visualizzazione strato"
#~ msgctxt "@label"
#~ msgid "G-code Reader"
-#~ msgstr "Lettore codice G"
+#~ msgstr "Lettore G-code"
#~ msgctxt "@info:whatsthis"
#~ msgid "Allows loading and displaying G-code files."
-#~ msgstr "Consente il caricamento e la visualizzazione dei file codice G."
+#~ msgstr "Consente il caricamento e la visualizzazione dei file G-code."
#~ msgctxt "@label"
#~ msgid "Cura Profile Writer"
@@ -5211,7 +5736,7 @@ msgstr "Visualizzazione strato"
#~ msgctxt "@option:check"
#~ msgid "Only display top layer(s) in layer view"
-#~ msgstr "In visualizzazione strato, visualizza solo lo/gli strato/i superiore/i"
+#~ msgstr "In visualizzazione layer, visualizza solo il/i layer(s) superiore/i"
#~ msgctxt "@label"
#~ msgid "Opening files"
diff --git a/resources/i18n/it_IT/fdmextruder.def.json.po b/resources/i18n/it_IT/fdmextruder.def.json.po
index ec3c8051cf..f34ebd04ff 100644
--- a/resources/i18n/it_IT/fdmextruder.def.json.po
+++ b/resources/i18n/it_IT/fdmextruder.def.json.po
@@ -2,12 +2,12 @@
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek , 2017.
-#
+#
msgid ""
msgstr ""
"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: 2017-11-30 13:05+0100\n"
"Last-Translator: Bothof \n"
"Language-Team: Italian\n"
diff --git a/resources/i18n/it_IT/fdmprinter.def.json.po b/resources/i18n/it_IT/fdmprinter.def.json.po
index 5fb7743130..e5f440e2b2 100644
--- a/resources/i18n/it_IT/fdmprinter.def.json.po
+++ b/resources/i18n/it_IT/fdmprinter.def.json.po
@@ -1,15 +1,15 @@
-# Cura JSON setting files
-# Copyright (C) 2017 Ultimaker
+# Cura
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
"PO-Revision-Date: 2017-11-30 13:05+0100\n"
-"Last-Translator: Bothof \n"
+"Last-Translator: Crea-3D \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
@@ -49,26 +49,26 @@ msgstr "Sceglie se mostrare le diverse varianti di questa macchina, descritte in
#: fdmprinter.def.json
msgctxt "machine_start_gcode label"
msgid "Start GCode"
-msgstr "Codice G avvio"
+msgstr "Avvio GCode"
#: fdmprinter.def.json
msgctxt "machine_start_gcode description"
msgid ""
"Gcode commands to be executed at the very start - separated by \n"
"."
-msgstr "I comandi codice G da eseguire all’avvio, separati da \n."
+msgstr "I comandi del Gcode da eseguire all’avvio, separati da \n."
#: fdmprinter.def.json
msgctxt "machine_end_gcode label"
msgid "End GCode"
-msgstr "Codice G fine"
+msgstr "Fine GCode"
#: fdmprinter.def.json
msgctxt "machine_end_gcode description"
msgid ""
"Gcode commands to be executed at the very end - separated by \n"
"."
-msgstr "I comandi codice G da eseguire alla fine, separati da \n."
+msgstr "I comandi del Gcode da eseguire alla fine, separati da \n."
#: fdmprinter.def.json
msgctxt "material_guid label"
@@ -88,7 +88,7 @@ msgstr "Attendi il riscaldamento del piano di stampa"
#: fdmprinter.def.json
msgctxt "material_bed_temp_wait description"
msgid "Whether to insert a command to wait until the build plate temperature is reached at the start."
-msgstr "Sceglie se inserire un comando per attendere finché la temperatura del piano di stampa non viene raggiunta all’avvio."
+msgstr "Scegli se inserire un comando per attendere finché la temperatura del piano di stampa non viene raggiunta all’avvio."
#: fdmprinter.def.json
msgctxt "material_print_temp_wait label"
@@ -98,7 +98,7 @@ msgstr "Attendi il riscaldamento dell’ugello"
#: fdmprinter.def.json
msgctxt "material_print_temp_wait description"
msgid "Whether to wait until the nozzle temperature is reached at the start."
-msgstr "Sceglie se attendere finché la temperatura dell’ugello non viene raggiunta all’avvio."
+msgstr "Scegli se attendere finché la temperatura dell’ugello non viene raggiunta all’avvio."
#: fdmprinter.def.json
msgctxt "material_print_temp_prepend label"
@@ -108,7 +108,7 @@ msgstr "Includi le temperature del materiale"
#: fdmprinter.def.json
msgctxt "material_print_temp_prepend description"
msgid "Whether to include nozzle temperature commands at the start of the gcode. When the start_gcode already contains nozzle temperature commands Cura frontend will automatically disable this setting."
-msgstr "Sceglie se includere comandi temperatura ugello all’avvio del codice G. Quando start_gcode contiene già comandi temperatura ugello la parte anteriore di Cura disabilita automaticamente questa impostazione."
+msgstr "Scegli se includere comandi temperatura ugello all’avvio del Gcode. Quando start_gcode contiene già comandi temperatura ugello, il frontend di Cura disabilita automaticamente questa impostazione."
#: fdmprinter.def.json
msgctxt "material_bed_temp_prepend label"
@@ -118,7 +118,7 @@ msgstr "Includi temperatura piano di stampa"
#: fdmprinter.def.json
msgctxt "material_bed_temp_prepend description"
msgid "Whether to include build plate temperature commands at the start of the gcode. When the start_gcode already contains build plate temperature commands Cura frontend will automatically disable this setting."
-msgstr "Sceglie se includere comandi temperatura piano di stampa all’avvio del codice G. Quando start_gcode contiene già comandi temperatura piano di stampa la parte anteriore di Cura disabilita automaticamente questa impostazione."
+msgstr "Scegli se includere comandi temperatura piano di stampa all’avvio del gcode. Quando start_gcode contiene già comandi temperatura piano di stampa il frontend di Cura disabilita automaticamente questa impostazione."
#: fdmprinter.def.json
msgctxt "machine_width label"
@@ -183,7 +183,7 @@ msgstr "Indica se la macchina ha un piano di stampa riscaldato."
#: fdmprinter.def.json
msgctxt "machine_center_is_zero label"
msgid "Is Center Origin"
-msgstr "Origine del centro"
+msgstr "Origine al centro"
#: fdmprinter.def.json
msgctxt "machine_center_is_zero description"
@@ -198,7 +198,7 @@ msgstr "Numero di estrusori"
#: fdmprinter.def.json
msgctxt "machine_extruder_count description"
msgid "Number of extruder trains. An extruder train is the combination of a feeder, bowden tube, and nozzle."
-msgstr "Il numero di treni di estrusori. Un treno di estrusori è la combinazione di un alimentatore, un tubo bowden e un ugello."
+msgstr "Il numero dei blocchi di estrusori. Un blocco di estrusori è la combinazione di un alimentatore, un tubo bowden e un ugello."
#: fdmprinter.def.json
msgctxt "machine_nozzle_tip_outer_diameter label"
@@ -218,7 +218,7 @@ msgstr "Lunghezza ugello"
#: fdmprinter.def.json
msgctxt "machine_nozzle_head_distance description"
msgid "The height difference between the tip of the nozzle and the lowest part of the print head."
-msgstr "La differenza di altezza tra la punta dell’ugello e la parte inferiore della testina di stampa."
+msgstr "La differenza di altezza tra la punta dell’ugello e la parte inferiore della testa di stampa."
#: fdmprinter.def.json
msgctxt "machine_nozzle_expansion_angle label"
@@ -293,12 +293,12 @@ msgstr "Il tempo minimo in cui un estrusore deve essere inattivo prima che l’u
#: fdmprinter.def.json
msgctxt "machine_gcode_flavor label"
msgid "Gcode flavour"
-msgstr "Tipo di codice G"
+msgstr "Tipo di Gcode"
#: fdmprinter.def.json
msgctxt "machine_gcode_flavor description"
msgid "The type of gcode to be generated."
-msgstr "Il tipo di codice G da generare."
+msgstr "Il tipo di gcode da generare."
#: fdmprinter.def.json
msgctxt "machine_gcode_flavor option RepRap (Marlin/Sprinter)"
@@ -345,6 +345,16 @@ msgctxt "machine_gcode_flavor option Repetier"
msgid "Repetier"
msgstr "Repetier"
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract label"
+msgid "Firmware Retraction"
+msgstr "Retrazione firmware"
+
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract description"
+msgid "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material."
+msgstr "Specifica se usare comandi di retrazione firmware (G10/G11) anziché utilizzare la proprietà E nei comandi G1 per retrarre il materiale."
+
#: fdmprinter.def.json
msgctxt "machine_disallowed_areas label"
msgid "Disallowed areas"
@@ -353,7 +363,7 @@ msgstr "Aree non consentite"
#: fdmprinter.def.json
msgctxt "machine_disallowed_areas description"
msgid "A list of polygons with areas the print head is not allowed to enter."
-msgstr "Un elenco di poligoni con aree alle quali la testina di stampa non può accedere."
+msgstr "Un elenco di poligoni con aree alle quali la testa di stampa non può accedere."
#: fdmprinter.def.json
msgctxt "nozzle_disallowed_areas label"
@@ -368,22 +378,22 @@ msgstr "Un elenco di poligoni con aree alle quali l’ugello non può accedere."
#: fdmprinter.def.json
msgctxt "machine_head_polygon label"
msgid "Machine head polygon"
-msgstr "Poligono testina macchina"
+msgstr "Poligono testa macchina"
#: fdmprinter.def.json
msgctxt "machine_head_polygon description"
msgid "A 2D silhouette of the print head (fan caps excluded)."
-msgstr "Una silhouette 2D della testina di stampa (cappucci ventola esclusi)."
+msgstr "Una silhouette 2D della testa di stampa (coperture ventola escluse)."
#: fdmprinter.def.json
msgctxt "machine_head_with_fans_polygon label"
msgid "Machine head & Fan polygon"
-msgstr "Poligono testina macchina e ventola"
+msgstr "Poligono testa macchina e ventola"
#: fdmprinter.def.json
msgctxt "machine_head_with_fans_polygon description"
msgid "A 2D silhouette of the print head (fan caps included)."
-msgstr "Una silhouette 2D della testina di stampa (cappucci ventola inclusi)."
+msgstr "Una silhouette 2D della testa di stampa (coperture ventola incluse)."
#: fdmprinter.def.json
msgctxt "gantry_height label"
@@ -403,7 +413,7 @@ msgstr "ID ugello"
#: fdmprinter.def.json
msgctxt "machine_nozzle_id description"
msgid "The nozzle ID for an extruder train, such as \"AA 0.4\" and \"BB 0.8\"."
-msgstr "ID ugello per un treno estrusore, come \"AA 0.4\" e \"BB 0.8\"."
+msgstr "ID ugello per un blocco estrusore, come \"AA 0.4\" e \"BB 0.8\"."
#: fdmprinter.def.json
msgctxt "machine_nozzle_size label"
@@ -583,52 +593,27 @@ msgstr "Qualità"
#: fdmprinter.def.json
msgctxt "resolution description"
msgid "All settings that influence the resolution of the print. These settings have a large impact on the quality (and print time)"
-msgstr "Indica tutte le impostazioni che influiscono sulla risoluzione della stampa. Queste impostazioni hanno un elevato impatto sulla qualità (e il tempo di stampa)"
+msgstr "Indica tutte le impostazioni che influiscono sulla risoluzione della stampa. Queste impostazioni hanno un elevato impatto sulla qualità (e sul tempo di stampa)"
#: fdmprinter.def.json
msgctxt "layer_height label"
msgid "Layer Height"
-msgstr "Altezza dello strato"
+msgstr "Altezza del layer"
#: fdmprinter.def.json
msgctxt "layer_height description"
msgid "The height of each layer in mm. Higher values produce faster prints in lower resolution, lower values produce slower prints in higher resolution."
-msgstr "Indica l’altezza di ciascuno strato in mm. Valori più elevati generano stampe più rapide con risoluzione inferiore, valori più bassi generano stampe più lente con risoluzione superiore."
+msgstr "Indica l’altezza di ciascun layer in mm. Valori più elevati generano stampe più rapide con risoluzione inferiore, valori più bassi generano stampe più lente con risoluzione superiore."
#: fdmprinter.def.json
msgctxt "layer_height_0 label"
msgid "Initial Layer Height"
-msgstr "Altezza dello strato iniziale"
+msgstr "Altezza iniziale del layer"
#: fdmprinter.def.json
msgctxt "layer_height_0 description"
msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier."
-msgstr "Indica l’altezza dello strato iniziale in mm. Uno strato iniziale più spesso facilita l’adesione al piano di stampa."
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance label"
-msgid "Slicing Tolerance"
-msgstr "Tolleranza di sezionamento"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance description"
-msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
-msgstr "Modalità di sezionamento di strati con superfici diagonali. Le aree di uno strato possono essere generate in base al punto in cui la parte intermedia dello strato interseca la superficie (intermedia). In alternativa le aree di ciascuno strato possono ricadere all'interno del volume per tutta l'altezza dello strato (Esclusiva) ovvero possono cadere in qualsiasi punto all'interno dello strato (Inclusiva). La tolleranza esclusiva mantiene il maggior numero di dettagli, la tolleranza inclusiva è la più idonea, mentre la tolleranza intermedia richiede il minor tempo di processo."
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option middle"
-msgid "Middle"
-msgstr "Intermedia"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option exclusive"
-msgid "Exclusive"
-msgstr "Esclusiva"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option inclusive"
-msgid "Inclusive"
-msgstr "Inclusiva"
+msgstr "Indica l’altezza del layer iniziale in mm. Un layer iniziale più spesso facilita l’adesione al piano di stampa."
#: fdmprinter.def.json
msgctxt "line_width label"
@@ -670,16 +655,6 @@ msgctxt "wall_line_width_x description"
msgid "Width of a single wall line for all wall lines except the outermost one."
msgstr "Indica la larghezza di una singola linea della parete per tutte le linee della parete tranne quella più esterna."
-#: fdmprinter.def.json
-msgctxt "roofing_line_width label"
-msgid "Top Surface Skin Line Width"
-msgstr "Larghezza linea rivestimento superficie superiore"
-
-#: fdmprinter.def.json
-msgctxt "roofing_line_width description"
-msgid "Width of a single line of the areas at the top of the print."
-msgstr "Larghezza di un singola linea delle aree nella parte superiore della stampa"
-
#: fdmprinter.def.json
msgctxt "skin_line_width label"
msgid "Top/Bottom Line Width"
@@ -758,17 +733,17 @@ msgstr "Larghezza della linea della torre di innesco"
#: fdmprinter.def.json
msgctxt "prime_tower_line_width description"
msgid "Width of a single prime tower line."
-msgstr "Indica la larghezza di una singola linea della torre di innesco."
+msgstr "Indica la larghezza di una singola linea della prime tower."
#: fdmprinter.def.json
msgctxt "initial_layer_line_width_factor label"
msgid "Initial Layer Line Width"
-msgstr "Larghezza linea strato iniziale"
+msgstr "Larghezza linea layer iniziale"
#: fdmprinter.def.json
msgctxt "initial_layer_line_width_factor description"
msgid "Multiplier of the line width on the first layer. Increasing this could improve bed adhesion."
-msgstr "Moltiplicatore della larghezza della linea del primo strato Il suo aumento potrebbe migliorare l'adesione al piano"
+msgstr "Moltiplicatore della larghezza della linea del primo layer. Il suo aumento potrebbe migliorare l'adesione al piano"
#: fdmprinter.def.json
msgctxt "shell label"
@@ -788,7 +763,7 @@ msgstr "Estrusore pareti"
#: fdmprinter.def.json
msgctxt "wall_extruder_nr description"
msgid "The extruder train used for printing the walls. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per stampare le pareti. Si utilizza nell'estrusione multipla."
+msgstr "Blocco estrusore utilizzato per stampare le pareti. Si utilizza nell'estrusione multipla."
#: fdmprinter.def.json
msgctxt "wall_0_extruder_nr label"
@@ -798,7 +773,7 @@ msgstr "Estrusore parete esterna"
#: fdmprinter.def.json
msgctxt "wall_0_extruder_nr description"
msgid "The extruder train used for printing the outer wall. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per stampare la parete esterna. Si utilizza nell'estrusione multipla."
+msgstr "Blocco estrusore utilizzato per stampare la parete esterna. Si utilizza nell'estrusione multipla."
#: fdmprinter.def.json
msgctxt "wall_x_extruder_nr label"
@@ -808,7 +783,7 @@ msgstr "Estrusore parete interna"
#: fdmprinter.def.json
msgctxt "wall_x_extruder_nr description"
msgid "The extruder train used for printing the inner walls. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per stampare le pareti interne. Si utilizza nell'estrusione multipla."
+msgstr "Blocco estrusore utilizzato per stampare le pareti interne. Si utilizza nell'estrusione multipla."
#: fdmprinter.def.json
msgctxt "wall_thickness label"
@@ -828,7 +803,7 @@ msgstr "Numero delle linee perimetrali"
#: fdmprinter.def.json
msgctxt "wall_line_count description"
msgid "The number of walls. When calculated by the wall thickness, this value is rounded to a whole number."
-msgstr "Indica il numero delle pareti. Quando calcolato mediante lo spessore della parete, il valore viene arrotondato a numero intero."
+msgstr "Indica il numero delle pareti. Se calcolato mediante lo spessore della parete, il valore viene arrotondato a numero intero."
#: fdmprinter.def.json
msgctxt "wall_0_wipe_dist label"
@@ -848,52 +823,17 @@ msgstr "Estrusore rivestimento superficie superiore"
#: fdmprinter.def.json
msgctxt "roofing_extruder_nr description"
msgid "The extruder train used for printing the top most skin. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per stampare il rivestimento più in alto. Si utilizza nell'estrusione multipla."
+msgstr "Blocco estrusore utilizzato per stampare il rivestimento più in alto. Si utilizza nell'estrusione multipla."
#: fdmprinter.def.json
msgctxt "roofing_layer_count label"
msgid "Top Surface Skin Layers"
-msgstr "Strati di rivestimento superficie superiore"
+msgstr "Layer di rivestimento superficie superiore"
#: fdmprinter.def.json
msgctxt "roofing_layer_count description"
msgid "The number of top most skin layers. Usually only one top most layer is sufficient to generate higher quality top surfaces."
-msgstr "Numero degli strati di rivestimento superiori. Solitamente è sufficiente un unico strato di sommità per ottenere superfici superiori di qualità elevata."
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern label"
-msgid "Top Surface Skin Pattern"
-msgstr "Configurazione del rivestimento superficie superiore"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern description"
-msgid "The pattern of the top most layers."
-msgstr "Configurazione degli strati superiori."
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option lines"
-msgid "Lines"
-msgstr "Linee"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option concentric"
-msgid "Concentric"
-msgstr "Concentrica"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option zigzag"
-msgid "Zig Zag"
-msgstr "Zig Zag"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles label"
-msgid "Top Surface Skin Line Directions"
-msgstr "Direzioni linea rivestimento superficie superiore"
-
-#: fdmprinter.def.json
-msgctxt "roofing_angles description"
-msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
-msgstr "Un elenco di direzioni linee intere da usare quando gli strati rivestimento superficie superiore utilizzano le linee o la configurazione zig zag. Gli elementi dall’elenco sono utilizzati in sequenza con il progredire degli strati e, al raggiungimento della fine dell’elenco, la sequenza ricomincia dall’inizio. Le voci elencate sono separate da virgole e l’intero elenco è racchiuso tra parentesi quadre. L’elenco predefinito è vuoto, vale a dire che utilizza i valori angolari predefiniti (45 e 135 gradi)."
+msgstr "Numero dei layers di rivestimento superiori. Solitamente è sufficiente un unico layer di sommità per ottenere superfici superiori di qualità elevata."
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr label"
@@ -903,67 +843,67 @@ msgstr "Estrusore superiore/inferiore"
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr description"
msgid "The extruder train used for printing the top and bottom skin. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per stampare il rivestimento superiore e quello inferiore. Si utilizza nell'estrusione multipla."
+msgstr "Blocco estrusore utilizzato per stampare il rivestimento superiore e quello inferiore. Si utilizza nell'estrusione multipla."
#: fdmprinter.def.json
msgctxt "top_bottom_thickness label"
msgid "Top/Bottom Thickness"
-msgstr "Spessore dello strato superiore/inferiore"
+msgstr "Spessore del layer superiore/inferiore"
#: fdmprinter.def.json
msgctxt "top_bottom_thickness description"
msgid "The thickness of the top/bottom layers in the print. This value divided by the layer height defines the number of top/bottom layers."
-msgstr "Indica lo spessore degli strati superiore/inferiore nella stampa. Questo valore diviso per la l’altezza dello strato definisce il numero degli strati superiori/inferiori."
+msgstr "Indica lo spessore dei layers superiore/inferiore nella stampa. Questo valore diviso per la l’altezza del layer definisce il numero dei layers superiori/inferiori."
#: fdmprinter.def.json
msgctxt "top_thickness label"
msgid "Top Thickness"
-msgstr "Spessore dello strato superiore"
+msgstr "Spessore del layer superiore"
#: fdmprinter.def.json
msgctxt "top_thickness description"
msgid "The thickness of the top layers in the print. This value divided by the layer height defines the number of top layers."
-msgstr "Indica lo spessore degli strati superiori nella stampa. Questo valore diviso per la l’altezza dello strato definisce il numero degli strati superiori."
+msgstr "Indica lo spessore dei layers superiori nella stampa. Questo valore diviso per la l’altezza del layer definisce il numero dei layers superiori."
#: fdmprinter.def.json
msgctxt "top_layers label"
msgid "Top Layers"
-msgstr "Strati superiori"
+msgstr "Layers superiori"
#: fdmprinter.def.json
msgctxt "top_layers description"
msgid "The number of top layers. When calculated by the top thickness, this value is rounded to a whole number."
-msgstr "Indica il numero degli strati superiori. Quando calcolato mediante lo spessore dello strato superiore, il valore viene arrotondato a numero intero."
+msgstr "Indica il numero dei layers superiori. Se calcolato mediante lo spessore del layer superiore, il valore viene arrotondato a numero intero."
#: fdmprinter.def.json
msgctxt "bottom_thickness label"
msgid "Bottom Thickness"
-msgstr "Spessore degli strati inferiori"
+msgstr "Spessore dei layers inferiori"
#: fdmprinter.def.json
msgctxt "bottom_thickness description"
msgid "The thickness of the bottom layers in the print. This value divided by the layer height defines the number of bottom layers."
-msgstr "Indica lo spessore degli strati inferiori nella stampa. Questo valore diviso per la l’altezza dello strato definisce il numero degli strati inferiori."
+msgstr "Indica lo spessore dei layers inferiori nella stampa. Questo valore diviso per la l’altezza del layer definisce il numero dei layers inferiori."
#: fdmprinter.def.json
msgctxt "bottom_layers label"
msgid "Bottom Layers"
-msgstr "Strati inferiori"
+msgstr "Layers inferiori"
#: fdmprinter.def.json
msgctxt "bottom_layers description"
msgid "The number of bottom layers. When calculated by the bottom thickness, this value is rounded to a whole number."
-msgstr "Indica il numero degli strati inferiori. Quando calcolato mediante lo spessore dello strato inferiore, il valore viene arrotondato a numero intero."
+msgstr "Indica il numero dei layers inferiori. Quando calcolato mediante lo spessore del layer inferiore, il valore viene arrotondato a numero intero."
#: fdmprinter.def.json
msgctxt "top_bottom_pattern label"
msgid "Top/Bottom Pattern"
-msgstr "Configurazione dello strato superiore/inferiore"
+msgstr "Configurazione del layer superiore/inferiore"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern description"
msgid "The pattern of the top/bottom layers."
-msgstr "Indica la configurazione degli strati superiori/inferiori."
+msgstr "Indica la configurazione dei layers superiori/inferiori."
#: fdmprinter.def.json
msgctxt "top_bottom_pattern option lines"
@@ -983,12 +923,12 @@ msgstr "Zig Zag"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 label"
msgid "Bottom Pattern Initial Layer"
-msgstr "Strato iniziale configurazione inferiore"
+msgstr "Layer iniziale configurazione inferiore"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 description"
msgid "The pattern on the bottom of the print on the first layer."
-msgstr "La configurazione al fondo della stampa sul primo strato."
+msgstr "La configurazione al fondo della stampa sul primo layer."
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 option lines"
@@ -1013,7 +953,7 @@ msgstr "Direzioni delle linee superiori/inferiori"
#: fdmprinter.def.json
msgctxt "skin_angles description"
msgid "A list of integer line directions to use when the top/bottom layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
-msgstr "Un elenco di direzioni linee intere da usare quando gli strati superiori/inferiori utilizzano le linee o la configurazione zig zag. Gli elementi dall’elenco sono utilizzati in sequenza con il progredire degli strati e, al raggiungimento della fine dell’elenco, la sequenza ricomincia dall’inizio. Le voci elencate sono separate da virgole e l’intero elenco è racchiuso tra parentesi quadre. L’elenco predefinito è vuoto, vale a dire che utilizza i valori angolari predefiniti (45 e 135 gradi)."
+msgstr "Un elenco di direzioni linee intere da usare quando i layers superiori/inferiori utilizzano le linee o la configurazione zig zag. Gli elementi dall’elenco sono utilizzati in sequenza con il progredire dei layers e, al raggiungimento della fine dell’elenco, la sequenza ricomincia dall’inizio. Le voci elencate sono separate da virgole e l’intero elenco è racchiuso tra parentesi quadre. L’elenco predefinito è vuoto, vale a dire che utilizza i valori angolari predefiniti (45 e 135 gradi)."
#: fdmprinter.def.json
msgctxt "wall_0_inset label"
@@ -1025,6 +965,16 @@ msgctxt "wall_0_inset description"
msgid "Inset applied to the path of the outer wall. If the outer wall is smaller than the nozzle, and printed after the inner walls, use this offset to get the hole in the nozzle to overlap with the inner walls instead of the outside of the model."
msgstr "Inserto applicato al percorso della parete esterna. Se la parete esterna è di dimensioni inferiori all’ugello e stampata dopo le pareti interne, utilizzare questo offset per fare in modo che il foro dell’ugello si sovrapponga alle pareti interne anziché all’esterno del modello."
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order label"
+msgid "Optimize Wall Printing Order"
+msgstr "Ottimizzazione sequenza di stampa pareti"
+
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order description"
+msgid "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."
+msgstr "Ottimizza l'ordine in cui vengono stampate le pareti in modo da ridurre le retrazioni e la distanza percorsa. L'abilitazione di questa funzione porta vantaggi per la maggior parte dei pezzi, ma alcuni potrebbero richiedere un maggior tempo di esecuzione, per cui si consiglia di confrontare i tempi di stampa stimati con e senza ottimizzazione."
+
#: fdmprinter.def.json
msgctxt "outer_inset_first label"
msgid "Outer Before Inner Walls"
@@ -1095,6 +1045,16 @@ msgctxt "fill_perimeter_gaps option everywhere"
msgid "Everywhere"
msgstr "In tutti i possibili punti"
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps label"
+msgid "Filter Out Tiny Gaps"
+msgstr "Esclusione spazi minimi"
+
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps description"
+msgid "Filter out tiny gaps to reduce blobs on outside of model."
+msgstr "Esclude gli spazi minimi per ridurre le gocce sull’esterno del modello."
+
#: fdmprinter.def.json
msgctxt "fill_outline_gaps label"
msgid "Print Thin Walls"
@@ -1113,17 +1073,17 @@ msgstr "Espansione orizzontale"
#: fdmprinter.def.json
msgctxt "xy_offset description"
msgid "Amount of offset applied to all polygons in each layer. Positive values can compensate for too big holes; negative values can compensate for too small holes."
-msgstr "Determina l'entità di offset (o estensione dello strato) applicata a tutti i poligoni su ciascuno strato. I valori positivi possono compensare fori troppo estesi; i valori negativi possono compensare fori troppo piccoli."
+msgstr "Determina l'entità di offset (o estensione del layer) applicata a tutti i poligoni su ciascun layer. I valori positivi possono compensare fori troppo estesi; i valori negativi possono compensare fori troppo piccoli."
#: fdmprinter.def.json
msgctxt "xy_offset_layer_0 label"
msgid "Initial Layer Horizontal Expansion"
-msgstr "Espansione orizzontale dello strato iniziale"
+msgstr "Espansione orizzontale del layer iniziale"
#: fdmprinter.def.json
msgctxt "xy_offset_layer_0 description"
msgid "Amount of offset applied to all polygons in the first layer. A negative value can compensate for squishing of the first layer known as \"elephant's foot\"."
-msgstr "È l'entità di offset (estensione dello strato) applicata a tutti i poligoni di supporto in ciascuno strato. Un valore negativo può compensare lo schiacciamento del primo strato noto come \"zampa di elefante\"."
+msgstr "È l'entità di offset (estensione del layer) applicata a tutti i poligoni di supporto in ciascun layer. Un valore negativo può compensare lo schiacciamento del primo layer noto come \"zampa di elefante\"."
#: fdmprinter.def.json
msgctxt "z_seam_type label"
@@ -1133,7 +1093,7 @@ msgstr "Allineamento delle giunzioni a Z"
#: fdmprinter.def.json
msgctxt "z_seam_type description"
msgid "Starting point of each path in a layer. When paths in consecutive layers start at the same point a vertical seam may show on the print. When aligning these near a user specified location, the seam is easiest to remove. When placed randomly the inaccuracies at the paths' start will be less noticeable. When taking the shortest path the print will be quicker."
-msgstr "Punto di partenza di ogni percorso nell'ambito di uno strato. Quando i percorsi in strati consecutivi iniziano nello stesso punto, sulla stampa può apparire una linea di giunzione verticale. Se si allineano in prossimità di una posizione specificata dall’utente, la linea di giunzione può essere rimossa più facilmente. Se disposti in modo casuale, le imprecisioni in corrispondenza dell'inizio del percorso saranno meno evidenti. Prendendo il percorso più breve la stampa sarà più veloce."
+msgstr "Punto di partenza di ogni percorso nell'ambito di un layer. Quando i percorsi in layers consecutivi iniziano nello stesso punto, sulla stampa può apparire una linea di giunzione verticale. Se si allineano in prossimità di una posizione specificata dall’utente, la linea di giunzione può essere rimossa più facilmente. Se disposti in modo casuale, le imprecisioni in corrispondenza dell'inizio del percorso saranno meno evidenti. Prendendo il percorso più breve la stampa sarà più veloce."
#: fdmprinter.def.json
msgctxt "z_seam_type option back"
@@ -1163,7 +1123,7 @@ msgstr "Giunzione Z X"
#: fdmprinter.def.json
msgctxt "z_seam_x description"
msgid "The X coordinate of the position near where to start printing each part in a layer."
-msgstr "La coordinata X della posizione in prossimità della quale si innesca all’avvio della stampa di ciascuna parte in uno strato."
+msgstr "La coordinata X della posizione in prossimità della quale si innesca all’avvio della stampa di ciascuna parte in un layer."
#: fdmprinter.def.json
msgctxt "z_seam_y label"
@@ -1173,7 +1133,7 @@ msgstr "Giunzione Z Y"
#: fdmprinter.def.json
msgctxt "z_seam_y description"
msgid "The Y coordinate of the position near where to start printing each part in a layer."
-msgstr "La coordinata Y della posizione in prossimità della quale si innesca all’avvio della stampa di ciascuna parte in uno strato."
+msgstr "La coordinata Y della posizione in prossimità della quale si innesca all’avvio della stampa di ciascuna parte in un layer."
#: fdmprinter.def.json
msgctxt "z_seam_corner label"
@@ -1248,12 +1208,12 @@ msgstr "Ulteriore passaggio sopra la superficie superiore, senza estrusione di m
#: fdmprinter.def.json
msgctxt "ironing_only_highest_layer label"
msgid "Iron Only Highest Layer"
-msgstr "Stiramento del solo strato più elevato"
+msgstr "Stiramento del solo layer più elevato"
#: fdmprinter.def.json
msgctxt "ironing_only_highest_layer description"
msgid "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish."
-msgstr "Effettua lo stiramento solo dell'ultimissimo strato della maglia. È possibile quindi risparmiare tempo se gli strati inferiori non richiedono una finitura con superficie liscia."
+msgstr "Effettua lo stiramento solo dell'ultimissimo layer della maglia. È possibile quindi risparmiare tempo se i layers inferiori non richiedono una finitura con superficie liscia."
#: fdmprinter.def.json
msgctxt "ironing_pattern label"
@@ -1353,7 +1313,7 @@ msgstr "Estrusore riempimento"
#: fdmprinter.def.json
msgctxt "infill_extruder_nr description"
msgid "The extruder train used for printing infill. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per stampare il riempimento. Si utilizza nell'estrusione multipla."
+msgstr "Blocco estrusore utilizzato per stampare il riempimento. Si utilizza nell'estrusione multipla."
#: fdmprinter.def.json
msgctxt "infill_sparse_density label"
@@ -1383,7 +1343,7 @@ msgstr "Configurazione di riempimento"
#: fdmprinter.def.json
msgctxt "infill_pattern description"
msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
-msgstr "Configurazione del materiale di riempimento della stampa. Il riempimento a linea e a zig zag cambia direzione su strati alternati, riducendo il costo del materiale. Le configurazioni a griglia, a triangolo, tri-esagonali, cubiche, ottagonali, a quarto di cubo, incrociate e concentriche sono stampate completamente su ogni strato. Le configurazioni cubiche, a quarto di cubo e ottagonali variano per ciascuno strato per garantire una più uniforme distribuzione della forza in ogni direzione."
+msgstr "Configurazione del materiale di riempimento della stampa. Il riempimento a linea e a zig zag cambia direzione su strati alternati, riducendo il costo del materiale. Le configurazioni a griglia, a triangolo, tri-esagonali, cubiche, ottagonali, a quarto di cubo, incrociate e concentriche sono stampate completamente su ogni layer. Le configurazioni cubiche, a quarto di cubo e ottagonali variano per ciascun layer per garantire una più uniforme distribuzione della forza in ogni direzione."
#: fdmprinter.def.json
msgctxt "infill_pattern option grid"
@@ -1468,7 +1428,7 @@ msgstr "Direzioni delle linee di riempimento"
#: fdmprinter.def.json
msgctxt "infill_angles description"
msgid "A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees for the lines and zig zag patterns and 45 degrees for all other patterns)."
-msgstr "Un elenco di direzioni linee intere. Gli elementi dall’elenco sono utilizzati in sequenza con il progredire degli strati e, al raggiungimento della fine dell’elenco, la sequenza ricomincia dall’inizio. Le voci elencate sono separate da virgole e l’intero elenco è racchiuso tra parentesi quadre. L’elenco predefinito è vuoto, vale a dire che utilizza i valori angolari predefiniti (45 e 135 gradi per le linee e la configurazione zig zag e 45 gradi per tutte le altre configurazioni)."
+msgstr "Un elenco di direzioni linee intere. Gli elementi dall’elenco sono utilizzati in sequenza con il progredire dei layers e, al raggiungimento della fine dell’elenco, la sequenza ricomincia dall’inizio. Le voci elencate sono separate da virgole e l’intero elenco è racchiuso tra parentesi quadre. L’elenco predefinito è vuoto, vale a dire che utilizza i valori angolari predefiniti (45 e 135 gradi per le linee e la configurazione zig zag e 45 gradi per tutte le altre configurazioni)."
#: fdmprinter.def.json
msgctxt "infill_offset_x label"
@@ -1477,8 +1437,8 @@ msgstr "Offset X riempimento"
#: fdmprinter.def.json
msgctxt "infill_offset_x description"
-msgid "The infill pattern is offset this distance along the X axis."
-msgstr "Il riempimento si scosta di questa distanza lungo l'asse X."
+msgid "The infill pattern is moved this distance along the X axis."
+msgstr "Il riempimento si sposta di questa distanza lungo l'asse X."
#: fdmprinter.def.json
msgctxt "infill_offset_y label"
@@ -1487,8 +1447,8 @@ msgstr "Offset Y riempimento"
#: fdmprinter.def.json
msgctxt "infill_offset_y description"
-msgid "The infill pattern is offset this distance along the Y axis."
-msgstr "Il riempimento si scosta di questa distanza lungo l'asse Y."
+msgid "The infill pattern is moved this distance along the Y axis."
+msgstr "Il riempimento si sposta di questa distanza lungo l'asse Y."
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
@@ -1507,8 +1467,8 @@ msgstr "Percentuale di sovrapposizione del riempimento"
#: fdmprinter.def.json
msgctxt "infill_overlap description"
-msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
-msgstr "Indica la quantità di sovrapposizione tra il riempimento e le pareti. Una leggera sovrapposizione consente il saldo collegamento delle pareti al riempimento."
+msgid "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill."
+msgstr "Indica la quantità di sovrapposizione tra il riempimento e le pareti come percentuale della larghezza della linea di riempimento. Una leggera sovrapposizione consente il saldo collegamento delle pareti al riempimento."
#: fdmprinter.def.json
msgctxt "infill_overlap_mm label"
@@ -1527,8 +1487,8 @@ msgstr "Percentuale di sovrapposizione del rivestimento esterno"
#: fdmprinter.def.json
msgctxt "skin_overlap description"
-msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
-msgstr "Entità della sovrapposizione tra il rivestimento e le pareti espressa in percentuale della larghezza della linea. Una leggera sovrapposizione consente alle pareti di essere saldamente collegate al rivestimento. È una percentuale delle larghezze medie delle linee del rivestimento e della parete più interna."
+msgid "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+msgstr "Entità della sovrapposizione tra il rivestimento e le pareti espressa in percentuale della larghezza della linea del rivestimento esterno. Una leggera sovrapposizione consente alle pareti di essere saldamente collegate al rivestimento. È una percentuale delle larghezze medie delle linee del rivestimento e della parete più interna."
#: fdmprinter.def.json
msgctxt "skin_overlap_mm label"
@@ -1553,12 +1513,12 @@ msgstr "Indica la distanza di uno spostamento inserito dopo ogni linea di riempi
#: fdmprinter.def.json
msgctxt "infill_sparse_thickness label"
msgid "Infill Layer Thickness"
-msgstr "Spessore dello strato di riempimento"
+msgstr "Spessore del layer di riempimento"
#: fdmprinter.def.json
msgctxt "infill_sparse_thickness description"
msgid "The thickness per layer of infill material. This value should always be a multiple of the layer height and is otherwise rounded."
-msgstr "Indica lo spessore per strato di materiale di riempimento. Questo valore deve sempre essere un multiplo dell’altezza dello strato e in caso contrario viene arrotondato."
+msgstr "Indica lo spessore per layer di materiale di riempimento. Questo valore deve sempre essere un multiplo dell’altezza del layer e in caso contrario viene arrotondato."
#: fdmprinter.def.json
msgctxt "gradual_infill_steps label"
@@ -1633,7 +1593,7 @@ msgstr "Larghezza massima delle aree di rivestimento inferiore che è possibile
#: fdmprinter.def.json
msgctxt "expand_skins_expand_distance label"
msgid "Skin Expand Distance"
-msgstr "Distanza prolunga rivestimento esterno"
+msgstr "Distanza espansione rivestimento esterno"
#: fdmprinter.def.json
msgctxt "expand_skins_expand_distance description"
@@ -1643,7 +1603,7 @@ msgstr "Distanza per cui i rivestimenti si estendono nel riempimento. Valori mag
#: fdmprinter.def.json
msgctxt "top_skin_expand_distance label"
msgid "Top Skin Expand Distance"
-msgstr "Distanza prolunga rivestimento superiore"
+msgstr "Distanza espansione rivestimento superiore"
#: fdmprinter.def.json
msgctxt "top_skin_expand_distance description"
@@ -1653,7 +1613,7 @@ msgstr "Distanza per cui i rivestimenti superiori si estendono nel riempimento.
#: fdmprinter.def.json
msgctxt "bottom_skin_expand_distance label"
msgid "Bottom Skin Expand Distance"
-msgstr "Distanza prolunga rivestimento inferiore"
+msgstr "Distanza espansione rivestimento inferiore"
#: fdmprinter.def.json
msgctxt "bottom_skin_expand_distance description"
@@ -1663,7 +1623,7 @@ msgstr "Distanza per cui i rivestimenti inferiori si estendono nel riempimento.
#: fdmprinter.def.json
msgctxt "max_skin_angle_for_expansion label"
msgid "Maximum Skin Angle for Expansion"
-msgstr "Angolo massimo rivestimento esterno per prolunga"
+msgstr "Angolo massimo rivestimento esterno per espansione"
#: fdmprinter.def.json
msgctxt "max_skin_angle_for_expansion description"
@@ -1673,7 +1633,7 @@ msgstr "Per le superfici inferiori e/o superiori dell’oggetto con un angolo ma
#: fdmprinter.def.json
msgctxt "min_skin_width_for_expansion label"
msgid "Minimum Skin Width for Expansion"
-msgstr "Larghezza minima rivestimento esterno per prolunga"
+msgstr "Larghezza minima rivestimento esterno per espansione"
#: fdmprinter.def.json
msgctxt "min_skin_width_for_expansion description"
@@ -1690,16 +1650,6 @@ msgctxt "material description"
msgid "Material"
msgstr "Materiale"
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature label"
-msgid "Auto Temperature"
-msgstr "Temperatura automatica"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature description"
-msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
-msgstr "Modifica automaticamente la temperatura per ciascuno strato con la velocità media del flusso per tale strato."
-
#: fdmprinter.def.json
msgctxt "default_material_print_temperature label"
msgid "Default Printing Temperature"
@@ -1723,12 +1673,12 @@ msgstr "Indica la temperatura usata per la stampa."
#: fdmprinter.def.json
msgctxt "material_print_temperature_layer_0 label"
msgid "Printing Temperature Initial Layer"
-msgstr "Temperatura di stampa Strato iniziale"
+msgstr "Temperatura di stampa layer iniziale"
#: fdmprinter.def.json
msgctxt "material_print_temperature_layer_0 description"
msgid "The temperature used for printing the first layer. Set at 0 to disable special handling of the initial layer."
-msgstr "Indica la temperatura usata per la stampa del primo strato. Impostare a 0 per disabilitare la manipolazione speciale dello strato iniziale."
+msgstr "Indica la temperatura usata per la stampa del primo layer. Impostare a 0 per disabilitare la manipolazione speciale del layer iniziale."
#: fdmprinter.def.json
msgctxt "material_initial_print_temperature label"
@@ -1750,16 +1700,6 @@ msgctxt "material_final_print_temperature description"
msgid "The temperature to which to already start cooling down just before the end of printing."
msgstr "La temperatura alla quale può già iniziare il raffreddamento prima della fine della stampa."
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph label"
-msgid "Flow Temperature Graph"
-msgstr "Grafico della temperatura del flusso"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph description"
-msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
-msgstr "Collegamento dei dati di flusso del materiale (in mm3 al secondo) alla temperatura (in °C)."
-
#: fdmprinter.def.json
msgctxt "material_extrusion_cool_down_speed label"
msgid "Extrusion Cool Down Speed Modifier"
@@ -1777,18 +1717,18 @@ msgstr "Temperatura piano di stampa"
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
-msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
-msgstr "Indica la temperatura usata per il piano di stampa riscaldato. Se è 0, il piano non si riscalda per questa stampa."
+msgid "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted."
+msgstr "Indica la temperatura usata per il piano di stampa riscaldato. Se è 0, la temperatura del piano non si regola."
#: fdmprinter.def.json
msgctxt "material_bed_temperature_layer_0 label"
msgid "Build Plate Temperature Initial Layer"
-msgstr "Temperatura piano di stampa Strato iniziale"
+msgstr "Temperatura piano di stampa Layer iniziale"
#: fdmprinter.def.json
msgctxt "material_bed_temperature_layer_0 description"
msgid "The temperature used for the heated build plate at the first layer."
-msgstr "Indica la temperatura usata per il piano di stampa riscaldato per il primo strato."
+msgstr "Indica la temperatura usata per il piano di stampa riscaldato per il primo layer."
#: fdmprinter.def.json
msgctxt "material_diameter label"
@@ -1843,12 +1783,12 @@ msgstr "Ritrae il filamento quando l'ugello si sta muovendo su un'area non stamp
#: fdmprinter.def.json
msgctxt "retract_at_layer_change label"
msgid "Retract at Layer Change"
-msgstr "Retrazione al cambio strato"
+msgstr "Retrazione al cambio layer"
#: fdmprinter.def.json
msgctxt "retract_at_layer_change description"
msgid "Retract the filament when the nozzle is moving to the next layer."
-msgstr "Ritrae il filamento quando l'ugello si sta muovendo allo strato successivo. "
+msgstr "Ritrae il filamento quando l'ugello si sta muovendo al layer successivo. "
#: fdmprinter.def.json
msgctxt "retraction_amount label"
@@ -2113,12 +2053,12 @@ msgstr "Velocità alla quale viene stampata la parte inferiore del supporto. La
#: fdmprinter.def.json
msgctxt "speed_prime_tower label"
msgid "Prime Tower Speed"
-msgstr "Velocità della torre di innesco"
+msgstr "Velocità della Prime Tower"
#: fdmprinter.def.json
msgctxt "speed_prime_tower description"
msgid "The speed at which the prime tower is printed. Printing the prime tower slower can make it more stable when the adhesion between the different filaments is suboptimal."
-msgstr "Indica la velocità alla quale è stampata la torre di innesco. La stampa della torre di innesco a una velocità inferiore può renderla maggiormente stabile quando l’adesione tra i diversi filamenti non è ottimale."
+msgstr "Indica la velocità alla quale è stampata la Prime Tower. La stampa della Prime Tower a una velocità inferiore può renderla maggiormente stabile quando l’adesione tra i diversi filamenti non è ottimale."
#: fdmprinter.def.json
msgctxt "speed_travel label"
@@ -2133,32 +2073,32 @@ msgstr "Indica la velocità alla quale vengono effettuati gli spostamenti."
#: fdmprinter.def.json
msgctxt "speed_layer_0 label"
msgid "Initial Layer Speed"
-msgstr "Velocità di stampa dello strato iniziale"
+msgstr "Velocità di stampa del layer iniziale"
#: fdmprinter.def.json
msgctxt "speed_layer_0 description"
msgid "The speed for the initial layer. A lower value is advised to improve adhesion to the build plate."
-msgstr "Indica la velocità per lo strato iniziale. Un valore inferiore è consigliabile per migliorare l’adesione al piano di stampa."
+msgstr "Indica la velocità per il layer iniziale. Un valore inferiore è consigliabile per migliorare l’adesione al piano di stampa."
#: fdmprinter.def.json
msgctxt "speed_print_layer_0 label"
msgid "Initial Layer Print Speed"
-msgstr "Velocità di stampa strato iniziale"
+msgstr "Velocità di stampa layer iniziale"
#: fdmprinter.def.json
msgctxt "speed_print_layer_0 description"
msgid "The speed of printing for the initial layer. A lower value is advised to improve adhesion to the build plate."
-msgstr "Indica la velocità di stampa per lo strato iniziale. Un valore inferiore è consigliabile per migliorare l’adesione al piano di stampa."
+msgstr "Indica la velocità di stampa per il layer iniziale. Un valore inferiore è consigliabile per migliorare l’adesione al piano di stampa."
#: fdmprinter.def.json
msgctxt "speed_travel_layer_0 label"
msgid "Initial Layer Travel Speed"
-msgstr "Velocità di spostamento dello strato iniziale"
+msgstr "Velocità di spostamento del layer iniziale"
#: fdmprinter.def.json
msgctxt "speed_travel_layer_0 description"
msgid "The speed of travel moves in the initial layer. A lower value is advised to prevent pulling previously printed parts away from the build plate. The value of this setting can automatically be calculated from the ratio between the Travel Speed and the Print Speed."
-msgstr "Indica la velocità di spostamento per lo strato iniziale. Un valore inferiore è consigliabile per evitare di rimuovere le parti precedentemente stampate dal piano di stampa. Il valore di questa impostazione può essere calcolato automaticamente dal rapporto tra la velocità di spostamento e la velocità di stampa."
+msgstr "Indica la velocità di spostamento del layer iniziale. Un valore inferiore è consigliabile per evitare di rimuovere le parti precedentemente stampate dal piano di stampa. Il valore di questa impostazione può essere calcolato automaticamente dal rapporto tra la velocità di spostamento e la velocità di stampa."
#: fdmprinter.def.json
msgctxt "skirt_brim_speed label"
@@ -2183,12 +2123,12 @@ msgstr "Indica la velocità massima di spostamento del piano di stampa. L’impo
#: fdmprinter.def.json
msgctxt "speed_slowdown_layers label"
msgid "Number of Slower Layers"
-msgstr "Numero di strati stampati a velocità inferiore"
+msgstr "Numero di layers stampati a velocità inferiore"
#: fdmprinter.def.json
msgctxt "speed_slowdown_layers description"
msgid "The first few layers are printed slower than the rest of the model, to get better adhesion to the build plate and improve the overall success rate of prints. The speed is gradually increased over these layers."
-msgstr "I primi strati vengono stampati più lentamente rispetto al resto del modello, per ottenere una migliore adesione al piano di stampa ed ottimizzare nel complesso la percentuale di successo delle stampe. La velocità aumenta gradualmente nel corso di esecuzione degli strati successivi."
+msgstr "I primi layers vengono stampati più lentamente rispetto al resto del modello, per ottenere una migliore adesione al piano di stampa ed ottimizzare nel complesso la percentuale di successo delle stampe. La velocità aumenta gradualmente nel corso di esecuzione dei layers successivi."
#: fdmprinter.def.json
msgctxt "speed_equalize_flow_enabled label"
@@ -2343,12 +2283,12 @@ msgstr "Accelerazione alla quale vengono stampate le parti inferiori del support
#: fdmprinter.def.json
msgctxt "acceleration_prime_tower label"
msgid "Prime Tower Acceleration"
-msgstr "Accelerazione della torre di innesco"
+msgstr "Accelerazione della Prime Tower"
#: fdmprinter.def.json
msgctxt "acceleration_prime_tower description"
msgid "The acceleration with which the prime tower is printed."
-msgstr "Indica l’accelerazione con cui viene stampata la torre di innesco."
+msgstr "Indica l’accelerazione con cui viene stampata la Prime Tower."
#: fdmprinter.def.json
msgctxt "acceleration_travel label"
@@ -2363,32 +2303,32 @@ msgstr "Indica l’accelerazione alla quale vengono effettuati gli spostamenti."
#: fdmprinter.def.json
msgctxt "acceleration_layer_0 label"
msgid "Initial Layer Acceleration"
-msgstr "Accelerazione dello strato iniziale"
+msgstr "Accelerazione del layer iniziale"
#: fdmprinter.def.json
msgctxt "acceleration_layer_0 description"
msgid "The acceleration for the initial layer."
-msgstr "Indica l’accelerazione dello strato iniziale."
+msgstr "Indica l’accelerazione del layer iniziale."
#: fdmprinter.def.json
msgctxt "acceleration_print_layer_0 label"
msgid "Initial Layer Print Acceleration"
-msgstr "Accelerazione di stampa strato iniziale"
+msgstr "Accelerazione di stampa layer iniziale"
#: fdmprinter.def.json
msgctxt "acceleration_print_layer_0 description"
msgid "The acceleration during the printing of the initial layer."
-msgstr "Indica l’accelerazione durante la stampa dello strato iniziale."
+msgstr "Indica l’accelerazione durante la stampa del layer iniziale."
#: fdmprinter.def.json
msgctxt "acceleration_travel_layer_0 label"
msgid "Initial Layer Travel Acceleration"
-msgstr "Accelerazione spostamenti dello strato iniziale"
+msgstr "Accelerazione spostamenti del layer iniziale"
#: fdmprinter.def.json
msgctxt "acceleration_travel_layer_0 description"
msgid "The acceleration for travel moves in the initial layer."
-msgstr "Indica l’accelerazione degli spostamenti dello strato iniziale."
+msgstr "Indica l’accelerazione degli spostamenti del layer iniziale."
#: fdmprinter.def.json
msgctxt "acceleration_skirt_brim label"
@@ -2398,7 +2338,7 @@ msgstr "Accelerazione skirt/brim"
#: fdmprinter.def.json
msgctxt "acceleration_skirt_brim description"
msgid "The acceleration with which the skirt and brim are printed. Normally this is done with the initial layer acceleration, but sometimes you might want to print the skirt or brim at a different acceleration."
-msgstr "Indica l’accelerazione alla quale sono stampati lo skirt ed il brim. Normalmente questa operazione viene svolta all’accelerazione dello strato iniziale, ma a volte è possibile che si desideri stampare lo skirt o il brim ad un’accelerazione diversa."
+msgstr "Indica l’accelerazione alla quale sono stampati lo skirt ed il brim. Normalmente questa operazione viene svolta all’accelerazione del layer iniziale, ma a volte è possibile che si desideri stampare lo skirt o il brim ad un’accelerazione diversa."
#: fdmprinter.def.json
msgctxt "jerk_enabled label"
@@ -2408,7 +2348,7 @@ msgstr "Abilita controllo jerk"
#: fdmprinter.def.json
msgctxt "jerk_enabled description"
msgid "Enables adjusting the jerk of print head when the velocity in the X or Y axis changes. Increasing the jerk can reduce printing time at the cost of print quality."
-msgstr "Abilita la regolazione del jerk della testina di stampa quando la velocità nell’asse X o Y cambia. Aumentando il jerk il tempo di stampa si riduce a discapito della qualità di stampa."
+msgstr "Abilita la regolazione del jerk della testa di stampa quando la velocità nell’asse X o Y cambia. Aumentando il jerk il tempo di stampa si riduce a discapito della qualità di stampa."
#: fdmprinter.def.json
msgctxt "jerk_print label"
@@ -2418,7 +2358,7 @@ msgstr "Jerk stampa"
#: fdmprinter.def.json
msgctxt "jerk_print description"
msgid "The maximum instantaneous velocity change of the print head."
-msgstr "Indica il cambio della velocità istantanea massima della testina di stampa."
+msgstr "Indica il cambio della velocità istantanea massima della testa di stampa."
#: fdmprinter.def.json
msgctxt "jerk_infill label"
@@ -2468,7 +2408,7 @@ msgstr "Jerk del rivestimento superficie superiore"
#: fdmprinter.def.json
msgctxt "jerk_roofing description"
msgid "The maximum instantaneous velocity change with which top surface skin layers are printed."
-msgstr "Indica la variazione di velocità istantanea massima con cui vengono stampati gli strati rivestimento superficie superiore."
+msgstr "Indica la variazione di velocità istantanea massima con cui vengono stampati i layers rivestimento superficie superiore."
#: fdmprinter.def.json
msgctxt "jerk_topbottom label"
@@ -2478,7 +2418,7 @@ msgstr "Jerk strato superiore/inferiore"
#: fdmprinter.def.json
msgctxt "jerk_topbottom description"
msgid "The maximum instantaneous velocity change with which top/bottom layers are printed."
-msgstr "Indica il cambio della velocità istantanea massima con cui vengono stampati gli strati superiore/inferiore."
+msgstr "Indica il cambio della velocità istantanea massima con cui vengono stampati i layers superiore/inferiore."
#: fdmprinter.def.json
msgctxt "jerk_support label"
@@ -2533,12 +2473,12 @@ msgstr "Indica la variazione della velocità istantanea massima con cui vengono
#: fdmprinter.def.json
msgctxt "jerk_prime_tower label"
msgid "Prime Tower Jerk"
-msgstr "Jerk della torre di innesco"
+msgstr "Jerk della Prime Tower"
#: fdmprinter.def.json
msgctxt "jerk_prime_tower description"
msgid "The maximum instantaneous velocity change with which the prime tower is printed."
-msgstr "Indica il cambio della velocità istantanea massima con cui viene stampata la torre di innesco del supporto."
+msgstr "Indica il cambio della velocità istantanea massima con cui viene stampata la Prime Tower del supporto."
#: fdmprinter.def.json
msgctxt "jerk_travel label"
@@ -2553,32 +2493,32 @@ msgstr "Indica il cambio della velocità istantanea massima con cui vengono effe
#: fdmprinter.def.json
msgctxt "jerk_layer_0 label"
msgid "Initial Layer Jerk"
-msgstr "Jerk dello strato iniziale"
+msgstr "Jerk del layer iniziale"
#: fdmprinter.def.json
msgctxt "jerk_layer_0 description"
msgid "The print maximum instantaneous velocity change for the initial layer."
-msgstr "Indica il cambio della velocità istantanea massima dello strato iniziale."
+msgstr "Indica il cambio della velocità istantanea massima del layer iniziale."
#: fdmprinter.def.json
msgctxt "jerk_print_layer_0 label"
msgid "Initial Layer Print Jerk"
-msgstr "Jerk di stampa strato iniziale"
+msgstr "Jerk di stampa layer iniziale"
#: fdmprinter.def.json
msgctxt "jerk_print_layer_0 description"
msgid "The maximum instantaneous velocity change during the printing of the initial layer."
-msgstr "Indica il cambio della velocità istantanea massima durante la stampa dello strato iniziale."
+msgstr "Indica il cambio della velocità istantanea massima durante la stampa del layer iniziale."
#: fdmprinter.def.json
msgctxt "jerk_travel_layer_0 label"
msgid "Initial Layer Travel Jerk"
-msgstr "Jerk spostamenti dello strato iniziale"
+msgstr "Jerk spostamenti del layer iniziale"
#: fdmprinter.def.json
msgctxt "jerk_travel_layer_0 description"
msgid "The acceleration for travel moves in the initial layer."
-msgstr "Indica l’accelerazione degli spostamenti dello strato iniziale."
+msgstr "Indica l’accelerazione degli spostamenti del layer iniziale."
#: fdmprinter.def.json
msgctxt "jerk_skirt_brim label"
@@ -2658,22 +2598,22 @@ msgstr "La distanza tra l’ugello e le parti già stampate quando si effettua l
#: fdmprinter.def.json
msgctxt "start_layers_at_same_position label"
msgid "Start Layers with the Same Part"
-msgstr "Avvio strati con la stessa parte"
+msgstr "Avvio layers con la stessa parte"
#: fdmprinter.def.json
msgctxt "start_layers_at_same_position description"
msgid "In each layer start with printing the object near the same point, so that we don't start a new layer with printing the piece which the previous layer ended with. This makes for better overhangs and small parts, but increases printing time."
-msgstr "In ciascuno strato inizia la stampa dell’oggetto vicino allo stesso punto, in modo che non si inizia un nuovo strato con la stampa del pezzo con cui è terminato lo strato precedente. Questo consente di ottenere migliori sovrapposizioni e parti piccole, ma aumenta il tempo di stampa."
+msgstr "In ciascun layer inizia la stampa dell’oggetto vicino allo stesso punto, in modo che non si inizi un nuovo layer con la stampa del pezzo con cui è terminato il layer precedente. Questo consente di ottenere migliori sovrapposizioni e parti piccole, ma aumenta il tempo di stampa."
#: fdmprinter.def.json
msgctxt "layer_start_x label"
msgid "Layer Start X"
-msgstr "Avvio strato X"
+msgstr "Avvio layer X"
#: fdmprinter.def.json
msgctxt "layer_start_x description"
msgid "The X coordinate of the position near where to find the part to start printing each layer."
-msgstr "La coordinata X della posizione in prossimità della quale si trova la parte per avviare la stampa di ciascuno strato."
+msgstr "La coordinata X della posizione in prossimità della quale si trova la parte per avviare la stampa di ciascun layer."
#: fdmprinter.def.json
msgctxt "layer_start_y label"
@@ -2683,7 +2623,7 @@ msgstr "Avvio strato Y"
#: fdmprinter.def.json
msgctxt "layer_start_y description"
msgid "The Y coordinate of the position near where to find the part to start printing each layer."
-msgstr "La coordinata Y della posizione in prossimità della quale si trova la parte per avviare la stampa di ciascuno strato."
+msgstr "La coordinata Y della posizione in prossimità della quale si trova la parte per avviare la stampa di ciascun layer."
#: fdmprinter.def.json
msgctxt "retraction_hop_enabled label"
@@ -2743,7 +2683,7 @@ msgstr "Abilitazione raffreddamento stampa"
#: fdmprinter.def.json
msgctxt "cool_fan_enabled description"
msgid "Enables the print cooling fans while printing. The fans improve print quality on layers with short layer times and bridging / overhangs."
-msgstr "Abilita le ventole di raffreddamento durante la stampa. Le ventole migliorano la qualità di stampa sugli strati con tempi per strato più brevi e ponti/sbalzi."
+msgstr "Abilita le ventole di raffreddamento durante la stampa. Le ventole migliorano la qualità di stampa sui layers con tempi per layer più brevi e ponti/sbalzi."
#: fdmprinter.def.json
msgctxt "cool_fan_speed label"
@@ -2763,7 +2703,7 @@ msgstr "Velocità regolare della ventola"
#: fdmprinter.def.json
msgctxt "cool_fan_speed_min description"
msgid "The speed at which the fans spin before hitting the threshold. When a layer prints faster than the threshold, the fan speed gradually inclines towards the maximum fan speed."
-msgstr "Indica la velocità alla quale ruotano le ventole prima di raggiungere la soglia. Quando uno strato viene stampato a una velocità superiore alla soglia, la velocità della ventola tende gradualmente verso la velocità massima della ventola."
+msgstr "Indica la velocità alla quale ruotano le ventole prima di raggiungere la soglia. Quando un layer viene stampato a una velocità superiore alla soglia, la velocità della ventola tende gradualmente verso la velocità massima della ventola."
#: fdmprinter.def.json
msgctxt "cool_fan_speed_max label"
@@ -2773,7 +2713,7 @@ msgstr "Velocità massima della ventola"
#: fdmprinter.def.json
msgctxt "cool_fan_speed_max description"
msgid "The speed at which the fans spin on the minimum layer time. The fan speed gradually increases between the regular fan speed and maximum fan speed when the threshold is hit."
-msgstr "Indica la velocità di rotazione della ventola al tempo minimo per strato. La velocità della ventola aumenta gradualmente tra la velocità regolare della ventola e la velocità massima della ventola quando viene raggiunta la soglia."
+msgstr "Indica la velocità di rotazione della ventola al tempo minimo per layer. La velocità della ventola aumenta gradualmente tra la velocità regolare della ventola e la velocità massima della ventola quando viene raggiunta la soglia."
#: fdmprinter.def.json
msgctxt "cool_min_layer_time_fan_speed_max label"
@@ -2783,7 +2723,7 @@ msgstr "Soglia velocità regolare/massima della ventola"
#: fdmprinter.def.json
msgctxt "cool_min_layer_time_fan_speed_max description"
msgid "The layer time which sets the threshold between regular fan speed and maximum fan speed. Layers that print slower than this time use regular fan speed. For faster layers the fan speed gradually increases towards the maximum fan speed."
-msgstr "Indica il tempo per strato che definisce la soglia tra la velocità regolare e quella massima della ventola. Gli strati che vengono stampati a una velocità inferiore a questo valore utilizzano una velocità regolare della ventola. Per gli strati stampati più velocemente la velocità della ventola aumenta gradualmente verso la velocità massima della ventola."
+msgstr "Indica il tempo per layer che definisce la soglia tra la velocità regolare e quella massima della ventola. Gli strati che vengono stampati a una velocità inferiore a questo valore utilizzano una velocità regolare della ventola. Per i layers stampati più velocemente la velocità della ventola aumenta gradualmente verso la velocità massima della ventola."
#: fdmprinter.def.json
msgctxt "cool_fan_speed_0 label"
@@ -2793,7 +2733,7 @@ msgstr "Velocità iniziale della ventola"
#: fdmprinter.def.json
msgctxt "cool_fan_speed_0 description"
msgid "The speed at which the fans spin at the start of the print. In subsequent layers the fan speed is gradually increased up to the layer corresponding to Regular Fan Speed at Height."
-msgstr "La velocità di rotazione della ventola all’inizio della stampa. Negli strati successivi la velocità della ventola aumenta gradualmente da zero fino allo strato corrispondente alla velocità regolare in altezza."
+msgstr "La velocità di rotazione della ventola all’inizio della stampa. Nei layers successivi la velocità della ventola aumenta gradualmente da zero fino allo strato corrispondente alla velocità regolare in altezza."
#: fdmprinter.def.json
msgctxt "cool_fan_full_at_height label"
@@ -2803,27 +2743,27 @@ msgstr "Velocità regolare della ventola in altezza"
#: fdmprinter.def.json
msgctxt "cool_fan_full_at_height description"
msgid "The height at which the fans spin on regular fan speed. At the layers below the fan speed gradually increases from Initial Fan Speed to Regular Fan Speed."
-msgstr "Indica l’altezza alla quale la ventola ruota alla velocità regolare. Agli strati stampati a velocità inferiore la velocità della ventola aumenta gradualmente dalla velocità iniziale a quella regolare."
+msgstr "Indica l’altezza alla quale la ventola ruota alla velocità regolare. ai layers stampati a velocità inferiore la velocità della ventola aumenta gradualmente dalla velocità iniziale a quella regolare."
#: fdmprinter.def.json
msgctxt "cool_fan_full_layer label"
msgid "Regular Fan Speed at Layer"
-msgstr "Velocità regolare della ventola in corrispondenza dello strato"
+msgstr "Velocità regolare della ventola in corrispondenza del layer"
#: fdmprinter.def.json
msgctxt "cool_fan_full_layer description"
msgid "The layer at which the fans spin on regular fan speed. If regular fan speed at height is set, this value is calculated and rounded to a whole number."
-msgstr "Indica lo strato in corrispondenza del quale la ventola ruota alla velocità regolare. Se è impostata la velocità regolare in altezza, questo valore viene calcolato e arrotondato a un numero intero."
+msgstr "Indica il layer in corrispondenza del quale la ventola ruota alla velocità regolare. Se è impostata la velocità regolare in altezza, questo valore viene calcolato e arrotondato a un numero intero."
#: fdmprinter.def.json
msgctxt "cool_min_layer_time label"
msgid "Minimum Layer Time"
-msgstr "Tempo minimo per strato"
+msgstr "Tempo minimo per layer"
#: fdmprinter.def.json
msgctxt "cool_min_layer_time description"
msgid "The minimum time spent in a layer. This forces the printer to slow down, to at least spend the time set here in one layer. This allows the printed material to cool down properly before printing the next layer. Layers may still take shorter than the minimal layer time if Lift Head is disabled and if the Minimum Speed would otherwise be violated."
-msgstr "Indica il tempo minimo dedicato a uno strato. Questo forza la stampante a rallentare, per impiegare almeno il tempo impostato qui per uno strato. Questo consente il corretto raffreddamento del materiale stampato prima di procedere alla stampa dello strato successivo. La stampa degli strati potrebbe richiedere un tempo inferiore al minimo se la funzione Sollevamento della testina è disabilitata e se la velocità minima non viene rispettata."
+msgstr "Indica il tempo minimo dedicato a un layer. Questo forza la stampante a rallentare, per impiegare almeno il tempo impostato qui per un layer. Questo consente il corretto raffreddamento del materiale stampato prima di procedere alla stampa del layer successivo. La stampa dei layers potrebbe richiedere un tempo inferiore al minimo se la funzione Sollevamento della testina è disabilitata e se la velocità minima non viene rispettata."
#: fdmprinter.def.json
msgctxt "cool_min_speed label"
@@ -2833,7 +2773,7 @@ msgstr "Velocità minima"
#: fdmprinter.def.json
msgctxt "cool_min_speed description"
msgid "The minimum print speed, despite slowing down due to the minimum layer time. When the printer would slow down too much, the pressure in the nozzle would be too low and result in bad print quality."
-msgstr "Indica la velocità minima di stampa, a prescindere dal rallentamento per il tempo minimo per strato. Quando la stampante rallenta eccessivamente, la pressione nell’ugello risulta insufficiente con conseguente scarsa qualità di stampa."
+msgstr "Indica la velocità minima di stampa, a prescindere dal rallentamento per il tempo minimo per layer. Quando la stampante rallenta eccessivamente, la pressione nell’ugello risulta insufficiente con conseguente scarsa qualità di stampa."
#: fdmprinter.def.json
msgctxt "cool_lift_head label"
@@ -2843,7 +2783,7 @@ msgstr "Sollevamento della testina"
#: fdmprinter.def.json
msgctxt "cool_lift_head description"
msgid "When the minimum speed is hit because of minimum layer time, lift the head away from the print and wait the extra time until the minimum layer time is reached."
-msgstr "Quando viene raggiunta la velocità minima per il tempo minimo per strato, sollevare la testina dalla stampa e attendere il tempo supplementare fino al raggiungimento del valore per tempo minimo per strato."
+msgstr "Quando viene raggiunta la velocità minima per il tempo minimo per layer, sollevare la testina dalla stampa e attendere il tempo supplementare fino al raggiungimento del valore per tempo minimo per layer."
#: fdmprinter.def.json
msgctxt "support label"
@@ -2873,7 +2813,7 @@ msgstr "Estrusore del supporto"
#: fdmprinter.def.json
msgctxt "support_extruder_nr description"
msgid "The extruder train to use for printing the support. This is used in multi-extrusion."
-msgstr "Il treno estrusore utilizzato per la stampa del supporto. Utilizzato nell’estrusione multipla."
+msgstr "Il blocco estrusore utilizzato per la stampa del supporto. Utilizzato nell’estrusione multipla."
#: fdmprinter.def.json
msgctxt "support_infill_extruder_nr label"
@@ -2883,17 +2823,17 @@ msgstr "Estrusore riempimento del supporto"
#: fdmprinter.def.json
msgctxt "support_infill_extruder_nr description"
msgid "The extruder train to use for printing the infill of the support. This is used in multi-extrusion."
-msgstr "Il treno estrusore utilizzato per la stampa del riempimento del supporto. Utilizzato nell’estrusione multipla."
+msgstr "Il blocco estrusore utilizzato per la stampa del riempimento del supporto. Utilizzato nell’estrusione multipla."
#: fdmprinter.def.json
msgctxt "support_extruder_nr_layer_0 label"
msgid "First Layer Support Extruder"
-msgstr "Estrusore del supporto primo strato"
+msgstr "Estrusore del supporto primo layer"
#: fdmprinter.def.json
msgctxt "support_extruder_nr_layer_0 description"
msgid "The extruder train to use for printing the first layer of support infill. This is used in multi-extrusion."
-msgstr "Il treno estrusore utilizzato per la stampa del primo strato del riempimento del supporto. Utilizzato nell’estrusione multipla."
+msgstr "Il blocco estrusore utilizzato per la stampa del primo layer del riempimento del supporto. Utilizzato nell’estrusione multipla."
#: fdmprinter.def.json
msgctxt "support_interface_extruder_nr label"
@@ -2903,7 +2843,7 @@ msgstr "Estrusore interfaccia del supporto"
#: fdmprinter.def.json
msgctxt "support_interface_extruder_nr description"
msgid "The extruder train to use for printing the roofs and floors of the support. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per la stampa delle parti superiori e inferiori del supporto. Utilizzato nell’estrusione multipla."
+msgstr "Blocco estrusore utilizzato per la stampa delle parti superiori e inferiori del supporto. Utilizzato nell’estrusione multipla."
#: fdmprinter.def.json
msgctxt "support_roof_extruder_nr label"
@@ -2913,7 +2853,7 @@ msgstr "Estrusore parte superiore del supporto"
#: fdmprinter.def.json
msgctxt "support_roof_extruder_nr description"
msgid "The extruder train to use for printing the roofs of the support. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per la stampa delle parti superiori del supporto. Utilizzato nell’estrusione multipla."
+msgstr "Blocco estrusore utilizzato per la stampa delle parti superiori del supporto. Utilizzato nell’estrusione multipla."
#: fdmprinter.def.json
msgctxt "support_bottom_extruder_nr label"
@@ -2923,7 +2863,7 @@ msgstr "Estrusore parte inferiore del supporto"
#: fdmprinter.def.json
msgctxt "support_bottom_extruder_nr description"
msgid "The extruder train to use for printing the floors of the support. This is used in multi-extrusion."
-msgstr "Treno estrusore utilizzato per la stampa delle parti inferiori del supporto. Utilizzato nell’estrusione multipla."
+msgstr "Blocco estrusore utilizzato per la stampa delle parti inferiori del supporto. Utilizzato nell’estrusione multipla."
#: fdmprinter.def.json
msgctxt "support_type label"
@@ -3143,12 +3083,12 @@ msgstr "È l'entità di offset (estensione dello strato) applicato a tutti i pol
#: fdmprinter.def.json
msgctxt "support_infill_sparse_thickness label"
msgid "Support Infill Layer Thickness"
-msgstr "Spessore dello strato di riempimento di supporto"
+msgstr "Spessore dello layer di riempimento di supporto"
#: fdmprinter.def.json
msgctxt "support_infill_sparse_thickness description"
msgid "The thickness per layer of support infill material. This value should always be a multiple of the layer height and is otherwise rounded."
-msgstr "Indica lo spessore per strato del materiale di riempimento del supporto. Questo valore deve sempre essere un multiplo dell’altezza dello strato e in caso contrario viene arrotondato."
+msgstr "Indica lo spessore per layer del materiale di riempimento del supporto. Questo valore deve sempre essere un multiplo dell’altezza del layer e in caso contrario viene arrotondato."
#: fdmprinter.def.json
msgctxt "gradual_support_infill_steps label"
@@ -3228,7 +3168,7 @@ msgstr "Spessore parte inferiore del supporto"
#: fdmprinter.def.json
msgctxt "support_bottom_height description"
msgid "The thickness of the support floors. This controls the number of dense layers that are printed on top of places of a model on which support rests."
-msgstr "Indica lo spessore delle parti inferiori del supporto. Questo controlla il numero di strati fitti stampati sulla sommità dei punti di un modello su cui appoggia un supporto."
+msgstr "Indica lo spessore delle parti inferiori del supporto. Questo controlla il numero di layers fitti stampati sulla sommità dei punti di un modello su cui appoggia un supporto."
#: fdmprinter.def.json
msgctxt "support_interface_skip_height label"
@@ -3450,6 +3390,16 @@ msgctxt "support_tower_roof_angle description"
msgid "The angle of a rooftop of a tower. A higher value results in pointed tower roofs, a lower value results in flattened tower roofs."
msgstr "L’angolo della parte superiore di una torre. Un valore superiore genera parti superiori appuntite, un valore inferiore, parti superiori piatte."
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down label"
+msgid "Drop Down Support Mesh"
+msgstr "Maglia supporto di discesa"
+
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down description"
+msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
+msgstr "Genera supporti ovunque sotto la maglia di supporto, in modo che in questa non vi siano punti a sbalzo."
+
#: fdmprinter.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
@@ -3528,7 +3478,7 @@ msgstr "Estrusore adesione piano di stampa"
#: fdmprinter.def.json
msgctxt "adhesion_extruder_nr description"
msgid "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion."
-msgstr "Il treno estrusore utilizzato per la stampa dello skirt/brim/raft. Utilizzato nell’estrusione multipla."
+msgstr "Il blocco estrusore utilizzato per la stampa dello skirt/brim/raft. Utilizzato nell’estrusione multipla."
#: fdmprinter.def.json
msgctxt "skirt_line_count label"
@@ -3620,37 +3570,37 @@ msgstr "Traferro del raft"
#: fdmprinter.def.json
msgctxt "raft_airgap description"
msgid "The gap between the final raft layer and the first layer of the model. Only the first layer is raised by this amount to lower the bonding between the raft layer and the model. Makes it easier to peel off the raft."
-msgstr "È l'interstizio tra lo strato di raft finale ed il primo strato del modello. Solo il primo strato viene sollevato di questo valore per ridurre l'adesione fra lo strato di raft e il modello. Ciò rende più facile rimuovere il raft."
+msgstr "È l'interstizio tra il layer del raft finale ed il primo layer del modello. Solo il primo layer viene sollevato di questo valore per ridurre l'adesione fra lo strato di raft e il modello. Ciò rende più facile rimuovere il raft."
#: fdmprinter.def.json
msgctxt "layer_0_z_overlap label"
msgid "Initial Layer Z Overlap"
-msgstr "Z Sovrapposizione Primo Strato"
+msgstr "Z Sovrapposizione Primo Layer"
#: fdmprinter.def.json
msgctxt "layer_0_z_overlap description"
msgid "Make the first and second layer of the model overlap in the Z direction to compensate for the filament lost in the airgap. All models above the first model layer will be shifted down by this amount."
-msgstr "Effettua il primo e secondo strato di sovrapposizione modello nella direzione Z per compensare il filamento perso nel traferro. Tutti i modelli sopra il primo strato del modello saranno spostati verso il basso di questa quantità."
+msgstr "Effettua il primo e secondo layer di sovrapposizione modello nella direzione Z per compensare il filamento perso nel vuoto. Tutti i modelli sopra il primo layer del modello saranno spostati verso il basso di questa quantità."
#: fdmprinter.def.json
msgctxt "raft_surface_layers label"
msgid "Raft Top Layers"
-msgstr "Strati superiori del raft"
+msgstr "Layers superiori del raft"
#: fdmprinter.def.json
msgctxt "raft_surface_layers description"
msgid "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."
-msgstr "Numero di strati sulla parte superiore del secondo strato del raft. Si tratta di strati completamente riempiti su cui poggia il modello. 2 strati danno come risultato una superficie superiore più levigata rispetto ad 1 solo strato."
+msgstr "Numero di layers sulla parte superiore del secondo layer del raft. Si tratta di layers completamente riempiti su cui poggia il modello. 2 layers danno come risultato una superficie superiore più levigata rispetto ad 1 solo layer."
#: fdmprinter.def.json
msgctxt "raft_surface_thickness label"
msgid "Raft Top Layer Thickness"
-msgstr "Spessore dello strato superiore del raft"
+msgstr "Spessore del layer superiore del raft"
#: fdmprinter.def.json
msgctxt "raft_surface_thickness description"
msgid "Layer thickness of the top raft layers."
-msgstr "È lo spessore degli strati superiori del raft."
+msgstr "È lo spessore dei layers superiori del raft."
#: fdmprinter.def.json
msgctxt "raft_surface_line_width label"
@@ -3675,17 +3625,17 @@ msgstr "Indica la distanza tra le linee che costituiscono la maglia superiore de
#: fdmprinter.def.json
msgctxt "raft_interface_thickness label"
msgid "Raft Middle Thickness"
-msgstr "Spessore dello strato intermedio del raft"
+msgstr "Spessore del layer intermedio del raft"
#: fdmprinter.def.json
msgctxt "raft_interface_thickness description"
msgid "Layer thickness of the middle raft layer."
-msgstr "È lo spessore dello strato intermedio del raft."
+msgstr "È lo spessore del layer intermedio del raft."
#: fdmprinter.def.json
msgctxt "raft_interface_line_width label"
msgid "Raft Middle Line Width"
-msgstr "Larghezza delle linee dello strato intermedio del raft"
+msgstr "Larghezza delle linee del layer intermedio del raft"
#: fdmprinter.def.json
msgctxt "raft_interface_line_width description"
@@ -3695,12 +3645,12 @@ msgstr "Indica la larghezza delle linee dello strato intermedio del raft. Una ma
#: fdmprinter.def.json
msgctxt "raft_interface_line_spacing label"
msgid "Raft Middle Spacing"
-msgstr "Spaziatura dello strato intermedio del raft"
+msgstr "Spaziatura del layer intermedio del raft"
#: fdmprinter.def.json
msgctxt "raft_interface_line_spacing description"
msgid "The distance between the raft lines for the middle raft layer. The spacing of the middle should be quite wide, while being dense enough to support the top raft layers."
-msgstr "Indica la distanza fra le linee dello strato intermedio del raft. La spaziatura dello strato intermedio deve essere abbastanza ampia, ma al tempo stesso sufficientemente fitta da sostenere gli strati superiori del raft."
+msgstr "Indica la distanza fra le linee del layer intermedio del raft. La spaziatura del layer intermedio deve essere abbastanza ampia, ma al tempo stesso sufficientemente fitta da sostenere i layers superiori del raft."
#: fdmprinter.def.json
msgctxt "raft_base_thickness label"
@@ -3710,17 +3660,17 @@ msgstr "Spessore della base del raft"
#: fdmprinter.def.json
msgctxt "raft_base_thickness description"
msgid "Layer thickness of the base raft layer. This should be a thick layer which sticks firmly to the printer build plate."
-msgstr "Indica lo spessore dello strato di base del raft. Questo strato deve essere spesso per aderire saldamente al piano di stampa."
+msgstr "Indica lo spessore del layer di base del raft. Questo layer deve essere spesso per aderire saldamente al piano di stampa."
#: fdmprinter.def.json
msgctxt "raft_base_line_width label"
msgid "Raft Base Line Width"
-msgstr "Larghezza delle linee dello strato di base del raft"
+msgstr "Larghezza delle linee del layer di base del raft"
#: fdmprinter.def.json
msgctxt "raft_base_line_width description"
msgid "Width of the lines in the base raft layer. These should be thick lines to assist in build plate adhesion."
-msgstr "Indica la larghezza delle linee dello strato di base del raft. Le linee di questo strato devono essere spesse per favorire l'adesione al piano di stampa."
+msgstr "Indica la larghezza delle linee del layer di base del raft. Le linee di questo layer devono essere spesse per favorire l'adesione al piano di stampa."
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing label"
@@ -3730,7 +3680,7 @@ msgstr "Spaziatura delle linee del raft"
#: fdmprinter.def.json
msgctxt "raft_base_line_spacing description"
msgid "The distance between the raft lines for the base raft layer. Wide spacing makes for easy removal of the raft from the build plate."
-msgstr "Indica la distanza tra le linee che costituiscono lo strato di base del raft. Un'ampia spaziatura favorisce la rimozione del raft dal piano di stampa."
+msgstr "Indica la distanza tra le linee che costituiscono il layer di base del raft. Un'ampia spaziatura favorisce la rimozione del raft dal piano di stampa."
#: fdmprinter.def.json
msgctxt "raft_speed label"
@@ -3750,7 +3700,7 @@ msgstr "Velocità di stampa parte superiore del raft"
#: fdmprinter.def.json
msgctxt "raft_surface_speed description"
msgid "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."
-msgstr "Indica la velocità alla quale sono stampati gli strati superiori del raft. La stampa di questi strati deve avvenire un po' più lentamente, in modo da consentire all'ugello di levigare lentamente le linee superficiali adiacenti."
+msgstr "Indica la velocità alla quale sono stampati i layers superiori del raft. La stampa di questi layers deve avvenire un po' più lentamente, in modo da consentire all'ugello di levigare lentamente le linee superficiali adiacenti."
#: fdmprinter.def.json
msgctxt "raft_interface_speed label"
@@ -3760,7 +3710,7 @@ msgstr "Velocità di stampa raft intermedio"
#: fdmprinter.def.json
msgctxt "raft_interface_speed description"
msgid "The speed at which the middle raft layer is printed. This should be printed quite slowly, as the volume of material coming out of the nozzle is quite high."
-msgstr "Indica la velocità alla quale viene stampato lo strato intermedio del raft. La sua stampa deve avvenire molto lentamente, considerato che il volume di materiale che fuoriesce dall'ugello è piuttosto elevato."
+msgstr "Indica la velocità alla quale viene stampato il layer intermedio del raft. La sua stampa deve avvenire molto lentamente, considerato che il volume di materiale che fuoriesce dall'ugello è piuttosto elevato."
#: fdmprinter.def.json
msgctxt "raft_base_speed label"
@@ -3790,7 +3740,7 @@ msgstr "Accelerazione di stampa parte superiore del raft"
#: fdmprinter.def.json
msgctxt "raft_surface_acceleration description"
msgid "The acceleration with which the top raft layers are printed."
-msgstr "Indica l’accelerazione alla quale vengono stampati gli strati superiori del raft."
+msgstr "Indica l’accelerazione alla quale vengono stampati i layers superiori del raft."
#: fdmprinter.def.json
msgctxt "raft_interface_acceleration label"
@@ -3800,7 +3750,7 @@ msgstr "Accelerazione di stampa raft intermedio"
#: fdmprinter.def.json
msgctxt "raft_interface_acceleration description"
msgid "The acceleration with which the middle raft layer is printed."
-msgstr "Indica l’accelerazione con cui viene stampato lo strato intermedio del raft."
+msgstr "Indica l’accelerazione con cui viene stampato il layer intermedio del raft."
#: fdmprinter.def.json
msgctxt "raft_base_acceleration label"
@@ -3810,7 +3760,7 @@ msgstr "Accelerazione di stampa della base del raft"
#: fdmprinter.def.json
msgctxt "raft_base_acceleration description"
msgid "The acceleration with which the base raft layer is printed."
-msgstr "Indica l’accelerazione con cui viene stampato lo strato di base del raft."
+msgstr "Indica l’accelerazione con cui viene stampato il layer di base del raft."
#: fdmprinter.def.json
msgctxt "raft_jerk label"
@@ -3830,7 +3780,7 @@ msgstr "Jerk di stampa parte superiore del raft"
#: fdmprinter.def.json
msgctxt "raft_surface_jerk description"
msgid "The jerk with which the top raft layers are printed."
-msgstr "Indica il jerk al quale vengono stampati gli strati superiori del raft."
+msgstr "Indica il jerk al quale vengono stampati i layers superiori del raft."
#: fdmprinter.def.json
msgctxt "raft_interface_jerk label"
@@ -3840,7 +3790,7 @@ msgstr "Jerk di stampa raft intermedio"
#: fdmprinter.def.json
msgctxt "raft_interface_jerk description"
msgid "The jerk with which the middle raft layer is printed."
-msgstr "Indica il jerk con cui viene stampato lo strato intermedio del raft."
+msgstr "Indica il jerk con cui viene stampato il layer intermedio del raft."
#: fdmprinter.def.json
msgctxt "raft_base_jerk label"
@@ -3850,7 +3800,7 @@ msgstr "Jerk di stampa della base del raft"
#: fdmprinter.def.json
msgctxt "raft_base_jerk description"
msgid "The jerk with which the base raft layer is printed."
-msgstr "Indica il jerk con cui viene stampato lo strato di base del raft."
+msgstr "Indica il jerk con cui viene stampato il layer di base del raft."
#: fdmprinter.def.json
msgctxt "raft_fan_speed label"
@@ -3870,7 +3820,7 @@ msgstr "Velocità della ventola per la parte superiore del raft"
#: fdmprinter.def.json
msgctxt "raft_surface_fan_speed description"
msgid "The fan speed for the top raft layers."
-msgstr "Indica la velocità di rotazione della ventola per gli strati superiori del raft."
+msgstr "Indica la velocità di rotazione della ventola per i layers superiori del raft."
#: fdmprinter.def.json
msgctxt "raft_interface_fan_speed label"
@@ -3880,7 +3830,7 @@ msgstr "Velocità della ventola per il raft intermedio"
#: fdmprinter.def.json
msgctxt "raft_interface_fan_speed description"
msgid "The fan speed for the middle raft layer."
-msgstr "Indica la velocità di rotazione della ventola per gli strati intermedi del raft."
+msgstr "Indica la velocità di rotazione della ventola per i layers intermedi del raft."
#: fdmprinter.def.json
msgctxt "raft_base_fan_speed label"
@@ -3890,7 +3840,7 @@ msgstr "Velocità della ventola per la base del raft"
#: fdmprinter.def.json
msgctxt "raft_base_fan_speed description"
msgid "The fan speed for the base raft layer."
-msgstr "Indica la velocità di rotazione della ventola per lo strato di base del raft."
+msgstr "Indica la velocità di rotazione della ventola per il layer di base del raft."
#: fdmprinter.def.json
msgctxt "dual label"
@@ -3905,7 +3855,7 @@ msgstr "Indica le impostazioni utilizzate per la stampa con estrusori multipli."
#: fdmprinter.def.json
msgctxt "prime_tower_enable label"
msgid "Enable Prime Tower"
-msgstr "Abilitazione torre di innesco"
+msgstr "Abilitazione Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_enable description"
@@ -3915,57 +3865,57 @@ msgstr "Stampa una torre accanto alla stampa che serve per innescare il material
#: fdmprinter.def.json
msgctxt "prime_tower_size label"
msgid "Prime Tower Size"
-msgstr "Dimensioni torre di innesco"
+msgstr "Dimensioni Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_size description"
msgid "The width of the prime tower."
-msgstr "Indica la larghezza della torre di innesco."
+msgstr "Indica la larghezza della Prime Tower."
#: fdmprinter.def.json
msgctxt "prime_tower_min_volume label"
msgid "Prime Tower Minimum Volume"
-msgstr "Volume minimo torre di innesco"
+msgstr "Volume minimo Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_min_volume description"
msgid "The minimum volume for each layer of the prime tower in order to purge enough material."
-msgstr "Il volume minimo per ciascuno strato della torre di innesco per scaricare materiale a sufficienza."
+msgstr "Il volume minimo per ciascun layer della Prime Tower per scaricare materiale a sufficienza."
#: fdmprinter.def.json
msgctxt "prime_tower_wall_thickness label"
msgid "Prime Tower Thickness"
-msgstr "Spessore torre di innesco"
+msgstr "Spessore Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_wall_thickness description"
msgid "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower."
-msgstr "Lo spessore della torre di innesco cava. Uno spessore superiore alla metà del volume minimo della torre di innesco genera una torre di innesco densa."
+msgstr "Lo spessore della Prime Tower cava. Uno spessore superiore alla metà del volume minimo della Prime Tower genera una torre di innesco densa."
#: fdmprinter.def.json
msgctxt "prime_tower_position_x label"
msgid "Prime Tower X Position"
-msgstr "Posizione X torre di innesco"
+msgstr "Posizione X Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_position_x description"
msgid "The x coordinate of the position of the prime tower."
-msgstr "Indica la coordinata X della posizione della torre di innesco."
+msgstr "Indica la coordinata X della posizione della Prime Tower."
#: fdmprinter.def.json
msgctxt "prime_tower_position_y label"
msgid "Prime Tower Y Position"
-msgstr "Posizione Y torre di innesco"
+msgstr "Posizione Y Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_position_y description"
msgid "The y coordinate of the position of the prime tower."
-msgstr "Indica la coordinata Y della posizione della torre di innesco."
+msgstr "Indica la coordinata Y della posizione della Prime Tower."
#: fdmprinter.def.json
msgctxt "prime_tower_flow label"
msgid "Prime Tower Flow"
-msgstr "Flusso torre di innesco"
+msgstr "Flusso Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_flow description"
@@ -3975,17 +3925,17 @@ msgstr "Determina la compensazione del flusso: la quantità di materiale estruso
#: fdmprinter.def.json
msgctxt "prime_tower_wipe_enabled label"
msgid "Wipe Inactive Nozzle on Prime Tower"
-msgstr "Ugello pulitura inattiva sulla torre di innesco"
+msgstr "Ugello pulitura inattiva sulla Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_wipe_enabled description"
msgid "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower."
-msgstr "Dopo la stampa della torre di innesco con un ugello, pulisce il materiale fuoriuscito dall’altro ugello sulla torre di innesco."
+msgstr "Dopo la stampa della Prime Tower con un ugello, pulisce il materiale fuoriuscito dall’altro ugello sulla Prime Tower."
#: fdmprinter.def.json
msgctxt "dual_pre_wipe label"
msgid "Wipe Nozzle After Switch"
-msgstr "Ugello pulitura dopo commutazione"
+msgstr "Pulitura ugello dopo commutazione"
#: fdmprinter.def.json
msgctxt "dual_pre_wipe description"
@@ -3995,12 +3945,12 @@ msgstr "Dopo la commutazione dell’estrusore, pulire il materiale fuoriuscito d
#: fdmprinter.def.json
msgctxt "prime_tower_purge_volume label"
msgid "Prime Tower Purge Volume"
-msgstr "Volume di scarico torre di innesco"
+msgstr "Volume di scarico Prime Tower"
#: fdmprinter.def.json
msgctxt "prime_tower_purge_volume description"
msgid "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle."
-msgstr "Quantità di filamento da scaricare durante la pulizia della torre di innesco. Lo scarico è utile per compensare il filamento perso per colatura durante l'inattività dell'ugello."
+msgstr "Quantità di filamento da scaricare durante la pulizia della Prime Tower. Lo scarico è utile per compensare il filamento perso per colatura durante l'inattività dell'ugello."
#: fdmprinter.def.json
msgctxt "ooze_shield_enabled label"
@@ -4082,16 +4032,6 @@ msgctxt "meshfix_keep_open_polygons description"
msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode."
msgstr "Di norma Cura cerca di \"ricucire\" piccoli fori nella maglia e di rimuovere le parti di uno strato che presentano grossi fori. Abilitando questa opzione, Cura mantiene quelle parti che non possono essere 'ricucite'. Questa opzione deve essere utilizzata come ultima risorsa quando non sia stato possibile produrre un corretto GCode in nessun altro modo."
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution label"
-msgid "Maximum Resolution"
-msgstr "Risoluzione massima"
-
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution description"
-msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
-msgstr "La dimensione minima di un segmento di linea dopo il sezionamento. Se tale dimensione aumenta, la maglia avrà una risoluzione inferiore. Questo può consentire alla stampante di mantenere la velocità per processare il g-code ed aumenterà la velocità di sezionamento eliminando i dettagli della maglia che non è comunque in grado di processare."
-
#: fdmprinter.def.json
msgctxt "multiple_mesh_overlap label"
msgid "Merged Meshes Overlap"
@@ -4150,7 +4090,7 @@ msgstr "Sequenza di stampa"
#: fdmprinter.def.json
msgctxt "print_sequence description"
msgid "Whether to print all models one layer at a time or to wait for one model to finish, before moving on to the next. One at a time mode is only possible if all models are separated in such a way that the whole print head can move in between and all models are lower than the distance between the nozzle and the X/Y axes."
-msgstr "Indica se stampare tutti i modelli uno strato alla volta o se attendere di terminare un modello prima di passare al successivo. La modalità 'uno per volta' è possibile solo se tutti i modelli sono separati in modo tale che l'intera testina di stampa possa muoversi tra di essi e se tutti i modelli sono più bassi della distanza tra l'ugello e gli assi X/Y."
+msgstr "Indica se stampare tutti i modelli uno layer alla volta o se attendere di terminare un modello prima di passare al successivo. La modalità 'uno per volta' è possibile solo se tutti i modelli sono separati in modo tale che l'intera testa di stampa possa muoversi tra di essi e se tutti i modelli sono più bassi della distanza tra l'ugello e gli assi X/Y."
#: fdmprinter.def.json
msgctxt "print_sequence option all_at_once"
@@ -4242,16 +4182,6 @@ msgctxt "support_mesh description"
msgid "Use this mesh to specify support areas. This can be used to generate support structure."
msgstr "Utilizzare questa maglia per specificare le aree di supporto. Può essere usata per generare una struttura di supporto."
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down label"
-msgid "Drop Down Support Mesh"
-msgstr "Maglia supporto di discesa"
-
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down description"
-msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
-msgstr "Rappresenta il supporto ovunque sotto la maglia di supporto, in modo che in questa non vi siano punti a sbalzo."
-
#: fdmprinter.def.json
msgctxt "anti_overhang_mesh label"
msgid "Anti Overhang Mesh"
@@ -4315,7 +4245,7 @@ msgstr "Estrusione relativa"
#: fdmprinter.def.json
msgctxt "relative_extrusion description"
msgid "Use relative extrusion rather than absolute extrusion. Using relative E-steps makes for easier post-processing of the Gcode. However, it's not supported by all printers and it may produce very slight deviations in the amount of deposited material compared to absolute E-steps. Irrespective of this setting, the extrusion mode will always be set to absolute before any Gcode script is output."
-msgstr "Utilizza l'estrusione relativa invece di quella assoluta. L'utilizzo di fasi E relative facilita la post-elaborazione del codice G. Tuttavia, questa impostazione non è supportata da tutte le stampanti e può causare deviazioni molto piccole nella quantità di materiale depositato rispetto alle fasi E assolute. Indipendentemente da questa impostazione, la modalità estrusione sarà sempre impostata su assoluta prima che venga generato uno script in codice G."
+msgstr "Utilizza l'estrusione relativa invece di quella assoluta. L'utilizzo di fasi E relative facilita la post-elaborazione del Gcode. Tuttavia, questa impostazione non è supportata da tutte le stampanti e può causare deviazioni molto piccole nella quantità di materiale depositato rispetto agli E-steps assoluti. Indipendentemente da questa impostazione, la modalità estrusione sarà sempre impostata su assoluta prima che venga generato uno script Gcode."
#: fdmprinter.def.json
msgctxt "experimental label"
@@ -4328,14 +4258,194 @@ msgid "experimental!"
msgstr "sperimentale!"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order label"
-msgid "Optimize Wall Printing Order"
-msgstr "Ottimizzazione sequenza di stampa pareti"
+msgctxt "support_tree_enable label"
+msgid "Tree Support"
+msgstr "Supporto ad albero"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order description"
-msgid "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."
-msgstr "Ottimizza l'ordine in cui vengono stampate le pareti in modo da ridurre le retrazioni e la distanza percorsa. L'abilitazione di questa funzione porta vantaggi per la maggior parte dei pezzi, ma alcuni potrebbero richiedere un maggior tempo di esecuzione, per cui si consiglia di confrontare i tempi di stampa stimati con e senza ottimizzazione."
+msgctxt "support_tree_enable description"
+msgid "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time."
+msgstr "Genera un supporto tipo albero con rami di sostegno della stampa. Questo può ridurre l’impiego di materiale e il tempo di stampa, ma aumenta notevolmente il tempo di sezionamento."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle label"
+msgid "Tree Support Branch Angle"
+msgstr "Angolo ramo supporto ad albero"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle description"
+msgid "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."
+msgstr "L’angolo dei rami. Utilizzare un angolo minore per renderli più verticali e più stabili. Utilizzare un angolo maggiore per avere una portata maggiore."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance label"
+msgid "Tree Support Branch Distance"
+msgstr "Distanza ramo supporto ad albero"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance description"
+msgid "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove."
+msgstr "La distanza tra i rami necessaria quando toccano il modello. Una distanza ridotta causa il contatto del supporto ad albero con il modello in più punti, generando migliore sovrapposizione ma rendendo più difficoltosa la rimozione del supporto."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter label"
+msgid "Tree Support Branch Diameter"
+msgstr "Diametro ramo supporto ad albero"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter description"
+msgid "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this."
+msgstr "Il diametro dei rami più sottili del supporto. I rami più spessi sono più resistenti. I rami verso la base avranno spessore maggiore."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle label"
+msgid "Tree Support Branch Diameter Angle"
+msgstr "Angolo diametro ramo supporto ad albero"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle description"
+msgid "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support."
+msgstr "L’angolo del diametro dei rami con il graduale ispessimento verso il fondo. Un angolo pari a 0 genera rami con spessore uniforme sull’intera lunghezza. Un angolo minimo può aumentare la stabilità del supporto ad albero."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution label"
+msgid "Tree Support Collision Resolution"
+msgstr "Risoluzione collisione supporto ad albero"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution description"
+msgid "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically."
+msgstr "Risoluzione per calcolare le collisioni per evitare di colpire il modello. L’impostazione a un valore basso genera alberi più accurati che si rompono meno sovente, ma aumenta notevolmente il tempo di sezionamento."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness label"
+msgid "Tree Support Wall Thickness"
+msgstr "Spessore delle pareti supporto ad albero"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness description"
+msgid "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "Lo spessore delle pareti dei rami del supporto ad albero. Le pareti più spesse hanno un tempo di stampa superiore ma non cadono facilmente."
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count label"
+msgid "Tree Support Wall Line Count"
+msgstr "Numero delle linee perimetrali supporto ad albero"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count description"
+msgid "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "Il numero di pareti dei rami del supporto ad albero. Le pareti più spesse hanno un tempo di stampa superiore ma non cadono facilmente."
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance label"
+msgid "Slicing Tolerance"
+msgstr "Tolleranza di sezionamento"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance description"
+msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
+msgstr "Modalità di sezionamento di strati con superfici diagonali. Le aree di uno strato possono essere generate in base al punto in cui la parte intermedia dello strato interseca la superficie (intermedia). In alternativa le aree di ciascuno strato possono ricadere all'interno del volume per tutta l'altezza dello strato (Esclusiva) ovvero possono cadere in qualsiasi punto all'interno dello strato (Inclusiva). La tolleranza esclusiva mantiene il maggior numero di dettagli, la tolleranza inclusiva è la più idonea, mentre la tolleranza intermedia richiede il minor tempo di processo."
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option middle"
+msgid "Middle"
+msgstr "Intermedia"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option exclusive"
+msgid "Exclusive"
+msgstr "Esclusiva"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option inclusive"
+msgid "Inclusive"
+msgstr "Inclusiva"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width label"
+msgid "Top Surface Skin Line Width"
+msgstr "Larghezza linea rivestimento superficie superiore"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width description"
+msgid "Width of a single line of the areas at the top of the print."
+msgstr "Larghezza di un singola linea delle aree nella parte superiore della stampa"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern label"
+msgid "Top Surface Skin Pattern"
+msgstr "Configurazione del rivestimento superficie superiore"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern description"
+msgid "The pattern of the top most layers."
+msgstr "Configurazione dei layers superiori."
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option lines"
+msgid "Lines"
+msgstr "Linee"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option concentric"
+msgid "Concentric"
+msgstr "Concentrica"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option zigzag"
+msgid "Zig Zag"
+msgstr "Zig Zag"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles label"
+msgid "Top Surface Skin Line Directions"
+msgstr "Direzioni linea rivestimento superficie superiore"
+
+#: fdmprinter.def.json
+msgctxt "roofing_angles description"
+msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
+msgstr "Un elenco di direzioni linee intere da usare quando i layers rivestimento superficie superiore utilizzano le linee o la configurazione zig zag. Gli elementi dall’elenco sono utilizzati in sequenza con il progredire dei layers e, al raggiungimento della fine dell’elenco, la sequenza ricomincia dall’inizio. Le voci elencate sono separate da virgole e l’intero elenco è racchiuso tra parentesi quadre. L’elenco predefinito è vuoto, vale a dire che utilizza i valori angolari predefiniti (45 e 135 gradi)."
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization label"
+msgid "Infill Travel Optimization"
+msgstr "Ottimizzazione spostamenti riempimento"
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization description"
+msgid "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased."
+msgstr "Quando abilitato, l’ordine di stampa delle linee di riempimento viene ottimizzato per ridurre la distanza percorsa. La riduzione del tempo di spostamento ottenuta dipende in particolare dal modello sezionato, dalla configurazione di riempimento, dalla densità, ecc. Si noti che, per alcuni modelli che hanno piccole aree di riempimento, il tempo di sezionamento del modello può aumentare notevolmente."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature label"
+msgid "Auto Temperature"
+msgstr "Temperatura automatica"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature description"
+msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
+msgstr "Modifica automaticamente la temperatura per ciascun layer con la velocità media del flusso per tale strato."
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph label"
+msgid "Flow Temperature Graph"
+msgstr "Grafico della temperatura del flusso"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph description"
+msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
+msgstr "Collegamento dei dati di flusso del materiale (in mm3 al secondo) alla temperatura (in °C)."
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution label"
+msgid "Maximum Resolution"
+msgstr "Risoluzione massima"
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution description"
+msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
+msgstr "La dimensione minima di un segmento di linea dopo il sezionamento. Se tale dimensione aumenta, la maglia avrà una risoluzione inferiore. Questo può consentire alla stampante di mantenere la velocità per processare il g-code ed aumenterà la velocità di sezionamento eliminando i dettagli della maglia che non è comunque in grado di processare."
#: fdmprinter.def.json
msgctxt "support_skip_some_zags label"
@@ -4815,7 +4925,7 @@ msgstr "Ritardo tra due segmenti orizzontali WP"
#: fdmprinter.def.json
msgctxt "wireframe_flat_delay description"
msgid "Delay time between two horizontal segments. Introducing such a delay can cause better adhesion to previous layers at the connection points, while too long delays cause sagging. Only applies to Wire Printing."
-msgstr "Indica il tempo di ritardo tra due segmenti orizzontali. Introducendo un tale ritardo si può ottenere una migliore adesione agli strati precedenti in corrispondenza dei punti di collegamento, mentre ritardi troppo prolungati provocano cedimenti. Applicabile solo alla funzione Wire Printing."
+msgstr "Indica il tempo di ritardo tra due segmenti orizzontali. Introducendo un tale ritardo si può ottenere una migliore adesione ai layers precedenti in corrispondenza dei punti di collegamento, mentre ritardi troppo prolungati provocano cedimenti. Applicabile solo alla funzione Wire Printing."
#: fdmprinter.def.json
msgctxt "wireframe_up_half_speed label"
@@ -4837,7 +4947,7 @@ msgstr "Dimensione dei nodi WP"
#: fdmprinter.def.json
msgctxt "wireframe_top_jump description"
msgid "Creates a small knot at the top of an upward line, so that the consecutive horizontal layer has a better chance to connect to it. Only applies to Wire Printing."
-msgstr "Crea un piccolo nodo alla sommità di una linea verticale verso l'alto, in modo che lo strato orizzontale consecutivo abbia una migliore possibilità di collegarsi ad essa. Applicabile solo alla funzione Wire Printing."
+msgstr "Crea un piccolo nodo alla sommità di una linea verticale verso l'alto, in modo che il layer orizzontale consecutivo abbia una migliore possibilità di collegarsi ad essa. Applicabile solo alla funzione Wire Printing."
#: fdmprinter.def.json
msgctxt "wireframe_fall_down label"
@@ -4867,7 +4977,7 @@ msgstr "Strategia WP"
#: fdmprinter.def.json
msgctxt "wireframe_strategy description"
msgid "Strategy for making sure two consecutive layers connect at each connection point. Retraction lets the upward lines harden in the right position, but may cause filament grinding. A knot can be made at the end of an upward line to heighten the chance of connecting to it and to let the line cool; however, it may require slow printing speeds. Another strategy is to compensate for the sagging of the top of an upward line; however, the lines won't always fall down as predicted."
-msgstr "Strategia per garantire il collegamento di due strati consecutivi ad ogni punto di connessione. La retrazione consente l'indurimento delle linee verticali verso l'alto nella giusta posizione, ma può causare la deformazione del filamento. È possibile realizzare un nodo all'estremità di una linea verticale verso l'alto per accrescere la possibilità di collegamento e lasciarla raffreddare; tuttavia ciò può richiedere velocità di stampa ridotte. Un'altra strategia consiste nel compensare il cedimento della parte superiore di una linea verticale verso l'alto; tuttavia le linee non sempre ricadono come previsto."
+msgstr "Strategia per garantire il collegamento di due layers consecutivi ad ogni punto di connessione. La retrazione consente l'indurimento delle linee verticali verso l'alto nella giusta posizione, ma può causare la deformazione del filamento. È possibile realizzare un nodo all'estremità di una linea verticale verso l'alto per accrescere la possibilità di collegamento e lasciarla raffreddare; tuttavia ciò può richiedere velocità di stampa ridotte. Un'altra strategia consiste nel compensare il cedimento della parte superiore di una linea verticale verso l'alto; tuttavia le linee non sempre ricadono come previsto."
#: fdmprinter.def.json
msgctxt "wireframe_strategy option compensate"
@@ -4934,6 +5044,46 @@ msgctxt "wireframe_nozzle_clearance description"
msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing."
msgstr "Indica la distanza tra l'ugello e le linee diagonali verso il basso. Un maggior gioco risulta in linee diagonali verso il basso con un minor angolo di inclinazione, cosa che a sua volta si traduce in meno collegamenti verso l'alto con lo strato successivo. Applicabile solo alla funzione Wire Printing."
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled label"
+msgid "Use adaptive layers"
+msgstr "Uso di layers adattivi"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled description"
+msgid "Adaptive layers computes the layer heights depending on the shape of the model."
+msgstr "I layers adattivi calcolano l’altezza dei layers in base alla forma del modello."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation label"
+msgid "Adaptive layers maximum variation"
+msgstr "Variazione massima layers adattivi"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation description"
+msgid "The maximum allowed height different from the base layer height in mm."
+msgstr "La differenza di altezza massima rispetto all’altezza del layer di base in mm."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step label"
+msgid "Adaptive layers variation step size"
+msgstr "Dimensione variazione layers adattivi"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step description"
+msgid "The difference in height of the next layer height compared to the previous one."
+msgstr "La differenza in altezza del layer successivo rispetto al precedente."
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold label"
+msgid "Adaptive layers threshold"
+msgstr "Soglia layers adattivi"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold description"
+msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
+msgstr "Soglia per l’utilizzo o meno di un layer di dimensioni minori. Questo numero è confrontato al valore dell’inclinazione più ripida di un layer."
+
#: fdmprinter.def.json
msgctxt "command_line_settings label"
msgid "Command Line Settings"
@@ -4942,7 +5092,7 @@ msgstr "Impostazioni riga di comando"
#: fdmprinter.def.json
msgctxt "command_line_settings description"
msgid "Settings which are only used if CuraEngine isn't called from the Cura frontend."
-msgstr "Impostazioni utilizzate solo se CuraEngine non è chiamato dalla parte anteriore di Cura."
+msgstr "Impostazioni utilizzate solo se CuraEngine non è chiamato dal frontend di Cura."
#: fdmprinter.def.json
msgctxt "center_object label"
@@ -4994,13 +5144,33 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "Matrice di rotazione da applicare al modello quando caricato dal file."
+#~ msgctxt "infill_offset_x description"
+#~ msgid "The infill pattern is offset this distance along the X axis."
+#~ msgstr "Il riempimento si scosta di questa distanza lungo l'asse X."
+
+#~ msgctxt "infill_offset_y description"
+#~ msgid "The infill pattern is offset this distance along the Y axis."
+#~ msgstr "Il riempimento si scosta di questa distanza lungo l'asse Y."
+
+#~ msgctxt "infill_overlap description"
+#~ msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
+#~ msgstr "Indica la quantità di sovrapposizione tra il riempimento e le pareti. Una leggera sovrapposizione consente il saldo collegamento delle pareti al riempimento."
+
+#~ msgctxt "skin_overlap description"
+#~ msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+#~ msgstr "Entità della sovrapposizione tra il rivestimento e le pareti espressa in percentuale della larghezza della linea. Una leggera sovrapposizione consente alle pareti di essere saldamente collegate al rivestimento. È una percentuale delle larghezze medie delle linee del rivestimento e della parete più interna."
+
+#~ msgctxt "material_bed_temperature description"
+#~ msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
+#~ msgstr "Indica la temperatura usata per il piano di stampa riscaldato. Se è 0, il piano non si riscalda per questa stampa."
+
#~ msgctxt "wall_x_extruder_nr label"
#~ msgid "Inner Walls Extruder"
#~ msgstr "Estrusore parete interna"
#~ msgctxt "infill_pattern description"
#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, octet, quarter cubic and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
-#~ msgstr "Configurazione del materiale di riempimento della stampa. Il riempimento a linea e zig zag su strati alternati riduce il costo del materiale. Le configurazioni a griglia, triangolo, a cubo, ottagonale, a quarto di cubo e concentrica comportano la stampa completa in ogni strato. Il riempimento a cubi, a quarto di cubo e a ottagonale cambia a ogni strato per consentire una distribuzione più uniforme della resistenza in ogni direzione."
+#~ msgstr "Configurazione del materiale di riempimento della stampa. Il riempimento a linea e zig zag su layers alternati riduce il costo del materiale. Le configurazioni a griglia, triangolo, a cubo, ottagonale, a quarto di cubo e concentrica comportano la stampa completa in ogni layer. Il riempimento a cubi, a quarto di cubo e a ottagonale cambia a ogni layer per consentire una distribuzione più uniforme della resistenza in ogni direzione."
#~ msgctxt "zig_zaggify_infill description"
#~ msgid "Connect the ends where the infill pattern meets the inner wall using a lines which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduces the effects on infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
@@ -5016,19 +5186,19 @@ msgstr "Matrice di rotazione da applicare al modello quando caricato dal file."
#~ msgctxt "z_offset_layer_0 label"
#~ msgid "Initial Layer Z Offset"
-#~ msgstr "Scostamento Z strato iniziale"
+#~ msgstr "Scostamento Z layer iniziale"
#~ msgctxt "z_offset_layer_0 description"
#~ msgid "The extruder is offset from the normal height of the first layer by this amount. It can be positive (raised) or negative (lowered). Some filament types adhere to the build plate better if the extruder is raised slightly."
-#~ msgstr "L'estrusore viene posizionato ad una distanza dall'altezza normale del primo strato pari al valore indicato. Questo scostamento può essere positivo (più in alto) o negativo (più in basso). Alcuni tipi di filamento aderiscono meglio al piano di stampa se l'estrusore viene leggermente sollevato."
+#~ msgstr "L'estrusore viene posizionato ad una distanza dall'altezza normale del primo layer pari al valore indicato. Questo scostamento può essere positivo (più in alto) o negativo (più in basso). Alcuni tipi di filamento aderiscono meglio al piano di stampa se l'estrusore viene leggermente sollevato."
#~ msgctxt "z_offset_taper_layers label"
#~ msgid "Z Offset Taper Layers"
-#~ msgstr "Scostamento Z strati di rastremazione"
+#~ msgstr "Scostamento Z layers di rastremazione"
#~ msgctxt "z_offset_taper_layers description"
#~ msgid "When non-zero, the Z offset is reduced to 0 over that many layers. A value of 0 means that the Z offset remains constant for all the layers in the print."
-#~ msgstr "Se diverso da zero, lo scostamento Z viene ridotto a 0 entro il numero di strati indicato. Un valore di 0 indica che lo scostamento Z rimane costante per tutti gli strati di stampa."
+#~ msgstr "Se diverso da zero, lo scostamento Z viene ridotto a 0 entro il numero di layers indicato. Un valore di 0 indica che lo scostamento Z rimane costante per tutti i layers di stampa."
#~ msgctxt "raft_smoothing description"
#~ msgid "This setting control how much inner corners in the raft 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."
@@ -5036,7 +5206,7 @@ msgstr "Matrice di rotazione da applicare al modello quando caricato dal file."
#~ msgctxt "infill_pattern description"
#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, tetrahedral and concentric patterns are fully printed every layer. Cubic and tetrahedral infill change with every layer to provide a more equal distribution of strength over each direction."
-#~ msgstr "Indica la configurazione del materiale di riempimento della stampa. Il riempimento a linea e a zig zag cambia direzione su strati alternati, riducendo il costo del materiale. Le configurazioni a griglia, triangolo, cubo, tetraedriche e concentriche sono stampate completamente su ogni strato. Il riempimento delle configurazioni cubiche e tetraedriche cambia ad ogni strato per fornire una distribuzione più uniforme della forza su ciascuna direzione."
+#~ msgstr "Indica la configurazione del materiale di riempimento della stampa. Il riempimento a linea e a zig zag cambia direzione su layers alternati, riducendo il costo del materiale. Le configurazioni a griglia, triangolo, cubo, tetraedriche e concentriche sono stampate completamente su ogni layers. Il riempimento delle configurazioni cubiche e tetraedriche cambia ad ogni strato per fornire una distribuzione più uniforme della forza su ciascuna direzione."
#~ msgctxt "infill_pattern option tetrahedral"
#~ msgid "Tetrahedral"
@@ -5044,27 +5214,27 @@ msgstr "Matrice di rotazione da applicare al modello quando caricato dal file."
#~ msgctxt "expand_skins_into_infill label"
#~ msgid "Expand Skins Into Infill"
-#~ msgstr "Prolunga rivestimenti esterni nel riempimento"
+#~ msgstr "Estende rivestimenti esterni nel riempimento"
#~ msgctxt "expand_skins_into_infill description"
#~ msgid "Expand skin areas of top and/or bottom skin of flat surfaces. By default, skins stop under the wall lines that surround infill but this can lead to holes appearing when the infill density is low. This setting extends the skins beyond the wall lines so that the infill on the next layer rests on skin."
-#~ msgstr "Prolunga le aree di rivestimento esterno superiori e/o inferiori delle superfici piatte. Per default, i rivestimenti esterni si interrompono sotto le linee delle pareti circostanti il riempimento, ma questo può generare la comparsa di fori quando la densità del riempimento è bassa. Questa impostazione prolunga i rivestimenti esterni oltre le linee delle pareti in modo che il riempimento sullo strato successivo appoggi sul rivestimento esterno."
+#~ msgstr "Estende le aree di rivestimento esterno superiori e/o inferiori delle superfici piatte. Per default, i rivestimenti esterni si interrompono sotto le linee delle pareti circostanti il riempimento, ma questo può generare la comparsa di fori quando la densità del riempimento è bassa. Questa impostazione prolunga i rivestimenti esterni oltre le linee delle pareti in modo che il riempimento sullo strato successivo appoggi sul rivestimento esterno."
#~ msgctxt "expand_upper_skins label"
#~ msgid "Expand Top Skins Into Infill"
-#~ msgstr "Prolunga rivestimenti esterni superiori nel riempimento"
+#~ msgstr "Estendi rivestimenti esterni superiori nel riempimento"
#~ msgctxt "expand_upper_skins description"
#~ msgid "Expand the top skin areas (areas with air above) so that they support infill above."
-#~ msgstr "Prolunga le aree di rivestimento esterno superiori (aree con aria al di sopra) in modo che supportino il riempimento sovrastante."
+#~ msgstr "Estendi le aree di rivestimento esterno superiori (aree con aria al di sopra) in modo che supportino il riempimento sovrastante."
#~ msgctxt "expand_lower_skins label"
#~ msgid "Expand Bottom Skins Into Infill"
-#~ msgstr "Prolunga rivestimenti esterni inferiori nel riempimento"
+#~ msgstr "Estendi rivestimenti esterni inferiori nel riempimento"
#~ msgctxt "expand_lower_skins description"
#~ msgid "Expand the bottom skin areas (areas with air below) so that they are anchored by the infill layers above and below."
-#~ msgstr "Prolunga aree rivestimento esterno inferiori (aree con aria al di sotto) in modo che siano ancorate dagli strati di riempimento sovrastanti e sottostanti."
+#~ msgstr "Estendi aree rivestimento esterno inferiori (aree con aria al di sotto) in modo che siano ancorate dai layers di riempimento sovrastanti e sottostanti."
#~ msgctxt "expand_skins_expand_distance description"
#~ msgid "The distance the skins are expanded into the infill. The default distance is enough to bridge the gap between the infill lines and will stop holes appearing in the skin where it meets the wall when the infill density is low. A smaller distance will often be sufficient."
@@ -5160,19 +5330,19 @@ msgstr "Matrice di rotazione da applicare al modello quando caricato dal file."
#~ msgctxt "expand_upper_skins label"
#~ msgid "Expand Upper Skins"
-#~ msgstr "Prolunga rivestimenti esterni superiori"
+#~ msgstr "Estendi rivestimenti esterni superiori"
#~ msgctxt "expand_upper_skins description"
#~ msgid "Expand upper skin areas (areas with air above) so that they support infill above."
-#~ msgstr "Prolunga le aree di rivestimento esterno superiori (aree con aria al di sopra) in modo che supportino il riempimento sovrastante."
+#~ msgstr "Estendi le aree di rivestimento esterno superiori (aree con aria al di sopra) in modo che supportino il riempimento sovrastante."
#~ msgctxt "expand_lower_skins label"
#~ msgid "Expand Lower Skins"
-#~ msgstr "Prolunga rivestimenti esterni inferiori"
+#~ msgstr "Estendi rivestimenti esterni inferiori"
#~ msgctxt "expand_lower_skins description"
#~ msgid "Expand lower skin areas (areas with air below) so that they are anchored by the infill layers above and below."
-#~ msgstr "Prolunga aree rivestimento esterno inferiori (aree con aria al di sotto) in modo che siano ancorate dagli strati di riempimento sovrastanti e sottostanti."
+#~ msgstr "Estendi aree rivestimento esterno inferiori (aree con aria al di sotto) in modo che siano ancorate dagli strati di riempimento sovrastanti e sottostanti."
#~ msgctxt "speed_support_interface description"
#~ msgid "The speed at which the roofs and bottoms of support are printed. Printing the them at lower speeds can improve overhang quality."
@@ -5196,7 +5366,7 @@ msgstr "Matrice di rotazione da applicare al modello quando caricato dal file."
#~ msgctxt "support_interface_extruder_nr description"
#~ msgid "The extruder train to use for printing the roofs and bottoms of the support. This is used in multi-extrusion."
-#~ msgstr "Il treno estrusore utilizzato per la stampa delle parti superiori e inferiori del supporto. Utilizzato nell’estrusione multipla."
+#~ msgstr "Il blocco estrusore utilizzato per la stampa delle parti superiori e inferiori del supporto. Utilizzato nell’estrusione multipla."
#~ msgctxt "support_bottom_stair_step_height description"
#~ msgid "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures."
@@ -5204,11 +5374,11 @@ msgstr "Matrice di rotazione da applicare al modello quando caricato dal file."
#~ msgctxt "support_bottom_height label"
#~ msgid "Support Bottom Thickness"
-#~ msgstr "Spessore degli strati inferiori del supporto"
+#~ msgstr "Spessore dei layers inferiori del supporto"
#~ msgctxt "support_bottom_height description"
#~ msgid "The thickness of the support bottoms. This controls the number of dense layers are printed on top of places of a model on which support rests."
-#~ msgstr "Indica lo spessore degli strati inferiori del supporto. Questo controlla il numero di strati fitti stampati sulla sommità dei punti di un modello su cui appoggia un supporto."
+#~ msgstr "Indica lo spessore dei layers inferiori del supporto. Questo controlla il numero di slayers fitti stampati sulla sommità dei punti di un modello su cui appoggia un supporto."
#~ msgctxt "support_interface_skip_height description"
#~ msgid "When checking where there's model above the support, take steps of the given height. Lower values will slice slower, while higher values may cause normal support to be printed in some places where there should have been support interface."
diff --git a/resources/i18n/ja_JP/cura.po b/resources/i18n/ja_JP/cura.po
index 01b39b3229..11e6f08883 100644
--- a/resources/i18n/ja_JP/cura.po
+++ b/resources/i18n/ja_JP/cura.po
@@ -1,24 +1,24 @@
# Cura
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-11-21 16:58+0100\n"
-"PO-Revision-Date: 2017-11-30 13:05+0100\n"
-"Last-Translator: \n"
-"Language-Team: TEAM\n"
-"Language: xx_XX\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
+"PO-Revision-Date: 2018-02-10 04:58+0900\n"
+"Last-Translator: Brule \n"
+"Language-Team: Japanese\n"
+"Language: ja_JP\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: Poedit 1.8.7.1\n"
+"X-Generator: Poedit 2.0.4\n"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:29
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
msgctxt "@action"
msgid "Machine Settings"
msgstr "プリンターの設定"
@@ -55,12 +55,11 @@ msgstr "Doodle3D Connectに接続する"
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:646
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:875
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:659
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:370
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:78
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:104
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:99
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:355
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:376
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:139
@@ -100,7 +99,7 @@ msgctxt "@info:tooltip"
msgid "Open the Doodle3D Connect web interface"
msgstr "Doodle3D Connect web interfaceを開く"
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:34
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "Changelogの表示"
@@ -117,78 +116,83 @@ msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "プロファイルが平らになり、アクティベートされました。"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:26
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "USBプリンティング"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "USBを使ってプリントする"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:29
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "USBを使ってプリントする"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:31
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "USBにて接続する"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:status"
msgid "Unable to start a new job because the printer is busy or not connected."
msgstr "新しいプリントジョブをはじめることができません。プリンターが使用中または接続できていません。"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:title"
msgid "Printer Unavailable"
msgstr "プリンターが利用できません"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:status"
msgid "This printer does not support USB printing because it uses UltiGCode flavor."
msgstr "UltiGCodeを使用中のため、USBからのプリントができません。"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:title"
msgid "USB Printing"
msgstr "USBプリント"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
msgctxt "@info:status"
msgid "Unable to start a new job because the printer does not support usb printing."
msgstr "USBでの印刷ができないため、新しいプリントジョブができません。"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1349
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:946
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1418
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1496
msgctxt "@info:title"
msgid "Warning"
msgstr "警告"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
msgctxt "@info"
msgid "Unable to update firmware because there are no printers connected."
msgstr "プリンターが未接続のため、ファームウェアをアップデートできません。"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
#, python-format
msgctxt "@info"
msgid "Could not find firmware required for the printer at %s."
msgstr "プリンター(%s)に必要なファームウェアを探せませんでした。"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
msgctxt "@info:title"
msgid "Printer Firmware"
msgstr "ファームウェア"
+#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Prepare"
+msgstr "準備する"
+
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Save to Removable Drive"
@@ -232,11 +236,11 @@ msgid "Could not save to removable drive {0}: {1}"
msgstr "リムーバブルドライブ{0}に保存することができませんでした: {1}"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:146
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:693
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:701
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:153
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1358
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:160
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1427
msgctxt "@info:title"
msgid "Error"
msgstr "エラー"
@@ -286,7 +290,7 @@ msgid "Removable Drive"
msgstr "リムーバブルドライブ"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:53
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:51
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "ネットワーク上のプリント"
@@ -400,110 +404,110 @@ msgctxt "@info:title"
msgid "Printer Status"
msgstr "プリンターのステータス"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:691
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No Printcore loaded in slot {0}"
msgstr "プリントコアがスロット{0}に入っていません。プリントジョブを開始できません。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:699
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No material loaded in slot {0}"
msgstr "フィラメントがスロット{0}に入っていません。プリントジョブを開始できません。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:709
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:710
#, python-brace-format
msgctxt "@label"
msgid "Not enough material for spool {0}."
msgstr "フィラメント{0}の残量が足りません。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:719
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:720
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "異なるプリントコアが入っています(Cura:{0}, プリンター{1})エクストルーダー{2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:733
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:734
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "異なるフィラメントが入っています(Cura:{0}, プリンター{1})エクストルーダー{2}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:741
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:742
#, python-brace-format
msgctxt "@label"
msgid "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer."
msgstr "プリントコア{0}が適切にキャリブレーションできていません。XYキャリブレーションをプリンターで行ってください。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:746
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "選択された構成にてプリントを開始してもいいですか。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:748
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "プリンターの設定、キャリブレーションとCuraの構成にミスマッチがあります。プリンターに設置されたプリントコア及びフィラメントを元にCuraをスライスすることで最良の印刷結果を出すことができます。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:753
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:754
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "ミスマッチの構成"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:864
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:865
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:258
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "新しいデータの送信 (temporarily) をブロックします、前のプリントジョブが送信中です。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "プリンターにプリントデータを送信中"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:title"
msgid "Sending Data"
msgstr "プリントデータを送信中"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:944
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
msgctxt "@info:status"
msgid "Unable to send data to printer. Is another job still active?"
msgstr "データをプリンターに送ることができません。他のプリントジョブは進行中ですか?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1085
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1087
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:196
msgctxt "@label:MonitorStatus"
msgid "Aborting print..."
msgstr "プリントを停止します…"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1091
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1093
msgctxt "@label:MonitorStatus"
msgid "Print aborted. Please check the printer"
msgstr "プリントを中止しました。プリンターを確認してください。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1097
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
msgctxt "@label:MonitorStatus"
msgid "Pausing print..."
msgstr "プリントを一時停止します…"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1101
msgctxt "@label:MonitorStatus"
msgid "Resuming print..."
msgstr "プリント再開します…"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1289
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "プリンターと同期する"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "Curaで設定しているプリンタ構成を使用されますか?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1295
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "プリンターのプリントコア及びフィラメントが現在のプロジェクトと異なります。最善な印刷結果のために、プリンタに装着しているプリントコア、フィラメントに合わせてスライスして頂くことをお勧めします。"
@@ -524,145 +528,204 @@ msgid "{printer_name} has finished printing '{job_name}'. Please collect the pri
msgstr "{printer_name}は ‘{job_name}’印刷を終了しました。造形物を確認し、ビルドプレートから取り出してください。"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:115
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:520
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:533
#, python-brace-format
msgid "{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing."
msgstr "{printer_name} は '{job_name}'.を印刷予定です。印刷を開始するためにジョブに合わせた構成に変更してください。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:277
msgctxt "@info:status"
msgid "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."
msgstr "新しいプリントジョブをお送りできません。この3Dプリンターは繋がっているUltimaker3のグループをホストするために設定されていません。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:410
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to send print job to group {cluster_name}."
msgstr "プリントジョブをグループに送ることができません。{cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:418
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:431
#, python-brace-format
msgctxt "@info:status"
msgid "Sent {file_name} to group {cluster_name}."
msgstr "グループに送信完了{file_name} {cluster_name}."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:436
msgctxt "@action:button"
msgid "Show print jobs"
msgstr "プリントジョブを見る"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:424
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:437
msgctxt "@info:tooltip"
msgid "Opens the print jobs interface in your browser."
msgstr "プリントジョブのインターフェイスをブラウザーで開く"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:502
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Unknown"
msgstr "不明"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:492
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
#, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "プリンター’{printer_name}’が’{job_name}’のプリントを終了しました。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:494
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:497
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:507
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:510
msgctxt "@info:status"
msgid "Print finished"
msgstr "プリント終了"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:522
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:525
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:535
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:538
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:282
msgctxt "@label:status"
msgid "Action required"
msgstr "アクションが必要です。"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:643
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:656
#, python-brace-format
msgctxt "@info:progress"
msgid "Sending {file_name} to group {cluster_name}"
msgstr "グループに送信中{file_name} {cluster_name}"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:19
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:17
msgctxt "@action"
msgid "Connect via Network"
msgstr "ネットワーク上にて接続"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:64
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Monitor"
+msgstr "モニター"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr "{machine_name} で利用可能な新しい機能があります。プリンターのファームウェアをアップデートしてください。"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:65
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:67
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "新しい利用可能な%sファームウェアのアップデートがあります。"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:68
msgctxt "@action:button"
msgid "How to update"
msgstr "アップデートの仕方"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:77
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:79
msgctxt "@info"
msgid "Could not access update information."
msgstr "必要なアップデートの情報にアクセスできません。"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:199
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
msgctxt "@info:status"
-msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
-msgstr "ソリッドワークスのファイルを開く際にエラーが発生しました!ソリッドワークスで、問題なく開くことができるか確認してください。"
+msgid "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself."
+msgstr "ソリッドワークスがファイルを開く際にエラーを報告しました。ソリッドワークス内で問題を解決することをお勧めします。"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:31
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
+msgctxt "@info:status"
+msgid ""
+"Found no models inside your drawing. Could you please check it's content again and make sure one part or assembly is inside?\n"
+"\n"
+" Thanks!."
+msgstr ""
+"図面の中にモデルが見つかりません。中身を確認し、パートかアセンブリーが中に入っていることを確認してください。\n"
+"\n"
+" 再確認をお願いします。"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
+msgctxt "@info:status"
+msgid ""
+"Found more then one part or assembly inside your drawing. We currently only support drawings with exactly one part or assembly inside.\n"
+"\n"
+"Sorry!"
+msgstr ""
+"図面の中にパートかアセンブリーが2個以上見つかりました。今のところ、本製品はパートかアセンブリーが1個の図面のみに対応しています。\n"
+"\n"
+"申し訳ありません。"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
msgctxt "@item:inlistbox"
msgid "SolidWorks part file"
msgstr "ソリッドワークスパートファイル"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:35
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:29
msgctxt "@item:inlistbox"
msgid "SolidWorks assembly file"
msgstr "ソリッドワークスアセンブリーファイル"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.py:21
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
+msgctxt "@item:inlistbox"
+msgid "SolidWorks drawing file"
+msgstr "ソリッドワークス図面ファイル"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"We could not find a valid installation of SolidWorks on your system. That means that either SolidWorks is not installed or you don't own an valid license. Please make sure that running SolidWorks itself works without issues and/or contact your ICT.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+"お客様へ\n"
+"システム上に正規のソリッドワークスがインストールされていません。つまり、ソリッドワークスがインストールされていないか、有効なライセンスが存在しません。ソリッドワークスだけを問題なく使用できるようになっているか確認するか、自社のIT部門にご相談ください。\n"
+"\n"
+"お願いいたします。\n"
+" - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"You are currently running this plugin on an operating system other than Windows. This plugin will only work on Windows with SolidWorks installed, including an valid license. Please install this plugin on a Windows machine with SolidWorks installed.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr ""
+"お客様へ\n"
+"このプラグインは現在Windows以外のOSで実行されています。このプラグインは、ソリッドワークスがインストールされたWindowsでしか動作しません。有効なライセンスも必要です。ソリッドワークスがインストールされたWindowsマシンにこのプラグインをインストールしてください。\n"
+"\n"
+"お願いいたします。\n"
+" - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
msgid "Configure"
msgstr "構成"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/CommonComReader.py:135
-#, python-format
-msgctxt "@info:status"
-msgid "Error while starting %s!"
-msgstr "%sを開始中にエラーが発生"
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
+msgid "Installation guide for SolidWorks macro"
+msgstr "ソリッドワークス・マクロのインストールガイド"
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
-msgid "Simulation view"
-msgstr "シミュレーションビュー"
+msgid "Layer view"
+msgstr "レイヤービュー"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "Curaはワイヤープリンティング設定中には正確にレイヤーを表示しません。"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:101
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
msgctxt "@info:title"
msgid "Simulation View"
msgstr "シミュレーションビュー"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:26
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:25
msgid "Modify G-Code"
msgstr "G-codeを修正"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
msgctxt "@info"
-msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
-msgstr "Curaが非特定なスライスされた数字を集めました。プレファレンス内で無効にできます。"
+msgid "Cura collects anonymized usage statistics."
+msgstr "Curaは、匿名化した利用統計を収集します。"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
msgctxt "@info:title"
@@ -671,14 +734,43 @@ msgstr "データを収集中"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
msgctxt "@action:button"
-msgid "Dismiss"
-msgstr "却下する"
+msgid "Allow"
+msgstr "許可"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+msgctxt "@action:tooltip"
+msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
+msgstr "Curaが匿名化した利用統計を送信することを許可し、Curaの将来の改善を優先的に行うことに貢献します。プレファレンスと設定の一部、Curaのバージョン、スライスしているモデルのハッシュが送信されます。"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+msgctxt "@action:button"
+msgid "Disable"
+msgstr "無効化"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
+msgctxt "@action:tooltip"
+msgid "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."
+msgstr "Curaが匿名化した利用統計を送信することを許可しません。プレファレンスで許可に変更することができます。"
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Cura 15.04 プロファイル"
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
+msgctxt "@item:inlistbox"
+msgid "Blender file"
+msgstr "Blenderファイル"
+
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
+msgctxt "@info:status"
+msgid ""
+"Could not export using \"{}\" quality!\n"
+"Felt back to \"{}\"."
+msgstr ""
+"\"{}\"品質を使用したエクスポートができませんでした!\n"
+"\"{}\"になりました。"
+
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
msgctxt "@item:inlistbox"
@@ -710,49 +802,49 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "GIF画像"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "選ばれたプリンターまたは選ばれたプリント構成が異なるため進行中の材料にてスライスを完了できません。"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:297
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:319
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:327
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:336
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:349
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:357
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "スライスできません。"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:296
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "現在の設定でスライスが完了できません。以下の設定にエラーがあります: {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:318
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:348
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr "モデル別の設定があるためスライスできません。1つまたは複数のモデルで以下の設定にエラーが発生しました:{error_labels}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:356
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "プライムタワーまたはプライム位置が無効なためスライスできません。"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:335
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:365
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "モデルのデータがビルトボリュームに入っていないためスライスできるものがありません。スケールやローテーションにて合うように設定してください。"
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "レイヤーを処理しています。"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:title"
msgid "Information"
msgstr "インフォメーション"
@@ -789,14 +881,14 @@ msgstr "Siemens NXプラグインファイルのコピーに失敗しました
msgid "Failed to install Siemens NX plugin. Could not set environment variable UGII_USER_DIR for Siemens NX."
msgstr "Siemens NXプラグインのインストールに失敗しました。Siemens NX用の環境変数UGII_USER_DIRが設定できませんでした。"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:585
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:165
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
msgctxt "@title:tab"
msgid "Recommended"
msgstr "推奨"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:169
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:595
msgctxt "@title:tab"
msgid "Custom"
msgstr "カスタム"
@@ -807,24 +899,24 @@ msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "3MF ファイル"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:126
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1142
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:159
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1185
msgctxt "@label"
msgid "Nozzle"
msgstr "ノズル"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:152
#, python-brace-format
msgctxt "@info:status"
msgid "Failed to get plugin ID from {0}"
msgstr "{0}からプラグインIDを取得することに失敗しました。"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:165
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:153
msgctxt "@info:tile"
msgid "Warning"
msgstr "警告"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:203
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:191
msgctxt "@window:title"
msgid "Plugin browser"
msgstr "プラグインブラウザー"
@@ -839,18 +931,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "Gファイル"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:314
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:321
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "G-codeを解析"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:316
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:426
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:323
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:464
msgctxt "@info:title"
msgid "G-code Details"
msgstr "G-codeの詳細"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:424
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:462
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "データファイルを送信する前に、プリンターとプリンターの構成設定にそのG-codeが適応しているか確認してください。G-codeの表示が適切でない場合があります。"
@@ -861,6 +953,16 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Curaプロファイル"
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Profile Assistant"
+msgstr "プロファイルアシスタント"
+
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
+msgctxt "@item:inlistbox"
+msgid "Profile Assistant"
+msgstr "プロファイルアシスタント"
+
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
msgctxt "@item:inlistbox"
msgid "3MF file"
@@ -892,142 +994,116 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "ビルドプレートを調整する"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "アウターウォール"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "インナーウォール"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
msgctxt "@tooltip"
msgid "Skin"
msgstr "スキン"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
msgctxt "@tooltip"
msgid "Infill"
msgstr "インフィル"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:93
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "サポートイルフィル"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:94
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "サポートインターフェイス"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:95
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
msgctxt "@tooltip"
msgid "Support"
msgstr "サポート"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:96
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
msgctxt "@tooltip"
msgid "Skirt"
msgstr "スカート"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:97
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
msgctxt "@tooltip"
msgid "Travel"
msgstr "移動"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
msgctxt "@tooltip"
msgid "Retractions"
msgstr "退却"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
msgctxt "@tooltip"
msgid "Other"
msgstr "他"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:199
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:231
msgctxt "@label unknown material"
msgid "Unknown"
msgstr "不明"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:284
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:318
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "スライス前ファイル {0}"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:469
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:440
msgctxt "@item:material"
msgid "No material loaded"
msgstr "フィラメントがロードされていません。"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:476
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:447
msgctxt "@item:material"
msgid "Unknown material"
msgstr "未確認のフィラメント"
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:30
-msgctxt "@info:status"
-msgid "Finding new location for objects"
-msgstr "造形物のために新しい位置を探索中"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:34
-msgctxt "@info:title"
-msgid "Finding Location"
-msgstr "位置確認"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:89
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
-msgctxt "@info:status"
-msgid "Unable to find a location within the build volume for all objects"
-msgstr "全ての造形物の造形サイズに対し、適切な位置が確認できません"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:90
-msgctxt "@info:title"
-msgid "Can't Find Location"
-msgstr "位置を確保できません。"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:431
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:113
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:437
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:120
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "すでに存在するファイルです。"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:432
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:114
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:438
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
msgid "The file {0} already exists. Are you sure you want to overwrite it?"
msgstr "{0} は既に存在します。ファイルを上書きしますか?"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:872
msgctxt "@label"
msgid "Custom"
msgstr "カスタム"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:819
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:876
msgctxt "@label"
msgid "Custom Material"
msgstr "カスタムフィラメント"
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:182
-msgctxt "@menuitem"
-msgid "Global"
-msgstr "グローバル"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:229
+#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:205
msgctxt "@menuitem"
msgid "Not overridden"
msgstr "上書きできません"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:124
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "選択されたフィラメントはプリンターとそのプリント構成に適応しておりません。"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:125
#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24
msgctxt "@info:title"
msgid "Incompatible Material"
@@ -1048,67 +1124,89 @@ msgctxt "@action"
msgid "Undo changing the material diameter."
msgstr "フィラメント直径を変更を取り消す"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:144
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to export profile to {0}: {1}"
msgstr "{0}にプロファイルを書き出すのに失敗しました: {1}"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:158
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Failed to export profile to {0}: Writer plugin reported failure."
msgstr " {0}にプロファイルを書き出すことに失敗しました。:ライタープラグイン失敗の報告"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:156
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:163
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Exported profile to {0}"
msgstr "{0}にプロファイルを書き出しました。"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:157
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:164
msgctxt "@info:title"
msgid "Export succeeded"
msgstr "書き出し完了"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:183
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:205
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:214
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:248
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:211
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:271
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
msgstr "{0}: {1}からプロファイルを取り込むことに失敗しました。"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:216
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:252
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:230
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "This profile {0} contains incorrect data, could not import it."
+msgstr "このプロファイル{0}には、正しくないデータが含まれていて、インポートできません。"
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "The machine defined in profile {0} doesn't match with your current machine, could not import it."
+msgstr "プロファイル{0}の中で定義されているマシンは、現在お使いのマシンと一致しませんので、インポートできませんでした。"
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "プロファイル {0}の取り込み完了"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:255
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:277
+#, python-brace-format
+msgctxt "@info:status"
+msgid "File {0} does not contain any valid profile."
+msgstr "ファイル{0}には、正しいプロファイルが含まれていません。"
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "プロファイル{0}は不特定なファイルまたは破損があります。"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:298
msgctxt "@label"
msgid "Custom profile"
msgstr "カスタムプロファイル"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:285
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "プロファイルはクオリティータイプが不足しています。"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:349
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "進行中のプリント構成にあったクオリティータイプ{0}が見つかりませんでした。"
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:46
+#, python-brace-format
+msgctxt "@label"
+msgid "Group #{group_nr}"
+msgstr "グループ #{group_nr}"
+
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
@@ -1119,142 +1217,170 @@ msgctxt "@info:title"
msgid "Build Volume"
msgstr "造形サイズ"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:34
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:25
msgctxt "@info:status"
msgid "Multiplying and placing objects"
msgstr "造形データを増やす、配置する。"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:35
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:26
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
msgctxt "@info:title"
msgid "Placing Object"
msgstr "造形データを配置"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:80
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:88
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:152
+msgctxt "@info:status"
+msgid "Unable to find a location within the build volume for all objects"
+msgstr "全ての造形物の造形サイズに対し、適切な位置が確認できません"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:29
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:64
+msgctxt "@info:status"
+msgid "Finding new location for objects"
+msgstr "造形物のために新しい位置を探索中"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:33
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:68
+msgctxt "@info:title"
+msgid "Finding Location"
+msgstr "位置確認"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:89
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:153
+msgctxt "@info:title"
+msgid "Can't Find Location"
+msgstr "位置を確保できません。"
+
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:81
msgctxt "@title:window"
msgid "Crash Report"
msgstr "クラッシュ報告"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:93
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:94
msgctxt "@label crash message"
msgid ""
-"
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+"
A fatal error has occurred. Please send us this Crash Report to fix the problem
\n"
"
Please use the \"Send report\" button to post a bug report automatically to our servers
"
+
+#~ msgctxt "@action:inmenu menubar:view"
+#~ msgid "&Reset camera position"
+#~ msgstr "&カメラ位置のリセット"
+
+#~ msgctxt "@title:menu menubar:file"
+#~ msgid "Save project"
+#~ msgstr "プロジェクトを保存"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Prepare"
+#~ msgstr "準備する"
+
+#~ msgctxt "@title:tab"
+#~ msgid "Monitor"
+#~ msgstr "モニター"
+
+#~ msgctxt "@label"
+#~ msgid "Check compatibility"
+#~ msgstr "互換性の確認"
+
+#~ msgctxt "description"
+#~ msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
+#~ msgstr "ソリッドワークスにて特定のファイルを開くことが可能です。その後変換され、Curaに取り込めます。"
+
#~ msgctxt "@label:status"
#~ msgid "Blocked"
#~ msgstr "ブロックされました"
@@ -4442,13 +5004,9 @@ msgstr "Curaプロファイルリーダー"
#~ msgid "To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware regularly. This can be done on the {machine_name} (when connected to the network) or via USB."
#~ msgstr "{machine_name}が最新の機能を得るために、定期的にファームウェアをアップデートすることをお勧めします。{machine_name}(ネットワーク上で接続)またはUSBにて行ってください。 "
-msgctxt "@item:inlistbox"
-msgid "Layer view"
-msgstr "レイヤービュー"
-
-msgctxt "@info:title"
-msgid "Layer View"
-msgstr "レイヤービュー"
+#~ msgctxt "@info:title"
+#~ msgid "Layer View"
+#~ msgstr "レイヤービュー"
#~ msgctxt "@menuitem"
#~ msgid "Browse plugins"
@@ -4550,6 +5108,6 @@ msgstr "レイヤービュー"
#~ msgid "Provides the Layer view."
#~ msgstr "レイヤービューを供給する"
-msgctxt "name"
-msgid "Layer View"
-msgstr "レイヤービュー"
+#~ msgctxt "name"
+#~ msgid "Layer View"
+#~ msgstr "レイヤービュー"
diff --git a/resources/i18n/ja_JP/fdmextruder.def.json.po b/resources/i18n/ja_JP/fdmextruder.def.json.po
index d1ff38fcf2..69c13959bd 100644
--- a/resources/i18n/ja_JP/fdmextruder.def.json.po
+++ b/resources/i18n/ja_JP/fdmextruder.def.json.po
@@ -2,12 +2,12 @@
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek , 2017.
-#
+#
msgid ""
msgstr ""
"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2017-08-02 16:53+0000\n"
"PO-Revision-Date: 2017-11-30 13:05+0100\n"
"Last-Translator: Brule\n"
"Language-Team: Brule\n"
diff --git a/resources/i18n/ja_JP/fdmprinter.def.json.po b/resources/i18n/ja_JP/fdmprinter.def.json.po
index 56ba768bc9..25c3b58fe3 100644
--- a/resources/i18n/ja_JP/fdmprinter.def.json.po
+++ b/resources/i18n/ja_JP/fdmprinter.def.json.po
@@ -1,14 +1,14 @@
-# Cura JSON setting files
-# Copyright (C) 2017 Ultimaker
+# Cura
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.0\n"
-"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
-"POT-Creation-Date: 2017-11-21 16:58+0000\n"
-"PO-Revision-Date: 2017-11-30 13:05+0100\n"
+"Project-Id-Version: Cura 3.2\n"
+"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
+"PO-Revision-Date: 2018-02-10 05:04+0900\n"
"Last-Translator: Brule\n"
"Language-Team: Brule\n"
"Language: ja_JP\n"
@@ -62,7 +62,9 @@ msgctxt "machine_start_gcode description"
msgid ""
"Gcode commands to be executed at the very start - separated by \n"
"."
-msgstr "Gcodeのコマンドは −で始まり\nで区切られます。"
+msgstr ""
+"Gcodeのコマンドは −で始まり\n"
+"で区切られます。"
#: fdmprinter.def.json
msgctxt "machine_end_gcode label"
@@ -75,7 +77,9 @@ msgctxt "machine_end_gcode description"
msgid ""
"Gcode commands to be executed at the very end - separated by \n"
"."
-msgstr "Gcodeのコマンドは −で始まり\nで区切られます。"
+msgstr ""
+"Gcodeのコマンドは −で始まり\n"
+"で区切られます。"
#: fdmprinter.def.json
msgctxt "material_guid label"
@@ -377,6 +381,16 @@ msgctxt "machine_gcode_flavor option Repetier"
msgid "Repetier"
msgstr "Repetier"
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract label"
+msgid "Firmware Retraction"
+msgstr "ファームウェア引き戻し"
+
+#: fdmprinter.def.json
+msgctxt "machine_firmware_retract description"
+msgid "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material."
+msgstr "材料を引き戻すためにG1コマンドのEプロパティーを使用する代わりにファームウェア引き戻しコマンド (G10/G11) を使用するかどうか。"
+
# msgstr "Repetier"
#: fdmprinter.def.json
msgctxt "machine_disallowed_areas label"
@@ -641,31 +655,6 @@ msgctxt "layer_height_0 description"
msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier."
msgstr "初期レイヤーの高さ(mm)。厚い初期層はビルドプレートへの接着を容易にする。"
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance label"
-msgid "Slicing Tolerance"
-msgstr "スライス公差"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance description"
-msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
-msgstr "表面を斜めにスライスする方法を指定します。レイヤーの領域は、レイヤーの中央がサーフェス(中央)と交差する位置に基づいて生成できます。また、各層は、レイヤーの高さを通してボリュームの内側に収まる領域を持つ(排他)か、またはレイヤー内の任意の場所内に収まる領域を持っています(包括)。排他は最も細かく、包括は最もフィットし、中間は時間がかかります。"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option middle"
-msgid "Middle"
-msgstr "中間"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option exclusive"
-msgid "Exclusive"
-msgstr "排他"
-
-#: fdmprinter.def.json
-msgctxt "slicing_tolerance option inclusive"
-msgid "Inclusive"
-msgstr "包括"
-
#: fdmprinter.def.json
msgctxt "line_width label"
msgid "Line Width"
@@ -706,17 +695,6 @@ msgctxt "wall_line_width_x description"
msgid "Width of a single wall line for all wall lines except the outermost one."
msgstr "一番外側のウォールラインを除くすべてのウォールラインのラインの幅。"
-#: fdmprinter.def.json
-msgctxt "roofing_line_width label"
-msgid "Top Surface Skin Line Width"
-msgstr "最上面のライン幅"
-
-# msgstr "上表面スキンの線幅"
-#: fdmprinter.def.json
-msgctxt "roofing_line_width description"
-msgid "Width of a single line of the areas at the top of the print."
-msgstr "プリントの上部の 線の幅。"
-
#: fdmprinter.def.json
msgctxt "skin_line_width label"
msgid "Top/Bottom Line Width"
@@ -907,46 +885,6 @@ msgctxt "roofing_layer_count description"
msgid "The number of top most skin layers. Usually only one top most layer is sufficient to generate higher quality top surfaces."
msgstr "上部表面のレイヤー数。通常一層で綺麗に出来上がります"
-#: fdmprinter.def.json
-msgctxt "roofing_pattern label"
-msgid "Top Surface Skin Pattern"
-msgstr "上部表面パターン"
-
-# msgstr "上層表面スキンパターン"
-#: fdmprinter.def.json
-msgctxt "roofing_pattern description"
-msgid "The pattern of the top most layers."
-msgstr "上層のパターン"
-
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option lines"
-msgid "Lines"
-msgstr "直線"
-
-# msgstr "線"
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option concentric"
-msgid "Concentric"
-msgstr "同心円"
-
-# msgstr "同心"
-#: fdmprinter.def.json
-msgctxt "roofing_pattern option zigzag"
-msgid "Zig Zag"
-msgstr "ジグザグ"
-
-# msgstr "ジグザグ"
-#: fdmprinter.def.json
-msgctxt "roofing_angles label"
-msgid "Top Surface Skin Line Directions"
-msgstr "最上面のラインの向き"
-
-# msgstr "上層表面スキンラインの方向"
-#: fdmprinter.def.json
-msgctxt "roofing_angles description"
-msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
-msgstr "トップ表面層に縦かジグザグパターンを利用する時に使用する整数の行方向のリスト。リスト内から順番に使われていき、リストの最後に達するとまた最初の設定値に戻ります。リストアイテムはカンマで区切られ、全体はカッコで括られています。デフォルトでは何も入っておらず、設定角度は (45 度と 135 度)になっています。"
-
#: fdmprinter.def.json
msgctxt "top_bottom_extruder_nr label"
msgid "Top/Bottom Extruder"
@@ -1079,6 +1017,17 @@ msgctxt "wall_0_inset description"
msgid "Inset applied to the path of the outer wall. If the outer wall is smaller than the nozzle, and printed after the inner walls, use this offset to get the hole in the nozzle to overlap with the inner walls instead of the outside of the model."
msgstr "外壁の経路にはめ込む。外壁がノズルよりも小さく、内壁の後に造形されている場合は、オフセットを使用して、ノズルの穴をモデルの外側ではなく内壁と重なるようにします。"
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order label"
+msgid "Optimize Wall Printing Order"
+msgstr "壁印刷順序の最適化"
+
+# msgstr "壁のプリントの順番を最適化する"
+#: fdmprinter.def.json
+msgctxt "optimize_wall_printing_order description"
+msgid "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."
+msgstr "撤回と移動距離を減らすために、壁のプリント順序を最適化します。ほとんどの部品がこの設定を有効にしている方が良い印刷結果につながりますが、実際には時間がかかることがありますので、最適化の有無に関わらず印刷時間を比較してください。"
+
#: fdmprinter.def.json
msgctxt "outer_inset_first label"
msgid "Outer Before Inner Walls"
@@ -1149,6 +1098,16 @@ msgctxt "fill_perimeter_gaps option everywhere"
msgid "Everywhere"
msgstr "全対象"
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps label"
+msgid "Filter Out Tiny Gaps"
+msgstr "小さなギャップのフィルターアウト"
+
+#: fdmprinter.def.json
+msgctxt "filter_out_tiny_gaps description"
+msgid "Filter out tiny gaps to reduce blobs on outside of model."
+msgstr "モデル外部の塊を減らすために小さなギャップをフィルターアウトします。"
+
#: fdmprinter.def.json
msgctxt "fill_outline_gaps label"
msgid "Print Thin Walls"
@@ -1220,7 +1179,9 @@ msgstr "ZシームX"
#: fdmprinter.def.json
msgctxt "z_seam_x description"
msgid "The X coordinate of the position near where to start printing each part in a layer."
-msgstr "レイヤー内の各印刷を開始するX座\n標の位置。"
+msgstr ""
+"レイヤー内の各印刷を開始するX座\n"
+"標の位置。"
#: fdmprinter.def.json
msgctxt "z_seam_y label"
@@ -1561,7 +1522,7 @@ msgstr "インフィルXオフセット"
#: fdmprinter.def.json
msgctxt "infill_offset_x description"
-msgid "The infill pattern is offset this distance along the X axis."
+msgid "The infill pattern is moved this distance along the X axis."
msgstr "インフィルパターンはX軸に沿ってこの距離を移動します。"
#: fdmprinter.def.json
@@ -1571,7 +1532,7 @@ msgstr "インフィルYオフセット"
#: fdmprinter.def.json
msgctxt "infill_offset_y description"
-msgid "The infill pattern is offset this distance along the Y axis."
+msgid "The infill pattern is moved this distance along the Y axis."
msgstr "インフィルパターンはY軸に沿ってこの距離を移動します。"
#: fdmprinter.def.json
@@ -1589,11 +1550,10 @@ msgctxt "infill_overlap label"
msgid "Infill Overlap Percentage"
msgstr "インフィル公差量"
-# msgstr "インフィルのオーバーラップ率"
#: fdmprinter.def.json
msgctxt "infill_overlap description"
-msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
-msgstr "インフィルと壁が交差する量、わずかな交差によって壁がインフィルにしっかりつながります。"
+msgid "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill."
+msgstr "インフィルと壁のオーバーラップ量 (インフィルライン幅に対する%)。少しのオーバーラップによって壁がインフィルにしっかりつながります。"
#: fdmprinter.def.json
msgctxt "infill_overlap_mm label"
@@ -1613,8 +1573,8 @@ msgstr "表面公差量"
#: fdmprinter.def.json
msgctxt "skin_overlap description"
-msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
-msgstr "表面と壁の交わる量。ラインの幅の%で設定。少しの接触でしっかりと繋がります。表面と内壁の交わる量の平均値になります。"
+msgid "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+msgstr "スキンと壁のオーバーラップ量 (スキンライン幅に対する%)。少しのオーバーラップによって壁がスキンにしっかりつながります。これは、スキンライン幅の平均ライン幅と最内壁の%です。"
#: fdmprinter.def.json
msgctxt "skin_overlap_mm label"
@@ -1676,7 +1636,9 @@ msgstr "インフィル優先"
#: fdmprinter.def.json
msgctxt "infill_before_walls description"
msgid "Print the infill before printing the walls. Printing the walls first may lead to more accurate walls, but overhangs print worse. Printing the infill first leads to sturdier walls, but the infill pattern might sometimes show through the surface."
-msgstr "壁より前にインフィルをプリントします はじめに壁をプリントするとより精密な壁になりますが、オーバーハングのプリントは悪化します\nはじめにインフィルをプリントすると丈夫な壁になりますが、インフィルの模様が時折表面から透けて表れます"
+msgstr ""
+"壁より前にインフィルをプリントします はじめに壁をプリントするとより精密な壁になりますが、オーバーハングのプリントは悪化します\n"
+"はじめにインフィルをプリントすると丈夫な壁になりますが、インフィルの模様が時折表面から透けて表れます"
#: fdmprinter.def.json
msgctxt "min_infill_area label"
@@ -1783,16 +1745,6 @@ msgctxt "material description"
msgid "Material"
msgstr "マテリアル"
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature label"
-msgid "Auto Temperature"
-msgstr "自動温度"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_dependent_temperature description"
-msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
-msgstr "その画層の平均流速で自動的にレイヤーごとに温度を変更します。"
-
#: fdmprinter.def.json
msgctxt "default_material_print_temperature label"
msgid "Default Printing Temperature"
@@ -1843,16 +1795,6 @@ msgctxt "material_final_print_temperature description"
msgid "The temperature to which to already start cooling down just before the end of printing."
msgstr "印刷終了直前に冷却を開始する温度。"
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph label"
-msgid "Flow Temperature Graph"
-msgstr "フロー温度グラフ"
-
-#: fdmprinter.def.json
-msgctxt "material_flow_temp_graph description"
-msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
-msgstr "マテリアルフロー(毎秒 3mm) と温度 (° c) をリンクします。"
-
#: fdmprinter.def.json
msgctxt "material_extrusion_cool_down_speed label"
msgid "Extrusion Cool Down Speed Modifier"
@@ -1870,8 +1812,8 @@ msgstr "ビルドプレート温度"
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
-msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
-msgstr "加熱式ビルドプレート温度。これが 0 の場合、ベッドは加熱しません。"
+msgid "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted."
+msgstr "加熱式ビルドプレート温度。これが 0 の場合、ベッド温度は調整されません。"
#: fdmprinter.def.json
msgctxt "material_bed_temperature_layer_0 label"
@@ -3586,6 +3528,17 @@ msgctxt "support_tower_roof_angle description"
msgid "The angle of a rooftop of a tower. A higher value results in pointed tower roofs, a lower value results in flattened tower roofs."
msgstr "タワーの屋上の角度。値が高いほど尖った屋根が得られ、値が低いほど屋根が平らになります。"
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down label"
+msgid "Drop Down Support Mesh"
+msgstr "サポートメッシュの下処理"
+
+# msgstr "ドロップダウンサポートメッシュ"
+#: fdmprinter.def.json
+msgctxt "support_mesh_drop_down description"
+msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
+msgstr "サポートメッシュの下のサポート材を全箇所に作ります、これはサポートメッシュ下にてオーバーハングしないようにするためです。"
+
#: fdmprinter.def.json
msgctxt "platform_adhesion label"
msgid "Build Plate Adhesion"
@@ -3599,7 +3552,7 @@ msgstr "密着性"
#: fdmprinter.def.json
msgctxt "prime_blob_enable label"
msgid "Enable Prime Blob"
-msgstr "プライムボルブを有効にする"
+msgstr "プライムブロブを有効にする"
# msgstr "プライムブロブを有効にする"
#: fdmprinter.def.json
@@ -3691,7 +3644,9 @@ msgctxt "skirt_gap description"
msgid ""
"The horizontal distance between the skirt and the first layer of the print.\n"
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
-msgstr "スカートと印刷の最初の層の間の水平距離。\nこれは最小距離です。複数のスカートラインがこの距離から外側に展開されます。"
+msgstr ""
+"スカートと印刷の最初の層の間の水平距離。\n"
+"これは最小距離です。複数のスカートラインがこの距離から外側に展開されます。"
#: fdmprinter.def.json
msgctxt "skirt_brim_minimal_length label"
@@ -4224,16 +4179,6 @@ msgctxt "meshfix_keep_open_polygons description"
msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode."
msgstr "通常、Curaはメッシュ内の小さな穴をスティッチし、大きな穴のあるレイヤーの部分を削除しようとします。このオプションを有効にすると、スティッチできない部分が保持されます。このオプションは、他のすべてが適切なGCodeを生成できない場合の最後の手段として使用する必要があります。"
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution label"
-msgid "Maximum Resolution"
-msgstr "最大解像度"
-
-#: fdmprinter.def.json
-msgctxt "meshfix_maximum_resolution description"
-msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
-msgstr "スライス後の線分の最小サイズ。これを増やすと、メッシュの解像度が低くなります。これにより、プリンタが g コードの処理速度に追いつくことができ、処理できないメッシュの詳細を取り除いてスライス速度を速めます。"
-
#: fdmprinter.def.json
msgctxt "multiple_mesh_overlap label"
msgid "Merged Meshes Overlap"
@@ -4389,17 +4334,6 @@ msgctxt "support_mesh description"
msgid "Use this mesh to specify support areas. This can be used to generate support structure."
msgstr "このメッシュを使用してサポート領域を指定します。これは、サポート構造を生成するために使用できます。"
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down label"
-msgid "Drop Down Support Mesh"
-msgstr "サポートメッシュの下処理"
-
-# msgstr "ドロップダウンサポートメッシュ"
-#: fdmprinter.def.json
-msgctxt "support_mesh_drop_down description"
-msgid "Make support everywhere below the support mesh, so that there's no overhang in the support mesh."
-msgstr "サポートメッシュの下のサポート材を全箇所に作ります、これはサポートメッシュ下にてオーバーハングしないようにするためです。"
-
#: fdmprinter.def.json
msgctxt "anti_overhang_mesh label"
msgid "Anti Overhang Mesh"
@@ -4478,15 +4412,200 @@ msgid "experimental!"
msgstr "実験的"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order label"
-msgid "Optimize Wall Printing Order"
-msgstr "壁印刷順序の最適化"
+msgctxt "support_tree_enable label"
+msgid "Tree Support"
+msgstr "ツリーサポート"
-# msgstr "壁のプリントの順番を最適化する"
#: fdmprinter.def.json
-msgctxt "optimize_wall_printing_order description"
-msgid "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."
-msgstr "撤回と移動距離を減らすために、壁のプリント順序を最適化します。ほとんどの部品がこの設定を有効にしている方が良い印刷結果につながりますが、実際には時間がかかることがありますので、最適化の有無に関わらず印刷時間を比較してください。"
+msgctxt "support_tree_enable description"
+msgid "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time."
+msgstr "プリントを支えるために枝のついた木のようなサポートを生成します。材料とプリント時間が減る可能性がありますが、スライス時間が大きく増加します。"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle label"
+msgid "Tree Support Branch Angle"
+msgstr "ツリーサポート枝角度"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_angle description"
+msgid "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."
+msgstr "枝の角度。枝を垂直で安定したものにするためには小さい角度を使用します。高さを得るためには大きい角度を使用します。"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance label"
+msgid "Tree Support Branch Distance"
+msgstr "ツリーサポート枝間隔"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_distance description"
+msgid "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove."
+msgstr "枝がモデルに接触するところで確保する枝の間隔。この間隔を小さくするとツリーサポートがモデルに接触する点が増え、支える効果が高まりますが、サポートの取り外しが難しくなります。"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter label"
+msgid "Tree Support Branch Diameter"
+msgstr "ツリーサポート枝直径"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter description"
+msgid "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this."
+msgstr "ツリーサポートの最も細い枝の直径。枝は太いほど丈夫です。基部に近いところでは、枝はこれよりも太くなります。"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle label"
+msgid "Tree Support Branch Diameter Angle"
+msgstr "ツリーサポート枝直径角度"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_branch_diameter_angle description"
+msgid "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support."
+msgstr "基部に向かって徐々に太くなる枝の直径の角度。角度が0の場合、枝の太さは全長にわたって同じになります。少し角度を付けると、ツリーサポートの安定性が高まります。"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution label"
+msgid "Tree Support Collision Resolution"
+msgstr "ツリーサポート衝突精細度"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_collision_resolution description"
+msgid "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically."
+msgstr "モデルに干渉しないようにする衝突計算の精細度。小さい値を設定すると、失敗の少ない正確なツリーが生成されますが、スライス時間は大きく増加します。"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness label"
+msgid "Tree Support Wall Thickness"
+msgstr "ツリーサポート壁厚"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_thickness description"
+msgid "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "ツリーサポートの枝の壁の厚さ。壁が厚いほどプリント時間が長くなりますが、崩れにくくなります。"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count label"
+msgid "Tree Support Wall Line Count"
+msgstr "ツリーサポートウォールライン数"
+
+#: fdmprinter.def.json
+msgctxt "support_tree_wall_count description"
+msgid "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
+msgstr "ツリーサポートの枝の壁の数。壁が厚いほどプリント時間が長くなりますが、崩れにくくなります。"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance label"
+msgid "Slicing Tolerance"
+msgstr "スライス公差"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance description"
+msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
+msgstr "表面を斜めにスライスする方法を指定します。レイヤーの領域は、レイヤーの中央がサーフェス(中央)と交差する位置に基づいて生成できます。また、各層は、レイヤーの高さを通してボリュームの内側に収まる領域を持つ(排他)か、またはレイヤー内の任意の場所内に収まる領域を持っています(包括)。排他は最も細かく、包括は最もフィットし、中間は時間がかかります。"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option middle"
+msgid "Middle"
+msgstr "中間"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option exclusive"
+msgid "Exclusive"
+msgstr "排他"
+
+#: fdmprinter.def.json
+msgctxt "slicing_tolerance option inclusive"
+msgid "Inclusive"
+msgstr "包括"
+
+#: fdmprinter.def.json
+msgctxt "roofing_line_width label"
+msgid "Top Surface Skin Line Width"
+msgstr "最上面のライン幅"
+
+# msgstr "上表面スキンの線幅"
+#: fdmprinter.def.json
+msgctxt "roofing_line_width description"
+msgid "Width of a single line of the areas at the top of the print."
+msgstr "プリントの上部の 線の幅。"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern label"
+msgid "Top Surface Skin Pattern"
+msgstr "上部表面パターン"
+
+# msgstr "上層表面スキンパターン"
+#: fdmprinter.def.json
+msgctxt "roofing_pattern description"
+msgid "The pattern of the top most layers."
+msgstr "上層のパターン"
+
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option lines"
+msgid "Lines"
+msgstr "直線"
+
+# msgstr "線"
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option concentric"
+msgid "Concentric"
+msgstr "同心円"
+
+# msgstr "同心"
+#: fdmprinter.def.json
+msgctxt "roofing_pattern option zigzag"
+msgid "Zig Zag"
+msgstr "ジグザグ"
+
+# msgstr "ジグザグ"
+#: fdmprinter.def.json
+msgctxt "roofing_angles label"
+msgid "Top Surface Skin Line Directions"
+msgstr "最上面のラインの向き"
+
+# msgstr "上層表面スキンラインの方向"
+#: fdmprinter.def.json
+msgctxt "roofing_angles description"
+msgid "A list of integer line directions to use when the top surface skin layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
+msgstr "トップ表面層に縦かジグザグパターンを利用する時に使用する整数の行方向のリスト。リスト内から順番に使われていき、リストの最後に達するとまた最初の設定値に戻ります。リストアイテムはカンマで区切られ、全体はカッコで括られています。デフォルトでは何も入っておらず、設定角度は (45 度と 135 度)になっています。"
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization label"
+msgid "Infill Travel Optimization"
+msgstr "インフィル移動最適化"
+
+#: fdmprinter.def.json
+msgctxt "infill_enable_travel_optimization description"
+msgid "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased."
+msgstr "有効化すると、移動距離が減少するようにインフィルラインをプリントする順序が最適化されます。移動時間の削減は、スライスするモデル、インフィルパターン、密度などに大きく依存します。特に、インフィルを行う小さなエリアが多数あるモデルの場合、モデルをスライスする時間が大きく増えることがあります。"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature label"
+msgid "Auto Temperature"
+msgstr "自動温度"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_dependent_temperature description"
+msgid "Change the temperature for each layer automatically with the average flow speed of that layer."
+msgstr "その画層の平均流速で自動的にレイヤーごとに温度を変更します。"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph label"
+msgid "Flow Temperature Graph"
+msgstr "フロー温度グラフ"
+
+#: fdmprinter.def.json
+msgctxt "material_flow_temp_graph description"
+msgid "Data linking material flow (in mm3 per second) to temperature (degrees Celsius)."
+msgstr "マテリアルフロー(毎秒 3mm) と温度 (° c) をリンクします。"
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution label"
+msgid "Maximum Resolution"
+msgstr "最大解像度"
+
+#: fdmprinter.def.json
+msgctxt "meshfix_maximum_resolution description"
+msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
+msgstr "スライス後の線分の最小サイズ。これを増やすと、メッシュの解像度が低くなります。これにより、プリンタが g コードの処理速度に追いつくことができ、処理できないメッシュの詳細を取り除いてスライス速度を速めます。"
#: fdmprinter.def.json
msgctxt "support_skip_some_zags label"
@@ -5097,6 +5216,46 @@ msgctxt "wireframe_nozzle_clearance description"
msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing."
msgstr "ノズルと水平方向に下向きの線間の距離。大きな隙間がある場合、急な角度で斜め下方線となり、次の層が上方接続しずらくなる。ワイヤ印刷にのみ適用されます。"
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled label"
+msgid "Use adaptive layers"
+msgstr "適応レイヤーの使用"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_enabled description"
+msgid "Adaptive layers computes the layer heights depending on the shape of the model."
+msgstr "適応レイヤーは、レイヤーの高さをモデルの形状に合わせて計算します。"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation label"
+msgid "Adaptive layers maximum variation"
+msgstr "適応レイヤー最大差分"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation description"
+msgid "The maximum allowed height different from the base layer height in mm."
+msgstr "基準レイヤー高さと比較して許容される最大の高さ (mm)。"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step label"
+msgid "Adaptive layers variation step size"
+msgstr "適応レイヤー差分ステップサイズ"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_variation_step description"
+msgid "The difference in height of the next layer height compared to the previous one."
+msgstr "次のレイヤーの高さを前のレイヤーの高さと比べた差。"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold label"
+msgid "Adaptive layers threshold"
+msgstr "適応レイヤー閾値"
+
+#: fdmprinter.def.json
+msgctxt "adaptive_layer_height_threshold description"
+msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
+msgstr "小さいレイヤーを使用するかどうかの閾値。この値が、レイヤー中の最も急な斜面のタンジェントと比較されます。"
+
#: fdmprinter.def.json
msgctxt "command_line_settings label"
msgid "Command Line Settings"
@@ -5157,6 +5316,27 @@ msgctxt "mesh_rotation_matrix description"
msgid "Transformation matrix to be applied to the model when loading it from file."
msgstr "ファイルから読み込むときに、モデルに適用するトランスフォーメーションマトリックス。"
+#~ msgctxt "infill_offset_x description"
+#~ msgid "The infill pattern is offset this distance along the X axis."
+#~ msgstr "インフィルパターンはX軸に沿ってこの距離を移動します。"
+
+#~ msgctxt "infill_offset_y description"
+#~ msgid "The infill pattern is offset this distance along the Y axis."
+#~ msgstr "インフィルパターンはY軸に沿ってこの距離を移動します。"
+
+# msgstr "インフィルのオーバーラップ率"
+#~ msgctxt "infill_overlap description"
+#~ msgid "The amount of overlap between the infill and the walls. A slight overlap allows the walls to connect firmly to the infill."
+#~ msgstr "インフィルと壁が交差する量、わずかな交差によって壁がインフィルにしっかりつながります。"
+
+#~ msgctxt "skin_overlap description"
+#~ msgid "The amount of overlap between the skin and the walls as a percentage of the line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
+#~ msgstr "表面と壁の交わる量。ラインの幅の%で設定。少しの接触でしっかりと繋がります。表面と内壁の交わる量の平均値になります。"
+
+#~ msgctxt "material_bed_temperature description"
+#~ msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
+#~ msgstr "加熱式ビルドプレート温度。これが 0 の場合、ベッドは加熱しません。"
+
#~ msgctxt "infill_pattern description"
#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, octet, quarter cubic and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
#~ msgstr "印刷物のインフィルのパターン。線とジグザグのインフィルはレイヤーごとに交互に方向を変え、材料費を削減します。グリッド、三角形、キュービック、オクテット、クォーターキュービック、同心円のパターンは、すべてのレイヤーにて完全に印刷されます。キュービック、クォーターキュービック、オクテットのインフィルは各レイヤーごとに変化し、各方向の強度が均等になるように分布します。"
diff --git a/resources/i18n/ko_KR/cura.po b/resources/i18n/ko_KR/cura.po
index a72274dc2c..f63d7ba64f 100644
--- a/resources/i18n/ko_KR/cura.po
+++ b/resources/i18n/ko_KR/cura.po
@@ -1,16 +1,16 @@
# Cura
-# Copyright (C) 2017 Ultimaker
+# Copyright (C) 2018 Ultimaker
# This file is distributed under the same license as the Cura package.
-# Ruben Dulek , 2017.
-#
+# Ruben Dulek , 2018.
+#
msgid ""
msgstr ""
-"Project-Id-Version: Cura 3.1\n"
+"Project-Id-Version: Cura 3.2\n"
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
-"POT-Creation-Date: 2017-11-21 16:58+0100\n"
-"PO-Revision-Date: 2017-11-30 13:05+0100\n"
-"Last-Translator: Brule\n"
-"Language-Team: Brule\n"
+"POT-Creation-Date: 2018-01-29 09:48+0000\n"
+"PO-Revision-Date: 2018-02-05 13:25+0100\n"
+"Last-Translator: Bothof \n"
+"Language-Team: Korean\n"
"Language: ko_KR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -18,7 +18,7 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.0.4\n"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:29
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
msgctxt "@action"
msgid "Machine Settings"
msgstr "기계 설정"
@@ -55,12 +55,11 @@ msgstr "Doodle3D Connect에 연결"
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:87
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:155
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:646
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:875
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:659
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:370
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrintWindow.qml:78
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:104
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:99
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:355
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:188
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:376
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:139
@@ -100,7 +99,7 @@ msgctxt "@info:tooltip"
msgid "Open the Doodle3D Connect web interface"
msgstr "Doodle3D Connect 웹 인터페이스 열기"
-#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:34
+#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.py:33
msgctxt "@item:inmenu"
msgid "Show Changelog"
msgstr "변경 내역 표시"
@@ -115,78 +114,83 @@ msgctxt "@info:status"
msgid "Profile has been flattened & activated."
msgstr "프로필이 병합되고 활성화되었습니다."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:26
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
msgctxt "@item:inmenu"
msgid "USB printing"
msgstr "USB 인쇄"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:27
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print via USB"
msgstr "USB를 통해 인쇄"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:28
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:29
msgctxt "@info:tooltip"
msgid "Print via USB"
msgstr "USB를 통해 인쇄"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:31
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "USB를 통해 연결"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:status"
msgid "Unable to start a new job because the printer is busy or not connected."
msgstr "프린터가 사용 중이거나 연결되어 있지 않아 새 작업을 시작할 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:154
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:183
msgctxt "@info:title"
msgid "Printer Unavailable"
msgstr "프린터 사용 불가"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:status"
msgid "This printer does not support USB printing because it uses UltiGCode flavor."
msgstr "이 프린터는 UltiGCode flavor를 사용하기 때문에 USB 인쇄를 지원하지 않습니다."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:457
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:485
msgctxt "@info:title"
msgid "USB Printing"
msgstr "USB 인쇄"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
msgctxt "@info:status"
msgid "Unable to start a new job because the printer does not support usb printing."
msgstr "프린터가 USB 인쇄를 지원하지 않기 때문에 새 작업을 시작할 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:461
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:146
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:158
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1349
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:946
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1418
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1496
msgctxt "@info:title"
msgid "Warning"
msgstr "경고"
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:108
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:103
msgctxt "@info"
msgid "Unable to update firmware because there are no printers connected."
msgstr "프린터가 연결되어 있지 않으므로 펌웨어를 업데이트 할 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
#, python-format
msgctxt "@info"
msgid "Could not find firmware required for the printer at %s."
msgstr "프린터에 필요한 펌웨어를 찾을 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:122
+#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDeviceManager.py:117
msgctxt "@info:title"
msgid "Printer Firmware"
msgstr "프린터 펌웨어"
+#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Prepare"
+msgstr "준비"
+
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Save to Removable Drive"
@@ -230,11 +234,11 @@ msgid "Could not save to removable drive {0}: {1}"
msgstr "이동식 드라이브에 저장할 수 없습니다 :"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:132
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:146
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:693
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:701
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:153
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1358
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:160
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1427
msgctxt "@info:title"
msgid "Error"
msgstr "에러"
@@ -284,7 +288,7 @@ msgid "Removable Drive"
msgstr "이동식 드라이브"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:109
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:53
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:51
msgctxt "@action:button Preceded by 'Ready to'."
msgid "Print over network"
msgstr "네트워크를 통해 인쇄"
@@ -398,110 +402,110 @@ msgctxt "@info:title"
msgid "Printer Status"
msgstr "프린터 상태"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:691
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:692
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No Printcore loaded in slot {0}"
msgstr "새 인쇄 작업을 시작할 수 없습니다. 슬롯 {0}에로드 된 Printcore가 없습니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:699
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:700
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to start a new print job. No material loaded in slot {0}"
msgstr "새 인쇄 작업을 시작할 수 없습니다. 슬롯 {0}에로드 된 자료 없음"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:709
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:710
#, python-brace-format
msgctxt "@label"
msgid "Not enough material for spool {0}."
msgstr "스풀 {0}의 재료가 충분하지 않습니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:719
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:720
#, python-brace-format
msgctxt "@label"
msgid "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "압출기 {2}에 대해 선택된 다른 PrintCore (Cura : {0}, Printer : {1})"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:733
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:734
#, python-brace-format
msgctxt "@label"
msgid "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}"
msgstr "압출기 {2}에 대해 선택된 다른 재료 (Cura : {0}, Printer : {1})"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:741
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:742
#, python-brace-format
msgctxt "@label"
msgid "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer."
msgstr "PrintCore가 올바르게 조정되지 않았습니다. XY 교정은 프린터에서 수행해야합니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:746
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
msgctxt "@label"
msgid "Are you sure you wish to print with the selected configuration?"
msgstr "선택한 구성으로 인쇄 하시겠습니까?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:747
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:748
msgctxt "@label"
msgid "There is a mismatch between the configuration or calibration of the printer and Cura. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "프린터 및 Cura의 구성 또는 교정간에 불일치가 있습니다. 최상의 결과를 얻으려면 프린터에 삽입 된 PrintCores 및 재료를 항상 슬라이스하십시오."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:753
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:754
msgctxt "@window:title"
msgid "Mismatched configuration"
msgstr "일치하지 않는 구성"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:864
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:262
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:865
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:258
msgctxt "@info:status"
msgid "Sending new jobs (temporarily) blocked, still sending the previous print job."
msgstr "새 작업 전송 (일시적)이 차단되어 이전 인쇄 작업을 계속 보냅니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:status"
msgid "Sending data to printer"
msgstr "프린터로 데이터 보내기"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:873
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:874
msgctxt "@info:title"
msgid "Sending Data"
msgstr "데이터 전송 중"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:944
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:945
msgctxt "@info:status"
msgid "Unable to send data to printer. Is another job still active?"
msgstr "프린터로 데이터를 보낼 수 없습니다. 다른 작업이 여전히 작동중인가요?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1085
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1087
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:196
msgctxt "@label:MonitorStatus"
msgid "Aborting print..."
msgstr "인쇄 중단 중 ..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1091
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1093
msgctxt "@label:MonitorStatus"
msgid "Print aborted. Please check the printer"
msgstr "인쇄가 중단되었습니다. 프린터를 확인하십시오"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1097
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
msgctxt "@label:MonitorStatus"
msgid "Pausing print..."
msgstr "인쇄 일시 중지 중 ..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1099
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1101
msgctxt "@label:MonitorStatus"
msgid "Resuming print..."
msgstr "인쇄 재개 중 ..."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1289
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
msgctxt "@window:title"
msgid "Sync with your printer"
msgstr "프린터와 동기화"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1291
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
msgctxt "@label"
msgid "Would you like to use your current printer configuration in Cura?"
msgstr "Cura에서 현재 프린터 구성을 사용 하시겠습니까?"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1293
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:1295
msgctxt "@label"
msgid "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."
msgstr "프린터의 PrintCores 및 / 또는 자료는 현재 프로젝트 내의 자료와 다릅니다. 최상의 결과를 얻으려면 프린터에 삽입 된 PrintCores 및 재료를 항상 슬라이스하십시오."
@@ -522,145 +526,188 @@ msgid "{printer_name} has finished printing '{job_name}'. Please collect the pri
msgstr "인쇄를 완료했습니다. 인쇄물을 수거하고 빌드 플레이트를 지우십시오."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:115
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:520
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:533
#, python-brace-format
msgid "{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing."
msgstr "?을 인쇄하기 위해 예약되어 있습니다. 인쇄를 시작하려면 프린터의 구성을 작업에 맞게 변경하십시오."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:278
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:277
msgctxt "@info:status"
msgid "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."
msgstr "새로운 인쇄 작업을 보낼 수 없습니다 :이 3D 프린터는 아직 연결된 Ultimaker 3 프린터 그룹을 호스트하도록 설정되지 않았습니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:410
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to send print job to group {cluster_name}."
msgstr "{cluster_name} 그룹에 인쇄 작업을 보낼 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:418
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:431
#, python-brace-format
msgctxt "@info:status"
msgid "Sent {file_name} to group {cluster_name}."
msgstr "파일이름을 cluster_name 그룹에 보냈습니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:423
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:436
msgctxt "@action:button"
msgid "Show print jobs"
msgstr "인쇄 작업 표시"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:424
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:437
msgctxt "@info:tooltip"
msgid "Opens the print jobs interface in your browser."
msgstr "브라우저에서 인쇄 작업 인터페이스를 엽니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:489
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:502
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Unknown"
-msgstr "알 수 없는"
+msgstr "알 수 없음"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:492
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
#, fuzzy, python-brace-format
msgctxt "@info:status"
msgid "Printer '{printer_name}' has finished printing '{job_name}'."
msgstr "프린터가 인쇄를 완료했습니다."
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:494
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:497
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:507
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:510
msgctxt "@info:status"
msgid "Print finished"
msgstr "프린트가 완료됐습니다"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:522
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:525
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:535
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:538
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:282
msgctxt "@label:status"
msgid "Action required"
msgstr "필요한 조치"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:643
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:656
#, python-brace-format
msgctxt "@info:progress"
msgid "Sending {file_name} to group {cluster_name}"
msgstr "{filename} {file_name} filename>을 {cluster_name} 그룹에 보냄"
-#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:19
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.py:17
msgctxt "@action"
msgid "Connect via Network"
msgstr "네트워크를 통해 연결"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:64
+#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Monitor"
+msgstr "모니터"
+
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
#, python-brace-format
msgctxt "@info Don't translate {machine_name}, since it gets replaced by a printer name!"
msgid "New features are available for your {machine_name}! It is recommended to update the firmware on your printer."
msgstr "{machine_name}의 새로운 기능을 사용할 수 있습니다! 프린터의 펌웨어를 업데이트하는 것이 좋습니다."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:65
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:67
#, python-format
msgctxt "@info:title The %s gets replaced with the printer name."
msgid "New %s firmware available"
msgstr "새로운 펌웨어를 사용할 수 있습니다."
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:68
msgctxt "@action:button"
msgid "How to update"
msgstr "업데이트 방법"
-#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:77
+#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:79
msgctxt "@info"
msgid "Could not access update information."
msgstr "업데이트 정보에 액세스 할 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:199
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
msgctxt "@info:status"
-msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
-msgstr "SolidWorks 파일을 여는 중 오류가 발생했습니다! 문제없이 SolidWorks에서 파일을 열 수 있는지 확인하십시오"
+msgid "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself."
+msgstr "파일을 여는 도중 SolidWorks가 오류 보고. SolidWorks 자체에서 이 문제를 해결하도록 권장합니다."
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:31
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
+msgctxt "@info:status"
+msgid ""
+"Found no models inside your drawing. Could you please check it's content again and make sure one part or assembly is inside?\n"
+"\n"
+" Thanks!."
+msgstr "도면에 모델이 없습니다. 내용을 다시 확인하시고 내부에 하나의 부품이나 조립만 있는지 확인하시겠습니까?\n\n 감사합니다!."
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
+msgctxt "@info:status"
+msgid ""
+"Found more then one part or assembly inside your drawing. We currently only support drawings with exactly one part or assembly inside.\n"
+"\n"
+"Sorry!"
+msgstr "도면에 하나 이상의 부품 또는 조립이 있습니다. 저희는 현재 정확하게 하나의 부품 또는 조립만 있는 도면을 지원합니다.\n\n죄송합니다!"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
msgctxt "@item:inlistbox"
msgid "SolidWorks part file"
msgstr "SolidWorks 파트 파일"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:35
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:29
msgctxt "@item:inlistbox"
msgid "SolidWorks assembly file"
msgstr "SolidWorks 어셈블리 파일"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.py:21
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
+msgctxt "@item:inlistbox"
+msgid "SolidWorks drawing file"
+msgstr "SolidWorks 도면 파일"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"We could not find a valid installation of SolidWorks on your system. That means that either SolidWorks is not installed or you don't own an valid license. Please make sure that running SolidWorks itself works without issues and/or contact your ICT.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr "안녕하십니까,\n귀하의 시스템에 유효한 SolidWorks를 찾을 수 없습니다. 이는 곧 SolidWorks가 설치되어 있지 않거나 유효한 라이센스가 없음을 의미합니다. SolidWorks가 문제없이 실행될 수 있도록 해주시고 그리고/또는 귀사의 ICT에 연락해 주십시오.\n\n감사합니다.\n - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
+msgctxt "@info:status"
+msgid ""
+"Dear customer,\n"
+"You are currently running this plugin on an operating system other than Windows. This plugin will only work on Windows with SolidWorks installed, including an valid license. Please install this plugin on a Windows machine with SolidWorks installed.\n"
+"\n"
+"With kind regards\n"
+" - Thomas Karl Pietrowski"
+msgstr "안녕하십니까,\n귀하는 현재 Windows가 아닌 다른 운영 시스템에서 이 플러그인을 실행 중입니다. 이 플러그인은 유효한 라이센스가 있는 SolidWorks가 설치된 Windows에서만 사용 가능합니다. 이 플러그인을 SolidWorks가 설치된 Windows 컴퓨터에 설치하십시오.\n\n감사합니다\n - Thomas Karl Pietrowski"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
msgid "Configure"
msgstr "구성"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/CommonComReader.py:135
-#, python-format
-msgctxt "@info:status"
-msgid "Error while starting %s!"
-msgstr "시작하는 도중 오류가 발생했습니다!"
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
+msgid "Installation guide for SolidWorks macro"
+msgstr "SolidWorks 매크로 설치 가이드"
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
msgctxt "@item:inlistbox"
-msgid "Simulation view"
-msgstr "시뮬레이션 보기"
+msgid "Layer view"
+msgstr "레이어 보기"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:103
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "와이어 인쇄가 활성화되어있을 때 Cura는 레이어를 정확하게 표시하지 않습니다"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:101
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.py:104
msgctxt "@info:title"
msgid "Simulation View"
msgstr "시뮬레이션 보기"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:26
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.py:25
msgid "Modify G-Code"
msgstr "G 코드 수정"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
msgctxt "@info"
-msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
-msgstr "Cura는 익명의 슬라이싱 통계를 수집합니다. 환경 설정에서 이 기능을 비활성화 할 수 있습니다."
+msgid "Cura collects anonymized usage statistics."
+msgstr "Cura는 익명의 사용 통계를 수집합니다."
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
msgctxt "@info:title"
@@ -669,14 +716,41 @@ msgstr "데이터 수집"
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
msgctxt "@action:button"
-msgid "Dismiss"
-msgstr "버리다"
+msgid "Allow"
+msgstr "허용"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
+msgctxt "@action:tooltip"
+msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
+msgstr "Cura가 익명의 사용 통계를 보내 Cura에 대한 향후 개선을 우선화하는 데 도움을 줍니다. Cura 버전과 슬라이싱하는 모델의 해쉬 등 일부 선호사항과 설정이 발송됩니다."
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
+msgctxt "@action:button"
+msgid "Disable"
+msgstr "사용 안함"
+
+#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
+msgctxt "@action:tooltip"
+msgid "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."
+msgstr "Cura가 익명의 사용 통계를 보내지 않게 합니다. 환경 설정에서 다시 사용할 수 있습니다."
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
msgctxt "@item:inlistbox"
msgid "Cura 15.04 profiles"
msgstr "Cura 15.04 프로필"
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
+msgctxt "@item:inlistbox"
+msgid "Blender file"
+msgstr "Blender 파일"
+
+#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
+msgctxt "@info:status"
+msgid ""
+"Could not export using \"{}\" quality!\n"
+"Felt back to \"{}\"."
+msgstr "\"{}\" 품질을 사용하여 내보낼 수 없습니다!\n \"{}\"(으)로 돌아갑니다."
+
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
msgctxt "@item:inlistbox"
@@ -708,49 +782,49 @@ msgctxt "@item:inlistbox"
msgid "GIF Image"
msgstr "GIF 이미지"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
msgctxt "@info:status"
msgid "Unable to slice with the current material as it is incompatible with the selected machine or configuration."
msgstr "선택한 소재 또는 구성과 호환되지 않기 때문에 현재 소재로 슬라이스 할 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:269
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:297
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:319
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:299
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:327
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:336
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:349
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:357
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:366
msgctxt "@info:title"
msgid "Unable to slice"
msgstr "슬라이스 할 수 없습니다"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:296
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice with the current settings. The following settings have errors: {0}"
msgstr "현재 설정으로 슬라이스 할 수 없습니다. 다음 설정에는 오류가 있습니다 : {0}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:318
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:348
#, python-brace-format
msgctxt "@info:status"
msgid "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}"
msgstr "일부 모델별 설정으로 인해 슬라이스할 수 없습니다. 하나 이상의 모델에서 다음 설정에 오류가 있습니다. {error_labels}"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:326
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:356
msgctxt "@info:status"
msgid "Unable to slice because the prime tower or prime position(s) are invalid."
msgstr "프라임 탑 또는 주요 위치가 유효하지 않아 슬라이스 할 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:335
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/CuraEngineBackend.py:365
msgctxt "@info:status"
msgid "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."
msgstr "어떤 모델도 빌드 볼륨에 맞지 않으므로 슬라이스 할 수 없습니다. 크기에 맞게 모형을 회전하거나 회전하십시오."
#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:50
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:status"
msgid "Processing Layers"
msgstr "레이어 처리 중"
-#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:239
+#: /home/ruben/Projects/Cura/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py:242
msgctxt "@info:title"
msgid "Information"
msgstr "정보"
@@ -787,14 +861,14 @@ msgstr "Siemens NX 플러그인 파일을 복사하지 못했습니다. UGII_USE
msgid "Failed to install Siemens NX plugin. Could not set environment variable UGII_USER_DIR for Siemens NX."
msgstr "Siemens NX 플러그인을 설치하지 못했습니다. Siemens NX의 환경 변수 UGII_USER_DIR을 설정할 수 없습니다."
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:585
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:165
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
msgctxt "@title:tab"
msgid "Recommended"
msgstr "추천"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:169
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:590
+#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.py:167
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:595
msgctxt "@title:tab"
msgid "Custom"
msgstr "관습"
@@ -805,24 +879,24 @@ msgctxt "@item:inlistbox"
msgid "3MF File"
msgstr "3MF 파일"
-#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:126
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1142
+#: /home/ruben/Projects/Cura/plugins/3MFReader/ThreeMFWorkspaceReader.py:159
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:1185
msgctxt "@label"
msgid "Nozzle"
msgstr "노즐"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:164
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:152
#, python-brace-format
msgctxt "@info:status"
msgid "Failed to get plugin ID from {0}"
msgstr "?에서 플러그인 ID를 가져 오는 데 실패했습니다"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:165
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:153
msgctxt "@info:tile"
msgid "Warning"
msgstr "경고"
-#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:203
+#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.py:191
msgctxt "@window:title"
msgid "Plugin browser"
msgstr "플러그인 브라우저"
@@ -837,18 +911,18 @@ msgctxt "@item:inlistbox"
msgid "G File"
msgstr "G파일"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:314
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:321
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr "G 코드 파싱"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:316
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:426
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:323
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:464
msgctxt "@info:title"
msgid "G-code Details"
msgstr "G 코드 세부 정보"
-#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:424
+#: /home/ruben/Projects/Cura/plugins/GCodeReader/FlavorParser.py:462
msgctxt "@info:generic"
msgid "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."
msgstr "파일을 보내기 전에 g 코드가 프린터 및 프린터 구성에 적합한 지 확인하십시오. g 코드 표현이 정확하지 않을 수 있습니다."
@@ -859,6 +933,16 @@ msgctxt "@item:inlistbox"
msgid "Cura Profile"
msgstr "Cura 프로필"
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
+msgctxt "@item:inmenu"
+msgid "Profile Assistant"
+msgstr "프로필 어시스턴트"
+
+#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
+msgctxt "@item:inlistbox"
+msgid "Profile Assistant"
+msgstr "프로필 어시스턴트"
+
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
msgctxt "@item:inlistbox"
msgid "3MF file"
@@ -890,142 +974,116 @@ msgctxt "@action"
msgid "Level build plate"
msgstr "레벨 빌드 플레이트"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:89
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
msgctxt "@tooltip"
msgid "Outer Wall"
msgstr "외벽"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:90
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
msgctxt "@tooltip"
msgid "Inner Walls"
msgstr "내벽"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:91
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:100
msgctxt "@tooltip"
msgid "Skin"
msgstr "외판"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:92
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:101
msgctxt "@tooltip"
msgid "Infill"
msgstr "빈 공간 채우기"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:93
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:102
msgctxt "@tooltip"
msgid "Support Infill"
msgstr "빈 공간 채우기 지지"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:94
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:103
msgctxt "@tooltip"
msgid "Support Interface"
msgstr "지원 인터페이스"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:95
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:104
msgctxt "@tooltip"
msgid "Support"
msgstr "지지물"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:96
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:105
msgctxt "@tooltip"
msgid "Skirt"
msgstr "둘러싸다"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:97
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:106
msgctxt "@tooltip"
msgid "Travel"
msgstr "움직여 가다"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:98
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:107
msgctxt "@tooltip"
msgid "Retractions"
msgstr "취소"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:99
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:108
msgctxt "@tooltip"
msgid "Other"
msgstr "다른"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:199
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:231
msgctxt "@label unknown material"
msgid "Unknown"
msgstr "알 수 없음"
-#: /home/ruben/Projects/Cura/cura/PrintInformation.py:284
+#: /home/ruben/Projects/Cura/cura/PrintInformation.py:318
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr "미리 잘라낸 파일 {0}"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:469
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:440
msgctxt "@item:material"
msgid "No material loaded"
msgstr "로드 된 자료 없음"
-#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:476
+#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:447
msgctxt "@item:material"
msgid "Unknown material"
msgstr "알 수없는 자료"
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:30
-msgctxt "@info:status"
-msgid "Finding new location for objects"
-msgstr "객체의 새 위치 삽입"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:34
-msgctxt "@info:title"
-msgid "Finding Location"
-msgstr "위치 찾기"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:89
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
-msgctxt "@info:status"
-msgid "Unable to find a location within the build volume for all objects"
-msgstr "모든 개체에 대한 빌드 볼륨 내의 위치를 찾을 수 없습니다"
-
-#: /home/ruben/Projects/Cura/cura/ArrangeObjectsJob.py:90
-msgctxt "@info:title"
-msgid "Can't Find Location"
-msgstr "위치를 찾을 수 없음"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:431
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:113
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:437
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:120
msgctxt "@title:window"
msgid "File Already Exists"
msgstr "파일이 이미 있습니다"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:432
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:114
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:438
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:121
#, python-brace-format
msgctxt "@label Don't translate the XML tag !"
msgid "The file {0} already exists. Are you sure you want to overwrite it?"
msgstr "파일이 이미 있습니다. 덮어 쓰시겠습니까?"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:815
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:872
msgctxt "@label"
msgid "Custom"
msgstr "맞춤"
-#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:819
+#: /home/ruben/Projects/Cura/cura/Settings/ContainerManager.py:876
msgctxt "@label"
msgid "Custom Material"
msgstr "맞춤 소재"
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:182
-msgctxt "@menuitem"
-msgid "Global"
-msgstr "전역"
-
-#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:229
+#: /home/ruben/Projects/Cura/cura/Settings/ExtrudersModel.py:205
msgctxt "@menuitem"
msgid "Not overridden"
msgstr "재정의되지 않음"
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:117
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:124
msgctxt "@info:status"
msgid "The selected material is incompatible with the selected machine or configuration."
msgstr "선택한 재료가 선택한 기계 또는 구성과 호환되지 않습니다."
-#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:118
+#: /home/ruben/Projects/Cura/cura/Settings/MachineManager.py:125
#: /home/ruben/Projects/Cura/cura/Settings/MaterialManager.py:24
msgctxt "@info:title"
msgid "Incompatible Material"
@@ -1046,67 +1104,89 @@ msgctxt "@action"
msgid "Undo changing the material diameter."
msgstr "재료 직경 변경을 취소하십시오."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:144
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to export profile to {0}: {1}"
msgstr "프로필을 ?로 내보내는데 실패했습니다"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:151
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:158
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Failed to export profile to {0}: Writer plugin reported failure."
msgstr "프로필을 ?로 내보내지 못했습니다. Writer 플러그인이 오류를 보고했습니다."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:156
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:163
#, python-brace-format
msgctxt "@info:status Don't translate the XML tag !"
msgid "Exported profile to {0}"
msgstr "프로파일을 ?에 내보냅니다"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:157
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:164
msgctxt "@info:title"
msgid "Export succeeded"
msgstr "내보내기 완료"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:183
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:205
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:214
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:248
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:190
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:211
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:271
#, python-brace-format
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Failed to import profile from {0}: {1}"
msgstr "?에서 프로필을 가져 오지 못했습니다"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:216
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:252
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:230
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "This profile {0} contains incorrect data, could not import it."
+msgstr "이 프로필 {0}에는 정확하지 않은 데이터가 포함되어 있으므로, 불러올 수 없습니다."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
+#, python-brace-format
+msgctxt "@info:status Don't translate the XML tags or !"
+msgid "The machine defined in profile {0} doesn't match with your current machine, could not import it."
+msgstr "프로필 {0}에 정의된 기계가 현재 기계와 일치하지 않으므로, 불러올 수 없습니다."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
#, python-brace-format
msgctxt "@info:status"
msgid "Successfully imported profile {0}"
msgstr "프로필 {0}을 (를) 성공적으로 가져 왔습니다."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:255
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:277
+#, python-brace-format
+msgctxt "@info:status"
+msgid "File {0} does not contain any valid profile."
+msgstr "파일 {0}에 유효한 프로필이 포함되어 있지 않습니다."
+
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
#, python-brace-format
msgctxt "@info:status"
msgid "Profile {0} has an unknown file type or is corrupted."
msgstr "프로파일 {0}에 알 수없는 파일 유형이 있거나 손상되었습니다."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:298
msgctxt "@label"
msgid "Custom profile"
msgstr "사용자 정의 프로필"
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:285
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:313
msgctxt "@info:status"
msgid "Profile is missing a quality type."
msgstr "프로필에 품질 유형이 누락되었습니다."
-#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:321
+#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:349
#, python-brace-format
msgctxt "@info:status"
msgid "Could not find a quality type {0} for the current configuration."
msgstr "현재 구성에 대해 품질 유형 {0}을 (를) 찾을 수 없습니다."
+#: /home/ruben/Projects/Cura/cura/ObjectsModel.py:46
+#, python-brace-format
+msgctxt "@label"
+msgid "Group #{group_nr}"
+msgstr "그룹 #{group_nr}"
+
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
msgctxt "@info:status"
msgid "The build volume height has been reduced due to the value of the \"Print Sequence\" setting to prevent the gantry from colliding with printed models."
@@ -1117,142 +1197,167 @@ msgctxt "@info:title"
msgid "Build Volume"
msgstr "빌드 볼륨"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:34
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:25
msgctxt "@info:status"
msgid "Multiplying and placing objects"
msgstr "객체 곱하기 및 배치"
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:35
-#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:83
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:26
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
msgctxt "@info:title"
msgid "Placing Object"
msgstr "개체 배치 중"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:80
+#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:78
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:88
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:152
+msgctxt "@info:status"
+msgid "Unable to find a location within the build volume for all objects"
+msgstr "모든 개체에 대한 빌드 볼륨 내의 위치를 찾을 수 없습니다"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:29
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:64
+msgctxt "@info:status"
+msgid "Finding new location for objects"
+msgstr "객체의 새 위치 삽입"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:33
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:68
+msgctxt "@info:title"
+msgid "Finding Location"
+msgstr "위치 찾기"
+
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsJob.py:89
+#: /home/ruben/Projects/Cura/cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py:153
+msgctxt "@info:title"
+msgid "Can't Find Location"
+msgstr "위치를 찾을 수 없음"
+
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:81
msgctxt "@title:window"
msgid "Crash Report"
msgstr "오류 보고서"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:93
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:94
msgctxt "@label crash message"
msgid ""
-"
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+"
A fatal error has occurred. Please send us this Crash Report to fix the problem
\n"
"
Please use the \"Send report\" button to post a bug report automatically to our servers
"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:141
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:147
msgctxt "@title:groupbox"
-msgid "Exception traceback"
-msgstr "예외 역추적"
+msgid "Error traceback"
+msgstr "오류 추적"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:208
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:214
msgctxt "@title:groupbox"
msgid "Logs"
msgstr "로그"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:231
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:237
msgctxt "@title:groupbox"
msgid "User description"
msgstr "사용자 설명"
-#: /home/ruben/Projects/Cura/cura/CrashHandler.py:246
+#: /home/ruben/Projects/Cura/cura/CrashHandler.py:252
msgctxt "@action:button"
msgid "Send report"
msgstr "보고서 전송"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:256
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:274
msgctxt "@info:progress"
msgid "Loading machines..."
msgstr "기계로드 중 ..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:661
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:660
msgctxt "@info:progress"
msgid "Setting up scene..."
msgstr "장면 설정 중 ..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:703
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:702
msgctxt "@info:progress"
msgid "Loading interface..."
msgstr "인터페이스로드 중 ..."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:874
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:899
#, python-format
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
msgid "%(width).1f x %(depth).1f x %(height).1f mm"
-msgstr "% (너비) .1f x % (깊이) .1f x % (높이) .1f mm"
+msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1348
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr "한 번에 하나의 G 코드 파일만 로드 할 수 있습니다. 가져 오기를 건너 뛰었습니다."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1357
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1426
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr "G 코드가 로드되어 있으면 다른 파일을 열 수 없습니다. 가져 오기를 건너 뛰었습니다."
-#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1416
+#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1495
msgctxt "@info:status"
msgid "The selected model was too small to load."
msgstr "선택한 모델이 너무 작아서 로드할 수 없습니다."
@@ -1281,12 +1386,11 @@ msgstr "X (너비)"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:119
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:129
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:235
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:288
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:300
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:391
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:401
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:413
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:840
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:383
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:394
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:424
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:849
msgctxt "@label"
msgid "mm"
msgstr "mm"
@@ -1376,68 +1480,67 @@ msgctxt "@tooltip"
msgid "The height difference between the tip of the nozzle and the gantry system (X and Y axes). Used to prevent collisions between previous prints and the gantry when printing \"One at a Time\"."
msgstr "노즐 끝과 갠트리 시스템 사이의 높이 차이 (X 및 Y 축). \"한 번에 한 장\"을 인쇄 할 때 이전 인쇄물과 갠트리 사이의 충돌을 방지하는 데 사용됩니다."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:255
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:254
msgctxt "@label"
msgid "Number of Extruders"
msgstr "압출기의 수"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:289
-msgctxt "@tooltip"
-msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
-msgstr "프린터가 지원하는 필라멘트의 공칭 직경. 정확한 직경은 소재 및 / 또는 프로파일에 의해 무시됩니다."
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:291
-msgctxt "@label"
-msgid "Material diameter"
-msgstr "재료 직경"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:299
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:390
-msgctxt "@label"
-msgid "Nozzle size"
-msgstr "노즐 크기"
-
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:317
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:310
msgctxt "@label"
msgid "Start Gcode"
msgstr "Gcode 시작"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:327
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:320
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very start."
msgstr "시작시 Gcode 명령이 실행됩니다."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:336
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:329
msgctxt "@label"
msgid "End Gcode"
msgstr "Gcode 끝내기"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:346
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:339
msgctxt "@tooltip"
msgid "Gcode commands to be executed at the very end."
msgstr "Gcode 명령어가 맨 마지막에 실행됩니다."
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:378
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:370
msgctxt "@label"
msgid "Nozzle Settings"
msgstr "노즐 설정"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:400
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:382
+msgctxt "@label"
+msgid "Nozzle size"
+msgstr "노즐 크기"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:393
+msgctxt "@label"
+msgid "Compatible material diameter"
+msgstr "호환이 되는 재료 직경"
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:395
+msgctxt "@tooltip"
+msgid "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile."
+msgstr "프린터가 지원하는 필라멘트의 공칭 직경. 정확한 직경은 소재 및 / 또는 프로파일에 의해 무시됩니다."
+
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:411
msgctxt "@label"
msgid "Nozzle offset X"
msgstr "노즐 오프셋 X"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:412
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:423
msgctxt "@label"
msgid "Nozzle offset Y"
msgstr "노즐 오프셋 Y"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:433
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:444
msgctxt "@label"
msgid "Extruder Start Gcode"
msgstr "압출기 시작 Gcode"
-#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:451
+#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:462
msgctxt "@label"
msgid "Extruder End Gcode"
msgstr "압출기 종료 Gcode"
@@ -1450,8 +1553,9 @@ msgstr "변경 내역"
#: /home/ruben/Projects/Cura/plugins/ChangeLogPlugin/ChangeLog.qml:37
#: /home/ruben/Projects/Cura/plugins/USBPrinting/FirmwareUpdateWindow.qml:107
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/UM3InfoComponents.qml:55
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:445
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:357
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:306
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:456
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:492
#: /home/ruben/Projects/Cura/plugins/PluginBrowser/PluginBrowser.qml:80
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:123
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:147
@@ -1532,7 +1636,7 @@ msgstr "편집"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:50
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:95
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:190
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:171
msgctxt "@action:button"
msgid "Remove"
msgstr "제거"
@@ -1554,14 +1658,14 @@ msgid "Type"
msgstr "유형"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:233
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3"
-msgstr "얼티메이커 3"
+msgstr "Ultimaker 3"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:236
-msgctxt "@label"
+msgctxt "@label Printer name"
msgid "Ultimaker 3 Extended"
-msgstr "얼티메이커 3 확장판"
+msgstr "Ultimaker 3 Extended"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:252
msgctxt "@label"
@@ -1605,8 +1709,6 @@ msgid "Enter the IP address or hostname of your printer on the network."
msgstr "네트워크에 프린터의 IP 주소 또는 호스트 이름을 입력하십시오."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:379
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:92
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:88
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:181
msgctxt "@action:button"
msgid "OK"
@@ -1627,6 +1729,11 @@ msgctxt "@label: arg 1 is group name"
msgid "%1 is not set up to host a group of connected Ultimaker 3 printers"
msgstr "?은 연결된 Ultimaker 3에 연결된 프린터 그룹을 호스트하도록 설정되지 않았습니다"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
+msgctxt "@label link to connect manager"
+msgid "Add/Remove printers"
+msgstr "프린터 추가/제거"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
msgctxt "@info:tooltip"
msgid "Opens the print jobs page with your default web browser."
@@ -1657,11 +1764,16 @@ msgid "Available"
msgstr "유효한"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:43
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:101
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:100
msgctxt "@label:MonitorStatus"
msgid "Lost connection with the printer"
msgstr "프린터와의 연결이 끊어졌습니다"
+#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
+msgctxt "@label Printer status"
+msgid "Unknown"
+msgstr "알 수 없음"
+
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:257
msgctxt "@label:status"
msgid "Disabled"
@@ -1753,138 +1865,250 @@ msgctxt "@action:button"
msgid "Activate Configuration"
msgstr "구성 활성화"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:20
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:21
msgctxt "@title:window"
-msgid "Cura SolidWorks Plugin Configuration"
-msgstr "Cura SolidWorks Plugin 설정"
+msgid "SolidWorks: Export wizard"
+msgstr "SolidWorks: 내보내기 마법사"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:44
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:45
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:140
msgctxt "@action:label"
-msgid "Default quality of the exported STL:"
-msgstr "내 보낸 STL의 기본 품질 :"
+msgid "Quality:"
+msgstr "품질:"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:78
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:179
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always ask"
-msgstr "항상 물어보다"
+msgid "Fine (3D-printing)"
+msgstr "고품질 (3D-프린팅)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:79
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:180
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Fine quality"
-msgstr "항상 좋은 품질을 사용하십시오"
+msgid "Coarse (3D-printing)"
+msgstr "저품질 (3D-프린팅)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ConfigDialog.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:80
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:181
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Always use Coarse quality"
-msgstr "거친 품질을 항상 사용하십시오"
+msgid "Fine (SolidWorks)"
+msgstr "고품질 (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:20
-msgctxt "@title:window"
-msgid "Import SolidWorks File as STL..."
-msgstr "SolidWorks 파일을 STL로 가져 오기 ..."
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:43
-msgctxt "@info:tooltip"
-msgid "Quality of the Exported STL"
-msgstr "내보낸 STL의 품질"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:48
-msgctxt "@action:label"
-msgid "Quality"
-msgstr "품질"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:62
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:182
msgctxt "@option:curaSolidworksStlQuality"
-msgid "Coarse"
-msgstr "결이 거친"
+msgid "Coarse (SolidWorks)"
+msgstr "저품질 (SolidWorks)"
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:63
-msgctxt "@option:curaSolidworksStlQuality"
-msgid "Fine"
-msgstr "우수한"
-
-#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/ExportSTLUI.qml:78
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:82
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:94
msgctxt "@text:window"
-msgid "Remember my choice"
-msgstr "내 선택을 기억하라"
+msgid "Show this dialog again"
+msgstr "이 대화창을 다시 표시"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:81
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:104
+msgctxt "@action:button"
+msgid "Continue"
+msgstr "계속"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:116
+msgctxt "@action:button"
+msgid "Abort"
+msgstr "중단"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:21
+msgctxt "@title:window"
+msgid "How to install Cura SolidWorks macro"
+msgstr "Cura SolidWorks 매크로 설치 방법"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:62
+msgctxt "@description:label"
+msgid "Steps:"
+msgstr "단계:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:140
+msgctxt "@action:button"
+msgid ""
+"Open the directory\n"
+"with macro and icon"
+msgstr "매크로와 아이콘으로\n디렉토리 열기"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:160
+msgctxt "@description:label"
+msgid "Instructions:"
+msgstr "안내:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:202
+msgctxt "@action:playpause"
+msgid "Play"
+msgstr "재생"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:206
+msgctxt "@action:playpause"
+msgid "Pause"
+msgstr "중지"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:268
+msgctxt "@action:button"
+msgid "Previous Step"
+msgstr "이전 단계"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:283
+msgctxt "@action:button"
+msgid "Done"
+msgstr "완료"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:287
+msgctxt "@action:button"
+msgid "Next Step"
+msgstr "다음 단계"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:21
+msgctxt "@title:window"
+msgid "SolidWorks plugin: Configuration"
+msgstr "SolidWorks 플러그인: 구성"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:39
+msgctxt "@title:tab"
+msgid "Conversion settings"
+msgstr "변환 설정"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:66
+msgctxt "@label"
+msgid "First choice:"
+msgstr "첫 번째 선택:"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:86
+msgctxt "@text:menu"
+msgid "Latest installed version (Recommended)"
+msgstr "가장 최근 설치 버전(권장)"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:95
+msgctxt "@text:menu"
+msgid "Default version"
+msgstr "기본 버전"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:193
+msgctxt "@label"
+msgid "Show wizard before opening SolidWorks files"
+msgstr "SolidWorks 파일을 열기 전 마법사 표시"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:203
+msgctxt "@label"
+msgid "Automatically rotate opened file into normed orientation"
+msgstr "열린 파일을 규정된 방향으로 자동 회전"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:210
+msgctxt "@title:tab"
+msgid "Installation(s)"
+msgstr "설치"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:284
+msgctxt "@label"
+msgid "COM service found"
+msgstr "COM 서비스 발견"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:295
+msgctxt "@label"
+msgid "Executable found"
+msgstr "실행가능 발견"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:306
+msgctxt "@label"
+msgid "COM starting"
+msgstr "COM 시작"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:317
+msgctxt "@label"
+msgid "Revision number"
+msgstr "수정 번호"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:328
+msgctxt "@label"
+msgid "Functions available"
+msgstr "사용 가능한 기능"
+
+#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:341
+#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
+msgctxt "@action:button"
+msgid "Save"
+msgstr "저장하다"
+
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:117
msgctxt "@label"
msgid "Color scheme"
msgstr "색 구성표"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:96
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:132
msgctxt "@label:listbox"
msgid "Material Color"
msgstr "재질 색상"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:100
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:136
msgctxt "@label:listbox"
msgid "Line Type"
msgstr "라인 유형"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:104
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:140
msgctxt "@label:listbox"
msgid "Feedrate"
msgstr "이송 속도"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:108
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:144
msgctxt "@label:listbox"
msgid "Layer thickness"
msgstr "레이어 두께"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:148
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:185
msgctxt "@label"
msgid "Compatibility Mode"
msgstr "호환 모드"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:230
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:264
msgctxt "@label"
msgid "Show Travels"
msgstr "이동 표시"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:236
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:270
msgctxt "@label"
msgid "Show Helpers"
msgstr "도움말 보이기"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:242
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:276
msgctxt "@label"
msgid "Show Shell"
msgstr "셸 표시"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:248
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:282
msgctxt "@label"
msgid "Show Infill"
msgstr "충전물 표시"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:297
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:330
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr "상단 레이어 만 표시"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:306
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:339
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr "상단에 5 개의 세부 레이어 표시"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:317
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:350
msgctxt "@label"
msgid "Top / Bottom"
msgstr "위 / 아래"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:321
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:354
msgctxt "@label"
msgid "Inner Wall"
msgstr "내벽"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:378
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:410
msgctxt "@label"
msgid "min"
msgstr "최소"
-#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:420
+#: /home/ruben/Projects/Cura/plugins/SimulationView/SimulationView.qml:452
msgctxt "@label"
msgid "max"
msgstr "최대"
@@ -1909,7 +2133,7 @@ msgctxt "@label"
msgid "Settings"
msgstr "설정"
-#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:455
+#: /home/ruben/Projects/Cura/plugins/PostProcessingPlugin/PostProcessingPlugin.qml:466
msgctxt "@info:tooltip"
msgid "Change active post-processing scripts"
msgstr "활성 사후 처리 스크립트 변경"
@@ -1984,23 +2208,53 @@ msgctxt "@action:label"
msgid "Smoothing"
msgstr "부드럽게하기"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:208
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
+msgctxt "@label"
+msgid "Mesh Type"
+msgstr "메쉬 유형"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
+msgctxt "@label"
+msgid "Normal model"
+msgstr "일반 모델"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
+msgctxt "@label"
+msgid "Print as support"
+msgstr "서포터로 프린팅"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
+msgctxt "@label"
+msgid "Don't support overlap with other models"
+msgstr "다른 모델과 오버랩되도록 지지하지 않음"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
+msgctxt "@label"
+msgid "Modify settings for overlap with other models"
+msgstr "다른 모델과의 오버랩에 대한 설정 수정"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
+msgctxt "@label"
+msgid "Modify settings for infill of other models"
+msgstr "다른 모델의 충진재에 대한 설정 수정"
+
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:333
msgctxt "@action:button"
msgid "Select settings"
msgstr "설정 선택"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:248
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:375
msgctxt "@title:window"
msgid "Select Settings to Customize for this model"
msgstr "이 모델에 맞게 사용자 정의 설정을 선택하십시오"
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:272
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:402
#: /home/ruben/Projects/Cura/resources/qml/Preferences/SettingVisibilityPage.qml:91
msgctxt "@label:textbox"
msgid "Filter..."
msgstr "필터..."
-#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:296
+#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:426
msgctxt "@label:checkbox"
msgid "Show all"
msgstr "모두 보이기"
@@ -2359,66 +2613,66 @@ msgctxt "@label"
msgid "Everything is in order! You're done with your CheckUp."
msgstr "당신의 점검으로 모든 것이 순조롭게 끝났습니다."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:88
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:87
msgctxt "@label:MonitorStatus"
msgid "Not connected to a printer"
msgstr "프린터에 연결되지 않음"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:90
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:89
msgctxt "@label:MonitorStatus"
msgid "Printer does not accept commands"
msgstr "프린터가 명령을 받아들이지 않습니다"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:96
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:95
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:194
msgctxt "@label:MonitorStatus"
msgid "In maintenance. Please check the printer"
msgstr "유지 보수 중. 프린터를 확인하십시오"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:103
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:102
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:184
msgctxt "@label:MonitorStatus"
msgid "Printing..."
msgstr "인쇄..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:106
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:105
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:186
msgctxt "@label:MonitorStatus"
msgid "Paused"
msgstr "일시 중지됨"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:109
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:108
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:188
msgctxt "@label:MonitorStatus"
msgid "Preparing..."
msgstr "준비 중 ..."
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:111
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:110
msgctxt "@label:MonitorStatus"
msgid "Please remove the print"
msgstr "인쇄를 제거하십시오"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:237
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
msgctxt "@label:"
msgid "Resume"
msgstr "다시 시작하다"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:245
msgctxt "@label:"
msgid "Pause"
msgstr "중지"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:274
msgctxt "@label:"
msgid "Abort Print"
msgstr "인쇄 중단"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:280
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:284
msgctxt "@window:title"
msgid "Abort print"
msgstr "인쇄 중단"
-#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:282
+#: /home/ruben/Projects/Cura/resources/qml/MonitorButton.qml:286
msgctxt "@label"
msgid "Are you sure you want to abort the print?"
msgstr "인쇄를 중단 하시겠습니까?"
@@ -2451,19 +2705,19 @@ msgid "Customized"
msgstr "맞춤형"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:157
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:593
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr "항상 이걸 내게 부탁해"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:158
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:594
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:596
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr "버리고 다시 묻지 마십시오"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:159
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:595
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:597
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr "계속하고 다시 묻지 마라"
@@ -2498,72 +2752,72 @@ msgctxt "@label"
msgid "Brand"
msgstr "상표"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:92
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:88
msgctxt "@label"
msgid "Material Type"
msgstr "자재 유형"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:105
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:97
msgctxt "@label"
msgid "Color"
msgstr "색깔"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:139
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
msgctxt "@label"
msgid "Properties"
msgstr "속성"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:143
msgctxt "@label"
msgid "Density"
msgstr "밀도"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:156
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:158
msgctxt "@label"
msgid "Diameter"
msgstr "직경"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:187
msgctxt "@label"
msgid "Filament Cost"
msgstr "필라멘트 비용"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:203
msgctxt "@label"
msgid "Filament weight"
msgstr "필라멘트 무게"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:218
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:220
msgctxt "@label"
msgid "Filament length"
msgstr "필라멘트 길이"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:227
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:229
msgctxt "@label"
msgid "Cost per Meter"
msgstr "미터 당 비용"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:241
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:243
msgctxt "@label"
msgid "This material is linked to %1 and shares some of its properties."
msgstr "이 자료는 ?에 연결되어 있으며 일부 속성을 공유합니다."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:250
msgctxt "@label"
msgid "Unlink Material"
msgstr "재질 연결 해제"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:259
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:261
msgctxt "@label"
msgid "Description"
msgstr "기술"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:272
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:274
msgctxt "@label"
msgid "Adhesion Information"
msgstr "접착 정보"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:300
msgctxt "@label"
msgid "Print settings"
msgstr "인쇄 설정"
@@ -2604,7 +2858,7 @@ msgid "Unit"
msgstr "단위"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:14
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:439
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:509
msgctxt "@title:tab"
msgid "General"
msgstr "일반"
@@ -2619,230 +2873,255 @@ msgctxt "@label"
msgid "Language:"
msgstr "언어:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:205
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:207
msgctxt "@label"
msgid "Currency:"
msgstr "통화:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:219
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:221
msgctxt "@label"
msgid "Theme:"
msgstr "테마:"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:279
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:281
msgctxt "@label"
msgid "You will need to restart the application for these changes to have effect."
msgstr "이러한 변경 사항을 적용하려면 응용 프로그램을 다시 시작해야합니다."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:296
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:298
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr "설정을 변경할 때 자동으로 슬라이스합니다."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:304
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:306
msgctxt "@option:check"
msgid "Slice automatically"
msgstr "자동으로 슬라이스"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:318
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:320
msgctxt "@label"
msgid "Viewport behavior"
msgstr "뷰포트 동작"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:326
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:328
msgctxt "@info:tooltip"
msgid "Highlight unsupported areas of the model in red. Without support these areas will not print properly."
msgstr "지원되지 않는 모델 영역을 빨간색으로 강조 표시하십시오. 지지무이 없으면 이 영역이 제대로 인쇄되지 않습니다."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:337
msgctxt "@option:check"
msgid "Display overhang"
msgstr "오버행 표시"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:342
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:344
msgctxt "@info:tooltip"
msgid "Moves the camera so the model is in the center of the view when a model is selected"
msgstr "모델을 선택하면 모델이 뷰의 가운데에 오도록 카메라를 이동합니다"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:347
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:349
msgctxt "@action:button"
msgid "Center camera when item is selected"
msgstr "항목을 선택하면 중앙 카메라"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:356
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:358
msgctxt "@info:tooltip"
msgid "Should the default zoom behavior of cura be inverted?"
msgstr "cura의 기본 확대 / 축소 동작을 반전시켜야합니까"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:363
msgctxt "@action:button"
msgid "Invert the direction of camera zoom."
msgstr "카메라 줌의 방향을 반전시킵니다."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:370
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:372
msgctxt "@info:tooltip"
msgid "Should zooming move in the direction of the mouse?"
msgstr "확대 / 축소가 마우스 방향으로 이동해야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:375
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:377
msgctxt "@action:button"
msgid "Zoom toward mouse direction"
msgstr "마우스 방향으로 확대 / 축소"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:384
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:386
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved so that they no longer intersect?"
msgstr "플랫폼의 모델을 더 이상 교차시키지 않도록 이동해야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:389
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
msgctxt "@option:check"
msgid "Ensure models are kept apart"
msgstr "모델이 분리되어 있는지 확인하십시오"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:397
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:399
msgctxt "@info:tooltip"
msgid "Should models on the platform be moved down to touch the build plate?"
msgstr "플랫폼의 모델을 빌드 플레이트에 닿도록 아래로 움직여야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:402
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:404
msgctxt "@option:check"
msgid "Automatically drop models to the build plate"
msgstr "모델을 빌드 플레이트에 자동으로 놓기"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:414
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:416
msgctxt "@info:tooltip"
msgid "Show caution message in gcode reader."
msgstr "gcode 리더에 주의 메시지를 표시하십시오"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:423
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:425
msgctxt "@option:check"
msgid "Caution message in gcode reader"
msgstr "gcode 리더의 주의 메시지"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:430
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:432
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr "레이어가 호환 모드로 강제 설정되어야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:435
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:437
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr "강제 레이어보기 호환성 모드 (다시 시작해야 함)"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:451
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:453
msgctxt "@label"
msgid "Opening and saving files"
msgstr "파일 열기 및 저장"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:457
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:459
msgctxt "@info:tooltip"
msgid "Should models be scaled to the build volume if they are too large?"
msgstr "크기가 너무 큰 경우 모델을 빌드 볼륨에 맞게 조정해야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:462
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:464
msgctxt "@option:check"
msgid "Scale large models"
msgstr "대형 모델 확장"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:471
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:473
msgctxt "@info:tooltip"
msgid "An model may appear extremely small if its unit is for example in meters rather than millimeters. Should these models be scaled up?"
msgstr "단위가 밀리미터가 아닌 미터 단위 인 경우 모델이 매우 작게 나타날 수 있습니다. 이 모델을 확장해야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:476
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:478
msgctxt "@option:check"
msgid "Scale extremely small models"
msgstr "매우 작은 모델의 크기 조정"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:485
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:487
msgctxt "@info:tooltip"
msgid "Should a prefix based on the printer name be added to the print job name automatically?"
msgstr "프린터 이름에 기반한 접두어가 인쇄 작업 이름에 자동으로 추가되어야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:490
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:492
msgctxt "@option:check"
msgid "Add machine prefix to job name"
msgstr "작업 이름에 기계 접두어 추가"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:499
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:501
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "프로젝트 파일을 저장할 때 요약이 표시되어야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:505
msgctxt "@option:check"
msgid "Show summary dialog when saving project"
msgstr "프로젝트 저장시 요약 대화 상자 표시"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:514
msgctxt "@info:tooltip"
msgid "Default behavior when opening a project file"
msgstr "프로젝트 파일을 열 때 기본 동작"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:520
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
msgctxt "@window:text"
msgid "Default behavior when opening a project file: "
msgstr "프로젝트 파일을 열 때 기본 동작 "
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:533
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
msgctxt "@option:openProject"
msgid "Always ask"
msgstr "항상 물어보다"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:534
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:536
msgctxt "@option:openProject"
msgid "Always open as a project"
msgstr "항상 프로젝트로 여십시오"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:535
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:537
msgctxt "@option:openProject"
msgid "Always import models"
msgstr "항상 모델 가져 오기"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:571
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:573
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr "프로필을 변경하고 다른 프로필로 전환하면 수정 사항을 유지할지 여부를 묻는 대화 상자가 표시되거나 기본 행동을 선택하고 해당 대화 상자를 다시 표시 할 수 없습니다."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:580
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:582
msgctxt "@label"
msgid "Override Profile"
msgstr "프로필 재정의"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:629
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:631
msgctxt "@label"
msgid "Privacy"
msgstr "은둔"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:636
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:638
msgctxt "@info:tooltip"
msgid "Should Cura check for updates when the program is started?"
msgstr "Cura가 프로그램이 시작될 때 업데이트를 확인해야합니까?"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:641
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:643
msgctxt "@option:check"
msgid "Check for updates on start"
msgstr "시작시 업데이트 확인"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:651
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:653
msgctxt "@info:tooltip"
msgid "Should anonymous data about your print be sent to Ultimaker? Note, no models, IP addresses or other personally identifiable information is sent or stored."
msgstr "인쇄물에 대한 익명의 데이터를 Ultimaker로 보내야합니까? 모델, IP 주소 또는 기타 개인 식별 정보는 전송되거나 저장되지 않습니다."
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:656
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:658
msgctxt "@option:check"
msgid "Send (anonymous) print information"
msgstr "인쇄 (익명) 인쇄 정보"
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:674
+msgctxt "@label"
+msgid "Experimental"
+msgstr "실험적"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:680
+msgctxt "@info:tooltip"
+msgid "Use multi build plate functionality"
+msgstr "다수의 빌드 플레이트를 기능적으로 사용하기"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:685
+msgctxt "@option:check"
+msgid "Use multi build plate functionality (restart required)"
+msgstr "다수의 빌드 플레이트 기능성 사용(다시 시작해야 합니다)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
+msgctxt "@info:tooltip"
+msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
+msgstr "새롭게 로드한 모델을 빌드 플레이트에 정렬해야 합니까? 다수의 빌드 플레이트와 연계하여 사용(실험 중)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
+msgctxt "@option:check"
+msgid "Do not arrange objects on load"
+msgstr "로드 중인 대상물을 정렬하지 마십시오"
+
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:514
msgctxt "@title:tab"
msgid "Printers"
msgstr "프린터"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:37
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:51
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:137
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:138
msgctxt "@action:button"
msgid "Activate"
msgstr "활성화"
@@ -2885,7 +3164,7 @@ msgid "Waiting for a printjob"
msgstr "인쇄거리를 기다리는 중"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:448
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:518
msgctxt "@title:tab"
msgid "Profiles"
msgstr "프로필"
@@ -2911,13 +3190,13 @@ msgid "Duplicate"
msgstr "복제"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:113
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:201
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:182
msgctxt "@action:button"
msgid "Import"
msgstr "가져오다"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:119
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:212
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:193
msgctxt "@action:button"
msgid "Export"
msgstr "내보내다"
@@ -2983,7 +3262,7 @@ msgid "Export Profile"
msgstr "프로필 내보내기"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:15
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:516
msgctxt "@title:tab"
msgid "Materials"
msgstr "자재"
@@ -2998,60 +3277,60 @@ msgctxt "@action:label %1 is printer name"
msgid "Printer: %1"
msgstr "프린터"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:149
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:150
msgctxt "@action:button"
msgid "Create"
msgstr "창조하다"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:160
msgctxt "@action:button"
msgid "Duplicate"
msgstr "복제"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:319
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:298
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:306
msgctxt "@title:window"
msgid "Import Material"
msgstr "재료 가져 오기"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:320
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:307
msgctxt "@info:status Don't translate the XML tags or !"
msgid "Could not import material %1: %2"
msgstr "자재를 가져올 수 없습니다"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:324
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:311
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully imported material %1"
msgstr "자재를 성공적으로 가져왔습니다"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:343
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:329
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:344
msgctxt "@title:window"
msgid "Export Material"
msgstr "자재 내보내기"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:362
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:348
msgctxt "@info:status Don't translate the XML tags and !"
msgid "Failed to export material to %1: %2"
msgstr "자재를 내보내는데 실패했습니다"
-#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:368
+#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:354
msgctxt "@info:status Don't translate the XML tag !"
msgid "Successfully exported material to %1"
msgstr "자재를 성공적으로 내보냈습니다"
#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:18
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:793
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:869
msgctxt "@title:window"
msgid "Add Printer"
msgstr "프린터 추가"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:185
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:194
msgctxt "@label"
msgid "Printer Name:"
msgstr "프린터 이름 :"
-#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/AddMachineDialog.qml:217
msgctxt "@action:button"
msgid "Add Printer"
msgstr "프린터 추가"
@@ -3178,12 +3457,7 @@ msgctxt "@label"
msgid "Profile:"
msgstr "윤곽:"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:66
-msgctxt "@"
-msgid "No Profile Available"
-msgstr "프로필을 사용할 수 없음"
-
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:104
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:102
msgctxt "@tooltip"
msgid ""
"Some setting/override values are different from the values stored in the profile.\n"
@@ -3191,37 +3465,37 @@ msgid ""
"Click to open the profile manager."
msgstr "일부 설정 / 대체 값은 프로파일에 저장된 값과 다릅니다.\n\n프로파일 매니저를 열려면 클릭하십시오."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:152
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:150
msgctxt "@label:textbox"
msgid "Search..."
msgstr "찾다..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:483
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:482
msgctxt "@action:menu"
msgid "Copy value to all extruders"
msgstr "모든 압출기에 값 복사"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:497
msgctxt "@action:menu"
msgid "Hide this setting"
msgstr "이 설정 숨기기"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:508
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:507
msgctxt "@action:menu"
msgid "Don't show this setting"
msgstr "이 설정을 표시하지 않음"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:512
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:511
msgctxt "@action:menu"
msgid "Keep this setting visible"
msgstr "이 설정을 계속 표시하십시오"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:531
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:530
msgctxt "@action:menu"
msgid "Configure setting visiblity..."
msgstr "설정 표시 설정 ..."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:123
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:250
msgctxt "@label"
msgid ""
"Some hidden settings use values different from their normal calculated value.\n"
@@ -3229,27 +3503,27 @@ msgid ""
"Click to make these settings visible."
msgstr "일부 숨겨진 설정은 정상 계산 값과 다른 값을 사용합니다.\n\n이 설정을 표시하려면 클릭하십시오."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:62
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
msgctxt "@label Header for list of settings."
msgid "Affects"
msgstr "영향"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:67
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:66
msgctxt "@label Header for list of settings."
msgid "Affected By"
msgstr "영향을 받다"
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:157
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:156
msgctxt "@label"
-msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders"
-msgstr "이 설정은 항상 모든 압출기간에 공유됩니다. 여기에서 변경하면 모든 압출기의 값이 변경됩니다"
+msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
+msgstr "이 설정은 항상 모든 압출기 사이에 공유됩니다. 여기서 변경하면 모든 압출기에 대한 값이 변경됩니다."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:160
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:159
msgctxt "@label"
msgid "The value is resolved from per-extruder values "
msgstr "이 값은 압출기 값마다 결정됩니다 "
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:186
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:190
msgctxt "@label"
msgid ""
"This setting has a value that is different from the profile.\n"
@@ -3257,7 +3531,7 @@ msgid ""
"Click to restore the value of the profile."
msgstr "이 설정에는 프로필과 다른 값이 있습니다.\n\n프로필 값을 복원하려면 클릭하십시오."
-#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:284
+#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:288
msgctxt "@label"
msgid ""
"This setting is normally calculated, but it currently has an absolute value set.\n"
@@ -3265,71 +3539,71 @@ msgid ""
"Click to restore the calculated value."
msgstr "이 설정은 일반적으로 계산되지만 현재는 절대 값이 설정되어 있습니다.\n\n계산 된 값을 복원하려면 클릭하십시오."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid "Print Setup"
msgstr "인쇄 설정"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:120
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:128
msgctxt "@label:listbox"
msgid ""
"Print Setup disabled\n"
"G-code files cannot be modified"
msgstr "인쇄 설정 사용 안 함\nG 코드 파일은 수정할 수 없습니다"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:336
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:342
msgctxt "@label Hours and minutes"
msgid "00h 00min"
msgstr "00h 00min"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:354
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:359
msgctxt "@tooltip"
-msgid "Time specification
"
-msgstr "시간 사양
"
+msgid "Time specification"
+msgstr "시간 사양"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:429
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:441
msgctxt "@label"
msgid "Cost specification"
msgstr "비용 사양"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:434
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:445
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:455
msgctxt "@label m for meter"
msgid "%1m"
msgstr "%1m"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:435
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:446
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:447
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:456
msgctxt "@label g for grams"
msgid "%1g"
msgstr "%1g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:444
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:454
msgctxt "@label"
msgid "Total:"
msgstr "총계:"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:498
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:504
msgctxt "@label Print estimates: m for meters, g for grams, %4 is currency and %3 is print cost"
msgid "%1m / ~ %2g / ~ %4 %3"
msgstr "%1m / ~ %2g / ~ %4 %3"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:503
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:509
msgctxt "@label Print estimates: m for meters, g for grams"
msgid "%1m / ~ %2g"
msgstr "%1m / ~ %2g"
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:586
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
msgctxt "@tooltip"
msgid "Recommended Print Setup
Print with the recommended settings for the selected printer, material and quality."
msgstr "권장 인쇄 설정\n선택한 프린터, 재질 및 품질에 대한 권장 설정으로 인쇄하십시오."
-#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:591
+#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:596
msgctxt "@tooltip"
msgid "Custom Print Setup
Print with finegrained control over every last bit of the slicing process."
msgstr "사용자 정의 인쇄 설정\n마지막 스플 라이스 프로세스의 모든 비트를 미세하게 제어하여 인쇄하십시오."
-#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:49
+#: /home/ruben/Projects/Cura/resources/qml/Menus/MaterialMenu.qml:50
msgctxt "@title:menuitem %1 is the automatically selected material"
msgid "Automatic: %1"
msgstr "자동"
@@ -3339,6 +3613,16 @@ msgctxt "@title:menu menubar:toplevel"
msgid "&View"
msgstr "조망"
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:37
+msgctxt "@action:inmenu menubar:view"
+msgid "&Camera position"
+msgstr "카메라 위치(&C)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:52
+msgctxt "@action:inmenu menubar:view"
+msgid "&Build plate"
+msgstr "빌드 플레이트(&B)"
+
#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:40
msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer"
msgid "Automatic: %1"
@@ -3351,14 +3635,14 @@ msgid_plural "Print Selected Models With:"
msgstr[0] "선택된 모델 인쇄 :"
msgstr[1] "선택된 모델ㄷ 인쇄 :"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:83
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:114
msgctxt "@title:window"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "선택한 모델 곱하기"
msgstr[1] "선택한 모델ㄷ 곱하기"
-#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:108
+#: /home/ruben/Projects/Cura/resources/qml/Menus/ContextMenu.qml:139
msgctxt "@label"
msgid "Number of Copies"
msgstr "매수"
@@ -3374,7 +3658,7 @@ msgid "No printer connected"
msgstr "연결된 프린터 없음"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:90
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:138
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:139
msgctxt "@label"
msgid "Extruder"
msgstr "압출기"
@@ -3484,254 +3768,294 @@ msgctxt "@label"
msgid "Estimated time left"
msgstr "예상 남은 시간"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
msgctxt "@action:inmenu"
msgid "Toggle Fu&ll Screen"
msgstr "Fu & ll 화면 토글"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:79
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:86
msgctxt "@action:inmenu menubar:edit"
msgid "&Undo"
msgstr "풀어 놓다"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:89
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:96
msgctxt "@action:inmenu menubar:edit"
msgid "&Redo"
msgstr "다시하다"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:99
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:106
msgctxt "@action:inmenu menubar:file"
msgid "&Quit"
msgstr "그만두다"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:107
-msgctxt "@action:inmenu menubar:view"
-msgid "&Reset camera position"
-msgstr "카메라 위치 재설정(&R)"
-
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
+msgctxt "@action:inmenu menubar:view"
+msgid "&3D View"
+msgstr "3D 보기(&3)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+msgctxt "@action:inmenu menubar:view"
+msgid "&Front View"
+msgstr "앞에서 보기(&F)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
+msgctxt "@action:inmenu menubar:view"
+msgid "&Top View"
+msgstr "위에서 보기(&T)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
+msgctxt "@action:inmenu menubar:view"
+msgid "&Left Side View"
+msgstr "왼쪽에서 보기(&L)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+msgctxt "@action:inmenu menubar:view"
+msgid "&Right Side View"
+msgstr "오른쪽에서 보기(&R)"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
msgctxt "@action:inmenu"
msgid "Configure Cura..."
msgstr "Cura 구성 ..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:156
msgctxt "@action:inmenu menubar:printer"
msgid "&Add Printer..."
msgstr "프린터 추가 ..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:127
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
msgctxt "@action:inmenu menubar:printer"
msgid "Manage Pr&inters..."
msgstr "프린터 관리 ..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:169
msgctxt "@action:inmenu"
msgid "Manage Materials..."
msgstr "자료 관리 ..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:177
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "현재 설정 / 재정의로 프로필 업데이트"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:150
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:185
msgctxt "@action:inmenu menubar:profile"
msgid "&Discard current changes"
msgstr "현재 변경 사항 무시"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
msgctxt "@action:inmenu menubar:profile"
msgid "&Create profile from current settings/overrides..."
msgstr "현재 설정 / 재정의 프로필 작성"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:168
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
msgctxt "@action:inmenu menubar:profile"
msgid "Manage Profiles..."
msgstr "프로필 관리 ..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:175
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:210
msgctxt "@action:inmenu menubar:help"
msgid "Show Online &Documentation"
msgstr "온라인 및 문서 표시"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:183
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:218
msgctxt "@action:inmenu menubar:help"
msgid "Report a &Bug"
msgstr "버그보고"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:191
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
msgctxt "@action:inmenu menubar:help"
msgid "&About..."
msgstr "대략..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:233
msgctxt "@action:inmenu menubar:edit"
msgid "Delete &Selected Model"
msgid_plural "Delete &Selected Models"
msgstr[0] "선택한 모델 삭제 및 선택"
msgstr[1] "선택한 모델들 삭제 및 선택"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:208
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:243
msgctxt "@action:inmenu menubar:edit"
msgid "Center Selected Model"
msgid_plural "Center Selected Models"
msgstr[0] "선택한 모델 중심"
msgstr[1] "선택한 모델들 중심"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:217
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:252
msgctxt "@action:inmenu menubar:edit"
msgid "Multiply Selected Model"
msgid_plural "Multiply Selected Models"
msgstr[0] "선택한 모델 곱하기"
msgstr[1] "선택한 모델ㄷ 곱하기"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:226
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:261
msgctxt "@action:inmenu"
msgid "Delete Model"
msgstr "모델 삭제"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:234
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:269
msgctxt "@action:inmenu"
msgid "Ce&nter Model on Platform"
msgstr "플랫폼에 대한 모델"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:275
msgctxt "@action:inmenu menubar:edit"
msgid "&Group Models"
msgstr "그룹 모델"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:250
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:295
msgctxt "@action:inmenu menubar:edit"
msgid "Ungroup Models"
msgstr "모델 그룹 해제"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:260
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:305
msgctxt "@action:inmenu menubar:edit"
msgid "&Merge Models"
msgstr "모델 합치기"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:270
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:315
msgctxt "@action:inmenu"
msgid "&Multiply Model..."
msgstr "모델 곱하기"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:277
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:322
msgctxt "@action:inmenu menubar:edit"
msgid "&Select All Models"
msgstr "모든 모델 선택"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:287
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:332
msgctxt "@action:inmenu menubar:edit"
msgid "&Clear Build Plate"
msgstr "빌드 플레이트 지우기"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:297
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:342
msgctxt "@action:inmenu menubar:file"
msgid "Re&load All Models"
msgstr "모든 모델 새로고치"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:306
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
+msgctxt "@action:inmenu menubar:edit"
+msgid "Arrange All Models To All Build Plates"
+msgstr "모든 모델을 모든 빌드 플레이트에 정렬"
+
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange All Models"
msgstr "모든 모델 정렬"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:314
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
msgctxt "@action:inmenu menubar:edit"
msgid "Arrange Selection"
msgstr "선택 정렬"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:321
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model Positions"
msgstr "모든 모델 위치 재설정"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:328
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:380
msgctxt "@action:inmenu menubar:edit"
msgid "Reset All Model &Transformations"
msgstr "모든 모델 및 변환 재설정"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:335
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:387
msgctxt "@action:inmenu menubar:file"
msgid "&Open File(s)..."
msgstr "파일 열기"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:343
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:395
msgctxt "@action:inmenu menubar:file"
msgid "&New Project..."
msgstr "새 프로젝트..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:402
msgctxt "@action:inmenu menubar:help"
msgid "Show Engine &Log..."
msgstr "엔진 및 로그 표시"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:410
msgctxt "@action:inmenu menubar:help"
msgid "Show Configuration Folder"
msgstr "구성 폴더 표시"
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:365
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:417
msgctxt "@action:menu"
msgid "Configure setting visibility..."
msgstr "설정 표시 설정 ..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:372
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:424
msgctxt "@action:menu"
msgid "Browse plugins..."
msgstr "플러그인 찾아보기 ..."
-#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:379
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:431
msgctxt "@action:menu"
msgid "Installed plugins..."
msgstr "설치된 플러그인 ..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:28
+#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:438
+msgctxt "@action:inmenu menubar:view"
+msgid "Expand/Collapse Sidebar"
+msgstr "사이드바 확장/축소"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
msgctxt "@label:PrintjobStatus"
msgid "Please load a 3D model"
msgstr "3D 모델을 로드하십시오"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:34
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr "슬라이스 준비 완료"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:36
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
msgctxt "@label:PrintjobStatus"
msgid "Slicing..."
msgstr "슬라이싱 ..."
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:38
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
msgctxt "@label:PrintjobStatus %1 is target operation"
msgid "Ready to %1"
msgstr "준비 완료"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:40
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
msgctxt "@label:PrintjobStatus"
msgid "Unable to Slice"
msgstr "슬라이스 할 수 없음"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:42
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:44
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr "슬라이스 사용 불가"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Slice current printjob"
+msgstr "현재 출력물 슬라이스"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
+msgctxt "@info:tooltip"
+msgid "Cancel slicing process"
+msgstr "슬라이싱 프로세스 취소"
+
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr "준비하다"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:162
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr "취소하다"
-#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:302
+#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:317
msgctxt "@info:tooltip"
msgid "Select the active output device"
msgstr "활성 출력 장치 선택"
#: /home/ruben/Projects/Cura/resources/qml/OpenFilesIncludingProjectsDialog.qml:19
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:620
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:696
msgctxt "@title:window"
msgid "Open file(s)"
msgstr "열린 파일"
@@ -3751,114 +4075,114 @@ msgctxt "@title:window"
msgid "Ultimaker Cura"
msgstr "Ultimaker Cura"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:81
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:102
msgctxt "@title:menu menubar:toplevel"
msgid "&File"
msgstr "파일"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:98
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:119
msgctxt "@action:inmenu menubar:file"
msgid "&Save Selection to File"
msgstr "선택 사항을 파일에 저장"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:107
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:128
msgctxt "@title:menu menubar:file"
msgid "Save &As..."
msgstr "다른 이름으로 저장..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:118
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
msgctxt "@title:menu menubar:file"
-msgid "Save project"
-msgstr "프로젝트 저장"
+msgid "Save &Project..."
+msgstr "프로젝트 저장(&P)..."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:141
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
msgctxt "@title:menu menubar:toplevel"
msgid "&Edit"
msgstr "편집하다"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:158
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:179
msgctxt "@title:menu"
msgid "&View"
msgstr "조망"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184
msgctxt "@title:menu"
msgid "&Settings"
msgstr "설정"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:165
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186
msgctxt "@title:menu menubar:toplevel"
msgid "&Printer"
msgstr "프린터"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:175
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:187
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:196
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:208
msgctxt "@title:menu"
msgid "&Material"
msgstr "자재"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:176
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:188
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:197
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:209
msgctxt "@title:menu"
msgid "&Profile"
msgstr "윤곽"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:180
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:201
msgctxt "@action:inmenu"
msgid "Set as Active Extruder"
msgstr "활성 압출기로 설정"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:198
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:219
msgctxt "@title:menu menubar:toplevel"
msgid "E&xtensions"
msgstr "확장 프로그램"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:232
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:253
msgctxt "@title:menu menubar:toplevel"
msgid "P&lugins"
msgstr "플러그인"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:240
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:261
msgctxt "@title:menu menubar:toplevel"
msgid "P&references"
msgstr "환경 설정"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:248
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:269
msgctxt "@title:menu menubar:toplevel"
msgid "&Help"
msgstr "도움"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:330
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:351
msgctxt "@action:button"
msgid "Open File"
msgstr "파일 열기"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:442
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:512
msgctxt "@title:tab"
msgid "Settings"
msgstr "설정"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:478
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:554
msgctxt "@title:window"
msgid "New project"
msgstr "새 프로젝트"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:479
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:555
msgctxt "@info:question"
msgid "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings."
msgstr "새 프로젝트를 시작 하시겠습니까? 빌드 플레이트 및 저장하지 않은 설정이 지워집니다."
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:721
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:797
msgctxt "@window:title"
msgid "Install Plugin"
msgstr "플러그인 설치"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:728
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:804
msgctxt "@title:window"
msgid "Open File(s)"
msgstr "파일 열기"
-#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:731
+#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:807
msgctxt "@text:window"
msgid "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one."
msgstr "선택한 파일 내에 하나 이상의 G 코드 파일이 있습니다. 한 번에 하나의 G 코드 파일 만 열 수 있습니다. G 코드 파일을 열려면 하나만 선택하십시오."
@@ -3883,97 +4207,82 @@ msgctxt "@action:label"
msgid "Don't show project summary on save again"
msgstr "프로젝트 요약을 다시 저장하지 마십시오"
-#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
-msgctxt "@action:button"
-msgid "Save"
-msgstr "저장하다"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:74
-msgctxt "@title:tab"
-msgid "Prepare"
-msgstr "준비하다"
-
-#: /home/ruben/Projects/Cura/resources/qml/Topbar.qml:100
-msgctxt "@title:tab"
-msgid "Monitor"
-msgstr "감시 장치"
-
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:163
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:175
msgctxt "@label"
msgid "Layer Height"
msgstr "층 높이"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:323
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:345
msgctxt "@tooltip"
msgid "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab"
msgstr "사용자 지정 프로필이 현재 활성 상태입니다. 품질 슬라이더를 실행하려면 사용자 지정 탭에서 기본 품질 프로필을 선택하십시오."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:340
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:362
msgctxt "@label"
msgid "Print Speed"
msgstr "인쇄 속도"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:350
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:374
msgctxt "@label"
msgid "Slower"
msgstr "천천히"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:361
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:385
msgctxt "@label"
msgid "Faster"
msgstr "빨리"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:388
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:423
msgctxt "@tooltip"
msgid "You have modified some profile settings. If you want to change these go to custom mode."
msgstr "일부 프로필 설정을 수정했습니다. 이러한 설정을 변경하려면 사용자 지정 모드로 이동하십시오."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:413
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:446
msgctxt "@label"
msgid "Infill"
msgstr "빈 공간 채우기"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:633
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:668
msgctxt "@label"
msgid "Gradual infill will gradually increase the amount of infill towards the top."
msgstr "점차적인 빈 공간 채우기는 점차적으로 빈 공간 채우기의 양을 증가시킵니다."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:645
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:680
msgctxt "@label"
msgid "Enable gradual"
msgstr "점진적으로 사용"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:712
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:747
msgctxt "@label"
msgid "Generate Support"
msgstr "지지물 생성"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:746
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:781
msgctxt "@label"
msgid "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing."
msgstr "돌출부가있는 모델의 부분을 지원하는 구조를 생성합니다. 이러한 구조가 없으면 이러한 부분이 인쇄 중에 붕괴됩니다."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:764
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:799
msgctxt "@label"
msgid "Support Extruder"
msgstr "압출기 지지물"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:816
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:851
msgctxt "@label"
msgid "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."
msgstr "지원할 압출기를 선택하십시오. 이렇게하면 모형 아래에 지지 구조가 만들어져 모델이 중간 공기에서 처지거나 인쇄되는 것을 방지합니다."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:839
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:874
msgctxt "@label"
msgid "Build Plate Adhesion"
msgstr "플레이트 접착력 강화"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:894
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:929
msgctxt "@label"
msgid "Enable printing a brim or raft. This will add a flat area around or under your object which is easy to cut off afterwards."
msgstr "테두리 또는 raft 인쇄를 사용합니다. 이렇게하면 개체 주변이나 아래에 평평한 영역이 추가되어 나중에 쉽게자를 수 있습니다."
-#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:934
+#: /home/ruben/Projects/Cura/resources/qml/SidebarSimple.qml:969
msgctxt "@label"
msgid "Need help improving your prints? Read the Ultimaker Troubleshooting Guides"
msgstr "인쇄물 개선에 도움이 필요하십니까?\nUltimaker 문제 해결 가이드 읽기"
@@ -3990,17 +4299,22 @@ msgctxt "@title:window"
msgid "Open project file"
msgstr "프로젝트 파일 열기"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:72
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:93
msgctxt "@text:window"
msgid "This is a Cura project file. Would you like to open it as a project or import the models from it?"
msgstr "이 파일은 Cura 프로젝트 파일입니다. 프로젝트로 열거나 모델을 가져 오시겠습니까?"
#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:103
+msgctxt "@text:window"
+msgid "Remember my choice"
+msgstr "내 선택을 기억하라"
+
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
msgctxt "@action:button"
msgid "Open as project"
msgstr "프로젝트로 열기"
-#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:122
+#: /home/ruben/Projects/Cura/resources/qml/AskOpenAsProjectOrModelsDialog.qml:131
msgctxt "@action:button"
msgid "Import models"
msgstr "모델 가져 오기"
@@ -4010,21 +4324,36 @@ msgctxt "@title:window"
msgid "Engine Log"
msgstr "엔진 로그"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:242
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:245
msgctxt "@label"
msgid "Material"
msgstr "자재"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:349
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:352
msgctxt "@label"
-msgid "Check compatibility"
-msgstr "호환성 확인"
+msgid "Check compatibility"
+msgstr "호환성 확인"
-#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:369
+#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
msgctxt "@tooltip"
msgid "Click to check the material compatibility on Ultimaker.com."
msgstr "Ultimaker.com의 재질 호환성을 확인하려면 클릭하십시오."
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:211
+msgctxt "@option:check"
+msgid "See only current build plate"
+msgstr "현재의 빌드 플레이트만 보기"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:227
+msgctxt "@action:button"
+msgid "Arrange to all build plates"
+msgstr "모든 빌드 플레이트 정렬"
+
+#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:247
+msgctxt "@action:button"
+msgid "Arrange current build plate"
+msgstr "현재의 빌드 플레이트 정렬"
+
#: MachineSettingsAction/plugin.json
msgctxt "description"
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc)"
@@ -4115,6 +4444,26 @@ msgctxt "name"
msgid "USB printing"
msgstr "USB 인쇄"
+#: PrepareStage/plugin.json
+msgctxt "description"
+msgid "Provides a prepare stage in Cura."
+msgstr "Cura에서 준비 단계 제공."
+
+#: PrepareStage/plugin.json
+msgctxt "name"
+msgid "Prepare Stage"
+msgstr "준비 단계"
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "description"
+msgid "Provides an edit window for direct script editing."
+msgstr "직접 스크립트 편집을 위한 편집창 제공."
+
+#: CuraLiveScriptingPlugin/plugin.json
+msgctxt "name"
+msgid "Live scripting tool"
+msgstr "라이브 스크립팅 도구"
+
#: RemovableDriveOutputDevice/plugin.json
msgctxt "description"
msgid "Provides removable drive hotplugging and writing support."
@@ -4135,6 +4484,16 @@ msgctxt "name"
msgid "UM3 Network Connection"
msgstr "UM3 네트워크 연결"
+#: MonitorStage/plugin.json
+msgctxt "description"
+msgid "Provides a monitor stage in Cura."
+msgstr "Cura에서 모니터 단계 제공."
+
+#: MonitorStage/plugin.json
+msgctxt "name"
+msgid "Monitor Stage"
+msgstr "모니터 단계"
+
#: FirmwareUpdateChecker/plugin.json
msgctxt "description"
msgid "Checks for firmware updates."
@@ -4147,8 +4506,8 @@ msgstr "펌웨어 업데이트 검사기"
#: CuraSolidWorksPlugin/plugin.json
msgctxt "description"
-msgid "Gives you the possibility to open certain files via SolidWorks itself. These are then converted and loaded into Cura"
-msgstr "SolidWorks 자체를 통해 특정 파일을 열 수 있습니다. 이것들은 변환되어 Cura에 로드됩니다"
+msgid "Gives you the possibility to open certain files using SolidWorks itself. Conversion is done by this plugin and additional optimizations."
+msgstr "SolidWorks를 사용하여 특정 파일을 열 수 있는 가능성을 제공합니다. 이 플러그인과 추가 최적화로 변환을 수행합니다."
#: CuraSolidWorksPlugin/plugin.json
msgctxt "name"
@@ -4215,6 +4574,16 @@ msgctxt "name"
msgid "Legacy Cura Profile Reader"
msgstr "레거시 Cura 프로필 리더"
+#: CuraBlenderPlugin/plugin.json
+msgctxt "description"
+msgid "Helps to open Blender files directly in Cura."
+msgstr "Cura에서 직접 Blender 파일을 열도록 도와줍니다."
+
+#: CuraBlenderPlugin/plugin.json
+msgctxt "name"
+msgid "Blender Integration (experimental)"
+msgstr "Blender 통합(실험 중)"
+
#: GCodeProfileReader/plugin.json
msgctxt "description"
msgid "Provides support for importing profiles from g-code files."
@@ -4375,6 +4744,16 @@ msgctxt "name"
msgid "Cura Profile Writer"
msgstr "Cura 프로필 작성자"
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "description"
+msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
+msgstr "재료 제조사가 드롭 인 UI를 사용하여 새로운 재료와 품질 프로필을 만들 수 있게 합니다."
+
+#: CuraPrintProfileCreator/plugin.json
+msgctxt "name"
+msgid "Print Profile Assistant"
+msgstr "프린트 프로필 어시스턴트"
+
#: 3MFWriter/plugin.json
msgctxt "description"
msgid "Provides support for writing 3MF files."
@@ -4415,6 +4794,156 @@ msgctxt "name"
msgid "Cura Profile Reader"
msgstr "Cura 프로필 판독기"
+#~ msgctxt "@label"
+#~ msgid "Unknown"
+#~ msgstr "알 수 없는"
+
+#~ msgctxt "@info:status"
+#~ msgid "Errors appeared while opening your SolidWorks file! Please check, whether it is possible to open your file in SolidWorks itself without any problems as well!"
+#~ msgstr "SolidWorks 파일을 여는 중 오류가 발생했습니다! 문제없이 SolidWorks에서 파일을 열 수 있는지 확인하십시오"
+
+#~ msgctxt "@info:status"
+#~ msgid "Error while starting %s!"
+#~ msgstr "시작하는 도중 오류가 발생했습니다!"
+
+#~ msgctxt "@item:inlistbox"
+#~ msgid "Simulation view"
+#~ msgstr "시뮬레이션 보기"
+
+#~ msgctxt "@info"
+#~ msgid "Cura collects anonymised slicing statistics. You can disable this in the preferences."
+#~ msgstr "Cura는 익명의 슬라이싱 통계를 수집합니다. 환경 설정에서 이 기능을 비활성화 할 수 있습니다."
+
+#~ msgctxt "@action:button"
+#~ msgid "Dismiss"
+#~ msgstr "버리다"
+
+#~ msgctxt "@menuitem"
+#~ msgid "Global"
+#~ msgstr "전역"
+
+#~ msgctxt "@label crash message"
+#~ msgid ""
+#~ "
A fatal exception has occurred. Please send us this Crash Report to fix the problem
\n"
+#~ "
Please use the \"Send report\" button to post a bug report automatically to our servers