mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-06-30 12:45:10 +08:00
CURA-4523 solved merge conflict
This commit is contained in:
commit
d19015e85f
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<!--
|
||||
The following template is useful for filing new issues. Processing an issue will go much faster when this is filled out.
|
||||
Before filing, please check if the issue already exists (either open or closed).
|
||||
|
||||
It is also helpful to attach a project (.3MF) file and Cura log file so we can debug issues quicker.
|
||||
Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations.
|
||||
|
||||
Thank you for using Cura!
|
||||
-->
|
||||
|
||||
**Application Version**
|
||||
<!-- The version of the application this issue occurs with -->
|
||||
|
||||
**Platform**
|
||||
<!-- Information about the platform the issue occurs on -->
|
||||
|
||||
**Qt**
|
||||
<!-- The version of Qt used (not necessary if you're using the version from Ultimaker's website) -->
|
||||
|
||||
**PyQt**
|
||||
<!-- The version of PyQt used (not necessary if you're using the version from Ultimaker's website) -->
|
||||
|
||||
**Display Driver**
|
||||
<!-- Video driver name and version -->
|
||||
|
||||
**Steps to Reproduce**
|
||||
<!-- Add the steps needed that lead up to the issue (replace this text) -->
|
||||
|
||||
**Actual Results**
|
||||
<!-- What happens after the above steps have been followed (replace this text) -->
|
||||
|
||||
**Expected results**
|
||||
<!-- What should happen after the above steps have been followed (replace this text) -->
|
||||
|
||||
**Additional Information**
|
||||
<!-- Extra information relevant to the issue, like screenshots (replace this text) -->
|
21
.gitignore
vendored
21
.gitignore
vendored
@ -33,20 +33,23 @@ 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/PostProcessingPlugin
|
||||
plugins/ProfileFlattener
|
||||
plugins/X3GWriter
|
||||
|
||||
#Build stuff
|
||||
CMakeCache.txt
|
||||
|
70
Jenkinsfile
vendored
70
Jenkinsfile
vendored
@ -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") {
|
||||
parallel_nodes(['linux && cura', 'windows && cura']) {
|
||||
// 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.'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
71
README.md
71
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 version>`\cura.log (Windows), or usually C:\Users\\`<your username>`\AppData\Roaming\cura\\`<Cura version>`\cura.log
|
||||
* $User/Library/Application Support/cura/`<Cura version>`/cura.log (OSX)
|
||||
* $USER/.local/share/cura/`<Cura version>`/cura.log (Ubuntu/Linux)
|
||||
* `%APPDATA%\cura\<Cura version>\cura.log` (Windows), or usually `C:\Users\\<your username>\AppData\Roaming\cura\<Cura version>\cura.log`
|
||||
* `$USER/Library/Application Support/cura/<Cura version>/cura.log` (OSX)
|
||||
* `$USER/.local/share/cura/<Cura version>/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,53 +18,26 @@ 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
|
||||
Plugins
|
||||
-------------
|
||||
* [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/Plugin-Directory) for details about creating and using plugins.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
* 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
|
||||
|
||||
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
|
||||
----------------
|
||||
@ -93,3 +60,7 @@ To submit your translation, ideally you would make two pull requests where all `
|
||||
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 `<message>` 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.
|
||||
|
@ -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}")
|
||||
|
25
cura/Arrange.py → cura/Arranging/Arrange.py
Executable file → Normal file
25
cura/Arrange.py → cura/Arranging/Arrange.py
Executable file → Normal file
@ -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
|
154
cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py
Normal file
154
cura/Arranging/ArrangeObjectsAllBuildPlatesJob.py
Normal file
@ -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()
|
7
cura/ArrangeObjectsJob.py → cura/Arranging/ArrangeObjectsJob.py
Executable file → Normal file
7
cura/ArrangeObjectsJob.py → cura/Arranging/ArrangeObjectsJob.py
Executable file → Normal file
@ -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
|
||||
|
7
cura/ShapeArray.py → cura/Arranging/ShapeArray.py
Executable file → Normal file
7
cura/ShapeArray.py → cura/Arranging/ShapeArray.py
Executable file → Normal file
@ -43,13 +43,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)
|
0
cura/Arranging/__init__.py
Normal file
0
cura/Arranging/__init__.py
Normal file
53
cura/BuildPlateModel.py
Normal file
53
cura/BuildPlateModel.py
Normal file
@ -0,0 +1,53 @@
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Logger import Logger
|
||||
from UM.Application import Application
|
||||
|
||||
|
||||
class BuildPlateModel(ListModel):
|
||||
maxBuildPlateChanged = pyqtSignal()
|
||||
activeBuildPlateChanged = pyqtSignal()
|
||||
selectionChanged = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||
|
||||
self._max_build_plate = 1 # default
|
||||
self._active_build_plate = -1
|
||||
self._selection_build_plates = []
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def createBuildPlateModel():
|
||||
return BuildPlateModel()
|
||||
|
||||
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
|
@ -37,7 +37,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,
|
||||
@ -53,7 +53,7 @@ class CrashHandler:
|
||||
self.exception_type = exception_type
|
||||
self.value = value
|
||||
self.traceback = tb
|
||||
self.dialog = QDialog()
|
||||
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()
|
||||
@ -71,6 +71,7 @@ class CrashHandler:
|
||||
if not application:
|
||||
sys.exit(1)
|
||||
|
||||
self.dialog = QDialog()
|
||||
self._createDialog()
|
||||
|
||||
## Creates a modal dialog.
|
||||
@ -288,4 +289,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)
|
||||
|
@ -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()
|
||||
|
||||
@ -124,5 +134,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)
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from PyQt5.QtNetwork import QLocalServer
|
||||
from PyQt5.QtNetwork import QLocalSocket
|
||||
@ -16,7 +17,6 @@ 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,15 +32,19 @@ 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.CuraSceneController import CuraSceneController
|
||||
|
||||
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
@ -53,12 +57,13 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
||||
from cura.Settings.UserProfilesModel import UserProfilesModel
|
||||
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
|
||||
|
||||
|
||||
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
|
||||
@ -72,8 +77,9 @@ from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
|
||||
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
||||
from cura.Settings.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 cura.BuildPlateModel import BuildPlateModel
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
@ -85,7 +91,6 @@ import sys
|
||||
import os.path
|
||||
import numpy
|
||||
import copy
|
||||
import urllib.parse
|
||||
import os
|
||||
import argparse
|
||||
import json
|
||||
@ -203,8 +208,11 @@ 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._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
|
||||
|
||||
@ -311,12 +319,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/arrange_objects_on_load", True)
|
||||
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")
|
||||
|
||||
@ -389,6 +400,8 @@ class CuraApplication(QtApplication):
|
||||
|
||||
self._plugin_registry.addSupportedPluginExtension("curaplugin", "Cura Plugin")
|
||||
|
||||
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
||||
|
||||
def _onEngineCreated(self):
|
||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||
|
||||
@ -409,6 +422,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"])
|
||||
@ -676,14 +697,22 @@ class CuraApplication(QtApplication):
|
||||
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(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
|
||||
self.getSettingInheritanceManager)
|
||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager",
|
||||
self.getSimpleModeSettingsManager)
|
||||
|
||||
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 2, "ObjectsModel", self.getObjectsModel)
|
||||
qmlRegisterSingletonType(BuildPlateModel, "Cura", 1, 2, "BuildPlateModel", self.getBuildPlateModel)
|
||||
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 2, "SceneController", self.getCuraSceneController)
|
||||
|
||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||
|
||||
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
|
||||
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
||||
|
||||
run_without_gui = self.getCommandLineOption("headless", False) or self.getCommandLineOption("invisible", False)
|
||||
run_without_gui = self.getCommandLineOption("headless", False)
|
||||
if not run_without_gui:
|
||||
self.initializeEngine()
|
||||
controller.setActiveStage("PrepareStage")
|
||||
@ -715,6 +744,22 @@ class CuraApplication(QtApplication):
|
||||
self._material_manager = MaterialManager.createMaterialManager()
|
||||
return self._material_manager
|
||||
|
||||
def getObjectsModel(self, *args):
|
||||
if self._object_manager is None:
|
||||
self._object_manager = ObjectsModel.createObjectsModel()
|
||||
return self._object_manager
|
||||
|
||||
def getBuildPlateModel(self, *args):
|
||||
if self._build_plate_model is None:
|
||||
self._build_plate_model = BuildPlateModel.createBuildPlateModel()
|
||||
|
||||
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()
|
||||
@ -850,7 +895,7 @@ class CuraApplication(QtApplication):
|
||||
scene_bounding_box = None
|
||||
is_block_slicing_node = False
|
||||
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), SceneNode) or (not node.getMeshData() and not node.callDecoration("getLayerData")):
|
||||
continue
|
||||
if node.callDecoration("isBlockSlicing"):
|
||||
is_block_slicing_node = True
|
||||
@ -967,7 +1012,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
Selection.clear()
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode:
|
||||
if not issubclass(type(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,6 +1020,9 @@ 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"):
|
||||
continue # i.e. node with layer data
|
||||
|
||||
Selection.add(node)
|
||||
|
||||
## Delete all nodes containing mesh data in the scene.
|
||||
@ -986,7 +1034,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
nodes = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode:
|
||||
if type(node) not in {SceneNode, CuraSceneNode}:
|
||||
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.
|
||||
@ -1002,13 +1050,15 @@ class CuraApplication(QtApplication):
|
||||
op.push()
|
||||
Selection.clear()
|
||||
|
||||
## Reset all translation on nodes with mesh data.
|
||||
self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
|
||||
|
||||
## 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 issubclass(type(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 +1086,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 issubclass(type(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"):
|
||||
continue # i.e. node with layer data
|
||||
nodes.append(node)
|
||||
|
||||
@ -1060,10 +1110,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 issubclass(type(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"):
|
||||
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.getBuildPlateModel().activeBuildPlate
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if not issubclass(type(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 +1142,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"):
|
||||
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 +1158,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 issubclass(type(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 +1166,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"):
|
||||
continue # i.e. node with layer data
|
||||
if node in nodes: # exclude selected node from fixed_nodes
|
||||
continue
|
||||
fixed_nodes.append(node)
|
||||
@ -1110,7 +1186,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 issubclass(type(node), SceneNode) or not node.getMeshData():
|
||||
continue
|
||||
|
||||
nodes.append(node)
|
||||
@ -1127,7 +1203,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 +1277,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.getBuildPlateModel().activeBuildPlate))
|
||||
group_node.setParent(self.getController().getScene().getRoot())
|
||||
group_node.setSelectable(True)
|
||||
center = Selection.getSelectionCenter()
|
||||
@ -1349,8 +1426,15 @@ class CuraApplication(QtApplication):
|
||||
min_offset = 8
|
||||
|
||||
self.fileLoaded.emit(filename)
|
||||
arrange_objects_on_load = (
|
||||
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
|
||||
Preferences.getInstance().getValue("cura/arrange_objects_on_load"))
|
||||
target_build_plate = self.getBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
|
||||
|
||||
for original_node in nodes:
|
||||
node = CuraSceneNode() # We want our own CuraSceneNode
|
||||
node.setMeshData(original_node.getMeshData())
|
||||
|
||||
for node in nodes:
|
||||
node.setSelectable(True)
|
||||
node.setName(os.path.basename(filename))
|
||||
|
||||
@ -1377,21 +1461,23 @@ 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)
|
||||
|
||||
node.addDecorator(BuildPlateDecorator(target_build_plate))
|
||||
|
||||
op = AddSceneNodeOperation(node, scene.getRoot())
|
||||
op.push()
|
||||
|
101
cura/CuraSceneController.py
Normal file
101
cura/CuraSceneController.py
Normal file
@ -0,0 +1,101 @@
|
||||
from UM.Logger import Logger
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, QObject
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from cura.ObjectsModel import ObjectsModel
|
||||
from cura.BuildPlateModel import BuildPlateModel
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Selection import Selection
|
||||
|
||||
|
||||
class CuraSceneController(QObject):
|
||||
def __init__(self, objects_model: ObjectsModel, build_plate_model: BuildPlateModel):
|
||||
super().__init__()
|
||||
|
||||
self._objects_model = objects_model
|
||||
self._build_plate_model = 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 issubclass(type(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._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._build_plate_model.setItems(build_plates)
|
||||
# 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")
|
||||
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"]
|
||||
Selection.clear()
|
||||
Selection.add(node)
|
||||
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||
if build_plate_number is not None and build_plate_number != -1:
|
||||
self._build_plate_model.setActiveBuildPlate(build_plate_number)
|
||||
|
||||
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._build_plate_model.setActiveBuildPlate(nr)
|
||||
self._objects_model.setActiveBuildPlate(nr)
|
||||
|
||||
@staticmethod
|
||||
def createCuraSceneController():
|
||||
objects_model = Application.getInstance().getObjectsModel()
|
||||
build_plate_model = Application.getInstance().getBuildPlateModel()
|
||||
return CuraSceneController(objects_model = objects_model, build_plate_model = build_plate_model)
|
@ -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)
|
||||
|
49
cura/ObjectsModel.py
Normal file
49
cura/ObjectsModel.py
Normal file
@ -0,0 +1,49 @@
|
||||
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
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
||||
if not issubclass(type(node), SceneNode) or (not node.getMeshData() and not node.callDecoration("getLayerData")):
|
||||
continue
|
||||
if not node.callDecoration("isSliceable"):
|
||||
continue
|
||||
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||
if filter_current_build_plate and node_build_plate_number != active_build_plate_number:
|
||||
continue
|
||||
nodes.append({
|
||||
"name": node.getName(),
|
||||
"isSelected": Selection.isSelected(node),
|
||||
"isOutsideBuildArea": node.isOutsideBuildArea(),
|
||||
"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()
|
27
cura/Operations/SetBuildPlateNumberOperation.py
Normal file
27
cura/Operations/SetBuildPlateNumberOperation.py
Normal file
@ -0,0 +1,27 @@
|
||||
# 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:
|
||||
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)
|
0
cura/Operations/__init__.py
Normal file
0
cura/Operations/__init__.py
Normal file
@ -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 between two models, applicable for small models
|
||||
|
||||
Preferences.getInstance().addPreference("physics/automatic_push_free", True)
|
||||
Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
|
||||
@ -57,10 +58,11 @@ class PlatformPhysics:
|
||||
|
||||
# Only check nodes inside build area.
|
||||
nodes = [node for node in nodes if (hasattr(node, "_outside_buildarea") and not node._outside_buildarea)]
|
||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
||||
|
||||
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 issubclass(type(node), SceneNode) or node.getBoundingBox() is None:
|
||||
continue
|
||||
|
||||
bbox = node.getBoundingBox()
|
||||
@ -76,11 +78,12 @@ class PlatformPhysics:
|
||||
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 +101,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 +118,41 @@ 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_scale_factor = self._move_factor
|
||||
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
|
||||
if abs(temp_x_factor) > abs(temp_y_factor):
|
||||
temp_scale_factor = temp_x_factor
|
||||
else:
|
||||
temp_scale_factor = 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.
|
||||
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()
|
||||
|
@ -54,10 +54,10 @@ 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
|
||||
|
||||
@ -68,10 +68,15 @@ class PrintInformation(QObject):
|
||||
self._base_name = ""
|
||||
self._abbr_machine = ""
|
||||
self._job_name = ""
|
||||
self._project_name = ""
|
||||
self._active_build_plate = 0
|
||||
self._initVariablesWithBuildPlate(self._active_build_plate)
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._updateJobName)
|
||||
Application.getInstance().fileLoaded.connect(self.setBaseName)
|
||||
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
|
||||
Application.getInstance().workspaceLoaded.connect(self.setProjectName)
|
||||
|
||||
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
|
||||
|
||||
self._active_material_container = None
|
||||
@ -83,7 +88,7 @@ class PrintInformation(QObject):
|
||||
# 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 +106,26 @@ class PrintInformation(QObject):
|
||||
|
||||
self._print_time_message_values = {}
|
||||
|
||||
# Full fill message values using keys from _print_time_message_translations
|
||||
for key in self._print_time_message_translations.keys():
|
||||
self._print_time_message_values[key] = Duration(None, self)
|
||||
|
||||
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[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,64 +141,71 @@ 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, material_amounts):
|
||||
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):
|
||||
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):
|
||||
def _calculateInformation(self, build_plate_number):
|
||||
if Application.getInstance().getGlobalContainerStack() 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"))
|
||||
|
||||
@ -215,10 +243,10 @@ class PrintInformation(QObject):
|
||||
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,7 +257,8 @@ class PrintInformation(QObject):
|
||||
if preference != "cura/material_settings":
|
||||
return
|
||||
|
||||
self._calculateInformation()
|
||||
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
|
||||
self._calculateInformation(build_plate_number)
|
||||
|
||||
def _onActiveMaterialChanged(self):
|
||||
if self._active_material_container:
|
||||
@ -245,8 +274,22 @@ class PrintInformation(QObject):
|
||||
self._active_material_container = active_material_containers[0]
|
||||
self._active_material_container.metaDataChanged.connect(self._onMaterialMetaDataChanged)
|
||||
|
||||
def _onActiveBuildPlateChanged(self):
|
||||
new_active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
||||
if new_active_build_plate != self._active_build_plate:
|
||||
self._active_build_plate = new_active_build_plate
|
||||
|
||||
self._initVariablesWithBuildPlate(self._active_build_plate)
|
||||
|
||||
self.materialLengthsChanged.emit()
|
||||
self.materialWeightsChanged.emit()
|
||||
self.materialCostsChanged.emit()
|
||||
self.materialNamesChanged.emit()
|
||||
self.currentPrintTimeChanged.emit()
|
||||
|
||||
def _onMaterialMetaDataChanged(self, *args, **kwargs):
|
||||
self._calculateInformation()
|
||||
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
|
||||
self._calculateInformation(build_plate_number)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setJobName(self, name):
|
||||
@ -340,7 +383,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 +393,12 @@ class PrintInformation(QObject):
|
||||
return result
|
||||
|
||||
# Simulate message with zero time duration
|
||||
def setToZeroPrintInformation(self):
|
||||
def setToZeroPrintInformation(self, build_plate_number):
|
||||
temp_message = {}
|
||||
for key in self._print_time_message_values.keys():
|
||||
if build_plate_number not in self._print_time_message_values:
|
||||
self._print_time_message_values[build_plate_number] = {}
|
||||
for key in self._print_time_message_values[build_plate_number].keys():
|
||||
temp_message[key] = 0
|
||||
|
||||
temp_material_amounts = [0]
|
||||
self._onPrintDurationMessage(temp_message, temp_material_amounts)
|
||||
self._onPrintDurationMessage(build_plate_number, temp_message, temp_material_amounts)
|
||||
|
26
cura/Scene/BuildPlateDecorator.py
Normal file
26
cura/Scene/BuildPlateDecorator.py
Normal file
@ -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 issubclass(type(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()
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
@ -66,7 +65,7 @@ class ConvexHullNode(SceneNode):
|
||||
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
|
||||
|
||||
if self.getParent():
|
||||
if self.getMeshData():
|
||||
if self.getMeshData() and issubclass(type(self._node), SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().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)
|
43
cura/Scene/CuraSceneNode.py
Normal file
43
cura/Scene/CuraSceneNode.py
Normal file
@ -0,0 +1,43 @@
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
## 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 = True
|
||||
|
||||
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().getBuildPlateModel().activeBuildPlate
|
||||
|
||||
def isSelectable(self) -> bool:
|
||||
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate
|
||||
|
||||
## 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()
|
0
cura/Scene/__init__.py
Normal file
0
cura/Scene/__init__.py
Normal file
@ -514,7 +514,7 @@ 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
|
||||
|
||||
@ -687,7 +687,7 @@ class ContainerManager(QObject):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack or not quality_name:
|
||||
return ""
|
||||
machine_definition = global_stack.getBottom()
|
||||
machine_definition = global_stack.definition
|
||||
|
||||
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
||||
if active_stacks is None:
|
||||
|
@ -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
|
||||
@ -218,32 +219,13 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
if type(profile_or_list) is not list:
|
||||
profile_or_list = [profile_or_list]
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
# Import all profiles
|
||||
for profile_index, profile in enumerate(profile_or_list):
|
||||
if profile_index == 0:
|
||||
# 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())
|
||||
if not profile.getMetaDataEntry("extruder"):
|
||||
@ -252,6 +234,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
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(
|
||||
@ -305,7 +290,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
quality_type_criteria["definition"] = profile.getDefinition().getId()
|
||||
|
||||
else:
|
||||
profile.setDefinition(fdmprinter)
|
||||
profile.setDefinition("fdmprinter")
|
||||
quality_type_criteria["definition"] = "fdmprinter"
|
||||
|
||||
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
|
||||
@ -422,6 +407,10 @@ 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")
|
||||
@ -442,11 +431,42 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
extruder_stack.setDefinition(extruder_definition)
|
||||
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
# create a new definition_changes container for the extruder stack
|
||||
definition_changes_id = self.uniqueName(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")
|
||||
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("setting_version", CuraApplication.SettingVersion)
|
||||
user_container.setDefinition(machine.definition.getId())
|
||||
|
||||
@ -456,7 +476,15 @@ 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)
|
||||
|
@ -8,6 +8,7 @@ from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
|
||||
from . import Exceptions
|
||||
from .CuraContainerStack import CuraContainerStack
|
||||
@ -16,6 +17,11 @@ from .ExtruderManager import ExtruderManager
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
|
||||
_EXTRUDER_SPECIFIC_DEFINITION_CHANGES_SETTINGS = ["machine_nozzle_size",
|
||||
"material_diameter"]
|
||||
|
||||
|
||||
## Represents an Extruder and its related containers.
|
||||
#
|
||||
#
|
||||
@ -39,6 +45,29 @@ class ExtruderStack(CuraContainerStack):
|
||||
# For backward compatibility: Register the extruder with the Extruder Manager
|
||||
ExtruderManager.getInstance().registerExtruder(self, stack.id)
|
||||
|
||||
# Now each machine will have at least one extruder stack. If this is the first extruder, the extruder-specific
|
||||
# settings such as nozzle size and material diameter should be moved from the machine's definition_changes to
|
||||
# the this extruder's definition_changes.
|
||||
#
|
||||
# We do this here because it is tooooo expansive to do it in the version upgrade: During the version upgrade,
|
||||
# when we are upgrading a definition_changes container file, there is NO guarantee that other files such as
|
||||
# machine an extruder stack files are upgraded before this, so we cannot read those files assuming they are in
|
||||
# the latest format.
|
||||
if self.getMetaDataEntry("position") == "0":
|
||||
for key in _EXTRUDER_SPECIFIC_DEFINITION_CHANGES_SETTINGS:
|
||||
setting_value = stack.definitionChanges.getProperty(key, "value")
|
||||
if setting_value is None:
|
||||
continue
|
||||
|
||||
setting_definition = stack.getSettingDefinition(key)
|
||||
new_instance = SettingInstance(setting_definition, self.definitionChanges)
|
||||
new_instance.setProperty("value", setting_value)
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
self.definitionChanges.addInstance(new_instance)
|
||||
self.definitionChanges.setDirty(True)
|
||||
|
||||
stack.definitionChanges.removeInstance(key, postpone_emit = True)
|
||||
|
||||
@override(ContainerStack)
|
||||
def getNextStack(self) -> Optional["GlobalStack"]:
|
||||
return super().getNextStack()
|
||||
|
@ -32,14 +32,16 @@ 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._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 +59,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,14 +86,17 @@ 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":
|
||||
self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
|
||||
|
||||
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)
|
||||
|
||||
## Makes sure that the stack upon which the container stack is placed is
|
||||
# kept up to date.
|
||||
def _updateNextStack(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
import os.path
|
||||
import zipfile
|
||||
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Math.Vector import Vector
|
||||
@ -15,9 +14,10 @@ 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
|
||||
|
||||
MYPY = False
|
||||
|
||||
@ -43,6 +43,7 @@ class ThreeMFReader(MeshReader):
|
||||
}
|
||||
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 +78,12 @@ 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
|
||||
|
||||
um_node = CuraSceneNode()
|
||||
um_node.addDecorator(BuildPlateDecorator(0))
|
||||
um_node.setName(node_name)
|
||||
transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
|
||||
um_node.setTransformation(transformation)
|
||||
mesh_builder = MeshBuilder()
|
||||
@ -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")
|
||||
|
@ -461,7 +461,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)
|
||||
@ -583,7 +584,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)
|
||||
@ -681,12 +682,7 @@ 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)
|
||||
@ -740,7 +736,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)
|
||||
|
@ -7,6 +7,7 @@ from UM.Logger import Logger
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Application import Application
|
||||
import UM.Scene.SceneNode
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
||||
import Savitar
|
||||
|
||||
@ -63,7 +64,7 @@ class ThreeMFWriter(MeshWriter):
|
||||
## Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
|
||||
# \returns Uranium Scenen node.
|
||||
def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()):
|
||||
if type(um_node) is not UM.Scene.SceneNode.SceneNode:
|
||||
if type(um_node) not in [UM.Scene.SceneNode.SceneNode, CuraSceneNode]:
|
||||
return None
|
||||
|
||||
savitar_node = Savitar.SceneNode()
|
||||
|
@ -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().getBuildPlateModel().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)
|
||||
@ -105,17 +107,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 +177,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
|
||||
|
||||
@ -190,17 +194,34 @@ class CuraEngineBackend(QObject, Backend):
|
||||
|
||||
## 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")
|
||||
self._invokeSlice()
|
||||
return
|
||||
|
||||
# see if we really have to slice
|
||||
active_build_plate = Application.getInstance().getBuildPlateModel().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:
|
||||
Logger.log("d", "Build plate %s has 0 objects to be sliced, skipping", build_plate_to_be_sliced)
|
||||
self._invokeSlice()
|
||||
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 = []
|
||||
if not hasattr(self._scene, "gcode_list"):
|
||||
self._scene.gcode_list = {}
|
||||
self._scene.gcode_list[build_plate_to_be_sliced] = [] #[] indexed by build plate number
|
||||
self._slicing = True
|
||||
self.slicingStarted.emit()
|
||||
|
||||
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()
|
||||
|
||||
@ -331,15 +357,6 @@ class CuraEngineBackend(QObject, Backend):
|
||||
else:
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice:
|
||||
if Application.getInstance().platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
else:
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
return
|
||||
# Preparation completed, send it to the backend.
|
||||
self._socket.sendMessage(job.getSliceMessage())
|
||||
|
||||
@ -363,7 +380,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_list[node.callDecoration("getBuildPlateNumber")] = gcode_list
|
||||
|
||||
if self._use_timer == enable_timer:
|
||||
return self._use_timer
|
||||
@ -375,33 +392,48 @@ 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 issubclass(type(source), SceneNode):
|
||||
return
|
||||
|
||||
root_scene_nodes_changed = False
|
||||
build_plate_changed = set()
|
||||
source_build_plate_number = source.callDecoration("getBuildPlateNumber")
|
||||
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
|
||||
# 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
|
||||
|
||||
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.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 +441,17 @@ 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.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 +471,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().getBuildPlateModel().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 +507,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._need_slicing:
|
||||
self.needsSlicing()
|
||||
self._onChanged()
|
||||
|
||||
@ -476,7 +521,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||
#
|
||||
# \param message The protobuf message containing sliced layer data.
|
||||
def _onOptimizedLayerMessage(self, message):
|
||||
self._stored_optimized_layer_data.append(message)
|
||||
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 +530,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 +547,44 @@ 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_list[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().getBuildPlateModel().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()
|
||||
# Testing call slice again, allow backend to restart by using the timer
|
||||
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_list[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_list[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace"))
|
||||
|
||||
## Creates a new socket connection.
|
||||
def _createSocket(self):
|
||||
@ -551,7 +614,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 +668,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.getBuildPlateModel().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
|
||||
|
||||
@ -653,7 +722,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||
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):
|
||||
|
@ -4,7 +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
|
||||
@ -17,6 +16,7 @@ from UM.Logger import Logger
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
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":
|
||||
@ -74,16 +82,7 @@ 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.addDecorator(BuildPlateDecorator(self._build_plate_number))
|
||||
|
||||
# Force garbage collection.
|
||||
# For some reason, Python has a tendency to keep the layer data
|
||||
|
@ -10,15 +10,19 @@ 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 as SceneNode
|
||||
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,9 +36,32 @@ 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 gcode, using global stack", key_fragments[1], key_fragments[0])
|
||||
elif len(key_fragments) != 1:
|
||||
Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end gcode", 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)
|
||||
return "{" + key + "}"
|
||||
@ -51,10 +78,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 +104,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)
|
||||
@ -104,7 +141,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
|
||||
|
||||
@ -133,12 +170,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 type(node) is SceneNode 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,16 +267,33 @@ 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")
|
||||
@ -250,8 +310,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.
|
||||
@ -276,13 +337,13 @@ class StartSliceJob(Job):
|
||||
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)
|
||||
|
||||
# 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():
|
||||
|
@ -17,7 +17,7 @@ catalog = i18nCatalog("cura")
|
||||
from cura import LayerDataBuilder
|
||||
from cura 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
|
||||
|
@ -59,8 +59,9 @@ class GCodeWriter(MeshWriter):
|
||||
Logger.log("e", "GCode Writer does not support non-text mode.")
|
||||
return False
|
||||
|
||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
||||
scene = Application.getInstance().getController().getScene()
|
||||
gcode_list = getattr(scene, "gcode_list")
|
||||
gcode_list = getattr(scene, "gcode_list")[active_build_plate]
|
||||
if gcode_list:
|
||||
for gcode in gcode_list:
|
||||
stream.write(gcode)
|
||||
|
@ -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):
|
||||
|
@ -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,7 +124,7 @@ 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()
|
||||
current_printer_definition = global_container_stack.definition
|
||||
profile.setDefinition(current_printer_definition.getId())
|
||||
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]
|
||||
@ -139,14 +143,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 +162,20 @@ class LegacyProfileReader(ProfileReader):
|
||||
data = stream.getvalue()
|
||||
profile.deserialize(data)
|
||||
|
||||
return profile
|
||||
#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)
|
||||
|
||||
#Only the extruder stack has an extruder metadata entry.
|
||||
profile.addMetaDataEntry("extruder", ExtruderManager.getInstance().getActiveExtruderStack().definition.getId())
|
||||
|
||||
#Split all settings into per-extruder and global settings.
|
||||
for setting_key in profile.getAllKeys():
|
||||
settable_per_extruder = global_container_stack.getProperty(setting_key, "settable_per_extruder")
|
||||
if settable_per_extruder:
|
||||
global_profile.removeInstance(setting_key)
|
||||
else:
|
||||
profile.removeInstance(setting_key)
|
||||
|
||||
return [global_profile, profile]
|
||||
|
@ -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)
|
||||
@ -217,8 +206,8 @@ 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
|
||||
@ -226,24 +215,22 @@ class MachineSettingsAction(MachineAction):
|
||||
if not self._global_container_stack.getMetaDataEntry("has_materials", False):
|
||||
return
|
||||
|
||||
material = ExtruderManager.getInstance().getActiveExtruderStack().material
|
||||
material_diameter = material.getProperty("material_diameter", "value")
|
||||
extruder_stack = self._global_container_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))
|
||||
definition_changes = self._global_container_stack.definitionChanges
|
||||
machine_diameter = definition_changes.getProperty("material_diameter", "value")
|
||||
machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
|
||||
if not machine_diameter:
|
||||
machine_diameter = self._global_container_stack.definition.getProperty("material_diameter", "value")
|
||||
machine_diameter = extruder_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)
|
||||
@ -251,45 +238,47 @@ class MachineSettingsAction(MachineAction):
|
||||
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()
|
||||
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()
|
||||
|
||||
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")
|
||||
if old_material == self._empty_container:
|
||||
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 = 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.pop("brand", None)
|
||||
search_criteria["color_name"] = "Generic"
|
||||
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]
|
||||
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 = 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())
|
||||
Logger.log("i", "Selecting new material: %s", materials[0].getId())
|
||||
|
||||
stack.material = materials[0]
|
||||
extruder_stack.material = materials[0]
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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", "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
|
||||
@ -432,7 +452,7 @@ Cura.MachineAction
|
||||
property int areaHeight: parent.height - y
|
||||
property string settingKey: "machine_extruder_start_code"
|
||||
property bool isExtruderSetting: true
|
||||
}
|
||||
}
|
||||
}
|
||||
Column {
|
||||
height: parent.height
|
||||
@ -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,7 +587,10 @@ Cura.MachineAction
|
||||
TextField
|
||||
{
|
||||
id: textField
|
||||
text: (propertyProvider.properties.value) ? propertyProvider.properties.value : ""
|
||||
text: {
|
||||
const value = propertyProvider.properties.value;
|
||||
return value ? value : "";
|
||||
}
|
||||
validator: RegExpValidator { regExp: _allowNegative ? /-?[0-9\.]{0,6}/ : /[0-9\.]{0,6}/ }
|
||||
onEditingFinished:
|
||||
{
|
||||
@ -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 "";
|
||||
}
|
||||
|
@ -145,6 +145,7 @@ Item {
|
||||
property var settingDefinitionsModel: addedSettingsModel
|
||||
property var propertyProvider: provider
|
||||
property var globalPropertyProvider: inheritStackProvider
|
||||
property var externalResetHandler: false
|
||||
|
||||
//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,
|
||||
|
@ -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().getBuildPlateModel().activeBuildPlate
|
||||
head_position = None # Indicates the current position of the print head
|
||||
nozzle_node = None
|
||||
|
||||
@ -105,7 +106,7 @@ class SimulationPass(RenderPass):
|
||||
nozzle_node = node
|
||||
nozzle_node.setVisible(False)
|
||||
|
||||
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
|
||||
elif issubclass(type(node), SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible() and node.callDecoration("getBuildPlateNumber") == active_build_plate:
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
continue
|
||||
|
@ -4,6 +4,7 @@
|
||||
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
|
||||
@ -414,6 +417,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)
|
||||
|
@ -187,7 +187,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 +212,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();
|
||||
|
||||
|
@ -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 anonymized 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:
|
||||
@ -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)),
|
||||
|
@ -110,7 +110,7 @@ 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:
|
||||
|
@ -234,7 +234,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
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})
|
||||
self._print_view = Application.getInstance().createQmlComponent(path, {"OutputDevice": self})
|
||||
if self._print_view is not None:
|
||||
self._print_view.show()
|
||||
|
||||
@ -243,6 +243,11 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
self._selected_printer = self._automatic_printer # reset to default option
|
||||
self._request_job = [nodes, file_name, filter_by_machine, file_handler, kwargs]
|
||||
|
||||
# the build plates to be sent
|
||||
gcodes = getattr(Application.getInstance().getController().getScene(), "gcode_list")
|
||||
self._job_list = list(gcodes.keys())
|
||||
Logger.log("d", "build plates to be sent to printer: %s", (self._job_list))
|
||||
|
||||
if self._stage != OutputStage.ready:
|
||||
if self._error_message:
|
||||
self._error_message.hide()
|
||||
@ -252,14 +257,15 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
self._error_message.show()
|
||||
return
|
||||
|
||||
self._add_build_plate_number = len(self._job_list) > 1
|
||||
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:
|
||||
@ -274,28 +280,34 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
@pyqtSlot()
|
||||
def sendPrintJob(self):
|
||||
nodes, file_name, filter_by_machine, file_handler, kwargs = self._request_job
|
||||
require_printer_name = self._selected_printer["unique_name"]
|
||||
output_build_plate_number = self._job_list.pop(0)
|
||||
gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")[output_build_plate_number]
|
||||
|
||||
self._send_gcode_start = time.time()
|
||||
Logger.log("d", "Sending print job [%s] to host..." % file_name)
|
||||
Logger.log("d", "Sending print job [%s] to host, build plate [%s]..." % (file_name, output_build_plate_number))
|
||||
|
||||
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
|
||||
if self._add_build_plate_number:
|
||||
self._file_name = "%s_%d.gcode.gz" % (file_name, output_build_plate_number)
|
||||
else:
|
||||
self._file_name = "%s.gcode.gz" % (file_name)
|
||||
self._showProgressMessage()
|
||||
|
||||
new_request = self._buildSendPrintJobHttpRequest(require_printer_name)
|
||||
require_printer_name = self._selected_printer["unique_name"]
|
||||
|
||||
new_request = self._buildSendPrintJobHttpRequest(require_printer_name, gcode)
|
||||
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()
|
||||
# See _finishedPrintJobPostRequest()
|
||||
|
||||
def _buildSendPrintJobHttpRequest(self, require_printer_name):
|
||||
def _buildSendPrintJobHttpRequest(self, require_printer_name, gcode):
|
||||
api_url = QUrl(self._api_base_uri + "print_jobs/")
|
||||
request = QNetworkRequest(api_url)
|
||||
# Create multipart request and add the g-code.
|
||||
@ -304,9 +316,8 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
# Add gcode
|
||||
part = QHttpPart()
|
||||
part.setHeader(QNetworkRequest.ContentDispositionHeader,
|
||||
'form-data; name="file"; filename="%s"' % self._file_name)
|
||||
'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.
|
||||
@ -392,6 +403,9 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
|
||||
self._cleanupRequest()
|
||||
|
||||
if self._job_list: # start sending next job
|
||||
self.sendPrintJob()
|
||||
|
||||
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(
|
||||
|
@ -675,7 +675,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
||||
self._print_finished = True
|
||||
self.writeStarted.emit(self)
|
||||
self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
|
||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
||||
self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")[active_build_plate]
|
||||
|
||||
print_information = Application.getInstance().getPrintInformation()
|
||||
warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about.
|
||||
|
@ -122,21 +122,6 @@ class VersionUpgrade30to31(VersionUpgrade):
|
||||
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")
|
||||
|
||||
# Update version numbers
|
||||
parser["general"]["version"] = "2"
|
||||
parser["metadata"]["setting_version"] = "4"
|
||||
@ -215,133 +200,6 @@ class VersionUpgrade30to31(VersionUpgrade):
|
||||
|
||||
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, "")
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -737,7 +737,6 @@ class XmlMaterialProfile(InstanceContainer):
|
||||
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.
|
||||
|
55
resources/definitions/anycubic_i3_mega.def.json
Normal file
55
resources/definitions/anycubic_i3_mega.def.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"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": "*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{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}\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{travel_speed} ;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"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"id": "builder_premium_large",
|
||||
"version": 2,
|
||||
"name": "Builder Premium Large",
|
||||
"inherits": "fdmprinter",
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"id": "builder_premium_medium",
|
||||
"version": 2,
|
||||
"name": "Builder Premium Medium",
|
||||
"inherits": "fdmprinter",
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"id": "builder_premium_small",
|
||||
"version": 2,
|
||||
"name": "Builder Premium Small",
|
||||
"inherits": "fdmprinter",
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"id": "deltacomb",
|
||||
"version": 2,
|
||||
"name": "Deltacomb 3D",
|
||||
"inherits": "fdmprinter",
|
||||
|
@ -181,6 +181,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"platform_adhesion":
|
||||
{
|
||||
"label": "Build Plate Adhesion",
|
||||
|
@ -1614,7 +1614,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 +1635,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 +1644,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 +1665,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
|
||||
}
|
||||
|
54
resources/definitions/tevo_blackwidow.def.json
Normal file
54
resources/definitions/tevo_blackwidow.def.json
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"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": "prusai3_platform.stl",
|
||||
"preferred_quality": "*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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4595
resources/i18n/pt_PT/cura.po
Normal file
4595
resources/i18n/pt_PT/cura.po
Normal file
File diff suppressed because it is too large
Load Diff
210
resources/i18n/pt_PT/fdmextruder.def.json.po
Normal file
210
resources/i18n/pt_PT/fdmextruder.def.json.po
Normal file
@ -0,0 +1,210 @@
|
||||
# Cura JSON setting files
|
||||
# Copyright (C) 2017 Ultimaker
|
||||
# This file is distributed under the same license as the Cura package.
|
||||
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Cura 3.1\n"
|
||||
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
||||
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
|
||||
"PO-Revision-Date: 2017-12-07 13:41+0100\n"
|
||||
"Last-Translator: Bothof <info@bothof.nl>\n"
|
||||
"Language-Team: Bothof\n"
|
||||
"Language: pt_PT\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"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings label"
|
||||
msgid "Machine"
|
||||
msgstr "Máquina"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings description"
|
||||
msgid "Machine specific settings"
|
||||
msgstr "Definições específicas da máquina"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr "Extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr "A máquina extrusora utilizada para imprimir. Esta é utilizada em extrusões múltiplas."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_id label"
|
||||
msgid "Nozzle ID"
|
||||
msgstr "ID do bocal"
|
||||
|
||||
#: fdmextruder.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 "A ID do bocal para uma máquina de extrusão, tal como \"AA 0.4\" e \"BB 0.8\"."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_size label"
|
||||
msgid "Nozzle Diameter"
|
||||
msgstr "Diâmetro do bocal"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_size description"
|
||||
msgid ""
|
||||
"The inner diameter of the nozzle. Change this setting when using a non-"
|
||||
"standard nozzle size."
|
||||
msgstr "O diâmetro interno do bocal. Altere esta definição ao utilizar um tamanho de bocal não convencional."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr "Desvio X do bocal"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr "A coordenada X do desvio do bocal."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr "Desvio Y do bocal"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr "A coordenada Y do desvio do bocal."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr "G-Code inicial da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr "G-Code inicial a ser executado sempre que a extrusora for ligada."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr "Posição inicial absoluta da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs description"
|
||||
msgid ""
|
||||
"Make the extruder starting position absolute rather than relative to the "
|
||||
"last-known location of the head."
|
||||
msgstr "Torne a posição inicial da extrusora absoluta em vez de relativa à última posição conhecida da cabeça."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr "X da posição inicial da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "A coordenada X da posição inicial ao ligar a extrusora."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr "Y da posição inicial da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "A coordenada Y da posição inicial ao ligar a extrusora."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr "G-Code final da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr "G-Code final a ser executado sempre que a extrusora for desligada."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr "Posição final absoluta da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs description"
|
||||
msgid ""
|
||||
"Make the extruder ending position absolute rather than relative to the last-"
|
||||
"known location of the head."
|
||||
msgstr "Torne a posição final da extrusora absoluta em vez de relativa à última localização conhecida da cabeça."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr "X da posição final da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "A coordenada X da posição final ao desligar a extrusora."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr "Y da posição final da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "A coordenada Y da posição final ao desligar a extrusora."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr "Posição Z de preparação da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z description"
|
||||
msgid ""
|
||||
"The Z coordinate of the position where the nozzle primes at the start of "
|
||||
"printing."
|
||||
msgstr "A coordenada Z da posição de preparação do bocal ao iniciar a impressão."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr "Aderência à placa de construção"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr "Aderência"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr "Posição X de preparação da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x description"
|
||||
msgid ""
|
||||
"The X coordinate of the position where the nozzle primes at the start of "
|
||||
"printing."
|
||||
msgstr "A coordenada X da posição de preparação do bocal ao iniciar a impressão."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr "Posição Y de preparação da extrusora"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y description"
|
||||
msgid ""
|
||||
"The Y coordinate of the position where the nozzle primes at the start of "
|
||||
"printing."
|
||||
msgstr "A coordenada Y da posição de preparação do bocal ao iniciar a impressão."
|
5814
resources/i18n/pt_PT/fdmprinter.def.json.po
Normal file
5814
resources/i18n/pt_PT/fdmprinter.def.json.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
resources/meshes/anycubic_i3_mega_platform.stl
Normal file
BIN
resources/meshes/anycubic_i3_mega_platform.stl
Normal file
Binary file not shown.
BIN
resources/meshes/tevo_blackwidow.stl
Normal file
BIN
resources/meshes/tevo_blackwidow.stl
Normal file
Binary file not shown.
@ -17,7 +17,12 @@ Item
|
||||
property alias undo: undoAction;
|
||||
property alias redo: redoAction;
|
||||
|
||||
property alias homeCamera: homeCameraAction;
|
||||
property alias view3DCamera: view3DCameraAction;
|
||||
property alias viewFrontCamera: viewFrontCameraAction;
|
||||
property alias viewTopCamera: viewTopCameraAction;
|
||||
property alias viewLeftSideCamera: viewLeftSideCameraAction;
|
||||
property alias viewRightSideCamera: viewRightSideCameraAction;
|
||||
|
||||
property alias expandSidebar: expandSidebarAction;
|
||||
|
||||
property alias deleteSelection: deleteSelectionAction;
|
||||
@ -36,6 +41,7 @@ Item
|
||||
property alias selectAll: selectAllAction;
|
||||
property alias deleteAll: deleteAllAction;
|
||||
property alias reloadAll: reloadAllAction;
|
||||
property alias arrangeAllBuildPlates: arrangeAllBuildPlatesAction;
|
||||
property alias arrangeAll: arrangeAllAction;
|
||||
property alias arrangeSelection: arrangeSelectionAction;
|
||||
property alias resetAllTranslation: resetAllTranslationAction;
|
||||
@ -104,9 +110,37 @@ Item
|
||||
|
||||
Action
|
||||
{
|
||||
id: homeCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","&Reset camera position");
|
||||
onTriggered: CuraActions.homeCamera();
|
||||
id: view3DCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","&3D View");
|
||||
onTriggered: UM.Controller.rotateView("3d", 0);
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: viewFrontCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","&Front View");
|
||||
onTriggered: UM.Controller.rotateView("home", 0);
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: viewTopCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","&Top View");
|
||||
onTriggered: UM.Controller.rotateView("y", 90);
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: viewLeftSideCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","&Left Side View");
|
||||
onTriggered: UM.Controller.rotateView("x", 90);
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: viewRightSideCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","&Right Side View");
|
||||
onTriggered: UM.Controller.rotateView("x", -90);
|
||||
}
|
||||
|
||||
Action
|
||||
@ -311,6 +345,13 @@ Item
|
||||
onTriggered: CuraApplication.reloadAll();
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: arrangeAllBuildPlatesAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange All Models To All Build Plates");
|
||||
onTriggered: Printer.arrangeObjectsToAllBuildPlates();
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: arrangeAllAction;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
@ -25,6 +25,15 @@ UM.Dialog
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
|
||||
flags: {
|
||||
var window_flags = Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint;
|
||||
if (Cura.MachineManager.activeDefinitionId !== "") //Disallow closing the window if we have no active printer yet. You MUST add a printer.
|
||||
{
|
||||
window_flags |= Qt.WindowCloseButtonHint;
|
||||
}
|
||||
return window_flags;
|
||||
}
|
||||
|
||||
onVisibilityChanged:
|
||||
{
|
||||
// Reset selection and machine name
|
||||
|
@ -375,6 +375,18 @@ UM.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
ObjectsList
|
||||
{
|
||||
id: objectsList;
|
||||
visible: UM.Preferences.getValue("cura/use_multi_build_plate");
|
||||
anchors
|
||||
{
|
||||
bottom: parent.bottom;
|
||||
left: parent.left;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Topbar
|
||||
{
|
||||
id: topbar
|
||||
@ -401,7 +413,7 @@ UM.MainWindow
|
||||
collapseSidebarAnimation.start();
|
||||
}
|
||||
collapsed = !collapsed;
|
||||
UM.Preferences.setValue("cura/sidebar_collapse", collapsed);
|
||||
UM.Preferences.setValue("cura/sidebar_collapsed", collapsed);
|
||||
}
|
||||
|
||||
anchors
|
||||
@ -432,9 +444,10 @@ UM.MainWindow
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
var sidebarCollapsed = UM.Preferences.getValue("cura/sidebar_collapse");
|
||||
var sidebar_collapsed = UM.Preferences.getValue("cura/sidebar_collapsed");
|
||||
|
||||
if (sidebarCollapsed) {
|
||||
if (sidebar_collapsed)
|
||||
{
|
||||
sidebar.collapsed = true;
|
||||
viewportRect = Qt.rect(0, 0, 1, 1.0)
|
||||
collapseSidebarAnimation.start();
|
||||
@ -528,6 +541,12 @@ UM.MainWindow
|
||||
onTriggered: preferences.visible = true
|
||||
}
|
||||
|
||||
Connections
|
||||
{
|
||||
target: CuraApplication
|
||||
onShowPreferencesWindow: preferences.visible = true
|
||||
}
|
||||
|
||||
MessageDialog
|
||||
{
|
||||
id: newProjectDialog
|
||||
|
@ -7,7 +7,7 @@ import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import Cura 1.2 as Cura
|
||||
|
||||
Menu
|
||||
{
|
||||
@ -39,6 +39,35 @@ Menu
|
||||
onObjectRemoved: base.removeItem(object)
|
||||
}
|
||||
|
||||
MenuSeparator {
|
||||
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
|
||||
}
|
||||
|
||||
Instantiator
|
||||
{
|
||||
model: Cura.BuildPlateModel
|
||||
MenuItem {
|
||||
text: Cura.BuildPlateModel.getItem(index).name;
|
||||
onTriggered: CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.getItem(index).buildPlateNumber);
|
||||
checkable: true
|
||||
checked: Cura.BuildPlateModel.selectionBuildPlates.indexOf(Cura.BuildPlateModel.getItem(index).buildPlateNumber) != -1;
|
||||
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
|
||||
}
|
||||
onObjectAdded: base.insertItem(index, object);
|
||||
onObjectRemoved: base.removeItem(object);
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "New build plate";
|
||||
onTriggered: {
|
||||
CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.maxBuildPlate + 1);
|
||||
checked = false;
|
||||
}
|
||||
checkable: true
|
||||
checked: false
|
||||
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
|
||||
}
|
||||
|
||||
// Global actions
|
||||
MenuSeparator {}
|
||||
MenuItem { action: Cura.Actions.selectAll; }
|
||||
|
@ -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.
|
||||
|
||||
import QtQuick 2.2
|
||||
@ -33,9 +33,10 @@ Menu
|
||||
{
|
||||
id: materialDiameterProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: Cura.ExtruderManager.activeExtruderStackId
|
||||
key: "material_diameter"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 5
|
||||
}
|
||||
|
||||
MenuItem
|
||||
@ -207,8 +208,8 @@ Menu
|
||||
// Add to top section
|
||||
var materialId = items[i].id;
|
||||
genericMaterialsModel.append({
|
||||
id:materialId,
|
||||
name:items[i].name
|
||||
id: materialId,
|
||||
name: items[i].name
|
||||
});
|
||||
}
|
||||
else
|
||||
|
@ -5,12 +5,12 @@ import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import Cura 1.2 as Cura
|
||||
|
||||
Menu
|
||||
{
|
||||
title: catalog.i18nc("@title:menu menubar:toplevel", "&View");
|
||||
id: menu
|
||||
id: base
|
||||
enabled: !PrintInformation.preSliced
|
||||
|
||||
// main views
|
||||
@ -25,12 +25,49 @@ Menu
|
||||
exclusiveGroup: group
|
||||
onTriggered: UM.Controller.setActiveView(model.id)
|
||||
}
|
||||
onObjectAdded: menu.insertItem(index, object)
|
||||
onObjectRemoved: menu.removeItem(object)
|
||||
onObjectAdded: base.insertItem(index, object)
|
||||
onObjectRemoved: base.removeItem(object)
|
||||
}
|
||||
ExclusiveGroup { id: group }
|
||||
|
||||
MenuSeparator {}
|
||||
MenuItem { action: Cura.Actions.homeCamera; }
|
||||
|
||||
Menu
|
||||
{
|
||||
title: catalog.i18nc("@action:inmenu menubar:view","&Camera position");
|
||||
MenuItem { action: Cura.Actions.view3DCamera; }
|
||||
MenuItem { action: Cura.Actions.viewFrontCamera; }
|
||||
MenuItem { action: Cura.Actions.viewTopCamera; }
|
||||
MenuItem { action: Cura.Actions.viewLeftSideCamera; }
|
||||
MenuItem { action: Cura.Actions.viewRightSideCamera; }
|
||||
}
|
||||
|
||||
MenuSeparator {
|
||||
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
|
||||
}
|
||||
|
||||
Menu
|
||||
{
|
||||
id: buildPlateMenu;
|
||||
title: catalog.i18nc("@action:inmenu menubar:view","&Build plate");
|
||||
Instantiator
|
||||
{
|
||||
model: Cura.BuildPlateModel
|
||||
MenuItem {
|
||||
text: Cura.BuildPlateModel.getItem(index).name;
|
||||
onTriggered: Cura.SceneController.setActiveBuildPlate(Cura.BuildPlateModel.getItem(index).buildPlateNumber);
|
||||
checkable: true;
|
||||
checked: Cura.BuildPlateModel.getItem(index).buildPlateNumber == Cura.BuildPlateModel.activeBuildPlate;
|
||||
exclusiveGroup: buildPlateGroup;
|
||||
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
|
||||
}
|
||||
onObjectAdded: buildPlateMenu.insertItem(index, object);
|
||||
onObjectRemoved: buildPlateMenu.removeItem(object)
|
||||
}
|
||||
ExclusiveGroup { id: buildPlateGroup; }
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
MenuItem { action: Cura.Actions.expandSidebar; }
|
||||
}
|
||||
|
@ -199,12 +199,12 @@ Item
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateAdditionalComponents("monitorButtons")
|
||||
buttonsRow.updateAdditionalComponents("monitorButtons")
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CuraApplication
|
||||
onAdditionalComponentsChanged: updateAdditionalComponents
|
||||
onAdditionalComponentsChanged: buttonsRow.updateAdditionalComponents("monitorButtons")
|
||||
}
|
||||
|
||||
function updateAdditionalComponents (areaId) {
|
||||
|
267
resources/qml/ObjectsList.qml
Normal file
267
resources/qml/ObjectsList.qml
Normal file
@ -0,0 +1,267 @@
|
||||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 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 UM 1.3 as UM
|
||||
import Cura 1.2 as Cura
|
||||
|
||||
import "Menus"
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: base;
|
||||
|
||||
color: UM.Theme.getColor("tool_panel_background")
|
||||
|
||||
width: UM.Theme.getSize("objects_menu_size").width
|
||||
height: {
|
||||
if (collapsed) {
|
||||
return UM.Theme.getSize("objects_menu_size_collapsed").height;
|
||||
} else {
|
||||
return UM.Theme.getSize("objects_menu_size").height;
|
||||
}
|
||||
}
|
||||
Behavior on height { NumberAnimation { duration: 100 } }
|
||||
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
|
||||
property bool collapsed: false;
|
||||
|
||||
SystemPalette { id: palette }
|
||||
|
||||
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.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
width: UM.Theme.getSize("standard_arrow").width
|
||||
height: UM.Theme.getSize("standard_arrow").height
|
||||
|
||||
onClicked: collapsed = !collapsed
|
||||
|
||||
style: ButtonStyle
|
||||
{
|
||||
background: UM.RecolorImage
|
||||
{
|
||||
width: control.width
|
||||
height: control.height
|
||||
sourceSize.width: width
|
||||
sourceSize.height: width
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
source: collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
|
||||
}
|
||||
label: Label{ }
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: buildPlateDelegate
|
||||
Rectangle
|
||||
{
|
||||
height: childrenRect.height
|
||||
color: Cura.BuildPlateModel.getItem(index).buildPlateNumber == Cura.BuildPlateModel.activeBuildPlate ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
|
||||
width: parent.width
|
||||
Label
|
||||
{
|
||||
id: buildPlateNameLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
|
||||
text: Cura.BuildPlateModel.getItem(index) ? Cura.BuildPlateModel.getItem(index).name : "";
|
||||
color: Cura.BuildPlateModel.activeBuildPlate == index ? palette.highlightedText : palette.text
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent;
|
||||
onClicked:
|
||||
{
|
||||
Cura.SceneController.setActiveBuildPlate(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: buildPlateSelection
|
||||
frameVisible: true
|
||||
height: UM.Theme.getSize("build_plate_selection_size").height
|
||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").height
|
||||
style: UM.Theme.styles.scrollview
|
||||
|
||||
anchors
|
||||
{
|
||||
top: collapseButton.bottom;
|
||||
topMargin: UM.Theme.getSize("default_margin").height;
|
||||
left: parent.left;
|
||||
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||
//bottom: objectsList.top;
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
parent: viewport
|
||||
anchors.fill: parent
|
||||
color: palette.light
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
id: buildPlateListView
|
||||
model: Cura.BuildPlateModel
|
||||
width: parent.width
|
||||
delegate: buildPlateDelegate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: objectDelegate
|
||||
Rectangle
|
||||
{
|
||||
height: childrenRect.height
|
||||
color: Cura.ObjectsModel.getItem(index).isSelected ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
|
||||
width: parent.width
|
||||
Label
|
||||
{
|
||||
id: nodeNameLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
|
||||
text: Cura.ObjectsModel.getItem(index) ? Cura.ObjectsModel.getItem(index).name : "";
|
||||
color: Cura.ObjectsModel.getItem(index).isSelected ? palette.highlightedText : (Cura.ObjectsModel.getItem(index).isOutsideBuildArea ? palette.mid : palette.text)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: buildPlateNumberLabel
|
||||
width: 20
|
||||
anchors.left: nodeNameLabel.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
text: Cura.ObjectsModel.getItem(index).buildPlateNumber != -1 ? Cura.ObjectsModel.getItem(index).buildPlateNumber + 1 : "";
|
||||
color: Cura.ObjectsModel.getItem(index).isSelected ? palette.highlightedText : palette.text
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent;
|
||||
onClicked:
|
||||
{
|
||||
Cura.SceneController.changeSelection(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// list all the scene nodes
|
||||
ScrollView
|
||||
{
|
||||
id: objectsList
|
||||
frameVisible: true
|
||||
visible: !collapsed
|
||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").height
|
||||
|
||||
anchors
|
||||
{
|
||||
top: buildPlateSelection.bottom;
|
||||
topMargin: UM.Theme.getSize("default_margin").height;
|
||||
left: parent.left;
|
||||
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||
bottom: filterBuildPlateCheckbox.top;
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
parent: viewport
|
||||
anchors.fill: parent
|
||||
color: palette.light
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
id: listview
|
||||
model: Cura.ObjectsModel
|
||||
width: parent.width
|
||||
delegate: objectDelegate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CheckBox
|
||||
{
|
||||
id: filterBuildPlateCheckbox
|
||||
visible: !collapsed
|
||||
checked: UM.Preferences.getValue("view/filter_current_build_plate")
|
||||
onClicked: UM.Preferences.setValue("view/filter_current_build_plate", checked)
|
||||
|
||||
text: catalog.i18nc("@option:check","See only current build plate");
|
||||
style: UM.Theme.styles.checkbox;
|
||||
|
||||
anchors
|
||||
{
|
||||
left: parent.left;
|
||||
topMargin: UM.Theme.getSize("default_margin").height;
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||
bottom: arrangeAllBuildPlatesButton.top;
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: arrangeAllBuildPlatesButton;
|
||||
text: catalog.i18nc("@action:button","Arrange to all build plates");
|
||||
style: UM.Theme.styles.sidebar_action_button
|
||||
height: UM.Theme.getSize("objects_menu_button").height;
|
||||
tooltip: '';
|
||||
anchors
|
||||
{
|
||||
//top: buildPlateSelection.bottom;
|
||||
topMargin: UM.Theme.getSize("default_margin").height;
|
||||
left: parent.left;
|
||||
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||
right: parent.right;
|
||||
rightMargin: UM.Theme.getSize("default_margin").height;
|
||||
bottom: arrangeBuildPlateButton.top;
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||
}
|
||||
action: Cura.Actions.arrangeAllBuildPlates;
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: arrangeBuildPlateButton;
|
||||
text: catalog.i18nc("@action:button","Arrange current build plate");
|
||||
style: UM.Theme.styles.sidebar_action_button
|
||||
height: UM.Theme.getSize("objects_menu_button").height;
|
||||
tooltip: '';
|
||||
anchors
|
||||
{
|
||||
topMargin: UM.Theme.getSize("default_margin").height;
|
||||
left: parent.left;
|
||||
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||
right: parent.right;
|
||||
rightMargin: UM.Theme.getSize("default_margin").height;
|
||||
bottom: parent.bottom;
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||
}
|
||||
action: Cura.Actions.arrangeAll;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -159,6 +159,7 @@ UM.PreferencesPage
|
||||
append({ text: "Nederlands", code: "nl_NL" })
|
||||
append({ text: "Polski", code: "pl_PL" })
|
||||
append({ text: "Português do Brasil", code: "pt_BR" })
|
||||
append({ text: "Português", code: "pt_PT" })
|
||||
append({ text: "Русский", code: "ru_RU" })
|
||||
append({ text: "Türkçe", code: "tr_TR" })
|
||||
append({ text: "简体中文", code: "zh_CN" })
|
||||
@ -305,7 +306,7 @@ UM.PreferencesPage
|
||||
text: catalog.i18nc("@option:check","Slice automatically");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
//: Spacer
|
||||
@ -452,6 +453,34 @@ UM.PreferencesPage
|
||||
text: catalog.i18nc("@label","Opening and saving files")
|
||||
}
|
||||
|
||||
UM.TooltipArea {
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
text: catalog.i18nc("@info:tooltip","Use multi build plate functionality (EXPERIMENTAL)")
|
||||
|
||||
CheckBox
|
||||
{
|
||||
id: useMultiBuildPlateCheckbox
|
||||
text: catalog.i18nc("@option:check","Use multi build plate functionality (EXPERIMENTAL, restart)")
|
||||
checked: boolCheck(UM.Preferences.getValue("cura/use_multi_build_plate"))
|
||||
onCheckedChanged: UM.Preferences.setValue("cura/use_multi_build_plate", checked)
|
||||
}
|
||||
}
|
||||
|
||||
UM.TooltipArea {
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
text: catalog.i18nc("@info:tooltip","Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)")
|
||||
|
||||
CheckBox
|
||||
{
|
||||
id: arrangeOnLoadCheckbox
|
||||
text: catalog.i18nc("@option:check","Arrange objects on load (EXPERIMENTAL)")
|
||||
checked: boolCheck(UM.Preferences.getValue("cura/arrange_objects_on_load"))
|
||||
onCheckedChanged: UM.Preferences.setValue("cura/arrange_objects_on_load", checked)
|
||||
}
|
||||
}
|
||||
|
||||
UM.TooltipArea {
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
|
@ -387,9 +387,10 @@ UM.ManagementPage
|
||||
{
|
||||
id: materialDiameterProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: Cura.ExtruderManager.activeExtruderStackId
|
||||
key: "material_diameter"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 5
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
@ -138,12 +138,12 @@ Item {
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
addAdditionalComponents("saveButton")
|
||||
saveRow.addAdditionalComponents("saveButton")
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CuraApplication
|
||||
onAdditionalComponentsChanged: addAdditionalComponents
|
||||
onAdditionalComponentsChanged: saveRow.addAdditionalComponents("saveButton")
|
||||
}
|
||||
|
||||
function addAdditionalComponents (areaId) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2015 Ultimaker B.V.
|
||||
// Uranium is released under the terms of the LGPLv3 or higher.
|
||||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
@ -31,13 +31,17 @@ Button {
|
||||
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus();
|
||||
if(definition.expanded)
|
||||
{
|
||||
settingDefinitionsModel.collapse(definition.key);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
settingDefinitionsModel.expandAll(definition.key);
|
||||
}
|
||||
//Set focus so that tab navigation continues from this point on.
|
||||
//NB: This must be set AFTER collapsing/expanding the category so that the scroll position is correct.
|
||||
forceActiveFocus();
|
||||
}
|
||||
onActiveFocusChanged:
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2015 Ultimaker B.V.
|
||||
// Uranium is released under the terms of the LGPLv3 or higher.
|
||||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
@ -19,8 +19,6 @@ Item {
|
||||
property alias contents: controlContainer.children
|
||||
property alias hovered: mouse.containsMouse
|
||||
|
||||
property var resetHandler: false
|
||||
|
||||
property var showRevertButton: true
|
||||
property var showInheritButton: true
|
||||
property var showLinkedSettingIcon: true
|
||||
@ -156,7 +154,7 @@ Item {
|
||||
|
||||
onEntered: {
|
||||
hoverTimer.stop();
|
||||
var tooltipText = catalog.i18nc("@label", "This setting is always shared between all extruders. Changing it here will change the value for all extruders") + ".";
|
||||
var tooltipText = catalog.i18nc("@label", "This setting is always shared between all extruders. Changing it here will change the value for all extruders.");
|
||||
if ((resolve != "None") && (stackLevel != 0)) {
|
||||
// We come here if a setting has a resolve and the setting is not manually edited.
|
||||
tooltipText += " " + catalog.i18nc("@label", "The value is resolved from per-extruder values ") + "[" + Cura.ExtruderManager.getInstanceExtruderValues(definition.key) + "].";
|
||||
@ -183,8 +181,8 @@ Item {
|
||||
onClicked: {
|
||||
revertButton.focus = true
|
||||
|
||||
if (resetHandler) {
|
||||
resetHandler(propertyProvider.key)
|
||||
if (externalResetHandler) {
|
||||
externalResetHandler(propertyProvider.key)
|
||||
} else {
|
||||
Cura.MachineManager.clearUserSettingAllCurrentStacks(propertyProvider.key)
|
||||
}
|
||||
|
@ -287,6 +287,7 @@ Item
|
||||
property var settingDefinitionsModel: definitionsModel
|
||||
property var propertyProvider: provider
|
||||
property var globalPropertyProvider: inheritStackProvider
|
||||
property var externalResetHandler: false
|
||||
|
||||
//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,
|
||||
|
@ -97,7 +97,7 @@ Rectangle
|
||||
SidebarHeader {
|
||||
id: header
|
||||
width: parent.width
|
||||
visible: machineExtruderCount.properties.value > 1 || Cura.MachineManager.hasMaterials || Cura.MachineManager.hasVariants
|
||||
visible: (machineExtruderCount.properties.value > 1 || Cura.MachineManager.hasMaterials || Cura.MachineManager.hasVariants) && !monitoringPrint
|
||||
anchors.top: machineSelection.bottom
|
||||
|
||||
onShowTooltip: base.showTooltip(item, location, text)
|
||||
|
@ -340,6 +340,8 @@ Item
|
||||
text: catalog.i18nc("@label", "Print Speed")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
width: parseInt(UM.Theme.getSize("sidebar").width * 0.35)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label
|
||||
|
@ -25,9 +25,12 @@ Rectangle
|
||||
property int allItemsWidth: 0;
|
||||
|
||||
function updateMarginsAndSizes() {
|
||||
if (UM.Preferences.getValue("cura/sidebar_collapse")) {
|
||||
if (UM.Preferences.getValue("cura/sidebar_collapsed"))
|
||||
{
|
||||
rightMargin = UM.Theme.getSize("default_margin").width;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
rightMargin = UM.Theme.getSize("sidebar").width + UM.Theme.getSize("default_margin").width;
|
||||
}
|
||||
allItemsWidth = (
|
||||
@ -112,10 +115,8 @@ Rectangle
|
||||
iconSource: UM.Theme.getIcon("view_3d")
|
||||
style: UM.Theme.styles.small_tool_button
|
||||
anchors.verticalCenter: viewOrientationControl.verticalCenter
|
||||
onClicked:{
|
||||
UM.Controller.rotateView("3d", 0);
|
||||
}
|
||||
visible: base.width - allItemsWidth - 4 * this.width > 0;
|
||||
onClicked:UM.Controller.rotateView("3d", 0)
|
||||
visible: base.width - allItemsWidth - 4 * this.width > 0
|
||||
}
|
||||
|
||||
// #2 Front view
|
||||
@ -124,10 +125,8 @@ Rectangle
|
||||
iconSource: UM.Theme.getIcon("view_front")
|
||||
style: UM.Theme.styles.small_tool_button
|
||||
anchors.verticalCenter: viewOrientationControl.verticalCenter
|
||||
onClicked:{
|
||||
UM.Controller.rotateView("home", 0);
|
||||
}
|
||||
visible: base.width - allItemsWidth - 3 * this.width > 0;
|
||||
onClicked: UM.Controller.rotateView("home", 0);
|
||||
visible: base.width - allItemsWidth - 3 * this.width > 0
|
||||
}
|
||||
|
||||
// #3 Top view
|
||||
@ -136,10 +135,8 @@ Rectangle
|
||||
iconSource: UM.Theme.getIcon("view_top")
|
||||
style: UM.Theme.styles.small_tool_button
|
||||
anchors.verticalCenter: viewOrientationControl.verticalCenter
|
||||
onClicked:{
|
||||
UM.Controller.rotateView("y", 90);
|
||||
}
|
||||
visible: base.width - allItemsWidth - 2 * this.width > 0;
|
||||
onClicked: UM.Controller.rotateView("y", 90)
|
||||
visible: base.width - allItemsWidth - 2 * this.width > 0
|
||||
}
|
||||
|
||||
// #4 Left view
|
||||
@ -148,10 +145,8 @@ Rectangle
|
||||
iconSource: UM.Theme.getIcon("view_left")
|
||||
style: UM.Theme.styles.small_tool_button
|
||||
anchors.verticalCenter: viewOrientationControl.verticalCenter
|
||||
onClicked:{
|
||||
UM.Controller.rotateView("x", 90);
|
||||
}
|
||||
visible: base.width - allItemsWidth - 1 * this.width > 0;
|
||||
onClicked: UM.Controller.rotateView("x", 90)
|
||||
visible: base.width - allItemsWidth - 1 * this.width > 0
|
||||
}
|
||||
|
||||
// #5 Left view
|
||||
@ -160,10 +155,8 @@ Rectangle
|
||||
iconSource: UM.Theme.getIcon("view_right")
|
||||
style: UM.Theme.styles.small_tool_button
|
||||
anchors.verticalCenter: viewOrientationControl.verticalCenter
|
||||
onClicked:{
|
||||
UM.Controller.rotateView("x", -90);
|
||||
}
|
||||
visible: base.width - allItemsWidth > 0;
|
||||
onClicked: UM.Controller.rotateView("x", -90)
|
||||
visible: base.width - allItemsWidth > 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,60 @@
|
||||
[general]
|
||||
version = 2
|
||||
name = Draft
|
||||
definition = anycubic_i3_mega
|
||||
|
||||
[metadata]
|
||||
type = quality
|
||||
quality_type = draft
|
||||
weight = 0
|
||||
setting_version = 4
|
||||
|
||||
[values]
|
||||
acceleration_enabled = True
|
||||
acceleration_print = 2000
|
||||
acceleration_travel = 3500
|
||||
adhesion_type = skirt
|
||||
brim_width = 4.0
|
||||
cool_fan_full_at_height = 0.5
|
||||
cool_fan_speed = 100
|
||||
cool_fan_speed_0 = 100
|
||||
infill_overlap = 15
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 25
|
||||
initial_layer_line_width_factor = 140
|
||||
jerk_enabled = True
|
||||
jerk_print = 13
|
||||
jerk_travel = 13
|
||||
layer_height = 0.4
|
||||
layer_height_0 = 0.4
|
||||
material_bed_temperature = 60
|
||||
material_diameter = 1.75
|
||||
material_print_temperature = 200
|
||||
material_print_temperature_layer_0 = 0
|
||||
retract_at_layer_change = False
|
||||
retraction_amount = 7
|
||||
retraction_hop = 0.075
|
||||
retraction_hop_enabled = True
|
||||
retraction_hop_only_when_collides = True
|
||||
retraction_min_travel = 1.5
|
||||
retraction_speed = 40
|
||||
skirt_brim_speed = 40
|
||||
skirt_gap = 5
|
||||
skirt_line_count = 3
|
||||
speed_infill = 60
|
||||
speed_print = 60
|
||||
speed_support = 60
|
||||
speed_topbottom = 30
|
||||
speed_travel = 100
|
||||
speed_wall = 60
|
||||
speed_wall_x = 60
|
||||
support_angle = 60
|
||||
support_enable = True
|
||||
support_interface_enable = True
|
||||
support_pattern = triangles
|
||||
support_roof_enable = True
|
||||
support_type = everywhere
|
||||
support_use_towers = False
|
||||
support_xy_distance = 0.7
|
||||
top_bottom_thickness = 1.2
|
||||
wall_thickness = 1.2
|
@ -0,0 +1,60 @@
|
||||
[general]
|
||||
version = 2
|
||||
name = High
|
||||
definition = anycubic_i3_mega
|
||||
|
||||
[metadata]
|
||||
type = quality
|
||||
quality_type = high
|
||||
weight = 2
|
||||
setting_version = 4
|
||||
|
||||
[values]
|
||||
acceleration_enabled = True
|
||||
acceleration_print = 2000
|
||||
acceleration_travel = 3500
|
||||
adhesion_type = skirt
|
||||
brim_width = 4.0
|
||||
cool_fan_full_at_height = 0.5
|
||||
cool_fan_speed = 100
|
||||
cool_fan_speed_0 = 100
|
||||
infill_overlap = 15
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 25
|
||||
initial_layer_line_width_factor = 140
|
||||
jerk_enabled = True
|
||||
jerk_print = 13
|
||||
jerk_travel = 13
|
||||
layer_height = 0.1
|
||||
layer_height_0 = 0.1
|
||||
material_bed_temperature = 60
|
||||
material_diameter = 1.75
|
||||
material_print_temperature = 200
|
||||
material_print_temperature_layer_0 = 0
|
||||
retract_at_layer_change = False
|
||||
retraction_amount = 7
|
||||
retraction_hop = 0.075
|
||||
retraction_hop_enabled = True
|
||||
retraction_hop_only_when_collides = True
|
||||
retraction_min_travel = 1.5
|
||||
retraction_speed = 40
|
||||
skirt_brim_speed = 40
|
||||
skirt_gap = 5
|
||||
skirt_line_count = 3
|
||||
speed_infill = 50
|
||||
speed_print = 50
|
||||
speed_support = 30
|
||||
speed_topbottom = 20
|
||||
speed_travel = 50
|
||||
speed_wall = 50
|
||||
speed_wall_x = 50
|
||||
support_angle = 60
|
||||
support_enable = True
|
||||
support_interface_enable = True
|
||||
support_pattern = triangles
|
||||
support_roof_enable = True
|
||||
support_type = everywhere
|
||||
support_use_towers = False
|
||||
support_xy_distance = 0.7
|
||||
top_bottom_thickness = 1.2
|
||||
wall_thickness = 1.2
|
@ -0,0 +1,60 @@
|
||||
[general]
|
||||
version = 2
|
||||
name = Normal
|
||||
definition = anycubic_i3_mega
|
||||
|
||||
[metadata]
|
||||
type = quality
|
||||
quality_type = normal
|
||||
weight = 1
|
||||
setting_version = 4
|
||||
|
||||
[values]
|
||||
acceleration_enabled = True
|
||||
acceleration_print = 2000
|
||||
acceleration_travel = 3500
|
||||
adhesion_type = skirt
|
||||
brim_width = 4.0
|
||||
cool_fan_full_at_height = 0.5
|
||||
cool_fan_speed = 100
|
||||
cool_fan_speed_0 = 100
|
||||
infill_overlap = 15
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 25
|
||||
initial_layer_line_width_factor = 140
|
||||
jerk_enabled = True
|
||||
jerk_print = 13
|
||||
jerk_travel = 13
|
||||
layer_height = 0.2
|
||||
layer_height_0 = 0.2
|
||||
material_bed_temperature = 60
|
||||
material_diameter = 1.75
|
||||
material_print_temperature = 200
|
||||
material_print_temperature_layer_0 = 0
|
||||
retract_at_layer_change = False
|
||||
retraction_amount = 7
|
||||
retraction_hop = 0.075
|
||||
retraction_hop_enabled = True
|
||||
retraction_hop_only_when_collides = True
|
||||
retraction_min_travel = 1.5
|
||||
retraction_speed = 40
|
||||
skirt_brim_speed = 40
|
||||
skirt_gap = 5
|
||||
skirt_line_count = 3
|
||||
speed_infill = 50
|
||||
speed_print = 50
|
||||
speed_support = 30
|
||||
speed_topbottom = 20
|
||||
speed_travel = 100
|
||||
speed_wall = 50
|
||||
speed_wall_x = 50
|
||||
support_angle = 60
|
||||
support_enable = True
|
||||
support_interface_enable = True
|
||||
support_pattern = triangles
|
||||
support_roof_enable = True
|
||||
support_type = everywhere
|
||||
support_use_towers = False
|
||||
support_xy_distance = 0.7
|
||||
top_bottom_thickness = 1.2
|
||||
wall_thickness = 1.2
|
@ -0,0 +1,33 @@
|
||||
[general]
|
||||
version = 2
|
||||
name = Draft
|
||||
definition = tevo_blackwidow
|
||||
|
||||
[metadata]
|
||||
type = quality
|
||||
quality_type = draft
|
||||
weight = -2
|
||||
setting_version = 4
|
||||
|
||||
[values]
|
||||
brim_width = 4.0
|
||||
infill_pattern = zigzag
|
||||
layer_height = 0.4
|
||||
material_diameter = 1.75
|
||||
speed_infill = 50
|
||||
speed_print = 50
|
||||
speed_support = 30
|
||||
speed_topbottom = 20
|
||||
speed_travel = 100
|
||||
speed_wall = 50
|
||||
speed_wall_x = 50
|
||||
support_angle = 60
|
||||
support_enable = True
|
||||
support_interface_enable = True
|
||||
support_pattern = triangles
|
||||
support_roof_enable = True
|
||||
support_type = everywhere
|
||||
support_use_towers = False
|
||||
support_xy_distance = 0.7
|
||||
top_bottom_thickness = 1.2
|
||||
wall_thickness = 1.2
|
@ -0,0 +1,33 @@
|
||||
[general]
|
||||
version = 2
|
||||
name = High
|
||||
definition = tevo_blackwidow
|
||||
|
||||
[metadata]
|
||||
type = quality
|
||||
quality_type = high
|
||||
weight = 1
|
||||
setting_version = 4
|
||||
|
||||
[values]
|
||||
brim_width = 4.0
|
||||
infill_pattern = zigzag
|
||||
layer_height = 0.1
|
||||
material_diameter = 1.75
|
||||
speed_infill = 50
|
||||
speed_print = 50
|
||||
speed_support = 30
|
||||
speed_topbottom = 15
|
||||
speed_travel = 100
|
||||
speed_wall = 50
|
||||
speed_wall_x = 50
|
||||
support_angle = 60
|
||||
support_enable = True
|
||||
support_interface_enable = True
|
||||
support_pattern = triangles
|
||||
support_roof_enable = True
|
||||
support_type = everywhere
|
||||
support_use_towers = False
|
||||
support_xy_distance = 0.7
|
||||
top_bottom_thickness = 1.2
|
||||
wall_thickness = 1.2
|
@ -0,0 +1,33 @@
|
||||
[general]
|
||||
version = 2
|
||||
name = Normal
|
||||
definition = tevo_blackwidow
|
||||
|
||||
[metadata]
|
||||
type = quality
|
||||
quality_type = normal
|
||||
weight = 0
|
||||
setting_version = 4
|
||||
|
||||
[values]
|
||||
brim_width = 4.0
|
||||
infill_pattern = zigzag
|
||||
layer_height = 0.2
|
||||
material_diameter = 1.75
|
||||
speed_infill = 60
|
||||
speed_print = 50
|
||||
speed_support = 30
|
||||
speed_topbottom = 20
|
||||
speed_travel = 100
|
||||
speed_wall = 50
|
||||
speed_wall_x = 50
|
||||
support_angle = 60
|
||||
support_enable = True
|
||||
support_interface_enable = True
|
||||
support_pattern = triangles
|
||||
support_roof_enable = True
|
||||
support_type = everywhere
|
||||
support_use_towers = False
|
||||
support_xy_distance = 0.7
|
||||
top_bottom_thickness = 1.2
|
||||
wall_thickness = 1.2
|
@ -202,9 +202,8 @@ QtObject {
|
||||
height: Theme.getSize("topbar_button_icon").height
|
||||
Label
|
||||
{
|
||||
id: button_label
|
||||
text: control.text;
|
||||
anchors.right: (icon.visible || overlayIcon.visible) ? icon.left : parent.right
|
||||
anchors.rightMargin: (icon.visible || overlayIcon.visible) ? Theme.getSize("default_margin").width : 0
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
font: control.checked ? UM.Theme.getFont("large") : UM.Theme.getFont("large_nonbold")
|
||||
color:
|
||||
@ -227,6 +226,8 @@ QtObject {
|
||||
{
|
||||
visible: control.iconSource != ""
|
||||
id: icon
|
||||
anchors.left: button_label.right
|
||||
anchors.leftMargin: (icon.visible || overlayIcon.visible) ? Theme.getSize("default_margin").width : 0
|
||||
color: UM.Theme.getColor("text_emphasis")
|
||||
opacity: !control.enabled ? 0.2 : 1.0
|
||||
source: control.iconSource
|
||||
@ -238,6 +239,8 @@ QtObject {
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: overlayIcon
|
||||
anchors.left: button_label.right
|
||||
anchors.leftMargin: (icon.visible || overlayIcon.visible) ? Theme.getSize("default_margin").width : 0
|
||||
visible: control.overlayIconSource != "" && control.iconSource != ""
|
||||
color: control.overlayColor
|
||||
opacity: !control.enabled ? 0.2 : 1.0
|
||||
|
@ -391,6 +391,11 @@
|
||||
|
||||
"infill_button_margin": [0.5, 0.5],
|
||||
|
||||
"jobspecs_line": [2.0, 2.0]
|
||||
"jobspecs_line": [2.0, 2.0],
|
||||
|
||||
"objects_menu_size": [20, 40],
|
||||
"objects_menu_size_collapsed": [20, 17],
|
||||
"build_plate_selection_size": [15, 5],
|
||||
"objects_menu_button": [0.3, 2.7]
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import pytest
|
||||
import numpy
|
||||
import time
|
||||
|
||||
from cura.Arrange import Arrange
|
||||
from cura.ShapeArray import ShapeArray
|
||||
from cura.Arranging.Arrange import Arrange
|
||||
from cura.Arranging.ShapeArray import ShapeArray
|
||||
|
||||
|
||||
def gimmeShapeArray():
|
||||
|
Loading…
x
Reference in New Issue
Block a user