Merge branch 'master' into feature_firmware_updater
|
|
@ -225,7 +225,7 @@ class ThreeMFReader(MeshReader):
|
|||
|
||||
except Exception:
|
||||
Logger.logException("e", "An exception occurred in 3mf reader.")
|
||||
return []
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -85,14 +85,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name="application/x-curaproject+xml",
|
||||
comment="Cura Project File",
|
||||
suffixes=["curaproject.3mf"]
|
||||
)
|
||||
)
|
||||
|
||||
self._supported_extensions = [".3mf"]
|
||||
self._dialog = WorkspaceDialog()
|
||||
self._3mf_mesh_reader = None
|
||||
|
|
@ -726,8 +718,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
nodes = []
|
||||
|
||||
base_file_name = os.path.basename(file_name)
|
||||
if base_file_name.endswith(".curaproject.3mf"):
|
||||
base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
|
||||
self.setWorkspaceName(base_file_name)
|
||||
return nodes
|
||||
|
||||
|
|
@ -944,7 +934,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
root_material_id)
|
||||
|
||||
if material_node is not None and material_node.getContainer() is not None:
|
||||
extruder_stack.material = material_node.getContainer()
|
||||
extruder_stack.material = material_node.getContainer() # type: InstanceContainer
|
||||
|
||||
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
|
||||
# Clear all first
|
||||
|
|
|
|||
|
|
@ -18,11 +18,7 @@ catalog = i18nCatalog("cura")
|
|||
|
||||
|
||||
def getMetaData() -> Dict:
|
||||
# Workaround for osx not supporting double file extensions correctly.
|
||||
if Platform.isOSX():
|
||||
workspace_extension = "3mf"
|
||||
else:
|
||||
workspace_extension = "curaproject.3mf"
|
||||
workspace_extension = "3mf"
|
||||
|
||||
metaData = {}
|
||||
if "3MFReader.ThreeMFReader" in sys.modules:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading 3MF files.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,7 @@ from UM.Platform import Platform
|
|||
i18n_catalog = i18nCatalog("uranium")
|
||||
|
||||
def getMetaData():
|
||||
# Workarround for osx not supporting double file extensions correctly.
|
||||
if Platform.isOSX():
|
||||
workspace_extension = "3mf"
|
||||
else:
|
||||
workspace_extension = "curaproject.3mf"
|
||||
workspace_extension = "3mf"
|
||||
|
||||
metaData = {}
|
||||
|
||||
|
|
@ -36,7 +32,7 @@ def getMetaData():
|
|||
"output": [{
|
||||
"extension": workspace_extension,
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
||||
"mime_type": "application/x-curaproject+xml",
|
||||
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
|
||||
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
||||
}]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for writing 3MF files.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,103 @@
|
|||
[3.5.0]
|
||||
*Monitor page
|
||||
The monitor page of Ultimaker Cura has been remodeled for better consistency with the Cura Connect ‘Print jobs’ interface. This means less switching between interfaces, and more control from within Ultimaker Cura.
|
||||
|
||||
*Open recent projects
|
||||
Project files can now be found in the ‘Open Recent’ menu.
|
||||
|
||||
*New tool hotkeys
|
||||
New hotkeys have been assigned for quick toggling between the translate (T), scale (S), rotate (R) and mirror (M) tools.
|
||||
|
||||
*Project files use 3MF only
|
||||
A 3MF extension is now used for project files. The ‘.curaproject’ extension is no longer used.
|
||||
|
||||
*Camera maximum zoom
|
||||
The maximum zoom has been adjusted to scale with the size of the selected printer. This fixes third-party printers with huge build volumes to be correctly visible.
|
||||
|
||||
*Corrected width of layer number box
|
||||
The layer number indicator in the layer view now displays numbers above 999 correctly.
|
||||
|
||||
*Materials preferences
|
||||
This screen has been redesigned to improve user experience. Materials can now be set as a favorites, so they can be easily accessed in the material selection panel at the top-right of the screen.
|
||||
|
||||
*Installed packages checkmark
|
||||
Packages that are already installed in the Toolbox are now have a checkmark for easy reference.
|
||||
|
||||
*Mac OSX save dialog
|
||||
The save dialog has been restored to its native behavior and bugs have been fixed.
|
||||
|
||||
*Removed .gz extension
|
||||
Saving compressed g-code files from the save dialog has been removed because of incompatibility with MacOS. If sending jobs over Wi-Fi, g-code is still compressed.
|
||||
|
||||
*Updates to Chinese translations
|
||||
Improved and updated Chinese translations. Contributed by MarmaladeForMeat.
|
||||
|
||||
*Save project
|
||||
Saving the project no longer triggers the project to reslice.
|
||||
|
||||
*File menu
|
||||
The Save option in the file menu now saves project files. The export option now saves other types of files, such as STL.
|
||||
|
||||
*Improved processing of overhang walls
|
||||
Overhang walls are detected and printed with different speeds. It will not start a perimeter on an overhanging wall. The quality of overhanging walls may be improved by printing those at a different speed. Contributed by smartavionics.
|
||||
|
||||
*Prime tower reliability
|
||||
The prime tower has been improved for better reliability. This is especially useful when printing with two materials that do not adhere well.
|
||||
|
||||
*Support infill line direction
|
||||
The support infill lines can now be rotated to increase the supporting capabilities and reduce artifacts on the model. This setting rotates existing patterns, like triangle support infill. Contributed by fieldOfView.
|
||||
|
||||
*Minimum polygon circumference
|
||||
Polygons in sliced layers that have a circumference smaller than the setting value will be filtered out. Lower values lead to higher resolution meshes at the cost of increased slicing time. This setting is ideal for very tiny prints with a lot of detail, or for SLA printers. Contributed by cubiq.
|
||||
|
||||
*Initial layer support line distance
|
||||
This setting enables the user to reduce or increase the density of the support initial layer in order to increase or reduce adhesion to the build plate and the overall strength.
|
||||
|
||||
*Extra infill wall line count
|
||||
Adds extra walls around infill. Contributed by BagelOrb.
|
||||
|
||||
*Multiply infill
|
||||
Creates multiple infill lines on the same pattern for sturdier infill. Contributed by BagelOrb.
|
||||
|
||||
*Connected infill polygons
|
||||
Connecting infill lines now also works with concentric and cross infill patterns. The benefit would be stronger infill and more consistent material flow/saving retractions. Contributed by BagelOrb.
|
||||
|
||||
*Fan speed override
|
||||
New setting to modify the fan speed of supported areas. This setting can be found in Support settings > Fan Speed Override when support is enabled. Contributed by smartavionics.
|
||||
|
||||
*Minimum wall flow
|
||||
New setting to define a minimum flow for thin printed walls. Contributed by smartavionics.
|
||||
|
||||
*Custom support plugin
|
||||
A tool downloadable from the toolbox, similar to the support blocker, that adds cubes of support to the model manually by clicking parts of it. Contributed by Lokster.
|
||||
|
||||
*Quickly toggle autoslicing
|
||||
Adds a pause/play button to the progress bar to quickly toggle autoslicing. Contributed by fieldOfview.
|
||||
|
||||
*Cura-DuetRRFPlugin
|
||||
Adds output devices for a Duet RepRapFirmware printer: "Print", "Simulate", and "Upload". Contributed by Kriechi.
|
||||
|
||||
*Dremel 3D20
|
||||
This plugin adds the Dremel printer to Ultimaker Cura. Contributed by Kriechi.
|
||||
|
||||
*Bug fixes
|
||||
- Removed extra M109 commands. Older versions would generate superfluous M109 commands. This has been fixed for better temperature stability when printing.
|
||||
- Fixed minor mesh handling bugs. A few combinations of modifier meshes now lead to expected behavior.
|
||||
- Removed unnecessary travels. Connected infill lines are now always printed completely connected, without unnecessary travel moves.
|
||||
- Removed concentric 3D infill. This infill type has been removed due to lack of reliability.
|
||||
- Extra skin wall count. Fixed an issue that caused extra print moves with this setting enabled.
|
||||
- Concentric skin. Small gaps in concentric skin are now filled correctly.
|
||||
- Order of printed models. The order of a large batch of printed models is now more consistent, instead of random.
|
||||
|
||||
*Third party printers
|
||||
- TiZYX
|
||||
- Winbo
|
||||
- Tevo Tornado
|
||||
- Creality CR-10S
|
||||
- Wanhao Duplicator
|
||||
- Deltacomb (update)
|
||||
- Dacoma (update)
|
||||
|
||||
[3.4.1]
|
||||
*Bug fixes
|
||||
- Fixed an issue that would occasionally cause an unnecessary extra skin wall to be printed, which increased print time.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Shows changes since latest checked version.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,8 +179,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# This is useful for debugging and used to actually start the engine.
|
||||
# \return list of commands and args / parameters.
|
||||
def getEngineCommand(self) -> List[str]:
|
||||
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
|
||||
command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
|
||||
command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), ""]
|
||||
|
||||
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
|
||||
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
|
||||
|
|
@ -343,7 +342,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
if not self._global_container_stack:
|
||||
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
|
||||
return
|
||||
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
error_keys = [] #type: List[str]
|
||||
for extruder in extruders:
|
||||
error_keys.extend(extruder.getErrorKeys())
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
# Find out colors per extruder
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
manager = ExtruderManager.getInstance()
|
||||
extruders = list(manager.getMachineExtruders(global_container_stack.getId()))
|
||||
extruders = manager.getActiveExtruderStacks()
|
||||
if extruders:
|
||||
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
|
||||
for extruder in extruders:
|
||||
|
|
|
|||
|
|
@ -220,8 +220,10 @@ class StartSliceJob(Job):
|
|||
stack = global_stack
|
||||
skip_group = False
|
||||
for node in group:
|
||||
# Only check if the printing extruder is enabled for printing meshes
|
||||
is_non_printing_mesh = node.callDecoration("evaluateIsNonPrintingMesh")
|
||||
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||
if not extruders_enabled[extruder_position]:
|
||||
if not is_non_printing_mesh and not extruders_enabled[extruder_position]:
|
||||
skip_group = True
|
||||
has_model_with_disabled_extruders = True
|
||||
associated_disabled_extruders.add(extruder_position)
|
||||
|
|
@ -331,7 +333,7 @@ class StartSliceJob(Job):
|
|||
"-1": self._buildReplacementTokens(global_stack)
|
||||
}
|
||||
|
||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
|
||||
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
|
||||
|
||||
|
|
@ -438,8 +440,7 @@ class StartSliceJob(Job):
|
|||
Job.yieldThread()
|
||||
|
||||
# Ensure that the engine is aware what the build extruder is.
|
||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
changed_setting_keys.add("extruder_nr")
|
||||
changed_setting_keys.add("extruder_nr")
|
||||
|
||||
# Get values for all changed settings
|
||||
for key in changed_setting_keys:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "CuraEngine Backend",
|
||||
"author": "Ultimaker B.V.",
|
||||
"description": "Provides the link to the CuraEngine slicing backend.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"version": "1.0.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for importing Cura profiles.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for exporting Cura profiles.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog":"cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Checks for firmware updates.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,22 @@
|
|||
import gzip
|
||||
|
||||
from UM.Mesh.MeshReader import MeshReader #The class we're extending/implementing.
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType #To add the .gcode.gz files to the MIME type database.
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
|
||||
## A file reader that reads gzipped g-code.
|
||||
#
|
||||
# If you're zipping g-code, you might as well use gzip!
|
||||
class GCodeGzReader(MeshReader):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-cura-compressed-gcode-file",
|
||||
comment = "Cura Compressed GCode File",
|
||||
suffixes = ["gcode.gz"]
|
||||
)
|
||||
)
|
||||
self._supported_extensions = [".gcode.gz"]
|
||||
|
||||
def _read(self, file_name):
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Reads g-code from a compressed archive.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ catalog = i18nCatalog("cura")
|
|||
#
|
||||
# If you're zipping g-code, you might as well use gzip!
|
||||
class GCodeGzWriter(MeshWriter):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(add_to_recent_files = False)
|
||||
|
||||
## Writes the gzipped g-code to a stream.
|
||||
#
|
||||
# Note that even though the function accepts a collection of nodes, the
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ def getMetaData():
|
|||
"extension": file_extension,
|
||||
"description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
|
||||
"mime_type": "application/gzip",
|
||||
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode
|
||||
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode,
|
||||
"hide_in_file_dialog": True,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Writes g-code to a compressed archive.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for importing profiles from g-code files.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ class FlavorParser:
|
|||
## For showing correct x, y offsets for each extruder
|
||||
def _extruderOffsets(self) -> Dict[int, List[float]]:
|
||||
result = {}
|
||||
for extruder in ExtruderManager.getInstance().getExtruderStacks():
|
||||
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
result[int(extruder.getMetaData().get("position", "0"))] = [
|
||||
extruder.getProperty("machine_nozzle_offset_x", "value"),
|
||||
extruder.getProperty("machine_nozzle_offset_y", "value")]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright (c) 2017 Aleph Objects, Inc.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.FileHandler.FileReader import FileReader
|
||||
|
|
@ -11,13 +12,7 @@ catalog = i18nCatalog("cura")
|
|||
from . import MarlinFlavorParser, RepRapFlavorParser
|
||||
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-cura-gcode-file",
|
||||
comment = "Cura GCode File",
|
||||
suffixes = ["gcode", "gcode.gz"]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Class for loading and parsing G-code files
|
||||
|
|
@ -29,7 +24,15 @@ class GCodeReader(MeshReader):
|
|||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-cura-gcode-file",
|
||||
comment = "Cura GCode File",
|
||||
suffixes = ["gcode"]
|
||||
)
|
||||
)
|
||||
self._supported_extensions = [".gcode", ".g"]
|
||||
|
||||
self._flavor_reader = None
|
||||
|
||||
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Victor Larchenko",
|
||||
"version": "1.0.0",
|
||||
"description": "Allows loading and displaying G-code files.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class GCodeWriter(MeshWriter):
|
|||
_setting_keyword = ";SETTING_"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
super().__init__(add_to_recent_files = False)
|
||||
|
||||
self._application = Application.getInstance()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Writes g-code to a file.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ Cura.MachineAction
|
|||
property var extrudersModel: Cura.ExtrudersModel{}
|
||||
property int extruderTabsCount: 0
|
||||
|
||||
property var activeMachineId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.id : ""
|
||||
|
||||
Connections
|
||||
{
|
||||
target: base.extrudersModel
|
||||
|
|
@ -511,7 +513,7 @@ Cura.MachineAction
|
|||
}
|
||||
return "";
|
||||
}
|
||||
return Cura.MachineManager.activeMachineId;
|
||||
return base.activeMachineId
|
||||
}
|
||||
key: settingKey
|
||||
watchedProperties: [ "value", "description" ]
|
||||
|
|
@ -564,7 +566,7 @@ Cura.MachineAction
|
|||
}
|
||||
return "";
|
||||
}
|
||||
return Cura.MachineManager.activeMachineId;
|
||||
return base.activeMachineId
|
||||
}
|
||||
key: settingKey
|
||||
watchedProperties: [ "value", "description" ]
|
||||
|
|
@ -655,7 +657,7 @@ Cura.MachineAction
|
|||
}
|
||||
return "";
|
||||
}
|
||||
return Cura.MachineManager.activeMachineId;
|
||||
return base.activeMachineId
|
||||
}
|
||||
key: settingKey
|
||||
watchedProperties: [ "value", "options", "description" ]
|
||||
|
|
@ -754,7 +756,7 @@ Cura.MachineAction
|
|||
}
|
||||
return "";
|
||||
}
|
||||
return Cura.MachineManager.activeMachineId;
|
||||
return base.activeMachineId
|
||||
}
|
||||
key: settingKey
|
||||
watchedProperties: [ "value", "description" ]
|
||||
|
|
@ -879,7 +881,7 @@ Cura.MachineAction
|
|||
{
|
||||
id: machineExtruderCountProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: base.activeMachineId
|
||||
key: "machine_extruder_count"
|
||||
watchedProperties: [ "value", "description" ]
|
||||
storeIndex: manager.containerIndex
|
||||
|
|
@ -889,7 +891,7 @@ Cura.MachineAction
|
|||
{
|
||||
id: machineHeadPolygonProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: base.activeMachineId
|
||||
key: "machine_head_with_fans_polygon"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: manager.containerIndex
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "fieldOfView",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "Model Checker",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "0.1",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides a monitor stage in Cura.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ Item {
|
|||
|
||||
width: childrenRect.width;
|
||||
height: childrenRect.height;
|
||||
|
||||
property var all_categories_except_support: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
|
||||
"travel", "cooling", "platform_adhesion", "dual", "meshfix", "blackmagic", "experimental"]
|
||||
|
||||
|
|
@ -45,7 +44,7 @@ Item {
|
|||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: meshTypePropertyProvider
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStack: Cura.MachineManager.activeMachine
|
||||
watchedProperties: [ "enabled" ]
|
||||
}
|
||||
|
||||
|
|
@ -518,7 +517,7 @@ Item {
|
|||
{
|
||||
id: machineExtruderCount
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStack: Cura.MachineManager.activeMachine
|
||||
key: "machine_extruder_count"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
|
|
@ -528,7 +527,7 @@ Item {
|
|||
{
|
||||
id: printSequencePropertyProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStack: Cura.MachineManager.activeMachine
|
||||
key: "print_sequence"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the Per Model Settings.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
|
||||
# Copyright (c) 2018 Jaime van Kessel, Ultimaker B.V.
|
||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
|
@ -260,6 +261,9 @@ class PostProcessingPlugin(QObject, Extension):
|
|||
# Create the plugin dialog component
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
|
||||
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
if self._view is None:
|
||||
Logger.log("e", "Not creating PostProcessing button near save button because the QML component failed to be created.")
|
||||
return
|
||||
Logger.log("d", "Post processing view created.")
|
||||
|
||||
# Create the save button component
|
||||
|
|
@ -269,6 +273,9 @@ class PostProcessingPlugin(QObject, Extension):
|
|||
def showPopup(self):
|
||||
if self._view is None:
|
||||
self._createView()
|
||||
if self._view is None:
|
||||
Logger.log("e", "Not creating PostProcessing window since the QML component failed to be created.")
|
||||
return
|
||||
self._view.show()
|
||||
|
||||
## Property changed: trigger re-slice
|
||||
|
|
|
|||
|
|
@ -384,7 +384,7 @@ UM.Dialog
|
|||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: inheritStackProvider
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStack: Cura.MachineManager.activeMachine
|
||||
key: model.key ? model.key : "None"
|
||||
watchedProperties: [ "limit_to_extruder" ]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "Post Processing",
|
||||
"author": "Ultimaker",
|
||||
"version": "2.2",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"description": "Extension that allows for user created scripts for post processing",
|
||||
"catalog": "cura"
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
from ..Script import Script
|
||||
|
||||
class PauseAtHeightRepRapFirmwareDuet(Script):
|
||||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name": "Pause at height for RepRapFirmware DuetWifi / Duet Ethernet / Duet Maestro",
|
||||
"key": "PauseAtHeightRepRapFirmwareDuet",
|
||||
"metadata": {},
|
||||
"version": 2,
|
||||
"settings":
|
||||
{
|
||||
"pause_height":
|
||||
{
|
||||
"label": "Pause height",
|
||||
"description": "At what height should the pause occur",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 5.0
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
def execute(self, data):
|
||||
current_z = 0.
|
||||
pause_z = self.getSettingValueByKey("pause_height")
|
||||
|
||||
layers_started = False
|
||||
for layer_number, layer in enumerate(data):
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if ";LAYER:0" in line:
|
||||
layers_started = True
|
||||
continue
|
||||
|
||||
if not layers_started:
|
||||
continue
|
||||
|
||||
if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
|
||||
current_z = self.getValue(line, 'Z')
|
||||
if current_z != None:
|
||||
if current_z >= pause_z:
|
||||
prepend_gcode = ";TYPE:CUSTOM\n"
|
||||
prepend_gcode += "; -- Pause at height (%.2f mm) --\n" % pause_z
|
||||
prepend_gcode += self.putValue(M = 226) + "\n"
|
||||
layer = prepend_gcode + layer
|
||||
|
||||
data[layer_number] = layer # Override the data of this layer with the modified data
|
||||
return data
|
||||
break
|
||||
return data
|
||||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides a prepare stage in Cura.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"description": "Provides removable drive hotplugging and writing support.",
|
||||
"version": "1.0.0",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import QtQuick.Controls.Styles 1.1
|
|||
import UM 1.0 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item {
|
||||
Item
|
||||
{
|
||||
id: sliderRoot
|
||||
|
||||
// handle properties
|
||||
|
|
@ -39,40 +40,49 @@ Item {
|
|||
property real lowerValue: minimumValue
|
||||
|
||||
property bool layersVisible: true
|
||||
property bool manuallyChanged: true // Indicates whether the value was changed manually or during simulation
|
||||
|
||||
function getUpperValueFromSliderHandle() {
|
||||
function getUpperValueFromSliderHandle()
|
||||
{
|
||||
return upperHandle.getValue()
|
||||
}
|
||||
|
||||
function setUpperValue(value) {
|
||||
function setUpperValue(value)
|
||||
{
|
||||
upperHandle.setValue(value)
|
||||
updateRangeHandle()
|
||||
}
|
||||
|
||||
function getLowerValueFromSliderHandle() {
|
||||
function getLowerValueFromSliderHandle()
|
||||
{
|
||||
return lowerHandle.getValue()
|
||||
}
|
||||
|
||||
function setLowerValue(value) {
|
||||
function setLowerValue(value)
|
||||
{
|
||||
lowerHandle.setValue(value)
|
||||
updateRangeHandle()
|
||||
}
|
||||
|
||||
function updateRangeHandle() {
|
||||
function updateRangeHandle()
|
||||
{
|
||||
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
|
||||
}
|
||||
|
||||
// set the active handle to show only one label at a time
|
||||
function setActiveHandle(handle) {
|
||||
function setActiveHandle(handle)
|
||||
{
|
||||
activeHandle = handle
|
||||
}
|
||||
|
||||
function normalizeValue(value) {
|
||||
function normalizeValue(value)
|
||||
{
|
||||
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
||||
}
|
||||
|
||||
// slider track
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
id: track
|
||||
|
||||
width: sliderRoot.trackThickness
|
||||
|
|
@ -86,7 +96,8 @@ Item {
|
|||
}
|
||||
|
||||
// Range handle
|
||||
Item {
|
||||
Item
|
||||
{
|
||||
id: rangeHandle
|
||||
|
||||
y: upperHandle.y + upperHandle.height
|
||||
|
|
@ -96,7 +107,9 @@ Item {
|
|||
visible: sliderRoot.layersVisible
|
||||
|
||||
// set the new value when dragging
|
||||
function onHandleDragged () {
|
||||
function onHandleDragged()
|
||||
{
|
||||
sliderRoot.manuallyChanged = true
|
||||
|
||||
upperHandle.y = y - upperHandle.height
|
||||
lowerHandle.y = y + height
|
||||
|
|
@ -109,7 +122,14 @@ Item {
|
|||
UM.SimulationView.setMinimumLayer(lowerValue)
|
||||
}
|
||||
|
||||
function setValue (value) {
|
||||
function setValueManually(value)
|
||||
{
|
||||
sliderRoot.manuallyChanged = true
|
||||
upperHandle.setValue(value)
|
||||
}
|
||||
|
||||
function setValue(value)
|
||||
{
|
||||
var range = sliderRoot.upperValue - sliderRoot.lowerValue
|
||||
value = Math.min(value, sliderRoot.maximumValue)
|
||||
value = Math.max(value, sliderRoot.minimumValue + range)
|
||||
|
|
@ -118,17 +138,20 @@ Item {
|
|||
UM.SimulationView.setMinimumLayer(value - range)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
|
||||
height: parent.height + sliderRoot.handleSize
|
||||
anchors.centerIn: parent
|
||||
color: sliderRoot.rangeHandleColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
drag {
|
||||
drag
|
||||
{
|
||||
target: parent
|
||||
axis: Drag.YAxis
|
||||
minimumY: upperHandle.height
|
||||
|
|
@ -139,7 +162,8 @@ Item {
|
|||
onPressed: sliderRoot.setActiveHandle(rangeHandle)
|
||||
}
|
||||
|
||||
SimulationSliderLabel {
|
||||
SimulationSliderLabel
|
||||
{
|
||||
id: rangleHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
|
|
@ -152,12 +176,13 @@ Item {
|
|||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.upperValue
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: rangeHandle.setValue // connect callback functions
|
||||
setValue: rangeHandle.setValueManually // connect callback functions
|
||||
}
|
||||
}
|
||||
|
||||
// Upper handle
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
id: upperHandle
|
||||
|
||||
y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
|
||||
|
|
@ -168,10 +193,13 @@ Item {
|
|||
color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
|
||||
visible: sliderRoot.layersVisible
|
||||
|
||||
function onHandleDragged () {
|
||||
function onHandleDragged()
|
||||
{
|
||||
sliderRoot.manuallyChanged = true
|
||||
|
||||
// don't allow the lower handle to be heigher than the upper handle
|
||||
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) {
|
||||
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize)
|
||||
{
|
||||
lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
|
||||
}
|
||||
|
||||
|
|
@ -183,15 +211,23 @@ Item {
|
|||
}
|
||||
|
||||
// get the upper value based on the slider position
|
||||
function getValue () {
|
||||
function getValue()
|
||||
{
|
||||
var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
|
||||
result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
|
||||
result = sliderRoot.roundValues ? Math.round(result) : result
|
||||
return result
|
||||
}
|
||||
|
||||
function setValueManually(value)
|
||||
{
|
||||
sliderRoot.manuallyChanged = true
|
||||
upperHandle.setValue(value)
|
||||
}
|
||||
|
||||
// set the slider position based on the upper value
|
||||
function setValue (value) {
|
||||
function setValue(value)
|
||||
{
|
||||
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||
value = sliderRoot.normalizeValue(value)
|
||||
|
||||
|
|
@ -209,10 +245,12 @@ Item {
|
|||
Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||
|
||||
// dragging
|
||||
MouseArea {
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
drag {
|
||||
drag
|
||||
{
|
||||
target: parent
|
||||
axis: Drag.YAxis
|
||||
minimumY: 0
|
||||
|
|
@ -220,13 +258,15 @@ Item {
|
|||
}
|
||||
|
||||
onPositionChanged: parent.onHandleDragged()
|
||||
onPressed: {
|
||||
onPressed:
|
||||
{
|
||||
sliderRoot.setActiveHandle(upperHandle)
|
||||
upperHandleLabel.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
SimulationSliderLabel {
|
||||
SimulationSliderLabel
|
||||
{
|
||||
id: upperHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
|
|
@ -239,12 +279,13 @@ Item {
|
|||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.upperValue
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: upperHandle.setValue // connect callback functions
|
||||
setValue: upperHandle.setValueManually // connect callback functions
|
||||
}
|
||||
}
|
||||
|
||||
// Lower handle
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
id: lowerHandle
|
||||
|
||||
y: sliderRoot.height - sliderRoot.handleSize
|
||||
|
|
@ -256,10 +297,13 @@ Item {
|
|||
|
||||
visible: sliderRoot.layersVisible
|
||||
|
||||
function onHandleDragged () {
|
||||
function onHandleDragged()
|
||||
{
|
||||
sliderRoot.manuallyChanged = true
|
||||
|
||||
// don't allow the upper handle to be lower than the lower handle
|
||||
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) {
|
||||
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize)
|
||||
{
|
||||
upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
|
||||
}
|
||||
|
||||
|
|
@ -271,15 +315,24 @@ Item {
|
|||
}
|
||||
|
||||
// get the lower value from the current slider position
|
||||
function getValue () {
|
||||
function getValue()
|
||||
{
|
||||
var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
|
||||
result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
|
||||
result = sliderRoot.roundValues ? Math.round(result) : result
|
||||
return result
|
||||
}
|
||||
|
||||
function setValueManually(value)
|
||||
{
|
||||
sliderRoot.manuallyChanged = true
|
||||
lowerHandle.setValue(value)
|
||||
}
|
||||
|
||||
// set the slider position based on the lower value
|
||||
function setValue (value) {
|
||||
function setValue(value)
|
||||
{
|
||||
|
||||
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||
value = sliderRoot.normalizeValue(value)
|
||||
|
||||
|
|
@ -297,10 +350,12 @@ Item {
|
|||
Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||
|
||||
// dragging
|
||||
MouseArea {
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
drag {
|
||||
drag
|
||||
{
|
||||
target: parent
|
||||
axis: Drag.YAxis
|
||||
minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
|
||||
|
|
@ -308,13 +363,15 @@ Item {
|
|||
}
|
||||
|
||||
onPositionChanged: parent.onHandleDragged()
|
||||
onPressed: {
|
||||
onPressed:
|
||||
{
|
||||
sliderRoot.setActiveHandle(lowerHandle)
|
||||
lowerHandleLabel.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
SimulationSliderLabel {
|
||||
SimulationSliderLabel
|
||||
{
|
||||
id: lowerHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
|
|
@ -327,7 +384,7 @@ Item {
|
|||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.lowerValue
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: lowerHandle.setValue // connect callback functions
|
||||
setValue: lowerHandle.setValueManually // connect callback functions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,8 @@ import QtQuick.Controls.Styles 1.1
|
|||
import UM 1.0 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item {
|
||||
Item
|
||||
{
|
||||
id: sliderRoot
|
||||
|
||||
// handle properties
|
||||
|
|
@ -34,26 +35,32 @@ Item {
|
|||
property real handleValue: maximumValue
|
||||
|
||||
property bool pathsVisible: true
|
||||
property bool manuallyChanged: true // Indicates whether the value was changed manually or during simulation
|
||||
|
||||
function getHandleValueFromSliderHandle () {
|
||||
function getHandleValueFromSliderHandle()
|
||||
{
|
||||
return handle.getValue()
|
||||
}
|
||||
|
||||
function setHandleValue (value) {
|
||||
function setHandleValue(value)
|
||||
{
|
||||
handle.setValue(value)
|
||||
updateRangeHandle()
|
||||
}
|
||||
|
||||
function updateRangeHandle () {
|
||||
function updateRangeHandle()
|
||||
{
|
||||
rangeHandle.width = handle.x - sliderRoot.handleSize
|
||||
}
|
||||
|
||||
function normalizeValue(value) {
|
||||
function normalizeValue(value)
|
||||
{
|
||||
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
||||
}
|
||||
|
||||
// slider track
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
id: track
|
||||
|
||||
width: sliderRoot.width - sliderRoot.handleSize
|
||||
|
|
@ -67,7 +74,8 @@ Item {
|
|||
}
|
||||
|
||||
// Progress indicator
|
||||
Item {
|
||||
Item
|
||||
{
|
||||
id: rangeHandle
|
||||
|
||||
x: handle.width
|
||||
|
|
@ -76,7 +84,8 @@ Item {
|
|||
anchors.verticalCenter: sliderRoot.verticalCenter
|
||||
visible: sliderRoot.pathsVisible
|
||||
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
|
||||
width: parent.width + sliderRoot.handleSize
|
||||
anchors.centerIn: parent
|
||||
|
|
@ -85,7 +94,8 @@ Item {
|
|||
}
|
||||
|
||||
// Handle
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
id: handle
|
||||
|
||||
x: sliderRoot.handleSize
|
||||
|
|
@ -96,7 +106,9 @@ Item {
|
|||
color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor
|
||||
visible: sliderRoot.pathsVisible
|
||||
|
||||
function onHandleDragged () {
|
||||
function onHandleDragged()
|
||||
{
|
||||
sliderRoot.manuallyChanged = true
|
||||
|
||||
// update the range handle
|
||||
sliderRoot.updateRangeHandle()
|
||||
|
|
@ -106,15 +118,23 @@ Item {
|
|||
}
|
||||
|
||||
// get the value based on the slider position
|
||||
function getValue () {
|
||||
function getValue()
|
||||
{
|
||||
var result = x / (sliderRoot.width - sliderRoot.handleSize)
|
||||
result = result * sliderRoot.maximumValue
|
||||
result = sliderRoot.roundValues ? Math.round(result) : result
|
||||
return result
|
||||
}
|
||||
|
||||
function setValueManually(value)
|
||||
{
|
||||
sliderRoot.manuallyChanged = true
|
||||
handle.setValue(value)
|
||||
}
|
||||
|
||||
// set the slider position based on the value
|
||||
function setValue (value) {
|
||||
function setValue(value)
|
||||
{
|
||||
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||
value = sliderRoot.normalizeValue(value)
|
||||
|
||||
|
|
@ -132,23 +152,23 @@ Item {
|
|||
Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||
|
||||
// dragging
|
||||
MouseArea {
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
drag {
|
||||
drag
|
||||
{
|
||||
target: parent
|
||||
axis: Drag.XAxis
|
||||
minimumX: 0
|
||||
maximumX: sliderRoot.width - sliderRoot.handleSize
|
||||
}
|
||||
onPressed: {
|
||||
handleLabel.forceActiveFocus()
|
||||
}
|
||||
|
||||
onPressed: handleLabel.forceActiveFocus()
|
||||
onPositionChanged: parent.onHandleDragged()
|
||||
}
|
||||
|
||||
SimulationSliderLabel {
|
||||
SimulationSliderLabel
|
||||
{
|
||||
id: handleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
|
|
@ -162,7 +182,7 @@ Item {
|
|||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.handleValue
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: handle.setValue // connect callback functions
|
||||
setValue: handle.setValueManually // connect callback functions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,12 +44,11 @@ UM.PointingRectangle {
|
|||
id: valueLabel
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
|
||||
verticalCenter: parent.verticalCenter
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
width: maximumValue.toString().length * 12 * screenScaleFactor
|
||||
width: (maximumValue.toString().length + 1) * 10 * screenScaleFactor
|
||||
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
|
||||
horizontalAlignment: TextInput.AlignRight
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,13 @@ from UM.Platform import Platform
|
|||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Resources import Resources
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Signal import Signal
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.View.GL.OpenGLContext import OpenGLContext
|
||||
|
||||
|
||||
from UM.View.View import View
|
||||
from UM.i18n import i18nCatalog
|
||||
from cura.Scene.ConvexHullNode import ConvexHullNode
|
||||
|
|
@ -30,11 +33,20 @@ from cura.CuraApplication import CuraApplication
|
|||
from .NozzleNode import NozzleNode
|
||||
from .SimulationPass import SimulationPass
|
||||
from .SimulationViewProxy import SimulationViewProxy
|
||||
import numpy
|
||||
import os.path
|
||||
|
||||
from typing import Optional, TYPE_CHECKING, List
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Scene import Scene
|
||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||
from UM.View.RenderPass import RenderPass
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
import numpy
|
||||
import os.path
|
||||
|
||||
## View used to display g-code paths.
|
||||
class SimulationView(View):
|
||||
|
|
@ -44,7 +56,7 @@ class SimulationView(View):
|
|||
LAYER_VIEW_TYPE_FEEDRATE = 2
|
||||
LAYER_VIEW_TYPE_THICKNESS = 3
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._max_layers = 0
|
||||
|
|
@ -64,21 +76,21 @@ class SimulationView(View):
|
|||
self._busy = False
|
||||
self._simulation_running = False
|
||||
|
||||
self._ghost_shader = None
|
||||
self._layer_pass = None
|
||||
self._composite_pass = None
|
||||
self._ghost_shader = None # type: Optional["ShaderProgram"]
|
||||
self._layer_pass = None # type: Optional[SimulationPass]
|
||||
self._composite_pass = None # type: Optional[RenderPass]
|
||||
self._old_layer_bindings = None
|
||||
self._simulationview_composite_shader = None
|
||||
self._simulationview_composite_shader = None # type: Optional["ShaderProgram"]
|
||||
self._old_composite_shader = None
|
||||
|
||||
self._global_container_stack = None
|
||||
self._global_container_stack = None # type: Optional[ContainerStack]
|
||||
self._proxy = SimulationViewProxy()
|
||||
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
|
||||
|
||||
self._resetSettings()
|
||||
self._legend_items = None
|
||||
self._show_travel_moves = False
|
||||
self._nozzle_node = None
|
||||
self._nozzle_node = None # type: Optional[NozzleNode]
|
||||
|
||||
Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5)
|
||||
Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False)
|
||||
|
|
@ -102,29 +114,29 @@ class SimulationView(View):
|
|||
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
|
||||
title = catalog.i18nc("@info:title", "Simulation View"))
|
||||
|
||||
def _evaluateCompatibilityMode(self):
|
||||
def _evaluateCompatibilityMode(self) -> bool:
|
||||
return OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode"))
|
||||
|
||||
def _resetSettings(self):
|
||||
self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
|
||||
def _resetSettings(self) -> None:
|
||||
self._layer_view_type = 0 # type: int # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
|
||||
self._extruder_count = 0
|
||||
self._extruder_opacity = [1.0, 1.0, 1.0, 1.0]
|
||||
self._show_travel_moves = 0
|
||||
self._show_helpers = 1
|
||||
self._show_skin = 1
|
||||
self._show_infill = 1
|
||||
self._show_travel_moves = False
|
||||
self._show_helpers = True
|
||||
self._show_skin = True
|
||||
self._show_infill = True
|
||||
self.resetLayerData()
|
||||
|
||||
def getActivity(self):
|
||||
def getActivity(self) -> bool:
|
||||
return self._activity
|
||||
|
||||
def setActivity(self, activity):
|
||||
def setActivity(self, activity: bool) -> None:
|
||||
if self._activity == activity:
|
||||
return
|
||||
self._activity = activity
|
||||
self.activityChanged.emit()
|
||||
|
||||
def getSimulationPass(self):
|
||||
def getSimulationPass(self) -> SimulationPass:
|
||||
if not self._layer_pass:
|
||||
# Currently the RenderPass constructor requires a size > 0
|
||||
# This should be fixed in RenderPass's constructor.
|
||||
|
|
@ -133,30 +145,30 @@ class SimulationView(View):
|
|||
self._layer_pass.setSimulationView(self)
|
||||
return self._layer_pass
|
||||
|
||||
def getCurrentLayer(self):
|
||||
def getCurrentLayer(self) -> int:
|
||||
return self._current_layer_num
|
||||
|
||||
def getMinimumLayer(self):
|
||||
def getMinimumLayer(self) -> int:
|
||||
return self._minimum_layer_num
|
||||
|
||||
def getMaxLayers(self):
|
||||
def getMaxLayers(self) -> int:
|
||||
return self._max_layers
|
||||
|
||||
def getCurrentPath(self):
|
||||
def getCurrentPath(self) -> int:
|
||||
return self._current_path_num
|
||||
|
||||
def getMinimumPath(self):
|
||||
def getMinimumPath(self) -> int:
|
||||
return self._minimum_path_num
|
||||
|
||||
def getMaxPaths(self):
|
||||
def getMaxPaths(self) -> int:
|
||||
return self._max_paths
|
||||
|
||||
def getNozzleNode(self):
|
||||
def getNozzleNode(self) -> NozzleNode:
|
||||
if not self._nozzle_node:
|
||||
self._nozzle_node = NozzleNode()
|
||||
return self._nozzle_node
|
||||
|
||||
def _onSceneChanged(self, node):
|
||||
def _onSceneChanged(self, node: "SceneNode") -> None:
|
||||
if node.getMeshData() is None:
|
||||
self.resetLayerData()
|
||||
|
||||
|
|
@ -164,21 +176,21 @@ class SimulationView(View):
|
|||
self.calculateMaxLayers()
|
||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||
|
||||
def isBusy(self):
|
||||
def isBusy(self) -> bool:
|
||||
return self._busy
|
||||
|
||||
def setBusy(self, busy):
|
||||
def setBusy(self, busy: bool) -> None:
|
||||
if busy != self._busy:
|
||||
self._busy = busy
|
||||
self.busyChanged.emit()
|
||||
|
||||
def isSimulationRunning(self):
|
||||
def isSimulationRunning(self) -> bool:
|
||||
return self._simulation_running
|
||||
|
||||
def setSimulationRunning(self, running):
|
||||
def setSimulationRunning(self, running: bool) -> None:
|
||||
self._simulation_running = running
|
||||
|
||||
def resetLayerData(self):
|
||||
def resetLayerData(self) -> None:
|
||||
self._current_layer_mesh = None
|
||||
self._current_layer_jumps = None
|
||||
self._max_feedrate = sys.float_info.min
|
||||
|
|
@ -186,7 +198,7 @@ class SimulationView(View):
|
|||
self._max_thickness = sys.float_info.min
|
||||
self._min_thickness = sys.float_info.max
|
||||
|
||||
def beginRendering(self):
|
||||
def beginRendering(self) -> None:
|
||||
scene = self.getController().getScene()
|
||||
renderer = self.getRenderer()
|
||||
|
||||
|
|
@ -204,7 +216,7 @@ class SimulationView(View):
|
|||
if (node.getMeshData()) and node.isVisible():
|
||||
renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
|
||||
|
||||
def setLayer(self, value):
|
||||
def setLayer(self, value: int) -> None:
|
||||
if self._current_layer_num != value:
|
||||
self._current_layer_num = value
|
||||
if self._current_layer_num < 0:
|
||||
|
|
@ -218,7 +230,7 @@ class SimulationView(View):
|
|||
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def setMinimumLayer(self, value):
|
||||
def setMinimumLayer(self, value: int) -> None:
|
||||
if self._minimum_layer_num != value:
|
||||
self._minimum_layer_num = value
|
||||
if self._minimum_layer_num < 0:
|
||||
|
|
@ -232,7 +244,7 @@ class SimulationView(View):
|
|||
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def setPath(self, value):
|
||||
def setPath(self, value: int) -> None:
|
||||
if self._current_path_num != value:
|
||||
self._current_path_num = value
|
||||
if self._current_path_num < 0:
|
||||
|
|
@ -246,7 +258,7 @@ class SimulationView(View):
|
|||
|
||||
self.currentPathNumChanged.emit()
|
||||
|
||||
def setMinimumPath(self, value):
|
||||
def setMinimumPath(self, value: int) -> None:
|
||||
if self._minimum_path_num != value:
|
||||
self._minimum_path_num = value
|
||||
if self._minimum_path_num < 0:
|
||||
|
|
@ -263,24 +275,24 @@ class SimulationView(View):
|
|||
## Set the layer view type
|
||||
#
|
||||
# \param layer_view_type integer as in SimulationView.qml and this class
|
||||
def setSimulationViewType(self, layer_view_type):
|
||||
def setSimulationViewType(self, layer_view_type: int) -> None:
|
||||
self._layer_view_type = layer_view_type
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
## Return the layer view type, integer as in SimulationView.qml and this class
|
||||
def getSimulationViewType(self):
|
||||
def getSimulationViewType(self) -> int:
|
||||
return self._layer_view_type
|
||||
|
||||
## Set the extruder opacity
|
||||
#
|
||||
# \param extruder_nr 0..3
|
||||
# \param opacity 0.0 .. 1.0
|
||||
def setExtruderOpacity(self, extruder_nr, opacity):
|
||||
def setExtruderOpacity(self, extruder_nr: int, opacity: float) -> None:
|
||||
if 0 <= extruder_nr <= 3:
|
||||
self._extruder_opacity[extruder_nr] = opacity
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def getExtruderOpacities(self):
|
||||
def getExtruderOpacities(self)-> List[float]:
|
||||
return self._extruder_opacity
|
||||
|
||||
def setShowTravelMoves(self, show):
|
||||
|
|
@ -290,46 +302,46 @@ class SimulationView(View):
|
|||
def getShowTravelMoves(self):
|
||||
return self._show_travel_moves
|
||||
|
||||
def setShowHelpers(self, show):
|
||||
def setShowHelpers(self, show: bool) -> None:
|
||||
self._show_helpers = show
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def getShowHelpers(self):
|
||||
def getShowHelpers(self) -> bool:
|
||||
return self._show_helpers
|
||||
|
||||
def setShowSkin(self, show):
|
||||
def setShowSkin(self, show: bool) -> None:
|
||||
self._show_skin = show
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def getShowSkin(self):
|
||||
def getShowSkin(self) -> bool:
|
||||
return self._show_skin
|
||||
|
||||
def setShowInfill(self, show):
|
||||
def setShowInfill(self, show: bool) -> None:
|
||||
self._show_infill = show
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def getShowInfill(self):
|
||||
def getShowInfill(self) -> bool:
|
||||
return self._show_infill
|
||||
|
||||
def getCompatibilityMode(self):
|
||||
def getCompatibilityMode(self) -> bool:
|
||||
return self._compatibility_mode
|
||||
|
||||
def getExtruderCount(self):
|
||||
def getExtruderCount(self) -> int:
|
||||
return self._extruder_count
|
||||
|
||||
def getMinFeedrate(self):
|
||||
def getMinFeedrate(self) -> float:
|
||||
return self._min_feedrate
|
||||
|
||||
def getMaxFeedrate(self):
|
||||
def getMaxFeedrate(self) -> float:
|
||||
return self._max_feedrate
|
||||
|
||||
def getMinThickness(self):
|
||||
def getMinThickness(self) -> float:
|
||||
return self._min_thickness
|
||||
|
||||
def getMaxThickness(self):
|
||||
def getMaxThickness(self) -> float:
|
||||
return self._max_thickness
|
||||
|
||||
def calculateMaxLayers(self):
|
||||
def calculateMaxLayers(self) -> None:
|
||||
scene = self.getController().getScene()
|
||||
|
||||
self._old_max_layers = self._max_layers
|
||||
|
|
@ -383,7 +395,7 @@ class SimulationView(View):
|
|||
self.maxLayersChanged.emit()
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
def calculateMaxPathsOnLayer(self, layer_num):
|
||||
def calculateMaxPathsOnLayer(self, layer_num: int) -> None:
|
||||
# Update the currentPath
|
||||
scene = self.getController().getScene()
|
||||
for node in DepthFirstIterator(scene.getRoot()):
|
||||
|
|
@ -415,10 +427,10 @@ class SimulationView(View):
|
|||
def getProxy(self, engine, script_engine):
|
||||
return self._proxy
|
||||
|
||||
def endRendering(self):
|
||||
def endRendering(self) -> None:
|
||||
pass
|
||||
|
||||
def event(self, event):
|
||||
def event(self, event) -> bool:
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
ctrl_is_active = modifiers & Qt.ControlModifier
|
||||
shift_is_active = modifiers & Qt.ShiftModifier
|
||||
|
|
@ -447,7 +459,7 @@ class SimulationView(View):
|
|||
if QOpenGLContext.currentContext() is None:
|
||||
Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
|
||||
CuraApplication.getInstance().callLater(lambda e=event: self.event(e))
|
||||
return
|
||||
return False
|
||||
|
||||
# Make sure the SimulationPass is created
|
||||
layer_pass = self.getSimulationPass()
|
||||
|
|
@ -480,11 +492,14 @@ class SimulationView(View):
|
|||
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
|
||||
self._nozzle_node.setParent(None)
|
||||
if self._nozzle_node:
|
||||
self._nozzle_node.setParent(None)
|
||||
self.getRenderer().removeRenderPass(self._layer_pass)
|
||||
self._composite_pass.setLayerBindings(self._old_layer_bindings)
|
||||
self._composite_pass.setCompositeShader(self._old_composite_shader)
|
||||
if self._composite_pass:
|
||||
self._composite_pass.setLayerBindings(self._old_layer_bindings)
|
||||
self._composite_pass.setCompositeShader(self._old_composite_shader)
|
||||
|
||||
return False
|
||||
|
||||
def getCurrentLayerMesh(self):
|
||||
return self._current_layer_mesh
|
||||
|
|
@ -492,7 +507,7 @@ class SimulationView(View):
|
|||
def getCurrentLayerJumps(self):
|
||||
return self._current_layer_jumps
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
def _onGlobalStackChanged(self) -> None:
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
|
@ -504,17 +519,17 @@ class SimulationView(View):
|
|||
else:
|
||||
self._wireprint_warning_message.hide()
|
||||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
def _onPropertyChanged(self, key: str, property_name: str) -> None:
|
||||
if key == "wireframe_enabled" and property_name == "value":
|
||||
if self._global_container_stack.getProperty("wireframe_enabled", "value"):
|
||||
if self._global_container_stack and self._global_container_stack.getProperty("wireframe_enabled", "value"):
|
||||
self._wireprint_warning_message.show()
|
||||
else:
|
||||
self._wireprint_warning_message.hide()
|
||||
|
||||
def _onCurrentLayerNumChanged(self):
|
||||
def _onCurrentLayerNumChanged(self) -> None:
|
||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||
|
||||
def _startUpdateTopLayers(self):
|
||||
def _startUpdateTopLayers(self) -> None:
|
||||
if not self._compatibility_mode:
|
||||
return
|
||||
|
||||
|
|
@ -525,10 +540,10 @@ class SimulationView(View):
|
|||
self.setBusy(True)
|
||||
|
||||
self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
|
||||
self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
|
||||
self._top_layers_job.start()
|
||||
self._top_layers_job.finished.connect(self._updateCurrentLayerMesh) # type: ignore # mypy doesn't understand the whole private class thing that's going on here.
|
||||
self._top_layers_job.start() # type: ignore
|
||||
|
||||
def _updateCurrentLayerMesh(self, job):
|
||||
def _updateCurrentLayerMesh(self, job: "_CreateTopLayersJob") -> None:
|
||||
self.setBusy(False)
|
||||
|
||||
if not job.getResult():
|
||||
|
|
@ -539,9 +554,9 @@ class SimulationView(View):
|
|||
self._current_layer_jumps = job.getResult().get("jumps")
|
||||
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
|
||||
|
||||
self._top_layers_job = None
|
||||
self._top_layers_job = None # type: Optional["_CreateTopLayersJob"]
|
||||
|
||||
def _updateWithPreferences(self):
|
||||
def _updateWithPreferences(self) -> None:
|
||||
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
|
||||
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
|
||||
self._compatibility_mode = self._evaluateCompatibilityMode()
|
||||
|
|
@ -563,7 +578,7 @@ class SimulationView(View):
|
|||
self._startUpdateTopLayers()
|
||||
self.preferencesChanged.emit()
|
||||
|
||||
def _onPreferencesChanged(self, preference):
|
||||
def _onPreferencesChanged(self, preference: str) -> None:
|
||||
if preference not in {
|
||||
"view/top_layer_count",
|
||||
"view/only_show_top_layers",
|
||||
|
|
@ -581,7 +596,7 @@ class SimulationView(View):
|
|||
|
||||
|
||||
class _CreateTopLayersJob(Job):
|
||||
def __init__(self, scene, layer_number, solid_layers):
|
||||
def __init__(self, scene: "Scene", layer_number: int, solid_layers: int) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._scene = scene
|
||||
|
|
@ -589,7 +604,7 @@ class _CreateTopLayersJob(Job):
|
|||
self._solid_layers = solid_layers
|
||||
self._cancel = False
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
layer_data = None
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
|
|
@ -638,6 +653,6 @@ class _CreateTopLayersJob(Job):
|
|||
|
||||
self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
|
||||
|
||||
def cancel(self):
|
||||
def cancel(self) -> None:
|
||||
self._cancel = True
|
||||
super().cancel()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.4
|
||||
|
|
@ -12,30 +12,43 @@ import Cura 1.0 as Cura
|
|||
Item
|
||||
{
|
||||
id: base
|
||||
width: {
|
||||
if (UM.SimulationView.compatibilityMode) {
|
||||
width:
|
||||
{
|
||||
if (UM.SimulationView.compatibilityMode)
|
||||
{
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return UM.Theme.getSize("layerview_menu_size").width;
|
||||
}
|
||||
}
|
||||
height: {
|
||||
if (viewSettings.collapsed) {
|
||||
if (UM.SimulationView.compatibilityMode) {
|
||||
if (viewSettings.collapsed)
|
||||
{
|
||||
if (UM.SimulationView.compatibilityMode)
|
||||
{
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
|
||||
}
|
||||
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
|
||||
} else if (UM.SimulationView.compatibilityMode) {
|
||||
}
|
||||
else if (UM.SimulationView.compatibilityMode)
|
||||
{
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
|
||||
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
|
||||
}
|
||||
else if (UM.Preferences.getValue("layerview/layer_view_type") == 0)
|
||||
{
|
||||
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||
}
|
||||
}
|
||||
Behavior on height { NumberAnimation { duration: 100 } }
|
||||
|
||||
property var buttonTarget: {
|
||||
property var buttonTarget:
|
||||
{
|
||||
if(parent != null)
|
||||
{
|
||||
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
|
||||
|
|
@ -44,7 +57,8 @@ Item
|
|||
return Qt.point(0,0)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
id: layerViewMenu
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
|
@ -83,7 +97,8 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
ColumnLayout
|
||||
{
|
||||
id: viewSettings
|
||||
|
||||
property bool collapsed: false
|
||||
|
|
@ -195,7 +210,8 @@ Item
|
|||
width: width
|
||||
}
|
||||
|
||||
Connections {
|
||||
Connections
|
||||
{
|
||||
target: UM.Preferences
|
||||
onPreferenceChanged:
|
||||
{
|
||||
|
|
@ -212,18 +228,22 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
Repeater
|
||||
{
|
||||
model: Cura.ExtrudersModel{}
|
||||
CheckBox {
|
||||
CheckBox
|
||||
{
|
||||
id: extrudersModelCheckBox
|
||||
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
||||
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
|
||||
}
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
enabled: index + 1 <= 4
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: extrudersModelCheckBox.right
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
|
|
@ -253,8 +273,10 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
Repeater
|
||||
{
|
||||
model: ListModel
|
||||
{
|
||||
id: typesLegendModel
|
||||
Component.onCompleted:
|
||||
{
|
||||
|
|
@ -285,13 +307,16 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
CheckBox
|
||||
{
|
||||
id: legendModelCheckBox
|
||||
checked: model.initialValue
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
UM.Preferences.setValue(model.preference, checked);
|
||||
}
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: legendModelCheckBox.right
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
|
|
@ -320,18 +345,22 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
CheckBox
|
||||
{
|
||||
checked: viewSettings.only_show_top_layers
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
|
||||
}
|
||||
text: catalog.i18nc("@label", "Only Show Top Layers")
|
||||
visible: UM.SimulationView.compatibilityMode
|
||||
style: UM.Theme.styles.checkbox
|
||||
}
|
||||
CheckBox {
|
||||
CheckBox
|
||||
{
|
||||
checked: viewSettings.top_layer_count == 5
|
||||
onClicked: {
|
||||
onClicked:
|
||||
{
|
||||
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
|
||||
}
|
||||
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
||||
|
|
@ -339,8 +368,10 @@ Item
|
|||
style: UM.Theme.styles.checkbox
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
Repeater
|
||||
{
|
||||
model: ListModel
|
||||
{
|
||||
id: typesLegendModelNoCheck
|
||||
Component.onCompleted:
|
||||
{
|
||||
|
|
@ -355,11 +386,13 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Label
|
||||
{
|
||||
text: label
|
||||
visible: viewSettings.show_legend
|
||||
id: typesLegendModelLabel
|
||||
Rectangle {
|
||||
Rectangle
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: typesLegendModelLabel.right
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
|
|
@ -378,30 +411,37 @@ Item
|
|||
}
|
||||
|
||||
// Text for the minimum, maximum and units for the feedrates and layer thickness
|
||||
Item {
|
||||
Item
|
||||
{
|
||||
id: gradientLegend
|
||||
visible: viewSettings.show_gradient
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("layerview_row").height
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
Label
|
||||
{
|
||||
text: minText()
|
||||
anchors.left: parent.left
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
|
||||
function minText() {
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
||||
function minText()
|
||||
{
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||
{
|
||||
// Feedrate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
|
||||
}
|
||||
// Layer thickness selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
|
||||
}
|
||||
}
|
||||
|
|
@ -409,20 +449,25 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Label
|
||||
{
|
||||
text: unitsText()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
|
||||
function unitsText() {
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
||||
function unitsText()
|
||||
{
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||
{
|
||||
// Feedrate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||
{
|
||||
return "mm/s"
|
||||
}
|
||||
// Layer thickness selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||
{
|
||||
return "mm"
|
||||
}
|
||||
}
|
||||
|
|
@ -430,20 +475,25 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Label
|
||||
{
|
||||
text: maxText()
|
||||
anchors.right: parent.right
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
|
||||
function maxText() {
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
||||
function maxText()
|
||||
{
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||
{
|
||||
// Feedrate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
|
||||
}
|
||||
// Layer thickness selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
|
||||
}
|
||||
}
|
||||
|
|
@ -453,7 +503,8 @@ Item
|
|||
}
|
||||
|
||||
// Gradient colors for feedrate
|
||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
||||
Rectangle
|
||||
{ // In QML 5.9 can be changed by LinearGradient
|
||||
// Invert values because then the bar is rotated 90 degrees
|
||||
id: feedrateGradient
|
||||
visible: viewSettings.show_feedrate_gradient
|
||||
|
|
@ -463,20 +514,25 @@ Item
|
|||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
gradient: Gradient
|
||||
{
|
||||
GradientStop
|
||||
{
|
||||
position: 0.000
|
||||
color: Qt.rgba(1, 0.5, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
GradientStop
|
||||
{
|
||||
position: 0.625
|
||||
color: Qt.rgba(0.375, 0.5, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
GradientStop
|
||||
{
|
||||
position: 0.75
|
||||
color: Qt.rgba(0.25, 1, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
GradientStop
|
||||
{
|
||||
position: 1.0
|
||||
color: Qt.rgba(0, 0, 1, 1)
|
||||
}
|
||||
|
|
@ -484,7 +540,8 @@ Item
|
|||
}
|
||||
|
||||
// Gradient colors for layer thickness (similar to parula colormap)
|
||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
||||
Rectangle // In QML 5.9 can be changed by LinearGradient
|
||||
{
|
||||
// Invert values because then the bar is rotated 90 degrees
|
||||
id: thicknessGradient
|
||||
visible: viewSettings.show_thickness_gradient
|
||||
|
|
@ -494,24 +551,30 @@ Item
|
|||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
gradient: Gradient
|
||||
{
|
||||
GradientStop
|
||||
{
|
||||
position: 0.000
|
||||
color: Qt.rgba(1, 1, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
GradientStop
|
||||
{
|
||||
position: 0.25
|
||||
color: Qt.rgba(1, 0.75, 0.25, 1)
|
||||
}
|
||||
GradientStop {
|
||||
GradientStop
|
||||
{
|
||||
position: 0.5
|
||||
color: Qt.rgba(0, 0.75, 0.5, 1)
|
||||
}
|
||||
GradientStop {
|
||||
GradientStop
|
||||
{
|
||||
position: 0.75
|
||||
color: Qt.rgba(0, 0.375, 0.75, 1)
|
||||
}
|
||||
GradientStop {
|
||||
GradientStop
|
||||
{
|
||||
position: 1.0
|
||||
color: Qt.rgba(0, 0, 0.5, 1)
|
||||
}
|
||||
|
|
@ -520,19 +583,22 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Item
|
||||
{
|
||||
id: slidersBox
|
||||
|
||||
width: parent.width
|
||||
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
|
||||
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
top: parent.bottom
|
||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||
left: parent.left
|
||||
}
|
||||
|
||||
PathSlider {
|
||||
PathSlider
|
||||
{
|
||||
id: pathSlider
|
||||
|
||||
height: UM.Theme.getSize("slider_handle").width
|
||||
|
|
@ -553,25 +619,37 @@ Item
|
|||
rangeColor: UM.Theme.getColor("slider_groove_fill")
|
||||
|
||||
// update values when layer data changes
|
||||
Connections {
|
||||
Connections
|
||||
{
|
||||
target: UM.SimulationView
|
||||
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||
onCurrentPathChanged:
|
||||
{
|
||||
// Only pause the simulation when the layer was changed manually, not when the simulation is running
|
||||
if (pathSlider.manuallyChanged)
|
||||
{
|
||||
playButton.pauseSimulation()
|
||||
}
|
||||
pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the slider handlers show the correct value after switching views
|
||||
Component.onCompleted: {
|
||||
Component.onCompleted:
|
||||
{
|
||||
pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||
}
|
||||
}
|
||||
|
||||
LayerSlider {
|
||||
LayerSlider
|
||||
{
|
||||
id: layerSlider
|
||||
|
||||
width: UM.Theme.getSize("slider_handle").width
|
||||
height: UM.Theme.getSize("layerview_menu_size").height
|
||||
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
|
||||
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
|
||||
right: parent.right
|
||||
|
|
@ -593,56 +671,78 @@ Item
|
|||
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
|
||||
|
||||
// update values when layer data changes
|
||||
Connections {
|
||||
Connections
|
||||
{
|
||||
target: UM.SimulationView
|
||||
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||
onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
onCurrentLayerChanged:
|
||||
{
|
||||
// Only pause the simulation when the layer was changed manually, not when the simulation is running
|
||||
if (layerSlider.manuallyChanged)
|
||||
{
|
||||
playButton.pauseSimulation()
|
||||
}
|
||||
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the slider handlers show the correct value after switching views
|
||||
Component.onCompleted: {
|
||||
Component.onCompleted:
|
||||
{
|
||||
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
}
|
||||
}
|
||||
|
||||
// Play simulation button
|
||||
Button {
|
||||
Button
|
||||
{
|
||||
id: playButton
|
||||
iconSource: "./resources/simulation_resume.svg"
|
||||
style: UM.Theme.styles.small_tool_button
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
anchors {
|
||||
anchors
|
||||
{
|
||||
verticalCenter: pathSlider.verticalCenter
|
||||
}
|
||||
|
||||
property var status: 0 // indicates if it's stopped (0) or playing (1)
|
||||
|
||||
onClicked: {
|
||||
switch(status) {
|
||||
case 0: {
|
||||
onClicked:
|
||||
{
|
||||
switch(status)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
resumeSimulation()
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
case 1:
|
||||
{
|
||||
pauseSimulation()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pauseSimulation() {
|
||||
function pauseSimulation()
|
||||
{
|
||||
UM.SimulationView.setSimulationRunning(false)
|
||||
iconSource = "./resources/simulation_resume.svg"
|
||||
simulationTimer.stop()
|
||||
status = 0
|
||||
layerSlider.manuallyChanged = true
|
||||
pathSlider.manuallyChanged = true
|
||||
}
|
||||
|
||||
function resumeSimulation() {
|
||||
function resumeSimulation()
|
||||
{
|
||||
UM.SimulationView.setSimulationRunning(true)
|
||||
iconSource = "./resources/simulation_pause.svg"
|
||||
simulationTimer.start()
|
||||
layerSlider.manuallyChanged = false
|
||||
pathSlider.manuallyChanged = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -652,7 +752,8 @@ Item
|
|||
interval: 100
|
||||
running: false
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
onTriggered:
|
||||
{
|
||||
var currentPath = UM.SimulationView.currentPath
|
||||
var numPaths = UM.SimulationView.numPaths
|
||||
var currentLayer = UM.SimulationView.currentLayer
|
||||
|
|
@ -692,12 +793,15 @@ Item
|
|||
UM.SimulationView.setCurrentPath(currentPath+1)
|
||||
}
|
||||
}
|
||||
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
|
||||
// correctly, because part of the logic is in this trigger function.
|
||||
playButton.status = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FontMetrics {
|
||||
FontMetrics
|
||||
{
|
||||
id: fontMetrics
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ fragment41core =
|
|||
out vec4 frag_color;
|
||||
|
||||
uniform mediump vec4 u_ambientColor;
|
||||
uniform mediump vec4 u_minimumAlbedo;
|
||||
uniform highp vec3 u_lightPosition;
|
||||
|
||||
void main()
|
||||
|
|
@ -263,7 +264,7 @@ fragment41core =
|
|||
mediump vec4 finalColor = vec4(0.0);
|
||||
float alpha = f_color.a;
|
||||
|
||||
finalColor.rgb += f_color.rgb * 0.3;
|
||||
finalColor.rgb += f_color.rgb * 0.2 + u_minimumAlbedo.rgb;
|
||||
|
||||
highp vec3 normal = normalize(f_normal);
|
||||
highp vec3 light_dir = normalize(u_lightPosition - f_vertex);
|
||||
|
|
@ -285,6 +286,7 @@ u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
|
|||
u_specularColor = [0.4, 0.4, 0.4, 1.0]
|
||||
u_ambientColor = [0.3, 0.3, 0.3, 0.0]
|
||||
u_diffuseColor = [1.0, 0.79, 0.14, 1.0]
|
||||
u_minimumAlbedo = [0.1, 0.1, 0.1, 1.0]
|
||||
u_shininess = 20.0
|
||||
|
||||
u_show_travel_moves = 0
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the Simulation view.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import json
|
|||
import os
|
||||
import platform
|
||||
import time
|
||||
from typing import cast, Optional, Set
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ from UM.i18n import i18nCatalog
|
|||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
from typing import cast, Optional
|
||||
|
||||
from .SliceInfoJob import SliceInfoJob
|
||||
|
||||
|
||||
|
|
@ -95,13 +96,29 @@ class SliceInfo(QObject, Extension):
|
|||
def setSendSliceInfo(self, enabled: bool):
|
||||
Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled)
|
||||
|
||||
def _getUserModifiedSettingKeys(self) -> list:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = cast(CuraApplication, Application.getInstance())
|
||||
machine_manager = application.getMachineManager()
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
user_modified_setting_keys = set() # type: Set[str]
|
||||
|
||||
for stack in [global_stack] + list(global_stack.extruders.values()):
|
||||
# Get all settings in user_changes and quality_changes
|
||||
all_keys = stack.userChanges.getAllKeys() | stack.qualityChanges.getAllKeys()
|
||||
user_modified_setting_keys |= all_keys
|
||||
|
||||
return list(sorted(user_modified_setting_keys))
|
||||
|
||||
def _onWriteStarted(self, output_device):
|
||||
try:
|
||||
if not Application.getInstance().getPreferences().getValue("info/send_slice_info"):
|
||||
Logger.log("d", "'info/send_slice_info' is turned off.")
|
||||
return # Do nothing, user does not want to send data
|
||||
|
||||
application = Application.getInstance()
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = cast(CuraApplication, Application.getInstance())
|
||||
machine_manager = application.getMachineManager()
|
||||
print_information = application.getPrintInformation()
|
||||
|
||||
|
|
@ -164,6 +181,8 @@ class SliceInfo(QObject, Extension):
|
|||
|
||||
data["quality_profile"] = global_stack.quality.getMetaData().get("quality_type")
|
||||
|
||||
data["user_modified_setting_keys"] = self._getUserModifiedSettingKeys()
|
||||
|
||||
data["models"] = []
|
||||
# Listing all files placed on the build plate
|
||||
for node in DepthFirstIterator(application.getController().getScene().getRoot()):
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
}
|
||||
],
|
||||
"quality_profile": "fast",
|
||||
"user_modified_setting_keys": ["layer_height", "wall_line_width", "infill_sparse_density"],
|
||||
"models": [
|
||||
{
|
||||
"hash": "b72789b9beb5366dff20b1cf501020c3d4d4df7dc2295ecd0fddd0a6436df070",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides a normal solid mesh view.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Creates an eraser mesh to block the printing of support in certain places",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
"name": "Toolbox",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"description": "Find, manage and install new Cura packages."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ Window
|
|||
{
|
||||
id: header
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: mainView
|
||||
|
|
@ -75,6 +76,7 @@ Window
|
|||
visible: toolbox.viewCategory == "installed"
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxFooter
|
||||
{
|
||||
id: footer
|
||||
|
|
|
|||
|
|
@ -82,9 +82,16 @@ Item
|
|||
}
|
||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||
width: childrenRect.width
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Contact") + ":"
|
||||
text: catalog.i18nc("@label", "Website") + ":"
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Email") + ":"
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
}
|
||||
|
|
@ -100,18 +107,32 @@ Item
|
|||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (details.website)
|
||||
{
|
||||
return "<a href=\"" + details.website + "\">" + details.website + "</a>"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (details.email)
|
||||
{
|
||||
return "<a href=\"mailto:"+details.email+"\">"+details.name+"</a>"
|
||||
}
|
||||
else
|
||||
{
|
||||
return "<a href=\""+details.website+"\">"+details.name+"</a>"
|
||||
return "<a href=\"mailto:" + details.email + "\">" + details.email + "</a>"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
|
|
|
|||
|
|
@ -1,14 +1,25 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
|
||||
property var packageData
|
||||
property var technicalDataSheetUrl: {
|
||||
var link = undefined
|
||||
if ("Technical Data Sheet" in packageData.links)
|
||||
{
|
||||
link = packageData.links["Technical Data Sheet"]
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
height: visible ? childrenRect.height : 0
|
||||
visible: packageData.type == "material" && packageData.has_configs
|
||||
|
|
@ -132,4 +143,25 @@ Item
|
|||
width: Math.floor(table.width * 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: technical_data_sheet
|
||||
anchors.top: table.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height / 2
|
||||
visible: base.technicalDataSheetUrl !== undefined
|
||||
text:
|
||||
{
|
||||
if (base.technicalDataSheetUrl !== undefined)
|
||||
{
|
||||
return "<a href='%1'>%2</a>".arg(base.technicalDataSheetUrl).arg("Technical Data Sheet")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ Item
|
|||
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: details.icon_url || "../images/logobot.svg"
|
||||
source: details === null ? "" : (details.icon_url || "../images/logobot.svg")
|
||||
mipmap: true
|
||||
anchors
|
||||
{
|
||||
|
|
@ -55,7 +55,7 @@ Item
|
|||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
text: details.name || ""
|
||||
text: details === null ? "" : (details.name || "")
|
||||
font: UM.Theme.getFont("large")
|
||||
color: UM.Theme.getColor("text")
|
||||
wrapMode: Text.WordWrap
|
||||
|
|
@ -114,7 +114,7 @@ Item
|
|||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
text: details.version || catalog.i18nc("@label", "Unknown")
|
||||
text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
|
|
@ -122,6 +122,10 @@ Item
|
|||
{
|
||||
text:
|
||||
{
|
||||
if (details === null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
var date = new Date(details.last_updated)
|
||||
return date.toLocaleString(UM.Preferences.getValue("general/language"))
|
||||
}
|
||||
|
|
@ -132,6 +136,10 @@ Item
|
|||
{
|
||||
text:
|
||||
{
|
||||
if (details === null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
if (details.author_email)
|
||||
{
|
||||
return "<a href=\"mailto:" + details.author_email+"?Subject=Cura: " + details.name + "\">" + details.author_name + "</a>"
|
||||
|
|
@ -148,7 +156,7 @@ Item
|
|||
}
|
||||
Label
|
||||
{
|
||||
text: details.download_count || catalog.i18nc("@label", "Unknown")
|
||||
text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import UM 1.1 as UM
|
|||
|
||||
Item
|
||||
{
|
||||
property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfPackagesByAuthor(model.id) : 1
|
||||
id: toolboxDownloadsGridTile
|
||||
property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
|
||||
property int installedPackages: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
|
||||
height: childrenRect.height
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ ScrollView
|
|||
width: parent.width
|
||||
height: parent.height
|
||||
style: UM.Theme.styles.scrollview
|
||||
|
||||
flickableItem.flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
Column
|
||||
{
|
||||
width: base.width
|
||||
|
|
@ -30,7 +32,7 @@ ScrollView
|
|||
id: allPlugins
|
||||
width: parent.width
|
||||
heading: toolbox.viewCategory == "material" ? catalog.i18nc("@label", "Community Contributions") : catalog.i18nc("@label", "Community Plugins")
|
||||
model: toolbox.viewCategory == "material" ? toolbox.authorsModel : toolbox.packagesModel
|
||||
model: toolbox.viewCategory == "material" ? toolbox.materialsAvailableModel : toolbox.pluginsAvailableModel
|
||||
}
|
||||
|
||||
ToolboxDownloadsGrid
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import UM 1.1 as UM
|
|||
|
||||
Rectangle
|
||||
{
|
||||
property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfPackagesByAuthor(model.id) : 1
|
||||
property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
|
||||
property int installedPackages: toolbox.viewCategory == "material" ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
|
||||
id: tileBase
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_large").width + (2 * UM.Theme.getSize("default_lining").width)
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ Item
|
|||
left: parent.left
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
ToolboxTabButton
|
||||
{
|
||||
id: pluginsTabButton
|
||||
text: catalog.i18nc("@title:tab", "Plugins")
|
||||
active: toolbox.viewCategory == "plugin" && enabled
|
||||
enabled: toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
|
||||
|
|
@ -36,6 +38,7 @@ Item
|
|||
|
||||
ToolboxTabButton
|
||||
{
|
||||
id: materialsTabButton
|
||||
text: catalog.i18nc("@title:tab", "Materials")
|
||||
active: toolbox.viewCategory == "material" && enabled
|
||||
enabled: toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
|
||||
|
|
@ -49,6 +52,7 @@ Item
|
|||
}
|
||||
ToolboxTabButton
|
||||
{
|
||||
id: installedTabButton
|
||||
text: catalog.i18nc("@title:tab", "Installed")
|
||||
active: toolbox.viewCategory == "installed"
|
||||
anchors
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import QtQuick.Dialogs 1.1
|
|||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
ScrollView
|
||||
|
|
@ -16,6 +17,7 @@ ScrollView
|
|||
height: parent.height
|
||||
style: UM.Theme.styles.scrollview
|
||||
flickableItem.flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
Column
|
||||
{
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ class AuthorsModel(ListModel):
|
|||
|
||||
def _update(self):
|
||||
items = []
|
||||
if not self._metadata:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
for author in self._metadata:
|
||||
items.append({
|
||||
|
|
|
|||
|
|
@ -5,9 +5,13 @@ import re
|
|||
from typing import Dict
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtProperty
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
from .ConfigsModel import ConfigsModel
|
||||
|
||||
|
||||
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
|
||||
class PackagesModel(ListModel):
|
||||
def __init__(self, parent = None):
|
||||
|
|
@ -34,6 +38,8 @@ class PackagesModel(ListModel):
|
|||
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
||||
self.addRoleName(Qt.UserRole + 18, "download_count")
|
||||
self.addRoleName(Qt.UserRole + 19, "tags")
|
||||
self.addRoleName(Qt.UserRole + 20, "links")
|
||||
self.addRoleName(Qt.UserRole + 21, "website")
|
||||
|
||||
# List of filters for queries. The result is the union of the each list of results.
|
||||
self._filter = {} # type: Dict[str, str]
|
||||
|
|
@ -45,10 +51,16 @@ class PackagesModel(ListModel):
|
|||
def _update(self):
|
||||
items = []
|
||||
|
||||
for package in self._metadata:
|
||||
if self._metadata is None:
|
||||
Logger.logException("w", "Failed to load packages for Toolbox")
|
||||
self.setItems(items)
|
||||
return
|
||||
|
||||
for package in self._metadata:
|
||||
has_configs = False
|
||||
configs_model = None
|
||||
|
||||
links_dict = {}
|
||||
if "data" in package:
|
||||
if "supported_configs" in package["data"]:
|
||||
if len(package["data"]["supported_configs"]) > 0:
|
||||
|
|
@ -56,41 +68,47 @@ class PackagesModel(ListModel):
|
|||
configs_model = ConfigsModel()
|
||||
configs_model.setConfigs(package["data"]["supported_configs"])
|
||||
|
||||
# Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier
|
||||
# to process.
|
||||
link_list = package['data']['links'] if 'links' in package['data'] else []
|
||||
links_dict = {d["title"]: d["url"] for d in link_list}
|
||||
|
||||
if "author_id" not in package["author"] or "display_name" not in package["author"]:
|
||||
package["author"]["author_id"] = ""
|
||||
package["author"]["display_name"] = ""
|
||||
# raise Exception("Detected a package with malformed author data.")
|
||||
|
||||
items.append({
|
||||
"id": package["package_id"],
|
||||
"type": package["package_type"],
|
||||
"name": package["display_name"],
|
||||
"version": package["package_version"],
|
||||
"author_id": package["author"]["author_id"],
|
||||
"author_name": package["author"]["display_name"],
|
||||
"author_email": package["author"]["email"] if "email" in package["author"] else None,
|
||||
"description": package["description"] if "description" in package else None,
|
||||
"icon_url": package["icon_url"] if "icon_url" in package else None,
|
||||
"image_urls": package["image_urls"] if "image_urls" in package else None,
|
||||
"download_url": package["download_url"] if "download_url" in package else None,
|
||||
"last_updated": package["last_updated"] if "last_updated" in package else None,
|
||||
"is_bundled": package["is_bundled"] if "is_bundled" in package else False,
|
||||
"is_active": package["is_active"] if "is_active" in package else False,
|
||||
"is_installed": package["is_installed"] if "is_installed" in package else False,
|
||||
"has_configs": has_configs,
|
||||
"supported_configs": configs_model,
|
||||
"download_count": package["download_count"] if "download_count" in package else 0,
|
||||
"tags": package["tags"] if "tags" in package else []
|
||||
"id": package["package_id"],
|
||||
"type": package["package_type"],
|
||||
"name": package["display_name"],
|
||||
"version": package["package_version"],
|
||||
"author_id": package["author"]["author_id"],
|
||||
"author_name": package["author"]["display_name"],
|
||||
"author_email": package["author"]["email"] if "email" in package["author"] else None,
|
||||
"description": package["description"] if "description" in package else None,
|
||||
"icon_url": package["icon_url"] if "icon_url" in package else None,
|
||||
"image_urls": package["image_urls"] if "image_urls" in package else None,
|
||||
"download_url": package["download_url"] if "download_url" in package else None,
|
||||
"last_updated": package["last_updated"] if "last_updated" in package else None,
|
||||
"is_bundled": package["is_bundled"] if "is_bundled" in package else False,
|
||||
"is_active": package["is_active"] if "is_active" in package else False,
|
||||
"is_installed": package["is_installed"] if "is_installed" in package else False,
|
||||
"has_configs": has_configs,
|
||||
"supported_configs": configs_model,
|
||||
"download_count": package["download_count"] if "download_count" in package else 0,
|
||||
"tags": package["tags"] if "tags" in package else [],
|
||||
"links": links_dict,
|
||||
"website": package["website"] if "website" in package else None,
|
||||
})
|
||||
|
||||
# Filter on all the key-word arguments.
|
||||
for key, value in self._filter.items():
|
||||
if key is "tags":
|
||||
key_filter = lambda item, value = value: value in item["tags"]
|
||||
key_filter = lambda item, v = value: v in item["tags"]
|
||||
elif "*" in value:
|
||||
key_filter = lambda candidate, key = key, value = value: self._matchRegExp(candidate, key, value)
|
||||
key_filter = lambda candidate, k = key, v = value: self._matchRegExp(candidate, k, v)
|
||||
else:
|
||||
key_filter = lambda candidate, key = key, value = value: self._matchString(candidate, key, value)
|
||||
key_filter = lambda candidate, k = key, v = value: self._matchString(candidate, k, v)
|
||||
items = filter(key_filter, items)
|
||||
|
||||
# Execute all filters.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Dict, Optional, Union, Any, cast
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import platform
|
||||
from typing import cast, List
|
||||
from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
|
|
@ -20,9 +19,13 @@ from UM.Version import Version
|
|||
|
||||
import cura
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from .AuthorsModel import AuthorsModel
|
||||
from .PackagesModel import PackagesModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
|
|
@ -34,19 +37,19 @@ class Toolbox(QObject, Extension):
|
|||
def __init__(self, application: CuraApplication) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._application = application #type: CuraApplication
|
||||
self._application = application # type: CuraApplication
|
||||
|
||||
self._sdk_version = None # type: Optional[int]
|
||||
self._cloud_api_version = None # type: Optional[int]
|
||||
self._cloud_api_root = None # type: Optional[str]
|
||||
self._api_url = None # type: Optional[str]
|
||||
self._sdk_version = None # type: Optional[Union[str, int]]
|
||||
self._cloud_api_version = None # type: Optional[int]
|
||||
self._cloud_api_root = None # type: Optional[str]
|
||||
self._api_url = None # type: Optional[str]
|
||||
|
||||
# Network:
|
||||
self._download_request = None #type: Optional[QNetworkRequest]
|
||||
self._download_reply = None #type: Optional[QNetworkReply]
|
||||
self._download_progress = 0 #type: float
|
||||
self._is_downloading = False #type: bool
|
||||
self._network_manager = None #type: Optional[QNetworkAccessManager]
|
||||
self._download_request = None # type: Optional[QNetworkRequest]
|
||||
self._download_reply = None # type: Optional[QNetworkReply]
|
||||
self._download_progress = 0 # type: float
|
||||
self._is_downloading = False # type: bool
|
||||
self._network_manager = None # type: Optional[QNetworkAccessManager]
|
||||
self._request_header = [
|
||||
b"User-Agent",
|
||||
str.encode(
|
||||
|
|
@ -58,9 +61,10 @@ class Toolbox(QObject, Extension):
|
|||
)
|
||||
)
|
||||
]
|
||||
self._request_urls = {} # type: Dict[str, QUrl]
|
||||
self._request_urls = {} # type: Dict[str, QUrl]
|
||||
self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
|
||||
self._old_plugin_ids = [] # type: List[str]
|
||||
self._old_plugin_ids = set() # type: Set[str]
|
||||
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
# Data:
|
||||
self._metadata = {
|
||||
|
|
@ -73,7 +77,7 @@ class Toolbox(QObject, Extension):
|
|||
"materials_available": [],
|
||||
"materials_installed": [],
|
||||
"materials_generic": []
|
||||
} # type: Dict[str, List[Any]]
|
||||
} # type: Dict[str, List[Any]]
|
||||
|
||||
# Models:
|
||||
self._models = {
|
||||
|
|
@ -83,42 +87,40 @@ class Toolbox(QObject, Extension):
|
|||
"plugins_available": PackagesModel(self),
|
||||
"plugins_installed": PackagesModel(self),
|
||||
"materials_showcase": AuthorsModel(self),
|
||||
"materials_available": PackagesModel(self),
|
||||
"materials_available": AuthorsModel(self),
|
||||
"materials_installed": PackagesModel(self),
|
||||
"materials_generic": PackagesModel(self)
|
||||
} # type: Dict[str, ListModel]
|
||||
} # type: Dict[str, ListModel]
|
||||
|
||||
# These properties are for keeping track of the UI state:
|
||||
# ----------------------------------------------------------------------
|
||||
# View category defines which filter to use, and therefore effectively
|
||||
# which category is currently being displayed. For example, possible
|
||||
# values include "plugin" or "material", but also "installed".
|
||||
self._view_category = "plugin" #type: str
|
||||
self._view_category = "plugin" # type: str
|
||||
|
||||
# View page defines which type of page layout to use. For example,
|
||||
# possible values include "overview", "detail" or "author".
|
||||
self._view_page = "loading" #type: str
|
||||
self._view_page = "loading" # type: str
|
||||
|
||||
# Active package refers to which package is currently being downloaded,
|
||||
# installed, or otherwise modified.
|
||||
self._active_package = None # type: Optional[Dict[str, Any]]
|
||||
self._active_package = None # type: Optional[Dict[str, Any]]
|
||||
|
||||
self._dialog = None #type: Optional[QObject]
|
||||
self._confirm_reset_dialog = None #type: Optional[QObject]
|
||||
self._dialog = None # type: Optional[QObject]
|
||||
self._confirm_reset_dialog = None # type: Optional[QObject]
|
||||
self._resetUninstallVariables()
|
||||
|
||||
self._restart_required = False #type: bool
|
||||
self._restart_required = False # type: bool
|
||||
|
||||
# variables for the license agreement dialog
|
||||
self._license_dialog_plugin_name = "" #type: str
|
||||
self._license_dialog_license_content = "" #type: str
|
||||
self._license_dialog_plugin_file_location = "" #type: str
|
||||
self._restart_dialog_message = "" #type: str
|
||||
self._license_dialog_plugin_name = "" # type: str
|
||||
self._license_dialog_license_content = "" # type: str
|
||||
self._license_dialog_plugin_file_location = "" # type: str
|
||||
self._restart_dialog_message = "" # type: str
|
||||
|
||||
self._application.initializationFinished.connect(self._onAppInitialized)
|
||||
|
||||
|
||||
|
||||
# Signals:
|
||||
# --------------------------------------------------------------------------
|
||||
# Downloading changes
|
||||
|
|
@ -137,11 +139,11 @@ class Toolbox(QObject, Extension):
|
|||
showLicenseDialog = pyqtSignal()
|
||||
uninstallVariablesChanged = pyqtSignal()
|
||||
|
||||
def _resetUninstallVariables(self):
|
||||
self._package_id_to_uninstall = None
|
||||
def _resetUninstallVariables(self) -> None:
|
||||
self._package_id_to_uninstall = None # type: Optional[str]
|
||||
self._package_name_to_uninstall = ""
|
||||
self._package_used_materials = []
|
||||
self._package_used_qualities = []
|
||||
self._package_used_materials = [] # type: List[Tuple[GlobalStack, str, str]]
|
||||
self._package_used_qualities = [] # type: List[Tuple[GlobalStack, str, str]]
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getLicenseDialogPluginName(self) -> str:
|
||||
|
|
@ -205,14 +207,14 @@ class Toolbox(QObject, Extension):
|
|||
return cura.CuraVersion.CuraCloudAPIVersion # type: ignore
|
||||
|
||||
# Get the packages version depending on Cura version settings.
|
||||
def _getSDKVersion(self) -> int:
|
||||
def _getSDKVersion(self) -> Union[int, str]:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self._plugin_registry.APIVersion
|
||||
if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
|
||||
if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
|
||||
return self._plugin_registry.APIVersion
|
||||
if not cura.CuraVersion.CuraSDKVersion: # type: ignore
|
||||
if not cura.CuraVersion.CuraSDKVersion: # type: ignore
|
||||
return self._plugin_registry.APIVersion
|
||||
return cura.CuraVersion.CuraSDKVersion # type: ignore
|
||||
return cura.CuraVersion.CuraSDKVersion # type: ignore
|
||||
|
||||
@pyqtSlot()
|
||||
def browsePackages(self) -> None:
|
||||
|
|
@ -229,10 +231,12 @@ class Toolbox(QObject, Extension):
|
|||
# Make remote requests:
|
||||
self._makeRequestByType("packages")
|
||||
self._makeRequestByType("authors")
|
||||
self._makeRequestByType("plugins_showcase")
|
||||
self._makeRequestByType("materials_showcase")
|
||||
self._makeRequestByType("materials_available")
|
||||
self._makeRequestByType("materials_generic")
|
||||
# TODO: Uncomment in the future when the tag-filtered api calls work in the cloud server
|
||||
# self._makeRequestByType("plugins_showcase")
|
||||
# self._makeRequestByType("plugins_available")
|
||||
# self._makeRequestByType("materials_showcase")
|
||||
# self._makeRequestByType("materials_available")
|
||||
# self._makeRequestByType("materials_generic")
|
||||
|
||||
# Gather installed packages:
|
||||
self._updateInstalledModels()
|
||||
|
|
@ -285,8 +289,8 @@ class Toolbox(QObject, Extension):
|
|||
installed_package_ids = self._package_manager.getAllInstalledPackageIDs()
|
||||
scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs()
|
||||
|
||||
self._old_plugin_ids = []
|
||||
self._old_plugin_metadata = [] # type: List[Dict[str, Any]]
|
||||
self._old_plugin_ids = set()
|
||||
self._old_plugin_metadata = dict()
|
||||
|
||||
for plugin_id in old_plugin_ids:
|
||||
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
|
||||
|
|
@ -296,12 +300,20 @@ class Toolbox(QObject, Extension):
|
|||
old_metadata = self._plugin_registry.getMetaData(plugin_id)
|
||||
new_metadata = self._convertPluginMetadata(old_metadata)
|
||||
|
||||
self._old_plugin_ids.append(plugin_id)
|
||||
self._old_plugin_metadata.append(new_metadata)
|
||||
self._old_plugin_ids.add(plugin_id)
|
||||
self._old_plugin_metadata[new_metadata["package_id"]] = new_metadata
|
||||
|
||||
all_packages = self._package_manager.getAllInstalledPackagesInfo()
|
||||
if "plugin" in all_packages:
|
||||
self._metadata["plugins_installed"] = all_packages["plugin"] + self._old_plugin_metadata
|
||||
# For old plugins, we only want to include the old custom plugin that were installed via the old toolbox.
|
||||
# The bundled plugins will be included in JSON files in the "bundled_packages" folder, so the bundled
|
||||
# plugins should be excluded from the old plugins list/dict.
|
||||
all_plugin_package_ids = set(package["package_id"] for package in all_packages["plugin"])
|
||||
self._old_plugin_ids = set(plugin_id for plugin_id in self._old_plugin_ids
|
||||
if plugin_id not in all_plugin_package_ids)
|
||||
self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids}
|
||||
|
||||
self._metadata["plugins_installed"] = all_packages["plugin"] + list(self._old_plugin_metadata.values())
|
||||
self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
|
||||
self.metadataChanged.emit()
|
||||
if "material" in all_packages:
|
||||
|
|
@ -344,26 +356,26 @@ class Toolbox(QObject, Extension):
|
|||
self.uninstall(package_id)
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def pluginToUninstall(self):
|
||||
def pluginToUninstall(self) -> str:
|
||||
return self._package_name_to_uninstall
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def uninstallUsedMaterials(self):
|
||||
def uninstallUsedMaterials(self) -> str:
|
||||
return "\n".join(["%s (%s)" % (str(global_stack.getName()), material) for global_stack, extruder_nr, material in self._package_used_materials])
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def uninstallUsedQualities(self):
|
||||
def uninstallUsedQualities(self) -> str:
|
||||
return "\n".join(["%s (%s)" % (str(global_stack.getName()), quality) for global_stack, extruder_nr, quality in self._package_used_qualities])
|
||||
|
||||
@pyqtSlot()
|
||||
def closeConfirmResetDialog(self):
|
||||
def closeConfirmResetDialog(self) -> None:
|
||||
if self._confirm_reset_dialog is not None:
|
||||
self._confirm_reset_dialog.close()
|
||||
|
||||
## Uses "uninstall variables" to reset qualities and materials, then uninstall
|
||||
# It's used as an action on Confirm reset on Uninstall
|
||||
@pyqtSlot()
|
||||
def resetMaterialsQualitiesAndUninstall(self):
|
||||
def resetMaterialsQualitiesAndUninstall(self) -> None:
|
||||
application = CuraApplication.getInstance()
|
||||
material_manager = application.getMaterialManager()
|
||||
quality_manager = application.getQualityManager()
|
||||
|
|
@ -376,9 +388,9 @@ class Toolbox(QObject, Extension):
|
|||
default_quality_group = quality_manager.getDefaultQualityType(global_stack)
|
||||
machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
|
||||
|
||||
self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
|
||||
|
||||
self.uninstall(self._package_id_to_uninstall)
|
||||
if self._package_id_to_uninstall is not None:
|
||||
self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
|
||||
self.uninstall(self._package_id_to_uninstall)
|
||||
self._resetUninstallVariables()
|
||||
self.closeConfirmResetDialog()
|
||||
|
||||
|
|
@ -471,12 +483,14 @@ class Toolbox(QObject, Extension):
|
|||
# --------------------------------------------------------------------------
|
||||
@pyqtSlot(str, result = bool)
|
||||
def canUpdate(self, package_id: str) -> bool:
|
||||
if self.isOldPlugin(package_id):
|
||||
return True
|
||||
|
||||
local_package = self._package_manager.getInstalledPackageInfo(package_id)
|
||||
if local_package is None:
|
||||
return False
|
||||
Logger.log("i", "Could not find package [%s] as installed in the package manager, fall back to check the old plugins",
|
||||
package_id)
|
||||
local_package = self.getOldPluginPackageMetadata(package_id)
|
||||
if local_package is None:
|
||||
Logger.log("i", "Could not find package [%s] in the old plugins", package_id)
|
||||
return False
|
||||
|
||||
remote_package = self.getRemotePackage(package_id)
|
||||
if remote_package is None:
|
||||
|
|
@ -484,7 +498,16 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
local_version = Version(local_package["package_version"])
|
||||
remote_version = Version(remote_package["package_version"])
|
||||
return remote_version > local_version
|
||||
can_upgrade = False
|
||||
if remote_version > local_version:
|
||||
can_upgrade = True
|
||||
# A package with the same version can be built to have different SDK versions. So, for a package with the same
|
||||
# version, we also need to check if the current one has a lower SDK version. If so, this package should also
|
||||
# be upgradable.
|
||||
elif remote_version == local_version:
|
||||
can_upgrade = local_package.get("sdk_version", 0) < remote_package.get("sdk_version", 0)
|
||||
|
||||
return can_upgrade
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def canDowngrade(self, package_id: str) -> bool:
|
||||
|
|
@ -504,7 +527,11 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def isInstalled(self, package_id: str) -> bool:
|
||||
return self._package_manager.isPackageInstalled(package_id)
|
||||
result = self._package_manager.isPackageInstalled(package_id)
|
||||
# Also check the old plugins list if it's not found in the package manager.
|
||||
if not result:
|
||||
result = self.isOldPlugin(package_id)
|
||||
return result
|
||||
|
||||
@pyqtSlot(str, result = int)
|
||||
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
|
||||
|
|
@ -514,12 +541,14 @@ class Toolbox(QObject, Extension):
|
|||
count += 1
|
||||
return count
|
||||
|
||||
# This slot is only used to get the number of material packages by author, not any other type of packages.
|
||||
@pyqtSlot(str, result = int)
|
||||
def getTotalNumberOfPackagesByAuthor(self, author_id: str) -> int:
|
||||
def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
|
||||
count = 0
|
||||
for package in self._metadata["materials_available"]:
|
||||
if package["author"]["author_id"] == author_id:
|
||||
count += 1
|
||||
for package in self._metadata["packages"]:
|
||||
if package["package_type"] == "material":
|
||||
if package["author"]["author_id"] == author_id:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
|
|
@ -529,12 +558,14 @@ class Toolbox(QObject, Extension):
|
|||
return False
|
||||
|
||||
# Check for plugins that were installed with the old plugin browser
|
||||
@pyqtSlot(str, result = bool)
|
||||
def isOldPlugin(self, plugin_id: str) -> bool:
|
||||
if plugin_id in self._old_plugin_ids:
|
||||
return True
|
||||
return False
|
||||
|
||||
def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
|
||||
return self._old_plugin_metadata.get(plugin_id)
|
||||
|
||||
def loadingComplete(self) -> bool:
|
||||
populated = 0
|
||||
for list in self._metadata.items():
|
||||
|
|
@ -606,8 +637,22 @@ class Toolbox(QObject, Extension):
|
|||
self.resetDownload()
|
||||
return
|
||||
|
||||
# HACK: These request are not handled independently at this moment, but together from the "packages" call
|
||||
do_not_handle = [
|
||||
"materials_available",
|
||||
"materials_showcase",
|
||||
"materials_generic",
|
||||
"plugins_available",
|
||||
"plugins_showcase",
|
||||
]
|
||||
|
||||
if reply.operation() == QNetworkAccessManager.GetOperation:
|
||||
for type, url in self._request_urls.items():
|
||||
|
||||
# HACK: Do nothing because we'll handle these from the "packages" call
|
||||
if type in do_not_handle:
|
||||
continue
|
||||
|
||||
if reply.url() == url:
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
|
||||
try:
|
||||
|
|
@ -623,25 +668,16 @@ class Toolbox(QObject, Extension):
|
|||
if not self._models[type]:
|
||||
Logger.log("e", "Could not find the %s model.", type)
|
||||
break
|
||||
|
||||
# HACK: Eventually get rid of the code from here...
|
||||
if type is "plugins_showcase" or type is "materials_showcase":
|
||||
self._metadata["plugins_showcase"] = json_data["data"]["plugin"]["packages"]
|
||||
self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
|
||||
self._metadata["materials_showcase"] = json_data["data"]["material"]["authors"]
|
||||
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
|
||||
else:
|
||||
# ...until here.
|
||||
# This hack arises for multiple reasons but the main
|
||||
# one is because there are not separate API calls
|
||||
# for different kinds of showcases.
|
||||
self._metadata[type] = json_data["data"]
|
||||
self._models[type].setMetadata(self._metadata[type])
|
||||
|
||||
self._metadata[type] = json_data["data"]
|
||||
self._models[type].setMetadata(self._metadata[type])
|
||||
|
||||
# Do some auto filtering
|
||||
# TODO: Make multiple API calls in the future to handle this
|
||||
if type is "packages":
|
||||
self._models[type].setFilter({"type": "plugin"})
|
||||
self.buildMaterialsModels()
|
||||
self.buildPluginsModels()
|
||||
if type is "authors":
|
||||
self._models[type].setFilter({"package_types": "material"})
|
||||
if type is "materials_generic":
|
||||
|
|
@ -680,7 +716,7 @@ class Toolbox(QObject, Extension):
|
|||
self._temp_plugin_file.close()
|
||||
self._onDownloadComplete(file_path)
|
||||
|
||||
def _onDownloadComplete(self, file_path: str):
|
||||
def _onDownloadComplete(self, file_path: str) -> None:
|
||||
Logger.log("i", "Toolbox: Download complete.")
|
||||
package_info = self._package_manager.getPackageInfo(file_path)
|
||||
if not package_info:
|
||||
|
|
@ -739,9 +775,7 @@ class Toolbox(QObject, Extension):
|
|||
def viewPage(self) -> str:
|
||||
return self._view_page
|
||||
|
||||
|
||||
|
||||
# Expose Models:
|
||||
# Exposed Models:
|
||||
# --------------------------------------------------------------------------
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def authorsModel(self) -> AuthorsModel:
|
||||
|
|
@ -755,6 +789,10 @@ class Toolbox(QObject, Extension):
|
|||
def pluginsShowcaseModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["plugins_showcase"])
|
||||
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def pluginsAvailableModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["plugins_available"])
|
||||
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def pluginsInstalledModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["plugins_installed"])
|
||||
|
|
@ -763,6 +801,10 @@ class Toolbox(QObject, Extension):
|
|||
def materialsShowcaseModel(self) -> AuthorsModel:
|
||||
return cast(AuthorsModel, self._models["materials_showcase"])
|
||||
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def materialsAvailableModel(self) -> AuthorsModel:
|
||||
return cast(AuthorsModel, self._models["materials_available"])
|
||||
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def materialsInstalledModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["materials_installed"])
|
||||
|
|
@ -771,8 +813,6 @@ class Toolbox(QObject, Extension):
|
|||
def materialsGenericModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["materials_generic"])
|
||||
|
||||
|
||||
|
||||
# Filter Models:
|
||||
# --------------------------------------------------------------------------
|
||||
@pyqtSlot(str, str, str)
|
||||
|
|
@ -798,3 +838,48 @@ class Toolbox(QObject, Extension):
|
|||
return
|
||||
self._models[model_type].setFilter({})
|
||||
self.filterChanged.emit()
|
||||
|
||||
# HACK(S):
|
||||
# --------------------------------------------------------------------------
|
||||
def buildMaterialsModels(self) -> None:
|
||||
self._metadata["materials_showcase"] = []
|
||||
self._metadata["materials_available"] = []
|
||||
|
||||
processed_authors = [] # type: List[str]
|
||||
|
||||
for item in self._metadata["packages"]:
|
||||
if item["package_type"] == "material":
|
||||
|
||||
author = item["author"]
|
||||
if author["author_id"] in processed_authors:
|
||||
continue
|
||||
|
||||
# Generic materials to be in the same section
|
||||
if "generic" in item["tags"]:
|
||||
self._metadata["materials_generic"].append(item)
|
||||
else:
|
||||
if "showcase" in item["tags"]:
|
||||
self._metadata["materials_showcase"].append(author)
|
||||
else:
|
||||
self._metadata["materials_available"].append(author)
|
||||
|
||||
processed_authors.append(author["author_id"])
|
||||
|
||||
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
|
||||
self._models["materials_available"].setMetadata(self._metadata["materials_available"])
|
||||
self._models["materials_generic"].setMetadata(self._metadata["materials_generic"])
|
||||
|
||||
def buildPluginsModels(self) -> None:
|
||||
self._metadata["plugins_showcase"] = []
|
||||
self._metadata["plugins_available"] = []
|
||||
|
||||
for item in self._metadata["packages"]:
|
||||
if item["package_type"] == "plugin":
|
||||
|
||||
if "showcase" in item["tags"]:
|
||||
self._metadata["plugins_showcase"].append(item)
|
||||
else:
|
||||
self._metadata["plugins_available"].append(item)
|
||||
|
||||
self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
|
||||
self._models["plugins_available"].setMetadata(self._metadata["plugins_available"])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#Copyright (c) 2018 Ultimaker B.V.
|
||||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import cast
|
||||
|
||||
from Charon.VirtualFile import VirtualFile #To open UFP files.
|
||||
|
|
@ -9,10 +10,12 @@ from io import StringIO #For converting g-code to bytes.
|
|||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Mesh.MeshWriter import MeshWriter #The writer we need to implement.
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||
from UM.PluginRegistry import PluginRegistry #To get the g-code writer.
|
||||
from PyQt5.QtCore import QBuffer
|
||||
|
||||
from cura.Snapshot import Snapshot
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
|
@ -20,7 +23,16 @@ catalog = i18nCatalog("cura")
|
|||
|
||||
class UFPWriter(MeshWriter):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
super().__init__(add_to_recent_files = False)
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-cura-stl-file",
|
||||
comment = "Cura UFP File",
|
||||
suffixes = ["ufp"]
|
||||
)
|
||||
)
|
||||
|
||||
self._snapshot = None
|
||||
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._createSnapshot)
|
||||
|
||||
|
|
@ -29,6 +41,11 @@ class UFPWriter(MeshWriter):
|
|||
Logger.log("d", "Creating thumbnail image...")
|
||||
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
|
||||
|
||||
# This needs to be called on the main thread (Qt thread) because the serialization of material containers can
|
||||
# trigger loading other containers. Because those loaded containers are QtObjects, they must be created on the
|
||||
# Qt thread. The File read/write operations right now are executed on separated threads because they are scheduled
|
||||
# by the Job class.
|
||||
@call_on_qt_thread
|
||||
def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
|
||||
archive = VirtualFile()
|
||||
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
|
||||
|
|
@ -60,5 +77,50 @@ class UFPWriter(MeshWriter):
|
|||
else:
|
||||
Logger.log("d", "Thumbnail not created, cannot save it")
|
||||
|
||||
# Store the material.
|
||||
application = Application.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
material_manager = application.getMaterialManager()
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
material_extension = "xml.fdm_material"
|
||||
material_mime_type = "application/x-ultimaker-material-profile"
|
||||
|
||||
try:
|
||||
archive.addContentType(extension = material_extension, mime_type = material_mime_type)
|
||||
except:
|
||||
Logger.log("w", "The material extension: %s was already added", material_extension)
|
||||
|
||||
added_materials = []
|
||||
for extruder_stack in global_stack.extruders.values():
|
||||
material = extruder_stack.material
|
||||
material_file_name = material.getMetaData()["base_file"] + ".xml.fdm_material"
|
||||
material_file_name = "/Materials/" + material_file_name
|
||||
|
||||
#Same material cannot be added
|
||||
if material_file_name in added_materials:
|
||||
continue
|
||||
|
||||
material_root_id = material.getMetaDataEntry("base_file")
|
||||
material_group = material_manager.getMaterialGroup(material_root_id)
|
||||
if material_group is None:
|
||||
Logger.log("e", "Cannot find material container with root id [%s]", material_root_id)
|
||||
return False
|
||||
|
||||
material_container = material_group.root_material_node.getContainer()
|
||||
try:
|
||||
serialized_material = material_container.serialize()
|
||||
except NotImplementedError:
|
||||
Logger.log("e", "Unable serialize material container with root id: %s", material_root_id)
|
||||
return False
|
||||
|
||||
material_file = archive.getStream(material_file_name)
|
||||
material_file.write(serialized_material.encode("UTF-8"))
|
||||
archive.addRelation(virtual_path = material_file_name,
|
||||
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
|
||||
origin = "/3D/model.gcode")
|
||||
|
||||
added_materials.append(material_file_name)
|
||||
|
||||
archive.close()
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -11,16 +11,6 @@ except ImportError:
|
|||
|
||||
from UM.i18n import i18nCatalog #To translate the file format description.
|
||||
from UM.Mesh.MeshWriter import MeshWriter #For the binary mode flag.
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-cura-stl-file",
|
||||
comment = "Cura UFP File",
|
||||
suffixes = ["ufp"]
|
||||
)
|
||||
)
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for writing Ultimaker Format Packages.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -1,241 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Component
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
id: base
|
||||
property var manager: Cura.MachineManager.printerOutputDevices[0]
|
||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
||||
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
|
||||
|
||||
visible: manager != null
|
||||
anchors.fill: parent
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: activePrintersLabel
|
||||
font: UM.Theme.getFont("large")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right:parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
text: Cura.MachineManager.printerOutputDevices[0].name
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: printJobArea
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: lineColor
|
||||
anchors.top: activePrintersLabel.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin:UM.Theme.getSize("default_margin").width
|
||||
radius: cornerRadius
|
||||
height: childrenRect.height
|
||||
|
||||
Item
|
||||
{
|
||||
id: printJobTitleBar
|
||||
width: parent.width
|
||||
height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
id: printJobTitleLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
text: catalog.i18nc("@title", "Print jobs")
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.75
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
anchors.bottom: parent.bottom
|
||||
height: UM.Theme.getSize("default_lining").width
|
||||
color: lineColor
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: printJobColumn
|
||||
anchors.top: printJobTitleBar.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
opacity: 0.65
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Printing")
|
||||
font: UM.Theme.getFont("very_small")
|
||||
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: manager.activePrintJobs.length
|
||||
font: UM.Theme.getFont("small")
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
opacity: 0.65
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Queued")
|
||||
font: UM.Theme.getFont("very_small")
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: manager.queuedPrintJobs.length
|
||||
font: UM.Theme.getFont("small")
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenPanelButton
|
||||
{
|
||||
anchors.top: printJobColumn.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
id: configButton
|
||||
onClicked: base.manager.openPrintJobControlPanel()
|
||||
text: catalog.i18nc("@action:button", "View print jobs")
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
// spacer
|
||||
anchors.top: configButton.bottom
|
||||
width: UM.Theme.getSize("default_margin").width
|
||||
height: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: printersArea
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: lineColor
|
||||
anchors.top: printJobArea.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin:UM.Theme.getSize("default_margin").width
|
||||
radius: cornerRadius
|
||||
height: childrenRect.height
|
||||
|
||||
Item
|
||||
{
|
||||
id: printersTitleBar
|
||||
width: parent.width
|
||||
height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
id: printersTitleLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
text: catalog.i18nc("@label:title", "Printers")
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.75
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
anchors.bottom: parent.bottom
|
||||
height: UM.Theme.getSize("default_lining").width
|
||||
color: lineColor
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
Column
|
||||
{
|
||||
id: printersColumn
|
||||
anchors.top: printersTitleBar.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: manager.connectedPrintersTypeCount
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
opacity: 0.65
|
||||
Label
|
||||
{
|
||||
text: modelData.machine_type
|
||||
font: UM.Theme.getFont("very_small")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: modelData.count
|
||||
font: UM.Theme.getFont("small")
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenPanelButton
|
||||
{
|
||||
anchors.top: printersColumn.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
id: printerConfigButton
|
||||
onClicked: base.manager.openPrinterControlPanel()
|
||||
|
||||
text: catalog.i18nc("@action:button", "View printers")
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
// spacer
|
||||
anchors.top: printerConfigButton.bottom
|
||||
width: UM.Theme.getSize("default_margin").width
|
||||
height: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Component
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
id: monitorFrame
|
||||
width: maximumWidth
|
||||
height: maximumHeight
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight")
|
||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
||||
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: activePrintersLabel
|
||||
font: UM.Theme.getFont("large")
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: UM.Theme.getSize("default_margin").height * 2 // a bit more spacing to give it some breathing room
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
text: OutputDevice.printers.length == 0 ? catalog.i18nc("@label: arg 1 is group name", "%1 is not set up to host a group of connected Ultimaker 3 printers").arg(Cura.MachineManager.printerOutputDevices[0].name) : ""
|
||||
|
||||
visible: OutputDevice.printers.length == 0
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
width: Math.min(800 * screenScaleFactor, maximumWidth)
|
||||
height: children.height
|
||||
visible: OutputDevice.printers.length != 0
|
||||
|
||||
Label
|
||||
{
|
||||
id: addRemovePrintersLabel
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@label link to connect manager", "Add/Remove printers")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: addRemovePrintersLabel
|
||||
hoverEnabled: true
|
||||
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
|
||||
onEntered: addRemovePrintersLabel.font.underline = true
|
||||
onExited: addRemovePrintersLabel.font.underline = false
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: printerScrollView
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: activePrintersLabel.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_lining").width // To ensure border can be drawn.
|
||||
anchors.rightMargin: UM.Theme.getSize("default_lining").width
|
||||
anchors.right: parent.right
|
||||
|
||||
ListView
|
||||
{
|
||||
anchors.fill: parent
|
||||
spacing: -UM.Theme.getSize("default_lining").height
|
||||
|
||||
model: OutputDevice.printers
|
||||
|
||||
delegate: PrinterInfoBlock
|
||||
{
|
||||
printer: modelData
|
||||
width: Math.min(800 * screenScaleFactor, maximumWidth)
|
||||
height: 125 * screenScaleFactor
|
||||
|
||||
// Add a 1 pix margin, as the border is sometimes cut off otherwise.
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrinterVideoStream
|
||||
{
|
||||
visible: OutputDevice.activePrinter != null
|
||||
anchors.fill:parent
|
||||
}
|
||||
|
||||
onVisibleChanged:
|
||||
{
|
||||
if (!monitorFrame.visible)
|
||||
{
|
||||
// After switching the Tab ensure that active printer is Null, the video stream image
|
||||
// might be active
|
||||
OutputDevice.setActivePrinter(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
Button {
|
||||
objectName: "openPanelSaveAreaButton"
|
||||
id: openPanelSaveAreaButton
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
||||
height: UM.Theme.getSize("save_button_save_to_button").height
|
||||
tooltip: catalog.i18nc("@info:tooltip", "Opens the print jobs page with your default web browser.")
|
||||
text: catalog.i18nc("@action:button", "View print jobs")
|
||||
|
||||
// FIXME: This button style is copied and duplicated from SaveButton.qml
|
||||
style: ButtonStyle {
|
||||
background: Rectangle
|
||||
{
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color:
|
||||
{
|
||||
if(!control.enabled)
|
||||
return UM.Theme.getColor("action_button_disabled_border");
|
||||
else if(control.pressed)
|
||||
return UM.Theme.getColor("print_button_ready_pressed_border");
|
||||
else if(control.hovered)
|
||||
return UM.Theme.getColor("print_button_ready_hovered_border");
|
||||
else
|
||||
return UM.Theme.getColor("print_button_ready_border");
|
||||
}
|
||||
color:
|
||||
{
|
||||
if(!control.enabled)
|
||||
return UM.Theme.getColor("action_button_disabled");
|
||||
else if(control.pressed)
|
||||
return UM.Theme.getColor("print_button_ready_pressed");
|
||||
else if(control.hovered)
|
||||
return UM.Theme.getColor("print_button_ready_hovered");
|
||||
else
|
||||
return UM.Theme.getColor("print_button_ready");
|
||||
}
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 50; } }
|
||||
|
||||
implicitWidth: actualLabel.contentWidth + (UM.Theme.getSize("sidebar_margin").width * 2)
|
||||
|
||||
Label {
|
||||
id: actualLabel
|
||||
anchors.centerIn: parent
|
||||
color:
|
||||
{
|
||||
if(!control.enabled)
|
||||
return UM.Theme.getColor("action_button_disabled_text");
|
||||
else if(control.pressed)
|
||||
return UM.Theme.getColor("print_button_ready_text");
|
||||
else if(control.hovered)
|
||||
return UM.Theme.getColor("print_button_ready_text");
|
||||
else
|
||||
return UM.Theme.getColor("print_button_ready_text");
|
||||
}
|
||||
font: UM.Theme.getFont("action_button")
|
||||
text: control.text;
|
||||
}
|
||||
}
|
||||
label: Item { }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
id: extruderInfo
|
||||
property var printCoreConfiguration
|
||||
|
||||
width: Math.round(parent.width / 2)
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
id: materialLabel
|
||||
text: printCoreConfiguration.activeMaterial != null ? printCoreConfiguration.activeMaterial.name : ""
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
font: UM.Theme.getFont("very_small")
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: printCoreLabel
|
||||
text: printCoreConfiguration.hotendID
|
||||
anchors.top: materialLabel.bottom
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
font: UM.Theme.getFont("very_small")
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
|
|
@ -1,431 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
Rectangle
|
||||
{
|
||||
function strPadLeft(string, pad, length)
|
||||
{
|
||||
return (new Array(length + 1).join(pad) + string).slice(-length);
|
||||
}
|
||||
|
||||
function getPrettyTime(time)
|
||||
{
|
||||
return OutputDevice.formatDuration(time)
|
||||
}
|
||||
|
||||
function formatPrintJobPercent(printJob)
|
||||
{
|
||||
if (printJob == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
if (printJob.timeTotal === 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return Math.min(100, Math.round(printJob.timeElapsed / printJob.timeTotal * 100)) + "%";
|
||||
}
|
||||
|
||||
function printerStatusText(printer)
|
||||
{
|
||||
switch (printer.state)
|
||||
{
|
||||
case "pre_print":
|
||||
return catalog.i18nc("@label:status", "Preparing to print")
|
||||
case "printing":
|
||||
return catalog.i18nc("@label:status", "Printing");
|
||||
case "idle":
|
||||
return catalog.i18nc("@label:status", "Available");
|
||||
case "unreachable":
|
||||
return catalog.i18nc("@label:status", "Lost connection with the printer");
|
||||
case "maintenance":
|
||||
return catalog.i18nc("@label:status", "Unavailable");
|
||||
default:
|
||||
return catalog.i18nc("@label:status", "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
id: printerDelegate
|
||||
|
||||
property var printer: null
|
||||
property var printJob: printer != null ? printer.activePrintJob: null
|
||||
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: mouse.containsMouse ? emphasisColor : lineColor
|
||||
z: mouse.containsMouse ? 1 : 0 // Push this item up a bit on mouse over to ensure that the highlighted bottom border is visible.
|
||||
|
||||
MouseArea
|
||||
{
|
||||
id: mouse
|
||||
anchors.fill:parent
|
||||
onClicked: OutputDevice.setActivePrinter(printer)
|
||||
hoverEnabled: true;
|
||||
|
||||
// Only clickable if no printer is selected
|
||||
enabled: OutputDevice.activePrinter == null && printer.state !== "unreachable"
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Rectangle
|
||||
{
|
||||
width: Math.round(parent.width / 3)
|
||||
height: parent.height
|
||||
|
||||
Label // Print job name
|
||||
{
|
||||
id: jobNameLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
text: printJob != null ? printJob.name : ""
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
elide: Text.ElideRight
|
||||
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: jobOwnerLabel
|
||||
anchors.top: jobNameLabel.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
text: printJob != null ? printJob.owner : ""
|
||||
opacity: 0.50
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: totalTimeLabel
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
text: printJob != null ? getPrettyTime(printJob.timeTotal) : ""
|
||||
opacity: 0.65
|
||||
font: UM.Theme.getFont("default")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
width: Math.round(parent.width / 3 * 2)
|
||||
height: parent.height
|
||||
|
||||
Label // Friendly machine name
|
||||
{
|
||||
id: printerNameLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width)
|
||||
text: printer.name
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label // Machine variant
|
||||
{
|
||||
id: printerTypeLabel
|
||||
anchors.top: printerNameLabel.bottom
|
||||
width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
text: printer.type
|
||||
anchors.left: parent.left
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("very_small")
|
||||
opacity: 0.50
|
||||
}
|
||||
|
||||
Rectangle // Camera icon
|
||||
{
|
||||
id: showCameraIcon
|
||||
width: 40 * screenScaleFactor
|
||||
height: width
|
||||
radius: width
|
||||
anchors.right: printProgressArea.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
color: emphasisColor
|
||||
opacity: printer != null && printer.state === "unreachable" ? 0.3 : 1
|
||||
|
||||
Image
|
||||
{
|
||||
width: parent.width
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: parent.rightMargin
|
||||
source: "camera-icon.svg"
|
||||
}
|
||||
}
|
||||
|
||||
Row // PrintCore config
|
||||
{
|
||||
id: extruderInfo
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
height: childrenRect.height
|
||||
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: leftExtruderInfo
|
||||
width: Math.round((parent.width - extruderSeperator.width) / 2)
|
||||
printCoreConfiguration: printer.extruders[0]
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: extruderSeperator
|
||||
width: UM.Theme.getSize("default_lining").width
|
||||
height: parent.height
|
||||
color: lineColor
|
||||
}
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: rightExtruderInfo
|
||||
width: Math.round((parent.width - extruderSeperator.width) / 2)
|
||||
printCoreConfiguration: printer.extruders[1]
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle // Print progress
|
||||
{
|
||||
id: printProgressArea
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: showExtended ? parent.height: printProgressTitleBar.height
|
||||
width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: lineColor
|
||||
radius: cornerRadius
|
||||
property var showExtended: {
|
||||
if(printJob != null)
|
||||
{
|
||||
var extendStates = ["sent_to_printer", "wait_for_configuration", "printing", "pre_print", "post_print", "wait_cleanup", "queued"];
|
||||
return extendStates.indexOf(printJob.state) !== -1;
|
||||
}
|
||||
return printer.state == "disabled"
|
||||
}
|
||||
|
||||
Item // Status and Percent
|
||||
{
|
||||
id: printProgressTitleBar
|
||||
|
||||
property var showPercent: {
|
||||
return printJob != null && (["printing", "post_print", "pre_print", "sent_to_printer"].indexOf(printJob.state) !== -1);
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
//TODO: hardcoded value
|
||||
height: 40 * screenScaleFactor
|
||||
anchors.left: parent.left
|
||||
|
||||
Label
|
||||
{
|
||||
id: statusText
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: progressText.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (printer.state == "disabled")
|
||||
{
|
||||
return catalog.i18nc("@label:status", "Disabled");
|
||||
}
|
||||
|
||||
if (printer.state === "unreachable")
|
||||
{
|
||||
return printerStatusText(printer);
|
||||
}
|
||||
|
||||
if (printJob != null)
|
||||
{
|
||||
switch (printJob.state)
|
||||
{
|
||||
case "printing":
|
||||
case "post_print":
|
||||
return catalog.i18nc("@label:status", "Printing")
|
||||
case "wait_for_configuration":
|
||||
return catalog.i18nc("@label:status", "Reserved")
|
||||
case "wait_cleanup":
|
||||
case "wait_user_action":
|
||||
return catalog.i18nc("@label:status", "Finished")
|
||||
case "pre_print":
|
||||
case "sent_to_printer":
|
||||
return catalog.i18nc("@label", "Preparing to print")
|
||||
case "queued":
|
||||
return catalog.i18nc("@label:status", "Action required");
|
||||
case "pausing":
|
||||
case "paused":
|
||||
return catalog.i18nc("@label:status", "Paused");
|
||||
case "resuming":
|
||||
return catalog.i18nc("@label:status", "Resuming");
|
||||
case "aborted":
|
||||
return catalog.i18nc("@label:status", "Print aborted");
|
||||
default:
|
||||
// If print job has unknown status show printer.status
|
||||
return printerStatusText(printer);
|
||||
}
|
||||
}
|
||||
return printerStatusText(printer);
|
||||
}
|
||||
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("small")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: progressText
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: statusText.top
|
||||
|
||||
text: formatPrintJobPercent(printJob)
|
||||
visible: printProgressTitleBar.showPercent
|
||||
//TODO: Hardcoded value
|
||||
opacity: 0.65
|
||||
font: UM.Theme.getFont("very_small")
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
width: statusText.height
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: statusText.top
|
||||
|
||||
visible: !printProgressTitleBar.showPercent
|
||||
|
||||
source: {
|
||||
if (printer.state == "disabled")
|
||||
{
|
||||
return "blocked-icon.svg";
|
||||
}
|
||||
|
||||
if (printer.state === "unreachable")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (printJob != null)
|
||||
{
|
||||
if(printJob.state === "queued")
|
||||
{
|
||||
return "action-required-icon.svg";
|
||||
}
|
||||
else if (printJob.state === "wait_cleanup")
|
||||
{
|
||||
return "checkmark-icon.svg";
|
||||
}
|
||||
}
|
||||
return ""; // We're not going to show it, so it will not be resolved as a url.
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
//TODO: This will become a progress bar in the future
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
visible: printProgressArea.showExtended
|
||||
color: lineColor
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
anchors.top: printProgressTitleBar.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").width
|
||||
|
||||
visible: printProgressArea.showExtended
|
||||
|
||||
Label // Status detail
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (printer.state == "disabled")
|
||||
{
|
||||
return catalog.i18nc("@label", "Not accepting print jobs");
|
||||
}
|
||||
|
||||
if (printer.state === "unreachable")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if(printJob != null)
|
||||
{
|
||||
switch (printJob.state)
|
||||
{
|
||||
case "printing":
|
||||
case "post_print":
|
||||
return catalog.i18nc("@label", "Finishes at: ") + OutputDevice.getTimeCompleted(printJob.timeTotal - printJob.timeElapsed)
|
||||
case "wait_cleanup":
|
||||
return catalog.i18nc("@label", "Clear build plate")
|
||||
case "sent_to_printer":
|
||||
case "pre_print":
|
||||
return catalog.i18nc("@label", "Preparing to print")
|
||||
case "wait_for_configuration":
|
||||
return catalog.i18nc("@label", "Not accepting print jobs")
|
||||
case "queued":
|
||||
return catalog.i18nc("@label", "Waiting for configuration change");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
|
||||
Label // Status 2nd row
|
||||
{
|
||||
text: {
|
||||
if(printJob != null)
|
||||
{
|
||||
if(printJob.state == "printing" || printJob.state == "post_print")
|
||||
{
|
||||
return OutputDevice.getDateCompleted(printJob.timeTotal - printJob.timeElapsed)
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: base
|
||||
width: 250 * screenScaleFactor
|
||||
height: 250 * screenScaleFactor
|
||||
signal clicked()
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill:parent
|
||||
onClicked: base.clicked()
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
// TODO: Actually add UM icon / picture
|
||||
width: 100 * screenScaleFactor
|
||||
height: 100 * screenScaleFactor
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: nameLabel
|
||||
anchors.bottom: ipLabel.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
text: modelData.friendly_name.toString()
|
||||
font: UM.Theme.getFont("large")
|
||||
elide: Text.ElideMiddle;
|
||||
height: UM.Theme.getSize("section").height;
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: ipLabel
|
||||
text: modelData.ip_address.toString()
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
font: UM.Theme.getFont("default")
|
||||
height:10 * screenScaleFactor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import DiscoverUM3Action
|
||||
from .src import DiscoverUM3Action
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
from . import UM3OutputDevicePlugin
|
||||
from .src import UM3OutputDevicePlugin
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"description": "Manages network connections to Ultimaker 3 printers.",
|
||||
"version": "1.0.0",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
729
plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml
Normal file
|
|
@ -0,0 +1,729 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import QtQuick.Controls 2.0 as Controls2
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Component
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
id: base
|
||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
||||
|
||||
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
|
||||
visible: OutputDevice != null
|
||||
anchors.fill: parent
|
||||
color: "white"
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: printingLabel
|
||||
font: UM.Theme.getFont("large")
|
||||
anchors
|
||||
{
|
||||
margins: 2 * UM.Theme.getSize("default_margin").width
|
||||
leftMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: catalog.i18nc("@label", "Printing")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: managePrintersLabel
|
||||
anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||
anchors.right: printerScrollView.right
|
||||
anchors.bottom: printingLabel.bottom
|
||||
text: catalog.i18nc("@label link to connect manager", "Manage printers")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("primary")
|
||||
linkColor: UM.Theme.getColor("primary")
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: managePrintersLabel
|
||||
hoverEnabled: true
|
||||
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
|
||||
onEntered: managePrintersLabel.font.underline = true
|
||||
onExited: managePrintersLabel.font.underline = false
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: printerScrollView
|
||||
anchors
|
||||
{
|
||||
top: printingLabel.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
bottom: parent.bottom
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
|
||||
style: UM.Theme.styles.scrollview
|
||||
|
||||
ListView
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
rightMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
spacing: UM.Theme.getSize("default_margin").height -10
|
||||
model: OutputDevice.printers
|
||||
|
||||
delegate: Item
|
||||
{
|
||||
width: parent.width
|
||||
height: base.height + 2 * base.shadowRadius // To ensure that the shadow doesn't get cut off.
|
||||
Rectangle
|
||||
{
|
||||
width: parent.width - 2 * shadowRadius
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: base
|
||||
property var shadowRadius: 5
|
||||
property var collapsed: true
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow
|
||||
{
|
||||
radius: base.shadowRadius
|
||||
verticalOffset: 2
|
||||
color: "#3F000000" // 25% shadow
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: printerInfo
|
||||
height: machineIcon.height
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked: base.collapsed = !base.collapsed
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: machineIcon
|
||||
// Yeah, this is hardcoded now, but I can't think of a good way to fix this.
|
||||
// The UI is going to get another update soon, so it's probably not worth the effort...
|
||||
width: 58
|
||||
height: 58
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.left: parent.left
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
anchors.centerIn: parent
|
||||
source:
|
||||
{
|
||||
switch(modelData.type)
|
||||
{
|
||||
case "Ultimaker 3":
|
||||
return "../svg/UM3-icon.svg"
|
||||
case "Ultimaker 3 Extended":
|
||||
return "../svg/UM3x-icon.svg"
|
||||
case "Ultimaker S5":
|
||||
return "../svg/UMs5-icon.svg"
|
||||
}
|
||||
}
|
||||
width: sourceSize.width
|
||||
height: sourceSize.height
|
||||
|
||||
color:
|
||||
{
|
||||
if(modelData.state == "disabled")
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_disabled")
|
||||
}
|
||||
|
||||
if(modelData.activePrintJob != undefined)
|
||||
{
|
||||
return UM.Theme.getColor("primary")
|
||||
}
|
||||
|
||||
return UM.Theme.getColor("setting_control_disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
Item
|
||||
{
|
||||
height: childrenRect.height
|
||||
anchors
|
||||
{
|
||||
right: collapseIcon.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
left: machineIcon.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
verticalCenter: machineIcon.verticalCenter
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: machineNameLabel
|
||||
text: modelData.name
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: activeJobLabel
|
||||
text:
|
||||
{
|
||||
if (modelData.state == "disabled")
|
||||
{
|
||||
return catalog.i18nc("@label", "Not available")
|
||||
} else if (modelData.state == "unreachable")
|
||||
{
|
||||
return catalog.i18nc("@label", "Unreachable")
|
||||
}
|
||||
if (modelData.activePrintJob != null)
|
||||
{
|
||||
return modelData.activePrintJob.name
|
||||
}
|
||||
return catalog.i18nc("@label", "Available")
|
||||
}
|
||||
anchors.top: machineNameLabel.bottom
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.6
|
||||
}
|
||||
}
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: collapseIcon
|
||||
width: 15
|
||||
height: 15
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
source: base.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
color: "black"
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: detailedInfo
|
||||
property var printJob: modelData.activePrintJob
|
||||
visible: height == childrenRect.height
|
||||
anchors.top: printerInfo.bottom
|
||||
width: parent.width
|
||||
height: !base.collapsed ? childrenRect.height : 0
|
||||
opacity: visible ? 1 : 0
|
||||
Behavior on height { NumberAnimation { duration: 100 } }
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
Rectangle
|
||||
{
|
||||
id: topSpacer
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
height: 2
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
top: parent.top
|
||||
topMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
PrinterFamilyPill
|
||||
{
|
||||
id: printerFamilyPill
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
anchors.top: topSpacer.bottom
|
||||
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
text: modelData.type
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
padding: 3
|
||||
}
|
||||
Row
|
||||
{
|
||||
id: extrudersInfo
|
||||
anchors.top: printerFamilyPill.bottom
|
||||
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
height: childrenRect.height
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: leftExtruderInfo
|
||||
width: Math.round(parent.width / 2)
|
||||
printCoreConfiguration: modelData.printerConfiguration.extruderConfigurations[0]
|
||||
}
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: rightExtruderInfo
|
||||
width: Math.round(parent.width / 2)
|
||||
printCoreConfiguration: modelData.printerConfiguration.extruderConfigurations[1]
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: jobSpacer
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
height: 2
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
top: extrudersInfo.bottom
|
||||
topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: jobInfo
|
||||
property var showJobInfo: modelData.activePrintJob != null && modelData.activePrintJob.state != "queued"
|
||||
|
||||
anchors.top: jobSpacer.bottom
|
||||
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
anchors.leftMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
height: showJobInfo ? childrenRect.height + 2 * UM.Theme.getSize("default_margin").height: 0
|
||||
visible: showJobInfo
|
||||
Label
|
||||
{
|
||||
id: printJobName
|
||||
text: modelData.activePrintJob != null ? modelData.activePrintJob.name : ""
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
anchors.left: parent.left
|
||||
anchors.right: contextButton.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: ownerName
|
||||
anchors.top: printJobName.bottom
|
||||
text: modelData.activePrintJob != null ? modelData.activePrintJob.owner : ""
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.6
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
function switchPopupState()
|
||||
{
|
||||
if (popup.visible)
|
||||
{
|
||||
popup.close()
|
||||
}
|
||||
else
|
||||
{
|
||||
popup.open()
|
||||
}
|
||||
}
|
||||
|
||||
Controls2.Button
|
||||
{
|
||||
id: contextButton
|
||||
text: "\u22EE" //Unicode; Three stacked points.
|
||||
font.pixelSize: 25
|
||||
width: 35
|
||||
height: width
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: contextButton.down || contextButton.hovered ? 1 : 0
|
||||
width: contextButton.width
|
||||
height: contextButton.height
|
||||
radius: 0.5 * width
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
|
||||
onClicked: parent.switchPopupState()
|
||||
}
|
||||
|
||||
Controls2.Popup
|
||||
{
|
||||
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
|
||||
id: popup
|
||||
clip: true
|
||||
closePolicy: Controls2.Popup.CloseOnPressOutsideParent
|
||||
x: parent.width - width
|
||||
y: contextButton.height
|
||||
width: 160
|
||||
height: contentItem.height + 2 * padding
|
||||
visible: false
|
||||
|
||||
transformOrigin: Controls2.Popup.Top
|
||||
contentItem: Item
|
||||
{
|
||||
width: popup.width - 2 * popup.padding
|
||||
height: childrenRect.height + 15
|
||||
Controls2.Button
|
||||
{
|
||||
id: pauseButton
|
||||
text: modelData.activePrintJob != null && modelData.activePrintJob.state == "paused" ? catalog.i18nc("@label", "Resume") : catalog.i18nc("@label", "Pause")
|
||||
onClicked:
|
||||
{
|
||||
if(modelData.activePrintJob.state == "paused")
|
||||
{
|
||||
modelData.activePrintJob.setState("print")
|
||||
}
|
||||
else if(modelData.activePrintJob.state == "printing")
|
||||
{
|
||||
modelData.activePrintJob.setState("pause")
|
||||
}
|
||||
popup.close()
|
||||
}
|
||||
width: parent.width
|
||||
enabled: modelData.activePrintJob != null && ["paused", "printing"].indexOf(modelData.activePrintJob.state) >= 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
hoverEnabled: true
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: pauseButton.down || pauseButton.hovered ? 1 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
}
|
||||
|
||||
Controls2.Button
|
||||
{
|
||||
id: abortButton
|
||||
text: catalog.i18nc("@label", "Abort")
|
||||
onClicked:
|
||||
{
|
||||
abortConfirmationDialog.visible = true;
|
||||
popup.close();
|
||||
}
|
||||
width: parent.width
|
||||
anchors.top: pauseButton.bottom
|
||||
hoverEnabled: true
|
||||
enabled: modelData.activePrintJob != null && ["paused", "printing", "pre_print"].indexOf(modelData.activePrintJob.state) >= 0
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: abortButton.down || abortButton.hovered ? 1 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog
|
||||
{
|
||||
id: abortConfirmationDialog
|
||||
title: catalog.i18nc("@window:title", "Abort print")
|
||||
icon: StandardIcon.Warning
|
||||
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to abort %1?").arg(modelData.activePrintJob.name)
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
Component.onCompleted: visible = false
|
||||
onYes: modelData.activePrintJob.setState("abort")
|
||||
}
|
||||
}
|
||||
|
||||
background: Item
|
||||
{
|
||||
width: popup.width
|
||||
height: popup.height
|
||||
|
||||
DropShadow
|
||||
{
|
||||
anchors.fill: pointedRectangle
|
||||
radius: 5
|
||||
color: "#3F000000" // 25% shadow
|
||||
source: pointedRectangle
|
||||
transparentBorder: true
|
||||
verticalOffset: 2
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: pointedRectangle
|
||||
width: parent.width -10
|
||||
height: parent.height -10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: point
|
||||
height: 13
|
||||
width: 13
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
transform: Rotation { angle: 45}
|
||||
anchors.right: bloop.right
|
||||
y: 1
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: bloop
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition
|
||||
{
|
||||
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
|
||||
NumberAnimation { property: "visible"; duration: 75; }
|
||||
}
|
||||
enter: Transition
|
||||
{
|
||||
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
|
||||
NumberAnimation { property: "visible"; duration: 75; }
|
||||
}
|
||||
|
||||
onClosed: visible = false
|
||||
onOpened: visible = true
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
id: printJobPreview
|
||||
source: modelData.activePrintJob != null ? modelData.activePrintJob.previewImageUrl : ""
|
||||
anchors.top: ownerName.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width / 2
|
||||
height: width
|
||||
opacity:
|
||||
{
|
||||
if(modelData.activePrintJob == null)
|
||||
{
|
||||
return 1.0
|
||||
}
|
||||
|
||||
switch(modelData.activePrintJob.state)
|
||||
{
|
||||
case "wait_cleanup":
|
||||
case "wait_user_action":
|
||||
case "paused":
|
||||
return 0.5
|
||||
default:
|
||||
return 1.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: statusImage
|
||||
anchors.centerIn: printJobPreview
|
||||
source:
|
||||
{
|
||||
if(modelData.activePrintJob == null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
switch(modelData.activePrintJob.state)
|
||||
{
|
||||
case "paused":
|
||||
return "../svg/paused-icon.svg"
|
||||
case "wait_cleanup":
|
||||
if(modelData.activePrintJob.timeElapsed < modelData.activePrintJob.timeTotal)
|
||||
{
|
||||
return "../svg/aborted-icon.svg"
|
||||
}
|
||||
return "../svg/approved-icon.svg"
|
||||
case "wait_user_action":
|
||||
return "../svg/aborted-icon.svg"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
visible: source != ""
|
||||
width: 0.5 * printJobPreview.width
|
||||
height: 0.5 * printJobPreview.height
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
color: "black"
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: showCameraIcon
|
||||
width: 35 * screenScaleFactor
|
||||
height: width
|
||||
radius: 0.5 * width
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: printJobPreview.bottom
|
||||
color: UM.Theme.getColor("setting_control_border_highlight")
|
||||
Image
|
||||
{
|
||||
width: parent.width
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: parent.rightMargin
|
||||
source: "../svg/camera-icon.svg"
|
||||
}
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill:parent
|
||||
onClicked:
|
||||
{
|
||||
OutputDevice.setActiveCamera(modelData.camera)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProgressBar
|
||||
{
|
||||
property var progress:
|
||||
{
|
||||
if(modelData.activePrintJob == null)
|
||||
{
|
||||
return 0
|
||||
}
|
||||
var result = modelData.activePrintJob.timeElapsed / modelData.activePrintJob.timeTotal
|
||||
if(result > 1.0)
|
||||
{
|
||||
result = 1.0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
id: jobProgressBar
|
||||
width: parent.width
|
||||
value: progress
|
||||
anchors.top: detailedInfo.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
visible: modelData.activePrintJob != null && modelData.activePrintJob != undefined
|
||||
|
||||
style: ProgressBarStyle
|
||||
{
|
||||
property var progressText:
|
||||
{
|
||||
if(modelData.activePrintJob == null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
|
||||
switch(modelData.activePrintJob.state)
|
||||
{
|
||||
case "wait_cleanup":
|
||||
if(modelData.activePrintJob.timeTotal > modelData.activePrintJob.timeElapsed)
|
||||
{
|
||||
return catalog.i18nc("@label:status", "Aborted")
|
||||
}
|
||||
return catalog.i18nc("@label:status", "Finished")
|
||||
case "pre_print":
|
||||
case "sent_to_printer":
|
||||
return catalog.i18nc("@label:status", "Preparing")
|
||||
case "aborted":
|
||||
case "wait_user_action":
|
||||
return catalog.i18nc("@label:status", "Aborted")
|
||||
case "pausing":
|
||||
return catalog.i18nc("@label:status", "Pausing")
|
||||
case "paused":
|
||||
return catalog.i18nc("@label:status", "Paused")
|
||||
case "resuming":
|
||||
return catalog.i18nc("@label:status", "Resuming")
|
||||
case "queued":
|
||||
return catalog.i18nc("@label:status", "Action required")
|
||||
default:
|
||||
OutputDevice.formatDuration(modelData.activePrintJob.timeTotal - modelData.activePrintJob.timeElapsed)
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
implicitWidth: 100
|
||||
implicitHeight: visible ? 24 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
|
||||
progress: Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("primary")
|
||||
id: progressItem
|
||||
function getTextOffset()
|
||||
{
|
||||
if(progressItem.width + progressLabel.width < control.width)
|
||||
{
|
||||
return progressItem.width + UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
else
|
||||
{
|
||||
return progressItem.width - progressLabel.width - UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: progressLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: getTextOffset()
|
||||
text: progressText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: progressItem.width + progressLabel.width < control.width ? "black" : "white"
|
||||
width: contentWidth
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Component
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
id: monitorFrame
|
||||
width: maximumWidth
|
||||
height: maximumHeight
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight")
|
||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
||||
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: manageQueueLabel
|
||||
anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||
anchors.right: queuedPrintJobs.right
|
||||
anchors.bottom: queuedLabel.bottom
|
||||
text: catalog.i18nc("@label link to connect manager", "Manage queue")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("primary")
|
||||
linkColor: UM.Theme.getColor("primary")
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: manageQueueLabel
|
||||
hoverEnabled: true
|
||||
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
|
||||
onEntered: manageQueueLabel.font.underline = true
|
||||
onExited: manageQueueLabel.font.underline = false
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: queuedLabel
|
||||
anchors.left: queuedPrintJobs.left
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
anchors.leftMargin: 3 * UM.Theme.getSize("default_margin").width
|
||||
text: catalog.i18nc("@label", "Queued")
|
||||
font: UM.Theme.getFont("large")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: queuedPrintJobs
|
||||
|
||||
anchors
|
||||
{
|
||||
top: queuedLabel.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottomMargin: 0
|
||||
bottom: parent.bottom
|
||||
}
|
||||
style: UM.Theme.styles.scrollview
|
||||
width: Math.min(800 * screenScaleFactor, maximumWidth)
|
||||
ListView
|
||||
{
|
||||
anchors.fill: parent
|
||||
//anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
spacing: UM.Theme.getSize("default_margin").height - 10 // 2x the shadow radius
|
||||
|
||||
model: OutputDevice.queuedPrintJobs
|
||||
|
||||
delegate: PrintJobInfoBlock
|
||||
{
|
||||
printJob: modelData
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").height
|
||||
height: 175 * screenScaleFactor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrinterVideoStream
|
||||
{
|
||||
visible: OutputDevice.activeCamera != null
|
||||
anchors.fill: parent
|
||||
camera: OutputDevice.activeCamera
|
||||
}
|
||||
|
||||
onVisibleChanged:
|
||||
{
|
||||
if (monitorFrame != null && !monitorFrame.visible)
|
||||
{
|
||||
OutputDevice.setActiveCamera(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -364,7 +364,6 @@ Cura.MachineAction
|
|||
{
|
||||
id: addressField
|
||||
width: parent.width
|
||||
maximumLength: 40
|
||||
validator: RegExpValidator
|
||||
{
|
||||
regExp: /[a-zA-Z0-9\.\-\_]*/
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
id: extruderInfo
|
||||
property var printCoreConfiguration
|
||||
|
||||
width: Math.round(parent.width / 2)
|
||||
height: childrenRect.height
|
||||
|
||||
Item
|
||||
{
|
||||
id: extruderCircle
|
||||
width: 30
|
||||
height: 30
|
||||
|
||||
anchors.verticalCenter: printAndMaterialLabel.verticalCenter
|
||||
opacity:
|
||||
{
|
||||
if(printCoreConfiguration == null || printCoreConfiguration.activeMaterial == null || printCoreConfiguration.hotendID == null)
|
||||
{
|
||||
return 0.5
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors.fill: parent
|
||||
radius: Math.round(width / 2)
|
||||
border.width: 2
|
||||
border.color: "black"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
anchors.centerIn: parent
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
text: printCoreConfiguration.position + 1
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: printAndMaterialLabel
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
left: extruderCircle.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
height: childrenRect.height
|
||||
|
||||
Label
|
||||
{
|
||||
id: materialLabel
|
||||
text:
|
||||
{
|
||||
if(printCoreConfiguration != undefined && printCoreConfiguration.activeMaterial != undefined)
|
||||
{
|
||||
return printCoreConfiguration.activeMaterial.name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: printCoreLabel
|
||||
text:
|
||||
{
|
||||
if(printCoreConfiguration != undefined && printCoreConfiguration.hotendID != undefined)
|
||||
{
|
||||
return printCoreConfiguration.hotendID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
anchors.top: materialLabel.bottom
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
opacity: 0.6
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
}
|
||||
401
plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Controls 2.0
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
property var printJob: null
|
||||
property var shadowRadius: 5
|
||||
function getPrettyTime(time)
|
||||
{
|
||||
return OutputDevice.formatDuration(time)
|
||||
}
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: background
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
topMargin: 3
|
||||
left: parent.left
|
||||
leftMargin: base.shadowRadius
|
||||
rightMargin: base.shadowRadius
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
bottomMargin: base.shadowRadius
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow
|
||||
{
|
||||
radius: base.shadowRadius
|
||||
verticalOffset: 2
|
||||
color: "#3F000000" // 25% shadow
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
// Content on the left of the infobox
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.horizontalCenter
|
||||
margins: 2 * UM.Theme.getSize("default_margin").width
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: printJobName
|
||||
text: printJob.name
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: ownerName
|
||||
anchors.top: printJobName.bottom
|
||||
text: printJob.owner
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.6
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
id: printJobPreview
|
||||
source: printJob.previewImageUrl
|
||||
anchors.top: ownerName.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: totalTimeLabel.bottom
|
||||
width: height
|
||||
opacity: printJob.state == "error" ? 0.5 : 1.0
|
||||
}
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: statusImage
|
||||
anchors.centerIn: printJobPreview
|
||||
source: printJob.state == "error" ? "../svg/aborted-icon.svg" : ""
|
||||
visible: source != ""
|
||||
width: 0.5 * printJobPreview.width
|
||||
height: 0.5 * printJobPreview.height
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
color: "black"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: totalTimeLabel
|
||||
opacity: 0.6
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
font: UM.Theme.getFont("default")
|
||||
text: printJob != null ? getPrettyTime(printJob.timeTotal) : ""
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
// Content on the right side of the infobox.
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.horizontalCenter
|
||||
right: parent.right
|
||||
margins: 2 * UM.Theme.getSize("default_margin").width
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: targetPrinterLabel
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
text:
|
||||
{
|
||||
if(printJob.assignedPrinter == null)
|
||||
{
|
||||
if(printJob.state == "error")
|
||||
{
|
||||
return catalog.i18nc("@label", "Waiting for: Unavailable printer")
|
||||
}
|
||||
return catalog.i18nc("@label", "Waiting for: First available")
|
||||
}
|
||||
else
|
||||
{
|
||||
return catalog.i18nc("@label", "Waiting for: ") + printJob.assignedPrinter.name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: contextButton.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function switchPopupState()
|
||||
{
|
||||
popup.visible ? popup.close() : popup.open()
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: contextButton
|
||||
text: "\u22EE" //Unicode; Three stacked points.
|
||||
font.pixelSize: 25
|
||||
width: 35
|
||||
height: width
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: contextButton.down || contextButton.hovered ? 1 : 0
|
||||
width: contextButton.width
|
||||
height: contextButton.height
|
||||
radius: 0.5 * width
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
|
||||
onClicked: parent.switchPopupState()
|
||||
}
|
||||
|
||||
Popup
|
||||
{
|
||||
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
|
||||
id: popup
|
||||
clip: true
|
||||
closePolicy: Popup.CloseOnPressOutsideParent
|
||||
x: parent.width - width
|
||||
y: contextButton.height
|
||||
width: 160
|
||||
height: contentItem.height + 2 * padding
|
||||
visible: false
|
||||
|
||||
transformOrigin: Popup.Top
|
||||
contentItem: Item
|
||||
{
|
||||
width: popup.width - 2 * popup.padding
|
||||
height: childrenRect.height + 15
|
||||
Button
|
||||
{
|
||||
id: sendToTopButton
|
||||
text: catalog.i18nc("@label", "Move to top")
|
||||
onClicked:
|
||||
{
|
||||
sendToTopConfirmationDialog.visible = true;
|
||||
popup.close();
|
||||
}
|
||||
width: parent.width
|
||||
enabled: OutputDevice.queuedPrintJobs[0].key != printJob.key
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
hoverEnabled: true
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: sendToTopButton.down || sendToTopButton.hovered ? 1 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog
|
||||
{
|
||||
id: sendToTopConfirmationDialog
|
||||
title: catalog.i18nc("@window:title", "Move print job to top")
|
||||
icon: StandardIcon.Warning
|
||||
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to move %1 to the top of the queue?").arg(printJob.name)
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
Component.onCompleted: visible = false
|
||||
onYes: OutputDevice.sendJobToTop(printJob.key)
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: deleteButton
|
||||
text: catalog.i18nc("@label", "Delete")
|
||||
onClicked:
|
||||
{
|
||||
deleteConfirmationDialog.visible = true;
|
||||
popup.close();
|
||||
}
|
||||
width: parent.width
|
||||
anchors.top: sendToTopButton.bottom
|
||||
hoverEnabled: true
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: deleteButton.down || deleteButton.hovered ? 1 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog
|
||||
{
|
||||
id: deleteConfirmationDialog
|
||||
title: catalog.i18nc("@window:title", "Delete print job")
|
||||
icon: StandardIcon.Warning
|
||||
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to delete %1?").arg(printJob.name)
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
Component.onCompleted: visible = false
|
||||
onYes: OutputDevice.deleteJobFromQueue(printJob.key)
|
||||
}
|
||||
}
|
||||
|
||||
background: Item
|
||||
{
|
||||
width: popup.width
|
||||
height: popup.height
|
||||
|
||||
DropShadow
|
||||
{
|
||||
anchors.fill: pointedRectangle
|
||||
radius: 5
|
||||
color: "#3F000000" // 25% shadow
|
||||
source: pointedRectangle
|
||||
transparentBorder: true
|
||||
verticalOffset: 2
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: pointedRectangle
|
||||
width: parent.width -10
|
||||
height: parent.height -10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: point
|
||||
height: 13
|
||||
width: 13
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
transform: Rotation { angle: 45}
|
||||
anchors.right: bloop.right
|
||||
y: 1
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: bloop
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition
|
||||
{
|
||||
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
|
||||
NumberAnimation { property: "visible"; duration: 75; }
|
||||
}
|
||||
enter: Transition
|
||||
{
|
||||
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
|
||||
NumberAnimation { property: "visible"; duration: 75; }
|
||||
}
|
||||
|
||||
onClosed: visible = false
|
||||
onOpened: visible = true
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
id: printerFamilyPills
|
||||
spacing: 0.5 * UM.Theme.getSize("default_margin").width
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: extrudersInfo.top
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
height: childrenRect.height
|
||||
Repeater
|
||||
{
|
||||
model: printJob.compatibleMachineFamilies
|
||||
|
||||
delegate: PrinterFamilyPill
|
||||
{
|
||||
text: modelData
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
padding: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
// PrintCore && Material config
|
||||
Row
|
||||
{
|
||||
id: extrudersInfo
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: childrenRect.height
|
||||
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: leftExtruderInfo
|
||||
width: Math.round(parent.width / 2)
|
||||
printCoreConfiguration: printJob.configuration.extruderConfigurations[0]
|
||||
}
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: rightExtruderInfo
|
||||
width: Math.round(parent.width / 2)
|
||||
printCoreConfiguration: printJob.configuration.extruderConfigurations[1]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
width: 2
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import UM 1.2 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
property alias color: background.color
|
||||
property alias text: familyNameLabel.text
|
||||
property var padding: 0
|
||||
implicitHeight: familyNameLabel.contentHeight + 2 * padding // Apply the padding to top and bottom.
|
||||
implicitWidth: familyNameLabel.contentWidth + implicitHeight // The extra height is added to ensure the radius doesn't cut something off.
|
||||
Rectangle
|
||||
{
|
||||
id: background
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
color: parent.color
|
||||
anchors.right: parent.right
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
radius: 0.5 * height
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: familyNameLabel
|
||||
anchors.centerIn: parent
|
||||
text: ""
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ import UM 1.3 as UM
|
|||
|
||||
Item
|
||||
{
|
||||
property var camera: null
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors.fill:parent
|
||||
|
|
@ -17,7 +19,7 @@ Item
|
|||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked: OutputDevice.setActivePrinter(null)
|
||||
onClicked: OutputDevice.setActiveCamera(null)
|
||||
z: 0
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +34,7 @@ Item
|
|||
width: 20 * screenScaleFactor
|
||||
height: 20 * screenScaleFactor
|
||||
|
||||
onClicked: OutputDevice.setActivePrinter(null)
|
||||
onClicked: OutputDevice.setActiveCamera(null)
|
||||
|
||||
style: ButtonStyle
|
||||
{
|
||||
|
|
@ -65,23 +67,24 @@ Item
|
|||
{
|
||||
if(visible)
|
||||
{
|
||||
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
|
||||
if(camera != null)
|
||||
{
|
||||
OutputDevice.activePrinter.camera.start()
|
||||
camera.start()
|
||||
}
|
||||
} else
|
||||
{
|
||||
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
|
||||
if(camera != null)
|
||||
{
|
||||
OutputDevice.activePrinter.camera.stop()
|
||||
camera.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
source:
|
||||
{
|
||||
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
|
||||
if(camera != null && camera.latestImage != null)
|
||||
{
|
||||
return OutputDevice.activePrinter.camera.latestImage;
|
||||
return camera.latestImage;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
@ -92,7 +95,7 @@ Item
|
|||
anchors.fill: cameraImage
|
||||
onClicked:
|
||||
{
|
||||
OutputDevice.setActivePrinter(null)
|
||||
OutputDevice.setActiveCamera(null)
|
||||
}
|
||||
z: 1
|
||||
}
|
||||
1
plugins/UM3NetworkPrinting/resources/svg/UM3-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 46.16 48"><defs><style>.cls-1{fill:#9a9a9a;}</style></defs><title>UM3-icon</title><g id="Symbols"><g id="system_overview_inactive" data-name="system overview inactive"><path id="Shape" class="cls-1" d="M18.4,12.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27a.22.22,0,0,0-.2-.2H16.67a.22.22,0,0,0-.2.2L18.2,12C18.3,12.2,18.3,12.2,18.4,12.2Z"/><path id="Shape-2" data-name="Shape" class="cls-1" d="M38.33,35.08H7.72a.48.48,0,0,0-.5.51V37a.48.48,0,0,0,.5.51H38.44a.48.48,0,0,0,.5-.51V35.59A.64.64,0,0,0,38.33,35.08Z"/><path id="Shape-3" data-name="Shape" class="cls-1" d="M0,0V48H3.76a2.86,2.86,0,0,1,2.13-1H40.27a2.86,2.86,0,0,1,2.13,1h3.76V0ZM41.28,37a2.83,2.83,0,0,1-2.84,2.84H7.72A2.84,2.84,0,0,1,4.88,37V5.49a.65.65,0,0,1,.61-.61H40.67a.65.65,0,0,1,.61.61Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 847 B |
1
plugins/UM3NetworkPrinting/resources/svg/UM3x-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 46.16 58"><defs><style>.cls-1{fill:#9a9a9a;}</style></defs><title>UM3x-icon</title><g id="Symbols"><g id="system_overview_inactive" data-name="system overview inactive"><path id="Shape" class="cls-1" d="M18.4,12.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27a.22.22,0,0,0-.2-.2H16.67a.22.22,0,0,0-.2.2L18.2,12C18.3,12.2,18.3,12.2,18.4,12.2Z"/><path id="Shape-2" data-name="Shape" class="cls-1" d="M38.33,45.08H7.72a.48.48,0,0,0-.5.51V47a.48.48,0,0,0,.5.51H38.44a.48.48,0,0,0,.5-.51V45.59A.64.64,0,0,0,38.33,45.08Z"/><path id="Shape-3" data-name="Shape" class="cls-1" d="M0,0V58H3.76a2.86,2.86,0,0,1,2.13-1H40.27a2.86,2.86,0,0,1,2.13,1h3.76V0ZM41.28,35.32V47a2.83,2.83,0,0,1-2.84,2.84H7.72A2.84,2.84,0,0,1,4.88,47V5.49a.65.65,0,0,1,.61-.61H40.67a.65.65,0,0,1,.61.61Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 854 B |
1
plugins/UM3NetworkPrinting/resources/svg/UMs5-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58"><defs><style>.cls-1{fill:none;}.cls-2{fill:#9a9a9a;}</style></defs><title>UMs5-icon</title><path class="cls-1" d="M33.83,12.33c-.1.2-.1.2-.2.2H24.37c-.1,0-.1,0-.2-.2L22.44,8.06a.22.22,0,0,1,.2-.2H35.36a.22.22,0,0,1,.2.2Z"/><path class="cls-2" d="M35.36,7.86H22.64a.22.22,0,0,0-.2.2l1.73,4.27c.1.2.1.2.2.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27A.22.22,0,0,0,35.36,7.86Z"/><path class="cls-2" d="M0,0V58H3.75a2.85,2.85,0,0,1,2.12-1H52.13a2.85,2.85,0,0,1,2.12,1H58V0ZM37.5,53.82a1.5,1.5,0,0,1-1.5,1.5H22a1.5,1.5,0,0,1-1.5-1.5v-4a1.5,1.5,0,0,1,1.5-1.5H36a1.5,1.5,0,0,1,1.5,1.5Zm15.63-18.5V47a2.83,2.83,0,0,1-2.83,2.84H38.5v0a2.5,2.5,0,0,0-2.5-2.5H22a2.5,2.5,0,0,0-2.5,2.5v0H7.7A2.83,2.83,0,0,1,4.87,47V5.49a.65.65,0,0,1,.6-.61H52.53a.65.65,0,0,1,.6.61Z"/></svg>
|
||||
|
After Width: | Height: | Size: 842 B |
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>aborted-icon</title><path d="M16,0A16,16,0,1,0,32,16,16,16,0,0,0,16,0Zm1.69,28.89a13,13,0,1,1,11.2-11.2A13,13,0,0,1,17.69,28.89Z"/><polygon points="20.6 9.28 16 13.88 11.4 9.28 9.28 11.4 13.88 16 9.28 20.6 11.4 22.72 16 18.12 20.6 22.72 22.72 20.6 18.12 16 22.72 11.4 20.6 9.28"/></svg>
|
||||
|
After Width: | Height: | Size: 386 B |
|
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 844 B |
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>approved-icon</title><path d="M16,29A13,13,0,1,0,3,16,13,13,0,0,0,16,29ZM8,14.59l6,5.3L23.89,9l2.22,2L14.18,24.11,6,16.83Z" fill="none"/><path d="M16,32A16,16,0,1,0,0,16,16,16,0,0,0,16,32ZM16,3A13,13,0,1,1,3,16,13,13,0,0,1,16,3Z"/><polygon points="26.11 11.01 23.89 8.99 13.96 19.89 8 14.59 6 16.83 14.18 24.11 26.11 11.01"/></svg>
|
||||
|
After Width: | Height: | Size: 431 B |
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 438 B |
|
Before Width: | Height: | Size: 194 B After Width: | Height: | Size: 194 B |
1
plugins/UM3NetworkPrinting/resources/svg/paused-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>paused-icon</title><path d="M16,0A16,16,0,1,0,32,16,16,16,0,0,0,16,0Zm0,29A13,13,0,1,1,29,16,13,13,0,0,1,16,29Z"/><rect x="11.5" y="9" width="3" height="14"/><rect x="17.5" y="9" width="3" height="14"/></svg>
|
||||
|
After Width: | Height: | Size: 308 B |
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>warning-icon</title><path d="M18.09,1.31A2.35,2.35,0,0,0,16,0a2.31,2.31,0,0,0-2.09,1.31L.27,28.44A2.49,2.49,0,0,0,.11,30.3a2.38,2.38,0,0,0,1.16,1.42A2.33,2.33,0,0,0,2.36,32H29.64A2.4,2.4,0,0,0,32,29.57a2.55,2.55,0,0,0-.27-1.14ZM3.34,29,16,3.83,28.66,29Z"/><polygon points="13.94 25.19 13.94 25.19 13.94 25.19 13.94 25.19"/><polygon points="14.39 21.68 17.61 21.68 18.11 11.85 13.89 11.85 14.39 21.68"/><path d="M16.06,23.08a2.19,2.19,0,0,0-1.56,3.66,2.14,2.14,0,0,0,1.56.55,2.06,2.06,0,0,0,1.54-.55,2.1,2.1,0,0,0,.55-1.55,2.17,2.17,0,0,0-.53-1.55A2.05,2.05,0,0,0,16.06,23.08Z"/></svg>
|
||||
|
After Width: | Height: | Size: 684 B |
|
|
@ -4,19 +4,21 @@
|
|||
from typing import Any, cast, Optional, Set, Tuple, Union
|
||||
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
|
||||
from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously.
|
||||
from UM.FileHandler.FileWriter import FileWriter # To choose based on the output file mode (text vs. binary).
|
||||
from UM.FileHandler.WriteFileJob import WriteFileJob # To call the file writer asynchronously.
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Mesh.MeshWriter import MeshWriter # For typing
|
||||
|
||||
from UM.Message import Message
|
||||
from UM.Qt.Duration import Duration, DurationFormat
|
||||
from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing.
|
||||
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||
from UM.Version import Version #To check against firmware versions for support.
|
||||
from UM.OutputDevice import OutputDeviceError # To show that something went wrong when writing.
|
||||
from UM.Scene.SceneNode import SceneNode # For typing.
|
||||
from UM.Version import Version # To check against firmware versions for support.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
|
|
@ -27,14 +29,14 @@ from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
|
|||
from .SendMaterialJob import SendMaterialJob
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtGui import QDesktopServices, QImage
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
|
||||
|
||||
from time import time
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, List, Set
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
import io #To create the correct buffers for sending data to the printer.
|
||||
import io # To create the correct buffers for sending data to the printer.
|
||||
import json
|
||||
import os
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ i18n_catalog = i18nCatalog("cura")
|
|||
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||
printJobsChanged = pyqtSignal()
|
||||
activePrinterChanged = pyqtSignal()
|
||||
activeCameraChanged = pyqtSignal()
|
||||
|
||||
# This is a bit of a hack, as the notify can only use signals that are defined by the class that they are in.
|
||||
# Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
|
||||
|
|
@ -59,24 +62,24 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self._print_jobs = [] # type: List[PrintJobOutputModel]
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
|
||||
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterMonitorItem.qml")
|
||||
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterControlItem.qml")
|
||||
|
||||
# See comments about this hack with the clusterPrintersChanged signal
|
||||
self.printersChanged.connect(self.clusterPrintersChanged)
|
||||
|
||||
self._accepts_commands = True #type: bool
|
||||
self._accepts_commands = True # type: bool
|
||||
|
||||
# Cluster does not have authentication, so default to authenticated
|
||||
self._authentication_state = AuthState.Authenticated
|
||||
|
||||
self._error_message = None #type: Optional[Message]
|
||||
self._write_job_progress_message = None #type: Optional[Message]
|
||||
self._progress_message = None #type: Optional[Message]
|
||||
self._error_message = None # type: Optional[Message]
|
||||
self._write_job_progress_message = None # type: Optional[Message]
|
||||
self._progress_message = None # type: Optional[Message]
|
||||
|
||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||
|
||||
self._printer_selection_dialog = None #type: QObject
|
||||
self._printer_selection_dialog = None # type: QObject
|
||||
|
||||
self.setPriority(3) # Make sure the output device gets selected above local file output
|
||||
self.setName(self._id)
|
||||
|
|
@ -87,32 +90,35 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self._printer_uuid_to_unique_name_mapping = {} # type: Dict[str, str]
|
||||
|
||||
self._finished_jobs = [] # type: List[PrintJobOutputModel]
|
||||
self._finished_jobs = [] # type: List[PrintJobOutputModel]
|
||||
|
||||
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
||||
self._cluster_size = int(properties.get(b"cluster_size", 0)) # type: int
|
||||
|
||||
self._latest_reply_handler = None #type: Optional[QNetworkReply]
|
||||
self._latest_reply_handler = None # type: Optional[QNetworkReply]
|
||||
self._sending_job = None
|
||||
|
||||
self._active_camera = None # type: Optional[NetworkCamera]
|
||||
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||
self.writeStarted.emit(self)
|
||||
|
||||
self.sendMaterialProfiles()
|
||||
|
||||
#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 = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
||||
|
||||
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
#Create a list from the supported file formats string.
|
||||
# Create a list from the supported file formats string.
|
||||
if not global_stack:
|
||||
Logger.log("e", "Missing global stack!")
|
||||
return
|
||||
|
||||
machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
|
||||
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
|
||||
#Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
|
||||
# Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
|
||||
if "application/x-ufp" not in machine_file_formats and Version(self.firmwareVersion) >= Version("4.4"):
|
||||
machine_file_formats = ["application/x-ufp"] + machine_file_formats
|
||||
|
||||
|
|
@ -125,7 +131,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
raise OutputDeviceError.WriteRequestFailedError(i18n_catalog.i18nc("@info:status", "There are no file formats available to write with!"))
|
||||
preferred_format = file_formats[0]
|
||||
|
||||
#Just take the first file format available.
|
||||
# Just take the first file format available.
|
||||
if file_handler is not None:
|
||||
writer = file_handler.getWriterByMimeType(cast(str, preferred_format["mime_type"]))
|
||||
else:
|
||||
|
|
@ -135,29 +141,30 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
Logger.log("e", "Unexpected error when trying to get the FileWriter")
|
||||
return
|
||||
|
||||
#This function pauses with the yield, waiting on instructions on which printer it needs to print with.
|
||||
# This function pauses with the yield, waiting on instructions on which printer it needs to print with.
|
||||
if not writer:
|
||||
Logger.log("e", "Missing file or mesh writer!")
|
||||
return
|
||||
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
|
||||
self._sending_job.send(None) #Start the generator.
|
||||
if self._sending_job is not None:
|
||||
self._sending_job.send(None) # Start the generator.
|
||||
|
||||
if len(self._printers) > 1: #We need to ask the user.
|
||||
self._spawnPrinterSelectionDialog()
|
||||
is_job_sent = True
|
||||
else: #Just immediately continue.
|
||||
self._sending_job.send("") #No specifically selected printer.
|
||||
is_job_sent = self._sending_job.send(None)
|
||||
if len(self._printers) > 1: # We need to ask the user.
|
||||
self._spawnPrinterSelectionDialog()
|
||||
is_job_sent = True
|
||||
else: # Just immediately continue.
|
||||
self._sending_job.send("") # No specifically selected printer.
|
||||
is_job_sent = self._sending_job.send(None)
|
||||
|
||||
def _spawnPrinterSelectionDialog(self):
|
||||
if self._printer_selection_dialog is None:
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml")
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/PrintWindow.qml")
|
||||
self._printer_selection_dialog = CuraApplication.getInstance().createQmlComponent(path, {"OutputDevice": self})
|
||||
if self._printer_selection_dialog is not None:
|
||||
self._printer_selection_dialog.show()
|
||||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def clusterSize(self):
|
||||
def clusterSize(self) -> int:
|
||||
return self._cluster_size
|
||||
|
||||
## Allows the user to choose a printer to print with from the printer
|
||||
|
|
@ -165,7 +172,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
# \param target_printer The name of the printer to target.
|
||||
@pyqtSlot(str)
|
||||
def selectPrinter(self, target_printer: str = "") -> None:
|
||||
self._sending_job.send(target_printer)
|
||||
if self._sending_job is not None:
|
||||
self._sending_job.send(target_printer)
|
||||
|
||||
@pyqtSlot()
|
||||
def cancelPrintSelection(self) -> None:
|
||||
|
|
@ -214,8 +222,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
job.start()
|
||||
|
||||
yield True #Return that we had success!
|
||||
yield #To prevent having to catch the StopIteration exception.
|
||||
yield True # Return that we had success!
|
||||
yield # To prevent having to catch the StopIteration exception.
|
||||
|
||||
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
|
||||
if self._write_job_progress_message:
|
||||
|
|
@ -240,7 +248,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
file_name = CuraApplication.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
|
||||
|
||||
output = stream.getvalue() #Either str or bytes depending on the output mode.
|
||||
output = stream.getvalue() # Either str or bytes depending on the output mode.
|
||||
if isinstance(stream, io.StringIO):
|
||||
output = cast(str, output).encode("utf-8")
|
||||
output = cast(bytes, output)
|
||||
|
|
@ -253,6 +261,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
||||
return self._active_printer
|
||||
|
||||
@pyqtProperty(QObject, notify=activeCameraChanged)
|
||||
def activeCamera(self) -> Optional[NetworkCamera]:
|
||||
return self._active_camera
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
|
||||
if self._active_printer != printer:
|
||||
|
|
@ -261,6 +273,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._active_printer = printer
|
||||
self.activePrinterChanged.emit()
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def setActiveCamera(self, camera: Optional[NetworkCamera]) -> None:
|
||||
if self._active_camera != camera:
|
||||
if self._active_camera:
|
||||
self._active_camera.stop()
|
||||
|
||||
self._active_camera = camera
|
||||
|
||||
if self._active_camera:
|
||||
self._active_camera.start()
|
||||
|
||||
self.activeCameraChanged.emit()
|
||||
|
||||
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
|
||||
if self._progress_message:
|
||||
self._progress_message.hide()
|
||||
|
|
@ -279,8 +304,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
# If successfully sent:
|
||||
if bytes_sent == bytes_total:
|
||||
# Show a confirmation to the user so they know the job was sucessful and provide the option to switch to the
|
||||
# monitor tab.
|
||||
# Show a confirmation to the user so they know the job was sucessful and provide the option to switch to
|
||||
# the monitor tab.
|
||||
self._success_message = Message(
|
||||
i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."),
|
||||
lifetime=5, dismissable=True,
|
||||
|
|
@ -329,7 +354,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
|
||||
return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
|
||||
return [print_job for print_job in self._print_jobs if print_job.state == "queued" or print_job.state == "error"]
|
||||
|
||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||
def activePrintJobs(self) -> List[PrintJobOutputModel]:
|
||||
|
|
@ -348,6 +373,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])})
|
||||
return result
|
||||
|
||||
@pyqtProperty("QVariantList", notify=clusterPrintersChanged)
|
||||
def printers(self):
|
||||
return self._printers
|
||||
|
||||
@pyqtSlot(int, result = str)
|
||||
def formatDuration(self, seconds: int) -> str:
|
||||
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
||||
|
|
@ -364,6 +393,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
||||
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def sendJobToTop(self, print_job_uuid: str) -> None:
|
||||
# This function is part of the output device (and not of the printjob output model) as this type of operation
|
||||
# is a modification of the cluster queue and not of the actual job.
|
||||
data = "{\"to_position\": 0}"
|
||||
self.put("print_jobs/{uuid}/move_to_position".format(uuid = print_job_uuid), data, on_finished=None)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def deleteJobFromQueue(self, print_job_uuid: str) -> None:
|
||||
# This function is part of the output device (and not of the printjob output model) as this type of operation
|
||||
# is a modification of the cluster queue and not of the actual job.
|
||||
self.delete("print_jobs/{uuid}".format(uuid = print_job_uuid), on_finished=None)
|
||||
|
||||
def _printJobStateChanged(self) -> None:
|
||||
username = self._getUserName()
|
||||
|
||||
|
|
@ -392,11 +434,26 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
super().connect()
|
||||
self.sendMaterialProfiles()
|
||||
|
||||
def _onGetPreviewImageFinished(self, reply: QNetworkReply) -> None:
|
||||
reply_url = reply.url().toString()
|
||||
|
||||
uuid = reply_url[reply_url.find("print_jobs/")+len("print_jobs/"):reply_url.rfind("/preview_image")]
|
||||
|
||||
print_job = findByKey(self._print_jobs, uuid)
|
||||
if print_job:
|
||||
image = QImage()
|
||||
image.loadFromData(reply.readAll())
|
||||
print_job.updatePreviewImage(image)
|
||||
|
||||
def _update(self) -> None:
|
||||
super()._update()
|
||||
self.get("printers/", on_finished = self._onGetPrintersDataFinished)
|
||||
self.get("print_jobs/", on_finished = self._onGetPrintJobsFinished)
|
||||
|
||||
for print_job in self._print_jobs:
|
||||
if print_job.getPreviewImage() is None:
|
||||
self.get("print_jobs/{uuid}/preview_image".format(uuid=print_job.key), on_finished=self._onGetPreviewImageFinished)
|
||||
|
||||
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
|
||||
if not checkValidGetReply(reply):
|
||||
return
|
||||
|
|
@ -407,16 +464,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
print_jobs_seen = []
|
||||
job_list_changed = False
|
||||
for print_job_data in result:
|
||||
for idx, print_job_data in enumerate(result):
|
||||
print_job = findByKey(self._print_jobs, print_job_data["uuid"])
|
||||
|
||||
if print_job is None:
|
||||
print_job = self._createPrintJobModel(print_job_data)
|
||||
job_list_changed = True
|
||||
elif not job_list_changed:
|
||||
# Check if the order of the jobs has changed since the last check
|
||||
if self._print_jobs.index(print_job) != idx:
|
||||
job_list_changed = True
|
||||
|
||||
self._updatePrintJob(print_job, print_job_data)
|
||||
|
||||
if print_job.state != "queued": # Print job should be assigned to a printer.
|
||||
if print_job.state != "queued" and print_job.state != "error": # Print job should be assigned to a printer.
|
||||
if print_job.state in ["failed", "finished", "aborted", "none"]:
|
||||
# Print job was already completed, so don't attach it to a printer.
|
||||
printer = None
|
||||
|
|
@ -437,6 +497,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
job_list_changed = job_list_changed or self._removeJob(removed_job)
|
||||
|
||||
if job_list_changed:
|
||||
# Override the old list with the new list (either because jobs were removed / added or order changed)
|
||||
self._print_jobs = print_jobs_seen
|
||||
self.printJobsChanged.emit() # Do a single emit for all print job changes.
|
||||
|
||||
def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
|
||||
|
|
@ -478,16 +540,59 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
|
||||
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
||||
key=data["uuid"], name= data["name"])
|
||||
|
||||
configuration = ConfigurationModel()
|
||||
extruders = [ExtruderConfigurationModel(position = idx) for idx in range(0, self._number_of_extruders)]
|
||||
for index in range(0, self._number_of_extruders):
|
||||
try:
|
||||
extruder_data = data["configuration"][index]
|
||||
except IndexError:
|
||||
continue
|
||||
extruder = extruders[int(data["configuration"][index]["extruder_index"])]
|
||||
extruder.setHotendID(extruder_data.get("print_core_id", ""))
|
||||
extruder.setMaterial(self._createMaterialOutputModel(extruder_data.get("material", {})))
|
||||
|
||||
configuration.setExtruderConfigurations(extruders)
|
||||
print_job.updateConfiguration(configuration)
|
||||
print_job.setCompatibleMachineFamilies(data.get("compatible_machine_families", []))
|
||||
print_job.stateChanged.connect(self._printJobStateChanged)
|
||||
self._print_jobs.append(print_job)
|
||||
return print_job
|
||||
|
||||
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
|
||||
print_job.updateTimeTotal(data["time_total"])
|
||||
print_job.updateTimeElapsed(data["time_elapsed"])
|
||||
print_job.updateState(data["status"])
|
||||
impediments_to_printing = data.get("impediments_to_printing", [])
|
||||
print_job.updateOwner(data["owner"])
|
||||
|
||||
status_set_by_impediment = False
|
||||
for impediment in impediments_to_printing:
|
||||
if impediment["severity"] == "UNFIXABLE":
|
||||
status_set_by_impediment = True
|
||||
print_job.updateState("error")
|
||||
break
|
||||
|
||||
if not status_set_by_impediment:
|
||||
print_job.updateState(data["status"])
|
||||
|
||||
|
||||
def _createMaterialOutputModel(self, material_data) -> MaterialOutputModel:
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type="material", GUID=material_data["guid"])
|
||||
if containers:
|
||||
color = containers[0].getMetaDataEntry("color_code")
|
||||
brand = containers[0].getMetaDataEntry("brand")
|
||||
material_type = containers[0].getMetaDataEntry("material")
|
||||
name = containers[0].getName()
|
||||
else:
|
||||
Logger.log("w",
|
||||
"Unable to find material with guid {guid}. Using data as provided by cluster".format(
|
||||
guid=material_data["guid"]))
|
||||
color = material_data["color"]
|
||||
brand = material_data["brand"]
|
||||
material_type = material_data["material"]
|
||||
name = "Empty" if material_data["material"] == "empty" else "Unknown"
|
||||
return MaterialOutputModel(guid=material_data["guid"], type=material_type,
|
||||
brand=brand, color=color, name=name)
|
||||
|
||||
def _updatePrinter(self, printer: PrinterOutputModel, data: Dict[str, Any]) -> None:
|
||||
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
|
||||
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
|
||||
|
|
@ -523,24 +628,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
material_data = extruder_data["material"]
|
||||
if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_data["guid"]:
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type="material",
|
||||
GUID=material_data["guid"])
|
||||
if containers:
|
||||
color = containers[0].getMetaDataEntry("color_code")
|
||||
brand = containers[0].getMetaDataEntry("brand")
|
||||
material_type = containers[0].getMetaDataEntry("material")
|
||||
name = containers[0].getName()
|
||||
else:
|
||||
Logger.log("w",
|
||||
"Unable to find material with guid {guid}. Using data as provided by cluster".format(
|
||||
guid=material_data["guid"]))
|
||||
color = material_data["color"]
|
||||
brand = material_data["brand"]
|
||||
material_type = material_data["material"]
|
||||
name = "Empty" if material_data["material"] == "empty" else "Unknown"
|
||||
|
||||
material = MaterialOutputModel(guid=material_data["guid"], type=material_type,
|
||||
brand=brand, color=color, name=name)
|
||||
material = self._createMaterialOutputModel(material_data)
|
||||
extruder.updateActiveMaterial(material)
|
||||
|
||||
def _removeJob(self, job: PrintJobOutputModel) -> bool:
|
||||
|
|
@ -568,6 +656,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
job = SendMaterialJob(device = self)
|
||||
job.run()
|
||||
|
||||
|
||||
def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]:
|
||||
try:
|
||||
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
|
|
@ -586,8 +675,8 @@ def checkValidGetReply(reply: QNetworkReply) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
|
||||
for item in list:
|
||||
def findByKey(lst: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
|
||||
for item in lst:
|
||||
if item.key == key:
|
||||
return item
|
||||
return None
|
||||