mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
Merge branch 'master' into python_type_hinting
This commit is contained in:
commit
d4619da358
132 changed files with 42584 additions and 952 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -31,5 +31,5 @@ debian*
|
||||||
plugins/Doodle3D-cura-plugin
|
plugins/Doodle3D-cura-plugin
|
||||||
plugins/GodMode
|
plugins/GodMode
|
||||||
plugins/PostProcessingPlugin
|
plugins/PostProcessingPlugin
|
||||||
plugins/UM3NetworkPrinting
|
|
||||||
plugins/X3GWriter
|
plugins/X3GWriter
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
project(cura NONE)
|
project(cura NONE)
|
||||||
cmake_minimum_required(VERSION 2.8.12)
|
cmake_minimum_required(VERSION 2.8.12)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ Dependencies
|
||||||
This will be needed at runtime to perform the actual slicing.
|
This will be needed at runtime to perform the actual slicing.
|
||||||
* [PySerial](https://github.com/pyserial/pyserial)
|
* [PySerial](https://github.com/pyserial/pyserial)
|
||||||
Only required for USB printing support.
|
Only required for USB printing support.
|
||||||
|
* [python-zeroconf](https://github.com/jstasiak/python-zeroconf)
|
||||||
|
Only required to detect mDNS-enabled printers
|
||||||
|
|
||||||
Configuring Cura
|
Configuring Cura
|
||||||
----------------
|
----------------
|
||||||
|
@ -47,10 +49,11 @@ Third party plugins
|
||||||
* [Barbarian Plugin](https://github.com/nallath/BarbarianPlugin): Simple scale tool for imperial to metric.
|
* [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.
|
* [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.
|
* [Auto orientation](https://github.com/nallath/CuraOrientationPlugin): Calculate the optimal orientation for a model.
|
||||||
|
* [OctoPrint Plugin](https://github.com/fieldofview/OctoPrintPlugin): Send printjobs directly to OctoPrint and monitor their progress in Cura.
|
||||||
|
|
||||||
Making profiles for other printers
|
Making profiles for other printers
|
||||||
----------------------------------
|
----------------------------------
|
||||||
There are two ways of doing it. You can either use the generator [here](http://quillford.github.io/CuraProfileMaker/) or you can use [this](https://github.com/Ultimaker/Cura/blob/master/resources/machines/ultimaker_original.json) as a template.
|
There are two ways of doing it. You can either use the generator [here](http://quillford.github.io/CuraProfileMaker/) or you can use [this](https://github.com/Ultimaker/Cura/blob/master/resources/definitions/ultimaker_original.def.json) as a template.
|
||||||
|
|
||||||
* Change the machine ID to something unique
|
* Change the machine ID to something unique
|
||||||
* Change the machine_name to your printer's name
|
* Change the machine_name to your printer's name
|
||||||
|
@ -62,4 +65,4 @@ There are two ways of doing it. You can either use the generator [here](http://q
|
||||||
* Set the start and end gcode in machine_start_gcode and machine_end_gcode
|
* Set the start and end gcode in machine_start_gcode and machine_end_gcode
|
||||||
* If your printer has a heated bed, set visible to true under material_bed_temperature
|
* If your printer has a heated bed, set visible to true under material_bed_temperature
|
||||||
|
|
||||||
Once you are done, put the profile you have made into resources/machines, or in machines in your cura profile folder.
|
Once you are done, put the profile you have made into resources/definitions, or in definitions in your cura profile folder.
|
||||||
|
|
|
@ -11,6 +11,7 @@ from UM.Application import Application
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
from UM.Math.Matrix import Matrix
|
||||||
from UM.Math.Color import Color
|
from UM.Math.Color import Color
|
||||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||||
from UM.Math.Polygon import Polygon
|
from UM.Math.Polygon import Polygon
|
||||||
|
@ -23,6 +24,7 @@ catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
import copy
|
import copy
|
||||||
|
import math
|
||||||
|
|
||||||
# Setting for clearance around the prime
|
# Setting for clearance around the prime
|
||||||
PRIME_CLEARANCE = 6.5
|
PRIME_CLEARANCE = 6.5
|
||||||
|
@ -43,6 +45,7 @@ class BuildVolume(SceneNode):
|
||||||
self._width = 0
|
self._width = 0
|
||||||
self._height = 0
|
self._height = 0
|
||||||
self._depth = 0
|
self._depth = 0
|
||||||
|
self._shape = ""
|
||||||
|
|
||||||
self._shader = None
|
self._shader = None
|
||||||
|
|
||||||
|
@ -137,6 +140,9 @@ class BuildVolume(SceneNode):
|
||||||
def setDepth(self, depth):
|
def setDepth(self, depth):
|
||||||
if depth: self._depth = depth
|
if depth: self._depth = depth
|
||||||
|
|
||||||
|
def setShape(self, shape):
|
||||||
|
if shape: self._shape = shape
|
||||||
|
|
||||||
def getDisallowedAreas(self):
|
def getDisallowedAreas(self):
|
||||||
return self._disallowed_areas
|
return self._disallowed_areas
|
||||||
|
|
||||||
|
@ -175,9 +181,11 @@ class BuildVolume(SceneNode):
|
||||||
min_d = -self._depth / 2
|
min_d = -self._depth / 2
|
||||||
max_d = self._depth / 2
|
max_d = self._depth / 2
|
||||||
|
|
||||||
mb = MeshBuilder()
|
z_fight_distance = 0.2 # Distance between buildplate and disallowed area meshes to prevent z-fighting
|
||||||
|
|
||||||
|
if self._shape != "elliptic":
|
||||||
# Outline 'cube' of the build volume
|
# Outline 'cube' of the build volume
|
||||||
|
mb = MeshBuilder()
|
||||||
mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor)
|
mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor)
|
||||||
mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor)
|
mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor)
|
||||||
mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
|
mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
|
||||||
|
@ -195,7 +203,48 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
self.setMeshData(mb.build())
|
self.setMeshData(mb.build())
|
||||||
|
|
||||||
|
# Build plate grid mesh
|
||||||
mb = MeshBuilder()
|
mb = MeshBuilder()
|
||||||
|
mb.addQuad(
|
||||||
|
Vector(min_w, min_h - z_fight_distance, min_d),
|
||||||
|
Vector(max_w, min_h - z_fight_distance, min_d),
|
||||||
|
Vector(max_w, min_h - z_fight_distance, max_d),
|
||||||
|
Vector(min_w, min_h - z_fight_distance, max_d)
|
||||||
|
)
|
||||||
|
|
||||||
|
for n in range(0, 6):
|
||||||
|
v = mb.getVertex(n)
|
||||||
|
mb.setVertexUVCoordinates(n, v[0], v[2])
|
||||||
|
self._grid_mesh = mb.build()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Bottom and top 'ellipse' of the build volume
|
||||||
|
aspect = 1.0
|
||||||
|
scale_matrix = Matrix()
|
||||||
|
if self._width != 0:
|
||||||
|
# Scale circular meshes by aspect ratio if width != height
|
||||||
|
aspect = self._height / self._width
|
||||||
|
scale_matrix.compose(scale = Vector(1, 1, aspect))
|
||||||
|
mb = MeshBuilder()
|
||||||
|
mb.addArc(max_w, Vector.Unit_Y, center = (0, min_h - z_fight_distance, 0), color = self.VolumeOutlineColor)
|
||||||
|
mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0), color = self.VolumeOutlineColor)
|
||||||
|
self.setMeshData(mb.build().getTransformed(scale_matrix))
|
||||||
|
|
||||||
|
# Build plate grid mesh
|
||||||
|
mb = MeshBuilder()
|
||||||
|
mb.addVertex(0, min_h - z_fight_distance, 0)
|
||||||
|
mb.addArc(max_w, Vector.Unit_Y, center = Vector(0, min_h - z_fight_distance, 0))
|
||||||
|
sections = mb.getVertexCount() - 1 # Center point is not an arc section
|
||||||
|
indices = []
|
||||||
|
for n in range(0, sections - 1):
|
||||||
|
indices.append([0, n + 2, n + 1])
|
||||||
|
mb.addIndices(numpy.asarray(indices, dtype = numpy.int32))
|
||||||
|
mb.calculateNormals()
|
||||||
|
|
||||||
|
for n in range(0, mb.getVertexCount()):
|
||||||
|
v = mb.getVertex(n)
|
||||||
|
mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
|
||||||
|
self._grid_mesh = mb.build().getTransformed(scale_matrix)
|
||||||
|
|
||||||
# Indication of the machine origin
|
# Indication of the machine origin
|
||||||
if self._global_container_stack.getProperty("machine_center_is_zero", "value"):
|
if self._global_container_stack.getProperty("machine_center_is_zero", "value"):
|
||||||
|
@ -203,6 +252,7 @@ class BuildVolume(SceneNode):
|
||||||
else:
|
else:
|
||||||
origin = Vector(min_w, min_h, max_d)
|
origin = Vector(min_w, min_h, max_d)
|
||||||
|
|
||||||
|
mb = MeshBuilder()
|
||||||
mb.addCube(
|
mb.addCube(
|
||||||
width = self._origin_line_length,
|
width = self._origin_line_length,
|
||||||
height = self._origin_line_width,
|
height = self._origin_line_width,
|
||||||
|
@ -226,19 +276,6 @@ class BuildVolume(SceneNode):
|
||||||
)
|
)
|
||||||
self._origin_mesh = mb.build()
|
self._origin_mesh = mb.build()
|
||||||
|
|
||||||
mb = MeshBuilder()
|
|
||||||
mb.addQuad(
|
|
||||||
Vector(min_w, min_h - 0.2, min_d),
|
|
||||||
Vector(max_w, min_h - 0.2, min_d),
|
|
||||||
Vector(max_w, min_h - 0.2, max_d),
|
|
||||||
Vector(min_w, min_h - 0.2, max_d)
|
|
||||||
)
|
|
||||||
|
|
||||||
for n in range(0, 6):
|
|
||||||
v = mb.getVertex(n)
|
|
||||||
mb.setVertexUVCoordinates(n, v[0], v[2])
|
|
||||||
self._grid_mesh = mb.build()
|
|
||||||
|
|
||||||
disallowed_area_height = 0.1
|
disallowed_area_height = 0.1
|
||||||
disallowed_area_size = 0
|
disallowed_area_size = 0
|
||||||
if self._disallowed_areas:
|
if self._disallowed_areas:
|
||||||
|
@ -351,6 +388,7 @@ class BuildVolume(SceneNode):
|
||||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||||
self._build_volume_message.hide()
|
self._build_volume_message.hide()
|
||||||
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
|
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
||||||
|
|
||||||
self._updateDisallowedAreas()
|
self._updateDisallowedAreas()
|
||||||
self._updateRaftThickness()
|
self._updateRaftThickness()
|
||||||
|
@ -410,6 +448,13 @@ class BuildVolume(SceneNode):
|
||||||
used_extruders = extruder_manager.getUsedExtruderStacks()
|
used_extruders = extruder_manager.getUsedExtruderStacks()
|
||||||
disallowed_border_size = self._getEdgeDisallowedSize()
|
disallowed_border_size = self._getEdgeDisallowedSize()
|
||||||
|
|
||||||
|
if not used_extruders:
|
||||||
|
# If no extruder is used, assume that the active extruder is used (else nothing is drawn)
|
||||||
|
if extruder_manager.getActiveExtruderStack():
|
||||||
|
used_extruders = [extruder_manager.getActiveExtruderStack()]
|
||||||
|
else:
|
||||||
|
used_extruders = [self._global_container_stack]
|
||||||
|
|
||||||
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
|
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
|
||||||
prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders)
|
prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders)
|
||||||
prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
|
prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
|
||||||
|
@ -440,9 +485,6 @@ class BuildVolume(SceneNode):
|
||||||
if collision:
|
if collision:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
if not collision:
|
|
||||||
#Prime areas are valid. Add as normal.
|
|
||||||
result_areas[extruder_id].extend(prime_areas[extruder_id])
|
result_areas[extruder_id].extend(prime_areas[extruder_id])
|
||||||
|
|
||||||
nozzle_disallowed_areas = extruder.getProperty("nozzle_disallowed_areas", "value")
|
nozzle_disallowed_areas = extruder.getProperty("nozzle_disallowed_areas", "value")
|
||||||
|
@ -582,6 +624,8 @@ class BuildVolume(SceneNode):
|
||||||
bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y)
|
bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y)
|
||||||
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
|
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
|
||||||
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
|
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
|
||||||
|
|
||||||
|
if self._shape != "elliptic":
|
||||||
if border_size - left_unreachable_border > 0:
|
if border_size - left_unreachable_border > 0:
|
||||||
result[extruder_id].append(Polygon(numpy.array([
|
result[extruder_id].append(Polygon(numpy.array([
|
||||||
[-half_machine_width, -half_machine_depth],
|
[-half_machine_width, -half_machine_depth],
|
||||||
|
@ -610,6 +654,49 @@ class BuildVolume(SceneNode):
|
||||||
[-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border],
|
[-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border],
|
||||||
[half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border]
|
[half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border]
|
||||||
], numpy.float32)))
|
], numpy.float32)))
|
||||||
|
else:
|
||||||
|
sections = 32
|
||||||
|
arc_vertex = [0, half_machine_depth - border_size]
|
||||||
|
for i in range(0, sections):
|
||||||
|
quadrant = math.floor(4 * i / sections)
|
||||||
|
vertices = []
|
||||||
|
if quadrant == 0:
|
||||||
|
vertices.append([-half_machine_width, half_machine_depth])
|
||||||
|
elif quadrant == 1:
|
||||||
|
vertices.append([-half_machine_width, -half_machine_depth])
|
||||||
|
elif quadrant == 2:
|
||||||
|
vertices.append([half_machine_width, -half_machine_depth])
|
||||||
|
elif quadrant == 3:
|
||||||
|
vertices.append([half_machine_width, half_machine_depth])
|
||||||
|
vertices.append(arc_vertex)
|
||||||
|
|
||||||
|
angle = 2 * math.pi * (i + 1) / sections
|
||||||
|
arc_vertex = [-(half_machine_width - border_size) * math.sin(angle), (half_machine_depth - border_size) * math.cos(angle)]
|
||||||
|
vertices.append(arc_vertex)
|
||||||
|
|
||||||
|
result[extruder_id].append(Polygon(numpy.array(vertices, numpy.float32)))
|
||||||
|
|
||||||
|
if border_size > 0:
|
||||||
|
result[extruder_id].append(Polygon(numpy.array([
|
||||||
|
[-half_machine_width, -half_machine_depth],
|
||||||
|
[-half_machine_width, half_machine_depth],
|
||||||
|
[-half_machine_width + border_size, 0]
|
||||||
|
], numpy.float32)))
|
||||||
|
result[extruder_id].append(Polygon(numpy.array([
|
||||||
|
[-half_machine_width, half_machine_depth],
|
||||||
|
[ half_machine_width, half_machine_depth],
|
||||||
|
[ 0, half_machine_depth - border_size]
|
||||||
|
], numpy.float32)))
|
||||||
|
result[extruder_id].append(Polygon(numpy.array([
|
||||||
|
[ half_machine_width, half_machine_depth],
|
||||||
|
[ half_machine_width, -half_machine_depth],
|
||||||
|
[ half_machine_width - border_size, 0]
|
||||||
|
], numpy.float32)))
|
||||||
|
result[extruder_id].append(Polygon(numpy.array([
|
||||||
|
[ half_machine_width,-half_machine_depth],
|
||||||
|
[-half_machine_width,-half_machine_depth],
|
||||||
|
[ 0, -half_machine_depth + border_size]
|
||||||
|
], numpy.float32)))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -683,7 +770,7 @@ class BuildVolume(SceneNode):
|
||||||
skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap")
|
skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap")
|
||||||
skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count")
|
skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count")
|
||||||
bed_adhesion_size = skirt_distance + (skirt_line_count * self._getSettingFromAdhesionExtruder("skirt_brim_line_width"))
|
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:
|
if len(ExtruderManager.getInstance().getUsedExtruderStacks()) > 1:
|
||||||
adhesion_extruder_nr = int(self._global_container_stack.getProperty("adhesion_extruder_nr", "value"))
|
adhesion_extruder_nr = int(self._global_container_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||||
extruder_values = ExtruderManager.getInstance().getAllExtruderValues("skirt_brim_line_width")
|
extruder_values = ExtruderManager.getInstance().getAllExtruderValues("skirt_brim_line_width")
|
||||||
del extruder_values[adhesion_extruder_nr] # Remove the value of the adhesion extruder nr.
|
del extruder_values[adhesion_extruder_nr] # Remove the value of the adhesion extruder nr.
|
||||||
|
@ -716,11 +803,12 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
move_from_wall_radius = 0 # Moves that start from outer wall.
|
move_from_wall_radius = 0 # Moves that start from outer wall.
|
||||||
move_from_wall_radius = max(move_from_wall_radius, max(self._getSettingFromAllExtruders("infill_wipe_dist")))
|
move_from_wall_radius = max(move_from_wall_radius, max(self._getSettingFromAllExtruders("infill_wipe_dist")))
|
||||||
avoid_enabled_per_extruder = self._getSettingFromAllExtruders(("travel_avoid_other_parts"))
|
used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
|
||||||
avoid_distance_per_extruder = self._getSettingFromAllExtruders("travel_avoid_distance")
|
avoid_enabled_per_extruder = [stack.getProperty("travel_avoid_other_parts","value") for stack in used_extruders]
|
||||||
for index, avoid_other_parts_enabled in enumerate(avoid_enabled_per_extruder): #For each extruder (or just global).
|
travel_avoid_distance_per_extruder = [stack.getProperty("travel_avoid_distance", "value") for stack in used_extruders]
|
||||||
|
for avoid_other_parts_enabled, avoid_distance in zip(avoid_enabled_per_extruder, travel_avoid_distance_per_extruder): #For each extruder (or just global).
|
||||||
if avoid_other_parts_enabled:
|
if avoid_other_parts_enabled:
|
||||||
move_from_wall_radius = max(move_from_wall_radius, avoid_distance_per_extruder[index]) #Index of the same extruder.
|
move_from_wall_radius = max(move_from_wall_radius, avoid_distance)
|
||||||
|
|
||||||
# Now combine our different pieces of data to get the final border 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 the bed adhesion, since the bed adhesion goes around support.
|
||||||
|
|
|
@ -2,9 +2,11 @@ import sys
|
||||||
import platform
|
import platform
|
||||||
import traceback
|
import traceback
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
import urllib
|
||||||
|
|
||||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication
|
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QCoreApplication
|
||||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit
|
from PyQt5.QtGui import QPixmap
|
||||||
|
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QHBoxLayout, QVBoxLayout, QLabel, QTextEdit
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
@ -22,9 +24,7 @@ fatal_exception_types = [
|
||||||
]
|
]
|
||||||
|
|
||||||
def show(exception_type, value, tb):
|
def show(exception_type, value, tb):
|
||||||
debug_mode = False
|
debug_mode = True
|
||||||
if QCoreApplication.instance():
|
|
||||||
debug_mode = QCoreApplication.instance().getCommandLineOption("debug-mode", False)
|
|
||||||
|
|
||||||
Logger.log("c", "An uncaught exception has occurred!")
|
Logger.log("c", "An uncaught exception has occurred!")
|
||||||
for line in traceback.format_exception(exception_type, value, tb):
|
for line in traceback.format_exception(exception_type, value, tb):
|
||||||
|
@ -38,14 +38,41 @@ def show(exception_type, value, tb):
|
||||||
if not application:
|
if not application:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
dialog = QDialog()
|
dialog = QDialog()
|
||||||
|
dialog.setMinimumWidth(640)
|
||||||
|
dialog.setMinimumHeight(640)
|
||||||
dialog.setWindowTitle(catalog.i18nc("@title:window", "Oops!"))
|
dialog.setWindowTitle(catalog.i18nc("@title:window", "Oops!"))
|
||||||
|
|
||||||
layout = QVBoxLayout(dialog)
|
layout = QVBoxLayout(dialog)
|
||||||
|
|
||||||
label = QLabel(dialog)
|
label = QLabel(dialog)
|
||||||
|
pixmap = QPixmap()
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
|
||||||
|
pixmap.loadFromData(data)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
from UM.Resources import Resources
|
||||||
|
path = Resources.getPath(Resources.Images, "kitten.jpg")
|
||||||
|
pixmap.load(path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
pixmap = pixmap.scaled(150, 150)
|
||||||
|
label.setPixmap(pixmap)
|
||||||
|
label.setAlignment(Qt.AlignCenter)
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
label.setText(catalog.i18nc("@label", "<p>A fatal exception has occurred that we could not recover from!</p><p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>"))
|
|
||||||
|
label = QLabel(dialog)
|
||||||
|
layout.addWidget(label)
|
||||||
|
|
||||||
|
#label.setScaledContents(True)
|
||||||
|
label.setText(catalog.i18nc("@label", """<p>A fatal exception has occurred that we could not recover from!</p>
|
||||||
|
<p>We hope this picture of a kitten helps you recover from the shock.</p>
|
||||||
|
<p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>
|
||||||
|
"""))
|
||||||
|
|
||||||
textarea = QTextEdit(dialog)
|
textarea = QTextEdit(dialog)
|
||||||
layout.addWidget(textarea)
|
layout.addWidget(textarea)
|
||||||
|
|
|
@ -7,6 +7,7 @@ from UM.Scene.Camera import Camera
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Math.Quaternion import Quaternion
|
from UM.Math.Quaternion import Quaternion
|
||||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||||
|
from UM.Math.Matrix import Matrix
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Scene.ToolHandle import ToolHandle
|
from UM.Scene.ToolHandle import ToolHandle
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
@ -61,43 +62,12 @@ from PyQt5.QtGui import QColor, QIcon
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
||||||
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
import numpy
|
import numpy
|
||||||
import copy
|
import copy
|
||||||
import urllib
|
import urllib.parse
|
||||||
import os
|
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")
|
numpy.seterr(all="ignore")
|
||||||
|
@ -145,7 +115,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
# For settings which are not settable_per_mesh and not settable_per_extruder:
|
# 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
|
# 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.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None, depends_on = "value")
|
||||||
|
|
||||||
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
||||||
|
|
||||||
|
@ -175,6 +145,7 @@ class CuraApplication(QtApplication):
|
||||||
{
|
{
|
||||||
("quality", UM.Settings.InstanceContainer.InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
|
("quality", UM.Settings.InstanceContainer.InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||||
("machine_stack", UM.Settings.ContainerStack.ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
|
("machine_stack", UM.Settings.ContainerStack.ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
|
||||||
|
("extruder_train", UM.Settings.ContainerStack.ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"),
|
||||||
("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"),
|
("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"),
|
||||||
("user", UM.Settings.InstanceContainer.InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
|
("user", UM.Settings.InstanceContainer.InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
|
||||||
}
|
}
|
||||||
|
@ -250,9 +221,7 @@ class CuraApplication(QtApplication):
|
||||||
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||||
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
|
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
|
||||||
|
|
||||||
# Set the filename to create if cura is writing in the config dir.
|
with ContainerRegistry.getInstance().lockFile():
|
||||||
self._config_lock_filename = os.path.join(Resources.getConfigStoragePath(), CONFIG_LOCK_FILENAME)
|
|
||||||
self.waitConfigLockFile()
|
|
||||||
ContainerRegistry.getInstance().load()
|
ContainerRegistry.getInstance().load()
|
||||||
|
|
||||||
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
||||||
|
@ -262,7 +231,8 @@ class CuraApplication(QtApplication):
|
||||||
Preferences.getInstance().addPreference("view/center_on_select", True)
|
Preferences.getInstance().addPreference("view/center_on_select", True)
|
||||||
Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
|
Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
|
||||||
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
|
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
|
||||||
|
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
|
||||||
|
Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False)
|
||||||
for key in [
|
for key in [
|
||||||
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
|
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
|
||||||
"dialog_profile_path",
|
"dialog_profile_path",
|
||||||
|
@ -279,6 +249,8 @@ class CuraApplication(QtApplication):
|
||||||
shell
|
shell
|
||||||
wall_thickness
|
wall_thickness
|
||||||
top_bottom_thickness
|
top_bottom_thickness
|
||||||
|
z_seam_x
|
||||||
|
z_seam_y
|
||||||
infill
|
infill
|
||||||
infill_sparse_density
|
infill_sparse_density
|
||||||
material
|
material
|
||||||
|
@ -333,14 +305,6 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self._recent_files.append(QUrl.fromLocalFile(f))
|
self._recent_files.append(QUrl.fromLocalFile(f))
|
||||||
|
|
||||||
def getContainerRegistry(self):
|
|
||||||
return CuraContainerRegistry.getInstance()
|
|
||||||
## 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):
|
def _onEngineCreated(self):
|
||||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||||
|
|
||||||
|
@ -368,11 +332,8 @@ class CuraApplication(QtApplication):
|
||||||
if not self._started: # Do not do saving during application start
|
if not self._started: # Do not do saving during application start
|
||||||
return
|
return
|
||||||
|
|
||||||
self.waitConfigLockFile()
|
# Lock file for "more" atomically loading and saving to/from config dir.
|
||||||
|
with ContainerRegistry.getInstance().lockFile():
|
||||||
# When starting Cura, we check for the lockFile which is created and deleted here
|
|
||||||
with lockFile(self._config_lock_filename):
|
|
||||||
|
|
||||||
for instance in ContainerRegistry.getInstance().findInstanceContainers():
|
for instance in ContainerRegistry.getInstance().findInstanceContainers():
|
||||||
if not instance.isDirty():
|
if not instance.isDirty():
|
||||||
continue
|
continue
|
||||||
|
@ -397,6 +358,8 @@ class CuraApplication(QtApplication):
|
||||||
path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
|
path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
|
||||||
elif instance_type == "variant":
|
elif instance_type == "variant":
|
||||||
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
|
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
|
||||||
|
elif instance_type == "definition_changes":
|
||||||
|
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
instance.setPath(path)
|
instance.setPath(path)
|
||||||
|
@ -461,7 +424,6 @@ class CuraApplication(QtApplication):
|
||||||
def addCommandLineOptions(self, parser):
|
def addCommandLineOptions(self, parser):
|
||||||
super().addCommandLineOptions(parser)
|
super().addCommandLineOptions(parser)
|
||||||
parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
|
parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
|
||||||
parser.add_argument("--debug", dest="debug-mode", action="store_true", default=False, help="Enable detailed crash reports.")
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
|
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
|
||||||
|
@ -651,7 +613,7 @@ class CuraApplication(QtApplication):
|
||||||
if not scene_bounding_box:
|
if not scene_bounding_box:
|
||||||
scene_bounding_box = AxisAlignedBox.Null
|
scene_bounding_box = AxisAlignedBox.Null
|
||||||
|
|
||||||
if repr(self._scene_bounding_box) != repr(scene_bounding_box):
|
if repr(self._scene_bounding_box) != repr(scene_bounding_box) and scene_bounding_box.isValid():
|
||||||
self._scene_bounding_box = scene_bounding_box
|
self._scene_bounding_box = scene_bounding_box
|
||||||
self.sceneBoundingBoxChanged.emit()
|
self.sceneBoundingBoxChanged.emit()
|
||||||
|
|
||||||
|
@ -753,6 +715,8 @@ class CuraApplication(QtApplication):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
|
if not node.isSelectable():
|
||||||
|
continue # i.e. node with layer data
|
||||||
Selection.add(node)
|
Selection.add(node)
|
||||||
|
|
||||||
## Delete all nodes containing mesh data in the scene.
|
## Delete all nodes containing mesh data in the scene.
|
||||||
|
@ -792,6 +756,8 @@ class CuraApplication(QtApplication):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
|
if not node.isSelectable():
|
||||||
|
continue # i.e. node with layer data
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
|
|
||||||
if nodes:
|
if nodes:
|
||||||
|
@ -818,6 +784,8 @@ class CuraApplication(QtApplication):
|
||||||
continue # Node that doesnt have a mesh and is not a group.
|
continue # Node that doesnt have a mesh and is not a group.
|
||||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
|
if not node.isSelectable():
|
||||||
|
continue # i.e. node with layer data
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
|
|
||||||
if nodes:
|
if nodes:
|
||||||
|
@ -901,10 +869,13 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
# Compute the center of the objects
|
# Compute the center of the objects
|
||||||
object_centers = []
|
object_centers = []
|
||||||
|
# Forget about the translation that the original objects have
|
||||||
|
zero_translation = Matrix(data=numpy.zeros(3))
|
||||||
for mesh, node in zip(meshes, group_node.getChildren()):
|
for mesh, node in zip(meshes, group_node.getChildren()):
|
||||||
orientation = node.getOrientation().toMatrix()
|
transformation = node.getLocalTransformation()
|
||||||
rotated_mesh = mesh.getTransformed(orientation)
|
transformation.setTranslation(zero_translation)
|
||||||
center = rotated_mesh.getCenterPosition().scale(node.getScale())
|
transformed_mesh = mesh.getTransformed(transformation)
|
||||||
|
center = transformed_mesh.getCenterPosition()
|
||||||
object_centers.append(center)
|
object_centers.append(center)
|
||||||
if object_centers and len(object_centers) > 0:
|
if object_centers and len(object_centers) > 0:
|
||||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||||
|
@ -916,12 +887,13 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
# Move each node to the same position.
|
# Move each node to the same position.
|
||||||
for mesh, node in zip(meshes, group_node.getChildren()):
|
for mesh, node in zip(meshes, group_node.getChildren()):
|
||||||
orientation = node.getOrientation().toMatrix()
|
transformation = node.getLocalTransformation()
|
||||||
rotated_mesh = mesh.getTransformed(orientation)
|
transformation.setTranslation(zero_translation)
|
||||||
|
transformed_mesh = mesh.getTransformed(transformation)
|
||||||
|
|
||||||
# Align the object around its zero position
|
# Align the object around its zero position
|
||||||
# and also apply the offset to center it inside the group.
|
# and also apply the offset to center it inside the group.
|
||||||
node.setPosition(-rotated_mesh.getZeroPosition().scale(node.getScale()) - offset)
|
node.setPosition(-transformed_mesh.getZeroPosition() - offset)
|
||||||
|
|
||||||
# Use the previously found center of the group bounding box as the new location of the group
|
# Use the previously found center of the group bounding box as the new location of the group
|
||||||
group_node.setPosition(group_node.getBoundingBox().center)
|
group_node.setPosition(group_node.getBoundingBox().center)
|
||||||
|
@ -1007,7 +979,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
def _reloadMeshFinished(self, job):
|
def _reloadMeshFinished(self, job):
|
||||||
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
|
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
|
||||||
mesh_data = job.getResult().getMeshData()
|
mesh_data = job.getResult()[0].getMeshData()
|
||||||
if mesh_data:
|
if mesh_data:
|
||||||
job._node.setMeshData(mesh_data)
|
job._node.setMeshData(mesh_data)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -6,13 +6,21 @@ from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||||
|
|
||||||
from UM.PluginObject import PluginObject
|
from UM.PluginObject import PluginObject
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
from UM.Logger import Logger
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
## Machine actions are actions that are added to a specific machine type. Examples of such actions are
|
||||||
|
# updating the firmware, connecting with remote devices or doing bed leveling. A machine action can also have a
|
||||||
|
# qml, which should contain a "Cura.MachineAction" item. When activated, the item will be displayed in a dialog
|
||||||
|
# and this object will be added as "manager" (so all pyqtSlot() functions can be called by calling manager.func())
|
||||||
class MachineAction(QObject, PluginObject):
|
class MachineAction(QObject, PluginObject):
|
||||||
|
|
||||||
|
## Create a new Machine action.
|
||||||
|
# \param key unique key of the machine action
|
||||||
|
# \param label Human readable label used to identify the machine action.
|
||||||
def __init__(self, key, label = ""):
|
def __init__(self, key, label = ""):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._key = key
|
self._key = key
|
||||||
|
@ -63,13 +71,16 @@ class MachineAction(QObject, PluginObject):
|
||||||
def finished(self):
|
def finished(self):
|
||||||
return self._finished
|
return self._finished
|
||||||
|
|
||||||
|
## Protected helper to create a view object based on provided QML.
|
||||||
def _createViewFromQML(self):
|
def _createViewFromQML(self):
|
||||||
path = QUrl.fromLocalFile(
|
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
|
||||||
os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
|
|
||||||
self._component = QQmlComponent(Application.getInstance()._engine, path)
|
self._component = QQmlComponent(Application.getInstance()._engine, path)
|
||||||
self._context = QQmlContext(Application.getInstance()._engine.rootContext())
|
self._context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||||
self._context.setContextProperty("manager", self)
|
self._context.setContextProperty("manager", self)
|
||||||
self._view = self._component.create(self._context)
|
self._view = self._component.create(self._context)
|
||||||
|
if self._view is None:
|
||||||
|
Logger.log("c", "QQmlComponent status %s", self._component.status())
|
||||||
|
Logger.log("c", "QQmlComponent error string %s", self._component.errorString())
|
||||||
|
|
||||||
@pyqtProperty(QObject, constant = True)
|
@pyqtProperty(QObject, constant = True)
|
||||||
def displayItem(self):
|
def displayItem(self):
|
||||||
|
|
|
@ -49,7 +49,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
self._printer_state = ""
|
self._printer_state = ""
|
||||||
self._printer_type = "unknown"
|
self._printer_type = "unknown"
|
||||||
|
|
||||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False):
|
self._camera_active = False
|
||||||
|
|
||||||
|
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
|
||||||
raise NotImplementedError("requestWrite needs to be implemented")
|
raise NotImplementedError("requestWrite needs to be implemented")
|
||||||
|
|
||||||
## Signals
|
## Signals
|
||||||
|
@ -136,6 +138,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def startCamera(self):
|
def startCamera(self):
|
||||||
|
self._camera_active = True
|
||||||
self._startCamera()
|
self._startCamera()
|
||||||
|
|
||||||
def _startCamera(self):
|
def _startCamera(self):
|
||||||
|
@ -143,6 +146,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def stopCamera(self):
|
def stopCamera(self):
|
||||||
|
self._camera_active = False
|
||||||
self._stopCamera()
|
self._stopCamera()
|
||||||
|
|
||||||
def _stopCamera(self):
|
def _stopCamera(self):
|
||||||
|
|
|
@ -155,6 +155,18 @@ class ExtruderManager(QObject):
|
||||||
if changed:
|
if changed:
|
||||||
self.extrudersChanged.emit(machine_id)
|
self.extrudersChanged.emit(machine_id)
|
||||||
|
|
||||||
|
def registerExtruder(self, extruder_train, machine_id):
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
if machine_id not in self._extruder_trains:
|
||||||
|
self._extruder_trains[machine_id] = {}
|
||||||
|
changed = True
|
||||||
|
if extruder_train:
|
||||||
|
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
self.extrudersChanged.emit(machine_id)
|
||||||
|
|
||||||
## Creates a container stack for an extruder train.
|
## Creates a container stack for an extruder train.
|
||||||
#
|
#
|
||||||
# The container stack has an extruder definition at the bottom, which is
|
# The container stack has an extruder definition at the bottom, which is
|
||||||
|
|
|
@ -141,8 +141,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||||
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
|
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
|
||||||
extruder_name = extruder.getName()
|
extruder_name = extruder.getName()
|
||||||
material = extruder.findContainer({ "type": "material" })
|
material = extruder.findContainer({ "type": "material" })
|
||||||
if material and not self._simple_names:
|
|
||||||
extruder_name = "%s (%s)" % (material.getName(), extruder_name)
|
|
||||||
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
||||||
try:
|
try:
|
||||||
position = int(position)
|
position = int(position)
|
||||||
|
|
|
@ -13,6 +13,7 @@ from UM.Message import Message
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
from UM.Settings.SettingDefinition import SettingDefinition
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
from UM.Settings.Validator import ValidatorState
|
from UM.Settings.Validator import ValidatorState
|
||||||
|
|
||||||
|
@ -39,6 +40,10 @@ class MachineManager(QObject):
|
||||||
self.globalContainerChanged.connect(self.activeQualityChanged)
|
self.globalContainerChanged.connect(self.activeQualityChanged)
|
||||||
|
|
||||||
self._stacks_have_errors = None
|
self._stacks_have_errors = None
|
||||||
|
self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_variant")[0]
|
||||||
|
self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_material")[0]
|
||||||
|
self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0]
|
||||||
|
self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0]
|
||||||
self._onGlobalContainerChanged()
|
self._onGlobalContainerChanged()
|
||||||
|
|
||||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
||||||
|
@ -53,11 +58,6 @@ class MachineManager(QObject):
|
||||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
|
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
|
||||||
self.activeStackChanged.connect(self.activeStackValueChanged)
|
self.activeStackChanged.connect(self.activeStackValueChanged)
|
||||||
|
|
||||||
self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_variant")[0]
|
|
||||||
self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
|
|
||||||
self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0]
|
|
||||||
self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality_changes")[0]
|
|
||||||
|
|
||||||
Preferences.getInstance().addPreference("cura/active_machine", "")
|
Preferences.getInstance().addPreference("cura/active_machine", "")
|
||||||
|
|
||||||
self._global_event_keys = set()
|
self._global_event_keys = set()
|
||||||
|
@ -114,6 +114,10 @@ class MachineManager(QObject):
|
||||||
def printerOutputDevices(self):
|
def printerOutputDevices(self):
|
||||||
return self._printer_output_devices
|
return self._printer_output_devices
|
||||||
|
|
||||||
|
@pyqtProperty(int, constant=True)
|
||||||
|
def totalNumberOfSettings(self):
|
||||||
|
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
|
||||||
|
|
||||||
def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None:
|
def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None:
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return
|
return
|
||||||
|
@ -251,6 +255,7 @@ class MachineManager(QObject):
|
||||||
quality = self._global_container_stack.findContainer({"type": "quality"})
|
quality = self._global_container_stack.findContainer({"type": "quality"})
|
||||||
quality.nameChanged.connect(self._onQualityNameChanged)
|
quality.nameChanged.connect(self._onQualityNameChanged)
|
||||||
|
|
||||||
|
self._updateStacksHaveErrors()
|
||||||
|
|
||||||
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
|
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
|
||||||
def _updateStacksHaveErrors(self):
|
def _updateStacksHaveErrors(self):
|
||||||
|
@ -290,6 +295,8 @@ class MachineManager(QObject):
|
||||||
elif container_type == "quality":
|
elif container_type == "quality":
|
||||||
self.activeQualityChanged.emit()
|
self.activeQualityChanged.emit()
|
||||||
|
|
||||||
|
self._updateStacksHaveErrors()
|
||||||
|
|
||||||
def _onPropertyChanged(self, key, property_name):
|
def _onPropertyChanged(self, key, property_name):
|
||||||
if property_name == "value":
|
if property_name == "value":
|
||||||
# Notify UI items, such as the "changed" star in profile pull down menu.
|
# Notify UI items, such as the "changed" star in profile pull down menu.
|
||||||
|
@ -302,6 +309,16 @@ class MachineManager(QObject):
|
||||||
changed_validation_state = self._active_container_stack.getProperty(key, property_name)
|
changed_validation_state = self._active_container_stack.getProperty(key, property_name)
|
||||||
else:
|
else:
|
||||||
changed_validation_state = self._global_container_stack.getProperty(key, property_name)
|
changed_validation_state = self._global_container_stack.getProperty(key, property_name)
|
||||||
|
|
||||||
|
if changed_validation_state is None:
|
||||||
|
# Setting is not validated. This can happen if there is only a setting definition.
|
||||||
|
# We do need to validate it, because a setting defintions value can be set by a function, which could
|
||||||
|
# be an invalid setting.
|
||||||
|
definition = self._active_container_stack.getSettingDefinition(key)
|
||||||
|
validator_type = SettingDefinition.getValidatorForType(definition.type)
|
||||||
|
if validator_type:
|
||||||
|
validator = validator_type(key)
|
||||||
|
changed_validation_state = validator(self._active_container_stack)
|
||||||
if changed_validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
|
if changed_validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
|
||||||
self._stacks_have_errors = True
|
self._stacks_have_errors = True
|
||||||
self.stacksValidationChanged.emit()
|
self.stacksValidationChanged.emit()
|
||||||
|
@ -311,6 +328,7 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setActiveMachine(self, stack_id: str) -> None:
|
def setActiveMachine(self, stack_id: str) -> None:
|
||||||
|
self.blurSettings.emit() # Ensure no-one has focus.
|
||||||
containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
|
containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
|
||||||
if containers:
|
if containers:
|
||||||
Application.getInstance().setGlobalContainerStack(containers[0])
|
Application.getInstance().setGlobalContainerStack(containers[0])
|
||||||
|
@ -476,6 +494,16 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantList", notify = activeMaterialChanged)
|
||||||
|
def activeMaterialNames(self):
|
||||||
|
result = []
|
||||||
|
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
|
||||||
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
|
material_container = stack.findContainer(type="material")
|
||||||
|
if material_container and material_container != self._empty_material_container:
|
||||||
|
result.append(material_container.getName())
|
||||||
|
return result
|
||||||
|
|
||||||
@pyqtProperty(str, notify=activeMaterialChanged)
|
@pyqtProperty(str, notify=activeMaterialChanged)
|
||||||
def activeMaterialId(self) -> str:
|
def activeMaterialId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
|
@ -867,7 +895,8 @@ class MachineManager(QObject):
|
||||||
if old_container:
|
if old_container:
|
||||||
old_container.nameChanged.disconnect(self._onQualityNameChanged)
|
old_container.nameChanged.disconnect(self._onQualityNameChanged)
|
||||||
else:
|
else:
|
||||||
Logger.log("w", "Could not find old "+ container.getMetaDataEntry("type") + " while changing active " + container.getMetaDataEntry("type") + ".")
|
Logger.log("e", "Could not find container of type %s in stack %s while replacing quality (changes) with container %s", container.getMetaDataEntry("type"), stack.getId(), container.getId())
|
||||||
|
return
|
||||||
|
|
||||||
# Swap in the new container into the stack.
|
# Swap in the new container into the stack.
|
||||||
stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit)
|
stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit)
|
||||||
|
@ -878,7 +907,7 @@ class MachineManager(QObject):
|
||||||
def _askUserToKeepOrClearCurrentSettings(self):
|
def _askUserToKeepOrClearCurrentSettings(self):
|
||||||
# Ask the user if the user profile should be cleared or not (discarding the current settings)
|
# 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
|
# 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):")
|
details_text = catalog.i18nc("@label", "You made changes to the following setting(s)/override(s):")
|
||||||
|
|
||||||
# user changes in global stack
|
# user changes in global stack
|
||||||
details_list = [setting.definition.label for setting in self._global_container_stack.getTop().findInstances(**{})]
|
details_list = [setting.definition.label for setting in self._global_container_stack.getTop().findInstances(**{})]
|
||||||
|
@ -893,13 +922,18 @@ class MachineManager(QObject):
|
||||||
# Format to output string
|
# Format to output string
|
||||||
details = "\n ".join([details_text, ] + details_list)
|
details = "\n ".join([details_text, ] + details_list)
|
||||||
|
|
||||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"),
|
num_changed_settings = len(details_list)
|
||||||
catalog.i18nc("@label",
|
Application.getInstance().messageBox(
|
||||||
"Do you want to transfer your changed settings to this profile?"),
|
catalog.i18nc("@window:title", "Switched profiles"),
|
||||||
catalog.i18nc("@label",
|
catalog.i18nc(
|
||||||
"If you transfer your settings they will override settings in the profile."),
|
"@label",
|
||||||
|
"Do you want to transfer your %d changed setting(s)/override(s) to this profile?") % num_changed_settings,
|
||||||
|
catalog.i18nc(
|
||||||
|
"@label",
|
||||||
|
"If you transfer your settings they will override settings in the profile. If you don't transfer these settings, they will be lost."),
|
||||||
details,
|
details,
|
||||||
buttons=QMessageBox.Yes + QMessageBox.No, icon=QMessageBox.Question,
|
buttons=QMessageBox.Yes + QMessageBox.No,
|
||||||
|
icon=QMessageBox.Question,
|
||||||
callback=self._keepUserSettingsDialogCallback)
|
callback=self._keepUserSettingsDialogCallback)
|
||||||
|
|
||||||
def _keepUserSettingsDialogCallback(self, button):
|
def _keepUserSettingsDialogCallback(self, button):
|
||||||
|
@ -1170,8 +1204,9 @@ class MachineManager(QObject):
|
||||||
else:
|
else:
|
||||||
material_search_criteria["definition"] = "fdmprinter"
|
material_search_criteria["definition"] = "fdmprinter"
|
||||||
material_containers = container_registry.findInstanceContainers(**material_search_criteria)
|
material_containers = container_registry.findInstanceContainers(**material_search_criteria)
|
||||||
if material_containers:
|
# Try all materials to see if there is a quality profile available.
|
||||||
search_criteria["material"] = material_containers[0].getId()
|
for material_container in material_containers:
|
||||||
|
search_criteria["material"] = material_container.getId()
|
||||||
|
|
||||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
containers = container_registry.findInstanceContainers(**search_criteria)
|
||||||
if containers:
|
if containers:
|
||||||
|
|
|
@ -8,7 +8,7 @@ class MaterialSettingsVisibilityHandler(SettingVisibilityHandler):
|
||||||
super().__init__(parent = parent, *args, **kwargs)
|
super().__init__(parent = parent, *args, **kwargs)
|
||||||
|
|
||||||
material_settings = set([
|
material_settings = set([
|
||||||
"material_print_temperature",
|
"default_material_print_temperature",
|
||||||
"material_bed_temperature",
|
"material_bed_temperature",
|
||||||
"material_standby_temperature",
|
"material_standby_temperature",
|
||||||
"cool_fan_speed",
|
"cool_fan_speed",
|
||||||
|
|
|
@ -9,6 +9,10 @@ import UM.Logger
|
||||||
import UM.Qt
|
import UM.Qt
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
import os
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
|
||||||
|
|
||||||
class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||||
KeyRole = Qt.UserRole + 1
|
KeyRole = Qt.UserRole + 1
|
||||||
|
@ -28,6 +32,7 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||||
self._extruder_definition_id = None
|
self._extruder_definition_id = None
|
||||||
self._quality_id = None
|
self._quality_id = None
|
||||||
self._material_id = None
|
self._material_id = None
|
||||||
|
self._i18n_catalog = None
|
||||||
|
|
||||||
self.addRoleName(self.KeyRole, "key")
|
self.addRoleName(self.KeyRole, "key")
|
||||||
self.addRoleName(self.LabelRole, "label")
|
self.addRoleName(self.LabelRole, "label")
|
||||||
|
@ -117,6 +122,18 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||||
|
|
||||||
quality_type = quality_container.getMetaDataEntry("quality_type")
|
quality_type = quality_container.getMetaDataEntry("quality_type")
|
||||||
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition())
|
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition())
|
||||||
|
definition = quality_container.getDefinition()
|
||||||
|
|
||||||
|
# Check if the definition container has a translation file.
|
||||||
|
definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix
|
||||||
|
catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix))
|
||||||
|
if catalog.hasTranslationLoaded():
|
||||||
|
self._i18n_catalog = catalog
|
||||||
|
|
||||||
|
for file_name in quality_container.getDefinition().getInheritedFiles():
|
||||||
|
catalog = i18nCatalog(os.path.basename(file_name))
|
||||||
|
if catalog.hasTranslationLoaded():
|
||||||
|
self._i18n_catalog = catalog
|
||||||
|
|
||||||
criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
|
criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
|
||||||
|
|
||||||
|
@ -167,6 +184,8 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||||
for definition in definition_container.findDefinitions():
|
for definition in definition_container.findDefinitions():
|
||||||
if definition.type == "category":
|
if definition.type == "category":
|
||||||
current_category = definition.label
|
current_category = definition.label
|
||||||
|
if self._i18n_catalog:
|
||||||
|
current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
profile_value = None
|
profile_value = None
|
||||||
|
@ -177,6 +196,12 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||||
profile_value_source = container.getMetaDataEntry("type")
|
profile_value_source = container.getMetaDataEntry("type")
|
||||||
profile_value = new_value
|
profile_value = new_value
|
||||||
|
|
||||||
|
# Global tab should use resolve (if there is one)
|
||||||
|
if not self._extruder_id:
|
||||||
|
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
|
||||||
|
if resolve_value is not None and profile_value is not None:
|
||||||
|
profile_value = resolve_value
|
||||||
|
|
||||||
user_value = None
|
user_value = None
|
||||||
if not self._extruder_id:
|
if not self._extruder_id:
|
||||||
user_value = global_container_stack.getTop().getProperty(definition.key, "value")
|
user_value = global_container_stack.getTop().getProperty(definition.key, "value")
|
||||||
|
@ -198,9 +223,14 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||||
if self._extruder_id == "" and settable_per_extruder:
|
if self._extruder_id == "" and settable_per_extruder:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
label = definition.label
|
||||||
|
if self._i18n_catalog:
|
||||||
|
label = self._i18n_catalog.i18nc(definition.key + " label", label)
|
||||||
|
|
||||||
items.append({
|
items.append({
|
||||||
"key": definition.key,
|
"key": definition.key,
|
||||||
"label": definition.label,
|
"label": label,
|
||||||
"unit": definition.unit,
|
"unit": definition.unit,
|
||||||
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
|
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
|
||||||
"profile_value_source": profile_value_source,
|
"profile_value_source": profile_value_source,
|
||||||
|
|
|
@ -89,7 +89,7 @@ class SettingInheritanceManager(QObject):
|
||||||
self._update() # Ensure that the settings_with_inheritance_warning list is populated.
|
self._update() # Ensure that the settings_with_inheritance_warning list is populated.
|
||||||
|
|
||||||
def _onPropertyChanged(self, key, property_name):
|
def _onPropertyChanged(self, key, property_name):
|
||||||
if property_name == "value" and self._global_container_stack:
|
if (property_name == "value" or property_name == "enabled") and self._global_container_stack:
|
||||||
definitions = self._global_container_stack.getBottom().findDefinitions(key = key)
|
definitions = self._global_container_stack.getBottom().findDefinitions(key = key)
|
||||||
if not definitions:
|
if not definitions:
|
||||||
return
|
return
|
||||||
|
@ -171,7 +171,16 @@ class SettingInheritanceManager(QObject):
|
||||||
continue
|
continue
|
||||||
if value is not None:
|
if value is not None:
|
||||||
# If a setting doesn't use any keys, it won't change it's value, so treat it as if it's a fixed value
|
# If a setting doesn't use any keys, it won't change it's value, so treat it as if it's a fixed value
|
||||||
has_setting_function = isinstance(value, SettingFunction) and len(value.getUsedSettingKeys()) > 0
|
has_setting_function = isinstance(value, SettingFunction)
|
||||||
|
if has_setting_function:
|
||||||
|
for setting_key in value.getUsedSettingKeys():
|
||||||
|
if setting_key in self._active_container_stack.getAllKeys():
|
||||||
|
break # We found an actual setting. So has_setting_function can remain true
|
||||||
|
else:
|
||||||
|
# All of the setting_keys turned out to not be setting keys at all!
|
||||||
|
# This can happen due enum keys also being marked as settings.
|
||||||
|
has_setting_function = False
|
||||||
|
|
||||||
if has_setting_function is False:
|
if has_setting_function is False:
|
||||||
has_non_function_value = True
|
has_non_function_value = True
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -184,9 +184,22 @@ class ThreeMFReader(MeshReader):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
build_item_node = self._createNodeFromObject(object, self._base_name + "_" + str(id))
|
build_item_node = self._createNodeFromObject(object, self._base_name + "_" + str(id))
|
||||||
|
|
||||||
|
# compensate for original center position, if object(s) is/are not around its zero position
|
||||||
|
transform_matrix = Matrix()
|
||||||
|
mesh_data = build_item_node.getMeshData()
|
||||||
|
if mesh_data is not None:
|
||||||
|
extents = mesh_data.getExtents()
|
||||||
|
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
|
||||||
|
transform_matrix.setByTranslation(center_vector)
|
||||||
|
|
||||||
|
# offset with transform from 3mf
|
||||||
transform = build_item.get("transform")
|
transform = build_item.get("transform")
|
||||||
if transform is not None:
|
if transform is not None:
|
||||||
build_item_node.setTransformation(self._createMatrixFromTransformationString(transform))
|
transform_matrix.multiply(self._createMatrixFromTransformationString(transform))
|
||||||
|
|
||||||
|
build_item_node.setTransformation(transform_matrix)
|
||||||
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
# Create a transformation Matrix to convert from 3mf worldspace into ours.
|
# Create a transformation Matrix to convert from 3mf worldspace into ours.
|
||||||
|
|
474
plugins/3MFReader/ThreeMFWorkspaceReader.py
Normal file
474
plugins/3MFReader/ThreeMFWorkspaceReader.py
Normal file
|
@ -0,0 +1,474 @@
|
||||||
|
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
||||||
|
from UM.Application import Application
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.MimeTypeDatabase import MimeTypeDatabase
|
||||||
|
from UM.Job import Job
|
||||||
|
from UM.Preferences import Preferences
|
||||||
|
from .WorkspaceDialog import WorkspaceDialog
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
|
import zipfile
|
||||||
|
import io
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
## Base implementation for reading 3MF workspace files.
|
||||||
|
class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._supported_extensions = [".3mf"]
|
||||||
|
self._dialog = WorkspaceDialog()
|
||||||
|
self._3mf_mesh_reader = None
|
||||||
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
|
self._definition_container_suffix = ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
|
||||||
|
self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it
|
||||||
|
self._instance_container_suffix = ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
|
||||||
|
self._container_stack_suffix = ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
|
||||||
|
|
||||||
|
self._resolve_strategies = {}
|
||||||
|
|
||||||
|
self._id_mapping = {}
|
||||||
|
|
||||||
|
## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
|
||||||
|
# This has nothing to do with speed, but with getting consistent new naming for instances & objects.
|
||||||
|
def getNewId(self, old_id):
|
||||||
|
if old_id not in self._id_mapping:
|
||||||
|
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
|
||||||
|
return self._id_mapping[old_id]
|
||||||
|
|
||||||
|
def preRead(self, file_name):
|
||||||
|
self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
|
||||||
|
if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
|
||||||
|
return WorkspaceReader.PreReadResult.failed
|
||||||
|
machine_name = ""
|
||||||
|
# Check if there are any conflicts, so we can ask the user.
|
||||||
|
archive = zipfile.ZipFile(file_name, "r")
|
||||||
|
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
|
||||||
|
container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
|
||||||
|
self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
|
||||||
|
machine_conflict = False
|
||||||
|
quality_changes_conflict = False
|
||||||
|
for container_stack_file in container_stack_files:
|
||||||
|
container_id = self._stripFileToId(container_stack_file)
|
||||||
|
serialized = archive.open(container_stack_file).read().decode("utf-8")
|
||||||
|
if machine_name == "":
|
||||||
|
machine_name = self._getMachineNameFromSerializedStack(serialized)
|
||||||
|
stacks = self._container_registry.findContainerStacks(id=container_id)
|
||||||
|
if stacks:
|
||||||
|
# Check if there are any changes at all in any of the container stacks.
|
||||||
|
id_list = self._getContainerIdListFromSerialized(serialized)
|
||||||
|
for index, container_id in enumerate(id_list):
|
||||||
|
if stacks[0].getContainer(index).getId() != container_id:
|
||||||
|
machine_conflict = True
|
||||||
|
Job.yieldThread()
|
||||||
|
|
||||||
|
material_labels = []
|
||||||
|
material_conflict = False
|
||||||
|
xml_material_profile = self._getXmlProfileClass()
|
||||||
|
if self._material_container_suffix is None:
|
||||||
|
self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix
|
||||||
|
if xml_material_profile:
|
||||||
|
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
|
||||||
|
for material_container_file in material_container_files:
|
||||||
|
container_id = self._stripFileToId(material_container_file)
|
||||||
|
materials = self._container_registry.findInstanceContainers(id=container_id)
|
||||||
|
material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8")))
|
||||||
|
if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict
|
||||||
|
material_conflict = True
|
||||||
|
Job.yieldThread()
|
||||||
|
# Check if any quality_changes instance container is in conflict.
|
||||||
|
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||||
|
quality_name = ""
|
||||||
|
quality_type = ""
|
||||||
|
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
|
||||||
|
for instance_container_file in instance_container_files:
|
||||||
|
container_id = self._stripFileToId(instance_container_file)
|
||||||
|
instance_container = InstanceContainer(container_id)
|
||||||
|
|
||||||
|
# Deserialize InstanceContainer by converting read data from bytes to string
|
||||||
|
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
||||||
|
container_type = instance_container.getMetaDataEntry("type")
|
||||||
|
if container_type == "quality_changes":
|
||||||
|
quality_name = instance_container.getName()
|
||||||
|
num_settings_overriden_by_quality_changes += len(instance_container._instances)
|
||||||
|
# Check if quality changes already exists.
|
||||||
|
quality_changes = self._container_registry.findInstanceContainers(id = container_id)
|
||||||
|
if quality_changes:
|
||||||
|
# Check if there really is a conflict by comparing the values
|
||||||
|
if quality_changes[0] != instance_container:
|
||||||
|
quality_changes_conflict = True
|
||||||
|
elif container_type == "quality":
|
||||||
|
# If the quality name is not set (either by quality or changes, set it now)
|
||||||
|
# Quality changes should always override this (as they are "on top")
|
||||||
|
if quality_name == "":
|
||||||
|
quality_name = instance_container.getName()
|
||||||
|
quality_type = instance_container.getName()
|
||||||
|
Job.yieldThread()
|
||||||
|
num_visible_settings = 0
|
||||||
|
try:
|
||||||
|
temp_preferences = Preferences()
|
||||||
|
temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks.
|
||||||
|
|
||||||
|
visible_settings_string = temp_preferences.getValue("general/visible_settings")
|
||||||
|
if visible_settings_string is not None:
|
||||||
|
num_visible_settings = len(visible_settings_string.split(";"))
|
||||||
|
active_mode = temp_preferences.getValue("cura/active_mode")
|
||||||
|
if not active_mode:
|
||||||
|
active_mode = Preferences.getInstance().getValue("cura/active_mode")
|
||||||
|
except KeyError:
|
||||||
|
# If there is no preferences file, it's not a workspace, so notify user of failure.
|
||||||
|
Logger.log("w", "File %s is not a valid workspace.", file_name)
|
||||||
|
return WorkspaceReader.PreReadResult.failed
|
||||||
|
|
||||||
|
# Show the dialog, informing the user what is about to happen.
|
||||||
|
self._dialog.setMachineConflict(machine_conflict)
|
||||||
|
self._dialog.setQualityChangesConflict(quality_changes_conflict)
|
||||||
|
self._dialog.setMaterialConflict(material_conflict)
|
||||||
|
self._dialog.setNumVisibleSettings(num_visible_settings)
|
||||||
|
self._dialog.setQualityName(quality_name)
|
||||||
|
self._dialog.setQualityType(quality_type)
|
||||||
|
self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
|
||||||
|
self._dialog.setActiveMode(active_mode)
|
||||||
|
self._dialog.setMachineName(machine_name)
|
||||||
|
self._dialog.setMaterialLabels(material_labels)
|
||||||
|
self._dialog.setHasObjectsOnPlate(Application.getInstance().getPlatformActivity)
|
||||||
|
self._dialog.show()
|
||||||
|
|
||||||
|
# Block until the dialog is closed.
|
||||||
|
self._dialog.waitForClose()
|
||||||
|
|
||||||
|
if self._dialog.getResult() == {}:
|
||||||
|
return WorkspaceReader.PreReadResult.cancelled
|
||||||
|
|
||||||
|
self._resolve_strategies = self._dialog.getResult()
|
||||||
|
|
||||||
|
return WorkspaceReader.PreReadResult.accepted
|
||||||
|
|
||||||
|
def read(self, file_name):
|
||||||
|
archive = zipfile.ZipFile(file_name, "r")
|
||||||
|
|
||||||
|
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
|
||||||
|
|
||||||
|
# Create a shadow copy of the preferences (we don't want all of the preferences, but we do want to re-use its
|
||||||
|
# parsing code.
|
||||||
|
temp_preferences = Preferences()
|
||||||
|
temp_preferences.readFromFile(io.TextIOWrapper(archive.open("Cura/preferences.cfg"))) # We need to wrap it, else the archive parser breaks.
|
||||||
|
|
||||||
|
# Copy a number of settings from the temp preferences to the global
|
||||||
|
global_preferences = Preferences.getInstance()
|
||||||
|
|
||||||
|
visible_settings = temp_preferences.getValue("general/visible_settings")
|
||||||
|
if visible_settings is None:
|
||||||
|
Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
|
||||||
|
else:
|
||||||
|
global_preferences.setValue("general/visible_settings", visible_settings)
|
||||||
|
|
||||||
|
categories_expanded = temp_preferences.getValue("cura/categories_expanded")
|
||||||
|
if categories_expanded is None:
|
||||||
|
Logger.log("w", "Workspace did not contain expanded categories. Leaving them unchanged")
|
||||||
|
else:
|
||||||
|
global_preferences.setValue("cura/categories_expanded", categories_expanded)
|
||||||
|
|
||||||
|
Application.getInstance().expandedCategoriesChanged.emit() # Notify the GUI of the change
|
||||||
|
|
||||||
|
self._id_mapping = {}
|
||||||
|
|
||||||
|
# We don't add containers right away, but wait right until right before the stack serialization.
|
||||||
|
# We do this so that if something goes wrong, it's easier to clean up.
|
||||||
|
containers_to_add = []
|
||||||
|
|
||||||
|
# TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
|
||||||
|
# TODO: cases that the container loaded is the same (most notable in materials & definitions).
|
||||||
|
# TODO: It might be possible that we need to add smarter checking in the future.
|
||||||
|
Logger.log("d", "Workspace loading is checking definitions...")
|
||||||
|
# Get all the definition files & check if they exist. If not, add them.
|
||||||
|
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
||||||
|
for definition_container_file in definition_container_files:
|
||||||
|
container_id = self._stripFileToId(definition_container_file)
|
||||||
|
definitions = self._container_registry.findDefinitionContainers(id=container_id)
|
||||||
|
if not definitions:
|
||||||
|
definition_container = DefinitionContainer(container_id)
|
||||||
|
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
|
||||||
|
self._container_registry.addContainer(definition_container)
|
||||||
|
Job.yieldThread()
|
||||||
|
|
||||||
|
Logger.log("d", "Workspace loading is checking materials...")
|
||||||
|
material_containers = []
|
||||||
|
# Get all the material files and check if they exist. If not, add them.
|
||||||
|
xml_material_profile = self._getXmlProfileClass()
|
||||||
|
if self._material_container_suffix is None:
|
||||||
|
self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
|
||||||
|
if xml_material_profile:
|
||||||
|
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
|
||||||
|
for material_container_file in material_container_files:
|
||||||
|
container_id = self._stripFileToId(material_container_file)
|
||||||
|
materials = self._container_registry.findInstanceContainers(id=container_id)
|
||||||
|
if not materials:
|
||||||
|
material_container = xml_material_profile(container_id)
|
||||||
|
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||||
|
containers_to_add.append(material_container)
|
||||||
|
else:
|
||||||
|
if not materials[0].isReadOnly(): # Only create new materials if they are not read only.
|
||||||
|
if self._resolve_strategies["material"] == "override":
|
||||||
|
materials[0].deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||||
|
elif self._resolve_strategies["material"] == "new":
|
||||||
|
# Note that we *must* deserialize it with a new ID, as multiple containers will be
|
||||||
|
# auto created & added.
|
||||||
|
material_container = xml_material_profile(self.getNewId(container_id))
|
||||||
|
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||||
|
containers_to_add.append(material_container)
|
||||||
|
material_containers.append(material_container)
|
||||||
|
Job.yieldThread()
|
||||||
|
|
||||||
|
Logger.log("d", "Workspace loading is checking instance containers...")
|
||||||
|
# Get quality_changes and user profiles saved in the workspace
|
||||||
|
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||||
|
user_instance_containers = []
|
||||||
|
quality_changes_instance_containers = []
|
||||||
|
for instance_container_file in instance_container_files:
|
||||||
|
container_id = self._stripFileToId(instance_container_file)
|
||||||
|
instance_container = InstanceContainer(container_id)
|
||||||
|
|
||||||
|
# Deserialize InstanceContainer by converting read data from bytes to string
|
||||||
|
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
||||||
|
container_type = instance_container.getMetaDataEntry("type")
|
||||||
|
Job.yieldThread()
|
||||||
|
if container_type == "user":
|
||||||
|
# Check if quality changes already exists.
|
||||||
|
user_containers = self._container_registry.findInstanceContainers(id=container_id)
|
||||||
|
if not user_containers:
|
||||||
|
containers_to_add.append(instance_container)
|
||||||
|
else:
|
||||||
|
if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
|
||||||
|
user_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
||||||
|
elif self._resolve_strategies["machine"] == "new":
|
||||||
|
# The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
|
||||||
|
extruder_id = instance_container.getMetaDataEntry("extruder", None)
|
||||||
|
if extruder_id:
|
||||||
|
new_id = self.getNewId(extruder_id) + "_current_settings"
|
||||||
|
instance_container._id = new_id
|
||||||
|
instance_container.setName(new_id)
|
||||||
|
instance_container.setMetaDataEntry("extruder", self.getNewId(extruder_id))
|
||||||
|
containers_to_add.append(instance_container)
|
||||||
|
|
||||||
|
machine_id = instance_container.getMetaDataEntry("machine", None)
|
||||||
|
if machine_id:
|
||||||
|
new_id = self.getNewId(machine_id) + "_current_settings"
|
||||||
|
instance_container._id = new_id
|
||||||
|
instance_container.setName(new_id)
|
||||||
|
instance_container.setMetaDataEntry("machine", self.getNewId(machine_id))
|
||||||
|
containers_to_add.append(instance_container)
|
||||||
|
user_instance_containers.append(instance_container)
|
||||||
|
elif container_type == "quality_changes":
|
||||||
|
# Check if quality changes already exists.
|
||||||
|
quality_changes = self._container_registry.findInstanceContainers(id = container_id)
|
||||||
|
if not quality_changes:
|
||||||
|
containers_to_add.append(instance_container)
|
||||||
|
else:
|
||||||
|
if self._resolve_strategies["quality_changes"] == "override":
|
||||||
|
quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
||||||
|
elif self._resolve_strategies["quality_changes"] is None:
|
||||||
|
# The ID already exists, but nothing in the values changed, so do nothing.
|
||||||
|
pass
|
||||||
|
quality_changes_instance_containers.append(instance_container)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add all the containers right before we try to add / serialize the stack
|
||||||
|
for container in containers_to_add:
|
||||||
|
self._container_registry.addContainer(container)
|
||||||
|
container.setDirty(True)
|
||||||
|
|
||||||
|
# Get the stack(s) saved in the workspace.
|
||||||
|
Logger.log("d", "Workspace loading is checking stacks containers...")
|
||||||
|
container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
|
||||||
|
global_stack = None
|
||||||
|
extruder_stacks = []
|
||||||
|
container_stacks_added = []
|
||||||
|
try:
|
||||||
|
for container_stack_file in container_stack_files:
|
||||||
|
container_id = self._stripFileToId(container_stack_file)
|
||||||
|
|
||||||
|
# Check if a stack by this ID already exists;
|
||||||
|
container_stacks = self._container_registry.findContainerStacks(id=container_id)
|
||||||
|
if container_stacks:
|
||||||
|
stack = container_stacks[0]
|
||||||
|
if self._resolve_strategies["machine"] == "override":
|
||||||
|
# TODO: HACK
|
||||||
|
# There is a machine, check if it has authenticationd data. If so, keep that data.
|
||||||
|
network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
|
||||||
|
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
|
||||||
|
container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8"))
|
||||||
|
if network_authentication_id:
|
||||||
|
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
|
||||||
|
if network_authentication_key:
|
||||||
|
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
|
||||||
|
elif self._resolve_strategies["machine"] == "new":
|
||||||
|
new_id = self.getNewId(container_id)
|
||||||
|
stack = ContainerStack(new_id)
|
||||||
|
stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
|
||||||
|
|
||||||
|
# Ensure a unique ID and name
|
||||||
|
stack._id = new_id
|
||||||
|
|
||||||
|
# Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
|
||||||
|
# bound machine also needs to change.
|
||||||
|
if stack.getMetaDataEntry("machine", None):
|
||||||
|
stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine")))
|
||||||
|
|
||||||
|
if stack.getMetaDataEntry("type") != "extruder_train":
|
||||||
|
# Only machines need a new name, stacks may be non-unique
|
||||||
|
stack.setName(self._container_registry.uniqueName(stack.getName()))
|
||||||
|
container_stacks_added.append(stack)
|
||||||
|
self._container_registry.addContainer(stack)
|
||||||
|
else:
|
||||||
|
Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
|
||||||
|
else:
|
||||||
|
stack = ContainerStack(container_id)
|
||||||
|
# Deserialize stack by converting read data from bytes to string
|
||||||
|
stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
|
||||||
|
container_stacks_added.append(stack)
|
||||||
|
self._container_registry.addContainer(stack)
|
||||||
|
|
||||||
|
if stack.getMetaDataEntry("type") == "extruder_train":
|
||||||
|
extruder_stacks.append(stack)
|
||||||
|
else:
|
||||||
|
global_stack = stack
|
||||||
|
Job.yieldThread()
|
||||||
|
except:
|
||||||
|
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
|
||||||
|
# Something went really wrong. Try to remove any data that we added.
|
||||||
|
for container in containers_to_add:
|
||||||
|
self._container_registry.getInstance().removeContainer(container.getId())
|
||||||
|
|
||||||
|
for container in container_stacks_added:
|
||||||
|
self._container_registry.getInstance().removeContainer(container.getId())
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self._resolve_strategies["machine"] == "new":
|
||||||
|
# A new machine was made, but it was serialized with the wrong user container. Fix that now.
|
||||||
|
for container in user_instance_containers:
|
||||||
|
extruder_id = container.getMetaDataEntry("extruder", None)
|
||||||
|
if extruder_id:
|
||||||
|
for extruder in extruder_stacks:
|
||||||
|
if extruder.getId() == extruder_id:
|
||||||
|
extruder.replaceContainer(0, container)
|
||||||
|
continue
|
||||||
|
machine_id = container.getMetaDataEntry("machine", None)
|
||||||
|
if machine_id:
|
||||||
|
if global_stack.getId() == machine_id:
|
||||||
|
global_stack.replaceContainer(0, container)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self._resolve_strategies["quality_changes"] == "new":
|
||||||
|
# Quality changes needs to get a new ID, added to registry and to the right stacks
|
||||||
|
for container in quality_changes_instance_containers:
|
||||||
|
old_id = container.getId()
|
||||||
|
container.setName(self._container_registry.uniqueName(container.getName()))
|
||||||
|
# We're not really supposed to change the ID in normal cases, but this is an exception.
|
||||||
|
container._id = self.getNewId(container.getId())
|
||||||
|
|
||||||
|
# The container was not added yet, as it didn't have an unique ID. It does now, so add it.
|
||||||
|
self._container_registry.addContainer(container)
|
||||||
|
|
||||||
|
# Replace the quality changes container
|
||||||
|
old_container = global_stack.findContainer({"type": "quality_changes"})
|
||||||
|
if old_container.getId() == old_id:
|
||||||
|
quality_changes_index = global_stack.getContainerIndex(old_container)
|
||||||
|
global_stack.replaceContainer(quality_changes_index, container)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for stack in extruder_stacks:
|
||||||
|
old_container = stack.findContainer({"type": "quality_changes"})
|
||||||
|
if old_container.getId() == old_id:
|
||||||
|
quality_changes_index = stack.getContainerIndex(old_container)
|
||||||
|
stack.replaceContainer(quality_changes_index, container)
|
||||||
|
|
||||||
|
if self._resolve_strategies["material"] == "new":
|
||||||
|
for material in material_containers:
|
||||||
|
old_material = global_stack.findContainer({"type": "material"})
|
||||||
|
if old_material.getId() in self._id_mapping:
|
||||||
|
material_index = global_stack.getContainerIndex(old_material)
|
||||||
|
global_stack.replaceContainer(material_index, material)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for stack in extruder_stacks:
|
||||||
|
old_material = stack.findContainer({"type": "material"})
|
||||||
|
if old_material.getId() in self._id_mapping:
|
||||||
|
material_index = stack.getContainerIndex(old_material)
|
||||||
|
stack.replaceContainer(material_index, material)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for stack in extruder_stacks:
|
||||||
|
ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
|
||||||
|
else:
|
||||||
|
# Machine has no extruders, but it needs to be registered with the extruder manager.
|
||||||
|
ExtruderManager.getInstance().registerExtruder(None, global_stack.getId())
|
||||||
|
|
||||||
|
Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
|
||||||
|
|
||||||
|
# Notify everything/one that is to notify about changes.
|
||||||
|
global_stack.containersChanged.emit(global_stack.getTop())
|
||||||
|
|
||||||
|
for stack in extruder_stacks:
|
||||||
|
stack.setNextStack(global_stack)
|
||||||
|
stack.containersChanged.emit(stack.getTop())
|
||||||
|
|
||||||
|
# Actually change the active machine.
|
||||||
|
Application.getInstance().setGlobalContainerStack(global_stack)
|
||||||
|
|
||||||
|
# Load all the nodes / meshdata of the workspace
|
||||||
|
nodes = self._3mf_mesh_reader.read(file_name)
|
||||||
|
if nodes is None:
|
||||||
|
nodes = []
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
def _stripFileToId(self, file):
|
||||||
|
return file.replace("Cura/", "").split(".")[0]
|
||||||
|
|
||||||
|
def _getXmlProfileClass(self):
|
||||||
|
return self._container_registry.getContainerForMimeType(MimeTypeDatabase.getMimeType("application/x-ultimaker-material-profile"))
|
||||||
|
|
||||||
|
## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data.
|
||||||
|
def _getContainerIdListFromSerialized(self, serialized):
|
||||||
|
parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
container_ids = []
|
||||||
|
if "containers" in parser:
|
||||||
|
for index, container_id in parser.items("containers"):
|
||||||
|
container_ids.append(container_id)
|
||||||
|
elif parser.has_option("general", "containers"):
|
||||||
|
container_string = parser["general"].get("containers", "")
|
||||||
|
container_list = container_string.split(",")
|
||||||
|
container_ids = [container_id for container_id in container_list if container_id != ""]
|
||||||
|
|
||||||
|
return container_ids
|
||||||
|
|
||||||
|
def _getMachineNameFromSerializedStack(self, serialized):
|
||||||
|
parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
return parser["general"].get("name", "")
|
||||||
|
|
||||||
|
def _getMaterialLabelFromSerialized(self, serialized):
|
||||||
|
data = ET.fromstring(serialized)
|
||||||
|
metadata = data.iterfind("./um:metadata/um:name/um:label", {"um": "http://www.ultimaker.com/material"})
|
||||||
|
for entry in metadata:
|
||||||
|
return entry.text
|
||||||
|
pass
|
||||||
|
|
228
plugins/3MFReader/WorkspaceDialog.py
Normal file
228
plugins/3MFReader/WorkspaceDialog.py
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
# Copyright (c) 2016 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, pyqtSlot, QObject, pyqtProperty, QCoreApplication
|
||||||
|
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||||
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceDialog(QObject):
|
||||||
|
showDialogSignal = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._component = None
|
||||||
|
self._context = None
|
||||||
|
self._view = None
|
||||||
|
self._qml_url = "WorkspaceDialog.qml"
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self._default_strategy = "override"
|
||||||
|
self._result = {"machine": self._default_strategy,
|
||||||
|
"quality_changes": self._default_strategy,
|
||||||
|
"material": self._default_strategy}
|
||||||
|
self._visible = False
|
||||||
|
self.showDialogSignal.connect(self.__show)
|
||||||
|
|
||||||
|
self._has_quality_changes_conflict = False
|
||||||
|
self._has_machine_conflict = False
|
||||||
|
self._has_material_conflict = False
|
||||||
|
self._num_visible_settings = 0
|
||||||
|
self._active_mode = ""
|
||||||
|
self._quality_name = ""
|
||||||
|
self._num_settings_overriden_by_quality_changes = 0
|
||||||
|
self._quality_type = ""
|
||||||
|
self._machine_name = ""
|
||||||
|
self._material_labels = []
|
||||||
|
self._objects_on_plate = False
|
||||||
|
|
||||||
|
machineConflictChanged = pyqtSignal()
|
||||||
|
qualityChangesConflictChanged = pyqtSignal()
|
||||||
|
materialConflictChanged = pyqtSignal()
|
||||||
|
numVisibleSettingsChanged = pyqtSignal()
|
||||||
|
activeModeChanged = pyqtSignal()
|
||||||
|
qualityNameChanged = pyqtSignal()
|
||||||
|
numSettingsOverridenByQualityChangesChanged = pyqtSignal()
|
||||||
|
qualityTypeChanged = pyqtSignal()
|
||||||
|
machineNameChanged = pyqtSignal()
|
||||||
|
materialLabelsChanged = pyqtSignal()
|
||||||
|
objectsOnPlateChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=objectsOnPlateChanged)
|
||||||
|
def hasObjectsOnPlate(self):
|
||||||
|
return self._objects_on_plate
|
||||||
|
|
||||||
|
def setHasObjectsOnPlate(self, objects_on_plate):
|
||||||
|
self._objects_on_plate = objects_on_plate
|
||||||
|
self.objectsOnPlateChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantList", notify = materialLabelsChanged)
|
||||||
|
def materialLabels(self):
|
||||||
|
return self._material_labels
|
||||||
|
|
||||||
|
def setMaterialLabels(self, material_labels):
|
||||||
|
self._material_labels = material_labels
|
||||||
|
self.materialLabelsChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify = machineNameChanged)
|
||||||
|
def machineName(self):
|
||||||
|
return self._machine_name
|
||||||
|
|
||||||
|
def setMachineName(self, machine_name):
|
||||||
|
self._machine_name = machine_name
|
||||||
|
self.machineNameChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=qualityTypeChanged)
|
||||||
|
def qualityType(self):
|
||||||
|
return self._quality_type
|
||||||
|
|
||||||
|
def setQualityType(self, quality_type):
|
||||||
|
self._quality_type = quality_type
|
||||||
|
self.qualityTypeChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(int, notify=numSettingsOverridenByQualityChangesChanged)
|
||||||
|
def numSettingsOverridenByQualityChanges(self):
|
||||||
|
return self._num_settings_overriden_by_quality_changes
|
||||||
|
|
||||||
|
def setNumSettingsOverridenByQualityChanges(self, num_settings_overriden_by_quality_changes):
|
||||||
|
self._num_settings_overriden_by_quality_changes = num_settings_overriden_by_quality_changes
|
||||||
|
self.numSettingsOverridenByQualityChangesChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=qualityNameChanged)
|
||||||
|
def qualityName(self):
|
||||||
|
return self._quality_name
|
||||||
|
|
||||||
|
def setQualityName(self, quality_name):
|
||||||
|
self._quality_name = quality_name
|
||||||
|
self.qualityNameChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=activeModeChanged)
|
||||||
|
def activeMode(self):
|
||||||
|
return self._active_mode
|
||||||
|
|
||||||
|
def setActiveMode(self, active_mode):
|
||||||
|
if active_mode == 0:
|
||||||
|
self._active_mode = i18n_catalog.i18nc("@title:tab", "Recommended")
|
||||||
|
else:
|
||||||
|
self._active_mode = i18n_catalog.i18nc("@title:tab", "Custom")
|
||||||
|
self.activeModeChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(int, constant = True)
|
||||||
|
def totalNumberOfSettings(self):
|
||||||
|
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
|
||||||
|
|
||||||
|
@pyqtProperty(int, notify = numVisibleSettingsChanged)
|
||||||
|
def numVisibleSettings(self):
|
||||||
|
return self._num_visible_settings
|
||||||
|
|
||||||
|
def setNumVisibleSettings(self, num_visible_settings):
|
||||||
|
self._num_visible_settings = num_visible_settings
|
||||||
|
self.numVisibleSettingsChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = machineConflictChanged)
|
||||||
|
def machineConflict(self):
|
||||||
|
return self._has_machine_conflict
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=qualityChangesConflictChanged)
|
||||||
|
def qualityChangesConflict(self):
|
||||||
|
return self._has_quality_changes_conflict
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=materialConflictChanged)
|
||||||
|
def materialConflict(self):
|
||||||
|
return self._has_material_conflict
|
||||||
|
|
||||||
|
@pyqtSlot(str, str)
|
||||||
|
def setResolveStrategy(self, key, strategy):
|
||||||
|
if key in self._result:
|
||||||
|
self._result[key] = strategy
|
||||||
|
|
||||||
|
def setMaterialConflict(self, material_conflict):
|
||||||
|
self._has_material_conflict = material_conflict
|
||||||
|
self.materialConflictChanged.emit()
|
||||||
|
|
||||||
|
def setMachineConflict(self, machine_conflict):
|
||||||
|
self._has_machine_conflict = machine_conflict
|
||||||
|
self.machineConflictChanged.emit()
|
||||||
|
|
||||||
|
def setQualityChangesConflict(self, quality_changes_conflict):
|
||||||
|
self._has_quality_changes_conflict = quality_changes_conflict
|
||||||
|
self.qualityChangesConflictChanged.emit()
|
||||||
|
|
||||||
|
def getResult(self):
|
||||||
|
if "machine" in self._result and not self._has_machine_conflict:
|
||||||
|
self._result["machine"] = None
|
||||||
|
if "quality_changes" in self._result and not self._has_quality_changes_conflict:
|
||||||
|
self._result["quality_changes"] = None
|
||||||
|
if "material" in self._result and not self._has_material_conflict:
|
||||||
|
self._result["material"] = None
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
def _createViewFromQML(self):
|
||||||
|
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url))
|
||||||
|
self._component = QQmlComponent(Application.getInstance()._engine, path)
|
||||||
|
self._context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||||
|
self._context.setContextProperty("manager", self)
|
||||||
|
self._view = self._component.create(self._context)
|
||||||
|
if self._view is None:
|
||||||
|
Logger.log("c", "QQmlComponent status %s", self._component.status())
|
||||||
|
Logger.log("c", "QQmlComponent error string %s", self._component.errorString())
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
# Emit signal so the right thread actually shows the view.
|
||||||
|
if threading.current_thread() != threading.main_thread():
|
||||||
|
self._lock.acquire()
|
||||||
|
# Reset the result
|
||||||
|
self._result = {"machine": self._default_strategy,
|
||||||
|
"quality_changes": self._default_strategy,
|
||||||
|
"material": self._default_strategy}
|
||||||
|
self._visible = True
|
||||||
|
self.showDialogSignal.emit()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
## Used to notify the dialog so the lock can be released.
|
||||||
|
def notifyClosed(self):
|
||||||
|
self._result = {}
|
||||||
|
self._visible = False
|
||||||
|
self._lock.release()
|
||||||
|
|
||||||
|
def hide(self):
|
||||||
|
self._visible = False
|
||||||
|
self._lock.release()
|
||||||
|
self._view.hide()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def onOkButtonClicked(self):
|
||||||
|
self._view.hide()
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def onCancelButtonClicked(self):
|
||||||
|
self._view.hide()
|
||||||
|
self.hide()
|
||||||
|
self._result = {}
|
||||||
|
|
||||||
|
## Block thread until the dialog is closed.
|
||||||
|
def waitForClose(self):
|
||||||
|
if self._visible:
|
||||||
|
if threading.current_thread() != threading.main_thread():
|
||||||
|
self._lock.acquire()
|
||||||
|
self._lock.release()
|
||||||
|
else:
|
||||||
|
# If this is not run from a separate thread, we need to ensure that the events are still processed.
|
||||||
|
while self._visible:
|
||||||
|
time.sleep(1 / 50)
|
||||||
|
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
|
||||||
|
|
||||||
|
def __show(self):
|
||||||
|
if self._view is None:
|
||||||
|
self._createViewFromQML()
|
||||||
|
if self._view:
|
||||||
|
self._view.show()
|
319
plugins/3MFReader/WorkspaceDialog.qml
Normal file
319
plugins/3MFReader/WorkspaceDialog.qml
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
// Copyright (c) 2016 Ultimaker B.V.
|
||||||
|
// Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import QtQuick 2.1
|
||||||
|
import QtQuick.Controls 1.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import QtQuick.Window 2.1
|
||||||
|
|
||||||
|
import UM 1.1 as UM
|
||||||
|
|
||||||
|
UM.Dialog
|
||||||
|
{
|
||||||
|
title: catalog.i18nc("@title:window", "Open Project")
|
||||||
|
|
||||||
|
width: 550
|
||||||
|
minimumWidth: 550
|
||||||
|
maximumWidth: 550
|
||||||
|
|
||||||
|
height: 350
|
||||||
|
minimumHeight: 350
|
||||||
|
maximumHeight: 350
|
||||||
|
property int comboboxHeight: 15
|
||||||
|
property int spacerHeight: 10
|
||||||
|
onClosing: manager.notifyClosed()
|
||||||
|
onVisibleChanged:
|
||||||
|
{
|
||||||
|
if(visible)
|
||||||
|
{
|
||||||
|
machineResolveComboBox.currentIndex = 0
|
||||||
|
qualityChangesResolveComboBox.currentIndex = 0
|
||||||
|
materialConflictComboBox.currentIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
UM.I18nCatalog
|
||||||
|
{
|
||||||
|
id: catalog;
|
||||||
|
name: "cura";
|
||||||
|
}
|
||||||
|
|
||||||
|
ListModel
|
||||||
|
{
|
||||||
|
id: resolveStrategiesModel
|
||||||
|
// Instead of directly adding the list elements, we add them afterwards.
|
||||||
|
// This is because it's impossible to use setting function results to be bound to listElement properties directly.
|
||||||
|
// See http://stackoverflow.com/questions/7659442/listelement-fields-as-properties
|
||||||
|
Component.onCompleted:
|
||||||
|
{
|
||||||
|
append({"key": "override", "label": catalog.i18nc("@action:ComboBox option", "Update existing")});
|
||||||
|
append({"key": "new", "label": catalog.i18nc("@action:ComboBox option", "Create new")});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 2
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: titleLabel
|
||||||
|
text: catalog.i18nc("@action:title", "Summary - Cura Project")
|
||||||
|
font.pixelSize: 22
|
||||||
|
}
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
id: separator
|
||||||
|
color: "black"
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Printer settings")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Name")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: manager.machineName
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.TooltipArea
|
||||||
|
{
|
||||||
|
id: machineResolveTooltip
|
||||||
|
width: parent.width / 3
|
||||||
|
height: visible ? comboboxHeight : 0
|
||||||
|
visible: manager.machineConflict
|
||||||
|
text: catalog.i18nc("@info:tooltip", "How should the conflict in the machine be resolved?")
|
||||||
|
ComboBox
|
||||||
|
{
|
||||||
|
model: resolveStrategiesModel
|
||||||
|
textRole: "label"
|
||||||
|
id: machineResolveComboBox
|
||||||
|
width: parent.width
|
||||||
|
onActivated:
|
||||||
|
{
|
||||||
|
manager.setResolveStrategy("machine", resolveStrategiesModel.get(index).key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Profile settings")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Name")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: manager.qualityName
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.TooltipArea
|
||||||
|
{
|
||||||
|
id: qualityChangesResolveTooltip
|
||||||
|
width: parent.width / 3
|
||||||
|
height: visible ? comboboxHeight : 0
|
||||||
|
visible: manager.qualityChangesConflict
|
||||||
|
text: catalog.i18nc("@info:tooltip", "How should the conflict in the profile be resolved?")
|
||||||
|
ComboBox
|
||||||
|
{
|
||||||
|
model: resolveStrategiesModel
|
||||||
|
textRole: "label"
|
||||||
|
id: qualityChangesResolveComboBox
|
||||||
|
width: parent.width
|
||||||
|
onActivated:
|
||||||
|
{
|
||||||
|
manager.setResolveStrategy("quality_changes", resolveStrategiesModel.get(index).key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Derivative from")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "%1, %2 override(s)" ).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges)
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
visible: manager.numSettingsOverridenByQualityChanges != 0
|
||||||
|
}
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Material settings")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater
|
||||||
|
{
|
||||||
|
model: manager.materialLabels
|
||||||
|
delegate: Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Name")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: modelData
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
visible: manager.materialConflict
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
width: parent.width / 3 * 2
|
||||||
|
height: comboboxHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.TooltipArea
|
||||||
|
{
|
||||||
|
id: materialResolveTooltip
|
||||||
|
width: parent.width / 3
|
||||||
|
height: visible ? comboboxHeight : 0
|
||||||
|
|
||||||
|
text: catalog.i18nc("@info:tooltip", "How should the conflict in the material be resolved?")
|
||||||
|
ComboBox
|
||||||
|
{
|
||||||
|
model: resolveStrategiesModel
|
||||||
|
textRole: "label"
|
||||||
|
id: materialResolveComboBox
|
||||||
|
width: parent.width
|
||||||
|
onActivated:
|
||||||
|
{
|
||||||
|
manager.setResolveStrategy("material", resolveStrategiesModel.get(index).key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Setting visibility")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Mode")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: manager.activeMode
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Visible settings:")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings)
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the buildplate")
|
||||||
|
visible: manager.hasObjectsOnPlate
|
||||||
|
color: "red"
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rightButtons: [
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: ok_button
|
||||||
|
text: catalog.i18nc("@action:button","OK");
|
||||||
|
onClicked: { manager.onOkButtonClicked() }
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: cancel_button
|
||||||
|
text: catalog.i18nc("@action:button","Cancel");
|
||||||
|
onClicked: { manager.onCancelButtonClicked() }
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -2,10 +2,11 @@
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from . import ThreeMFReader
|
from . import ThreeMFReader
|
||||||
|
from . import ThreeMFWorkspaceReader
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
return {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
|
@ -20,8 +21,17 @@ def getMetaData():
|
||||||
"extension": "3mf",
|
"extension": "3mf",
|
||||||
"description": catalog.i18nc("@item:inlistbox", "3MF File")
|
"description": catalog.i18nc("@item:inlistbox", "3MF File")
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"workspace_reader":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"extension": "curaproject.3mf",
|
||||||
|
"description": catalog.i18nc("@item:inlistbox", "3MF File")
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "mesh_reader": ThreeMFReader.ThreeMFReader() }
|
return {"mesh_reader": ThreeMFReader.ThreeMFReader(),
|
||||||
|
"workspace_reader": ThreeMFWorkspaceReader.ThreeMFWorkspaceReader()}
|
||||||
|
|
89
plugins/3MFWriter/ThreeMFWorkspaceWriter.py
Normal file
89
plugins/3MFWriter/ThreeMFWorkspaceWriter.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
from UM.Workspace.WorkspaceWriter import WorkspaceWriter
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Preferences import Preferences
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
import zipfile
|
||||||
|
from io import StringIO
|
||||||
|
import copy
|
||||||
|
|
||||||
|
|
||||||
|
class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode):
|
||||||
|
mesh_writer = Application.getInstance().getMeshFileHandler().getWriter("3MFWriter")
|
||||||
|
|
||||||
|
if not mesh_writer: # We need to have the 3mf mesh writer, otherwise we can't save the entire workspace
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Indicate that the 3mf mesh writer should not close the archive just yet (we still need to add stuff to it).
|
||||||
|
mesh_writer.setStoreArchive(True)
|
||||||
|
mesh_writer.write(stream, nodes, mode)
|
||||||
|
|
||||||
|
archive = mesh_writer.getArchive()
|
||||||
|
if archive is None: # This happens if there was no mesh data to write.
|
||||||
|
archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
|
# Add global container stack data to the archive.
|
||||||
|
self._writeContainerToArchive(global_container_stack, archive)
|
||||||
|
|
||||||
|
# Also write all containers in the stack to the file
|
||||||
|
for container in global_container_stack.getContainers():
|
||||||
|
self._writeContainerToArchive(container, archive)
|
||||||
|
|
||||||
|
# Check if the machine has extruders and save all that data as well.
|
||||||
|
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()):
|
||||||
|
self._writeContainerToArchive(extruder_stack, archive)
|
||||||
|
for container in extruder_stack.getContainers():
|
||||||
|
self._writeContainerToArchive(container, archive)
|
||||||
|
|
||||||
|
# Write preferences to archive
|
||||||
|
preferences_file = zipfile.ZipInfo("Cura/preferences.cfg")
|
||||||
|
preferences_string = StringIO()
|
||||||
|
Preferences.getInstance().writeToFile(preferences_string)
|
||||||
|
archive.writestr(preferences_file, preferences_string.getvalue())
|
||||||
|
|
||||||
|
# Close the archive & reset states.
|
||||||
|
archive.close()
|
||||||
|
mesh_writer.setStoreArchive(False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
## Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive.
|
||||||
|
# \param container That follows the \type{ContainerInterface} to archive.
|
||||||
|
# \param archive The archive to write to.
|
||||||
|
@staticmethod
|
||||||
|
def _writeContainerToArchive(container, archive):
|
||||||
|
if type(container) == type(ContainerRegistry.getInstance().getEmptyInstanceContainer()):
|
||||||
|
return # Empty file, do nothing.
|
||||||
|
|
||||||
|
file_suffix = ContainerRegistry.getMimeTypeForContainer(type(container)).preferredSuffix
|
||||||
|
|
||||||
|
# Some containers have a base file, which should then be the file to use.
|
||||||
|
if "base_file" in container.getMetaData():
|
||||||
|
base_file = container.getMetaDataEntry("base_file")
|
||||||
|
container = ContainerRegistry.getInstance().findContainers(id = base_file)[0]
|
||||||
|
|
||||||
|
file_name = "Cura/%s.%s" % (container.getId(), file_suffix)
|
||||||
|
|
||||||
|
if file_name in archive.namelist():
|
||||||
|
return # File was already saved, no need to do it again. Uranium guarantees unique ID's, so this should hold.
|
||||||
|
|
||||||
|
file_in_archive = zipfile.ZipInfo(file_name)
|
||||||
|
# For some reason we have to set the compress type of each file as well (it doesn't keep the type of the entire archive)
|
||||||
|
file_in_archive.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
if type(container) == ContainerStack and (container.getMetaDataEntry("network_authentication_id") or container.getMetaDataEntry("network_authentication_key")):
|
||||||
|
# TODO: Hack
|
||||||
|
# Create a shallow copy of the container, so we can filter out the network auth (if any)
|
||||||
|
container_copy = copy.deepcopy(container)
|
||||||
|
container_copy.removeMetaDataEntry("network_authentication_id")
|
||||||
|
container_copy.removeMetaDataEntry("network_authentication_key")
|
||||||
|
serialized_data = container_copy.serialize()
|
||||||
|
else:
|
||||||
|
serialized_data = container.serialize()
|
||||||
|
|
||||||
|
archive.writestr(file_in_archive, serialized_data)
|
207
plugins/3MFWriter/ThreeMFWriter.py
Normal file
207
plugins/3MFWriter/ThreeMFWriter.py
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
# Copyright (c) 2015 Ultimaker B.V.
|
||||||
|
# Uranium is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.Mesh.MeshWriter import MeshWriter
|
||||||
|
from UM.Math.Vector import Vector
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Math.Matrix import Matrix
|
||||||
|
from UM.Application import Application
|
||||||
|
|
||||||
|
try:
|
||||||
|
import xml.etree.cElementTree as ET
|
||||||
|
except ImportError:
|
||||||
|
Logger.log("w", "Unable to load cElementTree, switching to slower version")
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
import zipfile
|
||||||
|
import UM.Application
|
||||||
|
|
||||||
|
|
||||||
|
class ThreeMFWriter(MeshWriter):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._namespaces = {
|
||||||
|
"3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
|
||||||
|
"content-types": "http://schemas.openxmlformats.org/package/2006/content-types",
|
||||||
|
"relationships": "http://schemas.openxmlformats.org/package/2006/relationships",
|
||||||
|
"cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10"
|
||||||
|
}
|
||||||
|
|
||||||
|
self._unit_matrix_string = self._convertMatrixToString(Matrix())
|
||||||
|
self._archive = None
|
||||||
|
self._store_archive = False
|
||||||
|
|
||||||
|
def _convertMatrixToString(self, matrix):
|
||||||
|
result = ""
|
||||||
|
result += str(matrix._data[0,0]) + " "
|
||||||
|
result += str(matrix._data[1,0]) + " "
|
||||||
|
result += str(matrix._data[2,0]) + " "
|
||||||
|
result += str(matrix._data[0,1]) + " "
|
||||||
|
result += str(matrix._data[1,1]) + " "
|
||||||
|
result += str(matrix._data[2,1]) + " "
|
||||||
|
result += str(matrix._data[0,2]) + " "
|
||||||
|
result += str(matrix._data[1,2]) + " "
|
||||||
|
result += str(matrix._data[2,2]) + " "
|
||||||
|
result += str(matrix._data[0,3]) + " "
|
||||||
|
result += str(matrix._data[1,3]) + " "
|
||||||
|
result += str(matrix._data[2,3]) + " "
|
||||||
|
return result
|
||||||
|
|
||||||
|
## Should we store the archive
|
||||||
|
# Note that if this is true, the archive will not be closed.
|
||||||
|
# The object that set this parameter is then responsible for closing it correctly!
|
||||||
|
def setStoreArchive(self, store_archive):
|
||||||
|
self._store_archive = store_archive
|
||||||
|
|
||||||
|
def getArchive(self):
|
||||||
|
return self._archive
|
||||||
|
|
||||||
|
def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
|
||||||
|
self._archive = None # Reset archive
|
||||||
|
archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
|
||||||
|
try:
|
||||||
|
model_file = zipfile.ZipInfo("3D/3dmodel.model")
|
||||||
|
# Because zipfile is stupid and ignores archive-level compression settings when writing with ZipInfo.
|
||||||
|
model_file.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
|
||||||
|
# Create content types file
|
||||||
|
content_types_file = zipfile.ZipInfo("[Content_Types].xml")
|
||||||
|
content_types_file.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
content_types = ET.Element("Types", xmlns = self._namespaces["content-types"])
|
||||||
|
rels_type = ET.SubElement(content_types, "Default", Extension = "rels", ContentType = "application/vnd.openxmlformats-package.relationships+xml")
|
||||||
|
model_type = ET.SubElement(content_types, "Default", Extension = "model", ContentType = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml")
|
||||||
|
|
||||||
|
# Create _rels/.rels file
|
||||||
|
relations_file = zipfile.ZipInfo("_rels/.rels")
|
||||||
|
relations_file.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
relations_element = ET.Element("Relationships", xmlns = self._namespaces["relationships"])
|
||||||
|
model_relation_element = ET.SubElement(relations_element, "Relationship", Target = "/3D/3dmodel.model", Id = "rel0", Type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel")
|
||||||
|
|
||||||
|
model = ET.Element("model", unit = "millimeter", xmlns = self._namespaces["3mf"])
|
||||||
|
|
||||||
|
# Add the version of Cura this was created with. As "CuraVersion" is not a recognised metadata name
|
||||||
|
# by 3mf itself, we place it in our own namespace.
|
||||||
|
version_metadata = ET.SubElement(model, "metadata", xmlns = self._namespaces["cura"], name = "CuraVersion")
|
||||||
|
version_metadata.text = Application.getInstance().getVersion()
|
||||||
|
|
||||||
|
resources = ET.SubElement(model, "resources")
|
||||||
|
build = ET.SubElement(model, "build")
|
||||||
|
|
||||||
|
added_nodes = []
|
||||||
|
index = 0 # Ensure index always exists (even if there are no nodes to write)
|
||||||
|
# Write all nodes with meshData to the file as objects inside the resource tag
|
||||||
|
for index, n in enumerate(MeshWriter._meshNodes(nodes)):
|
||||||
|
added_nodes.append(n) # Save the nodes that have mesh data
|
||||||
|
object = ET.SubElement(resources, "object", id = str(index+1), type = "model")
|
||||||
|
mesh = ET.SubElement(object, "mesh")
|
||||||
|
|
||||||
|
mesh_data = n.getMeshData()
|
||||||
|
vertices = ET.SubElement(mesh, "vertices")
|
||||||
|
verts = mesh_data.getVertices()
|
||||||
|
|
||||||
|
if verts is None:
|
||||||
|
Logger.log("d", "3mf writer can't write nodes without mesh data. Skipping this node.")
|
||||||
|
continue # No mesh data, nothing to do.
|
||||||
|
if mesh_data.hasIndices():
|
||||||
|
for face in mesh_data.getIndices():
|
||||||
|
v1 = verts[face[0]]
|
||||||
|
v2 = verts[face[1]]
|
||||||
|
v3 = verts[face[2]]
|
||||||
|
xml_vertex1 = ET.SubElement(vertices, "vertex", x = str(v1[0]), y = str(v1[1]), z = str(v1[2]))
|
||||||
|
xml_vertex2 = ET.SubElement(vertices, "vertex", x = str(v2[0]), y = str(v2[1]), z = str(v2[2]))
|
||||||
|
xml_vertex3 = ET.SubElement(vertices, "vertex", x = str(v3[0]), y = str(v3[1]), z = str(v3[2]))
|
||||||
|
|
||||||
|
triangles = ET.SubElement(mesh, "triangles")
|
||||||
|
for face in mesh_data.getIndices():
|
||||||
|
triangle = ET.SubElement(triangles, "triangle", v1 = str(face[0]) , v2 = str(face[1]), v3 = str(face[2]))
|
||||||
|
else:
|
||||||
|
triangles = ET.SubElement(mesh, "triangles")
|
||||||
|
for idx, vert in enumerate(verts):
|
||||||
|
xml_vertex = ET.SubElement(vertices, "vertex", x = str(vert[0]), y = str(vert[1]), z = str(vert[2]))
|
||||||
|
|
||||||
|
# If we have no faces defined, assume that every three subsequent vertices form a face.
|
||||||
|
if idx % 3 == 0:
|
||||||
|
triangle = ET.SubElement(triangles, "triangle", v1 = str(idx), v2 = str(idx + 1), v3 = str(idx + 2))
|
||||||
|
|
||||||
|
# Handle per object settings
|
||||||
|
stack = n.callDecoration("getStack")
|
||||||
|
if stack is not None:
|
||||||
|
changed_setting_keys = set(stack.getTop().getAllKeys())
|
||||||
|
|
||||||
|
# Ensure that we save the extruder used for this object.
|
||||||
|
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||||
|
changed_setting_keys.add("extruder_nr")
|
||||||
|
|
||||||
|
settings_xml = ET.SubElement(object, "settings", xmlns=self._namespaces["cura"])
|
||||||
|
|
||||||
|
# Get values for all changed settings & save them.
|
||||||
|
for key in changed_setting_keys:
|
||||||
|
setting_xml = ET.SubElement(settings_xml, "setting", key = key)
|
||||||
|
setting_xml.text = str(stack.getProperty(key, "value"))
|
||||||
|
|
||||||
|
# Add one to the index as we haven't incremented the last iteration.
|
||||||
|
index += 1
|
||||||
|
nodes_to_add = set()
|
||||||
|
|
||||||
|
for node in added_nodes:
|
||||||
|
# Check the parents of the nodes with mesh_data and ensure that they are also added.
|
||||||
|
parent_node = node.getParent()
|
||||||
|
while parent_node is not None:
|
||||||
|
if parent_node.callDecoration("isGroup"):
|
||||||
|
nodes_to_add.add(parent_node)
|
||||||
|
parent_node = parent_node.getParent()
|
||||||
|
else:
|
||||||
|
parent_node = None
|
||||||
|
|
||||||
|
# Sort all the nodes by depth (so nodes with the highest depth are done first)
|
||||||
|
sorted_nodes_to_add = sorted(nodes_to_add, key=lambda node: node.getDepth(), reverse = True)
|
||||||
|
|
||||||
|
# We have already saved the nodes with mesh data, but now we also want to save nodes required for the scene
|
||||||
|
for node in sorted_nodes_to_add:
|
||||||
|
object = ET.SubElement(resources, "object", id=str(index + 1), type="model")
|
||||||
|
components = ET.SubElement(object, "components")
|
||||||
|
for child in node.getChildren():
|
||||||
|
if child in added_nodes:
|
||||||
|
component = ET.SubElement(components, "component", objectid = str(added_nodes.index(child) + 1), transform = self._convertMatrixToString(child.getLocalTransformation()))
|
||||||
|
index += 1
|
||||||
|
added_nodes.append(node)
|
||||||
|
|
||||||
|
# Create a transformation Matrix to convert from our worldspace into 3MF.
|
||||||
|
# First step: flip the y and z axis.
|
||||||
|
transformation_matrix = Matrix()
|
||||||
|
transformation_matrix._data[1, 1] = 0
|
||||||
|
transformation_matrix._data[1, 2] = -1
|
||||||
|
transformation_matrix._data[2, 1] = 1
|
||||||
|
transformation_matrix._data[2, 2] = 0
|
||||||
|
|
||||||
|
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||||
|
# Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
|
||||||
|
# build volume.
|
||||||
|
if global_container_stack:
|
||||||
|
translation_vector = Vector(x=global_container_stack.getProperty("machine_width", "value") / 2,
|
||||||
|
y=global_container_stack.getProperty("machine_depth", "value") / 2,
|
||||||
|
z=0)
|
||||||
|
translation_matrix = Matrix()
|
||||||
|
translation_matrix.setByTranslation(translation_vector)
|
||||||
|
transformation_matrix.preMultiply(translation_matrix)
|
||||||
|
|
||||||
|
# Find out what the final build items are and add them.
|
||||||
|
for node in added_nodes:
|
||||||
|
if node.getParent().callDecoration("isGroup") is None:
|
||||||
|
node_matrix = node.getLocalTransformation()
|
||||||
|
|
||||||
|
ET.SubElement(build, "item", objectid = str(added_nodes.index(node) + 1), transform = self._convertMatrixToString(node_matrix.preMultiply(transformation_matrix)))
|
||||||
|
|
||||||
|
archive.writestr(model_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(model))
|
||||||
|
archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
|
||||||
|
archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
|
||||||
|
except Exception as e:
|
||||||
|
Logger.logException("e", "Error writing zip file")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if not self._store_archive:
|
||||||
|
archive.close()
|
||||||
|
else:
|
||||||
|
self._archive = archive
|
||||||
|
|
||||||
|
return True
|
38
plugins/3MFWriter/__init__.py
Normal file
38
plugins/3MFWriter/__init__.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Copyright (c) 2015 Ultimaker B.V.
|
||||||
|
# Uranium is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
from . import ThreeMFWorkspaceWriter
|
||||||
|
from . import ThreeMFWriter
|
||||||
|
|
||||||
|
i18n_catalog = i18nCatalog("uranium")
|
||||||
|
|
||||||
|
def getMetaData():
|
||||||
|
return {
|
||||||
|
"plugin": {
|
||||||
|
"name": i18n_catalog.i18nc("@label", "3MF Writer"),
|
||||||
|
"author": "Ultimaker",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides support for writing 3MF files."),
|
||||||
|
"api": 3
|
||||||
|
},
|
||||||
|
"mesh_writer": {
|
||||||
|
"output": [{
|
||||||
|
"extension": "3mf",
|
||||||
|
"description": i18n_catalog.i18nc("@item:inlistbox", "3MF file"),
|
||||||
|
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
|
||||||
|
"mode": ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"workspace_writer": {
|
||||||
|
"output": [{
|
||||||
|
"extension": "curaproject.3mf",
|
||||||
|
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
||||||
|
"mime_type": "application/x-curaproject+xml",
|
||||||
|
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def register(app):
|
||||||
|
return {"mesh_writer": ThreeMFWriter.ThreeMFWriter(), "workspace_writer": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter()}
|
|
@ -156,28 +156,29 @@ class StartSliceJob(Job):
|
||||||
if group[0].getParent().callDecoration("isGroup"):
|
if group[0].getParent().callDecoration("isGroup"):
|
||||||
self._handlePerObjectSettings(group[0].getParent(), group_message)
|
self._handlePerObjectSettings(group[0].getParent(), group_message)
|
||||||
for object in group:
|
for object in group:
|
||||||
mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation())
|
mesh_data = object.getMeshData()
|
||||||
|
rot_scale = object.getWorldTransformation().getTransposed().getData()[0:3, 0:3]
|
||||||
|
translate = object.getWorldTransformation().getData()[:3, 3]
|
||||||
|
|
||||||
obj = group_message.addRepeatedMessage("objects")
|
# This effectively performs a limited form of MeshData.getTransformed that ignores normals.
|
||||||
obj.id = id(object)
|
|
||||||
verts = mesh_data.getVertices()
|
verts = mesh_data.getVertices()
|
||||||
indices = mesh_data.getIndices()
|
verts = verts.dot(rot_scale)
|
||||||
if indices is not None:
|
verts += translate
|
||||||
#TODO: This is a very slow way of doing it! It also locks up the GUI.
|
|
||||||
flat_vert_list = []
|
|
||||||
for face in indices:
|
|
||||||
for vert_index in face:
|
|
||||||
flat_vert_list.append(verts[vert_index])
|
|
||||||
Job.yieldThread()
|
|
||||||
verts = numpy.array(flat_vert_list)
|
|
||||||
else:
|
|
||||||
verts = numpy.array(verts)
|
|
||||||
|
|
||||||
# Convert from Y up axes to Z up axes. Equals a 90 degree rotation.
|
# Convert from Y up axes to Z up axes. Equals a 90 degree rotation.
|
||||||
verts[:, [1, 2]] = verts[:, [2, 1]]
|
verts[:, [1, 2]] = verts[:, [2, 1]]
|
||||||
verts[:, 1] *= -1
|
verts[:, 1] *= -1
|
||||||
|
|
||||||
obj.vertices = verts
|
obj = group_message.addRepeatedMessage("objects")
|
||||||
|
obj.id = id(object)
|
||||||
|
|
||||||
|
indices = mesh_data.getIndices()
|
||||||
|
if indices is not None:
|
||||||
|
flat_verts = numpy.take(verts, indices.flatten(), axis=0)
|
||||||
|
else:
|
||||||
|
flat_verts = numpy.array(verts)
|
||||||
|
|
||||||
|
obj.vertices = flat_verts
|
||||||
|
|
||||||
self._handlePerObjectSettings(object, obj)
|
self._handlePerObjectSettings(object, obj)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from UM.Application import Application
|
||||||
|
|
||||||
import LayerView
|
import LayerView
|
||||||
|
|
||||||
|
|
||||||
class LayerViewProxy(QObject):
|
class LayerViewProxy(QObject):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
|
@ -9,10 +9,10 @@ import os.path #For concatenating the path to the plugin and the relative path t
|
||||||
from UM.Application import Application # To get the machine manager to create the new profile in.
|
from UM.Application import Application # To get the machine manager to create the new profile in.
|
||||||
from UM.Logger import Logger # Logging errors.
|
from UM.Logger import Logger # Logging errors.
|
||||||
from UM.PluginRegistry import PluginRegistry # For getting the path to this plugin's directory.
|
from UM.PluginRegistry import PluginRegistry # For getting the path to this plugin's directory.
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer #For getting the current machine's defaults.
|
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
|
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
|
||||||
from cura.ProfileReader import ProfileReader # The plug-in type to implement.
|
from cura.ProfileReader import ProfileReader # The plug-in type to implement.
|
||||||
|
|
||||||
|
|
||||||
## A plugin that reads profile data from legacy Cura versions.
|
## A plugin that reads profile data from legacy Cura versions.
|
||||||
#
|
#
|
||||||
# It reads a profile from an .ini file, and performs some translations on it.
|
# It reads a profile from an .ini file, and performs some translations on it.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2016 Ultimaker B.V.
|
# Copyright (c) 2016 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
from cura.MachineAction import MachineAction
|
from cura.MachineAction import MachineAction
|
||||||
|
|
||||||
|
@ -16,40 +16,60 @@ from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||||
import UM.i18n
|
import UM.i18n
|
||||||
catalog = UM.i18n.i18nCatalog("cura")
|
catalog = UM.i18n.i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
## This action allows for certain settings that are "machine only") to be modified.
|
||||||
|
# It automatically detects machine definitions that it knows how to change and attaches itself to those.
|
||||||
class MachineSettingsAction(MachineAction):
|
class MachineSettingsAction(MachineAction):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
|
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
|
||||||
self._qml_url = "MachineSettingsAction.qml"
|
self._qml_url = "MachineSettingsAction.qml"
|
||||||
|
|
||||||
CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
self._container_index = 0
|
||||||
|
|
||||||
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
|
self._container_registry.containerAdded.connect(self._onContainerAdded)
|
||||||
|
|
||||||
def _reset(self):
|
def _reset(self):
|
||||||
global_container_stack = Application.Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if not global_container_stack:
|
||||||
variant = global_container_stack.findContainer({"type": "variant"})
|
return
|
||||||
if variant and variant.getId() == "empty_variant":
|
|
||||||
variant_index = global_container_stack.getContainerIndex(variant)
|
|
||||||
self._createVariant(global_container_stack, variant_index)
|
|
||||||
|
|
||||||
def _createVariant(self, global_container_stack, variant_index):
|
# Make sure there is a definition_changes container to store the machine settings
|
||||||
# Create and switch to a variant to store the settings in
|
definition_changes_container = global_container_stack.findContainer({"type": "definition_changes"})
|
||||||
new_variant = InstanceContainer(global_container_stack.getName() + "_variant")
|
if not definition_changes_container:
|
||||||
new_variant.addMetaDataEntry("type", "variant")
|
definition_changes_container = self._createDefinitionChangesContainer(global_container_stack)
|
||||||
new_variant.setDefinition(global_container_stack.getBottom())
|
|
||||||
ContainerRegistry.getInstance().addContainer(new_variant)
|
# Notify the UI in which container to store the machine settings data
|
||||||
global_container_stack.replaceContainer(variant_index, new_variant)
|
container_index = global_container_stack.getContainerIndex(definition_changes_container)
|
||||||
|
if container_index != self._container_index:
|
||||||
|
self._container_index = container_index
|
||||||
|
self.containerIndexChanged.emit()
|
||||||
|
|
||||||
|
def _createDefinitionChangesContainer(self, global_container_stack, container_index = None):
|
||||||
|
definition_changes_container = InstanceContainer(global_container_stack.getName() + "_settings")
|
||||||
|
definition = global_container_stack.getBottom()
|
||||||
|
definition_changes_container.setDefinition(definition)
|
||||||
|
definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
||||||
|
|
||||||
|
self._container_registry.addContainer(definition_changes_container)
|
||||||
|
# Insert definition_changes between the definition and the variant
|
||||||
|
global_container_stack.insertContainer(-1, definition_changes_container)
|
||||||
|
|
||||||
|
return definition_changes_container
|
||||||
|
|
||||||
|
containerIndexChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(int, notify = containerIndexChanged)
|
||||||
|
def containerIndex(self):
|
||||||
|
return self._container_index
|
||||||
|
|
||||||
def _onContainerAdded(self, container):
|
def _onContainerAdded(self, container):
|
||||||
# Add this action as a supported action to all machine definitions
|
# Add this action as a supported action to all machine definitions
|
||||||
if isinstance(container, UM.Settings.DefinitionContainer.DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
|
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
|
||||||
if container.getProperty("machine_extruder_count", "value") > 1:
|
if container.getProperty("machine_extruder_count", "value") > 1:
|
||||||
# Multiextruder printers are not currently supported
|
# Multiextruder printers are not currently supported
|
||||||
Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId())
|
Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId())
|
||||||
return
|
return
|
||||||
if container.getMetaDataEntry("has_variants", False):
|
|
||||||
# Machines that use variants are not currently supported
|
|
||||||
Logger.log("d", "Not attaching MachineSettingsAction to %s; Machines that use variants are not supported", container.getId())
|
|
||||||
return
|
|
||||||
|
|
||||||
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||||
|
|
||||||
|
@ -62,7 +82,7 @@ class MachineSettingsAction(MachineAction):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def updateHasMaterialsMetadata(self):
|
def updateHasMaterialsMetadata(self):
|
||||||
# Updates the has_materials metadata flag after switching gcode flavor
|
# Updates the has_materials metadata flag after switching gcode flavor
|
||||||
global_container_stack = UM.Application.Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
definition = global_container_stack.getBottom()
|
definition = global_container_stack.getBottom()
|
||||||
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
|
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
|
||||||
|
@ -80,7 +100,7 @@ class MachineSettingsAction(MachineAction):
|
||||||
# Set the material container to a sane default
|
# Set the material container to a sane default
|
||||||
if material_container.getId() == "empty_material":
|
if material_container.getId() == "empty_material":
|
||||||
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" }
|
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" }
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||||
if containers:
|
if containers:
|
||||||
global_container_stack.replaceContainer(material_index, containers[0])
|
global_container_stack.replaceContainer(material_index, containers[0])
|
||||||
else:
|
else:
|
||||||
|
@ -89,7 +109,7 @@ class MachineSettingsAction(MachineAction):
|
||||||
if "has_materials" in global_container_stack.getMetaData():
|
if "has_materials" in global_container_stack.getMetaData():
|
||||||
global_container_stack.removeMetaDataEntry("has_materials")
|
global_container_stack.removeMetaDataEntry("has_materials")
|
||||||
|
|
||||||
empty_material = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
|
empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
|
||||||
global_container_stack.replaceContainer(material_index, empty_material)
|
global_container_stack.replaceContainer(material_index, empty_material)
|
||||||
|
|
||||||
Application.getInstance().globalContainerStackChanged.emit()
|
Application.getInstance().globalContainerStackChanged.emit()
|
|
@ -120,19 +120,73 @@ Cura.MachineAction
|
||||||
|
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
CheckBox
|
Row
|
||||||
{
|
{
|
||||||
id: heatedBedCheckBox
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
text: catalog.i18nc("@option:check", "Heated Bed")
|
|
||||||
checked: String(machineHeatedBedProvider.properties.value).toLowerCase() != 'false'
|
Label
|
||||||
onClicked: machineHeatedBedProvider.setPropertyValue("value", checked)
|
{
|
||||||
|
text: catalog.i18nc("@label", "Build Plate Shape")
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox
|
||||||
|
{
|
||||||
|
id: shapeComboBox
|
||||||
|
model: ListModel
|
||||||
|
{
|
||||||
|
id: shapesModel
|
||||||
|
Component.onCompleted:
|
||||||
|
{
|
||||||
|
// Options come in as a string-representation of an OrderedDict
|
||||||
|
var options = machineShapeProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/);
|
||||||
|
if(options)
|
||||||
|
{
|
||||||
|
options = options[1].split("), (")
|
||||||
|
for(var i = 0; i < options.length; i++)
|
||||||
|
{
|
||||||
|
var option = options[i].substring(1, options[i].length - 1).split("', '")
|
||||||
|
shapesModel.append({text: option[1], value: option[0]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentIndex:
|
||||||
|
{
|
||||||
|
var currentValue = machineShapeProvider.properties.value;
|
||||||
|
var index = 0;
|
||||||
|
for(var i = 0; i < shapesModel.count; i++)
|
||||||
|
{
|
||||||
|
if(shapesModel.get(i).value == currentValue) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
onActivated:
|
||||||
|
{
|
||||||
|
machineShapeProvider.setPropertyValue("value", shapesModel.get(index).value);
|
||||||
|
manager.forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CheckBox
|
CheckBox
|
||||||
{
|
{
|
||||||
id: centerIsZeroCheckBox
|
id: centerIsZeroCheckBox
|
||||||
text: catalog.i18nc("@option:check", "Machine Center is Zero")
|
text: catalog.i18nc("@option:check", "Machine Center is Zero")
|
||||||
checked: String(machineCenterIsZeroProvider.properties.value).toLowerCase() != 'false'
|
checked: String(machineCenterIsZeroProvider.properties.value).toLowerCase() != 'false'
|
||||||
onClicked: machineCenterIsZeroProvider.setPropertyValue("value", checked)
|
onClicked:
|
||||||
|
{
|
||||||
|
machineCenterIsZeroProvider.setPropertyValue("value", checked);
|
||||||
|
manager.forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckBox
|
||||||
|
{
|
||||||
|
id: heatedBedCheckBox
|
||||||
|
text: catalog.i18nc("@option:check", "Heated Bed")
|
||||||
|
checked: String(machineHeatedBedProvider.properties.value).toLowerCase() != 'false'
|
||||||
|
onClicked: machineHeatedBedProvider.setPropertyValue("value", checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,19 +201,40 @@ Cura.MachineAction
|
||||||
|
|
||||||
ComboBox
|
ComboBox
|
||||||
{
|
{
|
||||||
model: ["RepRap (Marlin/Sprinter)", "UltiGCode", "Repetier"]
|
model: ListModel
|
||||||
|
{
|
||||||
|
id: flavorModel
|
||||||
|
Component.onCompleted:
|
||||||
|
{
|
||||||
|
// Options come in as a string-representation of an OrderedDict
|
||||||
|
var options = machineGCodeFlavorProvider.properties.options.match(/^OrderedDict\(\[\((.*)\)\]\)$/);
|
||||||
|
if(options)
|
||||||
|
{
|
||||||
|
options = options[1].split("), (")
|
||||||
|
for(var i = 0; i < options.length; i++)
|
||||||
|
{
|
||||||
|
var option = options[i].substring(1, options[i].length - 1).split("', '")
|
||||||
|
flavorModel.append({text: option[1], value: option[0]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
currentIndex:
|
currentIndex:
|
||||||
{
|
{
|
||||||
var index = model.indexOf(machineGCodeFlavorProvider.properties.value);
|
var currentValue = machineGCodeFlavorProvider.properties.value;
|
||||||
if(index == -1)
|
var index = 0;
|
||||||
|
for(var i = 0; i < flavorModel.count; i++)
|
||||||
{
|
{
|
||||||
index = 0;
|
if(flavorModel.get(i).value == currentValue) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
onActivated:
|
onActivated:
|
||||||
{
|
{
|
||||||
machineGCodeFlavorProvider.setPropertyValue("value", model[index]);
|
machineGCodeFlavorProvider.setPropertyValue("value", flavorModel.get(index).value);
|
||||||
manager.updateHasMaterialsMetadata();
|
manager.updateHasMaterialsMetadata();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,17 +348,20 @@ Cura.MachineAction
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Nozzle size")
|
text: catalog.i18nc("@label", "Nozzle size")
|
||||||
|
visible: !Cura.MachineManager.hasVariants
|
||||||
}
|
}
|
||||||
TextField
|
TextField
|
||||||
{
|
{
|
||||||
id: nozzleSizeField
|
id: nozzleSizeField
|
||||||
text: machineNozzleSizeProvider.properties.value
|
text: machineNozzleSizeProvider.properties.value
|
||||||
|
visible: !Cura.MachineManager.hasVariants
|
||||||
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
|
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
|
||||||
onEditingFinished: { machineNozzleSizeProvider.setPropertyValue("value", text) }
|
onEditingFinished: { machineNozzleSizeProvider.setPropertyValue("value", text) }
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "mm")
|
text: catalog.i18nc("@label", "mm")
|
||||||
|
visible: !Cura.MachineManager.hasVariants
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -308,6 +386,8 @@ Cura.MachineAction
|
||||||
id: machineStartGcodeField
|
id: machineStartGcodeField
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - y
|
height: parent.height - y
|
||||||
|
font: UM.Theme.getFont("fixed")
|
||||||
|
wrapMode: TextEdit.NoWrap
|
||||||
text: machineStartGcodeProvider.properties.value
|
text: machineStartGcodeProvider.properties.value
|
||||||
onActiveFocusChanged:
|
onActiveFocusChanged:
|
||||||
{
|
{
|
||||||
|
@ -330,6 +410,8 @@ Cura.MachineAction
|
||||||
id: machineEndGcodeField
|
id: machineEndGcodeField
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - y
|
height: parent.height - y
|
||||||
|
font: UM.Theme.getFont("fixed")
|
||||||
|
wrapMode: TextEdit.NoWrap
|
||||||
text: machineEndGcodeProvider.properties.value
|
text: machineEndGcodeProvider.properties.value
|
||||||
onActiveFocusChanged:
|
onActiveFocusChanged:
|
||||||
{
|
{
|
||||||
|
@ -377,7 +459,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_width"
|
key: "machine_width"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -387,7 +469,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_depth"
|
key: "machine_depth"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -397,7 +479,17 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_height"
|
key: "machine_height"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.SettingPropertyProvider
|
||||||
|
{
|
||||||
|
id: machineShapeProvider
|
||||||
|
|
||||||
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
|
key: "machine_shape"
|
||||||
|
watchedProperties: [ "value", "options" ]
|
||||||
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -407,7 +499,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_heated_bed"
|
key: "machine_heated_bed"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -417,7 +509,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_center_is_zero"
|
key: "machine_center_is_zero"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -426,8 +518,8 @@ Cura.MachineAction
|
||||||
|
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_gcode_flavor"
|
key: "machine_gcode_flavor"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value", "options" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -437,7 +529,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_nozzle_size"
|
key: "machine_nozzle_size"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -447,7 +539,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "gantry_height"
|
key: "gantry_height"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -457,7 +549,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_head_with_fans_polygon"
|
key: "machine_head_with_fans_polygon"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -468,7 +560,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_start_gcode"
|
key: "machine_start_gcode"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
@ -478,7 +570,7 @@ Cura.MachineAction
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "machine_end_gcode"
|
key: "machine_end_gcode"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 4
|
storeIndex: manager.containerIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -351,7 +351,7 @@ Item {
|
||||||
{
|
{
|
||||||
if(text != "")
|
if(text != "")
|
||||||
{
|
{
|
||||||
listview.model.filter = {"settable_per_mesh": true, "label": "*" + text}
|
listview.model.filter = {"settable_per_mesh": true, "i18n_label": "*" + text}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,41 +17,60 @@ class OSXRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
|
||||||
drives = {}
|
drives = {}
|
||||||
p = subprocess.Popen(["system_profiler", "SPUSBDataType", "-xml"], stdout = subprocess.PIPE)
|
p = subprocess.Popen(["system_profiler", "SPUSBDataType", "-xml"], stdout = subprocess.PIPE)
|
||||||
plist = plistlib.loads(p.communicate()[0])
|
plist = plistlib.loads(p.communicate()[0])
|
||||||
p.wait()
|
|
||||||
|
|
||||||
for entry in plist:
|
result = self._recursiveSearch(plist, "removable_media")
|
||||||
if "_items" in entry:
|
|
||||||
for item in entry["_items"]:
|
|
||||||
for dev in item["_items"]:
|
|
||||||
if "removable_media" in dev and dev["removable_media"] == "yes" and "volumes" in dev and len(dev["volumes"]) > 0:
|
|
||||||
for vol in dev["volumes"]:
|
|
||||||
if "mount_point" in vol:
|
|
||||||
volume = vol["mount_point"]
|
|
||||||
drives[volume] = os.path.basename(volume)
|
|
||||||
|
|
||||||
p = subprocess.Popen(["system_profiler", "SPCardReaderDataType", "-xml"], stdout=subprocess.PIPE)
|
p = subprocess.Popen(["system_profiler", "SPCardReaderDataType", "-xml"], stdout=subprocess.PIPE)
|
||||||
plist = plistlib.loads(p.communicate()[0])
|
plist = plistlib.loads(p.communicate()[0])
|
||||||
p.wait()
|
|
||||||
|
|
||||||
for entry in plist:
|
result.extend(self._recursiveSearch(plist, "removable_media"))
|
||||||
if "_items" in entry:
|
|
||||||
for item in entry["_items"]:
|
for drive in result:
|
||||||
for dev in item["_items"]:
|
# Ignore everything not explicitly marked as removable
|
||||||
if "removable_media" in dev and dev["removable_media"] == "yes" and "volumes" in dev and len(dev["volumes"]) > 0:
|
if drive["removable_media"] != "yes":
|
||||||
for vol in dev["volumes"]:
|
continue
|
||||||
if "mount_point" in vol:
|
|
||||||
volume = vol["mount_point"]
|
# Ignore any removable device that does not have an actual volume
|
||||||
drives[volume] = os.path.basename(volume)
|
if "volumes" not in drive or not drive["volumes"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for volume in drive["volumes"]:
|
||||||
|
if not "mount_point" in volume:
|
||||||
|
continue
|
||||||
|
|
||||||
|
mount_point = volume["mount_point"]
|
||||||
|
|
||||||
|
if "_name" in volume:
|
||||||
|
drive_name = volume["_name"]
|
||||||
|
else:
|
||||||
|
drive_name = os.path.basename(mount_point)
|
||||||
|
|
||||||
|
drives[mount_point] = drive_name
|
||||||
|
|
||||||
return drives
|
return drives
|
||||||
|
|
||||||
def performEjectDevice(self, device):
|
def performEjectDevice(self, device):
|
||||||
p = subprocess.Popen(["diskutil", "eject", device.getId()], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
p = subprocess.Popen(["diskutil", "eject", device.getId()], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
||||||
output = p.communicate()
|
output = p.communicate()
|
||||||
Logger.log("d", "umount returned: %s.", repr(output))
|
|
||||||
|
|
||||||
return_code = p.wait()
|
return_code = p.wait()
|
||||||
if return_code != 0:
|
if return_code != 0:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Recursively search for key in a plist parsed by plistlib
|
||||||
|
def _recursiveSearch(self, plist, key):
|
||||||
|
result = []
|
||||||
|
for entry in plist:
|
||||||
|
if key in entry:
|
||||||
|
result.append(entry)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "_items" in entry:
|
||||||
|
result.extend(self._recursiveSearch(entry["_items"], key))
|
||||||
|
|
||||||
|
if "Media" in entry:
|
||||||
|
result.extend(self._recursiveSearch(entry["Media"], key))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
|
@ -6,7 +6,7 @@ import os.path
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.Mesh.WriteMeshJob import WriteMeshJob
|
from UM.FileHandler.WriteFileJob import WriteFileJob
|
||||||
from UM.Mesh.MeshWriter import MeshWriter
|
from UM.Mesh.MeshWriter import MeshWriter
|
||||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||||
|
@ -37,13 +37,17 @@ class RemovableDriveOutputDevice(OutputDevice):
|
||||||
# meshes.
|
# meshes.
|
||||||
# \param limit_mimetypes Should we limit the available MIME types to the
|
# \param limit_mimetypes Should we limit the available MIME types to the
|
||||||
# MIME types available to the currently active machine?
|
# MIME types available to the currently active machine?
|
||||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False):
|
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
|
||||||
filter_by_machine = True # This plugin is indended to be used by machine (regardless of what it was told to do)
|
filter_by_machine = True # This plugin is indended to be used by machine (regardless of what it was told to do)
|
||||||
if self._writing:
|
if self._writing:
|
||||||
raise OutputDeviceError.DeviceBusyError()
|
raise OutputDeviceError.DeviceBusyError()
|
||||||
|
|
||||||
# Formats supported by this application (File types that we can actually write)
|
# Formats supported by this application (File types that we can actually write)
|
||||||
|
if file_handler:
|
||||||
|
file_formats = file_handler.getSupportedFileTypesWrite()
|
||||||
|
else:
|
||||||
file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
||||||
|
|
||||||
if filter_by_machine:
|
if filter_by_machine:
|
||||||
container = Application.getInstance().getGlobalContainerStack().findContainer({"file_formats": "*"})
|
container = Application.getInstance().getGlobalContainerStack().findContainer({"file_formats": "*"})
|
||||||
|
|
||||||
|
@ -58,7 +62,11 @@ class RemovableDriveOutputDevice(OutputDevice):
|
||||||
raise OutputDeviceError.WriteRequestFailedError()
|
raise OutputDeviceError.WriteRequestFailedError()
|
||||||
|
|
||||||
# Just take the first file format available.
|
# Just take the first file format available.
|
||||||
|
if file_handler is not None:
|
||||||
|
writer = file_handler.getWriterByMimeType(file_formats[0]["mime_type"])
|
||||||
|
else:
|
||||||
writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(file_formats[0]["mime_type"])
|
writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(file_formats[0]["mime_type"])
|
||||||
|
|
||||||
extension = file_formats[0]["extension"]
|
extension = file_formats[0]["extension"]
|
||||||
|
|
||||||
if file_name is None:
|
if file_name is None:
|
||||||
|
@ -72,7 +80,7 @@ class RemovableDriveOutputDevice(OutputDevice):
|
||||||
Logger.log("d", "Writing to %s", file_name)
|
Logger.log("d", "Writing to %s", file_name)
|
||||||
# Using buffering greatly reduces the write time for many lines of gcode
|
# Using buffering greatly reduces the write time for many lines of gcode
|
||||||
self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8")
|
self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8")
|
||||||
job = WriteMeshJob(writer, self._stream, nodes, MeshWriter.OutputMode.TextMode)
|
job = WriteFileJob(writer, self._stream, nodes, MeshWriter.OutputMode.TextMode)
|
||||||
job.setFileName(file_name)
|
job.setFileName(file_name)
|
||||||
job.progress.connect(self._onProgress)
|
job.progress.connect(self._onProgress)
|
||||||
job.finished.connect(self._onFinished)
|
job.finished.connect(self._onFinished)
|
||||||
|
|
148
plugins/UM3NetworkPrinting/DiscoverUM3Action.py
Normal file
148
plugins/UM3NetworkPrinting/DiscoverUM3Action.py
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
from cura.MachineAction import MachineAction
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QUrl, QObject
|
||||||
|
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
class DiscoverUM3Action(MachineAction):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network"))
|
||||||
|
self._qml_url = "DiscoverUM3Action.qml"
|
||||||
|
|
||||||
|
self._network_plugin = None
|
||||||
|
|
||||||
|
self.__additional_components_context = None
|
||||||
|
self.__additional_component = None
|
||||||
|
self.__additional_components_view = None
|
||||||
|
|
||||||
|
Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)
|
||||||
|
|
||||||
|
self._last_zeroconf_event_time = time.time()
|
||||||
|
self._zeroconf_change_grace_period = 0.25 # Time to wait after a zeroconf service change before allowing a zeroconf reset
|
||||||
|
|
||||||
|
printersChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def startDiscovery(self):
|
||||||
|
if not self._network_plugin:
|
||||||
|
self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
|
||||||
|
self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged)
|
||||||
|
self.printersChanged.emit()
|
||||||
|
|
||||||
|
## Re-filters the list of printers.
|
||||||
|
@pyqtSlot()
|
||||||
|
def reset(self):
|
||||||
|
self.printersChanged.emit()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def restartDiscovery(self):
|
||||||
|
# Ensure that there is a bit of time after a printer has been discovered.
|
||||||
|
# This is a work around for an issue with Qt 5.5.1 up to Qt 5.7 which can segfault if we do this too often.
|
||||||
|
# It's most likely that the QML engine is still creating delegates, where the python side already deleted or
|
||||||
|
# garbage collected the data.
|
||||||
|
# Whatever the case, waiting a bit ensures that it doesn't crash.
|
||||||
|
if time.time() - self._last_zeroconf_event_time > self._zeroconf_change_grace_period:
|
||||||
|
if not self._network_plugin:
|
||||||
|
self.startDiscovery()
|
||||||
|
else:
|
||||||
|
self._network_plugin.startDiscovery()
|
||||||
|
|
||||||
|
@pyqtSlot(str, str)
|
||||||
|
def removeManualPrinter(self, key, address):
|
||||||
|
if not self._network_plugin:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._network_plugin.removeManualPrinter(key, address)
|
||||||
|
|
||||||
|
@pyqtSlot(str, str)
|
||||||
|
def setManualPrinter(self, key, address):
|
||||||
|
if key != "":
|
||||||
|
# This manual printer replaces a current manual printer
|
||||||
|
self._network_plugin.removeManualPrinter(key)
|
||||||
|
|
||||||
|
if address != "":
|
||||||
|
self._network_plugin.addManualPrinter(address)
|
||||||
|
|
||||||
|
def _onPrinterDiscoveryChanged(self, *args):
|
||||||
|
self._last_zeroconf_event_time = time.time()
|
||||||
|
self.printersChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantList", notify = printersChanged)
|
||||||
|
def foundDevices(self):
|
||||||
|
if self._network_plugin:
|
||||||
|
if Application.getInstance().getGlobalContainerStack():
|
||||||
|
global_printer_type = Application.getInstance().getGlobalContainerStack().getBottom().getId()
|
||||||
|
else:
|
||||||
|
global_printer_type = "unknown"
|
||||||
|
|
||||||
|
printers = list(self._network_plugin.getPrinters().values())
|
||||||
|
# TODO; There are still some testing printers that don't have a correct printer type, so don't filter out unkown ones just yet.
|
||||||
|
printers = [printer for printer in printers if printer.printerType == global_printer_type or printer.printerType == "unknown"]
|
||||||
|
printers.sort(key = lambda k: k.name)
|
||||||
|
return printers
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def setKey(self, key):
|
||||||
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack:
|
||||||
|
meta_data = global_container_stack.getMetaData()
|
||||||
|
if "um_network_key" in meta_data:
|
||||||
|
global_container_stack.setMetaDataEntry("um_network_key", key)
|
||||||
|
# Delete old authentication data.
|
||||||
|
global_container_stack.removeMetaDataEntry("network_authentication_id")
|
||||||
|
global_container_stack.removeMetaDataEntry("network_authentication_key")
|
||||||
|
else:
|
||||||
|
global_container_stack.addMetaDataEntry("um_network_key", key)
|
||||||
|
|
||||||
|
if self._network_plugin:
|
||||||
|
# Ensure that the connection states are refreshed.
|
||||||
|
self._network_plugin.reCheckConnections()
|
||||||
|
|
||||||
|
@pyqtSlot(result = str)
|
||||||
|
def getStoredKey(self):
|
||||||
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack:
|
||||||
|
meta_data = global_container_stack.getMetaData()
|
||||||
|
if "um_network_key" in meta_data:
|
||||||
|
return global_container_stack.getMetaDataEntry("um_network_key")
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def loadConfigurationFromPrinter(self):
|
||||||
|
machine_manager = Application.getInstance().getMachineManager()
|
||||||
|
hotend_ids = machine_manager.printerOutputDevices[0].hotendIds
|
||||||
|
for index in range(len(hotend_ids)):
|
||||||
|
machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index])
|
||||||
|
material_ids = machine_manager.printerOutputDevices[0].materialIds
|
||||||
|
for index in range(len(material_ids)):
|
||||||
|
machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index])
|
||||||
|
|
||||||
|
def _createAdditionalComponentsView(self):
|
||||||
|
Logger.log("d", "Creating additional ui components for UM3.")
|
||||||
|
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml"))
|
||||||
|
self.__additional_component = QQmlComponent(Application.getInstance()._engine, path)
|
||||||
|
|
||||||
|
# We need access to engine (although technically we can't)
|
||||||
|
self.__additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||||
|
self.__additional_components_context.setContextProperty("manager", self)
|
||||||
|
|
||||||
|
self.__additional_components_view = self.__additional_component.create(self.__additional_components_context)
|
||||||
|
if not self.__additional_components_view:
|
||||||
|
Logger.log("w", "Could not create ui components for UM3.")
|
||||||
|
return
|
||||||
|
|
||||||
|
Application.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
|
||||||
|
Application.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo"))
|
369
plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
Normal file
369
plugins/UM3NetworkPrinting/DiscoverUM3Action.qml
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
import UM 1.2 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Controls 1.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import QtQuick.Window 2.1
|
||||||
|
|
||||||
|
Cura.MachineAction
|
||||||
|
{
|
||||||
|
id: base
|
||||||
|
anchors.fill: parent;
|
||||||
|
property var selectedPrinter: null
|
||||||
|
property bool completeProperties: true
|
||||||
|
property var connectingToPrinter: null
|
||||||
|
|
||||||
|
Connections
|
||||||
|
{
|
||||||
|
target: dialog ? dialog : null
|
||||||
|
ignoreUnknownSignals: true
|
||||||
|
onNextClicked:
|
||||||
|
{
|
||||||
|
// Connect to the printer if the MachineAction is currently shown
|
||||||
|
if(base.parent.wizard == dialog)
|
||||||
|
{
|
||||||
|
connectToPrinter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToPrinter()
|
||||||
|
{
|
||||||
|
if(base.selectedPrinter && base.completeProperties)
|
||||||
|
{
|
||||||
|
var printerKey = base.selectedPrinter.getKey()
|
||||||
|
if(connectingToPrinter != printerKey) {
|
||||||
|
// prevent an infinite loop
|
||||||
|
connectingToPrinter = printerKey;
|
||||||
|
manager.setKey(printerKey);
|
||||||
|
completed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
anchors.fill: parent;
|
||||||
|
id: discoverUM3Action
|
||||||
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
SystemPalette { id: palette }
|
||||||
|
UM.I18nCatalog { id: catalog; name:"cura" }
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: pageTitle
|
||||||
|
width: parent.width
|
||||||
|
text: catalog.i18nc("@title:window", "Connect to Networked Printer")
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font.pointSize: 18
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: pageDescription
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: catalog.i18nc("@label", "To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your printer from the list below:")
|
||||||
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
spacing: UM.Theme.getSize("default_lining").width
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: addButton
|
||||||
|
text: catalog.i18nc("@action:button", "Add");
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
manualPrinterDialog.showDialog("", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: editButton
|
||||||
|
text: catalog.i18nc("@action:button", "Edit")
|
||||||
|
enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true"
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
manualPrinterDialog.showDialog(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: removeButton
|
||||||
|
text: catalog.i18nc("@action:button", "Remove")
|
||||||
|
enabled: base.selectedPrinter != null && base.selectedPrinter.getProperty("manual") == "true"
|
||||||
|
onClicked: manager.removeManualPrinter(base.selectedPrinter.getKey(), base.selectedPrinter.ipAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: rediscoverButton
|
||||||
|
text: catalog.i18nc("@action:button", "Refresh")
|
||||||
|
onClicked: manager.restartDiscovery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
id: contentRow
|
||||||
|
width: parent.width
|
||||||
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
width: parent.width * 0.5
|
||||||
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
ScrollView
|
||||||
|
{
|
||||||
|
id: objectListContainer
|
||||||
|
frameVisible: true
|
||||||
|
width: parent.width
|
||||||
|
height: base.height - contentRow.y - discoveryTip.height
|
||||||
|
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
parent: viewport
|
||||||
|
anchors.fill: parent
|
||||||
|
color: palette.light
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView
|
||||||
|
{
|
||||||
|
id: listview
|
||||||
|
model: manager.foundDevices
|
||||||
|
onModelChanged:
|
||||||
|
{
|
||||||
|
var selectedKey = manager.getStoredKey();
|
||||||
|
for(var i = 0; i < model.length; i++) {
|
||||||
|
if(model[i].getKey() == selectedKey)
|
||||||
|
{
|
||||||
|
currentIndex = i;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentIndex = -1;
|
||||||
|
}
|
||||||
|
width: parent.width
|
||||||
|
currentIndex: -1
|
||||||
|
onCurrentIndexChanged:
|
||||||
|
{
|
||||||
|
base.selectedPrinter = listview.model[currentIndex];
|
||||||
|
// Only allow connecting if the printer has responded to API query since the last refresh
|
||||||
|
base.completeProperties = base.selectedPrinter != null && base.selectedPrinter.getProperty("incomplete") != "true";
|
||||||
|
}
|
||||||
|
Component.onCompleted: manager.startDiscovery()
|
||||||
|
delegate: Rectangle
|
||||||
|
{
|
||||||
|
height: childrenRect.height
|
||||||
|
color: ListView.isCurrentItem ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
|
||||||
|
width: parent.width
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.right: parent.right
|
||||||
|
text: listview.model[index].name
|
||||||
|
color: parent.ListView.isCurrentItem ? palette.highlightedText : palette.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
anchors.fill: parent;
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
if(!parent.ListView.isCurrentItem)
|
||||||
|
{
|
||||||
|
parent.ListView.view.currentIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: discoveryTip
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
//: Tips label
|
||||||
|
//TODO: get actual link from webteam
|
||||||
|
text: catalog.i18nc("@label", "If your printer is not listed, read the <a href='%1'>network-printing troubleshooting guide</a>").arg("https://ultimaker.com/en/troubleshooting");
|
||||||
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
width: parent.width * 0.5
|
||||||
|
visible: base.selectedPrinter ? true : false
|
||||||
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: base.selectedPrinter ? base.selectedPrinter.name : ""
|
||||||
|
font: UM.Theme.getFont("large")
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
Grid
|
||||||
|
{
|
||||||
|
visible: base.completeProperties
|
||||||
|
width: parent.width
|
||||||
|
columns: 2
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
width: parent.width * 0.5
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: catalog.i18nc("@label", "Type")
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
width: parent.width * 0.5
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text:
|
||||||
|
{
|
||||||
|
if(base.selectedPrinter)
|
||||||
|
{
|
||||||
|
if(base.selectedPrinter.printerType == "ultimaker3")
|
||||||
|
{
|
||||||
|
return catalog.i18nc("@label", "Ultimaker 3")
|
||||||
|
} else if(base.selectedPrinter.printerType == "ultimaker3_extended")
|
||||||
|
{
|
||||||
|
return catalog.i18nc("@label", "Ultimaker 3 Extended")
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return catalog.i18nc("@label", "Unknown") // We have no idea what type it is. Should not happen 'in the field'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
width: parent.width * 0.5
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: catalog.i18nc("@label", "Firmware version")
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
width: parent.width * 0.5
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: base.selectedPrinter ? base.selectedPrinter.firmwareVersion : ""
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
width: parent.width * 0.5
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: catalog.i18nc("@label", "Address")
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
width: parent.width * 0.5
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: base.selectedPrinter ? base.selectedPrinter.ipAddress : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
visible: base.selectedPrinter != null && !base.completeProperties
|
||||||
|
text: catalog.i18nc("@label", "The printer at this address has not yet responded." )
|
||||||
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:button", "Connect")
|
||||||
|
enabled: (base.selectedPrinter && base.completeProperties) ? true : false
|
||||||
|
onClicked: connectToPrinter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.Dialog
|
||||||
|
{
|
||||||
|
id: manualPrinterDialog
|
||||||
|
property string printerKey
|
||||||
|
property alias addressText: addressField.text
|
||||||
|
|
||||||
|
title: catalog.i18nc("@title:window", "Printer Address")
|
||||||
|
|
||||||
|
minimumWidth: 400 * Screen.devicePixelRatio
|
||||||
|
minimumHeight: 120 * Screen.devicePixelRatio
|
||||||
|
width: minimumWidth
|
||||||
|
height: minimumHeight
|
||||||
|
|
||||||
|
signal showDialog(string key, string address)
|
||||||
|
onShowDialog:
|
||||||
|
{
|
||||||
|
printerKey = key;
|
||||||
|
|
||||||
|
addressText = address;
|
||||||
|
addressField.selectAll();
|
||||||
|
addressField.focus = true;
|
||||||
|
|
||||||
|
manualPrinterDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted:
|
||||||
|
{
|
||||||
|
manager.setManualPrinter(printerKey, addressText)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@alabel","Enter the IP address or hostname of your printer on the network.")
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField
|
||||||
|
{
|
||||||
|
id: addressField
|
||||||
|
width: parent.width
|
||||||
|
maximumLength: 40
|
||||||
|
validator: RegExpValidator
|
||||||
|
{
|
||||||
|
regExp: /[a-zA-Z0-9\.\-\_]*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rightButtons: [
|
||||||
|
Button {
|
||||||
|
text: catalog.i18nc("@action:button","Cancel")
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
manualPrinterDialog.reject()
|
||||||
|
manualPrinterDialog.hide()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Button {
|
||||||
|
text: catalog.i18nc("@action:button", "Ok")
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
manualPrinterDialog.accept()
|
||||||
|
manualPrinterDialog.hide()
|
||||||
|
}
|
||||||
|
enabled: manualPrinterDialog.addressText.trim() != ""
|
||||||
|
isDefault: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
1027
plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py
Normal file
1027
plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py
Normal file
File diff suppressed because it is too large
Load diff
210
plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py
Normal file
210
plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||||
|
from . import NetworkPrinterOutputDevice
|
||||||
|
|
||||||
|
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Signal import Signal, signalemitter
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Preferences import Preferences
|
||||||
|
|
||||||
|
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkReply
|
||||||
|
from PyQt5.QtCore import QUrl
|
||||||
|
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
|
||||||
|
# Zero-Conf is used to detect printers, which are saved in a dict.
|
||||||
|
# If we discover a printer that has the same key as the active machine instance a connection is made.
|
||||||
|
@signalemitter
|
||||||
|
class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._zero_conf = None
|
||||||
|
self._browser = None
|
||||||
|
self._printers = {}
|
||||||
|
|
||||||
|
self._api_version = "1"
|
||||||
|
self._api_prefix = "/api/v" + self._api_version + "/"
|
||||||
|
|
||||||
|
self._network_manager = QNetworkAccessManager()
|
||||||
|
self._network_manager.finished.connect(self._onNetworkRequestFinished)
|
||||||
|
|
||||||
|
# List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces
|
||||||
|
# authentication requests.
|
||||||
|
self._old_printers = []
|
||||||
|
|
||||||
|
# Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
||||||
|
self.addPrinterSignal.connect(self.addPrinter)
|
||||||
|
self.removePrinterSignal.connect(self.removePrinter)
|
||||||
|
Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
|
||||||
|
|
||||||
|
# Get list of manual printers from preferences
|
||||||
|
self._preferences = Preferences.getInstance()
|
||||||
|
self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames
|
||||||
|
self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",")
|
||||||
|
|
||||||
|
addPrinterSignal = Signal()
|
||||||
|
removePrinterSignal = Signal()
|
||||||
|
printerListChanged = Signal()
|
||||||
|
|
||||||
|
## Start looking for devices on network.
|
||||||
|
def start(self):
|
||||||
|
self.startDiscovery()
|
||||||
|
|
||||||
|
def startDiscovery(self):
|
||||||
|
self.stop()
|
||||||
|
if self._browser:
|
||||||
|
self._browser.cancel()
|
||||||
|
self._browser = None
|
||||||
|
self._old_printers = [printer_name for printer_name in self._printers]
|
||||||
|
self._printers = {}
|
||||||
|
self.printerListChanged.emit()
|
||||||
|
# After network switching, one must make a new instance of Zeroconf
|
||||||
|
# On windows, the instance creation is very fast (unnoticable). Other platforms?
|
||||||
|
self._zero_conf = Zeroconf()
|
||||||
|
self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged])
|
||||||
|
|
||||||
|
# Look for manual instances from preference
|
||||||
|
for address in self._manual_instances:
|
||||||
|
if address:
|
||||||
|
self.addManualPrinter(address)
|
||||||
|
|
||||||
|
def addManualPrinter(self, address):
|
||||||
|
if address not in self._manual_instances:
|
||||||
|
self._manual_instances.append(address)
|
||||||
|
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
|
||||||
|
|
||||||
|
name = address
|
||||||
|
instance_name = "manual:%s" % address
|
||||||
|
properties = { b"name": name.encode("utf-8"), b"manual": b"true", b"incomplete": b"true" }
|
||||||
|
|
||||||
|
if instance_name not in self._printers:
|
||||||
|
# Add a preliminary printer instance
|
||||||
|
self.addPrinter(instance_name, address, properties)
|
||||||
|
|
||||||
|
self.checkManualPrinter(address)
|
||||||
|
|
||||||
|
def removeManualPrinter(self, key, address = None):
|
||||||
|
if key in self._printers:
|
||||||
|
if not address:
|
||||||
|
address = self._printers[key].ipAddress
|
||||||
|
self.removePrinter(key)
|
||||||
|
|
||||||
|
if address in self._manual_instances:
|
||||||
|
self._manual_instances.remove(address)
|
||||||
|
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
|
||||||
|
|
||||||
|
def checkManualPrinter(self, address):
|
||||||
|
# Check if a printer exists at this address
|
||||||
|
# If a printer responds, it will replace the preliminary printer created above
|
||||||
|
url = QUrl("http://" + address + self._api_prefix + "system")
|
||||||
|
name_request = QNetworkRequest(url)
|
||||||
|
self._network_manager.get(name_request)
|
||||||
|
|
||||||
|
## Handler for all requests that have finished.
|
||||||
|
def _onNetworkRequestFinished(self, reply):
|
||||||
|
reply_url = reply.url().toString()
|
||||||
|
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||||
|
|
||||||
|
if reply.operation() == QNetworkAccessManager.GetOperation:
|
||||||
|
if "system" in reply_url: # Name returned from printer.
|
||||||
|
if status_code == 200:
|
||||||
|
system_info = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||||
|
address = reply.url().host()
|
||||||
|
name = ("%s (%s)" % (system_info["name"], address))
|
||||||
|
|
||||||
|
instance_name = "manual:%s" % address
|
||||||
|
properties = { b"name": name.encode("utf-8"), b"firmware_version": system_info["firmware"].encode("utf-8"), b"manual": b"true" }
|
||||||
|
if instance_name in self._printers:
|
||||||
|
# Only replace the printer if it is still in the list of (manual) printers
|
||||||
|
self.removePrinter(instance_name)
|
||||||
|
self.addPrinter(instance_name, address, properties)
|
||||||
|
|
||||||
|
## Stop looking for devices on network.
|
||||||
|
def stop(self):
|
||||||
|
if self._zero_conf is not None:
|
||||||
|
Logger.log("d", "zeroconf close...")
|
||||||
|
self._zero_conf.close()
|
||||||
|
|
||||||
|
def getPrinters(self):
|
||||||
|
return self._printers
|
||||||
|
|
||||||
|
def reCheckConnections(self):
|
||||||
|
active_machine = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if not active_machine:
|
||||||
|
return
|
||||||
|
|
||||||
|
for key in self._printers:
|
||||||
|
if key == active_machine.getMetaDataEntry("um_network_key"):
|
||||||
|
Logger.log("d", "Connecting [%s]..." % key)
|
||||||
|
self._printers[key].connect()
|
||||||
|
self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
|
||||||
|
else:
|
||||||
|
if self._printers[key].isConnected():
|
||||||
|
Logger.log("d", "Closing connection [%s]..." % key)
|
||||||
|
self._printers[key].close()
|
||||||
|
|
||||||
|
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
||||||
|
def addPrinter(self, name, address, properties):
|
||||||
|
printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties, self._api_prefix)
|
||||||
|
self._printers[printer.getKey()] = printer
|
||||||
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"):
|
||||||
|
if printer.getKey() not in self._old_printers: # Was the printer already connected, but a re-scan forced?
|
||||||
|
Logger.log("d", "addPrinter, connecting [%s]..." % printer.getKey())
|
||||||
|
self._printers[printer.getKey()].connect()
|
||||||
|
printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
|
||||||
|
self.printerListChanged.emit()
|
||||||
|
|
||||||
|
def removePrinter(self, name):
|
||||||
|
printer = self._printers.pop(name, None)
|
||||||
|
if printer:
|
||||||
|
if printer.isConnected():
|
||||||
|
printer.connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
|
||||||
|
Logger.log("d", "removePrinter, disconnecting [%s]..." % name)
|
||||||
|
printer.disconnect()
|
||||||
|
self.printerListChanged.emit()
|
||||||
|
|
||||||
|
## Handler for when the connection state of one of the detected printers changes
|
||||||
|
def _onPrinterConnectionStateChanged(self, key):
|
||||||
|
if key not in self._printers:
|
||||||
|
return
|
||||||
|
if self._printers[key].isConnected():
|
||||||
|
self.getOutputDeviceManager().addOutputDevice(self._printers[key])
|
||||||
|
else:
|
||||||
|
self.getOutputDeviceManager().removeOutputDevice(key)
|
||||||
|
|
||||||
|
## Handler for zeroConf detection
|
||||||
|
def _onServiceChanged(self, zeroconf, service_type, name, state_change):
|
||||||
|
if state_change == ServiceStateChange.Added:
|
||||||
|
Logger.log("d", "Bonjour service added: %s" % name)
|
||||||
|
|
||||||
|
# First try getting info from zeroconf cache
|
||||||
|
info = ServiceInfo(service_type, name, properties = {})
|
||||||
|
for record in zeroconf.cache.entries_with_name(name.lower()):
|
||||||
|
info.update_record(zeroconf, time.time(), record)
|
||||||
|
|
||||||
|
for record in zeroconf.cache.entries_with_name(info.server):
|
||||||
|
info.update_record(zeroconf, time.time(), record)
|
||||||
|
if info.address:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Request more data if info is not complete
|
||||||
|
if not info.address:
|
||||||
|
Logger.log("d", "Trying to get address of %s", name)
|
||||||
|
info = zeroconf.get_service_info(service_type, name)
|
||||||
|
|
||||||
|
if info:
|
||||||
|
type_of_device = info.properties.get(b"type", None).decode("utf-8")
|
||||||
|
if type_of_device == "printer":
|
||||||
|
address = '.'.join(map(lambda n: str(n), info.address))
|
||||||
|
self.addPrinterSignal.emit(str(name), address, info.properties)
|
||||||
|
else:
|
||||||
|
Logger.log("w", "The type of the found device is '%s', not 'printer'! Ignoring.." %type_of_device )
|
||||||
|
else:
|
||||||
|
Logger.log("w", "Could not get information about %s" % name)
|
||||||
|
|
||||||
|
elif state_change == ServiceStateChange.Removed:
|
||||||
|
Logger.log("d", "Bonjour service removed: %s" % name)
|
||||||
|
self.removePrinterSignal.emit(str(name))
|
124
plugins/UM3NetworkPrinting/UM3InfoComponents.qml
Normal file
124
plugins/UM3NetworkPrinting/UM3InfoComponents.qml
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import UM 1.2 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Controls 1.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import QtQuick.Window 2.1
|
||||||
|
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
id: base
|
||||||
|
|
||||||
|
property bool isUM3: Cura.MachineManager.activeQualityDefinitionId == "ultimaker3"
|
||||||
|
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
|
||||||
|
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
|
||||||
|
property bool authenticationRequested: printerConnected && Cura.MachineManager.printerOutputDevices[0].authenticationState == 2 // AuthState.AuthenticationRequested
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
objectName: "networkPrinterConnectButton"
|
||||||
|
visible: isUM3
|
||||||
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
height: UM.Theme.getSize("save_button_save_to_button").height
|
||||||
|
tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer")
|
||||||
|
text: catalog.i18nc("@action:button", "Request Access")
|
||||||
|
style: UM.Theme.styles.sidebar_action_button
|
||||||
|
onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication()
|
||||||
|
visible: printerConnected && !printerAcceptsCommands && !authenticationRequested
|
||||||
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
height: UM.Theme.getSize("save_button_save_to_button").height
|
||||||
|
tooltip: catalog.i18nc("@info:tooltip", "Connect to a printer")
|
||||||
|
text: catalog.i18nc("@action:button", "Connect")
|
||||||
|
style: UM.Theme.styles.sidebar_action_button
|
||||||
|
onClicked: connectActionDialog.show()
|
||||||
|
visible: !printerConnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.Dialog
|
||||||
|
{
|
||||||
|
id: connectActionDialog
|
||||||
|
Loader
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
source: "DiscoverUM3Action.qml"
|
||||||
|
}
|
||||||
|
rightButtons: Button
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:button", "Close")
|
||||||
|
iconName: "dialog-close"
|
||||||
|
onClicked: connectActionDialog.reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
objectName: "networkPrinterConnectionInfo"
|
||||||
|
visible: isUM3
|
||||||
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer")
|
||||||
|
text: catalog.i18nc("@action:button", "Request Access")
|
||||||
|
onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication()
|
||||||
|
visible: printerConnected && !printerAcceptsCommands && !authenticationRequested
|
||||||
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
visible: printerConnected
|
||||||
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: childrenRect.height
|
||||||
|
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
Repeater
|
||||||
|
{
|
||||||
|
model: Cura.ExtrudersModel { simpleNames: true }
|
||||||
|
Label { text: model.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
Repeater
|
||||||
|
{
|
||||||
|
id: nozzleColumn
|
||||||
|
model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].hotendIds : null
|
||||||
|
Label { text: nozzleColumn.model[index] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
Repeater
|
||||||
|
{
|
||||||
|
id: materialColumn
|
||||||
|
model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].materialNames : null
|
||||||
|
Label { text: materialColumn.model[index] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura")
|
||||||
|
text: catalog.i18nc("@action:button", "Activate Configuration")
|
||||||
|
visible: printerConnected
|
||||||
|
onClicked: manager.loadConfigurationFromPrinter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.I18nCatalog{id: catalog; name:"cura"}
|
||||||
|
}
|
20
plugins/UM3NetworkPrinting/__init__.py
Normal file
20
plugins/UM3NetworkPrinting/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Copyright (c) 2015 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
from . import NetworkPrinterOutputDevicePlugin
|
||||||
|
from . import DiscoverUM3Action
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
def getMetaData():
|
||||||
|
return {
|
||||||
|
"plugin": {
|
||||||
|
"name": "UM3 Network Connection",
|
||||||
|
"author": "Ultimaker",
|
||||||
|
"description": catalog.i18nc("@info:whatsthis", "Manages network connections to Ultimaker 3 printers"),
|
||||||
|
"version": "1.0",
|
||||||
|
"api": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def register(app):
|
||||||
|
return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}
|
|
@ -432,7 +432,14 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
# This is ignored.
|
# This is ignored.
|
||||||
# \param filter_by_machine Whether to filter MIME types by machine. This
|
# \param filter_by_machine Whether to filter MIME types by machine. This
|
||||||
# is ignored.
|
# is ignored.
|
||||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False):
|
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
|
||||||
|
container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if container_stack.getProperty("machine_gcode_flavor", "value") == "UltiGCode" or not container_stack.getMetaDataEntry("supports_usb_connection"):
|
||||||
|
self._error_message = Message(catalog.i18nc("@info:status",
|
||||||
|
"Unable to start a new job because the printer does not support usb printing."))
|
||||||
|
self._error_message.show()
|
||||||
|
return
|
||||||
|
|
||||||
Application.getInstance().showPrintMonitor.emit(True)
|
Application.getInstance().showPrintMonitor.emit(True)
|
||||||
self.startPrint()
|
self.startPrint()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
## A simple action to handle manual bed leveling procedure for printers that don't have it on the firmware.
|
||||||
|
# This is currently only used by the Ultimaker Original+
|
||||||
class BedLevelMachineAction(MachineAction):
|
class BedLevelMachineAction(MachineAction):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("BedLevel", catalog.i18nc("@action", "Level build plate"))
|
super().__init__("BedLevel", catalog.i18nc("@action", "Level build plate"))
|
||||||
|
|
|
@ -8,6 +8,7 @@ from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
## Action to check up if the self-built UMO was done correctly.
|
||||||
class UMOCheckupMachineAction(MachineAction):
|
class UMOCheckupMachineAction(MachineAction):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("UMOCheckup", catalog.i18nc("@action", "Checkup"))
|
super().__init__("UMOCheckup", catalog.i18nc("@action", "Checkup"))
|
||||||
|
@ -27,7 +28,6 @@ class UMOCheckupMachineAction(MachineAction):
|
||||||
|
|
||||||
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||||
|
|
||||||
|
|
||||||
onBedTestCompleted = pyqtSignal()
|
onBedTestCompleted = pyqtSignal()
|
||||||
onHotendTestCompleted = pyqtSignal()
|
onHotendTestCompleted = pyqtSignal()
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,11 @@ from UM.i18n import i18nCatalog
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
import UM.Settings.InstanceContainer
|
||||||
|
|
||||||
|
|
||||||
|
## The Ultimaker Original can have a few revisions & upgrades. This action helps with selecting them, so they are added
|
||||||
|
# as a variant.
|
||||||
class UMOUpgradeSelection(MachineAction):
|
class UMOUpgradeSelection(MachineAction):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("UMOUpgradeSelection", catalog.i18nc("@action", "Select upgrades"))
|
super().__init__("UMOUpgradeSelection", catalog.i18nc("@action", "Select upgrades"))
|
||||||
|
@ -27,19 +32,23 @@ class UMOUpgradeSelection(MachineAction):
|
||||||
def setHeatedBed(self, heated_bed = True):
|
def setHeatedBed(self, heated_bed = True):
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
variant = global_container_stack.findContainer({"type": "variant"})
|
# Make sure there is a definition_changes container to store the machine settings
|
||||||
if variant:
|
definition_changes_container = global_container_stack.findContainer({"type": "definition_changes"})
|
||||||
if variant.getId() == "empty_variant":
|
if not definition_changes_container:
|
||||||
variant_index = global_container_stack.getContainerIndex(variant)
|
definition_changes_container = self._createDefinitionChangesContainer(global_container_stack)
|
||||||
variant = self._createVariant(global_container_stack, variant_index)
|
|
||||||
variant.setProperty("machine_heated_bed", "value", heated_bed)
|
definition_changes_container.setProperty("machine_heated_bed", "value", heated_bed)
|
||||||
self.heatedBedChanged.emit()
|
self.heatedBedChanged.emit()
|
||||||
|
|
||||||
def _createVariant(self, global_container_stack, variant_index):
|
def _createDefinitionChangesContainer(self, global_container_stack):
|
||||||
# Create and switch to a variant to store the settings in
|
# Create a definition_changes container to store the settings in and add it to the stack
|
||||||
new_variant = InstanceContainer(global_container_stack.getName() + "_variant")
|
definition_changes_container = UM.Settings.InstanceContainer(global_container_stack.getName() + "_settings")
|
||||||
new_variant.addMetaDataEntry("type", "variant")
|
definition = global_container_stack.getBottom()
|
||||||
new_variant.setDefinition(global_container_stack.getBottom())
|
definition_changes_container.setDefinition(definition)
|
||||||
ContainerRegistry.getInstance().addContainer(new_variant)
|
definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
||||||
global_container_stack.replaceContainer(variant_index, new_variant)
|
|
||||||
return new_variant
|
UM.Settings.ContainerRegistry.getInstance().addContainer(definition_changes_container)
|
||||||
|
# Insert definition_changes between the definition and the variant
|
||||||
|
global_container_stack.insertContainer(-1, definition_changes_container)
|
||||||
|
|
||||||
|
return definition_changes_container
|
||||||
|
|
|
@ -6,6 +6,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
## Upgrade the firmware of a machine by USB with this action.
|
||||||
class UpgradeFirmwareMachineAction(MachineAction):
|
class UpgradeFirmwareMachineAction(MachineAction):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware"))
|
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware"))
|
||||||
|
@ -13,6 +14,6 @@ class UpgradeFirmwareMachineAction(MachineAction):
|
||||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||||
|
|
||||||
def _onContainerAdded(self, container):
|
def _onContainerAdded(self, container):
|
||||||
# Add this action as a supported action to all machine definitions
|
# Add this action as a supported action to all machine definitions if they support USB connection
|
||||||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
|
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
|
||||||
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||||
|
|
120
plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py
Normal file
120
plugins/VersionUpgrade/VersionUpgrade22to24/VersionUpgrade.py
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
# Copyright (c) 2016 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import configparser #To get version numbers from config files.
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import io
|
||||||
|
|
||||||
|
from UM import Resources
|
||||||
|
from UM.VersionUpgrade import VersionUpgrade # Superclass of the plugin.
|
||||||
|
|
||||||
|
class VersionUpgrade22to24(VersionUpgrade):
|
||||||
|
|
||||||
|
def upgradeMachineInstance(self, serialised, filename):
|
||||||
|
# All of this is needed to upgrade custom variant machines from old Cura to 2.4 where
|
||||||
|
# `definition_changes` instance container has been introduced. Variant files which
|
||||||
|
# look like the the handy work of the old machine settings plugin are converted directly
|
||||||
|
# on disk.
|
||||||
|
|
||||||
|
config = configparser.ConfigParser(interpolation = None)
|
||||||
|
config.read_string(serialised) # Read the input string as config file.
|
||||||
|
config.set("general", "version", "3")
|
||||||
|
|
||||||
|
container_list = []
|
||||||
|
if config.has_section("containers"):
|
||||||
|
for index, container_id in config.items("containers"):
|
||||||
|
container_list.append(container_id)
|
||||||
|
elif config.has_option("general", "containers"):
|
||||||
|
containers = config.get("general", "containers")
|
||||||
|
container_list = containers.split(",")
|
||||||
|
|
||||||
|
user_variants = self.__getUserVariants()
|
||||||
|
name_path_dict = {}
|
||||||
|
for variant in user_variants:
|
||||||
|
name_path_dict[variant.get("name")] = variant.get("path")
|
||||||
|
|
||||||
|
user_variant_names = set(container_list).intersection(name_path_dict.keys())
|
||||||
|
if len(user_variant_names):
|
||||||
|
# One of the user defined variants appears in the list of containers in the stack.
|
||||||
|
|
||||||
|
for variant_name in user_variant_names: # really there should just be one variant to convert.
|
||||||
|
config_name = self.__convertVariant(name_path_dict.get(variant_name))
|
||||||
|
|
||||||
|
# Change the name of variant and insert empty_variant into the stack.
|
||||||
|
new_container_list = []
|
||||||
|
for item in container_list:
|
||||||
|
if item == variant_name:
|
||||||
|
new_container_list.append(config_name)
|
||||||
|
new_container_list.append("empty_variant")
|
||||||
|
else:
|
||||||
|
new_container_list.append(item)
|
||||||
|
|
||||||
|
container_list = new_container_list
|
||||||
|
|
||||||
|
if not config.has_section("containers"):
|
||||||
|
config.add_section("containers")
|
||||||
|
|
||||||
|
config.remove_option("general", "containers")
|
||||||
|
|
||||||
|
for index in range(len(container_list)):
|
||||||
|
config.set("containers", index, container_list[index])
|
||||||
|
|
||||||
|
output = io.StringIO()
|
||||||
|
config.write(output)
|
||||||
|
return [filename], [output.getvalue()]
|
||||||
|
|
||||||
|
def __convertVariant(self, variant_path):
|
||||||
|
# Copy the variant to the machine_instances/*_settings.inst.cfg
|
||||||
|
variant_config = configparser.ConfigParser(interpolation=None)
|
||||||
|
with open(variant_path, "r") as fhandle:
|
||||||
|
variant_config.read_file(fhandle)
|
||||||
|
|
||||||
|
if variant_config.has_section("general") and variant_config.has_option("general", "name"):
|
||||||
|
config_name = variant_config.get("general", "name")
|
||||||
|
if config_name.endswith("_variant"):
|
||||||
|
config_name = config_name[:-len("_variant")] + "_settings"
|
||||||
|
variant_config.set("general", "name", config_name)
|
||||||
|
|
||||||
|
if not variant_config.has_section("metadata"):
|
||||||
|
variant_config.add_section("metadata")
|
||||||
|
variant_config.set("metadata", "type", "definition_changes")
|
||||||
|
|
||||||
|
resource_path = Resources.getDataStoragePath()
|
||||||
|
machine_instances_dir = os.path.join(resource_path, "machine_instances")
|
||||||
|
|
||||||
|
if variant_path.endswith("_variant.inst.cfg"):
|
||||||
|
variant_path = variant_path[:-len("_variant.inst.cfg")] + "_settings.inst.cfg"
|
||||||
|
|
||||||
|
with open(os.path.join(machine_instances_dir, os.path.basename(variant_path)), "w") as fp:
|
||||||
|
variant_config.write(fp)
|
||||||
|
|
||||||
|
return config_name
|
||||||
|
|
||||||
|
def __getUserVariants(self):
|
||||||
|
resource_path = Resources.getDataStoragePath()
|
||||||
|
variants_dir = os.path.join(resource_path, "variants")
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for entry in os.scandir(variants_dir):
|
||||||
|
if entry.name.endswith('.inst.cfg') and entry.is_file():
|
||||||
|
config = configparser.ConfigParser(interpolation = None)
|
||||||
|
with open(entry.path, "r") as fhandle:
|
||||||
|
config.read_file(fhandle)
|
||||||
|
if config.has_section("general") and config.has_option("general", "name"):
|
||||||
|
result.append( { "path": entry.path, "name": config.get("general", "name") } )
|
||||||
|
return result
|
||||||
|
|
||||||
|
def upgradeExtruderTrain(self, serialised, filename):
|
||||||
|
config = configparser.ConfigParser(interpolation = None)
|
||||||
|
config.read_string(serialised) # Read the input string as config file.
|
||||||
|
config.set("general", "version", "3") # Just bump the version number. That is all we need for now.
|
||||||
|
|
||||||
|
output = io.StringIO()
|
||||||
|
config.write(output)
|
||||||
|
return [filename], [output.getvalue()]
|
||||||
|
|
||||||
|
def getCfgVersion(self, serialised):
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialised)
|
||||||
|
return int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
|
38
plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py
Normal file
38
plugins/VersionUpgrade/VersionUpgrade22to24/__init__.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Copyright (c) 2016 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from . import VersionUpgrade
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
upgrade = VersionUpgrade.VersionUpgrade22to24()
|
||||||
|
|
||||||
|
def getMetaData():
|
||||||
|
return {
|
||||||
|
"plugin": {
|
||||||
|
"name": catalog.i18nc("@label", "Version Upgrade 2.2 to 2.4"),
|
||||||
|
"author": "Ultimaker",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.2 to Cura 2.4."),
|
||||||
|
"api": 3
|
||||||
|
},
|
||||||
|
"version_upgrade": {
|
||||||
|
# From To Upgrade function
|
||||||
|
("machine_instance", 2): ("machine_stack", 3, upgrade.upgradeMachineInstance),
|
||||||
|
("extruder_train", 2): ("extruder_train", 3, upgrade.upgradeExtruderTrain)
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"machine_stack": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./machine_instances"}
|
||||||
|
},
|
||||||
|
"extruder_train": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./extruders"}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def register(app):
|
||||||
|
return { "version_upgrade": upgrade }
|
|
@ -700,7 +700,11 @@ class X3DReader(MeshReader):
|
||||||
if not c is None:
|
if not c is None:
|
||||||
pt = c.attrib.get("point")
|
pt = c.attrib.get("point")
|
||||||
if pt:
|
if pt:
|
||||||
co = [float(x) for x in pt.split()]
|
# allow the list of float values in 'point' attribute to
|
||||||
|
# be separated by commas or whitespace as per spec of
|
||||||
|
# XML encoding of X3D
|
||||||
|
# Ref ISO/IEC 19776-1:2015 : Section 5.1.2
|
||||||
|
co = [float(x) for vec in pt.split(',') for x in vec.split()]
|
||||||
num_verts = len(co) // 3
|
num_verts = len(co) // 3
|
||||||
self.verts = numpy.empty((4, num_verts), dtype=numpy.float32)
|
self.verts = numpy.empty((4, num_verts), dtype=numpy.float32)
|
||||||
self.verts[3,:] = numpy.ones((num_verts), dtype=numpy.float32)
|
self.verts[3,:] = numpy.ones((num_verts), dtype=numpy.float32)
|
||||||
|
|
|
@ -37,7 +37,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
if self.isReadOnly():
|
if self.isReadOnly():
|
||||||
return
|
return
|
||||||
if self.getMetaDataEntry(key, None) == value:
|
if self.getMetaDataEntry(key, None) == value:
|
||||||
# Prevent loop caused by for loop.
|
# Prevent recursion caused by for loop.
|
||||||
return
|
return
|
||||||
|
|
||||||
super().setMetaDataEntry(key, value)
|
super().setMetaDataEntry(key, value)
|
||||||
|
@ -67,6 +67,17 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
for container in containers:
|
for container in containers:
|
||||||
container.setName(new_name)
|
container.setName(new_name)
|
||||||
|
|
||||||
|
## Overridden from InstanceContainer, to set dirty to base file as well.
|
||||||
|
def setDirty(self, dirty):
|
||||||
|
super().setDirty(dirty)
|
||||||
|
base_file = self.getMetaDataEntry("base_file", None)
|
||||||
|
if base_file is not None and base_file != self._id:
|
||||||
|
containers = UM.Settings.ContainerRegistry.getInstance().findContainers(id=base_file)
|
||||||
|
if containers:
|
||||||
|
base_container = containers[0]
|
||||||
|
if not base_container.isReadOnly():
|
||||||
|
base_container.setDirty(dirty)
|
||||||
|
|
||||||
## Overridden from InstanceContainer
|
## Overridden from InstanceContainer
|
||||||
# def setProperty(self, key, property_name, property_value, container = None):
|
# def setProperty(self, key, property_name, property_value, container = None):
|
||||||
# if self.isReadOnly():
|
# if self.isReadOnly():
|
||||||
|
@ -348,10 +359,22 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
mapping[key] = element
|
mapping[key] = element
|
||||||
first.append(element)
|
first.append(element)
|
||||||
|
|
||||||
|
def clearData(self):
|
||||||
|
self._metadata = {}
|
||||||
|
self._name = ""
|
||||||
|
self._definition = None
|
||||||
|
self._instances = {}
|
||||||
|
self._read_only = False
|
||||||
|
self._dirty = False
|
||||||
|
self._path = ""
|
||||||
|
|
||||||
## Overridden from InstanceContainer
|
## Overridden from InstanceContainer
|
||||||
def deserialize(self, serialized):
|
def deserialize(self, serialized):
|
||||||
data = ET.fromstring(serialized)
|
data = ET.fromstring(serialized)
|
||||||
|
|
||||||
|
# Reset previous metadata
|
||||||
|
self.clearData() # Ensure any previous data is gone.
|
||||||
|
|
||||||
self.addMetaDataEntry("type", "material")
|
self.addMetaDataEntry("type", "material")
|
||||||
self.addMetaDataEntry("base_file", self.id)
|
self.addMetaDataEntry("base_file", self.id)
|
||||||
|
|
||||||
|
@ -411,7 +434,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
for entry in settings:
|
for entry in settings:
|
||||||
key = entry.get("key")
|
key = entry.get("key")
|
||||||
if key in self.__material_property_setting_map:
|
if key in self.__material_property_setting_map:
|
||||||
self.setProperty(self.__material_property_setting_map[key], "value", entry.text, self._definition)
|
self.setProperty(self.__material_property_setting_map[key], "value", entry.text)
|
||||||
global_setting_values[self.__material_property_setting_map[key]] = entry.text
|
global_setting_values[self.__material_property_setting_map[key]] = entry.text
|
||||||
elif key in self.__unmapped_settings:
|
elif key in self.__unmapped_settings:
|
||||||
if key == "hardware compatible":
|
if key == "hardware compatible":
|
||||||
|
@ -453,7 +476,16 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
definition = definitions[0]
|
definition = definitions[0]
|
||||||
|
|
||||||
if machine_compatibility:
|
if machine_compatibility:
|
||||||
new_material = XmlMaterialProfile(self.id + "_" + machine_id)
|
new_material_id = self.id + "_" + machine_id
|
||||||
|
|
||||||
|
# It could be that we are overwriting, so check if the ID already exists.
|
||||||
|
materials = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id=new_material_id)
|
||||||
|
if materials:
|
||||||
|
new_material = materials[0]
|
||||||
|
new_material.clearData()
|
||||||
|
else:
|
||||||
|
new_material = XmlMaterialProfile(new_material_id)
|
||||||
|
|
||||||
new_material.setName(self.getName())
|
new_material.setName(self.getName())
|
||||||
new_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
new_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
||||||
new_material.setDefinition(definition)
|
new_material.setDefinition(definition)
|
||||||
|
@ -461,16 +493,16 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
new_material.getMetaData()["compatible"] = machine_compatibility
|
new_material.getMetaData()["compatible"] = machine_compatibility
|
||||||
|
|
||||||
for key, value in global_setting_values.items():
|
for key, value in global_setting_values.items():
|
||||||
new_material.setProperty(key, "value", value, definition)
|
new_material.setProperty(key, "value", value)
|
||||||
|
|
||||||
for key, value in machine_setting_values.items():
|
for key, value in machine_setting_values.items():
|
||||||
new_material.setProperty(key, "value", value, definition)
|
new_material.setProperty(key, "value", value)
|
||||||
|
|
||||||
new_material._dirty = False
|
new_material._dirty = False
|
||||||
|
|
||||||
|
if not materials:
|
||||||
ContainerRegistry.getInstance().addContainer(new_material)
|
ContainerRegistry.getInstance().addContainer(new_material)
|
||||||
|
|
||||||
|
|
||||||
hotends = machine.iterfind("./um:hotend", self.__namespaces)
|
hotends = machine.iterfind("./um:hotend", self.__namespaces)
|
||||||
for hotend in hotends:
|
for hotend in hotends:
|
||||||
hotend_id = hotend.get("id")
|
hotend_id = hotend.get("id")
|
||||||
|
@ -499,7 +531,15 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
else:
|
else:
|
||||||
Logger.log("d", "Unsupported material setting %s", key)
|
Logger.log("d", "Unsupported material setting %s", key)
|
||||||
|
|
||||||
new_hotend_material = XmlMaterialProfile(self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_"))
|
# It could be that we are overwriting, so check if the ID already exists.
|
||||||
|
new_hotend_id = self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
|
||||||
|
materials = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id=new_hotend_id)
|
||||||
|
if materials:
|
||||||
|
new_hotend_material = materials[0]
|
||||||
|
new_hotend_material.clearData()
|
||||||
|
else:
|
||||||
|
new_hotend_material = XmlMaterialProfile(new_hotend_id)
|
||||||
|
|
||||||
new_hotend_material.setName(self.getName())
|
new_hotend_material.setName(self.getName())
|
||||||
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
||||||
new_hotend_material.setDefinition(definition)
|
new_hotend_material.setDefinition(definition)
|
||||||
|
@ -508,15 +548,16 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
|
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
|
||||||
|
|
||||||
for key, value in global_setting_values.items():
|
for key, value in global_setting_values.items():
|
||||||
new_hotend_material.setProperty(key, "value", value, definition)
|
new_hotend_material.setProperty(key, "value", value)
|
||||||
|
|
||||||
for key, value in machine_setting_values.items():
|
for key, value in machine_setting_values.items():
|
||||||
new_hotend_material.setProperty(key, "value", value, definition)
|
new_hotend_material.setProperty(key, "value", value)
|
||||||
|
|
||||||
for key, value in hotend_setting_values.items():
|
for key, value in hotend_setting_values.items():
|
||||||
new_hotend_material.setProperty(key, "value", value, definition)
|
new_hotend_material.setProperty(key, "value", value)
|
||||||
|
|
||||||
new_hotend_material._dirty = False
|
new_hotend_material._dirty = False
|
||||||
|
if not materials: # It was not added yet, do so now.
|
||||||
ContainerRegistry.getInstance().addContainer(new_hotend_material)
|
ContainerRegistry.getInstance().addContainer(new_hotend_material)
|
||||||
|
|
||||||
def _addSettingElement(self, builder, instance):
|
def _addSettingElement(self, builder, instance):
|
||||||
|
@ -537,7 +578,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
|
|
||||||
# Map XML file setting names to internal names
|
# Map XML file setting names to internal names
|
||||||
__material_property_setting_map = {
|
__material_property_setting_map = {
|
||||||
"print temperature": "material_print_temperature",
|
"print temperature": "default_material_print_temperature",
|
||||||
"heated bed temperature": "material_bed_temperature",
|
"heated bed temperature": "material_bed_temperature",
|
||||||
"standby temperature": "material_standby_temperature",
|
"standby temperature": "material_standby_temperature",
|
||||||
"processing temperature graph": "material_flow_temp_graph",
|
"processing temperature graph": "material_flow_temp_graph",
|
||||||
|
|
36
resources/definitions/delta_go.def.json
Normal file
36
resources/definitions/delta_go.def.json
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"id": "Delta_Go",
|
||||||
|
"name": "Delta Go",
|
||||||
|
"version": 2,
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Deltaprintr",
|
||||||
|
"manufacturer": "Deltaprintr",
|
||||||
|
"category": "Other",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"platform_offset": [ 0, 0, 0],
|
||||||
|
"platform": ""
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": { "default_value": "Delta Go" },
|
||||||
|
"material_diameter": { "default_value": 1.75 },
|
||||||
|
"speed_travel": { "default_value": 150 },
|
||||||
|
"prime_tower_size": { "default_value": 8.66 },
|
||||||
|
"infill_sparse_density": { "default_value": 10 },
|
||||||
|
"speed_wall_x": { "default_value": 30 },
|
||||||
|
"speed_wall_0": { "default_value": 30 },
|
||||||
|
"speed_topbottom": { "default_value": 20 },
|
||||||
|
"layer_height": { "default_value": 0.2 },
|
||||||
|
"speed_print": { "default_value": 30 },
|
||||||
|
"machine_heated_bed": { "default_value": false },
|
||||||
|
"machine_center_is_zero": { "default_value": true },
|
||||||
|
"machine_height": { "default_value": 127 },
|
||||||
|
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||||
|
"machine_depth": { "default_value": 115 },
|
||||||
|
"machine_width": { "default_value": 115 },
|
||||||
|
"retraction_amount": { "default_value": 4.2 },
|
||||||
|
"retraction_speed": { "default_value": 400 },
|
||||||
|
"machine_shape": { "default_value": "elliptic"}
|
||||||
|
}
|
||||||
|
}
|
|
@ -137,6 +137,21 @@
|
||||||
"settable_per_extruder": false,
|
"settable_per_extruder": false,
|
||||||
"settable_per_meshgroup": false
|
"settable_per_meshgroup": false
|
||||||
},
|
},
|
||||||
|
"machine_shape":
|
||||||
|
{
|
||||||
|
"label": "Build plate shape",
|
||||||
|
"description": "The shape of the build plate without taking unprintable areas into account.",
|
||||||
|
"default_value": "rectangular",
|
||||||
|
"type": "enum",
|
||||||
|
"options":
|
||||||
|
{
|
||||||
|
"rectangular": "Rectangular",
|
||||||
|
"elliptic": "Elliptic"
|
||||||
|
},
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": false,
|
||||||
|
"settable_per_meshgroup": false
|
||||||
|
},
|
||||||
"machine_height":
|
"machine_height":
|
||||||
{
|
{
|
||||||
"label": "Machine height",
|
"label": "Machine height",
|
||||||
|
@ -213,13 +228,24 @@
|
||||||
"machine_heat_zone_length":
|
"machine_heat_zone_length":
|
||||||
{
|
{
|
||||||
"label": "Heat zone length",
|
"label": "Heat zone length",
|
||||||
"description": "The distance from the tip of the nozzle in which heat from the nozzle is transfered to the filament.",
|
"description": "The distance from the tip of the nozzle in which heat from the nozzle is transferred to the filament.",
|
||||||
"default_value": 16,
|
"default_value": 16,
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true,
|
"settable_per_extruder": true,
|
||||||
"settable_per_meshgroup": false
|
"settable_per_meshgroup": false
|
||||||
},
|
},
|
||||||
|
"machine_filament_park_distance":
|
||||||
|
{
|
||||||
|
"label": "Filament Park Distance",
|
||||||
|
"description": "The distance from the tip of the nozzle where to park the filament when an extruder is no longer used.",
|
||||||
|
"default_value": 16,
|
||||||
|
"value": "machine_heat_zone_length",
|
||||||
|
"type": "float",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true,
|
||||||
|
"settable_per_meshgroup": false
|
||||||
|
},
|
||||||
"machine_nozzle_heat_up_speed":
|
"machine_nozzle_heat_up_speed":
|
||||||
{
|
{
|
||||||
"label": "Heat up speed",
|
"label": "Heat up speed",
|
||||||
|
@ -946,17 +972,39 @@
|
||||||
"z_seam_type":
|
"z_seam_type":
|
||||||
{
|
{
|
||||||
"label": "Z Seam Alignment",
|
"label": "Z Seam Alignment",
|
||||||
"description": "Starting point of each path in a layer. When paths in consecutive layers start at the same point a vertical seam may show on the print. When aligning these at the back, the seam is easiest to remove. When placed randomly the inaccuracies at the paths' start will be less noticeable. When taking the shortest path the print will be quicker.",
|
"description": "Starting point of each path in a layer. When paths in consecutive layers start at the same point a vertical seam may show on the print. When aligning these near a user specified location, the seam is easiest to remove. When placed randomly the inaccuracies at the paths' start will be less noticeable. When taking the shortest path the print will be quicker.",
|
||||||
"type": "enum",
|
"type": "enum",
|
||||||
"options":
|
"options":
|
||||||
{
|
{
|
||||||
"back": "Back",
|
"back": "User Specified",
|
||||||
"shortest": "Shortest",
|
"shortest": "Shortest",
|
||||||
"random": "Random"
|
"random": "Random"
|
||||||
},
|
},
|
||||||
"default_value": "shortest",
|
"default_value": "shortest",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
|
"z_seam_x":
|
||||||
|
{
|
||||||
|
"label": "Z Seam X",
|
||||||
|
"description": "The X coordinate of the position near where to start printing each part in a layer.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 100.0,
|
||||||
|
"value": "machine_width / 2",
|
||||||
|
"enabled": "z_seam_type == 'back'",
|
||||||
|
"settable_per_mesh": true
|
||||||
|
},
|
||||||
|
"z_seam_y":
|
||||||
|
{
|
||||||
|
"label": "Z Seam Y",
|
||||||
|
"description": "The Y coordinate of the position near where to start printing each part in a layer.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 100.0,
|
||||||
|
"value": "machine_depth * 3",
|
||||||
|
"enabled": "z_seam_type == 'back'",
|
||||||
|
"settable_per_mesh": true
|
||||||
|
},
|
||||||
"skin_no_small_gaps_heuristic":
|
"skin_no_small_gaps_heuristic":
|
||||||
{
|
{
|
||||||
"label": "Ignore Small Z Gaps",
|
"label": "Ignore Small Z Gaps",
|
||||||
|
@ -996,7 +1044,7 @@
|
||||||
"default_value": 2,
|
"default_value": 2,
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"minimum_value_warning": "infill_line_width",
|
"minimum_value_warning": "infill_line_width",
|
||||||
"value": "0 if infill_sparse_density == 0 else (infill_line_width * 100) / infill_sparse_density * (2 if infill_pattern == 'grid' else (3 if infill_pattern == 'triangles' or infill_pattern == 'cubic' else (4 if infill_pattern == 'tetrahedral' else 1)))",
|
"value": "0 if infill_sparse_density == 0 else (infill_line_width * 100) / infill_sparse_density * (2 if infill_pattern == 'grid' else (3 if infill_pattern == 'triangles' or infill_pattern == 'cubic' or infill_pattern == 'cubicsubdiv' else (4 if infill_pattern == 'tetrahedral' else 1)))",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1012,6 +1060,7 @@
|
||||||
"lines": "Lines",
|
"lines": "Lines",
|
||||||
"triangles": "Triangles",
|
"triangles": "Triangles",
|
||||||
"cubic": "Cubic",
|
"cubic": "Cubic",
|
||||||
|
"cubicsubdiv": "Cubic Subdivision",
|
||||||
"tetrahedral": "Tetrahedral",
|
"tetrahedral": "Tetrahedral",
|
||||||
"concentric": "Concentric",
|
"concentric": "Concentric",
|
||||||
"concentric_3d": "Concentric 3D",
|
"concentric_3d": "Concentric 3D",
|
||||||
|
@ -1022,6 +1071,32 @@
|
||||||
"value": "'lines' if infill_sparse_density > 25 else 'grid'",
|
"value": "'lines' if infill_sparse_density > 25 else 'grid'",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
|
"sub_div_rad_mult":
|
||||||
|
{
|
||||||
|
"label": "Cubic Subdivision Radius",
|
||||||
|
"description": "A multiplier on the radius from the center of each cube to check for the boundary of the model, as to decide whether this cube should be subdivided. Larger values lead to more subdivisions, i.e. more small cubes.",
|
||||||
|
"unit": "%",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 100,
|
||||||
|
"minimum_value": "0",
|
||||||
|
"minimum_value_warning": "100",
|
||||||
|
"maximum_value_warning": "200",
|
||||||
|
"enabled": "infill_sparse_density > 0 and infill_pattern == 'cubicsubdiv'",
|
||||||
|
"settable_per_mesh": true
|
||||||
|
},
|
||||||
|
"sub_div_rad_add":
|
||||||
|
{
|
||||||
|
"label": "Cubic Subdivision Shell",
|
||||||
|
"description": "An addition to the radius from the center of each cube to check for the boundary of the model, as to decide whether this cube should be subdivided. Larger values lead to a thicker shell of small cubes near the boundary of the model.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 0.4,
|
||||||
|
"value": "wall_line_width_x",
|
||||||
|
"minimum_value_warning": "-1 * infill_line_distance",
|
||||||
|
"maximum_value_warning": "5 * infill_line_distance",
|
||||||
|
"enabled": "infill_sparse_density > 0 and infill_pattern == 'cubicsubdiv'",
|
||||||
|
"settable_per_mesh": true
|
||||||
|
},
|
||||||
"infill_overlap":
|
"infill_overlap":
|
||||||
{
|
{
|
||||||
"label": "Infill Overlap Percentage",
|
"label": "Infill Overlap Percentage",
|
||||||
|
@ -1116,7 +1191,7 @@
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "4",
|
"maximum_value_warning": "4",
|
||||||
"maximum_value": "20 - math.log(infill_line_distance) / math.log(2)",
|
"maximum_value": "20 - math.log(infill_line_distance) / math.log(2)",
|
||||||
"enabled": "infill_sparse_density > 0",
|
"enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv'",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
"gradual_infill_step_height":
|
"gradual_infill_step_height":
|
||||||
|
@ -1129,7 +1204,7 @@
|
||||||
"minimum_value": "0.0001",
|
"minimum_value": "0.0001",
|
||||||
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
|
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
|
||||||
"maximum_value_warning": "100",
|
"maximum_value_warning": "100",
|
||||||
"enabled": "infill_sparse_density > 0 and gradual_infill_steps > 0",
|
"enabled": "infill_sparse_density > 0 and gradual_infill_steps > 0 and infill_pattern != 'cubicsubdiv'",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
"infill_before_walls":
|
"infill_before_walls":
|
||||||
|
@ -1161,6 +1236,17 @@
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true
|
||||||
},
|
},
|
||||||
|
"default_material_print_temperature":
|
||||||
|
{
|
||||||
|
"label": "Default Printing Temperature",
|
||||||
|
"description": "The default temperature used for printing. This should be the \"base\" temperature of a material. All other print temperatures should use offsets based on this value",
|
||||||
|
"unit": "°C",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 210,
|
||||||
|
"enabled": false,
|
||||||
|
"settable_per_extruder": true,
|
||||||
|
"minimum_value": "-273.15"
|
||||||
|
},
|
||||||
"material_print_temperature":
|
"material_print_temperature":
|
||||||
{
|
{
|
||||||
"label": "Printing Temperature",
|
"label": "Printing Temperature",
|
||||||
|
@ -1168,6 +1254,7 @@
|
||||||
"unit": "°C",
|
"unit": "°C",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 210,
|
"default_value": 210,
|
||||||
|
"value": "default_material_print_temperature",
|
||||||
"minimum_value": "-273.15",
|
"minimum_value": "-273.15",
|
||||||
"minimum_value_warning": "0",
|
"minimum_value_warning": "0",
|
||||||
"maximum_value_warning": "260",
|
"maximum_value_warning": "260",
|
||||||
|
@ -1196,7 +1283,8 @@
|
||||||
"description": "The minimal temperature while heating up to the Printing Temperature at which printing can already start.",
|
"description": "The minimal temperature while heating up to the Printing Temperature at which printing can already start.",
|
||||||
"unit": "°C",
|
"unit": "°C",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 190,
|
"default_value": 200,
|
||||||
|
"value": "max(-273.15, material_print_temperature - 10)",
|
||||||
"minimum_value": "-273.15",
|
"minimum_value": "-273.15",
|
||||||
"minimum_value_warning": "material_standby_temperature",
|
"minimum_value_warning": "material_standby_temperature",
|
||||||
"maximum_value_warning": "material_print_temperature",
|
"maximum_value_warning": "material_print_temperature",
|
||||||
|
@ -1210,7 +1298,8 @@
|
||||||
"description": "The temperature to which to already start cooling down just before the end of printing.",
|
"description": "The temperature to which to already start cooling down just before the end of printing.",
|
||||||
"unit": "°C",
|
"unit": "°C",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 180,
|
"default_value": 195,
|
||||||
|
"value": "max(-273.15, material_print_temperature - 15)",
|
||||||
"minimum_value": "-273.15",
|
"minimum_value": "-273.15",
|
||||||
"minimum_value_warning": "material_standby_temperature",
|
"minimum_value_warning": "material_standby_temperature",
|
||||||
"maximum_value_warning": "material_print_temperature",
|
"maximum_value_warning": "material_print_temperature",
|
||||||
|
@ -1236,7 +1325,7 @@
|
||||||
"description": "The extra speed by which the nozzle cools while extruding. The same value is used to signify the heat up speed lost when heating up while extruding.",
|
"description": "The extra speed by which the nozzle cools while extruding. The same value is used to signify the heat up speed lost when heating up while extruding.",
|
||||||
"unit": "°C/s",
|
"unit": "°C/s",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 0.5,
|
"default_value": 0.7,
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "10.0",
|
"maximum_value_warning": "10.0",
|
||||||
"maximum_value": "machine_nozzle_heat_up_speed",
|
"maximum_value": "machine_nozzle_heat_up_speed",
|
||||||
|
@ -1269,6 +1358,7 @@
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"resolve": "max(extruderValues('material_bed_temperature_layer_0'))",
|
"resolve": "max(extruderValues('material_bed_temperature_layer_0'))",
|
||||||
"default_value": 60,
|
"default_value": 60,
|
||||||
|
"value": "material_bed_temperature",
|
||||||
"minimum_value": "-273.15",
|
"minimum_value": "-273.15",
|
||||||
"minimum_value_warning": "0",
|
"minimum_value_warning": "0",
|
||||||
"maximum_value_warning": "260",
|
"maximum_value_warning": "260",
|
||||||
|
@ -1696,6 +1786,7 @@
|
||||||
"unit": "mm/s",
|
"unit": "mm/s",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 30,
|
"default_value": 30,
|
||||||
|
"value": "speed_print * 30 / 60",
|
||||||
"minimum_value": "0.1",
|
"minimum_value": "0.1",
|
||||||
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
|
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
|
||||||
"maximum_value_warning": "300",
|
"maximum_value_warning": "300",
|
||||||
|
@ -1718,7 +1809,7 @@
|
||||||
"speed_travel_layer_0":
|
"speed_travel_layer_0":
|
||||||
{
|
{
|
||||||
"label": "Initial Layer Travel Speed",
|
"label": "Initial Layer Travel Speed",
|
||||||
"description": "The speed of travel moves in the initial layer. A lower value is advised to prevent pulling previously printed parts away from the build plate.",
|
"description": "The speed of travel moves in the initial layer. A lower value is advised to prevent pulling previously printed parts away from the build plate. The value of this setting can automatically be calculated from the ratio between the Travel Speed and the Print Speed.",
|
||||||
"unit": "mm/s",
|
"unit": "mm/s",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 60,
|
"default_value": 60,
|
||||||
|
@ -2296,12 +2387,12 @@
|
||||||
},
|
},
|
||||||
"default_value": "all",
|
"default_value": "all",
|
||||||
"resolve": "'noskin' if 'noskin' in extruderValues('retraction_combing') else ('all' if 'all' in extruderValues('retraction_combing') else 'off')",
|
"resolve": "'noskin' if 'noskin' in extruderValues('retraction_combing') else ('all' if 'all' in extruderValues('retraction_combing') else 'off')",
|
||||||
"settable_per_mesh": true,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": false
|
"settable_per_extruder": false
|
||||||
},
|
},
|
||||||
"travel_avoid_other_parts":
|
"travel_avoid_other_parts":
|
||||||
{
|
{
|
||||||
"label": "Avoid Printed Parts when Traveling",
|
"label": "Avoid Printed Parts When Traveling",
|
||||||
"description": "The nozzle avoids already printed parts when traveling. This option is only available when combing is enabled.",
|
"description": "The nozzle avoids already printed parts when traveling. This option is only available when combing is enabled.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": true,
|
"default_value": true,
|
||||||
|
@ -2326,8 +2417,8 @@
|
||||||
},
|
},
|
||||||
"start_layers_at_same_position":
|
"start_layers_at_same_position":
|
||||||
{
|
{
|
||||||
"label": "Start Layers Near Same Point",
|
"label": "Start Layers with the Same Part",
|
||||||
"description": "Start printing the objects in each layer near the same point, so that we don't start a new layer with printing the piece which the previous layer ended with. This makes for better overhangs and small parts, but increases printing time.",
|
"description": "In each layer start with printing the object near the same point, so that we don't start a new layer with printing the piece which the previous layer ended with. This makes for better overhangs and small parts, but increases printing time.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": false,
|
"default_value": false,
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
|
@ -2337,7 +2428,7 @@
|
||||||
"layer_start_x":
|
"layer_start_x":
|
||||||
{
|
{
|
||||||
"label": "Layer Start X",
|
"label": "Layer Start X",
|
||||||
"description": "The X coordinate of the position near where to start printing objects each layer.",
|
"description": "The X coordinate of the position near where to find the part to start printing each layer.",
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 0.0,
|
"default_value": 0.0,
|
||||||
|
@ -2350,7 +2441,7 @@
|
||||||
"layer_start_y":
|
"layer_start_y":
|
||||||
{
|
{
|
||||||
"label": "Layer Start Y",
|
"label": "Layer Start Y",
|
||||||
"description": "The Y coordinate of the position near where to start printing objects each layer.",
|
"description": "The Y coordinate of the position near where to find the part to start printing each layer.",
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 0.0,
|
"default_value": 0.0,
|
||||||
|
@ -2361,7 +2452,7 @@
|
||||||
"settable_per_meshgroup": true
|
"settable_per_meshgroup": true
|
||||||
},
|
},
|
||||||
"retraction_hop_enabled": {
|
"retraction_hop_enabled": {
|
||||||
"label": "Z Hop when Retracted",
|
"label": "Z Hop When Retracted",
|
||||||
"description": "Whenever a retraction is done, the build plate is lowered to create clearance between the nozzle and the print. It prevents the nozzle from hitting the print during travel moves, reducing the chance to knock the print from the build plate.",
|
"description": "Whenever a retraction is done, the build plate is lowered to create clearance between the nozzle and the print. It prevents the nozzle from hitting the print during travel moves, reducing the chance to knock the print from the build plate.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": false,
|
"default_value": false,
|
||||||
|
@ -2433,20 +2524,6 @@
|
||||||
"settable_per_extruder": true,
|
"settable_per_extruder": true,
|
||||||
"children":
|
"children":
|
||||||
{
|
{
|
||||||
"cool_fan_speed_0":
|
|
||||||
{
|
|
||||||
"label": "Initial Fan Speed",
|
|
||||||
"description": "The speed at which the fans spin at the start of the print. In subsequent layers the fan speed is gradually increased up to the layer corresponding to Regular Fan Speed at Height.",
|
|
||||||
"unit": "%",
|
|
||||||
"type": "float",
|
|
||||||
"minimum_value": "0",
|
|
||||||
"maximum_value": "100",
|
|
||||||
"value": "cool_fan_speed",
|
|
||||||
"default_value": 100,
|
|
||||||
"enabled": "cool_fan_enabled",
|
|
||||||
"settable_per_mesh": false,
|
|
||||||
"settable_per_extruder": true
|
|
||||||
},
|
|
||||||
"cool_fan_speed_min":
|
"cool_fan_speed_min":
|
||||||
{
|
{
|
||||||
"label": "Regular Fan Speed",
|
"label": "Regular Fan Speed",
|
||||||
|
@ -2489,6 +2566,19 @@
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true
|
||||||
},
|
},
|
||||||
|
"cool_fan_speed_0":
|
||||||
|
{
|
||||||
|
"label": "Initial Fan Speed",
|
||||||
|
"description": "The speed at which the fans spin at the start of the print. In subsequent layers the fan speed is gradually increased up to the layer corresponding to Regular Fan Speed at Height.",
|
||||||
|
"unit": "%",
|
||||||
|
"type": "float",
|
||||||
|
"minimum_value": "0",
|
||||||
|
"maximum_value": "100",
|
||||||
|
"default_value": 0,
|
||||||
|
"enabled": "cool_fan_enabled",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
"cool_fan_full_at_height":
|
"cool_fan_full_at_height":
|
||||||
{
|
{
|
||||||
"label": "Regular Fan Speed at Height",
|
"label": "Regular Fan Speed at Height",
|
||||||
|
@ -3615,7 +3705,6 @@
|
||||||
"type": "category",
|
"type": "category",
|
||||||
"icon": "category_dual",
|
"icon": "category_dual",
|
||||||
"description": "Settings used for printing with multiple extruders.",
|
"description": "Settings used for printing with multiple extruders.",
|
||||||
"enabled": "machine_extruder_count > 1",
|
|
||||||
"children":
|
"children":
|
||||||
{
|
{
|
||||||
"prime_tower_enable":
|
"prime_tower_enable":
|
||||||
|
@ -3637,7 +3726,6 @@
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"enabled": "resolveOrValue('prime_tower_enable')",
|
"enabled": "resolveOrValue('prime_tower_enable')",
|
||||||
"default_value": 15,
|
"default_value": 15,
|
||||||
"value": "15 if resolveOrValue('prime_tower_enable') else 0",
|
|
||||||
"resolve": "max(extruderValues('prime_tower_size'))",
|
"resolve": "max(extruderValues('prime_tower_size'))",
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)",
|
"maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)",
|
||||||
|
@ -3646,6 +3734,38 @@
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": false
|
"settable_per_extruder": false
|
||||||
},
|
},
|
||||||
|
"prime_tower_min_volume":
|
||||||
|
{
|
||||||
|
"label": "Prime Tower Minimum Volume",
|
||||||
|
"description": "The minimum volume for each layer of the prime tower in order to purge enough material.",
|
||||||
|
"unit": "mm³",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 10,
|
||||||
|
"minimum_value": "0",
|
||||||
|
"maximum_value_warning": "resolveOrValue('prime_tower_size') ** 2 * resolveOrValue('layer_height')",
|
||||||
|
"enabled": "resolveOrValue('prime_tower_enable')",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"prime_tower_wall_thickness":
|
||||||
|
{
|
||||||
|
"label": "Prime Tower Thickness",
|
||||||
|
"description": "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 2,
|
||||||
|
"value": "max(2 * min(extruderValues('prime_tower_line_width')), 0.5 * (resolveOrValue('prime_tower_size') - math.sqrt(resolveOrValue('prime_tower_size') ** 2 - max(extruderValues('prime_tower_min_volume')) / resolveOrValue('layer_height'))))",
|
||||||
|
"resolve": "max(extruderValues('prime_tower_wall_thickness'))",
|
||||||
|
"minimum_value": "0.001",
|
||||||
|
"minimum_value_warning": "2 * min(extruderValues('prime_tower_line_width'))",
|
||||||
|
"maximum_value_warning": "resolveOrValue('prime_tower_size') / 2",
|
||||||
|
"enabled": "resolveOrValue('prime_tower_enable')",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"prime_tower_position_x":
|
"prime_tower_position_x":
|
||||||
{
|
{
|
||||||
"label": "Prime Tower X Position",
|
"label": "Prime Tower X Position",
|
||||||
|
@ -3692,25 +3812,23 @@
|
||||||
},
|
},
|
||||||
"prime_tower_wipe_enabled":
|
"prime_tower_wipe_enabled":
|
||||||
{
|
{
|
||||||
"label": "Wipe Nozzle on Prime Tower",
|
"label": "Wipe Inactive Nozzle on Prime Tower",
|
||||||
"description": "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower.",
|
"description": "After printing the prime tower with one nozzle, wipe the oozed material from the other nozzle off on the prime tower.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"enabled": "resolveOrValue('prime_tower_enable')",
|
"enabled": "resolveOrValue('prime_tower_enable')",
|
||||||
"default_value": true,
|
"default_value": true,
|
||||||
"resolve": "any(extruderValues('prime_tower_wipe_enabled'))",
|
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": false
|
"settable_per_extruder": true
|
||||||
},
|
},
|
||||||
"multiple_mesh_overlap":
|
"dual_pre_wipe":
|
||||||
{
|
{
|
||||||
"label": "Dual Extrusion Overlap",
|
"label": "Wipe Nozzle After Switch",
|
||||||
"description": "Make the models printed with different extruder trains overlap a bit. This makes the different materials bond together better.",
|
"description": "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print.",
|
||||||
"type": "float",
|
"type": "bool",
|
||||||
"unit": "mm",
|
"enabled": "resolveOrValue('prime_tower_enable')",
|
||||||
"default_value": 0.15,
|
"default_value": true,
|
||||||
"minimum_value": "0",
|
"settable_per_mesh": false,
|
||||||
"maximum_value_warning": "1.0",
|
"settable_per_extruder": true
|
||||||
"settable_per_mesh": true
|
|
||||||
},
|
},
|
||||||
"ooze_shield_enabled":
|
"ooze_shield_enabled":
|
||||||
{
|
{
|
||||||
|
@ -3763,7 +3881,7 @@
|
||||||
"meshfix_union_all":
|
"meshfix_union_all":
|
||||||
{
|
{
|
||||||
"label": "Union Overlapping Volumes",
|
"label": "Union Overlapping Volumes",
|
||||||
"description": "Ignore the internal geometry arising from overlapping volumes and print the volumes as one. This may cause internal cavities to disappear.",
|
"description": "Ignore the internal geometry arising from overlapping volumes within a mesh and print the volumes as one. This may cause unintended internal cavities to disappear.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": true,
|
"default_value": true,
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
|
@ -3792,10 +3910,21 @@
|
||||||
"default_value": false,
|
"default_value": false,
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
|
"multiple_mesh_overlap":
|
||||||
|
{
|
||||||
|
"label": "Merged Meshes Overlap",
|
||||||
|
"description": "Make meshes which are touching each other overlap a bit. This makes them bond together better.",
|
||||||
|
"type": "float",
|
||||||
|
"unit": "mm",
|
||||||
|
"default_value": 0.15,
|
||||||
|
"minimum_value": "0",
|
||||||
|
"maximum_value_warning": "1.0",
|
||||||
|
"settable_per_mesh": true
|
||||||
|
},
|
||||||
"carve_multiple_volumes":
|
"carve_multiple_volumes":
|
||||||
{
|
{
|
||||||
"label": "Remove Mesh Intersection",
|
"label": "Remove Mesh Intersection",
|
||||||
"description": "Remove areas where multiple objects are overlapping with each other. This may be used if merged dual material objects overlap with each other.",
|
"description": "Remove areas where multiple meshes are overlapping with each other. This may be used if merged dual material objects overlap with each other.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": true,
|
"default_value": true,
|
||||||
"value": "machine_extruder_count > 1",
|
"value": "machine_extruder_count > 1",
|
||||||
|
@ -3806,7 +3935,7 @@
|
||||||
"alternate_carve_order":
|
"alternate_carve_order":
|
||||||
{
|
{
|
||||||
"label": "Alternate Mesh Removal",
|
"label": "Alternate Mesh Removal",
|
||||||
"description": "With every layer switch to which model the intersecting volumes will belong, so that the overlapping volumes become interwoven.",
|
"description": "Switch to which mesh intersecting volumes will belong with every layer, so that the overlapping meshes become interwoven. Turning this setting off will cause one of the meshes to obtain all of the volume in the overlap, while it is removed from the other meshes.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": true,
|
"default_value": true,
|
||||||
"enabled": "carve_multiple_volumes",
|
"enabled": "carve_multiple_volumes",
|
||||||
|
@ -3865,6 +3994,28 @@
|
||||||
"settable_per_meshgroup": false,
|
"settable_per_meshgroup": false,
|
||||||
"settable_globally": false
|
"settable_globally": false
|
||||||
},
|
},
|
||||||
|
"support_mesh":
|
||||||
|
{
|
||||||
|
"label": "Support Mesh",
|
||||||
|
"description": "Use this mesh to specify support areas. This can be used to generate support structure.",
|
||||||
|
"type": "bool",
|
||||||
|
"default_value": false,
|
||||||
|
"settable_per_mesh": true,
|
||||||
|
"settable_per_extruder": false,
|
||||||
|
"settable_per_meshgroup": false,
|
||||||
|
"settable_globally": false
|
||||||
|
},
|
||||||
|
"anti_overhang_mesh":
|
||||||
|
{
|
||||||
|
"label": "Anti Overhang Mesh",
|
||||||
|
"description": "Use this mesh to specify where no part of the model should be detected as overhang. This can be used to remove unwanted support structure.",
|
||||||
|
"type": "bool",
|
||||||
|
"default_value": false,
|
||||||
|
"settable_per_mesh": true,
|
||||||
|
"settable_per_extruder": false,
|
||||||
|
"settable_per_meshgroup": false,
|
||||||
|
"settable_globally": false
|
||||||
|
},
|
||||||
"magic_mesh_surface_mode":
|
"magic_mesh_surface_mode":
|
||||||
{
|
{
|
||||||
"label": "Surface Mode",
|
"label": "Surface Mode",
|
||||||
|
@ -4043,6 +4194,7 @@
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": false,
|
"default_value": false,
|
||||||
"enabled": "support_enable",
|
"enabled": "support_enable",
|
||||||
|
"limit_to_extruder": "support_infill_extruder_nr",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
"support_conical_angle":
|
"support_conical_angle":
|
||||||
|
|
35
resources/definitions/jellybox.def.json
Normal file
35
resources/definitions/jellybox.def.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"id": "jellybox",
|
||||||
|
"version": 2,
|
||||||
|
"name": "JellyBOX",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "IMADE3D",
|
||||||
|
"manufacturer": "IMADE3D",
|
||||||
|
"category": "Other",
|
||||||
|
"platform": "jellybox_platform.stl",
|
||||||
|
"platform_offset": [ 0, -0.3, 0],
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"has_materials": true,
|
||||||
|
"has_machine_materials": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": { "default_value": "IMADE3D JellyBOX" },
|
||||||
|
"machine_width": { "default_value": 170 },
|
||||||
|
"machine_height": { "default_value": 145 },
|
||||||
|
"machine_depth": { "default_value": 160 },
|
||||||
|
"machine_nozzle_size": { "default_value": 0.4 },
|
||||||
|
"material_diameter": { "default_value": 1.75 },
|
||||||
|
"machine_heated_bed": { "default_value": true },
|
||||||
|
"machine_center_is_zero": { "default_value": false },
|
||||||
|
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for: {machine_name}\n; nozzle diameter: {machine_nozzle_size}\n; filament diameter: {material_diameter}\n; layer height: {layer_height}\n; 1st layer height: {layer_height_0}\n; line width: {line_width}\n; wall thickness: {wall_thickness}\n; infill density: {infill_sparse_density}\n; infill pattern: {infill_pattern}\n; print temperature: {material_print_temperature}\n; heated bed temperature: {material_bed_temperature}\n; regular fan speed: {cool_fan_speed_min}\n; max fan speed: {cool_fan_speed_max}\n; support? {support_enable}\n; spiralized? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature} ;set bed temperature and move on\nM104 S{material_print_temperature} ;set extruder temperature and move on\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z5 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F200 E5 ;extrude 5mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________"
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "\n;---------------------------------\n;;; Jellybox End Script Begin ;;;\n;_________________________________\nM117 Finishing Up ;write Finishing Up\n\nM104 S0 ;extruder heater off\nM140 S0 ;bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG28 X ;home x, so the head is out of the way\nG1 Y100 ;move Y forward, so the print is more accessible\nM84 ;steppers off\n\nM117 Print finished ;write Print finished\n;---------------------------------------\n;;; Jellybox End Script End ;;;\n;_______________________________________"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
resources/definitions/kossel_pro.def.json
Normal file
56
resources/definitions/kossel_pro.def.json
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"id": "kossel_pro",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Kossel Pro",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Chris Petersen",
|
||||||
|
"manufacturer": "OpenBeam",
|
||||||
|
"category": "Other",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "icon_ultimaker2",
|
||||||
|
"platform": "kossel_pro_build_platform.stl"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 240
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 240
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 240
|
||||||
|
},
|
||||||
|
"machine_center_is_zero": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.35
|
||||||
|
},
|
||||||
|
"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": "; info: M303 E0 S200 C8 ; Pid auto-tune \n\nM140 S{{material_bed_temperature}}; Start heating up the base\nG28 ; Home to top 3 endstops\n; Autolevel and adjust first layer\n; Adjust this value to fit your own printer! (positive is thicker)\n; This default value is intentionally very high to accommodate the\n; variety of print heads used with this printer. Many of you will\n; need tiny values like Z0 or Z0.1. Use feeler gauges to dial this\n; in as accurately as possible.\nG29 Z10\n\n; Squirt and wipe ;\nM109 S220 ; Wait for the temp to hit 220\nG00 X125 Y-60 Z0.1 ;\nG92 E0 ;\nG01 E25 F100 ; Extrude a little bit to replace oozage from auto levelling\nG01 X90 Y-50 F6000 ;\nG01 Z5 ;\n\n; Set the extruder to the requested print temperature\nM104 S{{material_print_temperature}}\n"
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ; turn off temperature\nM140 S0 ; turn off bed\nG28 ; home all axes\nM84 ; disable motors\n"
|
||||||
|
},
|
||||||
|
"machine_disallowed_areas": {
|
||||||
|
"default_value": [[[125.0, 125.0], [125.0, 0.0], [120.741, 32.352]], [[-125.0, 125.0], [-125.0, 0.0], [-120.741, 32.352]], [[125.0, -125.0], [125.0, -0.0], [120.741, -32.352]], [[-125.0, -125.0], [-125.0, -0.0], [-120.741, -32.352]], [[125.0, 125.0], [120.741, 32.352], [108.253, 62.5]], [[-125.0, 125.0], [-120.741, 32.352], [-108.253, 62.5]], [[125.0, -125.0], [120.741, -32.352], [108.253, -62.5]], [[-125.0, -125.0], [-120.741, -32.352], [-108.253, -62.5]], [[125.0, 125.0], [108.253, 62.5], [88.388, 88.388]], [[-125.0, 125.0], [-108.253, 62.5], [-88.388, 88.388]], [[125.0, -125.0], [108.253, -62.5], [88.388, -88.388]], [[-125.0, -125.0], [-108.253, -62.5], [-88.388, -88.388]], [[125.0, 125.0], [88.388, 88.388], [62.5, 108.253]], [[-125.0, 125.0], [-88.388, 88.388], [-62.5, 108.253]], [[125.0, -125.0], [88.388, -88.388], [62.5, -108.253]], [[-125.0, -125.0], [-88.388, -88.388], [-62.5, -108.253]], [[125.0, 125.0], [62.5, 108.253], [32.352, 120.741]], [[-125.0, 125.0], [-62.5, 108.253], [-32.352, 120.741]], [[125.0, -125.0], [62.5, -108.253], [32.352, -120.741]], [[-125.0, -125.0], [-62.5, -108.253], [-32.352, -120.741]], [[125.0, 125.0], [32.352, 120.741], [0.0, 125.0]], [[-125.0, 125.0], [-32.352, 120.741], [-0.0, 125.0]], [[125.0, -125.0], [32.352, -120.741], [0.0, -125.0]], [[-125.0, -125.0], [-32.352, -120.741], [-0.0, -125.0]]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
resources/definitions/printrbot_simple_extended.def.json
Normal file
45
resources/definitions/printrbot_simple_extended.def.json
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"id": "printrbot_simple_extended",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Printrbot Simple Metal Extended",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "samsector",
|
||||||
|
"manufacturer": "PrintrBot",
|
||||||
|
"category": "Other",
|
||||||
|
"platform": "printrbot_simple_metal_upgrade.stl",
|
||||||
|
"platform_offset": [0, -0.3, 0],
|
||||||
|
"file_formats": "text/x-gcode"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": { "default_value": "Printrbot Simple Metal Extended" },
|
||||||
|
"machine_heated_bed": { "default_value": true },
|
||||||
|
"machine_width": { "default_value": 250 },
|
||||||
|
"machine_height": { "default_value": 235 },
|
||||||
|
"machine_depth": { "default_value": 150 },
|
||||||
|
"machine_center_is_zero": { "default_value": false },
|
||||||
|
"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_head_with_fans_polygon": {
|
||||||
|
"default_value": [
|
||||||
|
[ 55, -20 ],
|
||||||
|
[ 55, 99999 ],
|
||||||
|
[ -49, 99999 ],
|
||||||
|
[ -49, -20 ]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gantry_height": { "default_value": 99999 },
|
||||||
|
"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\nM106 ;start with the fan on\nG28 X0 Y0 ;home X/Y\nG28 Z0 ;home Z\nG92 E0 ;zero the extruded length\nG29 ;initiate auto bed leveling sequence\n;start cleaning sequance\nG1 X250 Y150 Z15 F4000\nG1 X250 Y150 Z0.30 F1000\nG1 X1 Y150 Z0.25 E15.0 F2000\nG1 X1 Y150 Z0.25 E14.0 F4000\nG1 X1 Y1 Z0.25 F16000\nG1 X1 Y1 Z0.25 E15.0 F4000\nG92 E0\nM107 ;start with the fan off\n;end cleaning sequance\n;G92 X132.4 Y20 ;correct bed origin (G29 changes it)"
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM106 S0 ;fan off\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit\nG1 Z+1 E-5 F9000 ;move Z up a bit and retract even more\nG28 X0 Y0 ;home X/Y, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,7 @@
|
||||||
"machine_depth": { "default_value": 215 },
|
"machine_depth": { "default_value": 215 },
|
||||||
"machine_height": { "default_value": 200 },
|
"machine_height": { "default_value": 200 },
|
||||||
"machine_heated_bed": { "default_value": true },
|
"machine_heated_bed": { "default_value": true },
|
||||||
|
"machine_filament_park_distance": { "value": "machine_heat_zone_length + 26.5" },
|
||||||
"machine_nozzle_heat_up_speed": { "default_value": 1.4 },
|
"machine_nozzle_heat_up_speed": { "default_value": 1.4 },
|
||||||
"machine_nozzle_cool_down_speed": { "default_value": 0.8 },
|
"machine_nozzle_cool_down_speed": { "default_value": 0.8 },
|
||||||
"machine_head_with_fans_polygon":
|
"machine_head_with_fans_polygon":
|
||||||
|
@ -70,6 +71,7 @@
|
||||||
"machine_end_gcode": { "default_value": "" },
|
"machine_end_gcode": { "default_value": "" },
|
||||||
"prime_tower_position_x": { "default_value": 175 },
|
"prime_tower_position_x": { "default_value": 175 },
|
||||||
"prime_tower_position_y": { "default_value": 179 },
|
"prime_tower_position_y": { "default_value": 179 },
|
||||||
|
"prime_tower_wipe_enabled": { "default_value": false },
|
||||||
|
|
||||||
"acceleration_enabled": { "value": "True" },
|
"acceleration_enabled": { "value": "True" },
|
||||||
"acceleration_layer_0": { "value": "acceleration_topbottom" },
|
"acceleration_layer_0": { "value": "acceleration_topbottom" },
|
||||||
|
@ -103,20 +105,19 @@
|
||||||
"layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" },
|
"layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" },
|
||||||
"line_width": { "value": "machine_nozzle_size * 0.875" },
|
"line_width": { "value": "machine_nozzle_size * 0.875" },
|
||||||
"machine_min_cool_heat_time_window": { "value": "15" },
|
"machine_min_cool_heat_time_window": { "value": "15" },
|
||||||
"material_print_temperature": { "value": "200" },
|
"default_material_print_temperature": { "value": "200" },
|
||||||
|
"material_bed_temperature": { "maximum_value": "115" },
|
||||||
|
"material_bed_temperature_layer_0": { "maximum_value": "115" },
|
||||||
"material_standby_temperature": { "value": "100" },
|
"material_standby_temperature": { "value": "100" },
|
||||||
"multiple_mesh_overlap": { "value": "0" },
|
"multiple_mesh_overlap": { "value": "0" },
|
||||||
"prime_tower_enable": { "value": "True" },
|
"prime_tower_enable": { "value": "True" },
|
||||||
"raft_airgap": { "value": "0" },
|
"raft_airgap": { "value": "0" },
|
||||||
"raft_base_speed": { "value": "20" },
|
|
||||||
"raft_base_thickness": { "value": "0.3" },
|
"raft_base_thickness": { "value": "0.3" },
|
||||||
"raft_interface_line_spacing": { "value": "0.5" },
|
"raft_interface_line_spacing": { "value": "0.5" },
|
||||||
"raft_interface_line_width": { "value": "0.5" },
|
"raft_interface_line_width": { "value": "0.5" },
|
||||||
"raft_interface_speed": { "value": "20" },
|
|
||||||
"raft_interface_thickness": { "value": "0.2" },
|
"raft_interface_thickness": { "value": "0.2" },
|
||||||
"raft_jerk": { "value": "jerk_layer_0" },
|
"raft_jerk": { "value": "jerk_layer_0" },
|
||||||
"raft_margin": { "value": "10" },
|
"raft_margin": { "value": "10" },
|
||||||
"raft_speed": { "value": "25" },
|
|
||||||
"raft_surface_layers": { "value": "1" },
|
"raft_surface_layers": { "value": "1" },
|
||||||
"retraction_amount": { "value": "2" },
|
"retraction_amount": { "value": "2" },
|
||||||
"retraction_count_max": { "value": "10" },
|
"retraction_count_max": { "value": "10" },
|
||||||
|
@ -127,7 +128,7 @@
|
||||||
"retraction_min_travel": { "value": "5" },
|
"retraction_min_travel": { "value": "5" },
|
||||||
"retraction_prime_speed": { "value": "15" },
|
"retraction_prime_speed": { "value": "15" },
|
||||||
"skin_overlap": { "value": "10" },
|
"skin_overlap": { "value": "10" },
|
||||||
"speed_layer_0": { "value": "20" },
|
"speed_layer_0": { "value": "speed_print * 30 / 70" },
|
||||||
"speed_prime_tower": { "value": "speed_topbottom" },
|
"speed_prime_tower": { "value": "speed_topbottom" },
|
||||||
"speed_print": { "value": "35" },
|
"speed_print": { "value": "35" },
|
||||||
"speed_support": { "value": "speed_wall_0" },
|
"speed_support": { "value": "speed_wall_0" },
|
||||||
|
|
|
@ -73,10 +73,10 @@
|
||||||
"default_value": 2
|
"default_value": 2
|
||||||
},
|
},
|
||||||
"prime_tower_position_x": {
|
"prime_tower_position_x": {
|
||||||
"default_value": 185
|
"default_value": 195
|
||||||
},
|
},
|
||||||
"prime_tower_position_y": {
|
"prime_tower_position_y": {
|
||||||
"default_value": 160
|
"default_value": 149
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ultimaker3_extended_extruder_left",
|
"id": "ultimaker3_extended_extruder_left",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"name": "Print core 1",
|
"name": "Extruder 1",
|
||||||
"inherits": "fdmextruder",
|
"inherits": "fdmextruder",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"machine": "ultimaker3_extended",
|
"machine": "ultimaker3_extended",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ultimaker3_extended_extruder_right",
|
"id": "ultimaker3_extended_extruder_right",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"name": "Print core 2",
|
"name": "Extruder 2",
|
||||||
"inherits": "fdmextruder",
|
"inherits": "fdmextruder",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"machine": "ultimaker3_extended",
|
"machine": "ultimaker3_extended",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ultimaker3_extruder_left",
|
"id": "ultimaker3_extruder_left",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"name": "Print core 1",
|
"name": "Extruder 1",
|
||||||
"inherits": "fdmextruder",
|
"inherits": "fdmextruder",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"machine": "ultimaker3",
|
"machine": "ultimaker3",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ultimaker3_extruder_right",
|
"id": "ultimaker3_extruder_right",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"name": "Print core 2",
|
"name": "Extruder 2",
|
||||||
"inherits": "fdmextruder",
|
"inherits": "fdmextruder",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"machine": "ultimaker3",
|
"machine": "ultimaker3",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ultimaker_original_dual_1st",
|
"id": "ultimaker_original_dual_1st",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"name": "1st Extruder",
|
"name": "Extruder 1",
|
||||||
"inherits": "fdmextruder",
|
"inherits": "fdmextruder",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"machine": "ultimaker_original_dual",
|
"machine": "ultimaker_original_dual",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ultimaker_original_dual_2nd",
|
"id": "ultimaker_original_dual_2nd",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"name": "2nd Extruder",
|
"name": "Extruder 2",
|
||||||
"inherits": "fdmextruder",
|
"inherits": "fdmextruder",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"machine": "ultimaker_original_dual",
|
"machine": "ultimaker_original_dual",
|
||||||
|
|
|
@ -1913,7 +1913,7 @@ msgstr "&Beenden"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:97
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:97
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
msgid "Configure Cura..."
|
msgid "Configure Cura..."
|
||||||
msgstr "Cura wird konfiguriert..."
|
msgstr "Cura konfigurieren..."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:104
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:104
|
||||||
msgctxt "@action:inmenu menubar:printer"
|
msgctxt "@action:inmenu menubar:printer"
|
||||||
|
|
|
@ -2629,3 +2629,8 @@ msgstr "De configuratie van de printer in Cura laden"
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Activate Configuration"
|
msgid "Activate Configuration"
|
||||||
msgstr "Configuratie Activeren"
|
msgstr "Configuratie Activeren"
|
||||||
|
|
||||||
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:25
|
||||||
|
msgctxt "@title"
|
||||||
|
msgid "Information"
|
||||||
|
msgstr "Informatie"
|
||||||
|
|
BIN
resources/images/kitten.jpg
Normal file
BIN
resources/images/kitten.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 171 KiB |
BIN
resources/meshes/jellybox_platform.stl
Normal file
BIN
resources/meshes/jellybox_platform.stl
Normal file
Binary file not shown.
22822
resources/meshes/kossel_pro_build_platform.stl
Normal file
22822
resources/meshes/kossel_pro_build_platform.stl
Normal file
File diff suppressed because it is too large
Load diff
13722
resources/meshes/printrbot_simple_metal_upgrade.stl
Normal file
13722
resources/meshes/printrbot_simple_metal_upgrade.stl
Normal file
File diff suppressed because it is too large
Load diff
|
@ -11,6 +11,7 @@ import Cura 1.0 as Cura
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
property alias open: openAction;
|
property alias open: openAction;
|
||||||
|
property alias loadWorkspace: loadWorkspaceAction;
|
||||||
property alias quit: quitAction;
|
property alias quit: quitAction;
|
||||||
|
|
||||||
property alias undo: undoAction;
|
property alias undo: undoAction;
|
||||||
|
@ -122,7 +123,7 @@ Item
|
||||||
{
|
{
|
||||||
id: updateProfileAction;
|
id: updateProfileAction;
|
||||||
enabled: !Cura.MachineManager.stacksHaveErrors && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
|
enabled: !Cura.MachineManager.stacksHaveErrors && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
|
||||||
text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings");
|
text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings/overrides");
|
||||||
onTriggered: Cura.ContainerManager.updateQualityChanges();
|
onTriggered: Cura.ContainerManager.updateQualityChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +143,7 @@ Item
|
||||||
{
|
{
|
||||||
id: addProfileAction;
|
id: addProfileAction;
|
||||||
enabled: !Cura.MachineManager.stacksHaveErrors && Cura.MachineManager.hasUserSettings
|
enabled: !Cura.MachineManager.stacksHaveErrors && Cura.MachineManager.hasUserSettings
|
||||||
text: catalog.i18nc("@action:inmenu menubar:profile","&Create profile from current settings...");
|
text: catalog.i18nc("@action:inmenu menubar:profile","&Create profile from current settings/overrides...");
|
||||||
}
|
}
|
||||||
|
|
||||||
Action
|
Action
|
||||||
|
@ -286,6 +287,12 @@ Item
|
||||||
shortcut: StandardKey.Open;
|
shortcut: StandardKey.Open;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Action
|
||||||
|
{
|
||||||
|
id: loadWorkspaceAction
|
||||||
|
text: catalog.i18nc("@action:inmenu menubar:file","&Open Project...");
|
||||||
|
}
|
||||||
|
|
||||||
Action
|
Action
|
||||||
{
|
{
|
||||||
id: showEngineLogAction;
|
id: showEngineLogAction;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import QtQuick.Controls.Styles 1.1
|
||||||
import QtQuick.Layouts 1.1
|
import QtQuick.Layouts 1.1
|
||||||
import QtQuick.Dialogs 1.1
|
import QtQuick.Dialogs 1.1
|
||||||
|
|
||||||
import UM 1.2 as UM
|
import UM 1.3 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
import "Menus"
|
import "Menus"
|
||||||
|
@ -67,12 +67,18 @@ UM.MainWindow
|
||||||
id: fileMenu
|
id: fileMenu
|
||||||
title: catalog.i18nc("@title:menu menubar:toplevel","&File");
|
title: catalog.i18nc("@title:menu menubar:toplevel","&File");
|
||||||
|
|
||||||
MenuItem {
|
MenuItem
|
||||||
|
{
|
||||||
action: Cura.Actions.open;
|
action: Cura.Actions.open;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecentFilesMenu { }
|
RecentFilesMenu { }
|
||||||
|
|
||||||
|
MenuItem
|
||||||
|
{
|
||||||
|
action: Cura.Actions.loadWorkspace
|
||||||
|
}
|
||||||
|
|
||||||
MenuSeparator { }
|
MenuSeparator { }
|
||||||
|
|
||||||
MenuItem
|
MenuItem
|
||||||
|
@ -102,6 +108,22 @@ UM.MainWindow
|
||||||
onObjectRemoved: saveAllMenu.removeItem(object)
|
onObjectRemoved: saveAllMenu.removeItem(object)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MenuItem
|
||||||
|
{
|
||||||
|
id: saveWorkspaceMenu
|
||||||
|
text: catalog.i18nc("@title:menu menubar:file","Save project")
|
||||||
|
onTriggered:
|
||||||
|
{
|
||||||
|
if(UM.Preferences.getValue("cura/dialog_on_project_save"))
|
||||||
|
{
|
||||||
|
saveWorkspaceDialog.open()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UM.OutputDeviceManager.requestWriteToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "file_type": "workspace" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MenuItem { action: Cura.Actions.reloadAll; }
|
MenuItem { action: Cura.Actions.reloadAll; }
|
||||||
|
|
||||||
|
@ -487,14 +509,17 @@ UM.MainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged:
|
onVisibleChanged:
|
||||||
{
|
|
||||||
if(!visible)
|
|
||||||
{
|
{
|
||||||
// When the dialog closes, switch to the General page.
|
// When the dialog closes, switch to the General page.
|
||||||
// This prevents us from having a heavy page like Setting Visiblity active in the background.
|
// This prevents us from having a heavy page like Setting Visiblity active in the background.
|
||||||
setPage(0);
|
setPage(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorkspaceSummaryDialog
|
||||||
|
{
|
||||||
|
id: saveWorkspaceDialog
|
||||||
|
onYes: UM.OutputDeviceManager.requestWriteToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "file_type": "workspace" })
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
|
@ -723,6 +748,38 @@ UM.MainWindow
|
||||||
onTriggered: openDialog.open()
|
onTriggered: openDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileDialog
|
||||||
|
{
|
||||||
|
id: openWorkspaceDialog;
|
||||||
|
|
||||||
|
//: File open dialog title
|
||||||
|
title: catalog.i18nc("@title:window","Open workspace")
|
||||||
|
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
|
||||||
|
selectMultiple: false
|
||||||
|
nameFilters: UM.WorkspaceFileHandler.supportedReadFileTypes;
|
||||||
|
folder: CuraApplication.getDefaultPath("dialog_load_path")
|
||||||
|
onAccepted:
|
||||||
|
{
|
||||||
|
//Because several implementations of the file dialog only update the folder
|
||||||
|
//when it is explicitly set.
|
||||||
|
var f = folder;
|
||||||
|
folder = f;
|
||||||
|
|
||||||
|
CuraApplication.setDefaultPath("dialog_load_path", folder);
|
||||||
|
|
||||||
|
for(var i in fileUrls)
|
||||||
|
{
|
||||||
|
UM.WorkspaceFileHandler.readLocalFile(fileUrls[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections
|
||||||
|
{
|
||||||
|
target: Cura.Actions.loadWorkspace
|
||||||
|
onTriggered: openWorkspaceDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
EngineLog
|
EngineLog
|
||||||
{
|
{
|
||||||
id: engineLog;
|
id: engineLog;
|
||||||
|
|
|
@ -304,6 +304,20 @@ UM.PreferencesPage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UM.TooltipArea {
|
||||||
|
width: childrenRect.width
|
||||||
|
height: childrenRect.height
|
||||||
|
text: catalog.i18nc("@info:tooltip", "Should a summary be shown when saving a project file?")
|
||||||
|
|
||||||
|
CheckBox
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@option:check", "Show summary dialog when saving project")
|
||||||
|
checked: boolCheck(UM.Preferences.getValue("cura/dialog_on_project_save"))
|
||||||
|
onCheckedChanged: UM.Preferences.setValue("cura/dialog_on_project_save", checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
//: Spacer
|
//: Spacer
|
||||||
|
|
|
@ -127,7 +127,7 @@ UM.ManagementPage
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@action:button", "Activate");
|
text: catalog.i18nc("@action:button", "Activate");
|
||||||
iconName: "list-activate";
|
iconName: "list-activate";
|
||||||
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId
|
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId && Cura.MachineManager.hasMaterials
|
||||||
onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id)
|
onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id)
|
||||||
},
|
},
|
||||||
Button
|
Button
|
||||||
|
@ -144,9 +144,11 @@ UM.ManagementPage
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if(Cura.MachineManager.hasMaterials)
|
||||||
|
{
|
||||||
Cura.MachineManager.setActiveMaterial(material_id)
|
Cura.MachineManager.setActiveMaterial(material_id)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
|
|
|
@ -162,7 +162,7 @@ UM.ManagementPage
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
text: {
|
text: {
|
||||||
return catalog.i18nc("@action:button", "Update profile with current settings");
|
return catalog.i18nc("@action:button", "Update profile with current settings/overrides");
|
||||||
}
|
}
|
||||||
enabled: Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
|
enabled: Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
|
||||||
onClicked: Cura.ContainerManager.updateQualityChanges()
|
onClicked: Cura.ContainerManager.updateQualityChanges()
|
||||||
|
@ -187,7 +187,7 @@ UM.ManagementPage
|
||||||
Label {
|
Label {
|
||||||
id: defaultsMessage
|
id: defaultsMessage
|
||||||
visible: false
|
visible: false
|
||||||
text: catalog.i18nc("@action:label", "This profile uses the defaults specified by the printer, so it has no settings in the list below.")
|
text: catalog.i18nc("@action:label", "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below.")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ UM.PreferencesPage
|
||||||
|
|
||||||
placeholderText: catalog.i18nc("@label:textbox", "Filter...")
|
placeholderText: catalog.i18nc("@label:textbox", "Filter...")
|
||||||
|
|
||||||
onTextChanged: definitionsModel.filter = {"label": "*" + text}
|
onTextChanged: definitionsModel.filter = {"i18n_label": "*" + text}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView
|
ScrollView
|
||||||
|
|
|
@ -30,6 +30,7 @@ SettingItem
|
||||||
textRole: "name"
|
textRole: "name"
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
onCurrentIndexChanged: updateCurrentColor();
|
||||||
|
|
||||||
MouseArea
|
MouseArea
|
||||||
{
|
{
|
||||||
|
@ -115,12 +116,37 @@ SettingItem
|
||||||
propertyProvider.setPropertyValue("value", extruders_model.getItem(index).index);
|
propertyProvider.setPropertyValue("value", extruders_model.getItem(index).index);
|
||||||
control.color = extruders_model.getItem(index).color;
|
control.color = extruders_model.getItem(index).color;
|
||||||
}
|
}
|
||||||
|
|
||||||
onModelChanged: updateCurrentIndex();
|
onModelChanged: updateCurrentIndex();
|
||||||
|
|
||||||
Connections
|
Binding
|
||||||
{
|
{
|
||||||
target: propertyProvider
|
target: control
|
||||||
onPropertiesChanged: control.updateCurrentIndex();
|
property: "currentIndex"
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
for(var i = 0; i < extruders_model.rowCount(); ++i)
|
||||||
|
{
|
||||||
|
if(extruders_model.getItem(i).index == propertyProvider.properties.value)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In some cases we want to update the current color without updating the currentIndex, so it's a seperate function.
|
||||||
|
function updateCurrentColor()
|
||||||
|
{
|
||||||
|
for(var i = 0; i < extruders_model.rowCount(); ++i)
|
||||||
|
{
|
||||||
|
if(extruders_model.getItem(i).index == currentIndex)
|
||||||
|
{
|
||||||
|
control.color = extruders_model.getItem(i).color;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCurrentIndex()
|
function updateCurrentIndex()
|
||||||
|
@ -130,7 +156,6 @@ SettingItem
|
||||||
if(extruders_model.getItem(i).index == propertyProvider.properties.value)
|
if(extruders_model.getItem(i).index == propertyProvider.properties.value)
|
||||||
{
|
{
|
||||||
control.currentIndex = i;
|
control.currentIndex = i;
|
||||||
control.color = extruders_model.getItem(i).color;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,7 @@ Item {
|
||||||
elide: Text.ElideMiddle;
|
elide: Text.ElideMiddle;
|
||||||
|
|
||||||
color: UM.Theme.getColor("setting_control_text");
|
color: UM.Theme.getColor("setting_control_text");
|
||||||
|
opacity: (definition.visible) ? 1 : 0.5
|
||||||
// emphasize the setting if it has a value in the user or quality profile
|
// emphasize the setting if it has a value in the user or quality profile
|
||||||
font: base.doQualityUserSettingEmphasis && base.stackLevel != undefined && base.stackLevel <= 1 ? UM.Theme.getFont("default_italic") : UM.Theme.getFont("default")
|
font: base.doQualityUserSettingEmphasis && base.stackLevel != undefined && base.stackLevel <= 1 ? UM.Theme.getFont("default_italic") : UM.Theme.getFont("default")
|
||||||
}
|
}
|
||||||
|
@ -209,14 +210,26 @@ Item {
|
||||||
// But this will cause the binding to be re-evaluated when the enabled property changes.
|
// But this will cause the binding to be re-evaluated when the enabled property changes.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There are no settings with any warning.
|
||||||
if(Cura.SettingInheritanceManager.settingsWithInheritanceWarning.length == 0)
|
if(Cura.SettingInheritanceManager.settingsWithInheritanceWarning.length == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This setting has a resolve value, so an inheritance warning doesn't do anything.
|
||||||
|
if(resolve != "None")
|
||||||
|
{
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the setting does not have a limit_to_extruder property (or is -1), use the active stack.
|
||||||
if(globalPropertyProvider.properties.limit_to_extruder == null || globalPropertyProvider.properties.limit_to_extruder == -1)
|
if(globalPropertyProvider.properties.limit_to_extruder == null || globalPropertyProvider.properties.limit_to_extruder == -1)
|
||||||
{
|
{
|
||||||
return Cura.SettingInheritanceManager.settingsWithInheritanceWarning.indexOf(definition.key) >= 0;
|
return Cura.SettingInheritanceManager.settingsWithInheritanceWarning.indexOf(definition.key) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setting does have a limit_to_extruder property, so use that one instead.
|
||||||
return Cura.SettingInheritanceManager.getOverridesForExtruder(definition.key, globalPropertyProvider.properties.limit_to_extruder).indexOf(definition.key) >= 0;
|
return Cura.SettingInheritanceManager.getOverridesForExtruder(definition.key, globalPropertyProvider.properties.limit_to_extruder).indexOf(definition.key) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +240,7 @@ Item {
|
||||||
focus = true;
|
focus = true;
|
||||||
|
|
||||||
// Get the most shallow function value (eg not a number) that we can find.
|
// Get the most shallow function value (eg not a number) that we can find.
|
||||||
var last_entry = propertyProvider.stackLevels[propertyProvider.stackLevels.length]
|
var last_entry = propertyProvider.stackLevels[propertyProvider.stackLevels.length - 1]
|
||||||
for (var i = 1; i < base.stackLevels.length; i++)
|
for (var i = 1; i < base.stackLevels.length; i++)
|
||||||
{
|
{
|
||||||
var has_setting_function = typeof(propertyProvider.getPropertyValue("value", base.stackLevels[i])) == "object";
|
var has_setting_function = typeof(propertyProvider.getPropertyValue("value", base.stackLevels[i])) == "object";
|
||||||
|
|
|
@ -9,30 +9,175 @@ import QtQuick.Layouts 1.1
|
||||||
import UM 1.2 as UM
|
import UM 1.2 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
ScrollView
|
Item
|
||||||
{
|
{
|
||||||
id: base;
|
id: base;
|
||||||
|
|
||||||
style: UM.Theme.styles.scrollview;
|
|
||||||
flickableItem.flickableDirection: Flickable.VerticalFlick;
|
|
||||||
|
|
||||||
property Action configureSettings;
|
property Action configureSettings;
|
||||||
|
property bool findingSettings;
|
||||||
signal showTooltip(Item item, point location, string text);
|
signal showTooltip(Item item, point location, string text);
|
||||||
signal hideTooltip();
|
signal hideTooltip();
|
||||||
|
|
||||||
|
function toggleFilterField()
|
||||||
|
{
|
||||||
|
filterContainer.visible = !filterContainer.visible
|
||||||
|
if(filterContainer.visible)
|
||||||
|
{
|
||||||
|
filter.forceActiveFocus();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filter.text = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
id: filterContainer
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
border.color:
|
||||||
|
{
|
||||||
|
if(hoverMouseArea.containsMouse || clearFilterButton.containsMouse)
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("setting_control_border_highlight");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("setting_control_border");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color: UM.Theme.getColor("setting_control")
|
||||||
|
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
top: parent.top
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
}
|
||||||
|
height: visible ? UM.Theme.getSize("setting_control").height : 0
|
||||||
|
Behavior on height { NumberAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
TextField
|
||||||
|
{
|
||||||
|
id: filter;
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: clearFilterButton.left
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
placeholderText: catalog.i18nc("@label:textbox", "Filter...")
|
||||||
|
|
||||||
|
style: TextFieldStyle
|
||||||
|
{
|
||||||
|
textColor: UM.Theme.getColor("setting_control_text");
|
||||||
|
font: UM.Theme.getFont("default");
|
||||||
|
background: Item {}
|
||||||
|
}
|
||||||
|
|
||||||
|
property var expandedCategories
|
||||||
|
property bool lastFindingSettings: false
|
||||||
|
|
||||||
|
onTextChanged:
|
||||||
|
{
|
||||||
|
definitionsModel.filter = {"i18n_label": "*" + text};
|
||||||
|
findingSettings = (text.length > 0);
|
||||||
|
if(findingSettings != lastFindingSettings)
|
||||||
|
{
|
||||||
|
if(findingSettings)
|
||||||
|
{
|
||||||
|
expandedCategories = definitionsModel.expanded.slice();
|
||||||
|
definitionsModel.expanded = ["*"];
|
||||||
|
definitionsModel.showAncestors = true;
|
||||||
|
definitionsModel.showAll = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
definitionsModel.expanded = expandedCategories;
|
||||||
|
definitionsModel.showAncestors = false;
|
||||||
|
definitionsModel.showAll = false;
|
||||||
|
}
|
||||||
|
lastFindingSettings = findingSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEscapePressed:
|
||||||
|
{
|
||||||
|
filter.text = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
id: hoverMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
cursorShape: Qt.IBeamCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.SimpleButton
|
||||||
|
{
|
||||||
|
id: clearFilterButton
|
||||||
|
iconSource: UM.Theme.getIcon("cross1")
|
||||||
|
visible: findingSettings
|
||||||
|
|
||||||
|
height: parent.height * 0.4
|
||||||
|
width: visible ? height : 0
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
color: UM.Theme.getColor("setting_control_button")
|
||||||
|
hoverColor: UM.Theme.getColor("setting_control_button_hover")
|
||||||
|
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
filter.text = "";
|
||||||
|
filter.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView
|
||||||
|
{
|
||||||
|
anchors.top: filterContainer.bottom;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.topMargin: filterContainer.visible ? UM.Theme.getSize("default_margin").width : 0
|
||||||
|
Behavior on anchors.topMargin { NumberAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
style: UM.Theme.styles.scrollview;
|
||||||
|
flickableItem.flickableDirection: Flickable.VerticalFlick;
|
||||||
|
|
||||||
ListView
|
ListView
|
||||||
{
|
{
|
||||||
id: contents
|
id: contents
|
||||||
spacing: UM.Theme.getSize("default_lining").height;
|
spacing: UM.Theme.getSize("default_lining").height;
|
||||||
cacheBuffer: 1000000; // Set a large cache to effectively just cache every list item.
|
cacheBuffer: 1000000; // Set a large cache to effectively just cache every list item.
|
||||||
|
|
||||||
model: UM.SettingDefinitionsModel {
|
model: UM.SettingDefinitionsModel
|
||||||
|
{
|
||||||
id: definitionsModel;
|
id: definitionsModel;
|
||||||
containerId: Cura.MachineManager.activeDefinitionId
|
containerId: Cura.MachineManager.activeDefinitionId
|
||||||
visibilityHandler: UM.SettingPreferenceVisibilityHandler { }
|
visibilityHandler: UM.SettingPreferenceVisibilityHandler { }
|
||||||
exclude: ["machine_settings", "command_line_settings", "infill_mesh", "infill_mesh_order"] // TODO: infill_mesh settigns are excluded hardcoded, but should be based on the fact that settable_globally, settable_per_meshgroup and settable_per_extruder are false.
|
exclude: ["machine_settings", "command_line_settings", "infill_mesh", "infill_mesh_order"] // TODO: infill_mesh settigns are excluded hardcoded, but should be based on the fact that settable_globally, settable_per_meshgroup and settable_per_extruder are false.
|
||||||
expanded: Printer.expandedCategories
|
expanded: Printer.expandedCategories
|
||||||
onExpandedChanged: Printer.setExpandedCategories(expanded)
|
onExpandedChanged:
|
||||||
|
{
|
||||||
|
if(!findingSettings)
|
||||||
|
{
|
||||||
|
// Do not change expandedCategories preference while filtering settings
|
||||||
|
// because all categories are expanded while filtering
|
||||||
|
Printer.setExpandedCategories(expanded)
|
||||||
|
}
|
||||||
|
}
|
||||||
onVisibilityChanged: Cura.SettingInheritanceManager.forceUpdate()
|
onVisibilityChanged: Cura.SettingInheritanceManager.forceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +292,7 @@ ScrollView
|
||||||
onContextMenuRequested:
|
onContextMenuRequested:
|
||||||
{
|
{
|
||||||
contextMenu.key = model.key;
|
contextMenu.key = model.key;
|
||||||
|
contextMenu.settingVisible = model.visible;
|
||||||
contextMenu.provider = provider
|
contextMenu.provider = provider
|
||||||
contextMenu.popup();
|
contextMenu.popup();
|
||||||
}
|
}
|
||||||
|
@ -194,6 +340,7 @@ ScrollView
|
||||||
|
|
||||||
property string key
|
property string key
|
||||||
property var provider
|
property var provider
|
||||||
|
property bool settingVisible
|
||||||
|
|
||||||
MenuItem
|
MenuItem
|
||||||
{
|
{
|
||||||
|
@ -212,10 +359,38 @@ ScrollView
|
||||||
MenuItem
|
MenuItem
|
||||||
{
|
{
|
||||||
//: Settings context menu action
|
//: Settings context menu action
|
||||||
|
visible: !findingSettings;
|
||||||
text: catalog.i18nc("@action:menu", "Hide this setting");
|
text: catalog.i18nc("@action:menu", "Hide this setting");
|
||||||
onTriggered: definitionsModel.hide(contextMenu.key);
|
onTriggered: definitionsModel.hide(contextMenu.key);
|
||||||
}
|
}
|
||||||
MenuItem
|
MenuItem
|
||||||
|
{
|
||||||
|
//: Settings context menu action
|
||||||
|
text:
|
||||||
|
{
|
||||||
|
if (contextMenu.settingVisible)
|
||||||
|
{
|
||||||
|
return catalog.i18nc("@action:menu", "Don't show this setting");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return catalog.i18nc("@action:menu", "Keep this setting visible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visible: findingSettings;
|
||||||
|
onTriggered:
|
||||||
|
{
|
||||||
|
if (contextMenu.settingVisible)
|
||||||
|
{
|
||||||
|
definitionsModel.hide(contextMenu.key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
definitionsModel.show(contextMenu.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem
|
||||||
{
|
{
|
||||||
//: Settings context menu action
|
//: Settings context menu action
|
||||||
text: catalog.i18nc("@action:menu", "Configure setting visiblity...");
|
text: catalog.i18nc("@action:menu", "Configure setting visiblity...");
|
||||||
|
@ -235,3 +410,4 @@ ScrollView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import QtQuick.Layouts 1.1
|
||||||
|
|
||||||
import UM 1.2 as UM
|
import UM 1.2 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
|
import "Menus"
|
||||||
|
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
|
@ -30,9 +31,21 @@ Rectangle
|
||||||
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
|
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
|
||||||
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
|
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
|
||||||
|
|
||||||
color: UM.Theme.getColor("sidebar");
|
color: UM.Theme.getColor("sidebar")
|
||||||
UM.I18nCatalog { id: catalog; name:"cura"}
|
UM.I18nCatalog { id: catalog; name:"cura"}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: tooltipDelayTimer
|
||||||
|
interval: 500
|
||||||
|
repeat: false
|
||||||
|
property var item
|
||||||
|
property string text
|
||||||
|
|
||||||
|
onTriggered:
|
||||||
|
{
|
||||||
|
base.showTooltip(base, {x:1, y:item.y}, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showTooltip(item, position, text)
|
function showTooltip(item, position, text)
|
||||||
{
|
{
|
||||||
|
@ -73,7 +86,7 @@ Rectangle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode selection buttons for changing between Setting & Monitor print mode
|
// Printer selection and mode selection buttons for changing between Setting & Monitor print mode
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
id: sidebarHeaderBar
|
id: sidebarHeaderBar
|
||||||
|
@ -85,25 +98,90 @@ Rectangle
|
||||||
Row
|
Row
|
||||||
{
|
{
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width;
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
ToolButton
|
||||||
|
{
|
||||||
|
id: machineSelection
|
||||||
|
text: Cura.MachineManager.activeMachineName
|
||||||
|
|
||||||
|
width: parent.width - (showSettings.width + showMonitor.width + 2 * UM.Theme.getSize("default_margin").width)
|
||||||
|
height: UM.Theme.getSize("sidebar_header").height
|
||||||
|
tooltip: Cura.MachineManager.activeMachineName
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
style: ButtonStyle {
|
||||||
|
background: Rectangle {
|
||||||
|
color: control.hovered ? UM.Theme.getColor("button_hover") :
|
||||||
|
control.pressed ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("sidebar_header_bar")
|
||||||
|
Behavior on color { ColorAnimation { duration: 50; } }
|
||||||
|
|
||||||
|
UM.RecolorImage {
|
||||||
|
id: downArrow
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
width: UM.Theme.getSize("standard_arrow").width
|
||||||
|
height: UM.Theme.getSize("standard_arrow").height
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: width
|
||||||
|
color: UM.Theme.getColor("text_reversed")
|
||||||
|
source: UM.Theme.getIcon("arrow_bottom")
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: sidebarComboBoxLabel
|
||||||
|
color: UM.Theme.getColor("text_reversed")
|
||||||
|
text: control.text;
|
||||||
|
elide: Text.ElideRight;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.right: downArrow.left;
|
||||||
|
anchors.rightMargin: control.rightMargin;
|
||||||
|
anchors.verticalCenter: parent.verticalCenter;
|
||||||
|
font: UM.Theme.getFont("large")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
label: Label{}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu: PrinterMenu { }
|
||||||
|
}
|
||||||
|
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
id: showSettings
|
id: showSettings
|
||||||
width: (parent.width - UM.Theme.getSize("default_margin").width) / 2
|
width: height
|
||||||
height: UM.Theme.getSize("sidebar_header").height
|
height: UM.Theme.getSize("sidebar_header").height
|
||||||
onClicked: monitoringPrint = false
|
onClicked: monitoringPrint = false
|
||||||
iconSource: UM.Theme.getIcon("tab_settings");
|
iconSource: UM.Theme.getIcon("tab_settings");
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: !monitoringPrint
|
checked: !monitoringPrint
|
||||||
exclusiveGroup: sidebarHeaderBarGroup
|
exclusiveGroup: sidebarHeaderBarGroup
|
||||||
|
property string tooltipText: catalog.i18nc("@tooltip", "<b>Print Setup</b><br/><br/>Edit or review the settings for the active print job.")
|
||||||
|
|
||||||
|
onHoveredChanged: {
|
||||||
|
if (hovered)
|
||||||
|
{
|
||||||
|
tooltipDelayTimer.item = showSettings
|
||||||
|
tooltipDelayTimer.text = tooltipText
|
||||||
|
tooltipDelayTimer.start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tooltipDelayTimer.stop();
|
||||||
|
base.hideTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
style: UM.Theme.styles.sidebar_header_tab
|
style: UM.Theme.styles.sidebar_header_tab
|
||||||
}
|
}
|
||||||
|
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
id: showMonitor
|
id: showMonitor
|
||||||
width: (parent.width - UM.Theme.getSize("default_margin").width) / 2
|
width: height
|
||||||
height: UM.Theme.getSize("sidebar_header").height
|
height: UM.Theme.getSize("sidebar_header").height
|
||||||
onClicked: monitoringPrint = true
|
onClicked: monitoringPrint = true
|
||||||
iconSource: {
|
iconSource: {
|
||||||
|
@ -139,6 +217,21 @@ Rectangle
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: monitoringPrint
|
checked: monitoringPrint
|
||||||
exclusiveGroup: sidebarHeaderBarGroup
|
exclusiveGroup: sidebarHeaderBarGroup
|
||||||
|
property string tooltipText: catalog.i18nc("@tooltip", "<b>Print Monitor</b><br/><br/>Monitor the state of the connected printer and the print job in progress.")
|
||||||
|
|
||||||
|
onHoveredChanged: {
|
||||||
|
if (hovered)
|
||||||
|
{
|
||||||
|
tooltipDelayTimer.item = showMonitor
|
||||||
|
tooltipDelayTimer.text = tooltipText
|
||||||
|
tooltipDelayTimer.start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tooltipDelayTimer.stop();
|
||||||
|
base.hideTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
style: UM.Theme.styles.sidebar_header_tab
|
style: UM.Theme.styles.sidebar_header_tab
|
||||||
}
|
}
|
||||||
|
@ -151,7 +244,6 @@ Rectangle
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
anchors.top: sidebarHeaderBar.bottom
|
anchors.top: sidebarHeaderBar.bottom
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
|
||||||
|
|
||||||
onShowTooltip: base.showTooltip(item, location, text)
|
onShowTooltip: base.showTooltip(item, location, text)
|
||||||
onHideTooltip: base.hideTooltip()
|
onHideTooltip: base.hideTooltip()
|
||||||
|
@ -160,21 +252,13 @@ Rectangle
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: headerSeparator
|
id: headerSeparator
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: UM.Theme.getSize("sidebar_lining").height
|
visible: !monitoringPrint
|
||||||
|
height: visible ? UM.Theme.getSize("sidebar_lining").height : 0
|
||||||
color: UM.Theme.getColor("sidebar_lining")
|
color: UM.Theme.getColor("sidebar_lining")
|
||||||
anchors.top: header.bottom
|
anchors.top: header.bottom
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
anchors.topMargin: visible ? UM.Theme.getSize("default_margin").height : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
currentModeIndex:
|
|
||||||
{
|
|
||||||
var index = parseInt(UM.Preferences.getValue("cura/active_mode"))
|
|
||||||
if(index)
|
|
||||||
{
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
onCurrentModeIndexChanged:
|
onCurrentModeIndexChanged:
|
||||||
{
|
{
|
||||||
UM.Preferences.setValue("cura/active_mode", currentModeIndex);
|
UM.Preferences.setValue("cura/active_mode", currentModeIndex);
|
||||||
|
@ -214,13 +298,27 @@ Rectangle
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: model.index * (settingsModeSelection.width / 2)
|
anchors.leftMargin: model.index * (settingsModeSelection.width / 2)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: parent.width / 2
|
width: 0.5 * parent.width - (model.showFilterButton ? toggleFilterButton.width : 0)
|
||||||
text: model.text
|
text: model.text
|
||||||
exclusiveGroup: modeMenuGroup;
|
exclusiveGroup: modeMenuGroup;
|
||||||
checkable: true;
|
checkable: true;
|
||||||
checked: base.currentModeIndex == index
|
checked: base.currentModeIndex == index
|
||||||
onClicked: base.currentModeIndex = index
|
onClicked: base.currentModeIndex = index
|
||||||
|
|
||||||
|
onHoveredChanged: {
|
||||||
|
if (hovered)
|
||||||
|
{
|
||||||
|
tooltipDelayTimer.item = settingsModeSelection
|
||||||
|
tooltipDelayTimer.text = model.tooltipText
|
||||||
|
tooltipDelayTimer.start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tooltipDelayTimer.stop();
|
||||||
|
base.hideTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
style: ButtonStyle {
|
style: ButtonStyle {
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
@ -256,6 +354,44 @@ Rectangle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: toggleFilterButton
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.top: headerSeparator.bottom
|
||||||
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
height: settingsModeSelection.height
|
||||||
|
width: visible ? height : 0
|
||||||
|
|
||||||
|
visible: !monitoringPrint && modesListModel.get(base.currentModeIndex) != undefined && modesListModel.get(base.currentModeIndex).showFilterButton
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
|
onClicked: sidebarContents.currentItem.toggleFilterField()
|
||||||
|
|
||||||
|
style: ButtonStyle
|
||||||
|
{
|
||||||
|
background: Rectangle
|
||||||
|
{
|
||||||
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
border.color: UM.Theme.getColor("toggle_checked_border")
|
||||||
|
color: visible ? UM.Theme.getColor("toggle_checked") : UM.Theme.getColor("toggle_hovered")
|
||||||
|
Behavior on color { ColorAnimation { duration: 50; } }
|
||||||
|
}
|
||||||
|
label: UM.RecolorImage
|
||||||
|
{
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width / 2
|
||||||
|
|
||||||
|
source: UM.Theme.getIcon("search")
|
||||||
|
color: UM.Theme.getColor("toggle_checked_text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: monitorLabel
|
id: monitorLabel
|
||||||
text: catalog.i18nc("@label","Printer Monitor");
|
text: catalog.i18nc("@label","Printer Monitor");
|
||||||
|
@ -379,9 +515,25 @@ Rectangle
|
||||||
|
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
{
|
{
|
||||||
modesListModel.append({ text: catalog.i18nc("@title:tab", "Recommended"), item: sidebarSimple })
|
modesListModel.append({
|
||||||
modesListModel.append({ text: catalog.i18nc("@title:tab", "Custom"), item: sidebarAdvanced })
|
text: catalog.i18nc("@title:tab", "Recommended"),
|
||||||
|
tooltipText: catalog.i18nc("@tooltip", "<b>Recommended Print Setup</b><br/><br/>Print with the recommended settings for the selected printer, material and quality."),
|
||||||
|
item: sidebarSimple,
|
||||||
|
showFilterButton: false
|
||||||
|
})
|
||||||
|
modesListModel.append({
|
||||||
|
text: catalog.i18nc("@title:tab", "Custom"),
|
||||||
|
tooltipText: catalog.i18nc("@tooltip", "<b>Custom Print Setup</b><br/><br/>Print with finegrained control over every last bit of the slicing process."),
|
||||||
|
item: sidebarAdvanced,
|
||||||
|
showFilterButton: true
|
||||||
|
})
|
||||||
sidebarContents.push({ "item": modesListModel.get(base.currentModeIndex).item, "immediate": true });
|
sidebarContents.push({ "item": modesListModel.get(base.currentModeIndex).item, "immediate": true });
|
||||||
|
|
||||||
|
var index = parseInt(UM.Preferences.getValue("cura/active_mode"))
|
||||||
|
if(index)
|
||||||
|
{
|
||||||
|
currentModeIndex = index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
|
|
|
@ -21,44 +21,23 @@ Column
|
||||||
signal showTooltip(Item item, point location, string text)
|
signal showTooltip(Item item, point location, string text)
|
||||||
signal hideTooltip()
|
signal hideTooltip()
|
||||||
|
|
||||||
Row
|
Item
|
||||||
{
|
{
|
||||||
id: machineSelectionRow
|
id: extruderSelectionRow
|
||||||
height: UM.Theme.getSize("sidebar_setup").height
|
width: parent.width
|
||||||
|
height: UM.Theme.getSize("sidebar_tabs").height
|
||||||
|
visible: machineExtruderCount.properties.value > 1 && !sidebar.monitoringPrint
|
||||||
|
|
||||||
anchors
|
Rectangle
|
||||||
{
|
{
|
||||||
left: parent.left
|
id: extruderSeparator
|
||||||
leftMargin: UM.Theme.getSize("default_margin").width
|
visible: machineExtruderCount.properties.value > 1 && !sidebar.monitoringPrint
|
||||||
right: parent.right
|
|
||||||
rightMargin: UM.Theme.getSize("default_margin").width
|
|
||||||
}
|
|
||||||
|
|
||||||
Label
|
width: parent.width
|
||||||
{
|
height: parent.height
|
||||||
id: machineSelectionLabel
|
color: UM.Theme.getColor("sidebar_lining")
|
||||||
text: catalog.i18nc("@label:listbox", "Printer:");
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
font: UM.Theme.getFont("default");
|
|
||||||
color: UM.Theme.getColor("text");
|
|
||||||
|
|
||||||
width: parent.width * 0.45 - UM.Theme.getSize("default_margin").width
|
anchors.top: extruderSelectionRow.top
|
||||||
}
|
|
||||||
|
|
||||||
ToolButton
|
|
||||||
{
|
|
||||||
id: machineSelection
|
|
||||||
text: Cura.MachineManager.activeMachineName;
|
|
||||||
|
|
||||||
height: UM.Theme.getSize("setting_control").height
|
|
||||||
tooltip: Cura.MachineManager.activeMachineName
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
style: UM.Theme.styles.sidebar_header_button
|
|
||||||
|
|
||||||
width: parent.width * 0.55 + UM.Theme.getSize("default_margin").width
|
|
||||||
|
|
||||||
menu: PrinterMenu { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView
|
ListView
|
||||||
|
@ -66,17 +45,15 @@ Column
|
||||||
id: extrudersList
|
id: extrudersList
|
||||||
property var index: 0
|
property var index: 0
|
||||||
|
|
||||||
visible: machineExtruderCount.properties.value > 1 && !sidebar.monitoringPrint
|
height: UM.Theme.getSize("sidebar_header_mode_tabs").height
|
||||||
height: UM.Theme.getSize("sidebar_header_mode_toggle").height
|
width: parent.width
|
||||||
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
left: parent.left
|
left: parent.left
|
||||||
leftMargin: UM.Theme.getSize("default_margin").width
|
|
||||||
right: parent.right
|
right: parent.right
|
||||||
rightMargin: UM.Theme.getSize("default_margin").width
|
bottom: extruderSelectionRow.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
ExclusiveGroup { id: extruderMenuGroup; }
|
ExclusiveGroup { id: extruderMenuGroup; }
|
||||||
|
@ -117,14 +94,25 @@ Column
|
||||||
background: Rectangle
|
background: Rectangle
|
||||||
{
|
{
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
border.color: control.checked ? UM.Theme.getColor("toggle_checked_border") :
|
border.color: control.checked ? UM.Theme.getColor("tab_checked_border") :
|
||||||
control.pressed ? UM.Theme.getColor("toggle_active_border") :
|
control.pressed ? UM.Theme.getColor("tab_active_border") :
|
||||||
control.hovered ? UM.Theme.getColor("toggle_hovered_border") : UM.Theme.getColor("toggle_unchecked_border")
|
control.hovered ? UM.Theme.getColor("tab_hovered_border") : UM.Theme.getColor("tab_unchecked_border")
|
||||||
color: control.checked ? UM.Theme.getColor("toggle_checked") :
|
color: control.checked ? UM.Theme.getColor("tab_checked") :
|
||||||
control.pressed ? UM.Theme.getColor("toggle_active") :
|
control.pressed ? UM.Theme.getColor("tab_active") :
|
||||||
control.hovered ? UM.Theme.getColor("toggle_hovered") : UM.Theme.getColor("toggle_unchecked")
|
control.hovered ? UM.Theme.getColor("tab_hovered") : UM.Theme.getColor("tab_unchecked")
|
||||||
Behavior on color { ColorAnimation { duration: 50; } }
|
Behavior on color { ColorAnimation { duration: 50; } }
|
||||||
|
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
id: highlight
|
||||||
|
visible: control.checked
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
height: UM.Theme.getSize("sidebar_header_highlight").height
|
||||||
|
color: UM.Theme.getColor("sidebar_header_bar")
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
id: swatch
|
id: swatch
|
||||||
|
@ -137,7 +125,7 @@ Column
|
||||||
|
|
||||||
color: model.color
|
color: model.color
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
border.color: UM.Theme.getColor("toggle_checked")
|
border.color: UM.Theme.getColor("setting_control_border")
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -148,9 +136,9 @@ Column
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width / 2
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width / 2
|
||||||
|
|
||||||
color: control.checked ? UM.Theme.getColor("toggle_checked_text") :
|
color: control.checked ? UM.Theme.getColor("tab_checked_text") :
|
||||||
control.pressed ? UM.Theme.getColor("toggle_active_text") :
|
control.pressed ? UM.Theme.getColor("tab_active_text") :
|
||||||
control.hovered ? UM.Theme.getColor("toggle_hovered_text") : UM.Theme.getColor("toggle_unchecked_text")
|
control.hovered ? UM.Theme.getColor("tab_hovered_text") : UM.Theme.getColor("tab_unchecked_text")
|
||||||
|
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
text: control.text
|
text: control.text
|
||||||
|
@ -161,6 +149,15 @@ Column
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
id: variantRowSpacer
|
||||||
|
height: UM.Theme.getSize("default_margin").height / 4
|
||||||
|
width: height
|
||||||
|
visible: !extruderSelectionRow.visible
|
||||||
|
}
|
||||||
|
|
||||||
Row
|
Row
|
||||||
{
|
{
|
||||||
|
@ -329,7 +326,7 @@ Column
|
||||||
}
|
}
|
||||||
onEntered:
|
onEntered:
|
||||||
{
|
{
|
||||||
var content = catalog.i18nc("@tooltip","Some setting values are different from the values stored in the profile.\n\nClick to open the profile manager.")
|
var content = catalog.i18nc("@tooltip","Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.")
|
||||||
base.showTooltip(globalProfileRow, Qt.point(0, globalProfileRow.height / 2), content)
|
base.showTooltip(globalProfileRow, Qt.point(0, globalProfileRow.height / 2), content)
|
||||||
}
|
}
|
||||||
onExited: base.hideTooltip()
|
onExited: base.hideTooltip()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2015 Ultimaker B.V.
|
// Copyright (c) 2016 Ultimaker B.V.
|
||||||
// Cura is released under the terms of the AGPLv3 or higher.
|
// Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.2
|
import QtQuick 2.2
|
||||||
|
@ -25,17 +25,19 @@ Item
|
||||||
Component.onDestruction: PrintInformation.enabled = false
|
Component.onDestruction: PrintInformation.enabled = false
|
||||||
UM.I18nCatalog { id: catalog; name:"cura"}
|
UM.I18nCatalog { id: catalog; name:"cura"}
|
||||||
|
|
||||||
Rectangle{
|
Rectangle
|
||||||
|
{
|
||||||
id: infillCellLeft
|
id: infillCellLeft
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
width: base.width / 100 * 35 - UM.Theme.getSize("default_margin").width
|
width: base.width * .45 - UM.Theme.getSize("default_margin").width
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
||||||
Label{
|
Label
|
||||||
|
{
|
||||||
id: infillLabel
|
id: infillLabel
|
||||||
//: Infill selection label
|
//: Infill selection label
|
||||||
text: catalog.i18nc("@label", "Infill:");
|
text: catalog.i18nc("@label", "Infill");
|
||||||
font: UM.Theme.getFont("default");
|
font: UM.Theme.getFont("default");
|
||||||
color: UM.Theme.getColor("text");
|
color: UM.Theme.getColor("text");
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
@ -45,19 +47,22 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Flow {
|
Flow
|
||||||
|
{
|
||||||
id: infillCellRight
|
id: infillCellRight
|
||||||
|
|
||||||
height: childrenRect.height;
|
height: childrenRect.height;
|
||||||
width: base.width / 100 * 65
|
width: base.width * .55
|
||||||
spacing: UM.Theme.getSize("default_margin").width
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
anchors.left: infillCellLeft.right
|
anchors.left: infillCellLeft.right
|
||||||
anchors.top: infillCellLeft.top
|
anchors.top: infillCellLeft.top
|
||||||
|
|
||||||
Repeater {
|
Repeater
|
||||||
|
{
|
||||||
id: infillListView
|
id: infillListView
|
||||||
property int activeIndex: {
|
property int activeIndex:
|
||||||
|
{
|
||||||
var density = parseInt(infillDensity.properties.value)
|
var density = parseInt(infillDensity.properties.value)
|
||||||
for(var i = 0; i < infillModel.count; ++i)
|
for(var i = 0; i < infillModel.count; ++i)
|
||||||
{
|
{
|
||||||
|
@ -71,17 +76,20 @@ Item
|
||||||
}
|
}
|
||||||
model: infillModel;
|
model: infillModel;
|
||||||
|
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
width: childrenRect.width;
|
width: childrenRect.width;
|
||||||
height: childrenRect.height;
|
height: childrenRect.height;
|
||||||
|
|
||||||
Rectangle{
|
Rectangle
|
||||||
|
{
|
||||||
id: infillIconLining
|
id: infillIconLining
|
||||||
|
|
||||||
width: (infillCellRight.width - 3 * UM.Theme.getSize("default_margin").width) / 4;
|
width: (infillCellRight.width - 3 * UM.Theme.getSize("default_margin").width) / 4;
|
||||||
height: width
|
height: width
|
||||||
|
|
||||||
border.color: {
|
border.color:
|
||||||
|
{
|
||||||
if(!base.settingsEnabled)
|
if(!base.settingsEnabled)
|
||||||
{
|
{
|
||||||
return UM.Theme.getColor("setting_control_disabled_border")
|
return UM.Theme.getColor("setting_control_disabled_border")
|
||||||
|
@ -97,7 +105,8 @@ Item
|
||||||
return UM.Theme.getColor("setting_control_border")
|
return UM.Theme.getColor("setting_control_border")
|
||||||
}
|
}
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
color: {
|
color:
|
||||||
|
{
|
||||||
if(infillListView.activeIndex == index)
|
if(infillListView.activeIndex == index)
|
||||||
{
|
{
|
||||||
if(!base.settingsEnabled)
|
if(!base.settingsEnabled)
|
||||||
|
@ -109,7 +118,8 @@ Item
|
||||||
return "transparent"
|
return "transparent"
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.RecolorImage {
|
UM.RecolorImage
|
||||||
|
{
|
||||||
id: infillIcon
|
id: infillIcon
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
anchors.margins: UM.Theme.getSize("infill_button_margin").width
|
anchors.margins: UM.Theme.getSize("infill_button_margin").width
|
||||||
|
@ -130,7 +140,8 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea
|
||||||
|
{
|
||||||
id: infillMouseArea
|
id: infillMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
@ -141,15 +152,18 @@ Item
|
||||||
infillDensity.setPropertyValue("value", model.percentage)
|
infillDensity.setPropertyValue("value", model.percentage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onEntered: {
|
onEntered:
|
||||||
|
{
|
||||||
base.showTooltip(infillCellRight, Qt.point(-infillCellRight.x, 0), model.text);
|
base.showTooltip(infillCellRight, Qt.point(-infillCellRight.x, 0), model.text);
|
||||||
}
|
}
|
||||||
onExited: {
|
onExited:
|
||||||
|
{
|
||||||
base.hideTooltip();
|
base.hideTooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Label{
|
Label
|
||||||
|
{
|
||||||
id: infillLabel
|
id: infillLabel
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
anchors.top: infillIconLining.bottom
|
anchors.top: infillIconLining.bottom
|
||||||
|
@ -160,7 +174,8 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
ListModel
|
||||||
|
{
|
||||||
id: infillModel
|
id: infillModel
|
||||||
|
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
|
@ -201,7 +216,8 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: helpersCell
|
id: helpersCell
|
||||||
anchors.top: infillCellRight.bottom
|
anchors.top: infillCellRight.bottom
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
|
@ -209,34 +225,183 @@ Item
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
||||||
Label{
|
Label
|
||||||
id: adhesionHelperLabel
|
{
|
||||||
|
id: enableSupportLabel
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
anchors.verticalCenter: adhesionCheckBox.verticalCenter
|
anchors.verticalCenter: enableSupportCheckBox.verticalCenter
|
||||||
width: parent.width / 100 * 35 - 3 * UM.Theme.getSize("default_margin").width
|
width: parent.width * .45 - 3 * UM.Theme.getSize("default_margin").width
|
||||||
//: Bed adhesion label
|
text: catalog.i18nc("@label", "Enable Support");
|
||||||
text: catalog.i18nc("@label", "Helper Parts:");
|
|
||||||
font: UM.Theme.getFont("default");
|
font: UM.Theme.getFont("default");
|
||||||
color: UM.Theme.getColor("text");
|
color: UM.Theme.getColor("text");
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox{
|
CheckBox
|
||||||
|
{
|
||||||
|
id: enableSupportCheckBox
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: enableSupportLabel.right
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
style: UM.Theme.styles.checkbox;
|
||||||
|
enabled: base.settingsEnabled
|
||||||
|
|
||||||
|
checked: supportEnabled.properties.value == "True";
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
id: enableSupportMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: true
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
// The value is a string "True" or "False"
|
||||||
|
supportEnabled.setPropertyValue("value", supportEnabled.properties.value != "True");
|
||||||
|
}
|
||||||
|
onEntered:
|
||||||
|
{
|
||||||
|
base.showTooltip(enableSupportCheckBox, Qt.point(-enableSupportCheckBox.x, 0),
|
||||||
|
catalog.i18nc("@label", "Enable support structures. These structures support parts of the model with severe overhangs."));
|
||||||
|
}
|
||||||
|
onExited:
|
||||||
|
{
|
||||||
|
base.hideTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: supportExtruderLabel
|
||||||
|
visible: (supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1)
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.verticalCenter: supportExtruderCombobox.verticalCenter
|
||||||
|
width: parent.width * .45 - 3 * UM.Theme.getSize("default_margin").width
|
||||||
|
text: catalog.i18nc("@label", "Support Extruder");
|
||||||
|
font: UM.Theme.getFont("default");
|
||||||
|
color: UM.Theme.getColor("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox
|
||||||
|
{
|
||||||
|
id: supportExtruderCombobox
|
||||||
|
visible: (supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1)
|
||||||
|
model: extruderModel
|
||||||
|
|
||||||
|
property string color_override: "" // for manually setting values
|
||||||
|
property string color: // is evaluated automatically, but the first time is before extruderModel being filled
|
||||||
|
{
|
||||||
|
var current_extruder = extruderModel.get(currentIndex);
|
||||||
|
color_override = "";
|
||||||
|
if (current_extruder === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var model_color = current_extruder.color;
|
||||||
|
return (model_color) ? model_color : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
textRole: 'text' // this solves that the combobox isn't populated in the first time Cura is started
|
||||||
|
|
||||||
|
anchors.top: enableSupportCheckBox.bottom
|
||||||
|
anchors.topMargin:
|
||||||
|
{
|
||||||
|
if ((supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1))
|
||||||
|
{
|
||||||
|
return UM.Theme.getSize("default_margin").height;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anchors.left: supportExtruderLabel.right
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
width: parent.width * .55
|
||||||
|
height:
|
||||||
|
{
|
||||||
|
if ((supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1))
|
||||||
|
{
|
||||||
|
// default height when control is enabled
|
||||||
|
return UM.Theme.getSize("setting_control").height;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on height { NumberAnimation { duration: 100 } }
|
||||||
|
|
||||||
|
style: UM.Theme.styles.combobox_color
|
||||||
|
enabled: base.settingsEnabled
|
||||||
|
property alias _hovered: supportExtruderMouseArea.containsMouse
|
||||||
|
|
||||||
|
currentIndex: supportExtruderNr.properties !== null ? parseFloat(supportExtruderNr.properties.value) : 0
|
||||||
|
onActivated:
|
||||||
|
{
|
||||||
|
// Send the extruder nr as a string.
|
||||||
|
supportExtruderNr.setPropertyValue("value", String(index));
|
||||||
|
}
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
id: supportExtruderMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: base.settingsEnabled
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
onEntered:
|
||||||
|
{
|
||||||
|
base.showTooltip(supportExtruderCombobox, Qt.point(-supportExtruderCombobox.x, 0),
|
||||||
|
catalog.i18nc("@label", "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."));
|
||||||
|
}
|
||||||
|
onExited:
|
||||||
|
{
|
||||||
|
base.hideTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCurrentColor()
|
||||||
|
{
|
||||||
|
var current_extruder = extruderModel.get(currentIndex);
|
||||||
|
if (current_extruder !== undefined) {
|
||||||
|
supportExtruderCombobox.color_override = current_extruder.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: adhesionHelperLabel
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.verticalCenter: adhesionCheckBox.verticalCenter
|
||||||
|
width: parent.width * .45 - 3 * UM.Theme.getSize("default_margin").width
|
||||||
|
text: catalog.i18nc("@label", "Build Plate Adhesion");
|
||||||
|
font: UM.Theme.getFont("default");
|
||||||
|
color: UM.Theme.getColor("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox
|
||||||
|
{
|
||||||
id: adhesionCheckBox
|
id: adhesionCheckBox
|
||||||
property alias _hovered: adhesionMouseArea.containsMouse
|
property alias _hovered: adhesionMouseArea.containsMouse
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: supportExtruderCombobox.bottom
|
||||||
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
anchors.left: adhesionHelperLabel.right
|
anchors.left: adhesionHelperLabel.right
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
//: Setting enable printing build-plate adhesion helper checkbox
|
//: Setting enable printing build-plate adhesion helper checkbox
|
||||||
text: catalog.i18nc("@option:check", "Print Build Plate Adhesion");
|
|
||||||
style: UM.Theme.styles.checkbox;
|
style: UM.Theme.styles.checkbox;
|
||||||
enabled: base.settingsEnabled
|
enabled: base.settingsEnabled
|
||||||
|
|
||||||
checked: platformAdhesionType.properties.value != "skirt"
|
checked: platformAdhesionType.properties.value != "skirt"
|
||||||
|
|
||||||
MouseArea {
|
MouseArea
|
||||||
|
{
|
||||||
id: adhesionMouseArea
|
id: adhesionMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
@ -269,99 +434,8 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label{
|
ListModel
|
||||||
id: supportHelperLabel
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
|
||||||
anchors.verticalCenter: supportCheckBox.verticalCenter
|
|
||||||
width: parent.width / 100 * 35 - 3 * UM.Theme.getSize("default_margin").width
|
|
||||||
//: Support label
|
|
||||||
text: "";
|
|
||||||
font: UM.Theme.getFont("default");
|
|
||||||
color: UM.Theme.getColor("text");
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox{
|
|
||||||
id: supportCheckBox
|
|
||||||
visible: machineExtruderCount.properties.value <= 1
|
|
||||||
property alias _hovered: supportMouseArea.containsMouse
|
|
||||||
|
|
||||||
anchors.top: adhesionCheckBox.bottom
|
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
|
||||||
anchors.left: supportHelperLabel.right
|
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
|
||||||
|
|
||||||
//: Setting enable support checkbox
|
|
||||||
text: catalog.i18nc("@option:check", "Print Support Structure");
|
|
||||||
style: UM.Theme.styles.checkbox;
|
|
||||||
enabled: base.settingsEnabled
|
|
||||||
|
|
||||||
checked: supportEnabled.properties.value == "True"
|
|
||||||
MouseArea {
|
|
||||||
id: supportMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
enabled: base.settingsEnabled
|
|
||||||
onClicked:
|
|
||||||
{
|
{
|
||||||
supportEnabled.setPropertyValue("value", !parent.checked)
|
|
||||||
}
|
|
||||||
onEntered:
|
|
||||||
{
|
|
||||||
base.showTooltip(supportCheckBox, Qt.point(-supportCheckBox.x, 0),
|
|
||||||
catalog.i18nc("@label", "Enable printing support structures. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."));
|
|
||||||
}
|
|
||||||
onExited:
|
|
||||||
{
|
|
||||||
base.hideTooltip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: supportExtruderCombobox
|
|
||||||
visible: machineExtruderCount.properties.value > 1
|
|
||||||
model: extruderModel
|
|
||||||
|
|
||||||
anchors.top: adhesionCheckBox.bottom
|
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
|
||||||
anchors.left: supportHelperLabel.right
|
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
|
||||||
width: parent.width / 100 * 65
|
|
||||||
|
|
||||||
style: UM.Theme.styles.combobox
|
|
||||||
enabled: base.settingsEnabled
|
|
||||||
property alias _hovered: supportExtruderMouseArea.containsMouse
|
|
||||||
|
|
||||||
currentIndex: supportEnabled.properties.value == "True" ? parseFloat(supportExtruderNr.properties.value) + 1 : 0
|
|
||||||
onActivated: {
|
|
||||||
if(index==0) {
|
|
||||||
supportEnabled.setPropertyValue("value", false);
|
|
||||||
} else {
|
|
||||||
supportEnabled.setPropertyValue("value", true);
|
|
||||||
// Send the extruder nr as a string.
|
|
||||||
supportExtruderNr.setPropertyValue("value", String(index - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: supportExtruderMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
enabled: base.settingsEnabled
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
onEntered:
|
|
||||||
{
|
|
||||||
base.showTooltip(supportExtruderCombobox, Qt.point(-supportExtruderCombobox.x, 0),
|
|
||||||
catalog.i18nc("@label", "Select which extruder to use for support. This will build up supporting structures below the model to prevent the model from sagging or printing in mid air."));
|
|
||||||
}
|
|
||||||
onExited:
|
|
||||||
{
|
|
||||||
base.hideTooltip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: extruderModel
|
id: extruderModel
|
||||||
Component.onCompleted: populateExtruderModel()
|
Component.onCompleted: populateExtruderModel()
|
||||||
}
|
}
|
||||||
|
@ -377,19 +451,18 @@ Item
|
||||||
function populateExtruderModel()
|
function populateExtruderModel()
|
||||||
{
|
{
|
||||||
extruderModel.clear();
|
extruderModel.clear();
|
||||||
|
for(var extruderNumber = 0; extruderNumber < extruders.rowCount() ; extruderNumber++)
|
||||||
|
{
|
||||||
extruderModel.append({
|
extruderModel.append({
|
||||||
text: catalog.i18nc("@label", "Don't print support"),
|
text: extruders.getItem(extruderNumber).name,
|
||||||
color: ""
|
|
||||||
})
|
|
||||||
for(var extruderNumber = 0; extruderNumber < extruders.rowCount() ; extruderNumber++) {
|
|
||||||
extruderModel.append({
|
|
||||||
text: catalog.i18nc("@label", "Print support using %1").arg(extruders.getItem(extruderNumber).name),
|
|
||||||
color: extruders.getItem(extruderNumber).color
|
color: extruders.getItem(extruderNumber).color
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
supportExtruderCombobox.updateCurrentColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: tipsCell
|
id: tipsCell
|
||||||
anchors.top: helpersCell.bottom
|
anchors.top: helpersCell.bottom
|
||||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
|
@ -397,7 +470,8 @@ Item
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
||||||
Label{
|
Label
|
||||||
|
{
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
@ -438,7 +512,7 @@ Item
|
||||||
|
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
key: "support_enable"
|
key: "support_enable"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value", "description" ]
|
||||||
storeIndex: 0
|
storeIndex: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,11 @@ UM.PointingRectangle {
|
||||||
} else {
|
} else {
|
||||||
x = position.x - base.width;
|
x = position.x - base.width;
|
||||||
y = position.y - UM.Theme.getSize("tooltip_arrow_margins").height;
|
y = position.y - UM.Theme.getSize("tooltip_arrow_margins").height;
|
||||||
|
if(y < 0)
|
||||||
|
{
|
||||||
|
position.y += -y;
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
base.opacity = 1;
|
base.opacity = 1;
|
||||||
target = Qt.point(40 , position.y + UM.Theme.getSize("tooltip_arrow_margins").height / 2)
|
target = Qt.point(40 , position.y + UM.Theme.getSize("tooltip_arrow_margins").height / 2)
|
||||||
|
|
223
resources/qml/WorkspaceSummaryDialog.qml
Normal file
223
resources/qml/WorkspaceSummaryDialog.qml
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
// Copyright (c) 2016 Ultimaker B.V.
|
||||||
|
// Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import QtQuick 2.1
|
||||||
|
import QtQuick.Controls 1.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import QtQuick.Window 2.1
|
||||||
|
|
||||||
|
import UM 1.2 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
UM.Dialog
|
||||||
|
{
|
||||||
|
title: catalog.i18nc("@title:window", "Save Project")
|
||||||
|
|
||||||
|
width: 550
|
||||||
|
minimumWidth: 550
|
||||||
|
maximumWidth: 550
|
||||||
|
|
||||||
|
height: 350
|
||||||
|
minimumHeight: 350
|
||||||
|
maximumHeight: 350
|
||||||
|
property int spacerHeight: 10
|
||||||
|
|
||||||
|
property bool dontShowAgain: true
|
||||||
|
|
||||||
|
signal yes();
|
||||||
|
|
||||||
|
|
||||||
|
onClosing:
|
||||||
|
{
|
||||||
|
UM.Preferences.setValue("cura/asked_dialog_on_project_save", true)
|
||||||
|
UM.Preferences.setValue("cura/dialog_on_project_save", !dontShowAgainCheckbox.checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged:
|
||||||
|
{
|
||||||
|
if(visible)
|
||||||
|
{
|
||||||
|
if (UM.Preferences.getValue("cura/asked_dialog_on_project_save"))
|
||||||
|
{
|
||||||
|
dontShowAgain = true
|
||||||
|
} else { dontShowAgain = UM.Preferences.setValue("cura/dialog_on_project_save")}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
UM.SettingDefinitionsModel
|
||||||
|
{
|
||||||
|
id: definitionsModel
|
||||||
|
containerId: Cura.MachineManager.activeDefinitionId
|
||||||
|
showAll: true
|
||||||
|
exclude: ["command_line_settings"]
|
||||||
|
showAncestors: true
|
||||||
|
expanded: ["*"]
|
||||||
|
visibilityHandler: UM.SettingPreferenceVisibilityHandler { }
|
||||||
|
}
|
||||||
|
UM.I18nCatalog
|
||||||
|
{
|
||||||
|
id: catalog;
|
||||||
|
name: "cura";
|
||||||
|
}
|
||||||
|
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 2
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: titleLabel
|
||||||
|
text: catalog.i18nc("@action:title", "Summary - Cura Project")
|
||||||
|
font.pixelSize: 22
|
||||||
|
}
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
id: separator
|
||||||
|
color: "black"
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
}
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Printer settings")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Name")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: Cura.MachineManager.activeMachineName
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Profile settings")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Name")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: Cura.MachineManager.activeQualityName
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Material settings")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater
|
||||||
|
{
|
||||||
|
model: Cura.MachineManager.activeMaterialNames
|
||||||
|
delegate: Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Name")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: modelData
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Item // Spacer
|
||||||
|
{
|
||||||
|
height: spacerHeight
|
||||||
|
width: height
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Setting visibility")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
width: parent.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "Visible settings:")
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(definitionsModel.visibleCount).arg(Cura.MachineManager.totalNumberOfSettings)
|
||||||
|
width: parent.width / 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckBox
|
||||||
|
{
|
||||||
|
id: dontShowAgainCheckbox
|
||||||
|
text: catalog.i18nc("@action:label", "Don't show project summary on save again")
|
||||||
|
checked: dontShowAgain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rightButtons: [
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: cancel_button
|
||||||
|
text: catalog.i18nc("@action:button","Cancel");
|
||||||
|
enabled: true
|
||||||
|
onClicked: close()
|
||||||
|
},
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: ok_button
|
||||||
|
text: catalog.i18nc("@action:button","Save");
|
||||||
|
enabled: true
|
||||||
|
onClicked: {
|
||||||
|
close(); yes() }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -15,5 +15,6 @@ wall_thickness = 0.88
|
||||||
top_bottom_thickness = 0.72
|
top_bottom_thickness = 0.72
|
||||||
infill_sparse_density = 22
|
infill_sparse_density = 22
|
||||||
speed_print = 30
|
speed_print = 30
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 30)
|
||||||
cool_min_layer_time = 5
|
cool_min_layer_time = 5
|
||||||
cool_min_speed = 10
|
cool_min_speed = 10
|
||||||
|
|
|
@ -15,9 +15,9 @@ wall_thickness = 0.7
|
||||||
top_bottom_thickness = 0.75
|
top_bottom_thickness = 0.75
|
||||||
infill_sparse_density = 18
|
infill_sparse_density = 18
|
||||||
speed_print = 60
|
speed_print = 60
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 60)
|
||||||
speed_wall = 50
|
speed_wall = 50
|
||||||
speed_topbottom = 30
|
speed_topbottom = 30
|
||||||
speed_travel = 150
|
speed_travel = 150
|
||||||
speed_layer_0 = 30
|
|
||||||
cool_min_layer_time = 5
|
cool_min_layer_time = 5
|
||||||
cool_min_speed = 10
|
cool_min_speed = 10
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.05
|
||||||
top_bottom_thickness = 0.72
|
top_bottom_thickness = 0.72
|
||||||
infill_sparse_density = 22
|
infill_sparse_density = 22
|
||||||
speed_print = 50
|
speed_print = 50
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 50)
|
||||||
speed_topbottom = 20
|
speed_topbottom = 20
|
||||||
cool_min_layer_time = 5
|
cool_min_layer_time = 5
|
||||||
cool_min_speed = 10
|
cool_min_speed = 10
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.05
|
||||||
top_bottom_thickness = 0.8
|
top_bottom_thickness = 0.8
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 50
|
speed_print = 50
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 50)
|
||||||
speed_topbottom = 20
|
speed_topbottom = 20
|
||||||
cool_min_layer_time = 5
|
cool_min_layer_time = 5
|
||||||
cool_min_speed = 10
|
cool_min_speed = 10
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.59
|
||||||
top_bottom_thickness = 1.2
|
top_bottom_thickness = 1.2
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 55
|
speed_print = 55
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 55)
|
||||||
speed_wall = 40
|
speed_wall = 40
|
||||||
speed_wall_0 = 25
|
speed_wall_0 = 25
|
||||||
speed_topbottom = 20
|
speed_topbottom = 20
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 2.1
|
||||||
top_bottom_thickness = 1.2
|
top_bottom_thickness = 1.2
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 40
|
speed_print = 40
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 40)
|
||||||
speed_wall_0 = 25
|
speed_wall_0 = 25
|
||||||
cool_min_layer_time = 5
|
cool_min_layer_time = 5
|
||||||
cool_min_speed = 10
|
cool_min_speed = 10
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 0.88
|
||||||
top_bottom_thickness = 0.72
|
top_bottom_thickness = 0.72
|
||||||
infill_sparse_density = 22
|
infill_sparse_density = 22
|
||||||
speed_print = 30
|
speed_print = 30
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 30)
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 20
|
cool_fan_speed_min = 20
|
||||||
cool_min_speed = 10
|
cool_min_speed = 10
|
||||||
|
|
|
@ -15,10 +15,10 @@ wall_thickness = 0.7
|
||||||
top_bottom_thickness = 0.75
|
top_bottom_thickness = 0.75
|
||||||
infill_sparse_density = 18
|
infill_sparse_density = 18
|
||||||
speed_print = 55
|
speed_print = 55
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 55)
|
||||||
speed_wall = 40
|
speed_wall = 40
|
||||||
speed_topbottom = 30
|
speed_topbottom = 30
|
||||||
speed_travel = 150
|
speed_travel = 150
|
||||||
speed_layer_0 = 30
|
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 20
|
cool_fan_speed_min = 20
|
||||||
cool_min_speed = 10
|
cool_min_speed = 10
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.05
|
||||||
top_bottom_thickness = 0.72
|
top_bottom_thickness = 0.72
|
||||||
infill_sparse_density = 22
|
infill_sparse_density = 22
|
||||||
speed_print = 45
|
speed_print = 45
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 45)
|
||||||
speed_wall = 30
|
speed_wall = 30
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 20
|
cool_fan_speed_min = 20
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.05
|
||||||
top_bottom_thickness = 0.8
|
top_bottom_thickness = 0.8
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 45
|
speed_print = 45
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 45)
|
||||||
speed_wall = 30
|
speed_wall = 30
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 20
|
cool_fan_speed_min = 20
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.59
|
||||||
top_bottom_thickness = 1.2
|
top_bottom_thickness = 1.2
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 40
|
speed_print = 40
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 40)
|
||||||
speed_infill = 55
|
speed_infill = 55
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 50
|
cool_fan_speed_min = 50
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 2.1
|
||||||
top_bottom_thickness = 1.2
|
top_bottom_thickness = 1.2
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 40
|
speed_print = 40
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 40)
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 50
|
cool_fan_speed_min = 50
|
||||||
cool_min_speed = 15
|
cool_min_speed = 15
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 0.88
|
||||||
top_bottom_thickness = 0.72
|
top_bottom_thickness = 0.72
|
||||||
infill_sparse_density = 22
|
infill_sparse_density = 22
|
||||||
speed_print = 30
|
speed_print = 30
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 30)
|
||||||
cool_min_layer_time = 2
|
cool_min_layer_time = 2
|
||||||
cool_fan_speed_min = 20
|
cool_fan_speed_min = 20
|
||||||
cool_min_speed = 15
|
cool_min_speed = 15
|
||||||
|
|
|
@ -15,9 +15,9 @@ wall_thickness = 0.7
|
||||||
top_bottom_thickness = 0.75
|
top_bottom_thickness = 0.75
|
||||||
infill_sparse_density = 18
|
infill_sparse_density = 18
|
||||||
speed_print = 45
|
speed_print = 45
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 45)
|
||||||
speed_wall = 40
|
speed_wall = 40
|
||||||
speed_travel = 150
|
speed_travel = 150
|
||||||
speed_layer_0 = 30
|
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 80
|
cool_fan_speed_min = 80
|
||||||
cool_min_speed = 10
|
cool_min_speed = 10
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.05
|
||||||
top_bottom_thickness = 0.72
|
top_bottom_thickness = 0.72
|
||||||
infill_sparse_density = 22
|
infill_sparse_density = 22
|
||||||
speed_print = 45
|
speed_print = 45
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 45)
|
||||||
speed_wall = 30
|
speed_wall = 30
|
||||||
cool_min_layer_time = 2
|
cool_min_layer_time = 2
|
||||||
cool_fan_speed_min = 80
|
cool_fan_speed_min = 80
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.05
|
||||||
top_bottom_thickness = 0.8
|
top_bottom_thickness = 0.8
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 45
|
speed_print = 45
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 45)
|
||||||
speed_wall = 30
|
speed_wall = 30
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 80
|
cool_fan_speed_min = 80
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 1.59
|
||||||
top_bottom_thickness = 1.2
|
top_bottom_thickness = 1.2
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 40
|
speed_print = 40
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 40)
|
||||||
cool_min_layer_time = 5
|
cool_min_layer_time = 5
|
||||||
cool_fan_speed_min = 80
|
cool_fan_speed_min = 80
|
||||||
cool_min_speed = 8
|
cool_min_speed = 8
|
||||||
|
|
|
@ -15,6 +15,7 @@ wall_thickness = 2.1
|
||||||
top_bottom_thickness = 1.2
|
top_bottom_thickness = 1.2
|
||||||
infill_sparse_density = 20
|
infill_sparse_density = 20
|
||||||
speed_print = 40
|
speed_print = 40
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 40)
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
cool_fan_speed_min = 80
|
cool_fan_speed_min = 80
|
||||||
cool_min_speed = 8
|
cool_min_speed = 8
|
||||||
|
|
|
@ -20,7 +20,6 @@ raft_surface_thickness = 0.2
|
||||||
raft_surface_line_width = 0.57
|
raft_surface_line_width = 0.57
|
||||||
raft_interface_line_spacing = 1.4
|
raft_interface_line_spacing = 1.4
|
||||||
raft_margin = 15
|
raft_margin = 15
|
||||||
speed_layer_0 = 30
|
|
||||||
raft_airgap = 0.37
|
raft_airgap = 0.37
|
||||||
infill_overlap = 5
|
infill_overlap = 5
|
||||||
layer_height = 0.3
|
layer_height = 0.3
|
||||||
|
@ -40,6 +39,7 @@ line_width = 0.57
|
||||||
layer_0_z_overlap = 0.22
|
layer_0_z_overlap = 0.22
|
||||||
raft_base_line_width = 1.2
|
raft_base_line_width = 1.2
|
||||||
speed_print = 25
|
speed_print = 25
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 50)
|
||||||
support_line_distance = 2.85
|
support_line_distance = 2.85
|
||||||
support_angle = 45
|
support_angle = 45
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
|
|
|
@ -33,6 +33,7 @@ infill_sparse_density = 40
|
||||||
layer_0_z_overlap = 0.22
|
layer_0_z_overlap = 0.22
|
||||||
raft_base_line_width = 1.6
|
raft_base_line_width = 1.6
|
||||||
speed_print = 25
|
speed_print = 25
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 25)
|
||||||
speed_wall_0 = 20
|
speed_wall_0 = 20
|
||||||
support_angle = 45
|
support_angle = 45
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
|
|
|
@ -33,6 +33,7 @@ infill_sparse_density = 40
|
||||||
layer_0_z_overlap = 0.22
|
layer_0_z_overlap = 0.22
|
||||||
raft_base_line_width = 1.6
|
raft_base_line_width = 1.6
|
||||||
speed_print = 30
|
speed_print = 30
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 30)
|
||||||
speed_wall_0 = 20
|
speed_wall_0 = 20
|
||||||
support_angle = 45
|
support_angle = 45
|
||||||
cool_min_layer_time = 3
|
cool_min_layer_time = 3
|
||||||
|
|
|
@ -20,7 +20,6 @@ support_top_distance = 0.5
|
||||||
raft_surface_thickness = 0.2
|
raft_surface_thickness = 0.2
|
||||||
wall_thickness = 2.4
|
wall_thickness = 2.4
|
||||||
raft_margin = 15
|
raft_margin = 15
|
||||||
speed_layer_0 = 30
|
|
||||||
raft_airgap = 0.44
|
raft_airgap = 0.44
|
||||||
infill_overlap = 5
|
infill_overlap = 5
|
||||||
layer_height = 0.2
|
layer_height = 0.2
|
||||||
|
@ -41,6 +40,7 @@ infill_sparse_density = 40
|
||||||
layer_0_z_overlap = 0.25
|
layer_0_z_overlap = 0.25
|
||||||
raft_base_line_width = 1.6
|
raft_base_line_width = 1.6
|
||||||
speed_print = 55
|
speed_print = 55
|
||||||
|
speed_layer_0 = =round(speed_print * 30 / 55)
|
||||||
support_angle = 45
|
support_angle = 45
|
||||||
raft_interface_line_spacing = 1.8
|
raft_interface_line_spacing = 1.8
|
||||||
|
|
||||||
|
|
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