mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-23 14:44:13 -06:00
Merge branch 'um-master' into gh-issue-980
Conflicts: resources/definitions/fdmprinter.def.json
This commit is contained in:
commit
bf331f39af
220 changed files with 111349 additions and 168598 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@ docs/html
|
|||
resources/i18n/en
|
||||
resources/i18n/x-test
|
||||
resources/firmware
|
||||
LC_MESSAGES
|
||||
|
||||
# Editors and IDEs.
|
||||
*kdev*
|
||||
|
|
|
@ -43,6 +43,8 @@ if(NOT APPLE AND NOT WIN32)
|
|||
DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages/cura)
|
||||
install(FILES ${CMAKE_BINARY_DIR}/cura.desktop
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
install(FILES cura.appdata.xml
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/appdata)
|
||||
install(FILES cura.sharedmimeinfo
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages/
|
||||
RENAME cura.xml )
|
||||
|
|
|
@ -46,6 +46,7 @@ Third party 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.
|
||||
|
||||
Making profiles for other printers
|
||||
----------------------------------
|
||||
|
|
31
cura.appdata.xml
Normal file
31
cura.appdata.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2016 Richard Hughes <richard@hughsie.com> -->
|
||||
<component type="desktop">
|
||||
<id>cura.desktop</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>AGPL-3.0 and CC-BY-SA-4.0</project_license>
|
||||
<name>Cura</name>
|
||||
<summary>The world's most advanced 3d printer software</summary>
|
||||
<description>
|
||||
<p>
|
||||
Cura creates a seamless integration between hardware, software and
|
||||
materials for the best 3D printing experience around.
|
||||
Cura supports the 3MF, OBJ and STL file formats and is available on
|
||||
Windows, Mac and Linux.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Novices can start printing right away</li>
|
||||
<li>Experts are able to customize 200 settings to achieve the best results</li>
|
||||
<li>Optimized profiles for Ultimaker materials</li>
|
||||
<li>Supported by a global network of Ultimaker certified service partners</li>
|
||||
<li>Print multiple objects at once with different settings for each object</li>
|
||||
<li>Cura supports STL, 3MF and OBJ file formats</li>
|
||||
<li>Open source and completely free</li>
|
||||
</ul>
|
||||
</description>
|
||||
<screenshots>
|
||||
<screenshot type="default" width="1280" height="720">http://software.ultimaker.com/Cura.png</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://ultimaker.com/en/products/cura-software</url>
|
||||
<translation type="gettext">Cura</translation>
|
||||
</component>
|
|
@ -1,9 +1,10 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Scene.Platform import Platform
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Application import Application
|
||||
from UM.Resources import Resources
|
||||
|
@ -14,7 +15,7 @@ from UM.Math.AxisAlignedBox import AxisAlignedBox
|
|||
from UM.Math.Polygon import Polygon
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
catalog = i18nCatalog("cura")
|
||||
|
@ -26,26 +27,7 @@ import UM.Settings.ContainerRegistry
|
|||
|
||||
|
||||
# Setting for clearance around the prime
|
||||
PRIME_CLEARANCE = 10
|
||||
|
||||
|
||||
def approximatedCircleVertices(r):
|
||||
"""
|
||||
Return vertices from an approximated circle.
|
||||
:param r: radius
|
||||
:return: numpy 2-array with the vertices
|
||||
"""
|
||||
|
||||
return numpy.array([
|
||||
[-r, 0],
|
||||
[-r * 0.707, r * 0.707],
|
||||
[0, r],
|
||||
[r * 0.707, r * 0.707],
|
||||
[r, 0],
|
||||
[r * 0.707, -r * 0.707],
|
||||
[0, -r],
|
||||
[-r * 0.707, -r * 0.707]
|
||||
], numpy.float32)
|
||||
PRIME_CLEARANCE = 1.5
|
||||
|
||||
|
||||
## Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas.
|
||||
|
@ -69,8 +51,8 @@ class BuildVolume(SceneNode):
|
|||
self._disallowed_areas = []
|
||||
self._disallowed_area_mesh = None
|
||||
|
||||
self._prime_tower_area = None
|
||||
self._prime_tower_area_mesh = None
|
||||
self._error_areas = []
|
||||
self._error_mesh = None
|
||||
|
||||
self.setCalculateBoundingBox(False)
|
||||
self._volume_aabb = None
|
||||
|
@ -80,10 +62,47 @@ class BuildVolume(SceneNode):
|
|||
self._platform = Platform(self)
|
||||
|
||||
self._global_container_stack = None
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
|
||||
self._onGlobalContainerStackChanged()
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onStackChanged)
|
||||
self._onStackChanged()
|
||||
|
||||
self._has_errors = False
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
|
||||
|
||||
# Number of objects loaded at the moment.
|
||||
self._number_of_objects = 0
|
||||
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setInterval(100)
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.timeout.connect(self._onChangeTimerFinished)
|
||||
|
||||
self._build_volume_message = Message(catalog.i18nc("@info:status",
|
||||
"The build volume height has been reduced due to the value of the"
|
||||
" \"Print Sequence\" setting to prevent the gantry from colliding"
|
||||
" with printed models."))
|
||||
|
||||
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
|
||||
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
|
||||
# Therefore this works.
|
||||
Application.getInstance().getMachineManager().activeQualityChanged.connect(self._onStackChanged)
|
||||
# This should also ways work, and it is semantically more correct,
|
||||
# but it does not update the disallowed areas after material change
|
||||
Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged)
|
||||
|
||||
def _onSceneChanged(self, source):
|
||||
if self._global_container_stack:
|
||||
self._change_timer.start()
|
||||
|
||||
def _onChangeTimerFinished(self):
|
||||
root = Application.getInstance().getController().getScene().getRoot()
|
||||
new_number_of_objects = len([node for node in BreadthFirstIterator(root) if node.getMeshData() and type(node) is SceneNode])
|
||||
if new_number_of_objects != self._number_of_objects:
|
||||
recalculate = False
|
||||
if self._global_container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
recalculate = (new_number_of_objects < 2 and self._number_of_objects > 1) or (new_number_of_objects > 1 and self._number_of_objects < 2)
|
||||
self._number_of_objects = new_number_of_objects
|
||||
if recalculate:
|
||||
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
|
||||
|
||||
def setWidth(self, width):
|
||||
if width: self._width = width
|
||||
|
@ -113,8 +132,8 @@ class BuildVolume(SceneNode):
|
|||
if self._disallowed_area_mesh:
|
||||
renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9)
|
||||
|
||||
if self._prime_tower_area_mesh:
|
||||
renderer.queueNode(self, mesh = self._prime_tower_area_mesh, shader = self._shader, transparent=True,
|
||||
if self._error_mesh:
|
||||
renderer.queueNode(self, mesh=self._error_mesh, shader=self._shader, transparent=True,
|
||||
backface_cull=True, sort=-8)
|
||||
|
||||
return True
|
||||
|
@ -191,29 +210,29 @@ class BuildVolume(SceneNode):
|
|||
else:
|
||||
self._disallowed_area_mesh = None
|
||||
|
||||
if self._prime_tower_area:
|
||||
if self._error_areas:
|
||||
mb = MeshBuilder()
|
||||
color = Color(1.0, 0.0, 0.0, 0.5)
|
||||
points = self._prime_tower_area.getPoints()
|
||||
first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(points[0][1], min_d, max_d))
|
||||
previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(points[0][1], min_d, max_d))
|
||||
for point in points:
|
||||
new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(point[1], min_d, max_d))
|
||||
mb.addFace(first, previous_point, new_point, color=color)
|
||||
previous_point = new_point
|
||||
|
||||
self._prime_tower_area_mesh = mb.build()
|
||||
for error_area in self._error_areas:
|
||||
color = Color(1.0, 0.0, 0.0, 0.5)
|
||||
points = error_area.getPoints()
|
||||
first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(points[0][1], min_d, max_d))
|
||||
previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(points[0][1], min_d, max_d))
|
||||
for point in points:
|
||||
new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(point[1], min_d, max_d))
|
||||
mb.addFace(first, previous_point, new_point, color=color)
|
||||
previous_point = new_point
|
||||
self._error_mesh = mb.build()
|
||||
else:
|
||||
self._prime_tower_area_mesh = None
|
||||
self._error_mesh = None
|
||||
|
||||
self._volume_aabb = AxisAlignedBox(
|
||||
minimum = Vector(min_w, min_h - 1.0, min_d),
|
||||
maximum = Vector(max_w, max_h - self._raft_thickness, max_d))
|
||||
|
||||
bed_adhesion_size = self._getBedAdhesionSize()
|
||||
bed_adhesion_size = self._getEdgeDisallowedSize()
|
||||
|
||||
# As this works better for UM machines, we only add the disallowed_area_size for the z direction.
|
||||
# This is probably wrong in all other cases. TODO!
|
||||
|
@ -228,13 +247,6 @@ class BuildVolume(SceneNode):
|
|||
def getBoundingBox(self):
|
||||
return self._volume_aabb
|
||||
|
||||
def _buildVolumeMessage(self):
|
||||
Message(catalog.i18nc(
|
||||
"@info:status",
|
||||
"The build volume height has been reduced due to the value of the"
|
||||
" \"Print Sequence\" setting to prevent the gantry from colliding"
|
||||
" with printed models.")).show()
|
||||
|
||||
def getRaftThickness(self):
|
||||
return self._raft_thickness
|
||||
|
||||
|
@ -255,7 +267,8 @@ class BuildVolume(SceneNode):
|
|||
self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World)
|
||||
self.raftThicknessChanged.emit()
|
||||
|
||||
def _onGlobalContainerStackChanged(self):
|
||||
## Update the build volume visualization
|
||||
def _onStackChanged(self):
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
||||
|
@ -272,12 +285,15 @@ class BuildVolume(SceneNode):
|
|||
|
||||
self._width = self._global_container_stack.getProperty("machine_width", "value")
|
||||
machine_height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
if self._global_container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
if self._global_container_stack.getProperty("print_sequence", "value") == "one_at_a_time" and self._number_of_objects > 1:
|
||||
self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height)
|
||||
if self._height < machine_height:
|
||||
self._buildVolumeMessage()
|
||||
self._build_volume_message.show()
|
||||
else:
|
||||
self._build_volume_message.hide()
|
||||
else:
|
||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
self._build_volume_message.hide()
|
||||
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||
|
||||
self._updateDisallowedAreas()
|
||||
|
@ -292,15 +308,18 @@ class BuildVolume(SceneNode):
|
|||
rebuild_me = False
|
||||
if setting_key == "print_sequence":
|
||||
machine_height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time" and self._number_of_objects > 1:
|
||||
self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height)
|
||||
if self._height < machine_height:
|
||||
self._buildVolumeMessage()
|
||||
self._build_volume_message.show()
|
||||
else:
|
||||
self._build_volume_message.hide()
|
||||
else:
|
||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
self._build_volume_message.hide()
|
||||
rebuild_me = True
|
||||
|
||||
if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings or setting_key == "print_sequence":
|
||||
if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings or setting_key == "print_sequence" or setting_key in self._ooze_shield_settings or setting_key in self._distance_settings:
|
||||
self._updateDisallowedAreas()
|
||||
rebuild_me = True
|
||||
|
||||
|
@ -317,115 +336,177 @@ class BuildVolume(SceneNode):
|
|||
def _updateDisallowedAreas(self):
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
|
||||
self._has_errors = False # Reset.
|
||||
self._error_areas = []
|
||||
disallowed_areas = copy.deepcopy(
|
||||
self._global_container_stack.getProperty("machine_disallowed_areas", "value"))
|
||||
areas = []
|
||||
|
||||
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
||||
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||
self._prime_tower_area = None
|
||||
prime_tower_area = None
|
||||
|
||||
# Add prime tower location as disallowed area.
|
||||
if self._global_container_stack.getProperty("prime_tower_enable", "value") == True:
|
||||
if ExtruderManager.getInstance().getResolveOrValue("prime_tower_enable") == True:
|
||||
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
|
||||
prime_tower_x = self._global_container_stack.getProperty("prime_tower_position_x", "value") - machine_width / 2
|
||||
prime_tower_y = - self._global_container_stack.getProperty("prime_tower_position_y", "value") + machine_depth / 2
|
||||
|
||||
self._prime_tower_area = Polygon([
|
||||
prime_tower_area = Polygon([
|
||||
[prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size],
|
||||
[prime_tower_x, prime_tower_y - prime_tower_size],
|
||||
[prime_tower_x, prime_tower_y],
|
||||
[prime_tower_x - prime_tower_size, prime_tower_y],
|
||||
])
|
||||
disallowed_polygons = []
|
||||
|
||||
# Add extruder prime locations as disallowed areas.
|
||||
# Probably needs some rework after coordinate system change.
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
extruders = extruder_manager.getMachineExtruders(self._global_container_stack.getId())
|
||||
for single_extruder in extruders:
|
||||
extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value")
|
||||
extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value")
|
||||
# TODO: calculate everything in CuraEngine/Firmware/lower left as origin coordinates.
|
||||
# Here we transform the extruder prime pos (lower left as origin) to Cura coordinates
|
||||
# (center as origin, y from back to front)
|
||||
prime_x = extruder_prime_pos_x - machine_width / 2
|
||||
prime_y = machine_depth / 2 - extruder_prime_pos_y
|
||||
disallowed_areas.append([
|
||||
[prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE],
|
||||
[prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE],
|
||||
[prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE],
|
||||
[prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE],
|
||||
])
|
||||
|
||||
bed_adhesion_size = self._getBedAdhesionSize()
|
||||
|
||||
# Check if prime positions intersect with disallowed areas
|
||||
prime_collision = False
|
||||
if disallowed_areas:
|
||||
# Extend every area already in the disallowed_areas with the skirt size.
|
||||
for area in disallowed_areas:
|
||||
poly = Polygon(numpy.array(area, numpy.float32))
|
||||
poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size)))
|
||||
|
||||
# Minkowski with zero, to ensure that the polygon is correct & watertight.
|
||||
poly = poly.getMinkowskiHull(Polygon.approximatedCircle(0))
|
||||
disallowed_polygons.append(poly)
|
||||
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
extruders = extruder_manager.getMachineExtruders(self._global_container_stack.getId())
|
||||
prime_polygons = []
|
||||
# Each extruder has it's own prime location
|
||||
for extruder in extruders:
|
||||
prime_x = extruder.getProperty("extruder_prime_pos_x", "value") - machine_width / 2
|
||||
prime_y = machine_depth / 2 - extruder.getProperty("extruder_prime_pos_y", "value")
|
||||
|
||||
prime_polygon = Polygon([
|
||||
[prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE],
|
||||
[prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE],
|
||||
[prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE],
|
||||
[prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE],
|
||||
])
|
||||
prime_polygon = prime_polygon.getMinkowskiHull(Polygon.approximatedCircle(0))
|
||||
collision = False
|
||||
|
||||
# Check if prime polygon is intersecting with any of the other disallowed areas.
|
||||
# Note that we check the prime area without bed adhesion.
|
||||
for poly in disallowed_polygons:
|
||||
if prime_polygon.intersectsPolygon(poly) is not None:
|
||||
collision = True
|
||||
break
|
||||
|
||||
# Also collide with other prime positions
|
||||
for poly in prime_polygons:
|
||||
if prime_polygon.intersectsPolygon(poly) is not None:
|
||||
collision = True
|
||||
break
|
||||
|
||||
if not collision:
|
||||
# Prime area is valid. Add as normal.
|
||||
# Once it's added like this, it will recieve a bed adhesion offset, just like the others.
|
||||
prime_polygons.append(prime_polygon)
|
||||
else:
|
||||
self._error_areas.append(prime_polygon)
|
||||
prime_collision = collision or prime_collision
|
||||
|
||||
disallowed_polygons.extend(prime_polygons)
|
||||
|
||||
disallowed_border_size = self._getEdgeDisallowedSize()
|
||||
|
||||
# Extend every area already in the disallowed_areas with the skirt size.
|
||||
if disallowed_areas:
|
||||
for poly in disallowed_polygons:
|
||||
poly = poly.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
|
||||
areas.append(poly)
|
||||
|
||||
if self._prime_tower_area:
|
||||
self._prime_tower_area = self._prime_tower_area.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size)))
|
||||
|
||||
# Add the skirt areas around the borders of the build plate.
|
||||
if bed_adhesion_size > 0:
|
||||
if disallowed_border_size > 0:
|
||||
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
|
||||
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size],
|
||||
[-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size]
|
||||
[-half_machine_width + disallowed_border_size, half_machine_depth - disallowed_border_size],
|
||||
[-half_machine_width + disallowed_border_size, -half_machine_depth + disallowed_border_size]
|
||||
], numpy.float32)))
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[half_machine_width, half_machine_depth],
|
||||
[half_machine_width, -half_machine_depth],
|
||||
[half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size],
|
||||
[half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size]
|
||||
[half_machine_width - disallowed_border_size, -half_machine_depth + disallowed_border_size],
|
||||
[half_machine_width - disallowed_border_size, half_machine_depth - disallowed_border_size]
|
||||
], numpy.float32)))
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[half_machine_width, half_machine_depth],
|
||||
[half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size],
|
||||
[-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size]
|
||||
[half_machine_width - disallowed_border_size, half_machine_depth - disallowed_border_size],
|
||||
[-half_machine_width + disallowed_border_size, half_machine_depth - disallowed_border_size]
|
||||
], numpy.float32)))
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size],
|
||||
[half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size]
|
||||
[-half_machine_width + disallowed_border_size, -half_machine_depth + disallowed_border_size],
|
||||
[half_machine_width - disallowed_border_size, -half_machine_depth + disallowed_border_size]
|
||||
], numpy.float32)))
|
||||
|
||||
# Check if the prime tower area intersects with any of the other areas.
|
||||
# If this is the case, keep the polygon seperate, so it can be drawn in red.
|
||||
# If this is the case, add it to the error area's so it can be drawn in red.
|
||||
# If not, add it back to disallowed area's, so it's rendered as normal.
|
||||
collision = False
|
||||
if self._prime_tower_area:
|
||||
prime_tower_collision = False
|
||||
if prime_tower_area:
|
||||
# Using Minkowski of 0 fixes the prime tower area so it's rendered correctly
|
||||
prime_tower_area = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(0))
|
||||
for area in areas:
|
||||
if self._prime_tower_area.intersectsPolygon(area) is not None:
|
||||
collision = True
|
||||
if prime_tower_area.intersectsPolygon(area) is not None:
|
||||
prime_tower_collision = True
|
||||
break
|
||||
if not collision:
|
||||
areas.append(self._prime_tower_area)
|
||||
self._prime_tower_area = None
|
||||
self._has_errors = collision
|
||||
|
||||
if not prime_tower_collision:
|
||||
areas.append(prime_tower_area)
|
||||
else:
|
||||
self._error_areas.append(prime_tower_area)
|
||||
# The buildplate has errors if either prime tower or prime has a colission.
|
||||
self._has_errors = prime_tower_collision or prime_collision
|
||||
self._disallowed_areas = areas
|
||||
|
||||
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
|
||||
def _getSettingProperty(self, setting_key, property = "value"):
|
||||
## Private convenience function to get a setting from the adhesion
|
||||
# extruder.
|
||||
#
|
||||
# \param setting_key The key of the setting to get.
|
||||
# \param property The property to get from the setting.
|
||||
# \return The property of the specified setting in the adhesion extruder.
|
||||
def _getSettingFromAdhesionExtruder(self, setting_key, property = "value"):
|
||||
return self._getSettingFromExtruder(setting_key, "adhesion_extruder_nr", property)
|
||||
|
||||
## Private convenience function to get a setting from the support infill
|
||||
# extruder.
|
||||
#
|
||||
# \param setting_key The key of the setting to get.
|
||||
# \param property The property to get from the setting.
|
||||
# \return The property of the specified setting in the support infill
|
||||
# extruder.
|
||||
def _getSettingFromSupportInfillExtruder(self, setting_key, property = "value"):
|
||||
return self._getSettingFromExtruder(setting_key, "support_infill_extruder_nr", property)
|
||||
|
||||
## Helper function to get a setting from an extruder specified in another
|
||||
# setting.
|
||||
#
|
||||
# \param setting_key The key of the setting to get.
|
||||
# \param extruder_setting_key The key of the setting that specifies from
|
||||
# which extruder to get the setting, if there are multiple extruders.
|
||||
# \param property The property to get from the setting.
|
||||
# \return The property of the specified setting in the specified extruder.
|
||||
def _getSettingFromExtruder(self, setting_key, extruder_setting_key, property = "value"):
|
||||
multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
|
||||
if not multi_extrusion:
|
||||
return self._global_container_stack.getProperty(setting_key, property)
|
||||
|
||||
extruder_index = self._global_container_stack.getProperty(setting_key, "limit_to_extruder")
|
||||
extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value")
|
||||
|
||||
if extruder_index == "-1": # If extruder index is -1 use global instead
|
||||
return self._global_container_stack.getProperty(setting_key, property)
|
||||
|
||||
|
@ -433,12 +514,15 @@ class BuildVolume(SceneNode):
|
|||
stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
return stack.getProperty(setting_key, property)
|
||||
|
||||
## Convenience function to calculate the size of the bed adhesion in directions x, y.
|
||||
def _getBedAdhesionSize(self):
|
||||
## Convenience function to calculate the disallowed radius around the edge.
|
||||
#
|
||||
# This disallowed radius is to allow for space around the models that is
|
||||
# not part of the collision radius, such as bed adhesion (skirt/brim/raft)
|
||||
# and travel avoid distance.
|
||||
def _getEdgeDisallowedSize(self):
|
||||
if not self._global_container_stack:
|
||||
return 0
|
||||
container_stack = self._global_container_stack
|
||||
skirt_size = 0.0
|
||||
|
||||
# If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects
|
||||
if container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
|
@ -446,26 +530,56 @@ class BuildVolume(SceneNode):
|
|||
|
||||
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
||||
if adhesion_type == "skirt":
|
||||
skirt_distance = self._getSettingProperty("skirt_gap", "value")
|
||||
skirt_line_count = self._getSettingProperty("skirt_line_count", "value")
|
||||
skirt_size = skirt_distance + (skirt_line_count * self._getSettingProperty("skirt_brim_line_width", "value"))
|
||||
skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap")
|
||||
skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count")
|
||||
bed_adhesion_size = skirt_distance + (skirt_line_count * self._getSettingFromAdhesionExtruder("skirt_brim_line_width"))
|
||||
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
adhesion_extruder_nr = int(self._global_container_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||
extruder_values = ExtruderManager.getInstance().getAllExtruderValues("skirt_brim_line_width")
|
||||
del extruder_values[adhesion_extruder_nr] # Remove the value of the adhesion extruder nr.
|
||||
for value in extruder_values:
|
||||
bed_adhesion_size += value
|
||||
elif adhesion_type == "brim":
|
||||
skirt_size = self._getSettingProperty("brim_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value")
|
||||
bed_adhesion_size = self._getSettingFromAdhesionExtruder("brim_line_count") * self._getSettingFromAdhesionExtruder("skirt_brim_line_width")
|
||||
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
adhesion_extruder_nr = int(self._global_container_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||
extruder_values = ExtruderManager.getInstance().getAllExtruderValues("skirt_brim_line_width")
|
||||
del extruder_values[adhesion_extruder_nr] # Remove the value of the adhesion extruder nr.
|
||||
for value in extruder_values:
|
||||
bed_adhesion_size += value
|
||||
elif adhesion_type == "raft":
|
||||
skirt_size = self._getSettingProperty("raft_margin", "value")
|
||||
bed_adhesion_size = self._getSettingFromAdhesionExtruder("raft_margin")
|
||||
else:
|
||||
raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
|
||||
|
||||
support_expansion = 0
|
||||
if self._getSettingFromSupportInfillExtruder("support_offset"):
|
||||
support_expansion += self._getSettingFromSupportInfillExtruder("support_offset")
|
||||
|
||||
farthest_shield_distance = 0
|
||||
if container_stack.getProperty("draft_shield_enabled", "value"):
|
||||
skirt_size += container_stack.getProperty("draft_shield_dist", "value")
|
||||
farthest_shield_distance = max(farthest_shield_distance, container_stack.getProperty("draft_shield_dist", "value"))
|
||||
if container_stack.getProperty("ooze_shield_enabled", "value"):
|
||||
farthest_shield_distance = max(farthest_shield_distance, container_stack.getProperty("ooze_shield_dist", "value"))
|
||||
|
||||
if container_stack.getProperty("xy_offset", "value"):
|
||||
skirt_size += container_stack.getProperty("xy_offset", "value")
|
||||
move_from_wall_radius = 0 # Moves that start from outer wall.
|
||||
if self._getSettingFromAdhesionExtruder("infill_wipe_dist"):
|
||||
move_from_wall_radius = max(move_from_wall_radius, self._getSettingFromAdhesionExtruder("infill_wipe_dist"))
|
||||
if self._getSettingFromAdhesionExtruder("travel_avoid_distance"):
|
||||
move_from_wall_radius = max(move_from_wall_radius, self._getSettingFromAdhesionExtruder("travel_avoid_distance"))
|
||||
|
||||
return skirt_size
|
||||
#Now combine our different pieces of data to get the final border size.
|
||||
#Support expansion is added to the bed adhesion, since the bed adhesion goes around support.
|
||||
#Support expansion is added to farthest shield distance, since the shields go around support.
|
||||
border_size = max(move_from_wall_radius, support_expansion + farthest_shield_distance, support_expansion + bed_adhesion_size)
|
||||
return border_size
|
||||
|
||||
def _clamp(self, value, min_value, max_value):
|
||||
return max(min(value, max_value), min_value)
|
||||
|
||||
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"]
|
||||
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist"]
|
||||
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"]
|
||||
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"]
|
||||
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
|
||||
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
|
||||
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset"]
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
from UM.Application import Application
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
@ -112,7 +115,13 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
self._convex_hull_node = hull_node
|
||||
|
||||
def _onSettingValueChanged(self, key, property_name):
|
||||
if key in self._affected_settings and property_name == "value":
|
||||
if property_name != "value": #Not the value that was changed.
|
||||
return
|
||||
|
||||
if key in self._affected_settings:
|
||||
self._onChanged()
|
||||
if key in self._influencing_settings:
|
||||
self._init2DConvexHullCache() #Invalidate the cache.
|
||||
self._onChanged()
|
||||
|
||||
def _init2DConvexHullCache(self):
|
||||
|
@ -141,21 +150,17 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
if child_polygon == self._2d_convex_hull_group_child_polygon:
|
||||
return self._2d_convex_hull_group_result
|
||||
|
||||
# First, calculate the normal convex hull around the points
|
||||
convex_hull = child_polygon.getConvexHull()
|
||||
|
||||
# Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull.
|
||||
# This is done because of rounding errors.
|
||||
rounded_hull = self._roundHull(convex_hull)
|
||||
convex_hull = child_polygon.getConvexHull() #First calculate the normal convex hull around the points.
|
||||
offset_hull = self._offsetHull(convex_hull) #Then apply the offset from the settings.
|
||||
|
||||
# Store the result in the cache
|
||||
self._2d_convex_hull_group_child_polygon = child_polygon
|
||||
self._2d_convex_hull_group_result = rounded_hull
|
||||
self._2d_convex_hull_group_result = offset_hull
|
||||
|
||||
return rounded_hull
|
||||
return offset_hull
|
||||
|
||||
else:
|
||||
rounded_hull = None
|
||||
offset_hull = None
|
||||
mesh = None
|
||||
world_transform = None
|
||||
if self._node.getMeshData():
|
||||
|
@ -193,19 +198,15 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
hull = Polygon(vertex_data)
|
||||
|
||||
if len(vertex_data) >= 4:
|
||||
# First, calculate the normal convex hull around the points
|
||||
convex_hull = hull.getConvexHull()
|
||||
|
||||
# Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull.
|
||||
# This is done because of rounding errors.
|
||||
rounded_hull = convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))
|
||||
offset_hull = self._offsetHull(convex_hull)
|
||||
|
||||
# Store the result in the cache
|
||||
self._2d_convex_hull_mesh = mesh
|
||||
self._2d_convex_hull_mesh_world_transform = world_transform
|
||||
self._2d_convex_hull_mesh_result = rounded_hull
|
||||
self._2d_convex_hull_mesh_result = offset_hull
|
||||
|
||||
return rounded_hull
|
||||
return offset_hull
|
||||
|
||||
def _getHeadAndFans(self):
|
||||
return Polygon(numpy.array(self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32))
|
||||
|
@ -228,7 +229,6 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
# Compensate for raft/skirt/brim
|
||||
# Add extra margin depending on adhesion type
|
||||
adhesion_type = self._global_stack.getProperty("adhesion_type", "value")
|
||||
extra_margin = 0
|
||||
|
||||
if adhesion_type == "raft":
|
||||
extra_margin = max(0, self._getSettingProperty("raft_margin", "value"))
|
||||
|
@ -237,27 +237,33 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
elif adhesion_type == "skirt":
|
||||
extra_margin = max(
|
||||
0, self._getSettingProperty("skirt_gap", "value") +
|
||||
self._getSettingPropertyy("skirt_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value"))
|
||||
self._getSettingProperty("skirt_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value"))
|
||||
else:
|
||||
raise Exception("Unknown bed adhesion type. Did you forget to update the convex hull calculations for your new bed adhesion type?")
|
||||
|
||||
# adjust head_and_fans with extra margin
|
||||
if extra_margin > 0:
|
||||
# In Cura 2.2+, there is a function to create this circle-like polygon.
|
||||
extra_margin_polygon = Polygon(numpy.array([
|
||||
[-extra_margin, 0],
|
||||
[-extra_margin * 0.707, extra_margin * 0.707],
|
||||
[0, extra_margin],
|
||||
[extra_margin * 0.707, extra_margin * 0.707],
|
||||
[extra_margin, 0],
|
||||
[extra_margin * 0.707, -extra_margin * 0.707],
|
||||
[0, -extra_margin],
|
||||
[-extra_margin * 0.707, -extra_margin * 0.707]
|
||||
], numpy.float32))
|
||||
|
||||
extra_margin_polygon = Polygon.approximatedCircle(extra_margin)
|
||||
poly = poly.getMinkowskiHull(extra_margin_polygon)
|
||||
return poly
|
||||
|
||||
def _roundHull(self, convex_hull):
|
||||
return convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))
|
||||
## Offset the convex hull with settings that influence the collision area.
|
||||
#
|
||||
# This also applies a minimum offset of 0.5mm, because of edge cases due
|
||||
# to the rounding we apply.
|
||||
#
|
||||
# \param convex_hull Polygon of the original convex hull.
|
||||
# \return New Polygon instance that is offset with everything that
|
||||
# influences the collision area.
|
||||
def _offsetHull(self, convex_hull):
|
||||
horizontal_expansion = max(0.5, self._getSettingProperty("xy_offset", "value"))
|
||||
expansion_polygon = Polygon(numpy.array([
|
||||
[-horizontal_expansion, -horizontal_expansion],
|
||||
[-horizontal_expansion, horizontal_expansion],
|
||||
[horizontal_expansion, horizontal_expansion],
|
||||
[horizontal_expansion, -horizontal_expansion]
|
||||
], numpy.float32))
|
||||
return convex_hull.getMinkowskiHull(expansion_polygon)
|
||||
|
||||
def _onChanged(self, *args):
|
||||
self._raft_thickness = self._build_volume.getRaftThickness()
|
||||
|
@ -285,20 +291,27 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
|
||||
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
|
||||
def _getSettingProperty(self, setting_key, property="value"):
|
||||
multi_extrusion = self._global_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
per_mesh_stack = self._node.callDecoration("getStack")
|
||||
if per_mesh_stack:
|
||||
return per_mesh_stack.getProperty(setting_key, property)
|
||||
|
||||
multi_extrusion = self._global_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
if not multi_extrusion:
|
||||
return self._global_stack.getProperty(setting_key, property)
|
||||
|
||||
extruder_index = self._global_stack.getProperty(setting_key, "limit_to_extruder")
|
||||
if extruder_index == "-1": # If extruder index is -1 use global instead
|
||||
return self._global_stack.getProperty(setting_key, property)
|
||||
if extruder_index == "-1": #No limit_to_extruder.
|
||||
extruder_stack_id = self._node.callDecoration("getActiveExtruder")
|
||||
if not extruder_stack_id: #Decoration doesn't exist.
|
||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"]
|
||||
extruder_stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
return extruder_stack.getProperty(setting_key, property)
|
||||
else: #Limit_to_extruder is set. Use that one.
|
||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)]
|
||||
stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
return stack.getProperty(setting_key, property)
|
||||
|
||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)]
|
||||
stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack_id)[0]
|
||||
return stack.getProperty(setting_key, property)
|
||||
|
||||
## Returns true if node is a descendent or the same as the root node.
|
||||
## Returns true if node is a descendant or the same as the root node.
|
||||
def __isDescendant(self, root, node):
|
||||
if node is None:
|
||||
return False
|
||||
|
@ -310,3 +323,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
"adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers",
|
||||
"raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence",
|
||||
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
|
||||
|
||||
## Settings that change the convex hull.
|
||||
#
|
||||
# If these settings change, the convex hull should be recalculated.
|
||||
_influencing_settings = {"xy_offset"}
|
|
@ -49,11 +49,44 @@ from PyQt5.QtGui import QColor, QIcon
|
|||
from PyQt5.QtWidgets import QMessageBox
|
||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import numpy
|
||||
import copy
|
||||
import urllib
|
||||
import os
|
||||
import time
|
||||
|
||||
CONFIG_LOCK_FILENAME = "cura.lock"
|
||||
|
||||
## Contextmanager to create a lock file and remove it afterwards.
|
||||
@contextmanager
|
||||
def lockFile(filename):
|
||||
try:
|
||||
with open(filename, 'w') as lock_file:
|
||||
lock_file.write("Lock file - Cura is currently writing")
|
||||
except:
|
||||
Logger.log("e", "Could not create lock file [%s]" % filename)
|
||||
yield
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
except:
|
||||
Logger.log("e", "Could not delete lock file [%s]" % filename)
|
||||
|
||||
|
||||
## Wait for a lock file to disappear
|
||||
# the maximum allowable age is settable; if the file is too old, it will be ignored too
|
||||
def waitFileDisappear(filename, max_age_seconds=10, msg=""):
|
||||
now = time.time()
|
||||
while os.path.exists(filename) and now < os.path.getmtime(filename) + max_age_seconds and now > os.path.getmtime(filename):
|
||||
if msg:
|
||||
Logger.log("d", msg)
|
||||
time.sleep(1)
|
||||
now = time.time()
|
||||
|
||||
|
||||
numpy.seterr(all="ignore")
|
||||
|
||||
|
@ -86,14 +119,24 @@ class CuraApplication(QtApplication):
|
|||
# Need to do this before ContainerRegistry tries to load the machines
|
||||
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
# this setting can be changed for each group in one-at-a-time mode
|
||||
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
|
||||
# From which stack the setting would inherit if not defined per object (handled in the engine)
|
||||
# AND for settings which are not settable_per_mesh:
|
||||
# which extruder is the only extruder this setting is obtained from
|
||||
SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1")
|
||||
|
||||
# For settings which are not settable_per_mesh and not settable_per_extruder:
|
||||
# A function which determines the glabel/meshgroup value by looking at the values of the setting in all (used) extruders
|
||||
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None)
|
||||
|
||||
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
||||
|
||||
SettingFunction.registerOperator("extruderValues", cura.Settings.ExtruderManager.getExtruderValues)
|
||||
SettingFunction.registerOperator("extruderValue", cura.Settings.ExtruderManager.getExtruderValue)
|
||||
SettingFunction.registerOperator("resolveOrValue", cura.Settings.ExtruderManager.getResolveOrValue)
|
||||
|
||||
## Add the 4 types of profiles to storage.
|
||||
Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality")
|
||||
|
@ -112,16 +155,18 @@ class CuraApplication(QtApplication):
|
|||
|
||||
## Initialise the version upgrade manager with Cura's storage paths.
|
||||
import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies.
|
||||
self._version_upgrade_manager = UM.VersionUpgradeManager.VersionUpgradeManager(
|
||||
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
|
||||
{
|
||||
("quality", UM.Settings.InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("machine_stack", UM.Settings.ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
|
||||
("preferences", UM.Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences")
|
||||
("preferences", UM.Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"),
|
||||
("user", UM.Settings.InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
|
||||
}
|
||||
)
|
||||
|
||||
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
||||
self._machine_manager = None # This is initialized on demand.
|
||||
self._setting_inheritance_manager = None
|
||||
|
||||
self._additional_components = {} # Components to add to certain areas in the interface
|
||||
|
||||
|
@ -189,6 +234,9 @@ class CuraApplication(QtApplication):
|
|||
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
|
||||
|
||||
# Set the filename to create if cura is writing in the config dir.
|
||||
self._config_lock_filename = os.path.join(Resources.getConfigStoragePath(), CONFIG_LOCK_FILENAME)
|
||||
self.waitConfigLockFile()
|
||||
ContainerRegistry.getInstance().load()
|
||||
|
||||
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
||||
|
@ -269,6 +317,12 @@ class CuraApplication(QtApplication):
|
|||
|
||||
self._recent_files.append(QUrl.fromLocalFile(f))
|
||||
|
||||
## Lock file check: if (another) Cura is writing in the Config dir.
|
||||
# one may not be able to read a valid set of files while writing. Not entirely fool-proof,
|
||||
# but works when you start Cura shortly after shutting down.
|
||||
def waitConfigLockFile(self):
|
||||
waitFileDisappear(self._config_lock_filename, max_age_seconds=10, msg="Waiting for Cura to finish writing in the config dir...")
|
||||
|
||||
def _onEngineCreated(self):
|
||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||
|
||||
|
@ -296,38 +350,43 @@ class CuraApplication(QtApplication):
|
|||
if not self._started: # Do not do saving during application start
|
||||
return
|
||||
|
||||
for instance in ContainerRegistry.getInstance().findInstanceContainers():
|
||||
if not instance.isDirty():
|
||||
continue
|
||||
self.waitConfigLockFile()
|
||||
|
||||
try:
|
||||
data = instance.serialize()
|
||||
except NotImplementedError:
|
||||
continue
|
||||
except Exception:
|
||||
Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
|
||||
continue
|
||||
# When starting Cura, we check for the lockFile which is created and deleted here
|
||||
with lockFile(self._config_lock_filename):
|
||||
|
||||
mime_type = ContainerRegistry.getMimeTypeForContainer(type(instance))
|
||||
file_name = urllib.parse.quote_plus(instance.getId()) + "." + mime_type.preferredSuffix
|
||||
instance_type = instance.getMetaDataEntry("type")
|
||||
path = None
|
||||
if instance_type == "material":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.MaterialInstanceContainer, file_name)
|
||||
elif instance_type == "quality" or instance_type == "quality_changes":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.QualityInstanceContainer, file_name)
|
||||
elif instance_type == "user":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
|
||||
elif instance_type == "variant":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
|
||||
for instance in ContainerRegistry.getInstance().findInstanceContainers():
|
||||
if not instance.isDirty():
|
||||
continue
|
||||
|
||||
if path:
|
||||
instance.setPath(path)
|
||||
with SaveFile(path, "wt", -1, "utf-8") as f:
|
||||
f.write(data)
|
||||
try:
|
||||
data = instance.serialize()
|
||||
except NotImplementedError:
|
||||
continue
|
||||
except Exception:
|
||||
Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
|
||||
continue
|
||||
|
||||
for stack in ContainerRegistry.getInstance().findContainerStacks():
|
||||
self.saveStack(stack)
|
||||
mime_type = ContainerRegistry.getMimeTypeForContainer(type(instance))
|
||||
file_name = urllib.parse.quote_plus(instance.getId()) + "." + mime_type.preferredSuffix
|
||||
instance_type = instance.getMetaDataEntry("type")
|
||||
path = None
|
||||
if instance_type == "material":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.MaterialInstanceContainer, file_name)
|
||||
elif instance_type == "quality" or instance_type == "quality_changes":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.QualityInstanceContainer, file_name)
|
||||
elif instance_type == "user":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
|
||||
elif instance_type == "variant":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
|
||||
|
||||
if path:
|
||||
instance.setPath(path)
|
||||
with SaveFile(path, "wt", -1, "utf-8") as f:
|
||||
f.write(data)
|
||||
|
||||
for stack in ContainerRegistry.getInstance().findContainerStacks():
|
||||
self.saveStack(stack)
|
||||
|
||||
def saveStack(self, stack):
|
||||
if not stack.isDirty():
|
||||
|
@ -426,7 +485,7 @@ class CuraApplication(QtApplication):
|
|||
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
|
||||
cura.Settings.ExtruderManager.getInstance()
|
||||
qmlRegisterSingletonType(cura.Settings.MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||
|
||||
qmlRegisterSingletonType(cura.Settings.SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
|
||||
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))
|
||||
|
@ -449,6 +508,11 @@ class CuraApplication(QtApplication):
|
|||
self._machine_manager = cura.Settings.MachineManager.createMachineManager()
|
||||
return self._machine_manager
|
||||
|
||||
def getSettingInheritanceManager(self, *args):
|
||||
if self._setting_inheritance_manager is None:
|
||||
self._setting_inheritance_manager = cura.Settings.SettingInheritanceManager.createSettingInheritanceManager()
|
||||
return self._setting_inheritance_manager
|
||||
|
||||
## Get the machine action manager
|
||||
# We ignore any *args given to this, as we also register the machine manager as qml singleton.
|
||||
# It wants to give this function an engine and script engine, but we don't care about that.
|
||||
|
@ -485,6 +549,9 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterType(cura.Settings.ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||
|
||||
qmlRegisterType(cura.Settings.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
||||
qmlRegisterType(cura.Settings.ProfilesModel, "Cura", 1, 0, "ProfilesModel")
|
||||
qmlRegisterType(cura.Settings.QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel")
|
||||
qmlRegisterType(cura.Settings.UserProfilesModel, "Cura", 1, 0, "UserProfilesModel")
|
||||
qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||
qmlRegisterType(cura.Settings.QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
||||
|
||||
|
@ -579,8 +646,6 @@ class CuraApplication(QtApplication):
|
|||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||
op.push()
|
||||
|
||||
pass
|
||||
|
||||
## Remove an object from the scene.
|
||||
# Note that this only removes an object if it is selected.
|
||||
@pyqtSlot("quint64")
|
||||
|
@ -594,17 +659,17 @@ class CuraApplication(QtApplication):
|
|||
node = Selection.getSelectedObject(0)
|
||||
|
||||
if node:
|
||||
group_node = None
|
||||
if node.getParent():
|
||||
group_node = node.getParent()
|
||||
op = RemoveSceneNodeOperation(node)
|
||||
op = GroupedOperation()
|
||||
op.addOperation(RemoveSceneNodeOperation(node))
|
||||
|
||||
group_node = node.getParent()
|
||||
if group_node:
|
||||
# Note that at this point the node has not yet been deleted
|
||||
if len(group_node.getChildren()) <= 2 and group_node.callDecoration("isGroup"):
|
||||
op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent()))
|
||||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||
|
||||
op.push()
|
||||
if group_node:
|
||||
if len(group_node.getChildren()) == 1 and group_node.callDecoration("isGroup"):
|
||||
op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent()))
|
||||
op = RemoveSceneNodeOperation(group_node)
|
||||
op.push()
|
||||
|
||||
## Create a number of copies of existing object.
|
||||
@pyqtSlot("quint64", int)
|
||||
|
@ -800,11 +865,14 @@ class CuraApplication(QtApplication):
|
|||
return
|
||||
|
||||
# Compute the center of the objects when their origins are aligned.
|
||||
object_centers = [node.getMeshData().getCenterPosition().scale(node.getScale()) for node in group_node.getChildren()]
|
||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
||||
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
||||
offset = Vector(middle_x, middle_y, middle_z)
|
||||
object_centers = [node.getMeshData().getCenterPosition().scale(node.getScale()) for node in group_node.getChildren() if node.getMeshData()]
|
||||
if object_centers and len(object_centers) > 0:
|
||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
||||
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
||||
offset = Vector(middle_x, middle_y, middle_z)
|
||||
else:
|
||||
offset = Vector(0, 0, 0)
|
||||
|
||||
# Move each node to the same position.
|
||||
for center, node in zip(object_centers, group_node.getChildren()):
|
||||
|
|
|
@ -37,8 +37,8 @@ class LayerPolygon:
|
|||
|
||||
# Buffering the colors shouldn't be necessary as it is not
|
||||
# re-used and can save alot of memory usage.
|
||||
self._colors = self.__color_map[self._types]
|
||||
self._color_map = self.__color_map
|
||||
self._color_map = self.__color_map * [1, 1, 1, self._extruder] # The alpha component is used to store the extruder nr
|
||||
self._colors = self._color_map[self._types]
|
||||
|
||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
||||
# Should be generated in better way, not hardcoded.
|
||||
|
@ -172,31 +172,17 @@ class LayerPolygon:
|
|||
|
||||
return normals
|
||||
|
||||
__color_mapping = {
|
||||
NoneType: Color(1.0, 1.0, 1.0, 1.0),
|
||||
Inset0Type: Color(1.0, 0.0, 0.0, 1.0),
|
||||
InsetXType: Color(0.0, 1.0, 0.0, 1.0),
|
||||
SkinType: Color(1.0, 1.0, 0.0, 1.0),
|
||||
SupportType: Color(0.0, 1.0, 1.0, 1.0),
|
||||
SkirtType: Color(0.0, 1.0, 1.0, 1.0),
|
||||
InfillType: Color(1.0, 0.75, 0.0, 1.0),
|
||||
SupportInfillType: Color(0.0, 1.0, 1.0, 1.0),
|
||||
MoveCombingType: Color(0.0, 0.0, 1.0, 1.0),
|
||||
MoveRetractionType: Color(0.5, 0.5, 1.0, 1.0),
|
||||
SupportInterfaceType: Color(0.25, 0.75, 1.0, 1.0),
|
||||
}
|
||||
|
||||
# Should be generated in better way, not hardcoded.
|
||||
__color_map = numpy.array([
|
||||
[1.0, 1.0, 1.0, 1.0],
|
||||
[1.0, 0.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 0.0, 1.0],
|
||||
[1.0, 1.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[1.0, 0.75, 0.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[0.0, 0.0, 1.0, 1.0],
|
||||
[0.5, 0.5, 1.0, 1.0],
|
||||
[0.25, 0.75, 1.0, 1.0]
|
||||
[1.0, 1.0, 1.0, 1.0], # NoneType
|
||||
[1.0, 0.0, 0.0, 1.0], # Inset0Type
|
||||
[0.0, 1.0, 0.0, 1.0], # InsetXType
|
||||
[1.0, 1.0, 0.0, 1.0], # SkinType
|
||||
[0.0, 1.0, 1.0, 1.0], # SupportType
|
||||
[0.0, 1.0, 1.0, 1.0], # SkirtType
|
||||
[1.0, 0.75, 0.0, 1.0], # InfillType
|
||||
[0.0, 1.0, 1.0, 1.0], # SupportInfillType
|
||||
[0.0, 0.0, 1.0, 1.0], # MoveCombingType
|
||||
[0.5, 0.5, 1.0, 1.0], # MoveRetractionType
|
||||
[0.25, 0.75, 1.0, 1.0] # SupportInterfaceType
|
||||
])
|
|
@ -102,7 +102,7 @@ class PlatformPhysics:
|
|||
continue
|
||||
|
||||
# Ignore collisions within a group
|
||||
if other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None:
|
||||
if other_node.getParent() and node.getParent() and (other_node.getParent().callDecoration("isGroup") is not None or node.getParent().callDecoration("isGroup") is not None):
|
||||
continue
|
||||
|
||||
# Ignore nodes that do not have the right properties set.
|
||||
|
|
|
@ -121,6 +121,7 @@ class PrintInformation(QObject):
|
|||
@pyqtSlot(str, result = str)
|
||||
def createJobName(self, base_name):
|
||||
base_name = self._stripAccents(base_name)
|
||||
self._setAbbreviatedMachineName()
|
||||
if Preferences.getInstance().getValue("cura/jobname_prefix"):
|
||||
return self._abbr_machine + "_" + base_name
|
||||
else:
|
||||
|
@ -129,7 +130,12 @@ class PrintInformation(QObject):
|
|||
## Created an acronymn-like abbreviated machine name from the currently active machine name
|
||||
# Called each time the global stack is switched
|
||||
def _setAbbreviatedMachineName(self):
|
||||
global_stack_name = Application.getInstance().getGlobalContainerStack().getName()
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
self._abbr_machine = ""
|
||||
return
|
||||
|
||||
global_stack_name = global_container_stack.getName()
|
||||
split_name = global_stack_name.split(" ")
|
||||
abbr_machine = ""
|
||||
for word in split_name:
|
||||
|
|
|
@ -47,6 +47,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self._accepts_commands = True
|
||||
|
||||
self._printer_state = ""
|
||||
self._printer_type = "unknown"
|
||||
|
||||
def requestWrite(self, node, file_name = None, filter_by_machine = False):
|
||||
raise NotImplementedError("requestWrite needs to be implemented")
|
||||
|
@ -97,6 +98,12 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
printerStateChanged = pyqtSignal()
|
||||
|
||||
printerTypeChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(str, notify=printerTypeChanged)
|
||||
def printerType(self):
|
||||
return self._printer_type
|
||||
|
||||
@pyqtProperty(str, notify=printerStateChanged)
|
||||
def printerState(self):
|
||||
return self._printer_state
|
||||
|
@ -105,6 +112,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
def jobState(self):
|
||||
return self._job_state
|
||||
|
||||
def _updatePrinterType(self, printer_type):
|
||||
if self._printer_type != printer_type:
|
||||
self._printer_type = printer_type
|
||||
self.printerTypeChanged.emit()
|
||||
|
||||
def _updatePrinterState(self, printer_state):
|
||||
if self._printer_state != printer_state:
|
||||
self._printer_state = printer_state
|
||||
|
|
270
cura/QualityManager.py
Normal file
270
cura/QualityManager.py
Normal file
|
@ -0,0 +1,270 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
import UM.Application
|
||||
import cura.Settings.ExtruderManager
|
||||
import UM.Settings.ContainerRegistry
|
||||
|
||||
# This collects a lot of quality and quality changes related code which was split between ContainerManager
|
||||
# and the MachineManager and really needs to usable from both.
|
||||
|
||||
class QualityManager:
|
||||
|
||||
## Get the singleton instance for this class.
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
# Note: Explicit use of class name to prevent issues with inheritance.
|
||||
if QualityManager.__instance is None:
|
||||
QualityManager.__instance = cls()
|
||||
return QualityManager.__instance
|
||||
|
||||
__instance = None
|
||||
|
||||
## Find a quality by name for a specific machine definition and materials.
|
||||
#
|
||||
# \param quality_name
|
||||
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
|
||||
# specified then the currently selected machine definition is used.
|
||||
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
|
||||
# the current set of selected materials is used.
|
||||
# \return the matching quality containers \type{List[ContainerInstance]}
|
||||
def findQualityByName(self, quality_name, machine_definition=None, material_containers=None):
|
||||
criteria = {"type": "quality", "name": quality_name}
|
||||
return self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
|
||||
|
||||
## Find a quality changes container by name.
|
||||
#
|
||||
# \param quality_changes_name \type{str} the name of the quality changes container.
|
||||
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
|
||||
# specified then the currently selected machine definition is used.
|
||||
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
|
||||
# the current set of selected materials is used.
|
||||
# \return the matching quality changes containers \type{List[ContainerInstance]}
|
||||
def findQualityChangesByName(self, quality_changes_name, machine_definition=None):
|
||||
criteria = {"type": "quality_changes", "name": quality_changes_name}
|
||||
result = self._getFilteredContainersForStack(machine_definition, [], **criteria)
|
||||
|
||||
return result
|
||||
|
||||
## Fetch the list of available quality types for this combination of machine definition and materials.
|
||||
#
|
||||
# \param machine_definition \type{DefinitionContainer}
|
||||
# \param material_containers \type{List[InstanceContainer]}
|
||||
# \return \type{List[str]}
|
||||
def findAllQualityTypesForMachineAndMaterials(self, machine_definition, material_containers):
|
||||
# Determine the common set of quality types which can be
|
||||
# applied to all of the materials for this machine.
|
||||
quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
|
||||
common_quality_types = set(quality_type_dict.keys())
|
||||
for material_container in material_containers[1:]:
|
||||
next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
|
||||
common_quality_types.intersection_update(set(next_quality_type_dict.keys()))
|
||||
|
||||
return list(common_quality_types)
|
||||
|
||||
## Fetches a dict of quality types names to quality profiles for a combination of machine and material.
|
||||
#
|
||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
||||
# \param material \type{ContainerInstance} the material.
|
||||
# \return \type{Dict[str, ContainerInstance]} the dict of suitable quality type names mapping to qualities.
|
||||
def __fetchQualityTypeDictForMaterial(self, machine_definition, material):
|
||||
qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
|
||||
quality_type_dict = {}
|
||||
for quality in qualities:
|
||||
quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
|
||||
return quality_type_dict
|
||||
|
||||
## Find a quality container by quality type.
|
||||
#
|
||||
# \param quality_type \type{str} the name of the quality type to search for.
|
||||
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
|
||||
# specified then the currently selected machine definition is used.
|
||||
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
|
||||
# the current set of selected materials is used.
|
||||
# \return the matching quality container \type{ContainerInstance}
|
||||
def findQualityByQualityType(self, quality_type, machine_definition=None, material_containers=None):
|
||||
criteria = {"type": "quality"}
|
||||
if quality_type:
|
||||
criteria["quality_type"] = quality_type
|
||||
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
|
||||
|
||||
# Fall back to using generic materials and qualities if nothing could be found.
|
||||
if not result and material_containers and len(material_containers) == 1:
|
||||
basic_materials = self._getBasicMaterials(material_containers[0])
|
||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
||||
|
||||
return result[0] if result else None
|
||||
|
||||
## Find all suitable qualities for a combination of machine and material.
|
||||
#
|
||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
||||
# \param material_container \type{ContainerInstance} the material.
|
||||
# \return \type{List[ContainerInstance]} the list of suitable qualities.
|
||||
def findAllQualitiesForMachineMaterial(self, machine_definition, material_container):
|
||||
criteria = {"type": "quality" }
|
||||
result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
|
||||
if not result:
|
||||
basic_materials = self._getBasicMaterials(material_container)
|
||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
||||
|
||||
return result
|
||||
|
||||
## Find all quality changes for a machine.
|
||||
#
|
||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
||||
# \return \type{List[InstanceContainer]} the list of quality changes
|
||||
def findAllQualityChangesForMachine(self, machine_definition):
|
||||
if machine_definition.getMetaDataEntry("has_machine_quality"):
|
||||
definition_id = machine_definition.getId()
|
||||
else:
|
||||
definition_id = "fdmprinter"
|
||||
|
||||
filter_dict = { "type": "quality_changes", "extruder": None, "definition": definition_id }
|
||||
quality_changes_list = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
|
||||
return quality_changes_list
|
||||
|
||||
## Find all usable qualities for a machine and extruders.
|
||||
#
|
||||
# Finds all of the qualities for this combination of machine and extruders.
|
||||
# Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
|
||||
# then only one of then is returned (at random).
|
||||
#
|
||||
# \param global_container_stack \type{ContainerStack} the global machine definition
|
||||
# \param extruder_stacks \type{List[ContainerStack]} the list of extruder stacks
|
||||
# \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
|
||||
# return come from the first extruder in the given list of extruders.
|
||||
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack, extruder_stacks):
|
||||
global_machine_definition = global_container_stack.getBottom()
|
||||
|
||||
if extruder_stacks:
|
||||
# Multi-extruder machine detected.
|
||||
materials = [stack.findContainer(type="material") for stack in extruder_stacks]
|
||||
else:
|
||||
# Machine with one extruder.
|
||||
materials = [global_container_stack.findContainer(type="material")]
|
||||
|
||||
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
|
||||
|
||||
# Map the list of quality_types to InstanceContainers
|
||||
qualities = self.findAllQualitiesForMachineMaterial(global_machine_definition, materials[0])
|
||||
quality_type_dict = {}
|
||||
for quality in qualities:
|
||||
quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
|
||||
|
||||
return [quality_type_dict[quality_type] for quality_type in quality_types]
|
||||
|
||||
## Fetch more basic versions of a material.
|
||||
#
|
||||
# This tries to find a generic or basic version of the given material.
|
||||
# \param material_container \type{InstanceContainer} the material
|
||||
# \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
|
||||
def _getBasicMaterials(self, material_container):
|
||||
base_material = material_container.getMetaDataEntry("material")
|
||||
if material_container.getDefinition().getMetaDataEntry("has_machine_quality"):
|
||||
definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId())
|
||||
else:
|
||||
definition_id = "fdmprinter"
|
||||
|
||||
if base_material:
|
||||
# There is a basic material specified
|
||||
criteria = { "type": "material", "name": base_material, "definition": definition_id }
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
||||
containers = [basic_material for basic_material in containers if
|
||||
basic_material.getMetaDataEntry("variant") == material_container.getMetaDataEntry(
|
||||
"variant")]
|
||||
return containers
|
||||
|
||||
return []
|
||||
|
||||
def _getFilteredContainers(self, **kwargs):
|
||||
return self._getFilteredContainersForStack(None, None, **kwargs)
|
||||
|
||||
def _getFilteredContainersForStack(self, machine_definition=None, material_containers=None, **kwargs):
|
||||
# Fill in any default values.
|
||||
if machine_definition is None:
|
||||
machine_definition = UM.Application.getInstance().getGlobalContainerStack().getBottom()
|
||||
quality_definition_id = machine_definition.getMetaDataEntry("quality_definition")
|
||||
if quality_definition_id is not None:
|
||||
machine_definition = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0]
|
||||
|
||||
if material_containers is None:
|
||||
active_stacks = cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
||||
material_containers = [stack.findContainer(type="material") for stack in active_stacks]
|
||||
|
||||
criteria = kwargs
|
||||
filter_by_material = False
|
||||
|
||||
machine_definition = self.getParentMachineDefinition(machine_definition)
|
||||
whole_machine_definition = self.getWholeMachineDefinition(machine_definition)
|
||||
if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
|
||||
definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId())
|
||||
criteria["definition"] = definition_id
|
||||
|
||||
filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
|
||||
else:
|
||||
criteria["definition"] = "fdmprinter"
|
||||
|
||||
# Stick the material IDs in a set
|
||||
if material_containers is None or len(material_containers) == 0:
|
||||
filter_by_material = False
|
||||
else:
|
||||
material_ids = set()
|
||||
for material_instance in material_containers:
|
||||
if material_instance is not None:
|
||||
material_ids.add(material_instance.getId())
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
||||
|
||||
result = []
|
||||
for container in containers:
|
||||
# If the machine specifies we should filter by material, exclude containers that do not match any active material.
|
||||
if filter_by_material and container.getMetaDataEntry("material") not in material_ids:
|
||||
continue
|
||||
result.append(container)
|
||||
return result
|
||||
|
||||
## Get the parent machine definition of a machine definition.
|
||||
#
|
||||
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
|
||||
# an extruder definition.
|
||||
# \return \type{DefinitionContainer} the parent machine definition. If the given machine
|
||||
# definition doesn't have a parent then it is simply returned.
|
||||
def getParentMachineDefinition(self, machine_definition):
|
||||
container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
|
||||
machine_entry = machine_definition.getMetaDataEntry("machine")
|
||||
if machine_entry is None:
|
||||
# We have a normal (whole) machine defintion
|
||||
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
|
||||
if quality_definition is not None:
|
||||
parent_machine_definition = container_registry.findDefinitionContainers(id=quality_definition)[0]
|
||||
return self.getParentMachineDefinition(parent_machine_definition)
|
||||
else:
|
||||
return machine_definition
|
||||
else:
|
||||
# This looks like an extruder. Find the rest of the machine.
|
||||
whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
|
||||
parent_machine = self.getParentMachineDefinition(whole_machine)
|
||||
if whole_machine is parent_machine:
|
||||
# This extruder already belongs to a 'parent' machine def.
|
||||
return machine_definition
|
||||
else:
|
||||
# Look up the corresponding extruder definition in the parent machine definition.
|
||||
extruder_position = machine_definition.getMetaDataEntry("position")
|
||||
parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
|
||||
return container_registry.findDefinitionContainers(id=parent_extruder_id)[0]
|
||||
|
||||
## Get the whole/global machine definition from an extruder definition.
|
||||
#
|
||||
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
|
||||
# an extruder definition.
|
||||
# \return \type{DefinitionContainer}
|
||||
def getWholeMachineDefinition(self, machine_definition):
|
||||
machine_entry = machine_definition.getMetaDataEntry("machine")
|
||||
if machine_entry is None:
|
||||
# This already is a 'global' machine definition.
|
||||
return machine_definition
|
||||
else:
|
||||
container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0]
|
||||
return whole_machine
|
|
@ -15,6 +15,7 @@ import UM.MimeTypeDatabase
|
|||
import UM.Logger
|
||||
|
||||
import cura.Settings
|
||||
from cura.QualityManager import QualityManager
|
||||
|
||||
from UM.MimeTypeDatabase import MimeTypeNotFoundError
|
||||
|
||||
|
@ -432,9 +433,16 @@ class ContainerManager(QObject):
|
|||
def clearUserContainers(self):
|
||||
self._machine_manager.blurSettings.emit()
|
||||
|
||||
send_emits_containers = []
|
||||
|
||||
# Go through global and extruder stacks and clear their topmost container (the user settings).
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
stack.getTop().clear()
|
||||
container = stack.getTop()
|
||||
container.clear()
|
||||
send_emits_containers.append(container)
|
||||
|
||||
for container in send_emits_containers:
|
||||
container.sendPostponedEmits()
|
||||
|
||||
## Create quality changes containers from the user containers in the active stacks.
|
||||
#
|
||||
|
@ -468,7 +476,11 @@ class ContainerManager(QObject):
|
|||
UM.Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
|
||||
continue
|
||||
|
||||
new_changes = self._createQualityChanges(quality_container, unique_name, stack.getId())
|
||||
extruder_id = None if stack is global_stack else QualityManager.getInstance().getParentMachineDefinition(stack.getBottom()).getId()
|
||||
new_changes = self._createQualityChanges(quality_container, unique_name,
|
||||
UM.Application.getInstance().getGlobalContainerStack().getBottom(),
|
||||
extruder_id)
|
||||
self._performMerge(new_changes, quality_changes_container, clear_settings = False)
|
||||
self._performMerge(new_changes, user_container)
|
||||
|
||||
self._container_registry.addContainer(new_changes)
|
||||
|
@ -498,7 +510,12 @@ class ContainerManager(QObject):
|
|||
activate_quality = quality_name == self._machine_manager.activeQualityName
|
||||
activate_quality_type = None
|
||||
|
||||
for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack or not quality_name:
|
||||
return ""
|
||||
machine_definition = global_stack.getBottom()
|
||||
|
||||
for container in QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition):
|
||||
containers_found = True
|
||||
if activate_quality and not activate_quality_type:
|
||||
activate_quality_type = container.getMetaDataEntry("quality")
|
||||
|
@ -545,10 +562,16 @@ class ContainerManager(QObject):
|
|||
new_name = self._container_registry.uniqueName(new_name)
|
||||
|
||||
container_registry = self._container_registry
|
||||
for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
|
||||
|
||||
containers_to_rename = self._container_registry.findInstanceContainers(type = "quality_changes", name = quality_name)
|
||||
|
||||
for container in containers_to_rename:
|
||||
stack_id = container.getMetaDataEntry("extruder", global_stack.getId())
|
||||
container_registry.renameContainer(container.getId(), new_name, self._createUniqueId(stack_id, new_name))
|
||||
|
||||
if not containers_to_rename:
|
||||
UM.Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name)
|
||||
|
||||
self._machine_manager.activeQualityChanged.emit()
|
||||
return True
|
||||
|
||||
|
@ -566,33 +589,73 @@ class ContainerManager(QObject):
|
|||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack or not quality_name:
|
||||
return ""
|
||||
machine_definition = global_stack.getBottom()
|
||||
|
||||
active_stacks = cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
||||
material_containers = [stack.findContainer(type="material") for stack in active_stacks]
|
||||
|
||||
result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
|
||||
QualityManager.getInstance().getParentMachineDefinition(machine_definition),
|
||||
material_containers)
|
||||
return result[0].getName() if result else ""
|
||||
|
||||
## Duplicate a quality or quality changes profile specific to a machine type
|
||||
#
|
||||
# \param quality_name \type{str} the name of the quality or quality changes container to duplicate.
|
||||
# \param base_name \type{str} the desired name for the new container.
|
||||
# \param machine_definition \type{DefinitionContainer}
|
||||
# \param material_instances \type{List[InstanceContainer]}
|
||||
# \return \type{str} the name of the newly created container.
|
||||
def _duplicateQualityOrQualityChangesForMachineType(self, quality_name, base_name, machine_definition, material_instances):
|
||||
UM.Logger.log("d", "Attempting to duplicate the quality %s", quality_name)
|
||||
containers = self._container_registry.findInstanceContainers(name = quality_name)
|
||||
if not containers:
|
||||
UM.Logger.log("d", "Unable to duplicate the quality %s, because it doesn't exist.", quality_name)
|
||||
return ""
|
||||
|
||||
if base_name is None:
|
||||
base_name = quality_name
|
||||
|
||||
# Try to find a Quality with the name.
|
||||
containers = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_instances)
|
||||
if containers:
|
||||
container = containers[0]
|
||||
return self._duplicateQualityForMachineType(container, base_name, machine_definition)
|
||||
|
||||
# Assume it is a quality changes.
|
||||
return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition)
|
||||
|
||||
# Duplicate a quality profile
|
||||
def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition):
|
||||
if base_name is None:
|
||||
base_name = quality_container.getName()
|
||||
new_name = self._container_registry.uniqueName(base_name)
|
||||
|
||||
container_type = containers[0].getMetaDataEntry("type")
|
||||
if container_type == "quality":
|
||||
for container in self._getFilteredContainers(name = quality_name, type = "quality"):
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
new_changes = self._createQualityChanges(container, new_name, stack.getId())
|
||||
self._container_registry.addContainer(new_changes)
|
||||
elif container_type == "quality_changes":
|
||||
for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
|
||||
stack_id = container.getMetaDataEntry("extruder", global_stack.getId())
|
||||
new_container = container.duplicate(self._createUniqueId(stack_id, new_name), new_name)
|
||||
self._container_registry.addContainer(new_container)
|
||||
else:
|
||||
UM.Logger.log("w", "Unable to duplicate profile. It has the wrong type.")
|
||||
return ""
|
||||
new_change_instances = []
|
||||
|
||||
return new_name
|
||||
# Handle the global stack first.
|
||||
global_changes = self._createQualityChanges(quality_container, new_name, machine_definition, None)
|
||||
new_change_instances.append(global_changes)
|
||||
self._container_registry.addContainer(global_changes)
|
||||
|
||||
# Handle the extruders if present.
|
||||
extruders = machine_definition.getMetaDataEntry("machine_extruder_trains")
|
||||
if extruders:
|
||||
for extruder_id in extruders:
|
||||
extruder = extruders[extruder_id]
|
||||
new_changes = self._createQualityChanges(quality_container, new_name, machine_definition, extruder)
|
||||
new_change_instances.append(new_changes)
|
||||
self._container_registry.addContainer(new_changes)
|
||||
|
||||
return new_change_instances
|
||||
|
||||
# Duplicate a quality changes container
|
||||
def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition):
|
||||
new_change_instances = []
|
||||
for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name,
|
||||
machine_definition):
|
||||
new_unique_id = self._createUniqueId(container.getId(), base_name)
|
||||
new_container = container.duplicate(new_unique_id, base_name)
|
||||
new_change_instances.append(new_container)
|
||||
self._container_registry.addContainer(new_container)
|
||||
|
||||
return new_change_instances
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def duplicateMaterial(self, material_id):
|
||||
|
@ -617,12 +680,22 @@ class ContainerManager(QObject):
|
|||
duplicated_container.setDirty(True)
|
||||
self._container_registry.addContainer(duplicated_container)
|
||||
|
||||
## Get the singleton instance for this class.
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
# Note: Explicit use of class name to prevent issues with inheritance.
|
||||
if ContainerManager.__instance is None:
|
||||
ContainerManager.__instance = cls()
|
||||
return ContainerManager.__instance
|
||||
|
||||
__instance = None
|
||||
|
||||
# Factory function, used by QML
|
||||
@staticmethod
|
||||
def createContainerManager(engine, js_engine):
|
||||
return ContainerManager()
|
||||
return ContainerManager.getInstance()
|
||||
|
||||
def _performMerge(self, merge_into, merge):
|
||||
def _performMerge(self, merge_into, merge, clear_settings = True):
|
||||
assert isinstance(merge, type(merge_into))
|
||||
|
||||
if merge == merge_into:
|
||||
|
@ -631,7 +704,8 @@ class ContainerManager(QObject):
|
|||
for key in merge.getAllKeys():
|
||||
merge_into.setProperty(key, "value", merge.getProperty(key, "value"))
|
||||
|
||||
merge.clear()
|
||||
if clear_settings:
|
||||
merge.clear()
|
||||
|
||||
def _updateContainerNameFilters(self):
|
||||
self._container_name_filters = {}
|
||||
|
@ -677,39 +751,13 @@ class ContainerManager(QObject):
|
|||
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
|
||||
self._container_name_filters[name_filter] = entry
|
||||
|
||||
## Return a generator that iterates over a set of containers that are filtered by machine and material when needed.
|
||||
## Get containers filtered by machine type and material if required.
|
||||
#
|
||||
# \param kwargs Initial search criteria that the containers need to match.
|
||||
#
|
||||
# \return A generator that iterates over the list of containers matching the search criteria.
|
||||
# \return A list of containers matching the search criteria.
|
||||
def _getFilteredContainers(self, **kwargs):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return False
|
||||
|
||||
criteria = kwargs
|
||||
|
||||
filter_by_material = False
|
||||
|
||||
if global_stack.getMetaDataEntry("has_machine_quality"):
|
||||
definition = global_stack.getBottom()
|
||||
definition_id = definition.getMetaDataEntry("quality_definition", definition.getId())
|
||||
criteria["definition"] = definition_id
|
||||
|
||||
filter_by_material = global_stack.getMetaDataEntry("has_materials")
|
||||
|
||||
material_ids = []
|
||||
if filter_by_material:
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
material_ids.append(stack.findContainer(type = "material").getId())
|
||||
|
||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
||||
for container in containers:
|
||||
# If the machine specifies we should filter by material, exclude containers that do not match any active material.
|
||||
if filter_by_material and container.getMetaDataEntry("material") not in material_ids:
|
||||
continue
|
||||
|
||||
yield container
|
||||
return QualityManager.getInstance()._getFilteredContainers(**kwargs)
|
||||
|
||||
## Creates a unique ID for a container by prefixing the name with the stack ID.
|
||||
#
|
||||
|
@ -731,34 +779,26 @@ class ContainerManager(QObject):
|
|||
#
|
||||
# \param quality_container The quality container to create a changes container for.
|
||||
# \param new_name The name of the new quality_changes container.
|
||||
# \param stack_id The ID of the container stack the new container "belongs to". It is used primarily to ensure a unique ID.
|
||||
# \param machine_definition The machine definition this quality changes container is specific to.
|
||||
# \param extruder_id
|
||||
#
|
||||
# \return A new quality_changes container with the specified container as base.
|
||||
def _createQualityChanges(self, quality_container, new_name, stack_id):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
assert global_stack is not None
|
||||
def _createQualityChanges(self, quality_container, new_name, machine_definition, extruder_id):
|
||||
base_id = machine_definition.getId() if extruder_id is None else extruder_id
|
||||
|
||||
# Create a new quality_changes container for the quality.
|
||||
quality_changes = UM.Settings.InstanceContainer(self._createUniqueId(stack_id, new_name))
|
||||
quality_changes = UM.Settings.InstanceContainer(self._createUniqueId(base_id, new_name))
|
||||
quality_changes.setName(new_name)
|
||||
quality_changes.addMetaDataEntry("type", "quality_changes")
|
||||
quality_changes.addMetaDataEntry("quality", quality_container.getMetaDataEntry("quality_type"))
|
||||
quality_changes.addMetaDataEntry("quality_type", quality_container.getMetaDataEntry("quality_type"))
|
||||
|
||||
# If we are creating a container for an extruder, ensure we add that to the container
|
||||
if stack_id != global_stack.getId():
|
||||
quality_changes.addMetaDataEntry("extruder", stack_id)
|
||||
if extruder_id is not None:
|
||||
quality_changes.addMetaDataEntry("extruder", extruder_id)
|
||||
|
||||
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
|
||||
if not global_stack.getMetaDataEntry("has_machine_quality"):
|
||||
if not machine_definition.getMetaDataEntry("has_machine_quality"):
|
||||
quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
|
||||
else:
|
||||
definition = global_stack.getBottom()
|
||||
definition_id = definition.getMetaDataEntry("quality_definition", definition.getId())
|
||||
definition = self._container_registry.findContainers(id=definition_id)[0]
|
||||
quality_changes.setDefinition(definition)
|
||||
|
||||
if global_stack.getMetaDataEntry("has_materials"):
|
||||
material = quality_container.getMetaDataEntry("material")
|
||||
quality_changes.addMetaDataEntry("material", material)
|
||||
|
||||
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition))
|
||||
return quality_changes
|
||||
|
|
|
@ -16,6 +16,9 @@ from UM.Platform import Platform
|
|||
from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with.
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.ContainerManager import ContainerManager
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -81,11 +84,26 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if result == QMessageBox.No:
|
||||
return
|
||||
found_containers = []
|
||||
extruder_positions = []
|
||||
for instance_id in instance_ids:
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id)
|
||||
if containers:
|
||||
found_containers.append(containers[0])
|
||||
|
||||
# Determine the position of the extruder of this container
|
||||
extruder_id = containers[0].getMetaDataEntry("extruder", "")
|
||||
if extruder_id == "":
|
||||
# Global stack
|
||||
extruder_positions.append(-1)
|
||||
else:
|
||||
extruder_containers = ContainerRegistry.getInstance().findDefinitionContainers(id=extruder_id)
|
||||
if extruder_containers:
|
||||
extruder_positions.append(int(extruder_containers[0].getMetaDataEntry("position", 0)))
|
||||
else:
|
||||
extruder_positions.append(0)
|
||||
# Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...)
|
||||
found_containers = [containers for (positions, containers) in sorted(zip(extruder_positions, found_containers))]
|
||||
|
||||
profile_writer = self._findProfileWriter(extension, description)
|
||||
|
||||
try:
|
||||
|
@ -128,14 +146,25 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")}
|
||||
|
||||
plugin_registry = PluginRegistry.getInstance()
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
extension = file_name.split(".")[-1]
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return
|
||||
|
||||
machine_extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
|
||||
machine_extruders.sort(key = lambda k: k.getMetaDataEntry("position"))
|
||||
|
||||
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
|
||||
if meta_data["profile_reader"][0]["extension"] != extension:
|
||||
continue
|
||||
|
||||
profile_reader = plugin_registry.getPluginObject(plugin_id)
|
||||
try:
|
||||
profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader.
|
||||
profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader.
|
||||
except Exception as e:
|
||||
#Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
|
||||
Logger.log("e", "Failed to import profile from %s: %s", file_name, str(e))
|
||||
# Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
|
||||
Logger.log("e", "Failed to import profile from %s: %s while using profile reader", file_name, str(e), profile_reader.getPluginId())
|
||||
return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, str(e))}
|
||||
if profile_or_list: # Success!
|
||||
name_seed = os.path.splitext(os.path.basename(file_name))[0]
|
||||
|
@ -144,31 +173,60 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
self._configureProfile(profile, name_seed)
|
||||
return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) }
|
||||
else:
|
||||
profile_index = -1
|
||||
global_profile = None
|
||||
|
||||
new_name = self.uniqueName(name_seed)
|
||||
|
||||
for profile in profile_or_list:
|
||||
profile.setDirty(True) # Ensure the profiles are correctly saved
|
||||
if profile.getId() != "":
|
||||
container_registry.addContainer(profile)
|
||||
if profile_index >= 0:
|
||||
if len(machine_extruders) > profile_index:
|
||||
extruder_id = machine_extruders[profile_index].getBottom().getId()
|
||||
# Ensure the extruder profiles get non-conflicting names
|
||||
# NB: these are not user-facing
|
||||
if "extruder" in profile.getMetaData():
|
||||
profile.setMetaDataEntry("extruder", extruder_id)
|
||||
else:
|
||||
profile.addMetaDataEntry("extruder", extruder_id)
|
||||
profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_")
|
||||
elif profile_index == 0:
|
||||
# Importing a multiextrusion profile into a single extrusion machine; merge 1st extruder profile into global profile
|
||||
profile._id = self.uniqueName("temporary_profile")
|
||||
self.addContainer(profile)
|
||||
ContainerManager.getInstance().mergeContainers(global_profile.getId(), profile.getId())
|
||||
self.removeContainer(profile.getId())
|
||||
break
|
||||
else:
|
||||
# The imported composite profile has a profile for an extruder that this machine does not have. Ignore this extruder-profile
|
||||
break
|
||||
else:
|
||||
self._configureProfile(profile, name_seed)
|
||||
global_profile = profile
|
||||
profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
|
||||
|
||||
if len(profile_or_list) == 1:
|
||||
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())}
|
||||
else:
|
||||
profile_names = ", ".join([profile.getName() for profile in profile_or_list])
|
||||
return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profiles {0}", profile_names) }
|
||||
self._configureProfile(profile, profile_id, new_name)
|
||||
|
||||
#If it hasn't returned by now, none of the plugins loaded the profile successfully.
|
||||
return { "status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type.", file_name)}
|
||||
profile_index += 1
|
||||
|
||||
def _configureProfile(self, profile, name_seed):
|
||||
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())}
|
||||
|
||||
# If it hasn't returned by now, none of the plugins loaded the profile successfully.
|
||||
return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type.", file_name)}
|
||||
|
||||
def _configureProfile(self, profile, id_seed, new_name):
|
||||
profile.setReadOnly(False)
|
||||
profile.setDirty(True) # Ensure the profiles are correctly saved
|
||||
|
||||
new_name = self.createUniqueName("quality_changes", "", name_seed, catalog.i18nc("@label", "Custom profile"))
|
||||
new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
|
||||
profile._id = new_id
|
||||
profile.setName(new_name)
|
||||
profile._id = new_name
|
||||
|
||||
if "type" in profile.getMetaData():
|
||||
profile.setMetaDataEntry("type", "quality_changes")
|
||||
else:
|
||||
profile.addMetaDataEntry("type", "quality_changes")
|
||||
|
||||
if self._machineHasOwnQualities():
|
||||
profile.setDefinition(self._activeDefinition())
|
||||
profile.setDefinition(self._activeQualityDefinition())
|
||||
if self._machineHasOwnMaterials():
|
||||
profile.addMetaDataEntry("material", self._activeMaterialId())
|
||||
else:
|
||||
|
@ -189,12 +247,14 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
result.append( (plugin_id, meta_data) )
|
||||
return result
|
||||
|
||||
## Gets the active definition
|
||||
# \return the active definition object or None if there is no definition
|
||||
def _activeDefinition(self):
|
||||
## Get the definition to use to select quality profiles for the active machine
|
||||
# \return the active quality definition object or None if there is no quality definition
|
||||
def _activeQualityDefinition(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
definition = global_container_stack.getBottom()
|
||||
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom())
|
||||
definition = self.findDefinitionContainers(id=definition_id)[0]
|
||||
|
||||
if definition:
|
||||
return definition
|
||||
return None
|
||||
|
|
|
@ -13,9 +13,13 @@ import UM.Settings.SettingFunction
|
|||
#
|
||||
# This keeps a list of extruder stacks for each machine.
|
||||
class ExtruderManager(QObject):
|
||||
## Signal to notify other components when the list of extruders changes.
|
||||
## Signal to notify other components when the list of extruders for a machine definition changes.
|
||||
extrudersChanged = pyqtSignal(QVariant)
|
||||
|
||||
## Signal to notify other components when the global container stack is switched to a definition
|
||||
# that has different extruders than the previous global container stack
|
||||
globalContainerStackDefinitionChanged = pyqtSignal()
|
||||
|
||||
## Notify when the user switches the currently active extruder.
|
||||
activeExtruderChanged = pyqtSignal()
|
||||
|
||||
|
@ -25,6 +29,7 @@ class ExtruderManager(QObject):
|
|||
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
|
||||
self._active_extruder_index = 0
|
||||
UM.Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
|
||||
self._global_container_stack_definition_id = None
|
||||
self._addCurrentMachineExtruders()
|
||||
|
||||
## Gets the unique identifier of the currently active extruder stack.
|
||||
|
@ -136,11 +141,8 @@ class ExtruderManager(QObject):
|
|||
for extruder_train in extruder_trains:
|
||||
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
|
||||
|
||||
# Make sure the next stack is a stack that contains only the machine definition
|
||||
if not extruder_train.getNextStack():
|
||||
shallow_stack = UM.Settings.ContainerStack(machine_id + "_shallow")
|
||||
shallow_stack.addContainer(machine_definition)
|
||||
extruder_train.setNextStack(shallow_stack)
|
||||
# regardless of what the next stack is, we have to set it again, because of signal routing.
|
||||
extruder_train.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
|
||||
changed = True
|
||||
if changed:
|
||||
self.extrudersChanged.emit(machine_id)
|
||||
|
@ -180,7 +182,7 @@ class ExtruderManager(QObject):
|
|||
variant = variants[0]
|
||||
preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant")
|
||||
if preferred_variant_id:
|
||||
preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, type = "variant")
|
||||
preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, definition = machine_definition_id, type = "variant")
|
||||
if len(preferred_variants) >= 1:
|
||||
variant = preferred_variants[0]
|
||||
else:
|
||||
|
@ -192,7 +194,8 @@ class ExtruderManager(QObject):
|
|||
material = container_registry.findInstanceContainers(id = "empty_material")[0]
|
||||
if machine_definition.getMetaDataEntry("has_materials"):
|
||||
# First add any material. Later, overwrite with preference if the preference is valid.
|
||||
if machine_definition.getMetaDataEntry("has_variant_materials", default = "False") == "True":
|
||||
machine_has_variant_materials = machine_definition.getMetaDataEntry("has_variant_materials", default = False)
|
||||
if machine_has_variant_materials or machine_has_variant_materials == "True":
|
||||
materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId())
|
||||
else:
|
||||
materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id)
|
||||
|
@ -202,7 +205,7 @@ class ExtruderManager(QObject):
|
|||
if preferred_material_id:
|
||||
search_criteria = { "type": "material", "id": preferred_material_id}
|
||||
if machine_definition.getMetaDataEntry("has_machine_materials"):
|
||||
search_criteria["definition"] = machine_definition.id
|
||||
search_criteria["definition"] = machine_definition_id
|
||||
|
||||
if machine_definition.getMetaDataEntry("has_variants") and variant:
|
||||
search_criteria["variant"] = variant.id
|
||||
|
@ -256,14 +259,24 @@ class ExtruderManager(QObject):
|
|||
container_registry.addContainer(user_profile)
|
||||
container_stack.addContainer(user_profile)
|
||||
|
||||
# Make sure the next stack is a stack that contains only the machine definition
|
||||
if not container_stack.getNextStack():
|
||||
shallow_stack = UM.Settings.ContainerStack(machine_id + "_shallow")
|
||||
shallow_stack.addContainer(machine_definition)
|
||||
container_stack.setNextStack(shallow_stack)
|
||||
# regardless of what the next stack is, we have to set it again, because of signal routing.
|
||||
container_stack.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
|
||||
|
||||
container_registry.addContainer(container_stack)
|
||||
|
||||
def getAllExtruderValues(self, setting_key):
|
||||
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
if not multi_extrusion:
|
||||
return [global_container_stack.getProperty(setting_key, "value")]
|
||||
|
||||
result = []
|
||||
for index in self.extruderIds:
|
||||
extruder_stack_id = self.extruderIds[str(index)]
|
||||
stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack_id)[0]
|
||||
result.append(stack.getProperty(setting_key, "value"))
|
||||
return result
|
||||
|
||||
## Removes the container stack and user profile for the extruders for a specific machine.
|
||||
#
|
||||
# \param machine_id The machine to remove the extruders for.
|
||||
|
@ -284,22 +297,32 @@ class ExtruderManager(QObject):
|
|||
for name in self._extruder_trains[machine_id]:
|
||||
yield self._extruder_trains[machine_id][name]
|
||||
|
||||
## Returns a generator that will iterate over the global stack and per-extruder stacks.
|
||||
## Returns a list containing the global stack and active extruder stacks.
|
||||
#
|
||||
# The first generated element is the global container stack. After that any extruder stacks are generated.
|
||||
# The first element is the global container stack, followed by any extruder stacks.
|
||||
# \return \type{List[ContainerStack]}
|
||||
def getActiveGlobalAndExtruderStacks(self):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return
|
||||
return None
|
||||
|
||||
yield global_stack
|
||||
result = [global_stack]
|
||||
result.extend(self.getActiveExtruderStacks())
|
||||
return result
|
||||
|
||||
global_id = global_stack.getId()
|
||||
for name in self._extruder_trains[global_id]:
|
||||
yield self._extruder_trains[global_id][name]
|
||||
## Returns the list of active extruder stacks.
|
||||
#
|
||||
# \return \type{List[ContainerStack]} a list of
|
||||
def getActiveExtruderStacks(self):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
return list(self._extruder_trains[global_stack.getId()].values()) if global_stack else []
|
||||
|
||||
def __globalContainerStackChanged(self):
|
||||
self._addCurrentMachineExtruders()
|
||||
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id:
|
||||
self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
|
||||
self.globalContainerStackDefinitionChanged.emit()
|
||||
self.activeExtruderChanged.emit()
|
||||
|
||||
## Adds the extruders of the currently active machine.
|
||||
|
@ -324,7 +347,7 @@ class ExtruderManager(QObject):
|
|||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||
value = extruder.getRawProperty(key, "value")
|
||||
|
||||
if not value:
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if isinstance(value, UM.Settings.SettingFunction):
|
||||
|
@ -344,7 +367,7 @@ class ExtruderManager(QObject):
|
|||
# \param key The key of the setting to retieve values for.
|
||||
#
|
||||
# \return String representing the extruder values
|
||||
@pyqtSlot(str, result="QList<int>")
|
||||
@pyqtSlot(str, result="QVariant")
|
||||
def getInstanceExtruderValues(self, key):
|
||||
return ExtruderManager.getExtruderValues(key)
|
||||
|
||||
|
@ -369,3 +392,29 @@ class ExtruderManager(QObject):
|
|||
value = UM.Application.getInstance().getGlobalContainerStack().getProperty(key, "value")
|
||||
|
||||
return value
|
||||
|
||||
## Get the resolve value or value for a given key
|
||||
#
|
||||
# This is the effective value for a given key, it is used for values in the global stack.
|
||||
# This is exposed to SettingFunction to use in value functions.
|
||||
# \param key The key of the setting to get the value of.
|
||||
#
|
||||
# \return The effective value
|
||||
@staticmethod
|
||||
def getResolveOrValue(key):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
resolved_value = global_stack.getProperty(key, "resolve")
|
||||
if resolved_value is not None:
|
||||
user_container = global_stack.findContainer({"type": "user"})
|
||||
quality_changes_container = global_stack.findContainer({"type": "quality_changes"})
|
||||
if user_container.hasProperty(key, "value") or quality_changes_container.hasProperty(key, "value"):
|
||||
# Normal case
|
||||
value = global_stack.getProperty(key, "value")
|
||||
else:
|
||||
# We have a resolved value and we're using it because of no user and quality_changes value
|
||||
value = resolved_value
|
||||
else:
|
||||
value = global_stack.getRawProperty(key, "value")
|
||||
|
||||
return value
|
||||
|
|
|
@ -29,6 +29,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
# containers.
|
||||
IndexRole = Qt.UserRole + 4
|
||||
|
||||
# The ID of the definition of the extruder.
|
||||
DefinitionRole = Qt.UserRole + 5
|
||||
|
||||
## List of colours to display if there is no material or the material has no known
|
||||
# colour.
|
||||
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
||||
|
@ -44,6 +47,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.ColorRole, "color")
|
||||
self.addRoleName(self.IndexRole, "index")
|
||||
self.addRoleName(self.DefinitionRole, "definition")
|
||||
|
||||
self._add_global = False
|
||||
self._simple_names = False
|
||||
|
@ -51,8 +55,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
self._active_extruder_stack = None
|
||||
|
||||
#Listen to changes.
|
||||
UM.Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders)
|
||||
manager = ExtruderManager.getInstance()
|
||||
manager.extrudersChanged.connect(self._updateExtruders) #When the list of extruders changes in general.
|
||||
|
||||
self._updateExtruders()
|
||||
|
||||
|
@ -126,7 +130,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
"id": global_container_stack.getId(),
|
||||
"name": "Global",
|
||||
"color": color,
|
||||
"index": -1
|
||||
"index": -1,
|
||||
"definition": ""
|
||||
}
|
||||
items.append(item)
|
||||
changed = True
|
||||
|
@ -148,7 +153,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
"id": extruder.getId(),
|
||||
"name": extruder_name,
|
||||
"color": color,
|
||||
"index": position
|
||||
"index": position,
|
||||
"definition": extruder.getBottom().getId()
|
||||
}
|
||||
items.append(item)
|
||||
changed = True
|
||||
|
|
|
@ -3,22 +3,22 @@
|
|||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from UM import Util
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Settings.SettingRelation import RelationType
|
||||
|
||||
import UM.Settings
|
||||
|
||||
from cura.QualityManager import QualityManager
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
from . import ExtruderManager
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
import time
|
||||
import os
|
||||
|
||||
class MachineManager(QObject):
|
||||
|
@ -45,8 +45,9 @@ class MachineManager(QObject):
|
|||
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeQualityChanged)
|
||||
|
||||
self.globalContainerChanged.connect(self.activeStackChanged)
|
||||
self.globalValueChanged.connect(self.activeStackChanged)
|
||||
self.globalValueChanged.connect(self.activeStackValueChanged)
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
|
||||
self.activeStackChanged.connect(self.activeStackValueChanged)
|
||||
|
||||
self._empty_variant_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_variant")[0]
|
||||
self._empty_material_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
|
||||
|
@ -72,14 +73,18 @@ class MachineManager(QObject):
|
|||
self._auto_materials_changed = {}
|
||||
self._auto_hotends_changed = {}
|
||||
|
||||
globalContainerChanged = pyqtSignal()
|
||||
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
|
||||
"The selected material is incompatible with the selected machine or configuration."))
|
||||
|
||||
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
|
||||
activeMaterialChanged = pyqtSignal()
|
||||
activeVariantChanged = pyqtSignal()
|
||||
activeQualityChanged = pyqtSignal()
|
||||
activeStackChanged = pyqtSignal()
|
||||
activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value)
|
||||
|
||||
globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed.
|
||||
activeValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
|
||||
activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed.
|
||||
activeStackValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
|
||||
|
||||
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
|
||||
|
||||
|
@ -130,7 +135,7 @@ class MachineManager(QObject):
|
|||
|
||||
definition_id = "fdmprinter"
|
||||
if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
|
||||
definition_id = self._global_container_stack.getBottom().getId()
|
||||
definition_id = self.activeQualityDefinitionId
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id)
|
||||
if containers: # New material ID is known
|
||||
|
@ -141,9 +146,18 @@ class MachineManager(QObject):
|
|||
matching_extruder = extruder
|
||||
break
|
||||
|
||||
if matching_extruder and matching_extruder.findContainer({"type":"material"}).getMetaDataEntry("GUID") != material_id:
|
||||
if matching_extruder and matching_extruder.findContainer({"type": "material"}).getMetaDataEntry("GUID") != material_id:
|
||||
# Save the material that needs to be changed. Multiple changes will be handled by the callback.
|
||||
self._auto_materials_changed[str(index)] = containers[0].getId()
|
||||
variant_container = matching_extruder.findContainer({"type": "variant"})
|
||||
if self._global_container_stack.getBottom().getMetaDataEntry("has_variants") and variant_container:
|
||||
variant_id = self.getQualityVariantId(self._global_container_stack.getBottom(), variant_container)
|
||||
for container in containers:
|
||||
if container.getMetaDataEntry("variant") == variant_id:
|
||||
self._auto_materials_changed[str(index)] = container.getId()
|
||||
break
|
||||
else:
|
||||
# Just use the first result we found.
|
||||
self._auto_materials_changed[str(index)] = containers[0].getId()
|
||||
self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback)
|
||||
else:
|
||||
Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id))
|
||||
|
@ -219,6 +233,9 @@ class MachineManager(QObject):
|
|||
|
||||
def _onActiveExtruderStackChanged(self):
|
||||
self.blurSettings.emit() # Ensure no-one has focus.
|
||||
|
||||
old_active_container_stack = self._active_container_stack
|
||||
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
self._active_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
|
||||
self._active_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
|
@ -228,8 +245,16 @@ class MachineManager(QObject):
|
|||
self._active_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
else:
|
||||
self._active_container_stack = self._global_container_stack
|
||||
|
||||
old_active_stack_valid = self._active_stack_valid
|
||||
self._active_stack_valid = not self._checkStackForErrors(self._active_container_stack)
|
||||
self.activeValidationChanged.emit()
|
||||
if old_active_stack_valid != self._active_stack_valid:
|
||||
self.activeStackValidationChanged.emit()
|
||||
|
||||
if old_active_container_stack != self._active_container_stack:
|
||||
# Many methods and properties related to the active quality actually depend
|
||||
# on _active_container_stack. If it changes, then the properties change.
|
||||
self.activeQualityChanged.emit()
|
||||
|
||||
def _onInstanceContainersChanged(self, container):
|
||||
container_type = container.getMetaDataEntry("type")
|
||||
|
@ -243,19 +268,8 @@ class MachineManager(QObject):
|
|||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
if property_name == "value":
|
||||
# If a setting is not settable per extruder, but "has enabled relations" that are settable per extruder
|
||||
# we need to copy the value to global, so that the front-end displays the right settings.
|
||||
if not self._active_container_stack.getProperty(key, "settable_per_extruder"):
|
||||
relations = self._global_container_stack.getBottom()._getDefinition(key).relations
|
||||
for relation in filter(lambda r: r.role == "enabled" and r.type == RelationType.RequiredByTarget, relations):
|
||||
# Target setting is settable per extruder
|
||||
if self._active_container_stack.getProperty(relation.target.key, "settable_per_extruder"):
|
||||
new_value = self._global_container_stack.getProperty(key, "value")
|
||||
stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
||||
for extruder_stack in stacks:
|
||||
if extruder_stack.getProperty(key, "value") != new_value:
|
||||
extruder_stack.getTop().setProperty(key, "value", new_value)
|
||||
break
|
||||
# Notify UI items, such as the "changed" star in profile pull down menu.
|
||||
self.activeStackValueChanged.emit()
|
||||
|
||||
if property_name == "validationState":
|
||||
if self._active_stack_valid:
|
||||
|
@ -265,13 +279,11 @@ class MachineManager(QObject):
|
|||
changed_validation_state = self._global_container_stack.getProperty(key, property_name)
|
||||
if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
|
||||
self._active_stack_valid = False
|
||||
self.activeValidationChanged.emit()
|
||||
self.activeStackValidationChanged.emit()
|
||||
else:
|
||||
if not self._checkStackForErrors(self._active_container_stack) and not self._checkStackForErrors(self._global_container_stack):
|
||||
self._active_stack_valid = True
|
||||
self.activeValidationChanged.emit()
|
||||
|
||||
self.activeStackChanged.emit()
|
||||
self.activeStackValidationChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveMachine(self, stack_id):
|
||||
|
@ -347,7 +359,7 @@ class MachineManager(QObject):
|
|||
user_settings.clear()
|
||||
|
||||
## Check if the global_container has instances in the user container
|
||||
@pyqtProperty(bool, notify = activeStackChanged)
|
||||
@pyqtProperty(bool, notify = activeStackValueChanged)
|
||||
def hasUserSettings(self):
|
||||
if not self._global_container_stack:
|
||||
return False
|
||||
|
@ -355,7 +367,8 @@ class MachineManager(QObject):
|
|||
if self._global_container_stack.getTop().findInstances():
|
||||
return True
|
||||
|
||||
for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()):
|
||||
stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
for stack in stacks:
|
||||
if stack.getTop().findInstances():
|
||||
return True
|
||||
|
||||
|
@ -368,15 +381,33 @@ class MachineManager(QObject):
|
|||
if not self._global_container_stack:
|
||||
return
|
||||
|
||||
self._global_container_stack.getTop().removeInstance(key)
|
||||
send_emits_containers = []
|
||||
|
||||
for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()):
|
||||
stack.getTop().removeInstance(key)
|
||||
top_container = self._global_container_stack.getTop()
|
||||
top_container.removeInstance(key, postpone_emit=True)
|
||||
send_emits_containers.append(top_container)
|
||||
|
||||
linked = not self._global_container_stack.getProperty(key, "settable_per_extruder") or \
|
||||
self._global_container_stack.getProperty(key, "limit_to_extruder") != "-1"
|
||||
|
||||
if not linked:
|
||||
stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
stacks = [stack]
|
||||
else:
|
||||
stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
||||
|
||||
for stack in stacks:
|
||||
container = stack.getTop()
|
||||
container.removeInstance(key, postpone_emit=True)
|
||||
send_emits_containers.append(container)
|
||||
|
||||
for container in send_emits_containers:
|
||||
container.sendPostponedEmits()
|
||||
|
||||
## Check if the global profile does not contain error states
|
||||
# Note that the _active_stack_valid is cached due to performance issues
|
||||
# Calling _checkStackForErrors on every change is simply too expensive
|
||||
@pyqtProperty(bool, notify = activeValidationChanged)
|
||||
@pyqtProperty(bool, notify = activeStackValidationChanged)
|
||||
def isActiveStackValid(self):
|
||||
return bool(self._active_stack_valid)
|
||||
|
||||
|
@ -463,8 +494,8 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtProperty(str, notify=activeQualityChanged)
|
||||
def activeQualityName(self):
|
||||
if self._active_container_stack:
|
||||
quality = self._active_container_stack.findContainer({"type": "quality_changes"})
|
||||
if self._active_container_stack and self._global_container_stack:
|
||||
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
|
||||
if quality and quality != self._empty_quality_changes_container:
|
||||
return quality.getName()
|
||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
|
@ -474,27 +505,50 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtProperty(str, notify=activeQualityChanged)
|
||||
def activeQualityId(self):
|
||||
if self._global_container_stack:
|
||||
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
|
||||
if self._active_container_stack:
|
||||
quality = self._active_container_stack.findContainer({"type": "quality_changes"})
|
||||
if quality and quality != self._empty_quality_changes_container:
|
||||
return quality.getId()
|
||||
quality = self._global_container_stack.findContainer({"type": "quality"})
|
||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
if quality:
|
||||
return quality.getId()
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = activeQualityChanged)
|
||||
def activeQualityType(self):
|
||||
if self._global_container_stack:
|
||||
quality = self._global_container_stack.findContainer(type = "quality")
|
||||
if self._active_container_stack:
|
||||
quality = self._active_container_stack.findContainer(type = "quality")
|
||||
if quality:
|
||||
return quality.getMetaDataEntry("quality_type")
|
||||
return ""
|
||||
|
||||
@pyqtProperty(bool, notify = activeQualityChanged)
|
||||
def isActiveQualitySupported(self):
|
||||
if self._active_container_stack:
|
||||
quality = self._active_container_stack.findContainer(type = "quality")
|
||||
if quality:
|
||||
return Util.parseBool(quality.getMetaDataEntry("supported", True))
|
||||
return ""
|
||||
|
||||
## Get the Quality ID associated with the currently active extruder
|
||||
# Note that this only returns the "quality", not the "quality_changes"
|
||||
# \returns QualityID (string) if found, empty string otherwise
|
||||
# \sa activeQualityId()
|
||||
# \todo Ideally, this method would be named activeQualityId(), and the other one
|
||||
# would be named something like activeQualityOrQualityChanges() for consistency
|
||||
@pyqtProperty(str, notify = activeQualityChanged)
|
||||
def activeQualityContainerId(self):
|
||||
# We're using the active stack instead of the global stack in case the list of qualities differs per extruder
|
||||
if self._global_container_stack:
|
||||
quality = self._active_container_stack.findContainer(type = "quality")
|
||||
if quality:
|
||||
return quality.getId()
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = activeQualityChanged)
|
||||
def activeQualityChangesId(self):
|
||||
if self._global_container_stack:
|
||||
changes = self._global_container_stack.findContainer(type = "quality_changes")
|
||||
if self._active_container_stack:
|
||||
changes = self._active_container_stack.findContainer(type = "quality_changes")
|
||||
if changes:
|
||||
return changes.getId()
|
||||
return ""
|
||||
|
@ -527,43 +581,58 @@ class MachineManager(QObject):
|
|||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
|
||||
if not containers or not self._active_container_stack:
|
||||
return
|
||||
material_container = containers[0]
|
||||
|
||||
Logger.log("d", "Attempting to change the active material to %s", material_id)
|
||||
old_variant = self._active_container_stack.findContainer({"type": "variant"})
|
||||
|
||||
old_material = self._active_container_stack.findContainer({"type": "material"})
|
||||
old_quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
old_quality_changes = self._active_container_stack.findContainer({"type": "quality_changes"})
|
||||
if not old_material:
|
||||
Logger.log("w", "While trying to set the active material, no material was found to replace it.")
|
||||
return
|
||||
if (old_quality_changes.getId() == "empty_quality_changes" or #Don't want the empty one.
|
||||
old_quality_changes.getMetaDataEntry("material") != material_id): # The quality change is based off a different material; the quality change is probably a custom quality.
|
||||
|
||||
if old_quality_changes.getId() == "empty_quality_changes":
|
||||
old_quality_changes = None
|
||||
|
||||
self.blurSettings.emit()
|
||||
old_material.nameChanged.disconnect(self._onMaterialNameChanged)
|
||||
|
||||
material_index = self._active_container_stack.getContainerIndex(old_material)
|
||||
self._active_container_stack.replaceContainer(material_index, containers[0])
|
||||
self._active_container_stack.replaceContainer(material_index, material_container)
|
||||
Logger.log("d", "Active material changed")
|
||||
|
||||
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
||||
material_container.nameChanged.connect(self._onMaterialNameChanged)
|
||||
|
||||
if containers[0].getMetaDataEntry("compatible") == False:
|
||||
message = Message(catalog.i18nc("@info:status",
|
||||
"The selected material is imcompatible with the selected machine or configuration."))
|
||||
message.show()
|
||||
|
||||
|
||||
if old_quality:
|
||||
if old_quality_changes:
|
||||
new_quality = self._updateQualityChangesContainer(
|
||||
old_quality.getMetaDataEntry("quality_type"),
|
||||
preferred_quality_changes_name = old_quality_changes.getMetaDataEntry("name"))
|
||||
else:
|
||||
new_quality = self._updateQualityContainer(self._global_container_stack.getBottom(), old_variant, containers[0], old_quality.getName())
|
||||
if material_container.getMetaDataEntry("compatible") == False:
|
||||
self._material_incompatible_message.show()
|
||||
else:
|
||||
new_quality = self._updateQualityContainer(self._global_container_stack.getBottom(), old_variant, containers[0])
|
||||
self._material_incompatible_message.hide()
|
||||
|
||||
self.setActiveQuality(new_quality.getId())
|
||||
new_quality_id = old_quality.getId()
|
||||
quality_type = old_quality.getMetaDataEntry("quality_type")
|
||||
if old_quality_changes:
|
||||
quality_type = old_quality_changes.getMetaDataEntry("quality_type")
|
||||
new_quality_id = old_quality_changes.getId()
|
||||
|
||||
# See if the requested quality type is available in the new situation.
|
||||
machine_definition = self._active_container_stack.getBottom()
|
||||
quality_manager = QualityManager.getInstance()
|
||||
candidate_quality = quality_manager.findQualityByQualityType(quality_type,
|
||||
quality_manager.getWholeMachineDefinition(machine_definition),
|
||||
[material_container])
|
||||
if not candidate_quality or candidate_quality.getId() == "empty_quality":
|
||||
# Fall back to a quality
|
||||
new_quality = quality_manager.findQualityByQualityType(None,
|
||||
quality_manager.getWholeMachineDefinition(machine_definition),
|
||||
[material_container])
|
||||
if new_quality:
|
||||
new_quality_id = new_quality.getId()
|
||||
else:
|
||||
if not old_quality_changes:
|
||||
new_quality_id = candidate_quality.getId()
|
||||
|
||||
self.setActiveQuality(new_quality_id)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveVariant(self, variant_id):
|
||||
|
@ -577,7 +646,7 @@ class MachineManager(QObject):
|
|||
self.blurSettings.emit()
|
||||
variant_index = self._active_container_stack.getContainerIndex(old_variant)
|
||||
self._active_container_stack.replaceContainer(variant_index, containers[0])
|
||||
|
||||
Logger.log("d", "Active variant changed")
|
||||
preferred_material = None
|
||||
if old_material:
|
||||
preferred_material_name = old_material.getName()
|
||||
|
@ -586,100 +655,176 @@ class MachineManager(QObject):
|
|||
else:
|
||||
Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
|
||||
|
||||
## set the active quality
|
||||
# \param quality_id The quality_id of either a quality or a quality_changes
|
||||
@pyqtSlot(str)
|
||||
def setActiveQuality(self, quality_id):
|
||||
self.blurSettings.emit()
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id)
|
||||
if not containers or not self._global_container_stack:
|
||||
return
|
||||
|
||||
Logger.log("d", "Attempting to change the active quality to %s", quality_id)
|
||||
|
||||
self.blurSettings.emit()
|
||||
quality_container = None
|
||||
quality_changes_container = self._empty_quality_changes_container
|
||||
|
||||
# Quality profile come in two flavours: type=quality and type=quality_changes
|
||||
# If we found a quality_changes profile then look up its parent quality profile.
|
||||
container_type = containers[0].getMetaDataEntry("type")
|
||||
quality_name = containers[0].getName()
|
||||
quality_type = containers[0].getMetaDataEntry("quality_type")
|
||||
|
||||
# Get quality container and optionally the quality_changes container.
|
||||
if container_type == "quality":
|
||||
quality_container = containers[0]
|
||||
new_quality_settings_list = self._determineQualityAndQualityChangesForQualityType(quality_type)
|
||||
elif container_type == "quality_changes":
|
||||
quality_changes_container = containers[0]
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(
|
||||
quality_type = quality_changes_container.getMetaDataEntry("quality"))
|
||||
if not containers:
|
||||
Logger.log("e", "Could not find quality %s for changes %s, not changing quality", quality_changes_container.getMetaDataEntry("quality"), quality_changes_container.getId())
|
||||
return
|
||||
quality_container = containers[0]
|
||||
new_quality_settings_list = self._determineQualityAndQualityChangesForQualityChanges(quality_name)
|
||||
else:
|
||||
Logger.log("e", "Tried to set quality to a container that is not of the right type")
|
||||
return
|
||||
|
||||
quality_type = quality_container.getMetaDataEntry("quality_type")
|
||||
if not quality_type:
|
||||
quality_type = quality_changes_container.getName()
|
||||
name_changed_connect_stacks = [] # Connect these stacks to the name changed callback
|
||||
for setting_info in new_quality_settings_list:
|
||||
stack = setting_info["stack"]
|
||||
stack_quality = setting_info["quality"]
|
||||
stack_quality_changes = setting_info["quality_changes"]
|
||||
|
||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
extruder_id = stack.getId() if stack != self._global_container_stack else None
|
||||
name_changed_connect_stacks.append(stack_quality)
|
||||
name_changed_connect_stacks.append(stack_quality_changes)
|
||||
self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit = True)
|
||||
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit = True)
|
||||
|
||||
criteria = { "quality_type": quality_type, "extruder": extruder_id }
|
||||
# Send emits that are postponed in replaceContainer.
|
||||
# Here the stacks are finished replacing and every value can be resolved based on the current state.
|
||||
for setting_info in new_quality_settings_list:
|
||||
setting_info["stack"].sendPostponedEmits()
|
||||
|
||||
material = stack.findContainer(type = "material")
|
||||
if material and material is not self._empty_material_container:
|
||||
criteria["material"] = material.getId()
|
||||
|
||||
if self._global_container_stack.getMetaDataEntry("has_machine_quality"):
|
||||
criteria["definition"] = self.activeQualityDefinitionId
|
||||
else:
|
||||
criteria["definition"] = "fdmprinter"
|
||||
|
||||
stack_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
||||
if not stack_quality:
|
||||
criteria.pop("extruder")
|
||||
stack_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
||||
if not stack_quality:
|
||||
stack_quality = quality_container
|
||||
else:
|
||||
stack_quality = stack_quality[0]
|
||||
else:
|
||||
stack_quality = stack_quality[0]
|
||||
|
||||
if quality_changes_container != self._empty_quality_changes_container:
|
||||
stack_quality_changes = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(name = quality_changes_container.getName(), extruder = extruder_id)[0]
|
||||
else:
|
||||
stack_quality_changes = self._empty_quality_changes_container
|
||||
|
||||
old_quality = stack.findContainer(type = "quality")
|
||||
if old_quality:
|
||||
old_quality.nameChanged.disconnect(self._onQualityNameChanged)
|
||||
else:
|
||||
Logger.log("w", "Could not find old quality while changing active quality.")
|
||||
|
||||
old_changes = stack.findContainer(type = "quality_changes")
|
||||
if old_changes:
|
||||
old_changes.nameChanged.disconnect(self._onQualityNameChanged)
|
||||
else:
|
||||
Logger.log("w", "Could not find old quality_changes while changing active quality.")
|
||||
|
||||
stack.replaceContainer(stack.getContainerIndex(old_quality), stack_quality)
|
||||
stack.replaceContainer(stack.getContainerIndex(old_changes), stack_quality_changes)
|
||||
|
||||
stack_quality.nameChanged.connect(self._onQualityNameChanged)
|
||||
stack_quality_changes.nameChanged.connect(self._onQualityNameChanged)
|
||||
# Connect to onQualityNameChanged
|
||||
for stack in name_changed_connect_stacks:
|
||||
stack.nameChanged.connect(self._onQualityNameChanged)
|
||||
|
||||
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
|
||||
# Ask the user if the user profile should be cleared or not (discarding the current settings)
|
||||
# In Simple Mode we assume the user always wants to keep the (limited) current settings
|
||||
details = catalog.i18nc("@label", "You made changes to the following setting(s):")
|
||||
user_settings = self._active_container_stack.getTop().findInstances(**{})
|
||||
for setting in user_settings:
|
||||
details = details + "\n " + setting.definition.label
|
||||
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"), catalog.i18nc("@label", "Do you want to transfer your changed settings to this profile?"),
|
||||
catalog.i18nc("@label", "If you transfer your settings they will override settings in the profile."), details,
|
||||
buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._keepUserSettingsDialogCallback)
|
||||
self._askUserToKeepOrClearCurrentSettings()
|
||||
|
||||
self.activeQualityChanged.emit()
|
||||
|
||||
## Determine the quality and quality changes settings for the current machine for a quality name.
|
||||
#
|
||||
# \param quality_name \type{str} the name of the quality.
|
||||
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
|
||||
def _determineQualityAndQualityChangesForQualityType(self, quality_type):
|
||||
quality_manager = QualityManager.getInstance()
|
||||
result = []
|
||||
empty_quality_changes = self._empty_quality_changes_container
|
||||
global_container_stack = self._global_container_stack
|
||||
global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom())
|
||||
|
||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
if extruder_stacks:
|
||||
stacks = extruder_stacks
|
||||
else:
|
||||
stacks = [global_container_stack]
|
||||
|
||||
for stack in stacks:
|
||||
material = stack.findContainer(type="material")
|
||||
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
|
||||
result.append({"stack": stack, "quality": quality, "quality_changes": empty_quality_changes})
|
||||
|
||||
if extruder_stacks:
|
||||
# Add an extra entry for the global stack.
|
||||
result.append({"stack": global_container_stack, "quality": result[0]["quality"],
|
||||
"quality_changes": empty_quality_changes})
|
||||
return result
|
||||
|
||||
## Determine the quality and quality changes settings for the current machine for a quality changes name.
|
||||
#
|
||||
# \param quality_changes_name \type{str} the name of the quality changes.
|
||||
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
|
||||
def _determineQualityAndQualityChangesForQualityChanges(self, quality_changes_name):
|
||||
result = []
|
||||
quality_manager = QualityManager.getInstance()
|
||||
|
||||
global_container_stack = self._global_container_stack
|
||||
global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom())
|
||||
|
||||
quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name,
|
||||
global_machine_definition)
|
||||
|
||||
global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None][0]
|
||||
material = global_container_stack.findContainer(type="material")
|
||||
|
||||
# For the global stack, find a quality which matches the quality_type in
|
||||
# the quality changes profile and also satisfies any material constraints.
|
||||
quality_type = global_quality_changes.getMetaDataEntry("quality_type")
|
||||
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
|
||||
|
||||
# Find the values for each extruder.
|
||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
|
||||
for stack in extruder_stacks:
|
||||
extruder_definition = quality_manager.getParentMachineDefinition(stack.getBottom())
|
||||
|
||||
quality_changes_list = [qcp for qcp in quality_changes_profiles
|
||||
if qcp.getMetaDataEntry("extruder") == extruder_definition.getId()]
|
||||
if quality_changes_list:
|
||||
quality_changes = quality_changes_list[0]
|
||||
else:
|
||||
quality_changes = global_quality_changes
|
||||
|
||||
material = stack.findContainer(type="material")
|
||||
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
|
||||
|
||||
result.append({"stack": stack, "quality": quality, "quality_changes": quality_changes})
|
||||
|
||||
if extruder_stacks:
|
||||
# Duplicate the quality from the 1st extruder into the global stack. If anyone
|
||||
# then looks in the global stack, they should get a reasonable view.
|
||||
result.append({"stack": global_container_stack, "quality": result[0]["quality"], "quality_changes": global_quality_changes})
|
||||
else:
|
||||
result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes})
|
||||
|
||||
return result
|
||||
|
||||
def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False):
|
||||
# Disconnect the signal handling from the old container.
|
||||
old_container = stack.findContainer(type=container.getMetaDataEntry("type"))
|
||||
if old_container:
|
||||
old_container.nameChanged.disconnect(self._onQualityNameChanged)
|
||||
else:
|
||||
Logger.log("w", "Could not find old "+ container.getMetaDataEntry("type") + " while changing active " + container.getMetaDataEntry("type") + ".")
|
||||
|
||||
# Swap in the new container into the stack.
|
||||
stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit)
|
||||
|
||||
# Attach the needed signal handling.
|
||||
container.nameChanged.connect(self._onQualityNameChanged)
|
||||
|
||||
def _askUserToKeepOrClearCurrentSettings(self):
|
||||
# Ask the user if the user profile should be cleared or not (discarding the current settings)
|
||||
# In Simple Mode we assume the user always wants to keep the (limited) current settings
|
||||
details_text = catalog.i18nc("@label", "You made changes to the following setting(s):")
|
||||
|
||||
# user changes in global stack
|
||||
details_list = [setting.definition.label for setting in self._global_container_stack.getTop().findInstances(**{})]
|
||||
|
||||
# user changes in extruder stacks
|
||||
stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
for stack in stacks:
|
||||
details_list.extend([
|
||||
"%s (%s)" % (setting.definition.label, stack.getName())
|
||||
for setting in stack.getTop().findInstances(**{})])
|
||||
|
||||
# Format to output string
|
||||
details = "\n ".join([details_text, ] + details_list)
|
||||
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"),
|
||||
catalog.i18nc("@label",
|
||||
"Do you want to transfer your changed settings to this profile?"),
|
||||
catalog.i18nc("@label",
|
||||
"If you transfer your settings they will override settings in the profile."),
|
||||
details,
|
||||
buttons=QMessageBox.Yes + QMessageBox.No, icon=QMessageBox.Question,
|
||||
callback=self._keepUserSettingsDialogCallback)
|
||||
|
||||
def _keepUserSettingsDialogCallback(self, button):
|
||||
if button == QMessageBox.Yes:
|
||||
# Yes, keep the settings in the user profile with this profile
|
||||
|
@ -733,18 +878,15 @@ class MachineManager(QObject):
|
|||
# \param definition (DefinitionContainer) machine definition
|
||||
# \returns DefinitionID (string) if found, empty string otherwise
|
||||
def getQualityDefinitionId(self, definition):
|
||||
definition_id = definition.getMetaDataEntry("quality_definition")
|
||||
if not definition_id:
|
||||
definition_id = definition.getId()
|
||||
return definition_id
|
||||
return QualityManager.getInstance().getParentMachineDefinition(definition).getId()
|
||||
|
||||
## Get the Variant ID to use to select quality profiles for the currently active variant
|
||||
# \returns VariantID (string) if found, empty string otherwise
|
||||
# \sa getQualityVariantId
|
||||
@pyqtProperty(str, notify = activeVariantChanged)
|
||||
def activeQualityVariantId(self):
|
||||
if self._global_container_stack:
|
||||
variant = self._global_container_stack.findContainer({"type": "variant"})
|
||||
if self._active_container_stack:
|
||||
variant = self._active_container_stack.findContainer({"type": "variant"})
|
||||
if variant:
|
||||
return self.getQualityVariantId(self._global_container_stack.getBottom(), variant)
|
||||
return ""
|
||||
|
@ -847,14 +989,13 @@ class MachineManager(QObject):
|
|||
def _updateVariantContainer(self, definition):
|
||||
if not definition.getMetaDataEntry("has_variants"):
|
||||
return self._empty_variant_container
|
||||
|
||||
machine_definition_id = UM.Application.getInstance().getMachineManager().getQualityDefinitionId(definition)
|
||||
containers = []
|
||||
preferred_variant = definition.getMetaDataEntry("preferred_variant")
|
||||
if preferred_variant:
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = definition.id, id = preferred_variant)
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id, id = preferred_variant)
|
||||
if not containers:
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = definition.id)
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id)
|
||||
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
|
42
cura/Settings/ProfilesModel.py
Normal file
42
cura/Settings/ProfilesModel.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Uranium is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel
|
||||
|
||||
from cura.QualityManager import QualityManager
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.MachineManager import MachineManager
|
||||
|
||||
## QML Model for listing the current list of valid quality profiles.
|
||||
#
|
||||
class ProfilesModel(InstanceContainersModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._update)
|
||||
|
||||
Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
|
||||
Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
|
||||
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
|
||||
|
||||
## Fetch the list of containers to display.
|
||||
#
|
||||
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
|
||||
def _fetchInstanceContainers(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack is None:
|
||||
return []
|
||||
|
||||
# Get the list of extruders and place the selected extruder at the front of the list.
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
active_extruder = extruder_manager.getActiveExtruderStack()
|
||||
extruder_stacks = extruder_manager.getActiveExtruderStacks()
|
||||
if active_extruder in extruder_stacks:
|
||||
extruder_stacks.remove(active_extruder)
|
||||
extruder_stacks = [active_extruder] + extruder_stacks
|
||||
|
||||
# Fetch the list of useable qualities across all extruders.
|
||||
# The actual list of quality profiles come from the first extruder in the extruder list.
|
||||
return QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack,
|
||||
extruder_stacks)
|
45
cura/Settings/QualityAndUserProfilesModel.py
Normal file
45
cura/Settings/QualityAndUserProfilesModel.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
from UM.Application import Application
|
||||
|
||||
from cura.QualityManager import QualityManager
|
||||
from cura.Settings.ProfilesModel import ProfilesModel
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
## QML Model for listing the current list of valid quality and quality changes profiles.
|
||||
#
|
||||
class QualityAndUserProfilesModel(ProfilesModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
## Fetch the list of containers to display.
|
||||
#
|
||||
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
|
||||
def _fetchInstanceContainers(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return []
|
||||
|
||||
# Fetch the list of quality changes.
|
||||
quality_manager = QualityManager.getInstance()
|
||||
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom())
|
||||
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
|
||||
|
||||
# Get the list of extruders and place the selected extruder at the front of the list.
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
active_extruder = extruder_manager.getActiveExtruderStack()
|
||||
extruder_stacks = extruder_manager.getActiveExtruderStacks()
|
||||
if active_extruder in extruder_stacks:
|
||||
extruder_stacks.remove(active_extruder)
|
||||
extruder_stacks = [active_extruder] + extruder_stacks
|
||||
|
||||
# Fetch the list of useable qualities across all extruders.
|
||||
# The actual list of quality profiles come from the first extruder in the extruder list.
|
||||
quality_list = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack,
|
||||
extruder_stacks)
|
||||
|
||||
# Filter the quality_change by the list of available quality_types
|
||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
||||
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set]
|
||||
|
||||
return quality_list + filtered_quality_changes
|
|
@ -16,8 +16,9 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
LabelRole = Qt.UserRole + 2
|
||||
UnitRole = Qt.UserRole + 3
|
||||
ProfileValueRole = Qt.UserRole + 4
|
||||
UserValueRole = Qt.UserRole + 5
|
||||
CategoryRole = Qt.UserRole + 6
|
||||
ProfileValueSourceRole = Qt.UserRole + 5
|
||||
UserValueRole = Qt.UserRole + 6
|
||||
CategoryRole = Qt.UserRole + 7
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent = parent)
|
||||
|
@ -25,13 +26,15 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
self._container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
|
||||
self._extruder_id = None
|
||||
self._quality = None
|
||||
self._material = None
|
||||
self._extruder_definition_id = None
|
||||
self._quality_id = None
|
||||
self._material_id = None
|
||||
|
||||
self.addRoleName(self.KeyRole, "key")
|
||||
self.addRoleName(self.LabelRole, "label")
|
||||
self.addRoleName(self.UnitRole, "unit")
|
||||
self.addRoleName(self.ProfileValueRole, "profile_value")
|
||||
self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
|
||||
self.addRoleName(self.UserValueRole, "user_value")
|
||||
self.addRoleName(self.CategoryRole, "category")
|
||||
|
||||
|
@ -46,30 +49,41 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
def extruderId(self):
|
||||
return self._extruder_id
|
||||
|
||||
def setExtruderDefinition(self, extruder_definition):
|
||||
if extruder_definition != self._extruder_definition_id:
|
||||
self._extruder_definition_id = extruder_definition
|
||||
self._update()
|
||||
self.extruderDefinitionChanged.emit()
|
||||
|
||||
extruderDefinitionChanged = pyqtSignal()
|
||||
@pyqtProperty(str, fset = setExtruderDefinition, notify = extruderDefinitionChanged)
|
||||
def extruderDefinition(self):
|
||||
return self._extruder_definition_id
|
||||
|
||||
def setQuality(self, quality):
|
||||
if quality != self._quality:
|
||||
self._quality = quality
|
||||
if quality != self._quality_id:
|
||||
self._quality_id = quality
|
||||
self._update()
|
||||
self.qualityChanged.emit()
|
||||
|
||||
qualityChanged = pyqtSignal()
|
||||
@pyqtProperty(str, fset = setQuality, notify = qualityChanged)
|
||||
def quality(self):
|
||||
return self._quality
|
||||
return self._quality_id
|
||||
|
||||
def setMaterial(self, material):
|
||||
if material != self._material:
|
||||
self._material = material
|
||||
if material != self._material_id:
|
||||
self._material_id = material
|
||||
self._update()
|
||||
self.materialChanged.emit()
|
||||
|
||||
materialChanged = pyqtSignal()
|
||||
@pyqtProperty(str, fset = setMaterial, notify = materialChanged)
|
||||
def material(self):
|
||||
return self._material
|
||||
return self._material_id
|
||||
|
||||
def _update(self):
|
||||
if not self._quality:
|
||||
if not self._quality_id:
|
||||
return
|
||||
|
||||
items = []
|
||||
|
@ -77,9 +91,9 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
settings = collections.OrderedDict()
|
||||
definition_container = UM.Application.getInstance().getGlobalContainerStack().getBottom()
|
||||
|
||||
containers = self._container_registry.findInstanceContainers(id = self._quality)
|
||||
containers = self._container_registry.findInstanceContainers(id = self._quality_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could not find a quality container with id %s", self._quality)
|
||||
UM.Logger.log("w", "Could not find a quality container with id %s", self._quality_id)
|
||||
return
|
||||
|
||||
quality_container = None
|
||||
|
@ -92,13 +106,10 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
|
||||
criteria = {
|
||||
"type": "quality",
|
||||
"quality_type": quality_changes_container.getMetaDataEntry("quality"),
|
||||
"quality_type": quality_changes_container.getMetaDataEntry("quality_type"),
|
||||
"definition": quality_changes_container.getDefinition().getId()
|
||||
}
|
||||
|
||||
if self._material:
|
||||
criteria["material"] = self._material
|
||||
|
||||
quality_container = self._container_registry.findInstanceContainers(**criteria)
|
||||
if not quality_container:
|
||||
UM.Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId())
|
||||
|
@ -110,8 +121,8 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
|
||||
criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
|
||||
|
||||
if self._material:
|
||||
criteria["material"] = self._material
|
||||
if self._material_id and self._material_id != "empty_material":
|
||||
criteria["material"] = self._material_id
|
||||
|
||||
criteria["extruder"] = self._extruder_id
|
||||
|
||||
|
@ -122,9 +133,9 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
new_criteria.pop("extruder")
|
||||
containers = self._container_registry.findInstanceContainers(**new_criteria)
|
||||
|
||||
if not containers:
|
||||
if not containers and "material" in criteria:
|
||||
# Try again, this time without material
|
||||
criteria.pop("material")
|
||||
criteria.pop("material", None)
|
||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
||||
|
||||
if not containers:
|
||||
|
@ -133,13 +144,14 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
containers = self._container_registry.findInstanceContainers(**criteria)
|
||||
|
||||
if not containers:
|
||||
UM.Logger.log("Could not find any quality containers matching the search criteria %s" % str(criteria))
|
||||
UM.Logger.log("w", "Could not find any quality containers matching the search criteria %s" % str(criteria))
|
||||
return
|
||||
|
||||
if quality_changes_container:
|
||||
criteria = {"type": "quality_changes", "quality": quality_type, "definition": definition_id, "name": quality_changes_container.getName()}
|
||||
if self._extruder_id != "":
|
||||
criteria["extruder"] = self._extruder_id
|
||||
criteria = {"type": "quality_changes", "quality_type": quality_type, "definition": definition_id, "name": quality_changes_container.getName()}
|
||||
if self._extruder_definition_id != "":
|
||||
criteria["extruder"] = self._extruder_definition_id
|
||||
criteria["name"] = quality_changes_container.getName()
|
||||
else:
|
||||
criteria["extruder"] = None
|
||||
|
||||
|
@ -157,9 +169,11 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
continue
|
||||
|
||||
profile_value = None
|
||||
profile_value_source = ""
|
||||
for container in containers:
|
||||
new_value = container.getProperty(definition.key, "value")
|
||||
if new_value is not None:
|
||||
profile_value_source = container.getMetaDataEntry("type")
|
||||
profile_value = new_value
|
||||
|
||||
user_value = None
|
||||
|
@ -182,11 +196,13 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
# If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value.
|
||||
if self._extruder_id == "" and settable_per_extruder:
|
||||
continue
|
||||
|
||||
items.append({
|
||||
"key": definition.key,
|
||||
"label": definition.label,
|
||||
"unit": definition.unit,
|
||||
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
|
||||
"profile_value_source": profile_value_source,
|
||||
"user_value": "" if user_value is None else str(user_value),
|
||||
"category": current_category
|
||||
})
|
||||
|
|
180
cura/Settings/SettingInheritanceManager.py
Normal file
180
cura/Settings/SettingInheritanceManager.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
import UM.Settings
|
||||
from UM.Application import Application
|
||||
import cura.Settings
|
||||
|
||||
|
||||
## The settingInheritance manager is responsible for checking each setting in order to see if one of the "deeper"
|
||||
# containers has a setting function and the topmost one with a value has a value. We need to have this check
|
||||
# because some profiles tend to have 'hardcoded' values that break our inheritance. A good example of that are the
|
||||
# speed settings. If all the children of print_speed have a single value override, changing the speed won't
|
||||
# actually do anything, as only the 'leaf' settings are used by the engine.
|
||||
class SettingInheritanceManager(QObject):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
self._global_container_stack = None
|
||||
self._settings_with_inheritance_warning = []
|
||||
self._active_container_stack = None
|
||||
self._onGlobalContainerChanged()
|
||||
|
||||
cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
|
||||
self._onActiveExtruderChanged()
|
||||
|
||||
settingsWithIntheritanceChanged = pyqtSignal()
|
||||
|
||||
## Get the keys of all children settings with an override.
|
||||
@pyqtSlot(str, result = "QStringList")
|
||||
def getChildrenKeysWithOverride(self, key):
|
||||
definitions = self._global_container_stack.getBottom().findDefinitions(key=key)
|
||||
if not definitions:
|
||||
return
|
||||
result = []
|
||||
for key in definitions[0].getAllKeys():
|
||||
if key in self._settings_with_inheritance_warning:
|
||||
result.append(key)
|
||||
return result
|
||||
|
||||
@pyqtSlot(str)
|
||||
def manualRemoveOverride(self, key):
|
||||
if key in self._settings_with_inheritance_warning:
|
||||
self._settings_with_inheritance_warning.remove(key)
|
||||
self.settingsWithIntheritanceChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def forceUpdate(self):
|
||||
self._update()
|
||||
|
||||
def _onActiveExtruderChanged(self):
|
||||
new_active_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
if not new_active_stack:
|
||||
new_active_stack = self._global_container_stack
|
||||
|
||||
if new_active_stack != self._active_container_stack: # Check if changed
|
||||
if self._active_container_stack: # Disconnect signal from old container (if any)
|
||||
self._active_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
|
||||
self._active_container_stack = new_active_stack
|
||||
self._active_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
self._update() # Ensure that the settings_with_inheritance_warning list is populated.
|
||||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
if property_name == "value" and self._global_container_stack:
|
||||
definitions = self._global_container_stack.getBottom().findDefinitions(key = key)
|
||||
if not definitions:
|
||||
return
|
||||
|
||||
has_overwritten_inheritance = self._settingIsOverwritingInheritance(key)
|
||||
|
||||
settings_with_inheritance_warning_changed = False
|
||||
|
||||
# Check if the setting needs to be in the list.
|
||||
if key not in self._settings_with_inheritance_warning and has_overwritten_inheritance:
|
||||
self._settings_with_inheritance_warning.append(key)
|
||||
settings_with_inheritance_warning_changed = True
|
||||
elif key in self._settings_with_inheritance_warning and not has_overwritten_inheritance:
|
||||
self._settings_with_inheritance_warning.remove(key)
|
||||
settings_with_inheritance_warning_changed = True
|
||||
|
||||
# Find the topmost parent (Assumed to be a category)
|
||||
parent = definitions[0].parent
|
||||
while parent.parent is not None:
|
||||
parent = parent.parent
|
||||
|
||||
if parent.key not in self._settings_with_inheritance_warning and has_overwritten_inheritance:
|
||||
# Category was not in the list yet, so needs to be added now.
|
||||
self._settings_with_inheritance_warning.append(parent.key)
|
||||
settings_with_inheritance_warning_changed = True
|
||||
|
||||
elif parent.key in self._settings_with_inheritance_warning and not has_overwritten_inheritance:
|
||||
# Category was in the list and one of it's settings is not overwritten.
|
||||
if not self._recursiveCheck(parent): # Check if any of it's children have overwritten inheritance.
|
||||
self._settings_with_inheritance_warning.remove(parent.key)
|
||||
settings_with_inheritance_warning_changed = True
|
||||
|
||||
# Emit the signal if there was any change to the list.
|
||||
if settings_with_inheritance_warning_changed:
|
||||
self.settingsWithIntheritanceChanged.emit()
|
||||
|
||||
def _recursiveCheck(self, definition):
|
||||
for child in definition.children:
|
||||
if child.key in self._settings_with_inheritance_warning:
|
||||
return True
|
||||
if child.children:
|
||||
if self._recursiveCheck(child):
|
||||
return True
|
||||
return False
|
||||
|
||||
@pyqtProperty("QVariantList", notify = settingsWithIntheritanceChanged)
|
||||
def settingsWithInheritanceWarning(self):
|
||||
return self._settings_with_inheritance_warning
|
||||
|
||||
## Check if a setting has an inheritance function that is overwritten
|
||||
def _settingIsOverwritingInheritance(self, key):
|
||||
has_setting_function = False
|
||||
stack = self._active_container_stack
|
||||
containers = []
|
||||
|
||||
## Check if the setting has a user state. If not, it is never overwritten.
|
||||
has_user_state = self._active_container_stack.getProperty(key, "state") == UM.Settings.InstanceState.User
|
||||
if not has_user_state:
|
||||
return False
|
||||
|
||||
## If a setting is not enabled, don't label it as overwritten (It's never visible anyway).
|
||||
if not self._active_container_stack.getProperty(key, "enabled"):
|
||||
return False
|
||||
|
||||
## Also check if the top container is not a setting function (this happens if the inheritance is restored).
|
||||
if isinstance(self._active_container_stack.getTop().getProperty(key, "value"), UM.Settings.SettingFunction):
|
||||
return False
|
||||
|
||||
## Mash all containers for all the stacks together.
|
||||
while stack:
|
||||
containers.extend(stack.getContainers())
|
||||
stack = stack.getNextStack()
|
||||
has_non_function_value = False
|
||||
for container in containers:
|
||||
try:
|
||||
value = container.getProperty(key, "value")
|
||||
if value is not None:
|
||||
has_setting_function = isinstance(value, UM.Settings.SettingFunction)
|
||||
if has_setting_function is False:
|
||||
has_non_function_value = True
|
||||
continue
|
||||
except AttributeError:
|
||||
continue
|
||||
if has_setting_function:
|
||||
break # There is a setting function somewhere, stop looking deeper.
|
||||
return has_setting_function and has_non_function_value
|
||||
|
||||
def _update(self):
|
||||
self._settings_with_inheritance_warning = [] # Reset previous data.
|
||||
|
||||
# Check all setting keys that we know of and see if they are overridden.
|
||||
for setting_key in self._global_container_stack.getAllKeys():
|
||||
override = self._settingIsOverwritingInheritance(setting_key)
|
||||
if override:
|
||||
self._settings_with_inheritance_warning.append(setting_key)
|
||||
|
||||
# Check all the categories if any of their children have their inheritance overwritten.
|
||||
for category in self._global_container_stack.getBottom().findDefinitions(type = "category"):
|
||||
if self._recursiveCheck(category):
|
||||
self._settings_with_inheritance_warning.append(category.key)
|
||||
|
||||
# Notify others that things have changed.
|
||||
self.settingsWithIntheritanceChanged.emit()
|
||||
|
||||
def _onGlobalContainerChanged(self):
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
self._onActiveExtruderChanged()
|
||||
|
||||
@staticmethod
|
||||
def createSettingInheritanceManager(engine=None, script_engine=None):
|
||||
return SettingInheritanceManager()
|
|
@ -61,6 +61,15 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
|||
def getActiveExtruder(self):
|
||||
return self._extruder_stack
|
||||
|
||||
## Gets the currently active extruders position
|
||||
#
|
||||
# \return An extruder's position, or None if no position info is available.
|
||||
def getActiveExtruderPosition(self):
|
||||
containers = ContainerRegistry.getInstance().findContainers(id = self.getActiveExtruder())
|
||||
if containers:
|
||||
container_stack = containers[0]
|
||||
return container_stack.getMetaDataEntry("position", default=None)
|
||||
|
||||
def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function
|
||||
if property_name == "value": # Only reslice if the value has changed.
|
||||
Application.getInstance().getBackend().forceSlice()
|
||||
|
|
36
cura/Settings/UserProfilesModel.py
Normal file
36
cura/Settings/UserProfilesModel.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
from UM.Application import Application
|
||||
|
||||
from cura.QualityManager import QualityManager
|
||||
from cura.Settings.ProfilesModel import ProfilesModel
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
## QML Model for listing the current list of valid quality changes profiles.
|
||||
#
|
||||
class UserProfilesModel(ProfilesModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
## Fetch the list of containers to display.
|
||||
#
|
||||
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
|
||||
def _fetchInstanceContainers(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return []
|
||||
|
||||
# Fetch the list of quality changes.
|
||||
quality_manager = QualityManager.getInstance()
|
||||
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom())
|
||||
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
|
||||
|
||||
# Fetch the list of qualities
|
||||
quality_list = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack,
|
||||
ExtruderManager.getInstance().getActiveExtruderStacks())
|
||||
|
||||
# Filter the quality_change by the list of available quality_types
|
||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
||||
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set]
|
||||
|
||||
return filtered_quality_changes
|
|
@ -11,3 +11,7 @@ from .MachineManager import MachineManager
|
|||
from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
||||
from .SettingOverrideDecorator import SettingOverrideDecorator
|
||||
from .QualitySettingsModel import QualitySettingsModel
|
||||
from .SettingInheritanceManager import SettingInheritanceManager
|
||||
from .ProfilesModel import ProfilesModel
|
||||
from .QualityAndUserProfilesModel import QualityAndUserProfilesModel
|
||||
from .UserProfilesModel import UserProfilesModel
|
||||
|
|
|
@ -8,9 +8,11 @@ from UM.Math.Matrix import Matrix
|
|||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.GroupDecorator import GroupDecorator
|
||||
from UM.Math.Quaternion import Quaternion
|
||||
import UM.Application
|
||||
from UM.Job import Job
|
||||
|
||||
from UM.Math.Quaternion import Quaternion
|
||||
|
||||
import math
|
||||
import zipfile
|
||||
|
||||
|
@ -24,104 +26,141 @@ class ThreeMFReader(MeshReader):
|
|||
def __init__(self):
|
||||
super().__init__()
|
||||
self._supported_extensions = [".3mf"]
|
||||
|
||||
self._root = None
|
||||
self._namespaces = {
|
||||
"3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
|
||||
"cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10"
|
||||
}
|
||||
|
||||
def _createNodeFromObject(self, object, name = ""):
|
||||
node = SceneNode()
|
||||
mesh_builder = MeshBuilder()
|
||||
vertex_list = []
|
||||
|
||||
components = object.find(".//3mf:components", self._namespaces)
|
||||
if components:
|
||||
for component in components:
|
||||
id = component.get("objectid")
|
||||
new_object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces)
|
||||
|
||||
new_node = self._createNodeFromObject(new_object)
|
||||
node.addChild(new_node)
|
||||
transform = component.get("transform")
|
||||
if transform is not None:
|
||||
new_node.setTransformation(self._createMatrixFromTransformationString(transform))
|
||||
|
||||
# for vertex in entry.mesh.vertices.vertex:
|
||||
for vertex in object.findall(".//3mf:vertex", self._namespaces):
|
||||
vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
|
||||
Job.yieldThread()
|
||||
|
||||
# If this object has no vertices and just one child, just return the child.
|
||||
if len(vertex_list) == 0 and len(node.getChildren()) == 1:
|
||||
return node.getChildren()[0]
|
||||
|
||||
if len(node.getChildren()) > 0:
|
||||
group_decorator = GroupDecorator()
|
||||
node.addDecorator(group_decorator)
|
||||
|
||||
triangles = object.findall(".//3mf:triangle", self._namespaces)
|
||||
mesh_builder.reserveFaceCount(len(triangles))
|
||||
|
||||
for triangle in triangles:
|
||||
v1 = int(triangle.get("v1"))
|
||||
v2 = int(triangle.get("v2"))
|
||||
v3 = int(triangle.get("v3"))
|
||||
|
||||
mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2],
|
||||
vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2],
|
||||
vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2])
|
||||
|
||||
Job.yieldThread()
|
||||
|
||||
# Rotate the model; We use a different coordinate frame.
|
||||
rotation = Matrix()
|
||||
rotation.setByRotationAxis(-0.5 * math.pi, Vector(1, 0, 0))
|
||||
flip_matrix = Matrix()
|
||||
|
||||
flip_matrix._data[1, 1] = 0
|
||||
flip_matrix._data[1, 2] = 1
|
||||
flip_matrix._data[2, 1] = 1
|
||||
flip_matrix._data[2, 2] = 0
|
||||
|
||||
# TODO: We currently do not check for normals and simply recalculate them.
|
||||
mesh_builder.calculateNormals()
|
||||
mesh_builder.setFileName(name)
|
||||
mesh_data = mesh_builder.build().getTransformed(flip_matrix)
|
||||
|
||||
if len(mesh_data.getVertices()):
|
||||
node.setMeshData(mesh_data)
|
||||
|
||||
node.setSelectable(True)
|
||||
return node
|
||||
|
||||
def _createMatrixFromTransformationString(self, transformation):
|
||||
splitted_transformation = transformation.split()
|
||||
## Transformation is saved as:
|
||||
## M00 M01 M02 0.0
|
||||
## M10 M11 M12 0.0
|
||||
## M20 M21 M22 0.0
|
||||
## M30 M31 M32 1.0
|
||||
## We switch the row & cols as that is how everyone else uses matrices!
|
||||
temp_mat = Matrix()
|
||||
# Rotation & Scale
|
||||
temp_mat._data[0, 0] = splitted_transformation[0]
|
||||
temp_mat._data[1, 0] = splitted_transformation[1]
|
||||
temp_mat._data[2, 0] = splitted_transformation[2]
|
||||
temp_mat._data[0, 1] = splitted_transformation[3]
|
||||
temp_mat._data[1, 1] = splitted_transformation[4]
|
||||
temp_mat._data[2, 1] = splitted_transformation[5]
|
||||
temp_mat._data[0, 2] = splitted_transformation[6]
|
||||
temp_mat._data[1, 2] = splitted_transformation[7]
|
||||
temp_mat._data[2, 2] = splitted_transformation[8]
|
||||
|
||||
# Translation
|
||||
temp_mat._data[0, 3] = splitted_transformation[9]
|
||||
temp_mat._data[1, 3] = splitted_transformation[10]
|
||||
temp_mat._data[2, 3] = splitted_transformation[11]
|
||||
|
||||
flip_matrix = Matrix()
|
||||
flip_matrix._data[1, 1] = 0
|
||||
flip_matrix._data[1, 2] = 1
|
||||
flip_matrix._data[2, 1] = 1
|
||||
flip_matrix._data[2, 2] = 0
|
||||
temp_mat.multiply(flip_matrix)
|
||||
|
||||
return temp_mat
|
||||
|
||||
def read(self, file_name):
|
||||
result = SceneNode()
|
||||
# The base object of 3mf is a zipped archive.
|
||||
archive = zipfile.ZipFile(file_name, "r")
|
||||
try:
|
||||
root = ET.parse(archive.open("3D/3dmodel.model"))
|
||||
self._root = ET.parse(archive.open("3D/3dmodel.model"))
|
||||
|
||||
# There can be multiple objects, try to load all of them.
|
||||
objects = root.findall("./3mf:resources/3mf:object", self._namespaces)
|
||||
if len(objects) == 0:
|
||||
Logger.log("w", "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format", file_name)
|
||||
return None
|
||||
build_items = self._root.findall("./3mf:build/3mf:item", self._namespaces)
|
||||
|
||||
for entry in objects:
|
||||
mesh_builder = MeshBuilder()
|
||||
node = SceneNode()
|
||||
vertex_list = []
|
||||
#for vertex in entry.mesh.vertices.vertex:
|
||||
for vertex in entry.findall(".//3mf:vertex", self._namespaces):
|
||||
vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
|
||||
Job.yieldThread()
|
||||
for build_item in build_items:
|
||||
id = build_item.get("objectid")
|
||||
object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces)
|
||||
build_item_node = self._createNodeFromObject(object)
|
||||
transform = build_item.get("transform")
|
||||
if transform is not None:
|
||||
build_item_node.setTransformation(self._createMatrixFromTransformationString(transform))
|
||||
result.addChild(build_item_node)
|
||||
|
||||
triangles = entry.findall(".//3mf:triangle", self._namespaces)
|
||||
mesh_builder.reserveFaceCount(len(triangles))
|
||||
|
||||
for triangle in triangles:
|
||||
v1 = int(triangle.get("v1"))
|
||||
v2 = int(triangle.get("v2"))
|
||||
v3 = int(triangle.get("v3"))
|
||||
|
||||
mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2],
|
||||
vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2],
|
||||
vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2])
|
||||
|
||||
Job.yieldThread()
|
||||
|
||||
# Rotate the model; We use a different coordinate frame.
|
||||
rotation = Matrix()
|
||||
rotation.setByRotationAxis(-0.5 * math.pi, Vector(1, 0, 0))
|
||||
|
||||
# TODO: We currently do not check for normals and simply recalculate them.
|
||||
mesh_builder.calculateNormals()
|
||||
mesh_builder.setFileName(file_name)
|
||||
node.setMeshData(mesh_builder.build().getTransformed(rotation))
|
||||
node.setSelectable(True)
|
||||
|
||||
transformations = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(entry.get("id")), self._namespaces)
|
||||
transformation = transformations[0] if transformations else None
|
||||
if transformation is not None and transformation.get("transform"):
|
||||
splitted_transformation = transformation.get("transform").split()
|
||||
## Transformation is saved as:
|
||||
## M00 M01 M02 0.0
|
||||
## M10 M11 M12 0.0
|
||||
## M20 M21 M22 0.0
|
||||
## M30 M31 M32 1.0
|
||||
## We switch the row & cols as that is how everyone else uses matrices!
|
||||
temp_mat = Matrix()
|
||||
# Rotation & Scale
|
||||
temp_mat._data[0,0] = splitted_transformation[0]
|
||||
temp_mat._data[1,0] = splitted_transformation[1]
|
||||
temp_mat._data[2,0] = splitted_transformation[2]
|
||||
temp_mat._data[0,1] = splitted_transformation[3]
|
||||
temp_mat._data[1,1] = splitted_transformation[4]
|
||||
temp_mat._data[2,1] = splitted_transformation[5]
|
||||
temp_mat._data[0,2] = splitted_transformation[6]
|
||||
temp_mat._data[1,2] = splitted_transformation[7]
|
||||
temp_mat._data[2,2] = splitted_transformation[8]
|
||||
|
||||
# Translation
|
||||
temp_mat._data[0,3] = splitted_transformation[9]
|
||||
temp_mat._data[1,3] = splitted_transformation[10]
|
||||
temp_mat._data[2,3] = splitted_transformation[11]
|
||||
|
||||
node.setTransformation(temp_mat)
|
||||
|
||||
result.addChild(node)
|
||||
|
||||
Job.yieldThread()
|
||||
|
||||
# If there is more then one object, group them.
|
||||
if len(objects) > 1:
|
||||
group_decorator = GroupDecorator()
|
||||
result.addDecorator(group_decorator)
|
||||
elif len(objects) == 1:
|
||||
result = result.getChildren()[0] # Only one object found, return that.
|
||||
except Exception as e:
|
||||
Logger.log("e", "exception occured in 3mf reader: %s", e)
|
||||
|
||||
try: # Selftest - There might be more functions that should fail
|
||||
boundingBox = result.getBoundingBox()
|
||||
boundingBox.isValid()
|
||||
try: # Selftest - There might be more functions that should fail
|
||||
bounding_box = result.getBoundingBox()
|
||||
bounding_box.isValid()
|
||||
except:
|
||||
return None
|
||||
|
||||
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
if global_container_stack:
|
||||
translation = Vector(x=-global_container_stack.getProperty("machine_width", "value") / 2, y=0,
|
||||
z=global_container_stack.getProperty("machine_depth", "value") / 2)
|
||||
result.translate(translation, SceneNode.TransformSpace.World)
|
||||
return result
|
||||
|
|
|
@ -33,7 +33,6 @@ class ChangeLog(Extension, QObject,):
|
|||
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
||||
Preferences.getInstance().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
|
||||
self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
|
||||
#self.showChangelog()
|
||||
|
||||
def getChangeLogs(self):
|
||||
if not self._change_logs:
|
||||
|
@ -87,6 +86,13 @@ class ChangeLog(Extension, QObject,):
|
|||
else:
|
||||
latest_version_shown = Version(Preferences.getInstance().getValue("general/latest_version_changelog_shown"))
|
||||
|
||||
Preferences.getInstance().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())
|
||||
|
||||
# Do not show the changelog when there is no global container stack
|
||||
# This implies we are running Cura for the first time.
|
||||
if not Application.getInstance().getGlobalContainerStack():
|
||||
return
|
||||
|
||||
if self._version > latest_version_shown:
|
||||
self.showChangelog()
|
||||
|
||||
|
@ -95,7 +101,6 @@ class ChangeLog(Extension, QObject,):
|
|||
self.createChangelogWindow()
|
||||
|
||||
self._changelog_window.show()
|
||||
Preferences.getInstance().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())
|
||||
|
||||
def hideChangelog(self):
|
||||
if self._changelog_window:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Controls 1.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
|
@ -11,33 +11,31 @@ import UM 1.1 as UM
|
|||
UM.Dialog
|
||||
{
|
||||
id: base
|
||||
minimumWidth: 400 * Screen.devicePixelRatio
|
||||
minimumHeight: 300 * Screen.devicePixelRatio
|
||||
minimumWidth: UM.Theme.getSize("modal_window_minimum").width * 0.75
|
||||
minimumHeight: UM.Theme.getSize("modal_window_minimum").height * 0.75
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
title: catalog.i18nc("@label", "Changelog")
|
||||
|
||||
ScrollView
|
||||
TextArea
|
||||
{
|
||||
width: parent.width
|
||||
height: parent.height - 25
|
||||
Label
|
||||
{
|
||||
text: manager.getChangeLogString()
|
||||
width:base.width - 35
|
||||
wrapMode: Text.Wrap;
|
||||
}
|
||||
anchors.fill: parent
|
||||
text: manager.getChangeLogString()
|
||||
readOnly: true;
|
||||
textFormat: TextEdit.RichText
|
||||
}
|
||||
Button
|
||||
{
|
||||
UM.I18nCatalog
|
||||
|
||||
rightButtons: [
|
||||
Button
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
text: catalog.i18nc("@action:button", "Close")
|
||||
onClicked: base.hide()
|
||||
}
|
||||
anchors.bottom:parent.bottom
|
||||
text: catalog.i18nc("@action:button", "Close")
|
||||
onClicked: base.hide()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ The first thing you will notice is the speed. STL loading is now 10 to 20 times
|
|||
Machines with multiple extruders are now supported. If you’ve got the Ultimaker Original with the dual extrusion upgrade kit, we’ve got you covered.
|
||||
|
||||
*Custom Machine Support
|
||||
The new custom machine plug-in allows you to change machine settings with ease. That means it’s now much easier to use Cura with custom machines.
|
||||
It’s now much easier to use Cura with custom machines. You can edit the machine settings when you load a new custom machine.
|
||||
|
||||
*Improved Position Tool
|
||||
Place objects precisely where you want them by manually entering the values for the position.
|
||||
|
@ -20,10 +20,10 @@ Select an individual item in a group or merged object and edit as usual. Just Ct
|
|||
Profile management is improved. You can now easily see and track changes made to your profiles.
|
||||
|
||||
*Improved Setting Visibility
|
||||
Make multiple settings visible at once. The Visibility Overview setting indicates why a setting is not shown in the sidebar even if it is selected.
|
||||
Make multiple settings visible at once. The Visibility Overview setting indicates why a setting is not shown in the sidebar even if it is enabled.
|
||||
|
||||
*Improved time estimation
|
||||
Time estimations are more accurate. Based on our test time estimations should be within 5% accuracy.
|
||||
Time estimations are more accurate. Based on our test time estimations should be within 5% accuracy for Ultimaker printers.
|
||||
|
||||
*Optional G-code Machine Prefix
|
||||
Disable the g-code prefix in Preferences. No more UM2_ on your printer display!
|
||||
|
@ -35,14 +35,17 @@ Cura now estimates print weight as well as length.
|
|||
Configurations from older installations of Cura 2.1 are automatically imported into the newest installation.
|
||||
|
||||
*Slicing features
|
||||
*Infill Improvements
|
||||
We've introduced two new infill types: Tetrahedral and Cubic. They change along with the Z-axis for more uniform strength in all directions. Also, now you can change the density of the infill based on the distance from the top layers. You print get faster, use less material, and maintain the same object strength.
|
||||
*Infill Types
|
||||
We've introduced two new infill types: Tetrahedral and Cubic. They change along with the Z-axis for more uniform strength in all directions. There are now 7 infill types to choose from.
|
||||
|
||||
*Gradual Infill
|
||||
Now you can change the density of the infill based on the distance from the top layers. Your objects print faster, use less material, while top surfaces have the same quality.
|
||||
|
||||
*Set Acceleration and Jerk by Feature
|
||||
You can now set Jerk and Acceleration by feature, (infill, walls, top/bottom, etc) for more precision.
|
||||
You can now set Jerk and Acceleration by feature-type (infill, walls, top/bottom, etc), for more precision.
|
||||
|
||||
*Outer Wall Offset
|
||||
Apply an offset to where the outer wall is printed for better surface quality when the outer wall line width is smaller than the nozzle size.
|
||||
If your outer wall line width is smaller than your nozzle size, move the nozzle a bit inward when printing the outer wall, to improve surface quality.
|
||||
|
||||
*Enhanced Combing
|
||||
The “No Skin” option allows you to comb over infill only to avoid scars on top surfaces.
|
||||
|
@ -56,42 +59,46 @@ The Skin Overlap setting allows you to overlap the skin lines with the walls for
|
|||
*Control Initial Layer Travel Speed
|
||||
Set the travel speed of the initial layer(s) to reduce risk of extruder pulling the print from the bed.
|
||||
|
||||
*Support Bottoms
|
||||
This new feature duplicates the Support Roofs feature in the places where the support rests on the model.
|
||||
*Support Interface
|
||||
It is now possible to print a support bottom as well as a support roof. Support bottoms are placed where the support rests on the model. Printing the support interface with PVA leads to improved surface quality.
|
||||
|
||||
*Bug fixes & minor changes
|
||||
Deleting grouped objects works as intended again.
|
||||
Duplicating groups works as intended again.
|
||||
Bridging works as intended again.
|
||||
*Bug fixes
|
||||
Deleting grouped objects
|
||||
Duplicating groups
|
||||
Bridging
|
||||
Drag and drop on the first run on Windows
|
||||
Unretraction speeds
|
||||
Bottom layer in Spiralize mode
|
||||
Overlap Compensation
|
||||
Retractions on Raft
|
||||
Retractions now occur after each object printed in one-at-a-time mode.
|
||||
Rafts are no longer printed outside of build area.
|
||||
Messages are now displayed 30 seconds instead of 10, making it less likely that certain messages are missed.
|
||||
Drag and drop on the first run on windows works again.
|
||||
You are now notified if you try to save to a locked SD card.
|
||||
Combing is applied in more cases and results in better paths.
|
||||
Infill thickness now supports Grid infill also for even multiples of the layer height.
|
||||
Unretraction speeds are correct again.
|
||||
Spiralize mode doesn’t spiralize the bottom layer any more.
|
||||
Spiralize now spiralizes each part in a layer.
|
||||
Support is no longer removed by unprintable thin parts of the model.
|
||||
Support is now generated on each layer it’s supposed to.
|
||||
Support doesn't go outside overhang areas any more.
|
||||
Spiralize no longer only spiralizes the first printed segment only.
|
||||
Line distance is now the actual line distance.
|
||||
Enabling raft doesn’t influence at which height the model is sliced any more.
|
||||
Brim is now always printed just once.
|
||||
Overlap Compensation works as intended again.
|
||||
A raft now produces a reasonable amount of retractions.
|
||||
No more string plume on top of one-at-a-time printed objects.
|
||||
Engine log can be viewed even when slicing is finished.
|
||||
Support roofs now only occur just below overhang.
|
||||
Brim is now also generated under the support.
|
||||
Compensate overlapping wall parts now also works for inner walls.
|
||||
Bed Level and Checkup procedures for UMO+ can now be done without re-adding machine.
|
||||
|
||||
*Minor changes
|
||||
Messages are now displayed 30 seconds instead of 10, making it less likely that certain messages are missed.
|
||||
You are now notified if you try to save to a locked SD card.
|
||||
Engine log is now included in the application log.
|
||||
Undo and Redo now work correctly with multiple operations.
|
||||
The last used folder is now remembered (instead of defaulting to home folder)
|
||||
You can now adjust the speed at which the bed is lowered each layer.
|
||||
Support roofs now only generate directly below the mesh.
|
||||
Settings shared between skirt and brim now also activate when brim is selected.
|
||||
The last used folder is now remembered (instead of defaulting to home folder).
|
||||
Import X3D files.
|
||||
Made it possible to add multiple Per Model Settings at once.
|
||||
Bed Level and Checkup procedures for UMO+ can now be done without re-adding machine.
|
||||
Combing is applied in more cases and results in better paths.
|
||||
Infill thickness now supports Grid infill also for even multiples of the layer height.
|
||||
Support is no longer removed by unprintable thin parts of the model.
|
||||
Support is now generated on each layer it’s supposed to.
|
||||
Support doesn't go outside overhang areas any more.
|
||||
Support doesn't remove brim around the object any more.
|
||||
Brim is now also generated under the support.
|
||||
Draft shield and Ooze shield get their own brim or raft.
|
||||
Settings shared between skirt and brim now also activate when brim is selected.
|
||||
Compensate overlapping wall parts now also works for inner walls.
|
||||
You can now adjust the speed at which the bed is lowered each layer.
|
||||
|
||||
[2.1.3]
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ message Slice
|
|||
repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another
|
||||
SettingList global_settings = 2; // The global settings used for the whole print job
|
||||
repeated Extruder extruders = 3; // The settings sent to each extruder object
|
||||
repeated SettingExtruder limit_to_extruder = 4; //From which stack the setting would inherit if not defined in a stack.
|
||||
repeated SettingExtruder limit_to_extruder = 4; // From which stack the setting would inherit if not defined per object
|
||||
}
|
||||
|
||||
message Extruder
|
||||
|
|
|
@ -230,7 +230,7 @@ class CuraEngineBackend(Backend):
|
|||
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
|
||||
if Application.getInstance().getPlatformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status",
|
||||
"The selected material is imcompatible with the selected machine or configuration."))
|
||||
"The selected material is incompatible with the selected machine or configuration."))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
else:
|
||||
|
@ -336,6 +336,7 @@ class CuraEngineBackend(Backend):
|
|||
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 = []
|
||||
|
||||
|
@ -411,6 +412,7 @@ class CuraEngineBackend(Backend):
|
|||
# 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 = []
|
||||
else:
|
||||
|
@ -463,3 +465,5 @@ class CuraEngineBackend(Backend):
|
|||
if self._active_extruder_stack:
|
||||
self._active_extruder_stack.containersChanged.connect(self._onChanged)
|
||||
|
||||
def _onProcessLayersFinished(self, job):
|
||||
self._process_layers_job = None
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
import gc
|
||||
|
||||
from UM.Job import Job
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
@ -64,6 +66,12 @@ class ProcessSlicedLayersJob(Job):
|
|||
self._progress.hide()
|
||||
return
|
||||
|
||||
# Force garbage collection.
|
||||
# For some reason, Python has a tendency to keep the layer data
|
||||
# in memory longer than needed. Forcing the GC to run here makes
|
||||
# sure any old layer data is really cleaned up before adding new.
|
||||
gc.collect()
|
||||
|
||||
mesh = MeshData()
|
||||
layer_data = LayerDataBuilder.LayerDataBuilder()
|
||||
layer_count = len(self._layers)
|
||||
|
|
|
@ -160,7 +160,13 @@ class StartSliceJob(Job):
|
|||
|
||||
obj = group_message.addRepeatedMessage("objects")
|
||||
obj.id = id(object)
|
||||
verts = numpy.array(mesh_data.getVertices())
|
||||
verts = mesh_data.getVertices()
|
||||
indices = mesh_data.getIndices()
|
||||
if indices is not None:
|
||||
#TODO: This is a very slow way of doing it! It also locks up the GUI.
|
||||
verts = numpy.array([verts[vert_index] for face in indices for vert_index in face])
|
||||
else:
|
||||
verts = numpy.array(verts)
|
||||
|
||||
# Convert from Y up axes to Z up axes. Equals a 90 degree rotation.
|
||||
verts[:, [1, 2]] = verts[:, [2, 1]]
|
||||
|
@ -198,6 +204,9 @@ class StartSliceJob(Job):
|
|||
material_instance_container = stack.findContainer({"type": "material"})
|
||||
|
||||
for key in stack.getAllKeys():
|
||||
# Do not send settings that are not settable_per_extruder.
|
||||
if stack.getProperty(key, "settable_per_extruder") == False:
|
||||
continue
|
||||
setting = message.getMessage("settings").addRepeatedMessage("settings")
|
||||
setting.name = key
|
||||
if key == "material_guid" and material_instance_container:
|
||||
|
|
|
@ -98,6 +98,11 @@ class GCodeWriter(MeshWriter):
|
|||
return ""
|
||||
|
||||
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
|
||||
|
||||
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
||||
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
|
||||
flat_global_container.addMetaDataEntry("quality_type", stack.findContainer({"type": "quality"}).getMetaDataEntry("quality_type", "normal"))
|
||||
|
||||
serialized = flat_global_container.serialize()
|
||||
data = {"global_quality": serialized}
|
||||
|
||||
|
@ -106,9 +111,15 @@ class GCodeWriter(MeshWriter):
|
|||
if not extruder_quality:
|
||||
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
|
||||
continue
|
||||
|
||||
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
|
||||
|
||||
# Ensure that extruder is set. (Can happen if we have empty quality changes).
|
||||
if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
|
||||
flat_extruder_quality.addMetaDataEntry("extruder", extruder.getBottom().getId())
|
||||
|
||||
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
||||
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
|
||||
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.findContainer({"type": "quality"}).getMetaDataEntry("quality_type", "normal"))
|
||||
extruder_serialized = flat_extruder_quality.serialize()
|
||||
data.setdefault("extruder_quality", []).append(extruder_serialized)
|
||||
|
||||
|
|
87
plugins/LayerView/LayerPass.py
Normal file
87
plugins/LayerView/LayerPass.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Resources import Resources
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.ToolHandle import ToolHandle
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from UM.View.RenderPass import RenderPass
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
import os.path
|
||||
|
||||
## RenderPass used to display g-code paths.
|
||||
class LayerPass(RenderPass):
|
||||
def __init__(self, width, height):
|
||||
super().__init__("layerview", width, height)
|
||||
|
||||
self._layer_shader = None
|
||||
self._tool_handle_shader = None
|
||||
self._gl = OpenGL.getInstance().getBindingsObject()
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
self._extruder_manager = ExtruderManager.getInstance()
|
||||
|
||||
self._layer_view = None
|
||||
|
||||
def setLayerView(self, layerview):
|
||||
self._layerview = layerview
|
||||
|
||||
def render(self):
|
||||
if not self._layer_shader:
|
||||
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layers.shader"))
|
||||
# Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
|
||||
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
|
||||
if not self._tool_handle_shader:
|
||||
self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
|
||||
|
||||
self.bind()
|
||||
|
||||
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
|
||||
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
if isinstance(node, ToolHandle):
|
||||
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
|
||||
|
||||
elif isinstance(node, SceneNode) and node.getMeshData() and node.isVisible():
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
continue
|
||||
|
||||
# Render all layers below a certain number as line mesh instead of vertices.
|
||||
if self._layerview._current_layer_num - self._layerview._solid_layers > -1 and not self._layerview._only_show_top_layers:
|
||||
start = 0
|
||||
end = 0
|
||||
element_counts = layer_data.getElementCounts()
|
||||
for layer, counts in element_counts.items():
|
||||
if layer + self._layerview._solid_layers > self._layerview._current_layer_num:
|
||||
break
|
||||
end += counts
|
||||
|
||||
# This uses glDrawRangeElements internally to only draw a certain range of lines.
|
||||
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))
|
||||
batch.addItem(node.getWorldTransformation(), layer_data)
|
||||
batch.render(self._scene.getActiveCamera())
|
||||
|
||||
# Create a new batch that is not range-limited
|
||||
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
|
||||
|
||||
if self._layerview._current_layer_mesh:
|
||||
batch.addItem(node.getWorldTransformation(), self._layerview._current_layer_mesh)
|
||||
|
||||
if self._layerview._current_layer_jumps:
|
||||
batch.addItem(node.getWorldTransformation(), self._layerview._current_layer_jumps)
|
||||
|
||||
if len(batch.items) > 0:
|
||||
batch.render(self._scene.getActiveCamera())
|
||||
|
||||
# Render toolhandles on top of the layerview
|
||||
if len(tool_handle_batch.items) > 0:
|
||||
tool_handle_batch.render(self._scene.getActiveCamera())
|
||||
|
||||
self.release()
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.View.View import View
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Resources import Resources
|
||||
|
@ -28,18 +29,16 @@ from . import LayerViewProxy
|
|||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
from . import LayerPass
|
||||
|
||||
import numpy
|
||||
import os.path
|
||||
|
||||
## View used to display g-code paths.
|
||||
class LayerView(View):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._shader = None
|
||||
self._ghost_shader = None
|
||||
self._num_layers = 0
|
||||
self._layer_percentage = 0 # what percentage of layers need to be shown (Slider gives value between 0 - 100)
|
||||
self._proxy = LayerViewProxy.LayerViewProxy()
|
||||
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
|
||||
|
||||
self._max_layers = 0
|
||||
self._current_layer_num = 0
|
||||
self._current_layer_mesh = None
|
||||
|
@ -47,7 +46,19 @@ class LayerView(View):
|
|||
self._top_layers_job = None
|
||||
self._activity = False
|
||||
self._old_max_layers = 0
|
||||
|
||||
self._busy = False
|
||||
|
||||
self._ghost_shader = None
|
||||
self._layer_pass = None
|
||||
self._composite_pass = None
|
||||
self._old_layer_bindings = None
|
||||
self._layerview_composite_shader = None
|
||||
self._old_composite_shader = None
|
||||
|
||||
self._global_container_stack = None
|
||||
self._proxy = LayerViewProxy.LayerViewProxy()
|
||||
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
|
||||
|
||||
Preferences.getInstance().addPreference("view/top_layer_count", 5)
|
||||
Preferences.getInstance().addPreference("view/only_show_top_layers", False)
|
||||
|
@ -55,13 +66,21 @@ class LayerView(View):
|
|||
|
||||
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
|
||||
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
|
||||
self._busy = False
|
||||
|
||||
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"))
|
||||
|
||||
def getActivity(self):
|
||||
return self._activity
|
||||
|
||||
def getLayerPass(self):
|
||||
if not self._layer_pass:
|
||||
# Currently the RenderPass constructor requires a size > 0
|
||||
# This should be fixed in RenderPass's constructor.
|
||||
self._layer_pass = LayerPass.LayerPass(1, 1)
|
||||
self._layer_pass.setLayerView(self)
|
||||
self.getRenderer().addRenderPass(self._layer_pass)
|
||||
return self._layer_pass
|
||||
|
||||
def getCurrentLayer(self):
|
||||
return self._current_layer_num
|
||||
|
||||
|
@ -91,7 +110,7 @@ class LayerView(View):
|
|||
|
||||
if not self._ghost_shader:
|
||||
self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
|
||||
self._ghost_shader.setUniformValue("u_color", Color(0, 0, 0, 64))
|
||||
self._ghost_shader.setUniformValue("u_color", Color(32, 32, 32, 96))
|
||||
|
||||
for node in DepthFirstIterator(scene.getRoot()):
|
||||
# We do not want to render ConvexHullNode as it conflicts with the bottom layers.
|
||||
|
@ -101,35 +120,7 @@ class LayerView(View):
|
|||
|
||||
if not node.render(renderer):
|
||||
if node.getMeshData() and node.isVisible():
|
||||
renderer.queueNode(node,
|
||||
shader = self._ghost_shader,
|
||||
type = RenderBatch.RenderType.Transparent )
|
||||
|
||||
for node in DepthFirstIterator(scene.getRoot()):
|
||||
if type(node) is SceneNode:
|
||||
if node.getMeshData() and node.isVisible():
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
continue
|
||||
|
||||
# Render all layers below a certain number as line mesh instead of vertices.
|
||||
if self._current_layer_num - self._solid_layers > -1 and not self._only_show_top_layers:
|
||||
start = 0
|
||||
end = 0
|
||||
element_counts = layer_data.getElementCounts()
|
||||
for layer, counts in element_counts.items():
|
||||
if layer + self._solid_layers > self._current_layer_num:
|
||||
break
|
||||
end += counts
|
||||
|
||||
# This uses glDrawRangeElements internally to only draw a certain range of lines.
|
||||
renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end))
|
||||
|
||||
if self._current_layer_mesh:
|
||||
renderer.queueNode(node, mesh = self._current_layer_mesh)
|
||||
|
||||
if self._current_layer_jumps:
|
||||
renderer.queueNode(node, mesh = self._current_layer_jumps)
|
||||
renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
|
||||
|
||||
def setLayer(self, value):
|
||||
if self._current_layer_num != value:
|
||||
|
@ -163,7 +154,7 @@ class LayerView(View):
|
|||
|
||||
# The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
|
||||
# if it's the largest value. If we don't do this, we can have a slider block outside of the
|
||||
# slider.
|
||||
# slider.
|
||||
if new_max_layers > self._current_layer_num:
|
||||
self.maxLayersChanged.emit()
|
||||
self.setLayer(int(self._max_layers))
|
||||
|
@ -176,7 +167,7 @@ class LayerView(View):
|
|||
currentLayerNumChanged = Signal()
|
||||
|
||||
## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
|
||||
# as this caused some issues.
|
||||
# as this caused some issues.
|
||||
def getProxy(self, engine, script_engine):
|
||||
return self._proxy
|
||||
|
||||
|
@ -195,15 +186,32 @@ class LayerView(View):
|
|||
return True
|
||||
|
||||
if event.type == Event.ViewActivateEvent:
|
||||
# Make sure the LayerPass is created
|
||||
self.getLayerPass()
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
self._onGlobalStackChanged()
|
||||
|
||||
if not self._layerview_composite_shader:
|
||||
self._layerview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layerview_composite.shader"))
|
||||
|
||||
if not self._composite_pass:
|
||||
self._composite_pass = self.getRenderer().getRenderPass("composite")
|
||||
|
||||
self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later
|
||||
self._composite_pass.getLayerBindings().append("layerview")
|
||||
self._old_composite_shader = self._composite_pass.getCompositeShader()
|
||||
self._composite_pass.setCompositeShader(self._layerview_composite_shader)
|
||||
|
||||
elif event.type == Event.ViewDeactivateEvent:
|
||||
self._wireprint_warning_message.hide()
|
||||
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
|
||||
self._composite_pass.setLayerBindings(self._old_layer_bindings)
|
||||
self._composite_pass.setCompositeShader(self._old_composite_shader)
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
|
@ -315,3 +323,4 @@ class _CreateTopLayersJob(Job):
|
|||
def cancel(self):
|
||||
self._cancel = True
|
||||
super().cancel()
|
||||
|
||||
|
|
35
plugins/LayerView/layers.shader
Normal file
35
plugins/LayerView/layers.shader
Normal file
|
@ -0,0 +1,35 @@
|
|||
[shaders]
|
||||
vertex =
|
||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
||||
uniform lowp float u_active_extruder;
|
||||
uniform lowp float u_shade_factor;
|
||||
|
||||
attribute highp vec4 a_vertex;
|
||||
attribute lowp vec4 a_color;
|
||||
varying lowp vec4 v_color;
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
||||
// shade the color depending on the extruder index stored in the alpha component of the color
|
||||
v_color = (a_color.a == u_active_extruder) ? a_color : a_color * u_shade_factor;
|
||||
v_color.a = 1.0;
|
||||
}
|
||||
|
||||
fragment =
|
||||
varying lowp vec4 v_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = v_color;
|
||||
}
|
||||
|
||||
[defaults]
|
||||
u_active_extruder = 0.0
|
||||
u_shade_factor = 0.60
|
||||
|
||||
[bindings]
|
||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
||||
|
||||
[attributes]
|
||||
a_vertex = vertex
|
||||
a_color = color
|
78
plugins/LayerView/layerview_composite.shader
Normal file
78
plugins/LayerView/layerview_composite.shader
Normal file
|
@ -0,0 +1,78 @@
|
|||
[shaders]
|
||||
vertex =
|
||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
||||
attribute highp vec4 a_vertex;
|
||||
attribute highp vec2 a_uvs;
|
||||
|
||||
varying highp vec2 v_uvs;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
||||
v_uvs = a_uvs;
|
||||
}
|
||||
|
||||
fragment =
|
||||
uniform sampler2D u_layer0;
|
||||
uniform sampler2D u_layer1;
|
||||
uniform sampler2D u_layer2;
|
||||
|
||||
uniform vec2 u_offset[9];
|
||||
|
||||
uniform vec4 u_background_color;
|
||||
uniform float u_outline_strength;
|
||||
uniform vec4 u_outline_color;
|
||||
|
||||
varying vec2 v_uvs;
|
||||
|
||||
float kernel[9];
|
||||
|
||||
const vec3 x_axis = vec3(1.0, 0.0, 0.0);
|
||||
const vec3 y_axis = vec3(0.0, 1.0, 0.0);
|
||||
const vec3 z_axis = vec3(0.0, 0.0, 1.0);
|
||||
|
||||
void main()
|
||||
{
|
||||
kernel[0] = 0.0; kernel[1] = 1.0; kernel[2] = 0.0;
|
||||
kernel[3] = 1.0; kernel[4] = -4.0; kernel[5] = 1.0;
|
||||
kernel[6] = 0.0; kernel[7] = 1.0; kernel[8] = 0.0;
|
||||
|
||||
vec4 result = u_background_color;
|
||||
|
||||
vec4 main_layer = texture2D(u_layer0, v_uvs);
|
||||
vec4 selection_layer = texture2D(u_layer1, v_uvs);
|
||||
vec4 layerview_layer = texture2D(u_layer2, v_uvs);
|
||||
|
||||
result = main_layer * main_layer.a + result * (1.0 - main_layer.a);
|
||||
result = layerview_layer * layerview_layer.a + result * (1.0 - layerview_layer.a);
|
||||
|
||||
vec4 sum = vec4(0.0);
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
vec4 color = vec4(texture2D(u_layer1, v_uvs.xy + u_offset[i]).a);
|
||||
sum += color * (kernel[i] / u_outline_strength);
|
||||
}
|
||||
|
||||
if((selection_layer.rgb == x_axis || selection_layer.rgb == y_axis || selection_layer.rgb == z_axis))
|
||||
{
|
||||
gl_FragColor = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
gl_FragColor = mix(result, u_outline_color, abs(sum.a));
|
||||
}
|
||||
}
|
||||
|
||||
[defaults]
|
||||
u_layer0 = 0
|
||||
u_layer1 = 1
|
||||
u_layer2 = 2
|
||||
u_background_color = [0.965, 0.965, 0.965, 1.0]
|
||||
u_outline_strength = 1.0
|
||||
u_outline_color = [0.05, 0.66, 0.89, 1.0]
|
||||
|
||||
[bindings]
|
||||
|
||||
[attributes]
|
||||
a_vertex = vertex
|
||||
a_uvs = uv
|
|
@ -66,8 +66,14 @@ class LegacyProfileReader(ProfileReader):
|
|||
def read(self, file_name):
|
||||
if file_name.split(".")[-1] != "ini":
|
||||
return None
|
||||
|
||||
multi_extrusion = Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") > 1
|
||||
if multi_extrusion:
|
||||
Logger.log("e", "Unable to import legacy profile %s. Multi extrusion is not supported", file_name)
|
||||
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.
|
||||
profile = InstanceContainer("Imported Legacy Profile") # Create an empty profile.
|
||||
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
try:
|
||||
|
@ -130,5 +136,6 @@ 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.")
|
||||
profile.setDirty(True)
|
||||
profile.addMetaDataEntry("type", "quality")
|
||||
profile.addMetaDataEntry("type", "quality_changes")
|
||||
profile.addMetaDataEntry("quality", "normal")
|
||||
return profile
|
|
@ -147,8 +147,16 @@ Cura.MachineAction
|
|||
|
||||
ComboBox
|
||||
{
|
||||
model: ["RepRap (Marlin/Sprinter)", "UltiGCode"]
|
||||
currentIndex: machineGCodeFlavorProvider.properties.value != model[1] ? 0 : 1
|
||||
model: ["RepRap (Marlin/Sprinter)", "UltiGCode", "Repetier"]
|
||||
currentIndex:
|
||||
{
|
||||
var index = model.indexOf(machineGCodeFlavorProvider.properties.value);
|
||||
if(index == -1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
return index
|
||||
}
|
||||
onActivated:
|
||||
{
|
||||
machineGCodeFlavorProvider.setPropertyValue("value", model[index]);
|
||||
|
|
|
@ -55,21 +55,30 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
|
|||
|
||||
# Add all instances that are not added, but are in visibility list
|
||||
for item in visible:
|
||||
if not settings.getInstance(item):
|
||||
if not settings.getInstance(item): # Setting was not added already.
|
||||
definition = self._stack.getSettingDefinition(item)
|
||||
if definition:
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
stack_nr = -1
|
||||
if definition.limit_to_extruder and self._stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
#Obtain the value from the correct container stack. Only once, upon adding the setting.
|
||||
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder"))))) #Stack to get the setting from. Round it and remove the fractional part.
|
||||
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value"): #Property not defined, but we have an extruder number.
|
||||
stack_nr = str(int(round(float(self._stack.getProperty("extruder_nr", "value")))))
|
||||
if stack_nr in ExtruderManager.getInstance().extruderIds: #We have either a limit_to_extruder or an extruder_nr.
|
||||
stack = None
|
||||
# Check from what stack we should copy the raw property of the setting from.
|
||||
if definition.limit_to_extruder != "-1" and self._stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
|
||||
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
|
||||
|
||||
# Check if the found stack_number is in the extruder list of extruders.
|
||||
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None:
|
||||
stack_nr = -1
|
||||
|
||||
# Use the found stack number to get the right stack to copy the value from.
|
||||
if stack_nr in ExtruderManager.getInstance().extruderIds:
|
||||
stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
|
||||
|
||||
# Use the raw property to set the value (so the inheritance doesn't break)
|
||||
if stack is not None:
|
||||
new_instance.setProperty("value", stack.getRawProperty(item, "value"))
|
||||
else:
|
||||
stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
new_instance.setProperty("value", stack.getRawProperty(item, "value"))
|
||||
new_instance.setProperty("value", None)
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
visibility_changed = True
|
||||
|
|
|
@ -31,7 +31,7 @@ Item {
|
|||
spacing: UM.Theme.getSize("default_margin").width
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Print model with")
|
||||
text: catalog.i18nc("@label Followed by extruder selection drop-down.", "Print model with")
|
||||
anchors.verticalCenter: extruderSelector.verticalCenter
|
||||
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
|
@ -151,10 +151,11 @@ Item {
|
|||
|
||||
Column
|
||||
{
|
||||
spacing: UM.Theme.getSize("default_lining").height
|
||||
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
|
||||
// It kinda looks ugly otherwise (big panel, no content on it)
|
||||
height: contents.count * UM.Theme.getSize("section").height < 200 ? contents.count * UM.Theme.getSize("section").height : 200
|
||||
property int maximumHeight: 200 * Screen.devicePixelRatio
|
||||
height: Math.min(contents.count * (UM.Theme.getSize("section").height + UM.Theme.getSize("default_lining").height), maximumHeight)
|
||||
|
||||
ScrollView
|
||||
{
|
||||
height: parent.height
|
||||
|
@ -163,6 +164,7 @@ Item {
|
|||
ListView
|
||||
{
|
||||
id: contents
|
||||
spacing: UM.Theme.getSize("default_lining").height
|
||||
|
||||
model: UM.SettingDefinitionsModel
|
||||
{
|
||||
|
@ -260,6 +262,14 @@ Item {
|
|||
storeIndex: 0
|
||||
removeUnusedValue: false
|
||||
}
|
||||
|
||||
// If the extruder by which the object needs to be printed is changed, ensure that the
|
||||
// display is also notified of the fact.
|
||||
Connections
|
||||
{
|
||||
target: extruderSelector
|
||||
onActivated: provider.forcePropertiesChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ from UM.Application import Application
|
|||
from UM.Preferences import Preferences
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from UM.Event import Event
|
||||
|
||||
|
||||
## This tool allows the user to add & change settings per node in the scene.
|
||||
|
@ -31,6 +32,9 @@ class PerObjectSettingsTool(Tool):
|
|||
self._onGlobalContainerChanged()
|
||||
|
||||
def event(self, event):
|
||||
super().event(event)
|
||||
if event.type == Event.MousePressEvent and self._controller.getToolsEnabled():
|
||||
self.operationStopped.emit(self)
|
||||
return False
|
||||
|
||||
def getSelectedObjectId(self):
|
||||
|
@ -80,11 +84,20 @@ class PerObjectSettingsTool(Tool):
|
|||
default_stack = ExtruderManager.getInstance().getExtruderStack(0)
|
||||
if default_stack:
|
||||
default_stack_id = default_stack.getId()
|
||||
else: default_stack_id = global_container_stack.getId()
|
||||
else:
|
||||
default_stack_id = global_container_stack.getId()
|
||||
|
||||
root_node = Application.getInstance().getController().getScene().getRoot()
|
||||
for node in DepthFirstIterator(root_node):
|
||||
node.callDecoration("setActiveExtruder", default_stack_id)
|
||||
new_stack_id = default_stack_id
|
||||
# Get position of old extruder stack for this node
|
||||
old_extruder_pos = node.callDecoration("getActiveExtruderPosition")
|
||||
if old_extruder_pos is not None:
|
||||
# Fetch current (new) extruder stack at position
|
||||
new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos)
|
||||
if new_stack:
|
||||
new_stack_id = new_stack.getId()
|
||||
node.callDecoration("setActiveExtruder", new_stack_id)
|
||||
|
||||
self._updateEnabled()
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ class RemovableDriveOutputDevice(OutputDevice):
|
|||
self.setPriority(1)
|
||||
|
||||
self._writing = False
|
||||
self._stream = None
|
||||
|
||||
def requestWrite(self, node, file_name = None, filter_by_machine = False):
|
||||
filter_by_machine = True # This plugin is indended to be used by machine (regardless of what it was told to do)
|
||||
|
@ -65,8 +66,9 @@ class RemovableDriveOutputDevice(OutputDevice):
|
|||
|
||||
try:
|
||||
Logger.log("d", "Writing to %s", file_name)
|
||||
stream = open(file_name, "wt")
|
||||
job = WriteMeshJob(writer, stream, node, MeshWriter.OutputMode.TextMode)
|
||||
# Using buffering greatly reduces the write time for many lines of gcode
|
||||
self._stream = open(file_name, "wt", buffering = 1)
|
||||
job = WriteMeshJob(writer, self._stream, node, MeshWriter.OutputMode.TextMode)
|
||||
job.setFileName(file_name)
|
||||
job.progress.connect(self._onProgress)
|
||||
job.finished.connect(self._onFinished)
|
||||
|
@ -92,6 +94,11 @@ class RemovableDriveOutputDevice(OutputDevice):
|
|||
self.writeProgress.emit(self, progress)
|
||||
|
||||
def _onFinished(self, job):
|
||||
if self._stream:
|
||||
# Explicitly closing the stream flushes the write-buffer
|
||||
self._stream.close()
|
||||
self._stream = None
|
||||
|
||||
if hasattr(job, "_message"):
|
||||
job._message.hide()
|
||||
job._message = None
|
||||
|
@ -113,4 +120,9 @@ class RemovableDriveOutputDevice(OutputDevice):
|
|||
def _onActionTriggered(self, message, action):
|
||||
if action == "eject":
|
||||
if Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("RemovableDriveOutputDevice").ejectDevice(self):
|
||||
message.hide()
|
||||
message.hide()
|
||||
|
||||
eject_message = Message(catalog.i18nc("@info:status", "Ejected {0}. You can now safely remove the drive.").format(self.getName()))
|
||||
else:
|
||||
eject_message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Another program may be using the drive.").format(self.getName()))
|
||||
eject_message.show()
|
||||
|
|
|
@ -46,11 +46,6 @@ class RemovableDrivePlugin(OutputDevicePlugin):
|
|||
|
||||
if result:
|
||||
Logger.log("i", "Succesfully ejected the device")
|
||||
message = Message(catalog.i18nc("@info:status", "Ejected {0}. You can now safely remove the drive.").format(device.getName()))
|
||||
message.show()
|
||||
else:
|
||||
message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Another program may be using the drive.").format(device.getName()))
|
||||
message.show()
|
||||
return result
|
||||
|
||||
def performEjectDevice(self, device):
|
||||
|
|
|
@ -21,6 +21,7 @@ import urllib.request
|
|||
import urllib.parse
|
||||
import ssl
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -61,7 +62,7 @@ class SliceInfoJob(Job):
|
|||
# The data is only sent when the user in question gave permission to do so. All data is anonymous and
|
||||
# no model files are being sent (Just a SHA256 hash of the model).
|
||||
class SliceInfo(Extension):
|
||||
info_url = "http://stats.youmagine.com/curastats/slice"
|
||||
info_url = "https://stats.youmagine.com/curastats/slice"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
@ -101,21 +102,11 @@ class SliceInfo(Extension):
|
|||
print_information = Application.getInstance().getPrintInformation()
|
||||
material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value")
|
||||
|
||||
# TODO: Send material per extruder instead of mashing it on a pile
|
||||
material_used = math.pi * material_radius * material_radius * sum(print_information.materialLengths) #Volume of all materials used
|
||||
# Send material per extruder
|
||||
material_used = [str(math.pi * material_radius * material_radius * material_length) for material_length in print_information.materialLengths]
|
||||
material_used = ",".join(material_used)
|
||||
|
||||
# Bundle the collected data
|
||||
submitted_data = {
|
||||
"processor": platform.processor(),
|
||||
"machine": platform.machine(),
|
||||
"platform": platform.platform(),
|
||||
"settings": global_container_stack.serialize(), # global_container with references on used containers
|
||||
"version": Application.getInstance().getVersion(),
|
||||
"modelhash": modelhash_formatted,
|
||||
"printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601),
|
||||
"filament": material_used,
|
||||
"language": Preferences.getInstance().getValue("general/language"),
|
||||
}
|
||||
containers = { "": global_container_stack.serialize() }
|
||||
for container in global_container_stack.getContainers():
|
||||
container_id = container.getId()
|
||||
try:
|
||||
|
@ -123,12 +114,24 @@ class SliceInfo(Extension):
|
|||
except NotImplementedError:
|
||||
Logger.log("w", "Container %s could not be serialized!", container_id)
|
||||
continue
|
||||
|
||||
if container_serialized:
|
||||
submitted_data["settings_%s" %(container_id)] = container_serialized # This can be anything, eg. INI, JSON, etc.
|
||||
containers[container_id] = container_serialized
|
||||
else:
|
||||
Logger.log("i", "No data found in %s to be serialized!", container_id)
|
||||
|
||||
# Bundle the collected data
|
||||
submitted_data = {
|
||||
"processor": platform.processor(),
|
||||
"machine": platform.machine(),
|
||||
"platform": platform.platform(),
|
||||
"settings": json.dumps(containers), # bundle of containers with their serialized contents
|
||||
"version": Application.getInstance().getVersion(),
|
||||
"modelhash": modelhash_formatted,
|
||||
"printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601),
|
||||
"filament": material_used,
|
||||
"language": Preferences.getInstance().getValue("general/language"),
|
||||
}
|
||||
|
||||
# Convert data to bytes
|
||||
submitted_data = urllib.parse.urlencode(submitted_data)
|
||||
binary_data = submitted_data.encode("utf-8")
|
||||
|
@ -139,4 +142,4 @@ class SliceInfo(Extension):
|
|||
except Exception as e:
|
||||
# We really can't afford to have a mistake here, as this would break the sending of g-code to a device
|
||||
# (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
|
||||
Logger.log("e", "Exception raised while sending slice info: %s" %(repr(e))) # But we should be notified about these problems of course.
|
||||
Logger.log("e", "Exception raised while sending slice info: %s" %(repr(e))) # But we should be notified about these problems of course.
|
||||
|
|
|
@ -71,8 +71,9 @@ class SolidView(View):
|
|||
for node in DepthFirstIterator(scene.getRoot()):
|
||||
if not node.render(renderer):
|
||||
if node.getMeshData() and node.isVisible():
|
||||
|
||||
uniforms = {}
|
||||
shade_factor = 1.0
|
||||
|
||||
if not multi_extrusion:
|
||||
if global_container_stack:
|
||||
material = global_container_stack.findContainer({ "type": "material" })
|
||||
|
@ -87,13 +88,17 @@ class SolidView(View):
|
|||
extruder_index = max(0, self._extruders_model.find("id", extruder_id))
|
||||
|
||||
material_color = self._extruders_model.getItem(extruder_index)["color"]
|
||||
|
||||
if extruder_index != ExtruderManager.getInstance().activeExtruderIndex:
|
||||
# Shade objects that are printed with the non-active extruder 25% darker
|
||||
shade_factor = 0.6
|
||||
try:
|
||||
# Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
|
||||
# an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
|
||||
uniforms["diffuse_color"] = [
|
||||
int(material_color[1:3], 16) / 255,
|
||||
int(material_color[3:5], 16) / 255,
|
||||
int(material_color[5:7], 16) / 255,
|
||||
shade_factor * int(material_color[1:3], 16) / 255,
|
||||
shade_factor * int(material_color[3:5], 16) / 255,
|
||||
shade_factor * int(material_color[5:7], 16) / 255,
|
||||
1.0
|
||||
]
|
||||
except ValueError:
|
||||
|
|
|
@ -2,9 +2,13 @@
|
|||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
import UM.VersionUpgrade #To indicate that a file is of incorrect format.
|
||||
import UM.VersionUpgradeManager #To schedule more files to be upgraded.
|
||||
import UM.Resources #To get the config storage path.
|
||||
|
||||
import configparser #To read config files.
|
||||
import io #To write config files to strings as if they were files.
|
||||
import os.path #To get the path to write new user profiles to.
|
||||
import urllib #To serialise the user container file name properly.
|
||||
|
||||
## Creates a new machine instance instance by parsing a serialised machine
|
||||
# instance in version 1 of the file format.
|
||||
|
@ -84,16 +88,35 @@ class MachineInstance:
|
|||
active_quality_changes = "empty_quality_changes"
|
||||
else:
|
||||
active_quality = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.getQualityFallback(type_name, variant, active_material)
|
||||
if has_machine_qualities: #Then the profile will have split into multiple.
|
||||
active_quality_changes = self._active_profile_name + "_" + active_material + "_" + variant
|
||||
else:
|
||||
active_quality_changes = self._active_profile_name
|
||||
active_quality_changes = self._active_profile_name
|
||||
|
||||
if has_machine_qualities: #This machine now has machine-quality profiles.
|
||||
active_material += "_" + variant_materials #That means that the profile was split into multiple.
|
||||
active_material += "_" + variant_materials
|
||||
|
||||
#Create a new user profile and schedule it to be upgraded.
|
||||
user_profile = configparser.ConfigParser(interpolation = None)
|
||||
user_profile["general"] = {
|
||||
"version": "2",
|
||||
"name": "Current settings",
|
||||
"definition": type_name
|
||||
}
|
||||
user_profile["metadata"] = {
|
||||
"type": "user",
|
||||
"machine": self._name
|
||||
}
|
||||
user_profile["values"] = {}
|
||||
|
||||
version_upgrade_manager = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance()
|
||||
user_storage = os.path.join(UM.Resources.getDataStoragePath(), next(iter(version_upgrade_manager.getStoragePaths("user"))))
|
||||
user_profile_file = os.path.join(user_storage, urllib.parse.quote_plus(self._name) + "_current_settings.inst.cfg")
|
||||
if not os.path.exists(user_storage):
|
||||
os.makedirs(user_storage)
|
||||
with open(user_profile_file, "w") as file_handle:
|
||||
user_profile.write(file_handle)
|
||||
version_upgrade_manager.upgradeExtraFile(user_storage, urllib.parse.quote_plus(self._name), "user")
|
||||
|
||||
containers = [
|
||||
"", #The current profile doesn't know the definition ID when it was upgraded, only the instance ID, so it will be invalid. Sorry, your current settings are lost now.
|
||||
self._name + "_current_settings", #The current profile doesn't know the definition ID when it was upgraded, only the instance ID, so it will be invalid. Sorry, your current settings are lost now.
|
||||
active_quality_changes,
|
||||
active_quality,
|
||||
active_material,
|
||||
|
|
|
@ -49,7 +49,7 @@ class Profile:
|
|||
self._machine_type_id = parser.get("general", "machine_type", fallback = None)
|
||||
self._machine_variant_name = parser.get("general", "machine_variant", fallback = None)
|
||||
self._machine_instance_name = parser.get("general", "machine_instance", fallback = None)
|
||||
if "material" in parser["general"]:
|
||||
if "material" in parser["general"]: #Note: Material name is unused in this upgrade.
|
||||
self._material_name = parser.get("general", "material")
|
||||
elif self._type == "material":
|
||||
self._material_name = parser.get("general", "name", fallback = None)
|
||||
|
@ -94,7 +94,7 @@ class Profile:
|
|||
config.set("general", "definition", "fdmprinter") #In this case, the machine definition is unknown, and it might now have machine-specific profiles, in which case this will fail.
|
||||
|
||||
config.add_section("metadata")
|
||||
config.set("metadata", "quality", "normal") #This feature doesn't exist in 2.1 yet, so we don't know the actual quality type. For now, always base it on normal.
|
||||
config.set("metadata", "quality_type", "normal") #This feature doesn't exist in 2.1 yet, so we don't know the actual quality type. For now, always base it on normal.
|
||||
config.set("metadata", "type", "quality_changes")
|
||||
if self._weight:
|
||||
config.set("metadata", "weight", str(self._weight))
|
||||
|
@ -105,13 +105,13 @@ class Profile:
|
|||
config.set("metadata", "variant", self._machine_variant_name)
|
||||
|
||||
if self._settings:
|
||||
VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateSettings(self._settings)
|
||||
self._settings = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateSettings(self._settings)
|
||||
config.add_section("values")
|
||||
for key, value in self._settings.items():
|
||||
config.set("values", key, str(value))
|
||||
|
||||
if self._changed_settings_defaults:
|
||||
VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateSettings(self._changed_settings_defaults)
|
||||
self._changed_settings_defaults = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateSettings(self._changed_settings_defaults)
|
||||
config.add_section("defaults")
|
||||
for key, value in self._changed_settings_defaults.items():
|
||||
config.set("defaults", key, str(value))
|
||||
|
@ -124,34 +124,6 @@ class Profile:
|
|||
for item in disabled_settings_defaults[1:]:
|
||||
disabled_defaults_string += "," + str(item)
|
||||
|
||||
#Material metadata may cause the file to split, so do it last to minimise processing time (do more with the copy).
|
||||
filenames = []
|
||||
configs = []
|
||||
if self._material_name and self._type != "material":
|
||||
config.set("metadata", "material", self._material_name)
|
||||
filenames.append(self._filename)
|
||||
configs.append(config)
|
||||
elif self._type != "material" and self._machine_type_id in VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.machinesWithMachineQuality():
|
||||
#Split this profile into multiple profiles, one for each material.
|
||||
_new_materials = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.machinesWithMachineQuality()[self._machine_type_id]["materials"]
|
||||
_new_variants = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.machinesWithMachineQuality()[self._machine_type_id]["variants"]
|
||||
translated_machine = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translatePrinter(self._machine_type_id)
|
||||
for material_id in _new_materials:
|
||||
for variant_id in _new_variants:
|
||||
variant_id_new = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateVariant(variant_id, translated_machine)
|
||||
filenames.append("{profile}_{material}_{variant}".format(profile = self._filename, material = material_id, variant = variant_id_new))
|
||||
config_copy = configparser.ConfigParser(interpolation = None)
|
||||
config_copy.read_dict(config) #Copy the config to a new ConfigParser instance.
|
||||
variant_id_new_materials = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateVariantForMaterials(variant_id, translated_machine)
|
||||
config_copy.set("metadata", "material", "{material}_{variant}".format(material = material_id, variant = variant_id_new_materials))
|
||||
configs.append(config_copy)
|
||||
else:
|
||||
configs.append(config)
|
||||
filenames.append(self._filename)
|
||||
|
||||
outputs = []
|
||||
for config in configs:
|
||||
output = io.StringIO()
|
||||
config.write(output)
|
||||
outputs.append(output.getvalue())
|
||||
return filenames, outputs
|
||||
output = io.StringIO()
|
||||
config.write(output)
|
||||
return [self._filename], [output.getvalue()]
|
|
@ -398,6 +398,9 @@ class VersionUpgrade21to22(VersionUpgrade):
|
|||
if key == "retraction_combing": #Combing was made into an enum instead of a boolean.
|
||||
new_settings[key] = "off" if (value == "False") else "all"
|
||||
continue
|
||||
if key == "cool_fan_full_layer": #Layer counting was made one-indexed.
|
||||
new_settings[key] = str(int(value) + 1)
|
||||
continue
|
||||
if key in _setting_name_translations:
|
||||
new_settings[_setting_name_translations[key]] = value
|
||||
continue
|
||||
|
|
|
@ -35,6 +35,10 @@ def getMetaData():
|
|||
"preferences": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"."}
|
||||
},
|
||||
"user": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./user"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
916
plugins/X3DReader/X3DReader.py
Normal file
916
plugins/X3DReader/X3DReader.py
Normal file
|
@ -0,0 +1,916 @@
|
|||
# Contributed by Seva Alekseyev <sevaa@nih.gov> with National Institutes of Health, 2016
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Mesh.MeshReader import MeshReader
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Job import Job
|
||||
from math import pi, sin, cos, sqrt
|
||||
import numpy
|
||||
|
||||
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
|
||||
|
||||
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.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
|
||||
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
|
||||
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]))
|
||||
builder.setIndices(numpy.concatenate([shape.faces for shape in self.shapes]))
|
||||
builder.calculateNormals()
|
||||
builder.setFileName(file_name)
|
||||
mesh_data = builder.build()
|
||||
|
||||
# Manually try and get the extents of the mesh_data. This should prevent nasty NaN issues from
|
||||
# leaving the reader.
|
||||
mesh_data.getExtents()
|
||||
|
||||
node = SceneNode()
|
||||
node.setMeshData(mesh_data)
|
||||
node.setSelectable(True)
|
||||
node.setName(file_name)
|
||||
|
||||
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)
|
||||
if tag == "CADPart":
|
||||
self.processTransform(xml_node) # TODO: split the parts
|
||||
elif tag == "LOD":
|
||||
self.processNode(xml_node[0])
|
||||
elif tag == "Transform":
|
||||
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
|
||||
for sub_node in xml_node:
|
||||
if sub_node.tag == "Appearance" and not appearance:
|
||||
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...
|
||||
if not geometry is None:
|
||||
try:
|
||||
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.
|
||||
# Big caveat: XML element objects may evaluate to boolean False!!!
|
||||
# Don't ever use "if node:", use "if not node is None:" instead
|
||||
def resolveDefUse(self, node):
|
||||
USE = node.attrib.get("USE")
|
||||
if USE:
|
||||
return self.defs.get(USE, None)
|
||||
|
||||
DEF = node.attrib.get("DEF")
|
||||
if DEF:
|
||||
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.
|
||||
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
|
||||
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:
|
||||
T.translate(trans)
|
||||
if got_center:
|
||||
T.translate(center)
|
||||
if rot[0] != 0:
|
||||
T.rotateByAxis(*rot)
|
||||
if scale.x != 1 or scale.y != 1 or scale.z != 1:
|
||||
got_scale_orient = scale_orient[0] != 0
|
||||
if got_scale_orient:
|
||||
T.rotateByAxis(*scale_orient)
|
||||
# No scale by vector in place operation in UM
|
||||
S = Matrix()
|
||||
S.setByScaleVector(scale)
|
||||
T.multiply(S)
|
||||
if got_scale_orient:
|
||||
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):
|
||||
(dx, dy, dz) = readFloatArray(node, "size", [2, 2, 2])
|
||||
dx /= 2
|
||||
dy /= 2
|
||||
dz /= 2
|
||||
self.reserveFaceAndVertexCount(12, 8)
|
||||
|
||||
# xz plane at +y, ccw
|
||||
self.addVertex(dx, dy, dz)
|
||||
self.addVertex(-dx, dy, dz)
|
||||
self.addVertex(-dx, dy, -dz)
|
||||
self.addVertex(dx, dy, -dz)
|
||||
# xz plane at -y
|
||||
self.addVertex(dx, -dy, dz)
|
||||
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)
|
||||
subdiv = readIntArray(node, "subdivision", None)
|
||||
if subdiv:
|
||||
if len(subdiv) == 1:
|
||||
nr = ns = subdiv[0]
|
||||
else:
|
||||
(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):
|
||||
for seg in range(ns):
|
||||
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)
|
||||
# Bottom cap goes: up left down (starting from -y pole)
|
||||
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):
|
||||
tvb = 2 + ring * ns
|
||||
# First vertex index for the top edge of the ring
|
||||
bvb = tvb + ns
|
||||
# First vertex index for the bottom edge of the ring
|
||||
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):
|
||||
self.addTri(1 + (i + 1) % n, 0, 1 + i)
|
||||
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)
|
||||
bottom = readBoolean(node, "bottom", True)
|
||||
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):
|
||||
rs = -r * sin(angle * i)
|
||||
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):
|
||||
dx = readFloat(node, "xSpacing", 1)
|
||||
dz = readFloat(node, "zSpacing", 1)
|
||||
nx = readInt(node, "xDimension", 0)
|
||||
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)
|
||||
end_cap = readBoolean(node, "endCap", True)
|
||||
cross = readFloatArray(node, "crossSection", (1, 1, 1, -1, -1, -1, -1, 1, 1, 1))
|
||||
cross = [(cross[i], cross[i+1]) for i in range(0, len(cross), 2)]
|
||||
spine = readFloatArray(node, "spine", (0, 0, 0, 0, 1, 0))
|
||||
spine = [(spine[i], spine[i+1], spine[i+2]) for i in range(0, len(spine), 3)]
|
||||
orient = readFloatArray(node, "orientation", None)
|
||||
if orient:
|
||||
# This converts X3D's axis/angle rotation to a 3x3 numpy matrix
|
||||
def toRotationMatrix(rot):
|
||||
(x, y, z) = 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)))
|
||||
|
||||
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
|
||||
# the first one where necessary.
|
||||
crossClosed = cross[0] == cross[-1]
|
||||
if crossClosed:
|
||||
cross = cross[:-1]
|
||||
nc = len(cross)
|
||||
cross = [numpy.array((c[0], 0, c[1])) for c in cross]
|
||||
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,
|
||||
# it would kick in on the first iteration of the main loop by spine.
|
||||
def findFirstAngleNormal():
|
||||
for i in range(1, ns - 1):
|
||||
spt = spine[i]
|
||||
z = (spine[i + 1] - spt).cross(spine[i - 1] - spt)
|
||||
if z.length() > EPSILON:
|
||||
return z
|
||||
# All the spines are collinear. Fallback to the rotated source
|
||||
# XZ plane.
|
||||
# TODO: handle the situation where the first two spine points match
|
||||
if len(spine) < 2:
|
||||
return Vector(0, 0, 1)
|
||||
v = spine[1] - spine[0]
|
||||
orig_y = Vector(0, 1, 0)
|
||||
orig_z = Vector(0, 0, 1)
|
||||
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()
|
||||
s = a.length()/v.length()
|
||||
c = sqrt(1-s*s)
|
||||
t = 1-c
|
||||
m = 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)))
|
||||
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
|
||||
for i, spt in enumerate(spine):
|
||||
if (i > 0 and i < ns - 1) or spine_closed:
|
||||
snext = spine[(i + 1) % ns]
|
||||
sprev = spine[(i - 1 + ns) % ns]
|
||||
y = snext - sprev
|
||||
vnext = snext - spt
|
||||
vprev = sprev - spt
|
||||
try_z = vnext.cross(vprev)
|
||||
# Might be zero, then all kinds of fallback
|
||||
if try_z.length() > EPSILON:
|
||||
if z is not None and try_z.dot(z) < 0:
|
||||
try_z = -try_z
|
||||
z = try_z
|
||||
elif not z: # No z, and no previous z.
|
||||
# Look ahead, see if there's at least one point where
|
||||
# spines are not collinear.
|
||||
z = findFirstAngleNormal()
|
||||
elif i == 0: # And non-crossed
|
||||
snext = spine[i + 1]
|
||||
y = snext - spt
|
||||
z = findFirstAngleNormal()
|
||||
else: # last point and not crossed
|
||||
sprev = spine[i - 1]
|
||||
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
|
||||
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):
|
||||
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):
|
||||
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)
|
||||
self.addVertex(outerRadius*c, outerRadius*s, 0)
|
||||
if innerRadius:
|
||||
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)
|
||||
self.addVertex(-x/2, -y/2, 0)
|
||||
self.addVertex(x/2, -y/2, 0)
|
||||
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;
|
||||
verts = [(verts[i], verts[i+1], 0) for i in range(0, 6 * num_faces, 2)]
|
||||
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,
|
||||
"IndexedTriangleStripSet": processGeometryIndexedTriangleStripSet,
|
||||
"IndexedTriangleFanSet": processGeometryIndexedTriangleFanSet,
|
||||
"TriangleSet": processGeometryTriangleSet,
|
||||
"TriangleStripSet": processGeometryTriangleStripSet,
|
||||
"TriangleFanSet": processGeometryTriangleFanSet,
|
||||
"QuadSet": processGeometryQuadSet,
|
||||
"IndexedQuadSet": processGeometryIndexedQuadSet,
|
||||
"TriangleSet2D": processGeometryTriangleSet2D,
|
||||
"Rectangle2D": processGeometryRectangle2D,
|
||||
"Disk2D": processGeometryDisk2D,
|
||||
"ElevationGrid": processGeometryElevationGrid,
|
||||
"Extrusion": processGeometryExtrusion,
|
||||
"Sphere": processGeometrySphere,
|
||||
"Box": processGeometryBox,
|
||||
"Cylinder": processGeometryCylinder,
|
||||
"Cone": processGeometryCone
|
||||
}
|
||||
|
||||
# Parses the Coordinate.@point field, fills the verts array.
|
||||
def readVertices(self, node):
|
||||
for c in node:
|
||||
if c.tag == "Coordinate":
|
||||
c = self.resolveDefUse(c)
|
||||
if not c is None:
|
||||
pt = c.attrib.get("point")
|
||||
if pt:
|
||||
co = [float(x) for x in pt.split()]
|
||||
num_verts = len(co) // 3
|
||||
self.verts = numpy.empty((4, num_verts), dtype=numpy.float32)
|
||||
self.verts[3,:] = numpy.ones((num_verts), dtype=numpy.float32)
|
||||
# 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)
|
||||
|
||||
|
||||
# Arbitrary polygon triangulation.
|
||||
# Doesn't assume convexity and doesn't check the "convex" flag in the file.
|
||||
# Works by the "cutting of ears" algorithm:
|
||||
# - Find an outer vertex with the smallest angle and no vertices inside its adjacent triangle
|
||||
# - Remove the triangle at that vertex
|
||||
# - Repeat until done
|
||||
# Vertex coordinates are supposed to be already set
|
||||
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
|
||||
while n > 3:
|
||||
max_cos = EPSILON # We don't want to check anything on Pi angles
|
||||
i_min = 0 # max cos corresponds to min angle
|
||||
for i in range(n):
|
||||
inext = (i + 1) % n
|
||||
iprev = (i + n - 1) % n
|
||||
v = face[vi[i]]
|
||||
next = face[vi[inext]] - v
|
||||
prev = face[vi[iprev]] - v
|
||||
nextXprev = next.cross(prev)
|
||||
if nextXprev.dot(normal) > EPSILON: # If it's an inner angle
|
||||
cos = next.dot(prev) / (next.length() * prev.length())
|
||||
if cos > max_cos:
|
||||
# Check if there are vertices inside the triangle
|
||||
no_points_inside = True
|
||||
for j in range(n):
|
||||
if j != i and j != iprev and j != inext:
|
||||
vx = face[vi[j]] - v
|
||||
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
|
||||
# ------------------------------------------------------------
|
||||
def readFloatArray(node, attr, default):
|
||||
s = node.attrib.get(attr)
|
||||
if not s:
|
||||
return default
|
||||
return [float(x) for x in s.split()]
|
||||
|
||||
def readIntArray(node, attr, default):
|
||||
s = node.attrib.get(attr)
|
||||
if not s:
|
||||
return default
|
||||
return [int(x, 0) for x in s.split()]
|
||||
|
||||
def readFloat(node, attr, default):
|
||||
s = node.attrib.get(attr)
|
||||
if not s:
|
||||
return default
|
||||
return float(s)
|
||||
|
||||
def readInt(node, attr, default):
|
||||
s = node.attrib.get(attr)
|
||||
if not s:
|
||||
return default
|
||||
return int(s, 0)
|
||||
|
||||
def readBoolean(node, attr, default):
|
||||
s = node.attrib.get(attr)
|
||||
if not s:
|
||||
return default
|
||||
return s.lower() == "true"
|
||||
|
||||
def readVector(node, attr, default):
|
||||
v = readFloatArray(node, attr, default)
|
||||
return Vector(v[0], v[1], v[2])
|
||||
|
||||
def readRotation(node, attr, default):
|
||||
v = readFloatArray(node, attr, default)
|
||||
return (v[3], Vector(v[0], v[1], v[2]))
|
||||
|
||||
# Returns the -1-separated runs
|
||||
def readIndex(node, attr):
|
||||
v = readIntArray(node, attr, [])
|
||||
chunks = []
|
||||
chunk = []
|
||||
for i in range(len(v)):
|
||||
if v[i] == -1:
|
||||
if chunk:
|
||||
chunks.append(chunk)
|
||||
chunk = []
|
||||
else:
|
||||
chunk.append(v[i])
|
||||
if chunk:
|
||||
chunks.append(chunk)
|
||||
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):
|
||||
n = len(face)
|
||||
for i in range(n):
|
||||
for j in range(i+1, n):
|
||||
edge = face[j] - face[i]
|
||||
if edge.length() > EPSILON:
|
||||
edge = edge.normalized()
|
||||
prev_rejection = Vector()
|
||||
is_outer = True
|
||||
for k in range(n):
|
||||
if k != i and k != j:
|
||||
pt = face[k] - face[i]
|
||||
pte = pt.dot(edge)
|
||||
rejection = pt - edge*pte
|
||||
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
|
||||
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.
|
||||
# No error handling.
|
||||
# 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
|
||||
|
||||
def pointInsideTriangle(vx, next, prev, nextXprev):
|
||||
vxXprev = vx.cross(prev)
|
||||
r = ratio(vxXprev, nextXprev)
|
||||
if r < 0:
|
||||
return False
|
||||
vxXnext = vx.cross(next);
|
||||
s = -ratio(vxXnext, nextXprev)
|
||||
return s > 0 and (s + r) < 1
|
||||
|
26
plugins/X3DReader/__init__.py
Normal file
26
plugins/X3DReader/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Seva Alekseyev with National Institutes of Health, 2016
|
||||
|
||||
from . import X3DReader
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "X3D Reader"),
|
||||
"author": "Seva Alekseyev",
|
||||
"version": "0.5",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides support for reading X3D files."),
|
||||
"api": 3
|
||||
},
|
||||
"mesh_reader": [
|
||||
{
|
||||
"extension": "x3d",
|
||||
"description": catalog.i18nc("@item:inlistbox", "X3D File")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def register(app):
|
||||
return { "mesh_reader": X3DReader.X3DReader() }
|
|
@ -16,10 +16,6 @@ fragment =
|
|||
uniform sampler2D u_layer0;
|
||||
uniform sampler2D u_layer1;
|
||||
uniform sampler2D u_layer2;
|
||||
uniform sampler2D u_layer3;
|
||||
|
||||
uniform float u_imageWidth;
|
||||
uniform float u_imageHeight;
|
||||
|
||||
uniform vec2 u_offset[9];
|
||||
|
||||
|
@ -62,7 +58,6 @@ fragment =
|
|||
u_layer0 = 0
|
||||
u_layer1 = 1
|
||||
u_layer2 = 2
|
||||
u_layer3 = 3
|
||||
u_outline_strength = 1.0
|
||||
u_outline_color = [0.05, 0.66, 0.89, 1.0]
|
||||
u_error_color = [1.0, 0.0, 0.0, 1.0]
|
||||
|
|
|
@ -189,7 +189,8 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
try:
|
||||
product = UM.Dictionary.findKey(self.__product_id_map, definition_id)
|
||||
except ValueError:
|
||||
continue
|
||||
# An unknown product id; export it anyway
|
||||
product = definition_id
|
||||
|
||||
builder.start("machine")
|
||||
builder.start("machine_identifier", { "manufacturer": definition.getMetaDataEntry("manufacturer", ""), "product": product})
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"default_value": false
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap"
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
},
|
||||
"layer_height": {
|
||||
"default_value": 0.2
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"default_value": false
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap"
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
},
|
||||
"layer_height": {
|
||||
"default_value": 0.2
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"default_value": false
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap"
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
},
|
||||
"layer_height": {
|
||||
"default_value": 0.2
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"default_value": false
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap"
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
},
|
||||
"material_print_temperature": {
|
||||
"default_value": 210
|
||||
|
|
|
@ -177,7 +177,8 @@
|
|||
"minimum_value_warning": "machine_nozzle_offset_x",
|
||||
"maximum_value": "machine_width",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"enabled": false
|
||||
},
|
||||
"extruder_prime_pos_y":
|
||||
{
|
||||
|
@ -189,7 +190,8 @@
|
|||
"minimum_value_warning": "machine_nozzle_offset_y",
|
||||
"maximum_value_warning": "machine_depth",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
67
resources/definitions/kossel_mini.def.json
Normal file
67
resources/definitions/kossel_mini.def.json
Normal file
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"id": "kossel_mini",
|
||||
"version": 2,
|
||||
"name": "Kossel Mini",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"author": "Claudio Sampaio (Patola)",
|
||||
"manufacturer": "Other",
|
||||
"category": "Other",
|
||||
"file_formats": "text/x-gcode",
|
||||
"icon": "icon_ultimaker2",
|
||||
"platform": "kossel_platform.stl"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_heated_bed": {
|
||||
"default_value": true
|
||||
},
|
||||
"machine_width": {
|
||||
"default_value": 170
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 200
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 170
|
||||
},
|
||||
"machine_center_is_zero": {
|
||||
"default_value": true
|
||||
},
|
||||
"machine_nozzle_size": {
|
||||
"default_value": 0.4
|
||||
},
|
||||
"material_diameter": {
|
||||
"default_value": 1.75
|
||||
},
|
||||
"machine_nozzle_heat_up_speed": {
|
||||
"default_value": 2
|
||||
},
|
||||
"machine_nozzle_cool_down_speed": {
|
||||
"default_value": 2
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 ;Home all axes (max 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\nG28 ;Home all axes (max endstops)\nM84 ;steppers off\nG90 ;absolute positioning"
|
||||
},
|
||||
"machine_disallowed_areas": {
|
||||
"default_value": [
|
||||
[[-34, -85], [ -85, -85], [-70, -70]],
|
||||
[[-85, -85], [-85, -34], [-70, -70]],
|
||||
[[34, -85], [ 85, -85], [70, -70]],
|
||||
[[85, -85], [85, -34], [70, -70]],
|
||||
[[-34, 85], [ -85, 85], [-70, 70]],
|
||||
[[-85, 85], [-85, 34], [-70, 70]],
|
||||
[[34, 85], [ 85, 85], [70, 70]],
|
||||
[[85, 85], [85, 34], [70, 70]]
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@
|
|||
"default_value": 55
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap"
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
},
|
||||
"machine_disallowed_areas": {
|
||||
"default_value": []
|
||||
|
@ -144,7 +144,7 @@
|
|||
"default_value": 15
|
||||
},
|
||||
"adhesion_type": {
|
||||
"default_value": "Raft"
|
||||
"default_value": "raft"
|
||||
},
|
||||
"skirt_brim_minimal_length": {
|
||||
"default_value": 100
|
||||
|
|
|
@ -88,16 +88,13 @@
|
|||
"default_value": 0
|
||||
},
|
||||
"skirt_line_count": {
|
||||
"default_value": 3,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
"default_value": 3
|
||||
},
|
||||
"skirt_gap": {
|
||||
"default_value": 4,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
"default_value": 4
|
||||
},
|
||||
"skirt_brim_minimal_length": {
|
||||
"default_value": 200,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
"default_value": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,16 +91,13 @@
|
|||
"default_value": 0
|
||||
},
|
||||
"skirt_line_count": {
|
||||
"default_value": 3,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
"default_value": 3
|
||||
},
|
||||
"skirt_gap": {
|
||||
"default_value": 4,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
"default_value": 4
|
||||
},
|
||||
"skirt_brim_minimal_length": {
|
||||
"default_value": 200,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
"default_value": 200
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,16 @@
|
|||
},
|
||||
"material_standby_temperature": {
|
||||
"minimum_value": "0"
|
||||
},
|
||||
"extruder_prime_pos_y":
|
||||
{
|
||||
"minimum_value": "0",
|
||||
"maximum_value": "machine_depth"
|
||||
},
|
||||
"extruder_prime_pos_x":
|
||||
{
|
||||
"minimum_value": "0",
|
||||
"maximum_value": "machine_width"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
182
resources/i18n/de/fdmextruder.def.json.po
Normal file
182
resources/i18n/de/fdmextruder.def.json.po
Normal file
|
@ -0,0 +1,182 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Uranium json setting files\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2016-09-20 17:35+0000\n"
|
||||
"PO-Revision-Date: 2016-09-29 13:02+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings label"
|
||||
msgid "Machine"
|
||||
msgstr "Gerät"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings description"
|
||||
msgid "Machine specific settings"
|
||||
msgstr "Gerätespezifische Einstellungen"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr "Extruder"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr "Die für das Drucken verwendete Extruder-Einheit. Diese wird für die Mehrfach-Extrusion benutzt."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr "X-Versatz Düse"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr "Die X-Koordinate des Düsenversatzes."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr "Y-Versatz Düse"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr "Die Y-Koordinate des Düsenversatzes."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr "G-Code Extruder-Start"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr "Starten Sie den G-Code jedes Mal, wenn Sie den Extruder einschalten."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr "Absolute Startposition des Extruders"
|
||||
|
||||
#: 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 "Bevorzugen Sie eine absolute Startposition des Extruders anstelle einer relativen Position zur zuletzt bekannten Kopfposition."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr "X-Position Extruder-Start"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Die X-Koordinate der Startposition beim Einschalten des Extruders."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr "Y-Position Extruder-Start"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Die Y-Koordinate der Startposition beim Einschalten des Extruders."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr "G-Code Extruder-Ende"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr "Beenden Sie den G-Code jedes Mal, wenn Sie den Extruder ausschalten."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr "Absolute Extruder-Endposition"
|
||||
|
||||
#: 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 "Bevorzugen Sie eine absolute Endposition des Extruders anstelle einer relativen Position zur zuletzt bekannten Kopfposition."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr "Extruder-Endposition X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Die X-Koordinate der Endposition beim Ausschalten des Extruders."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr "Extruder-Endposition Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Die Y-Koordinate der Endposition beim Ausschalten des Extruders."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr "Z-Position Extruder-Einzug"
|
||||
|
||||
#: 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 "Die Z-Koordinate der Position, an der die Düse am Druckbeginn einzieht."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr "Druckplattenhaftung"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr "Haftung"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr "X-Position Extruder-Einzug"
|
||||
|
||||
#: 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 "Die X-Koordinate der Position, an der die Düse am Druckbeginn einzieht."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr "Y-Position Extruder-Einzug"
|
||||
|
||||
#: 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 "Die Y-Koordinate der Position, an der die Düse am Druckbeginn einzieht."
|
4028
resources/i18n/de/fdmprinter.def.json.po
Normal file
4028
resources/i18n/de/fdmprinter.def.json.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -953,7 +953,7 @@ msgid "Cancel"
|
|||
msgstr "Cancel"
|
||||
|
||||
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:34
|
||||
msgctxt "@label"
|
||||
msgctxt "@label Followed by extruder selection drop-down."
|
||||
msgid "Print model with"
|
||||
msgstr "Print model with"
|
||||
|
||||
|
@ -1852,12 +1852,12 @@ msgstr ""
|
|||
"Click to make these settings visible."
|
||||
|
||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:59
|
||||
msgctxt "@label"
|
||||
msgctxt "@label Header for list of settings."
|
||||
msgid "Affects"
|
||||
msgstr "Affects"
|
||||
|
||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:64
|
||||
msgctxt "@label"
|
||||
msgctxt "@label Header for list of settings."
|
||||
msgid "Affected By"
|
||||
msgstr "Affected By"
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
182
resources/i18n/es/fdmextruder.def.json.po
Normal file
182
resources/i18n/es/fdmextruder.def.json.po
Normal file
|
@ -0,0 +1,182 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Uranium json setting files\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2016-09-20 17:35+0000\n"
|
||||
"PO-Revision-Date: 2016-09-29 13:02+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\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 "Ajustes específicos de la máquina"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr "Extrusor"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr "El tren extrusor que se utiliza para imprimir. Se emplea en la extrusión múltiple."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr "Desplazamiento de la tobera sobre el eje X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr "Coordenada X del desplazamiento de la tobera."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr "Desplazamiento de la tobera sobre el eje Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr "Coordenada Y del desplazamiento de la tobera."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr "Gcode inicial del extrusor"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr "Gcode inicial que se ejecuta cada vez que se enciende el extrusor."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr "Posición de inicio absoluta del extrusor"
|
||||
|
||||
#: 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 "El extrusor se coloca en la posición de inicio absoluta según la última ubicación conocida del cabezal."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr "Posición de inicio del extrusor sobre el eje X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Coordenada X de la posición de inicio cuando se enciende el extrusor."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr "Posición de inicio del extrusor sobre el eje Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Coordenada Y de la posición de inicio cuando se enciende el extrusor."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr "Gcode final del extrusor"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr "Gcode final que se ejecuta cada vez que se apaga el extrusor."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr "Posición final absoluta del extrusor"
|
||||
|
||||
#: 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 "La posición final del extrusor se considera absoluta, en lugar de relativa a la última ubicación conocida del cabezal."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr "Posición de fin del extrusor sobre el eje X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Coordenada X de la posición de fin cuando se apaga el extrusor."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr "Posición de fin del extrusor sobre el eje Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Coordenada Y de la posición de fin cuando se apaga el extrusor."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr "Posición de preparación del extrusor sobre el eje Z"
|
||||
|
||||
#: 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 "Coordenada Z de la posición en la que la tobera queda preparada al inicio de la impresión."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr "Adherencia de la placa de impresión"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr "Adherencia"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr "Posición de preparación del extrusor sobre el eje X"
|
||||
|
||||
#: 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 "Coordenada X de la posición en la que la tobera se coloca al inicio de la impresión."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr "Posición de preparación del extrusor sobre el eje Y"
|
||||
|
||||
#: 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 "Coordenada X de la posición en la que la tobera se coloca al inicio de la impresión."
|
4028
resources/i18n/es/fdmprinter.def.json.po
Normal file
4028
resources/i18n/es/fdmprinter.def.json.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
182
resources/i18n/fdmextruder.def.json.pot
Normal file
182
resources/i18n/fdmextruder.def.json.pot
Normal file
|
@ -0,0 +1,182 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Uranium json setting files\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2016-09-20 14:48+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings label"
|
||||
msgid "Machine"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings description"
|
||||
msgid "Machine specific settings"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
4014
resources/i18n/fdmprinter.def.json.pot
Normal file
4014
resources/i18n/fdmprinter.def.json.pot
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
182
resources/i18n/fi/fdmextruder.def.json.po
Normal file
182
resources/i18n/fi/fdmextruder.def.json.po
Normal file
|
@ -0,0 +1,182 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Uranium json setting files\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2016-09-20 17:35+0000\n"
|
||||
"PO-Revision-Date: 2016-09-29 13:02+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings label"
|
||||
msgid "Machine"
|
||||
msgstr "Laite"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings description"
|
||||
msgid "Machine specific settings"
|
||||
msgstr "Laitekohtaiset asetukset"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr "Suulake"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr "Tulostukseen käytettävä suulakeryhmä. Tätä käytetään monipursotuksessa."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr "Suuttimen X-siirtymä"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr "Suuttimen siirtymän X-koordinaatti."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr "Suuttimen Y-siirtymä"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr "Suuttimen siirtymän Y-koordinaatti."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr "Suulakkeen aloitus-GCode"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr "Aloitus-GCode, joka suoritetaan suulakkeen käynnistyksen yhteydessä."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr "Suulakkeen aloitussijainti absoluuttinen"
|
||||
|
||||
#: 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 "Tekee suulakkeen aloitussijainnista absoluuttisen eikä suhteellisen viimeksi tunnettuun pään sijaintiin nähden."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr "Suulakkeen aloitussijainti X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Aloitussijainnin X-koordinaatti suulaketta käynnistettäessä."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr "Suulakkeen aloitussijainti Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Aloitussijainnin Y-koordinaatti suulaketta käynnistettäessä."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr "Suulakkeen lopetus-GCode"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr "Lopetus-GCode, joka suoritetaan, kun suulake poistetaan käytöstä."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr "Suulakkeen lopetussijainti absoluuttinen"
|
||||
|
||||
#: 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 "Tekee suulakkeen lopetussijainnista absoluuttisen eikä suhteellisen viimeksi tunnettuun pään sijaintiin nähden."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr "Suulakkeen lopetussijainti X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Lopetussijainnin X-koordinaatti, kun suulake poistetaan käytöstä."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr "Suulakkeen lopetussijainti Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Lopetussijainnin Y-koordinaatti, kun suulake poistetaan käytöstä."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr "Suulakkeen esitäytön Z-sijainti"
|
||||
|
||||
#: 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 "Z-koordinaatti sijainnille, jossa suutin esitäytetään tulostusta aloitettaessa."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr "Alustan tarttuvuus"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr "Tarttuvuus"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr "Suulakkeen esitäytön X-sijainti"
|
||||
|
||||
#: 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 "X-koordinaatti sijainnille, jossa suutin esitäytetään tulostusta aloitettaessa."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr "Suulakkeen esitäytön Y-sijainti"
|
||||
|
||||
#: 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 "Y-koordinaatti sijainnille, jossa suutin esitäytetään tulostusta aloitettaessa."
|
4028
resources/i18n/fi/fdmprinter.def.json.po
Normal file
4028
resources/i18n/fi/fdmprinter.def.json.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
182
resources/i18n/fr/fdmextruder.def.json.po
Normal file
182
resources/i18n/fr/fdmextruder.def.json.po
Normal file
|
@ -0,0 +1,182 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Uranium json setting files\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2016-09-20 17:35+0000\n"
|
||||
"PO-Revision-Date: 2016-09-29 13:02+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings label"
|
||||
msgid "Machine"
|
||||
msgstr "Machine"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings description"
|
||||
msgid "Machine specific settings"
|
||||
msgstr "Paramètres spécifiques de la machine"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr "Extrudeuse"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr "Le train d'extrudeuse utilisé pour l'impression. Cela est utilisé en multi-extrusion."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr "Buse Décalage X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr "Les coordonnées X du décalage de la buse."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr "Buse Décalage Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr "Les coordonnées Y du décalage de la buse."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr "Extrudeuse G-Code de démarrage"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr "G-Code de démarrage à exécuter à chaque mise en marche de l'extrudeuse."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr "Extrudeuse Position de départ absolue"
|
||||
|
||||
#: 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 "Rendre la position de départ de l'extrudeuse absolue plutôt que relative à la dernière position connue de la tête."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr "Extrudeuse Position de départ X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Les coordonnées X de la position de départ lors de la mise en marche de l'extrudeuse."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr "Extrudeuse Position de départ Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Les coordonnées Y de la position de départ lors de la mise en marche de l'extrudeuse."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr "Extrudeuse G-Code de fin"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr "G-Code de fin à exécuter à chaque arrêt de l'extrudeuse."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr "Extrudeuse Position de fin absolue"
|
||||
|
||||
#: 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 "Rendre la position de fin de l'extrudeuse absolue plutôt que relative à la dernière position connue de la tête."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr "Extrudeuse Position de fin X"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Les coordonnées X de la position de fin lors de l'arrêt de l'extrudeuse."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr "Extrudeuse Position de fin Y"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Les coordonnées Y de la position de fin lors de l'arrêt de l'extrudeuse."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr "Extrudeuse Position d'amorçage Z"
|
||||
|
||||
#: 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 "Les coordonnées Z de la position à laquelle la buse s'amorce au début de l'impression."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr "Adhérence du plateau"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr "Adhérence"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr "Extrudeuse Position d'amorçage X"
|
||||
|
||||
#: 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 "Les coordonnées X de la position à laquelle la buse s'amorce au début de l'impression."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr "Extrudeuse Position d'amorçage Y"
|
||||
|
||||
#: 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 "Les coordonnées Y de la position à laquelle la buse s'amorce au début de l'impression."
|
4028
resources/i18n/fr/fdmprinter.def.json.po
Normal file
4028
resources/i18n/fr/fdmprinter.def.json.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
182
resources/i18n/it/fdmextruder.def.json.po
Normal file
182
resources/i18n/it/fdmextruder.def.json.po
Normal file
|
@ -0,0 +1,182 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Uranium json setting files\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2016-09-20 17:35+0000\n"
|
||||
"PO-Revision-Date: 2016-09-29 13:02+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings label"
|
||||
msgid "Machine"
|
||||
msgstr "Macchina"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings description"
|
||||
msgid "Machine specific settings"
|
||||
msgstr "Impostazioni macchina specifiche"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr "Estrusore"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr "Treno estrusore utilizzato per la stampa. Utilizzato nell’estrusione multipla."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr "Offset X ugello"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr "La coordinata y dell’offset dell’ugello."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr "Offset Y ugello"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr "La coordinata y dell’offset dell’ugello."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr "Codice G avvio estrusore"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr "Codice G di avvio da eseguire ogniqualvolta si accende l’estrusore."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr "Assoluto posizione avvio estrusore"
|
||||
|
||||
#: 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 "Rende la posizione di partenza estrusore assoluta anziché relativa rispetto all’ultima posizione nota della testina."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr "X posizione avvio estrusore"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "La coordinata x della posizione di partenza all’accensione dell’estrusore."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr "Y posizione avvio estrusore"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "La coordinata y della posizione di partenza all’accensione dell’estrusore."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr "Codice G fine estrusore"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr "Codice G di fine da eseguire ogniqualvolta si spegne l’estrusore."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr "Assoluto posizione fine estrusore"
|
||||
|
||||
#: 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 "Rende la posizione di fine estrusore assoluta anziché relativa rispetto all’ultima posizione nota della testina."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr "Posizione X fine estrusore"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "La coordinata x della posizione di fine allo spegnimento dell’estrusore."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr "Posizione Y fine estrusore"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "La coordinata y della posizione di fine allo spegnimento dell’estrusore."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr "Posizione Z innesco estrusore"
|
||||
|
||||
#: 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 "Indica la coordinata Z della posizione in cui l’ugello si innesca all’avvio della stampa."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr "Adesione piano di stampa"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr "Adesione"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr "Posizione X innesco estrusore"
|
||||
|
||||
#: 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 "La coordinata X della posizione in cui l’ugello si innesca all’avvio della stampa."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr "Posizione Y innesco estrusore"
|
||||
|
||||
#: 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 "La coordinata Y della posizione in cui l’ugello si innesca all’avvio della stampa."
|
4028
resources/i18n/it/fdmprinter.def.json.po
Normal file
4028
resources/i18n/it/fdmprinter.def.json.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
182
resources/i18n/nl/fdmextruder.def.json.po
Normal file
182
resources/i18n/nl/fdmextruder.def.json.po
Normal file
|
@ -0,0 +1,182 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Uranium json setting files\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2016-09-20 17:35+0000\n"
|
||||
"PO-Revision-Date: 2016-09-29 13:02+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings label"
|
||||
msgid "Machine"
|
||||
msgstr "Machine"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings description"
|
||||
msgid "Machine specific settings"
|
||||
msgstr "Instellingen van de machine"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr "Extruder"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr "De extruder train die voor het printen wordt gebruikt. Deze wordt gebruikt in meervoudige doorvoer."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr "Offset X-nozzle"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr "De X-coördinaat van de offset van de nozzle."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr "Offset Y-nozzle"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr "De Y-coördinaat van de offset van de nozzle."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr "G-code van extruderstart"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr "Start-g-code die wordt uitgevoerd wanneer de extruder wordt ingeschakeld."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr "Absolute startpositie extruder"
|
||||
|
||||
#: 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 "Maak van de startpositie van de extruder de absolute startpositie, in plaats van de relatieve startpositie ten opzichte van de laatst bekende locatie van de printkop."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr "X-startpositie extruder"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "De X-coördinaat van de startpositie wanneer de extruder wordt ingeschakeld."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr "Y-startpositie extruder"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "De Y-coördinaat van de startpositie wanneer de extruder wordt ingeschakeld."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr "G-code van extrudereinde"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr "Eind-g-code die wordt uitgevoerd wanneer de extruder wordt uitgeschakeld."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr "Absolute eindpositie extruder"
|
||||
|
||||
#: 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 "Maak van de eindpositie van de extruder de absolute eindpositie, in plaats van de relatieve eindpositie ten opzichte van de laatst bekende locatie van de printkop."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr "X-eindpositie extruder"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "De X-coördinaat van de eindpositie wanneer de extruder wordt uitgeschakeld."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr "Y-eindpositie extruder"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "De Y-coördinaat van de eindpositie wanneer de extruder wordt uitgeschakeld."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr "Z-positie voor primen extruder"
|
||||
|
||||
#: 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 "De Z-coördinaat van de positie waar filament in de nozzle wordt teruggeduwd aan het begin van het printen."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr "Hechting aan platform"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr "Hechting"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr "X-positie voor primen extruder"
|
||||
|
||||
#: 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 "De X-coördinaat van de positie waar filament in de nozzle wordt teruggeduwd aan het begin van het printen."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr "Y-positie voor primen extruder"
|
||||
|
||||
#: 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 "De Y-coördinaat van de positie waar filament in de nozzle wordt teruggeduwd aan het begin van het printen."
|
4028
resources/i18n/nl/fdmprinter.def.json.po
Normal file
4028
resources/i18n/nl/fdmprinter.def.json.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2335
resources/i18n/tr/cura.po
Normal file
2335
resources/i18n/tr/cura.po
Normal file
File diff suppressed because it is too large
Load diff
182
resources/i18n/tr/fdmextruder.def.json.po
Normal file
182
resources/i18n/tr/fdmextruder.def.json.po
Normal file
|
@ -0,0 +1,182 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Uranium json setting files\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2016-09-20 17:35+0000\n"
|
||||
"PO-Revision-Date: 2016-09-29 13:02+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings label"
|
||||
msgid "Machine"
|
||||
msgstr "Makine"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_settings description"
|
||||
msgid "Machine specific settings"
|
||||
msgstr "Makine özel ayarları"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr label"
|
||||
msgid "Extruder"
|
||||
msgstr "Ekstruder"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_nr description"
|
||||
msgid "The extruder train used for printing. This is used in multi-extrusion."
|
||||
msgstr "Yazdırma için kullanılan ekstruder dişli çark. Çoklu ekstrüzyon işlemi için kullanılır."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x label"
|
||||
msgid "Nozzle X Offset"
|
||||
msgstr "Nozül NX Ofseti"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_x description"
|
||||
msgid "The x-coordinate of the offset of the nozzle."
|
||||
msgstr "Nozül ofsetinin x koordinatı."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y label"
|
||||
msgid "Nozzle Y Offset"
|
||||
msgstr "Nozül Y Ofseti"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_nozzle_offset_y description"
|
||||
msgid "The y-coordinate of the offset of the nozzle."
|
||||
msgstr "Nozül ofsetinin y koordinatı."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code label"
|
||||
msgid "Extruder Start G-Code"
|
||||
msgstr "Ekstruder G-Code'u başlatma"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_code description"
|
||||
msgid "Start g-code to execute whenever turning the extruder on."
|
||||
msgstr "Ekstruderi her açtığınızda g-code'u başlatın."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_abs label"
|
||||
msgid "Extruder Start Position Absolute"
|
||||
msgstr "Ekstruderin Mutlak Başlangıç Konumu"
|
||||
|
||||
#: 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 "Ekstruder başlama konumunu, yazıcı başlığının son konumuna göre ayarlamak yerine mutlak olarak ayarlayın."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x label"
|
||||
msgid "Extruder Start Position X"
|
||||
msgstr "Ekstruder X Başlangıç Konumu"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_x description"
|
||||
msgid "The x-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Ekstruder açılırken başlangıç konumunun x koordinatı."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y label"
|
||||
msgid "Extruder Start Position Y"
|
||||
msgstr "Ekstruder Y Başlangıç Konumu"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_start_pos_y description"
|
||||
msgid "The y-coordinate of the starting position when turning the extruder on."
|
||||
msgstr "Ekstruder açılırken başlangıç konumunun Y koordinatı."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code label"
|
||||
msgid "Extruder End G-Code"
|
||||
msgstr "Ekstruder G-Code'u Sonlandırma"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_code description"
|
||||
msgid "End g-code to execute whenever turning the extruder off."
|
||||
msgstr "Ekstruderi her kapattığınızda g-code'u sonlandırın."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_abs label"
|
||||
msgid "Extruder End Position Absolute"
|
||||
msgstr "Ekstruderin Mutlak Bitiş Konumu"
|
||||
|
||||
#: 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 "Ekstruder bitiş konumunu, yazıcı başlığının son konumuna göre ayarlamak yerine mutlak olarak ayarlayın."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x label"
|
||||
msgid "Extruder End Position X"
|
||||
msgstr "Ekstruderin X Bitiş Konumu"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_x description"
|
||||
msgid "The x-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Ekstruder kapatılırken bitiş konumunun x koordinatı."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y label"
|
||||
msgid "Extruder End Position Y"
|
||||
msgstr "Ekstruderin Y Bitiş Konumu"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "machine_extruder_end_pos_y description"
|
||||
msgid "The y-coordinate of the ending position when turning the extruder off."
|
||||
msgstr "Ekstruder kapatılırken bitiş konumunun Y koordinatı."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_z label"
|
||||
msgid "Extruder Prime Z Position"
|
||||
msgstr "Ekstruder İlk Z konumu"
|
||||
|
||||
#: 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 "Nozül yazdırma işlemini başlatmaya hazırlandığında konumun Z koordinatı."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion label"
|
||||
msgid "Build Plate Adhesion"
|
||||
msgstr "Yapı Levhası Yapıştırması"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "platform_adhesion description"
|
||||
msgid "Adhesion"
|
||||
msgstr "Yapıştırma"
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_x label"
|
||||
msgid "Extruder Prime X Position"
|
||||
msgstr "Extruder İlk X konumu"
|
||||
|
||||
#: 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 "Nozül yazdırma işlemini başlatmaya hazırlandığında konumun X koordinatı."
|
||||
|
||||
#: fdmextruder.def.json
|
||||
msgctxt "extruder_prime_pos_y label"
|
||||
msgid "Extruder Prime Y Position"
|
||||
msgstr "Extruder İlk Y konumu"
|
||||
|
||||
#: 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 "Nozül yazdırma işlemini başlatmaya hazırlandığında konumun Y koordinatı."
|
4028
resources/i18n/tr/fdmprinter.def.json.po
Normal file
4028
resources/i18n/tr/fdmprinter.def.json.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -9,7 +9,7 @@ Generic Nylon profile. Serves as an example file, data in this file is not corre
|
|||
<material>Nylon</material>
|
||||
<color>Generic</color>
|
||||
</name>
|
||||
<GUID>35ebb8df-66d2-41cd-9662-b7d96b9c2cbd</GUID>
|
||||
<GUID>28fb4162-db74-49e1-9008-d05f1e8bef5c</GUID>
|
||||
<version>1</version>
|
||||
<color_code>#3DF266</color_code>
|
||||
</metadata>
|
||||
|
|
25
resources/materials/ultimaker_nylon_black.xml.fdm_material
Normal file
25
resources/materials/ultimaker_nylon_black.xml.fdm_material
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Generic Nylon profile. Serves as an example file, data in this file is not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Ultimaker</brand>
|
||||
<material>Nylon</material>
|
||||
<color>Black</color>
|
||||
</name>
|
||||
<GUID>c64c2dbe-5691-4363-a7d9-66b2dc12837f</GUID>
|
||||
<version>1</version>
|
||||
<color_code>#000000</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.14</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">250</setting>
|
||||
<setting key="heated bed temperature">60</setting>
|
||||
<setting key="standby temperature">175</setting>
|
||||
</settings>
|
||||
</fdmmaterial>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Generic Nylon profile. Serves as an example file, data in this file is not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Ultimaker</brand>
|
||||
<material>Nylon</material>
|
||||
<color>Transparent</color>
|
||||
</name>
|
||||
<GUID>e256615d-a04e-4f53-b311-114b90560af9</GUID>
|
||||
<version>1</version>
|
||||
<color_code>#FFFFFF</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.14</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">250</setting>
|
||||
<setting key="heated bed temperature">60</setting>
|
||||
<setting key="standby temperature">175</setting>
|
||||
</settings>
|
||||
</fdmmaterial>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue