mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-06 21:44:01 -06:00
Merge branch 'master' into feature_unify_pause_at_height
This commit is contained in:
commit
4994eb291e
198 changed files with 18382 additions and 635 deletions
|
@ -86,7 +86,7 @@ class ThreeMFReader(MeshReader):
|
|||
|
||||
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.
|
||||
# \returns Scene node.
|
||||
def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode) -> Optional[SceneNode]:
|
||||
def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]:
|
||||
self._object_count += 1
|
||||
node_name = "Object %s" % self._object_count
|
||||
|
||||
|
@ -104,6 +104,10 @@ class ThreeMFReader(MeshReader):
|
|||
vertices = numpy.resize(data, (int(data.size / 3), 3))
|
||||
mesh_builder.setVertices(vertices)
|
||||
mesh_builder.calculateNormals(fast=True)
|
||||
if file_name:
|
||||
# The filename is used to give the user the option to reload the file if it is changed on disk
|
||||
# It is only set for the root node of the 3mf file
|
||||
mesh_builder.setFileName(file_name)
|
||||
mesh_data = mesh_builder.build()
|
||||
|
||||
if len(mesh_data.getVertices()):
|
||||
|
@ -171,7 +175,7 @@ class ThreeMFReader(MeshReader):
|
|||
scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
|
||||
self._unit = scene_3mf.getUnit()
|
||||
for node in scene_3mf.getSceneNodes():
|
||||
um_node = self._convertSavitarNodeToUMNode(node)
|
||||
um_node = self._convertSavitarNodeToUMNode(node, file_name)
|
||||
if um_node is None:
|
||||
continue
|
||||
# compensate for original center position, if object(s) is/are not around its zero position
|
||||
|
|
|
@ -118,7 +118,7 @@ class AMFReader(MeshReader):
|
|||
mesh.merge_vertices()
|
||||
mesh.remove_unreferenced_vertices()
|
||||
mesh.fix_normals()
|
||||
mesh_data = self._toMeshData(mesh)
|
||||
mesh_data = self._toMeshData(mesh, file_name)
|
||||
|
||||
new_node = CuraSceneNode()
|
||||
new_node.setSelectable(True)
|
||||
|
@ -147,7 +147,13 @@ class AMFReader(MeshReader):
|
|||
|
||||
return group_node
|
||||
|
||||
def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
|
||||
## Converts a Trimesh to Uranium's MeshData.
|
||||
# \param tri_node A Trimesh containing the contents of a file that was
|
||||
# just read.
|
||||
# \param file_name The full original filename used to watch for changes
|
||||
# \return Mesh data from the Trimesh in a way that Uranium can understand
|
||||
# it.
|
||||
def _toMeshData(self, tri_node: trimesh.base.Trimesh, file_name: str = "") -> MeshData:
|
||||
tri_faces = tri_node.faces
|
||||
tri_vertices = tri_node.vertices
|
||||
|
||||
|
@ -169,5 +175,5 @@ class AMFReader(MeshReader):
|
|||
indices = numpy.asarray(indices, dtype = numpy.int32)
|
||||
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
|
||||
|
||||
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals)
|
||||
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals,file_name = file_name)
|
||||
return mesh_data
|
||||
|
|
|
@ -421,7 +421,10 @@ class CuraEngineBackend(QObject, Backend):
|
|||
|
||||
if job.getResult() == StartJobResult.NothingToSlice:
|
||||
if self._application.platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume or are assigned to a disabled extruder. Please scale or rotate models to fit, or enable an extruder."),
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Please review settings and check if your models:"
|
||||
"\n- Fit within the build volume"
|
||||
"\n- Are assigned to an enabled extruder"
|
||||
"\n- Are not all set as modifier meshes"),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
self.setState(BackendState.Error)
|
||||
|
|
|
@ -44,6 +44,7 @@ class FirmwareUpdateCheckerJob(Job):
|
|||
try:
|
||||
# CURA-6698 Create an SSL context and use certifi CA certificates for verification.
|
||||
context = ssl.SSLContext(protocol = ssl.PROTOCOL_TLSv1_2)
|
||||
context.verify_mode = ssl.CERT_REQUIRED
|
||||
context.load_verify_locations(cafile = certifi.where())
|
||||
|
||||
request = urllib.request.Request(url, headers = self._headers)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import numpy
|
||||
|
@ -96,7 +96,7 @@ class ImageReader(MeshReader):
|
|||
texel_width = 1.0 / (width_minus_one) * scale_vector.x
|
||||
texel_height = 1.0 / (height_minus_one) * scale_vector.z
|
||||
|
||||
height_data = numpy.zeros((height, width), dtype=numpy.float32)
|
||||
height_data = numpy.zeros((height, width), dtype = numpy.float32)
|
||||
|
||||
for x in range(0, width):
|
||||
for y in range(0, height):
|
||||
|
@ -112,7 +112,7 @@ class ImageReader(MeshReader):
|
|||
height_data = 1 - height_data
|
||||
|
||||
for _ in range(0, blur_iterations):
|
||||
copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode= "edge")
|
||||
copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode = "edge")
|
||||
|
||||
height_data += copy[1:-1, 2:]
|
||||
height_data += copy[1:-1, :-2]
|
||||
|
@ -165,7 +165,7 @@ class ImageReader(MeshReader):
|
|||
offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height
|
||||
|
||||
# offsets for each texel quad
|
||||
heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1)
|
||||
heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype = numpy.float32), offsetsz], 1)
|
||||
heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3)
|
||||
|
||||
# apply height data to y values
|
||||
|
@ -174,7 +174,7 @@ class ImageReader(MeshReader):
|
|||
heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1)
|
||||
heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)
|
||||
|
||||
heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3)
|
||||
heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype = numpy.int32).reshape(-1, 3)
|
||||
|
||||
mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3)
|
||||
mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices
|
||||
|
@ -223,7 +223,7 @@ class ImageReader(MeshReader):
|
|||
mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
|
||||
mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)
|
||||
|
||||
mesh.calculateNormals(fast=True)
|
||||
mesh.calculateNormals(fast = True)
|
||||
|
||||
scene_node.setMeshData(mesh.build())
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
|
@ -33,9 +33,9 @@ class ImageReaderUI(QObject):
|
|||
self.base_height = 0.4
|
||||
self.peak_height = 2.5
|
||||
self.smoothing = 1
|
||||
self.lighter_is_higher = False;
|
||||
self.use_transparency_model = True;
|
||||
self.transmittance_1mm = 50.0; # based on pearl PLA
|
||||
self.lighter_is_higher = False
|
||||
self.use_transparency_model = True
|
||||
self.transmittance_1mm = 50.0 # based on pearl PLA
|
||||
|
||||
self._ui_lock = threading.Lock()
|
||||
self._cancelled = False
|
||||
|
@ -85,7 +85,7 @@ class ImageReaderUI(QObject):
|
|||
Logger.log("d", "Creating ImageReader config UI")
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("ImageReader"), "ConfigUI.qml")
|
||||
self._ui_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
self._ui_view.setFlags(self._ui_view.flags() & ~Qt.WindowCloseButtonHint & ~Qt.WindowMinimizeButtonHint & ~Qt.WindowMaximizeButtonHint);
|
||||
self._ui_view.setFlags(self._ui_view.flags() & ~Qt.WindowCloseButtonHint & ~Qt.WindowMinimizeButtonHint & ~Qt.WindowMaximizeButtonHint)
|
||||
self._disable_size_callbacks = False
|
||||
|
||||
@pyqtSlot()
|
||||
|
|
|
@ -71,7 +71,7 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
|
|||
|
||||
# Add all instances that are not added, but are in visibility list
|
||||
for item in visible:
|
||||
if settings.getInstance(item) is not None: # Setting was not added already.
|
||||
if settings.getInstance(item) is None: # Setting was not added already.
|
||||
definition = self._stack.getSettingDefinition(item)
|
||||
if definition:
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
|
|
|
@ -49,18 +49,6 @@ Item
|
|||
visibility_handler.addSkipResetSetting(currentMeshType)
|
||||
}
|
||||
|
||||
function setOverhangsMeshType()
|
||||
{
|
||||
if (infillOnlyCheckbox.checked)
|
||||
{
|
||||
setMeshType(infillMeshType)
|
||||
}
|
||||
else
|
||||
{
|
||||
setMeshType(cuttingMeshType)
|
||||
}
|
||||
}
|
||||
|
||||
function setMeshType(type)
|
||||
{
|
||||
UM.ActiveTool.setProperty("MeshType", type)
|
||||
|
@ -140,26 +128,43 @@ Item
|
|||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
CheckBox
|
||||
|
||||
ComboBox
|
||||
{
|
||||
id: infillOnlyCheckbox
|
||||
id: infillOnlyComboBox
|
||||
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
|
||||
|
||||
text: catalog.i18nc("@action:checkbox", "Infill only");
|
||||
model: ListModel
|
||||
{
|
||||
id: infillOnlyComboBoxModel
|
||||
|
||||
style: UM.Theme.styles.checkbox;
|
||||
Component.onCompleted: {
|
||||
append({ text: catalog.i18nc("@item:inlistbox", "Infill mesh only") })
|
||||
append({ text: catalog.i18nc("@item:inlistbox", "Cutting mesh") })
|
||||
}
|
||||
}
|
||||
|
||||
visible: currentMeshType === infillMeshType || currentMeshType === cuttingMeshType
|
||||
onClicked: setOverhangsMeshType()
|
||||
|
||||
|
||||
onActivated:
|
||||
{
|
||||
if (index == 0){
|
||||
setMeshType(infillMeshType)
|
||||
} else {
|
||||
setMeshType(cuttingMeshType)
|
||||
}
|
||||
}
|
||||
|
||||
Binding
|
||||
{
|
||||
target: infillOnlyCheckbox
|
||||
property: "checked"
|
||||
value: currentMeshType === infillMeshType
|
||||
target: infillOnlyComboBox
|
||||
property: "currentIndex"
|
||||
value: currentMeshType === infillMeshType ? 0 : 1
|
||||
}
|
||||
}
|
||||
|
||||
Column // Settings Dialog
|
||||
Column // List of selected Settings to override for the selected object
|
||||
{
|
||||
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
|
||||
// It kinda looks ugly otherwise (big panel, no content on it)
|
||||
|
|
|
@ -82,6 +82,7 @@ class PerObjectSettingsTool(Tool):
|
|||
selected_object.addDecorator(SettingOverrideDecorator())
|
||||
stack = selected_object.callDecoration("getStack")
|
||||
|
||||
settings_visibility_changed = False
|
||||
settings = stack.getTop()
|
||||
for property_key in ["infill_mesh", "cutting_mesh", "support_mesh", "anti_overhang_mesh"]:
|
||||
if property_key != mesh_type:
|
||||
|
@ -97,17 +98,20 @@ class PerObjectSettingsTool(Tool):
|
|||
|
||||
for property_key in ["top_bottom_thickness", "wall_thickness"]:
|
||||
if mesh_type == "infill_mesh":
|
||||
if not settings.getInstance(property_key):
|
||||
if settings.getInstance(property_key) is None:
|
||||
definition = stack.getSettingDefinition(property_key)
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
new_instance.setProperty("value", 0)
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
visible = self.visibility_handler.getVisible()
|
||||
visible.add(property_key)
|
||||
self.visibility_handler.setVisible(visible)
|
||||
settings_visibility_changed = True
|
||||
|
||||
elif old_mesh_type == "infill_mesh" and settings.getInstance(property_key) and settings.getProperty(property_key, "value") == 0:
|
||||
settings.removeInstance(property_key)
|
||||
settings_visibility_changed = True
|
||||
|
||||
if settings_visibility_changed:
|
||||
self.visibility_handler.forceVisibilityChanged()
|
||||
|
||||
self.propertyChanged.emit()
|
||||
return True
|
||||
|
|
|
@ -484,15 +484,53 @@ UM.Dialog
|
|||
onClicked: dialog.accept()
|
||||
}
|
||||
|
||||
Cura.SecondaryButton
|
||||
Item
|
||||
{
|
||||
objectName: "postProcessingSaveAreaButton"
|
||||
visible: activeScriptsList.count > 0
|
||||
height: UM.Theme.getSize("action_button").height
|
||||
width: height
|
||||
tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
|
||||
onClicked: dialog.show()
|
||||
iconSource: "postprocessing.svg"
|
||||
fixedWidthMode: true
|
||||
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
height: UM.Theme.getSize("action_button").height
|
||||
tooltip:
|
||||
{
|
||||
var tipText = catalog.i18nc("@info:tooltip", "Change active post-processing scripts.");
|
||||
if (activeScriptsList.count > 0)
|
||||
{
|
||||
tipText += "<br><br>" + catalog.i18ncp("@info:tooltip",
|
||||
"The following script is active:",
|
||||
"The following scripts are active:",
|
||||
activeScriptsList.count
|
||||
) + "<ul>";
|
||||
for(var i = 0; i < activeScriptsList.count; i++)
|
||||
{
|
||||
tipText += "<li>" + manager.getScriptLabelByKey(manager.scriptList[i]) + "</li>";
|
||||
}
|
||||
tipText += "</ul>";
|
||||
}
|
||||
return tipText
|
||||
}
|
||||
toolTipContentAlignment: Cura.ToolTip.ContentAlignment.AlignLeft
|
||||
onClicked: dialog.show()
|
||||
iconSource: "postprocessing.svg"
|
||||
fixedWidthMode: false
|
||||
}
|
||||
|
||||
Cura.NotificationIcon
|
||||
{
|
||||
id: activeScriptCountIcon
|
||||
visible: activeScriptsList.count > 0
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
rightMargin: (-0.5 * width) | 0
|
||||
topMargin: (-0.5 * height) | 0
|
||||
}
|
||||
|
||||
labelText: activeScriptsList.count
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ Item
|
|||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
}
|
||||
height: UM.Theme.getSize("toolbox_footer_button").height
|
||||
text: catalog.i18nc("@info:button", "Quit Cura")
|
||||
text: catalog.i18nc("@info:button, %1 is the application name", "Quit %1").arg(CuraApplication.applicationDisplayName)
|
||||
onClicked: toolbox.restart()
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ class TrimeshReader(MeshReader):
|
|||
mesh.merge_vertices()
|
||||
mesh.remove_unreferenced_vertices()
|
||||
mesh.fix_normals()
|
||||
mesh_data = self._toMeshData(mesh)
|
||||
mesh_data = self._toMeshData(mesh, file_name)
|
||||
|
||||
file_base_name = os.path.basename(file_name)
|
||||
new_node = CuraSceneNode()
|
||||
|
@ -133,9 +133,10 @@ class TrimeshReader(MeshReader):
|
|||
## Converts a Trimesh to Uranium's MeshData.
|
||||
# \param tri_node A Trimesh containing the contents of a file that was
|
||||
# just read.
|
||||
# \param file_name The full original filename used to watch for changes
|
||||
# \return Mesh data from the Trimesh in a way that Uranium can understand
|
||||
# it.
|
||||
def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
|
||||
def _toMeshData(self, tri_node: trimesh.base.Trimesh, file_name: str = "") -> MeshData:
|
||||
tri_faces = tri_node.faces
|
||||
tri_vertices = tri_node.vertices
|
||||
|
||||
|
@ -157,5 +158,5 @@ class TrimeshReader(MeshReader):
|
|||
indices = numpy.asarray(indices, dtype = numpy.int32)
|
||||
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
|
||||
|
||||
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals)
|
||||
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals, file_name = file_name)
|
||||
return mesh_data
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from time import time
|
||||
import os
|
||||
from typing import List, Optional, cast
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
|
@ -191,8 +192,9 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
def _onPrintJobCreated(self, job: ExportFileJob) -> None:
|
||||
output = job.getOutput()
|
||||
self._tool_path = output # store the tool path to prevent re-uploading when printing the same file again
|
||||
file_name = job.getFileName()
|
||||
request = CloudPrintJobUploadRequest(
|
||||
job_name=job.getFileName(),
|
||||
job_name=os.path.splitext(file_name)[0],
|
||||
file_size=len(output),
|
||||
content_type=job.getMimeType(),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue