mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-09 14:55:03 -06:00
Merge branch 'master' into feature_first_layer_travel_speed
This commit is contained in:
commit
20b96b4716
54 changed files with 1208 additions and 10772 deletions
|
@ -13,6 +13,7 @@ from UM.Math.Color import Color
|
|||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Math.Polygon import Polygon
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal
|
||||
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
@ -49,6 +50,8 @@ def approximatedCircleVertices(r):
|
|||
class BuildVolume(SceneNode):
|
||||
VolumeOutlineColor = Color(12, 169, 227, 255)
|
||||
|
||||
raftThicknessChanged = Signal()
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
|
@ -69,7 +72,6 @@ class BuildVolume(SceneNode):
|
|||
|
||||
self._raft_thickness = 0.0
|
||||
self._adhesion_type = None
|
||||
self._raft_mesh = None
|
||||
self._platform = Platform(self)
|
||||
|
||||
self._active_container_stack = None
|
||||
|
@ -103,8 +105,6 @@ class BuildVolume(SceneNode):
|
|||
renderer.queueNode(self, mesh = self._grid_mesh, shader = self._grid_shader, backface_cull = True)
|
||||
if self._disallowed_area_mesh:
|
||||
renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9)
|
||||
if self._raft_mesh and self._adhesion_type == "raft":
|
||||
renderer.queueNode(self, mesh=self._raft_mesh, transparent=True, backface_cull=True, sort=-9)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -153,17 +153,6 @@ class BuildVolume(SceneNode):
|
|||
mb.setVertexUVCoordinates(n, v[0], v[2])
|
||||
self._grid_mesh = mb.build()
|
||||
|
||||
# Build raft mesh: a plane on the height of the raft.
|
||||
mb = MeshBuilder()
|
||||
mb.addQuad(
|
||||
Vector(min_w, self._raft_thickness, min_d),
|
||||
Vector(max_w, self._raft_thickness, min_d),
|
||||
Vector(max_w, self._raft_thickness, max_d),
|
||||
Vector(min_w, self._raft_thickness, max_d),
|
||||
color=Color(128, 128, 128, 64)
|
||||
)
|
||||
self._raft_mesh = mb.build()
|
||||
|
||||
disallowed_area_height = 0.1
|
||||
disallowed_area_size = 0
|
||||
if self._disallowed_areas:
|
||||
|
@ -191,20 +180,22 @@ class BuildVolume(SceneNode):
|
|||
else:
|
||||
self._disallowed_area_mesh = None
|
||||
|
||||
self._volume_aabb = AxisAlignedBox(minimum = Vector(min_w, min_h - 1.0, min_d), maximum = Vector(max_w, max_h, max_d))
|
||||
self._volume_aabb = AxisAlignedBox(
|
||||
minimum = Vector(min_w, min_h - 1.0, min_d),
|
||||
maximum = Vector(max_w, max_h - self._raft_thickness, max_d))
|
||||
|
||||
skirt_size = 0.0
|
||||
bed_adhesion_size = 0.0
|
||||
|
||||
container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if container_stack:
|
||||
skirt_size = self._getSkirtSize(container_stack)
|
||||
bed_adhesion_size = self._getBedAdhesionSize(container_stack)
|
||||
|
||||
# As this works better for UM machines, we only add the disallowed_area_size for the z direction.
|
||||
# This is probably wrong in all other cases. TODO!
|
||||
# The +1 and -1 is added as there is always a bit of extra room required to work properly.
|
||||
scale_to_max_bounds = AxisAlignedBox(
|
||||
minimum = Vector(min_w + skirt_size + 1, min_h, min_d + disallowed_area_size - skirt_size + 1),
|
||||
maximum = Vector(max_w - skirt_size - 1, max_h, max_d - disallowed_area_size + skirt_size - 1)
|
||||
minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + disallowed_area_size - bed_adhesion_size + 1),
|
||||
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness, max_d - disallowed_area_size + bed_adhesion_size - 1)
|
||||
)
|
||||
|
||||
Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
|
||||
|
@ -219,6 +210,9 @@ class BuildVolume(SceneNode):
|
|||
" \"Print Sequence\" setting to prevent the gantry from colliding"
|
||||
" with printed objects."), lifetime=10).show()
|
||||
|
||||
def getRaftThickness(self):
|
||||
return self._raft_thickness
|
||||
|
||||
def _updateRaftThickness(self):
|
||||
old_raft_thickness = self._raft_thickness
|
||||
self._adhesion_type = self._active_container_stack.getProperty("adhesion_type", "value")
|
||||
|
@ -230,9 +224,11 @@ class BuildVolume(SceneNode):
|
|||
self._active_container_stack.getProperty("raft_surface_layers", "value") *
|
||||
self._active_container_stack.getProperty("raft_surface_thickness", "value") +
|
||||
self._active_container_stack.getProperty("raft_airgap", "value"))
|
||||
|
||||
# Rounding errors do not matter, we check if raft_thickness has changed at all
|
||||
if old_raft_thickness != self._raft_thickness:
|
||||
self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World)
|
||||
self.raftThicknessChanged.emit()
|
||||
|
||||
def _onGlobalContainerStackChanged(self):
|
||||
if self._active_container_stack:
|
||||
|
@ -309,62 +305,62 @@ class BuildVolume(SceneNode):
|
|||
[prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE],
|
||||
])
|
||||
|
||||
skirt_size = self._getSkirtSize(self._active_container_stack)
|
||||
bed_adhesion_size = self._getBedAdhesionSize(self._active_container_stack)
|
||||
|
||||
if disallowed_areas:
|
||||
# Extend every area already in the disallowed_areas with the skirt size.
|
||||
for area in disallowed_areas:
|
||||
poly = Polygon(numpy.array(area, numpy.float32))
|
||||
poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(skirt_size)))
|
||||
poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size)))
|
||||
|
||||
areas.append(poly)
|
||||
|
||||
# Add the skirt areas around the borders of the build plate.
|
||||
if skirt_size > 0:
|
||||
if bed_adhesion_size > 0:
|
||||
half_machine_width = self._active_container_stack.getProperty("machine_width", "value") / 2
|
||||
half_machine_depth = self._active_container_stack.getProperty("machine_depth", "value") / 2
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[-half_machine_width + skirt_size, half_machine_depth - skirt_size],
|
||||
[-half_machine_width + skirt_size, -half_machine_depth + skirt_size]
|
||||
[-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size],
|
||||
[-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size]
|
||||
], numpy.float32)))
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[half_machine_width, half_machine_depth],
|
||||
[half_machine_width, -half_machine_depth],
|
||||
[half_machine_width - skirt_size, -half_machine_depth + skirt_size],
|
||||
[half_machine_width - skirt_size, half_machine_depth - skirt_size]
|
||||
[half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size],
|
||||
[half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size]
|
||||
], numpy.float32)))
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[half_machine_width, half_machine_depth],
|
||||
[half_machine_width - skirt_size, half_machine_depth - skirt_size],
|
||||
[-half_machine_width + skirt_size, half_machine_depth - skirt_size]
|
||||
[half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size],
|
||||
[-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size]
|
||||
], numpy.float32)))
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width + skirt_size, -half_machine_depth + skirt_size],
|
||||
[half_machine_width - skirt_size, -half_machine_depth + skirt_size]
|
||||
[-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size],
|
||||
[half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size]
|
||||
], numpy.float32)))
|
||||
|
||||
self._disallowed_areas = areas
|
||||
|
||||
## Convenience function to calculate the size of the bed adhesion in directions x, y.
|
||||
def _getSkirtSize(self, container_stack):
|
||||
def _getBedAdhesionSize(self, container_stack):
|
||||
skirt_size = 0.0
|
||||
|
||||
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
||||
if adhesion_type == "skirt":
|
||||
skirt_distance = container_stack.getProperty("skirt_gap", "value")
|
||||
skirt_line_count = container_stack.getProperty("skirt_line_count", "value")
|
||||
skirt_size = skirt_distance + (skirt_line_count * container_stack.getProperty("skirt_line_width", "value"))
|
||||
skirt_size = skirt_distance + (skirt_line_count * container_stack.getProperty("skirt_brim_line_width", "value"))
|
||||
elif adhesion_type == "brim":
|
||||
skirt_size = container_stack.getProperty("brim_line_count", "value") * container_stack.getProperty("skirt_line_width", "value")
|
||||
skirt_size = container_stack.getProperty("brim_line_count", "value") * container_stack.getProperty("skirt_brim_line_width", "value")
|
||||
elif adhesion_type == "raft":
|
||||
skirt_size = container_stack.getProperty("raft_margin", "value")
|
||||
|
||||
|
@ -379,5 +375,5 @@ class BuildVolume(SceneNode):
|
|||
def _clamp(self, value, min_value, max_value):
|
||||
return max(min(value, max_value), min_value)
|
||||
|
||||
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"]
|
||||
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"]
|
||||
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"]
|
||||
|
|
|
@ -16,6 +16,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
self._init2DConvexHullCache()
|
||||
|
||||
self._global_stack = None
|
||||
|
||||
self._raft_thickness = 0.0
|
||||
# For raft thickness, DRY
|
||||
self._build_volume = Application.getInstance().getBuildVolume()
|
||||
self._build_volume.raftThicknessChanged.connect(self._onChanged)
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
Application.getInstance().getController().toolOperationStarted.connect(self._onChanged)
|
||||
Application.getInstance().getController().toolOperationStopped.connect(self._onChanged)
|
||||
|
@ -24,9 +30,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
|
||||
def setNode(self, node):
|
||||
previous_node = self._node
|
||||
# Disconnect from previous node signals
|
||||
if previous_node is not None and node is not previous_node:
|
||||
previous_node.transformationChanged.connect(self._onChanged)
|
||||
previous_node.parentChanged.connect(self._onChanged)
|
||||
previous_node.transformationChanged.disconnect(self._onChanged)
|
||||
previous_node.parentChanged.disconnect(self._onChanged)
|
||||
|
||||
super().setNode(node)
|
||||
|
||||
|
@ -45,6 +52,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
return None
|
||||
|
||||
hull = self._compute2DConvexHull()
|
||||
|
||||
if self._global_stack and self._node:
|
||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
|
||||
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32)))
|
||||
|
@ -66,7 +74,9 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
|
||||
if self._global_stack:
|
||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
|
||||
return self._compute2DConvexHeadMin()
|
||||
head_with_fans = self._compute2DConvexHeadMin()
|
||||
head_with_fans_with_adhesion_margin = self._add2DAdhesionMargin(head_with_fans)
|
||||
return head_with_fans_with_adhesion_margin
|
||||
return None
|
||||
|
||||
## Get convex hull of the node
|
||||
|
@ -78,6 +88,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
|
||||
if self._global_stack:
|
||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
|
||||
|
||||
# Printing one at a time and it's not an object in a group
|
||||
return self._compute2DConvexHull()
|
||||
return None
|
||||
|
@ -93,14 +104,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
|
||||
convex_hull = self.getConvexHull()
|
||||
if self._convex_hull_node:
|
||||
if self._convex_hull_node.getHull() == convex_hull:
|
||||
return
|
||||
self._convex_hull_node.setParent(None)
|
||||
hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, root)
|
||||
hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, self._raft_thickness, root)
|
||||
self._convex_hull_node = hull_node
|
||||
|
||||
def _onSettingValueChanged(self, key, property_name):
|
||||
if key == "print_sequence" and property_name == "value":
|
||||
if key in self._affected_settings and property_name == "value":
|
||||
self._onChanged()
|
||||
|
||||
def _init2DConvexHullCache(self):
|
||||
|
@ -157,7 +166,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
vertex_data = mesh.getConvexHullTransformedVertices(world_transform)
|
||||
# Don't use data below 0.
|
||||
# TODO; We need a better check for this as this gives poor results for meshes with long edges.
|
||||
vertex_data = vertex_data[vertex_data[:,1] >= 0]
|
||||
# Do not throw away vertices: the convex hull may be too small and objects can collide.
|
||||
# vertex_data = vertex_data[vertex_data[:,1] >= -0.01]
|
||||
|
||||
if len(vertex_data) >= 4:
|
||||
# Round the vertex data to 1/10th of a mm, then remove all duplicate vertices
|
||||
|
@ -209,10 +219,49 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
min_head_hull = self._compute2DConvexHull().getMinkowskiHull(head_and_fans)
|
||||
return min_head_hull
|
||||
|
||||
## Compensate given 2D polygon with adhesion margin
|
||||
# \return 2D polygon with added margin
|
||||
def _add2DAdhesionMargin(self, poly):
|
||||
# Compensate for raft/skirt/brim
|
||||
# Add extra margin depending on adhesion type
|
||||
adhesion_type = self._global_stack.getProperty("adhesion_type", "value")
|
||||
extra_margin = 0
|
||||
machine_head_coords = numpy.array(
|
||||
self._global_stack.getProperty("machine_head_with_fans_polygon", "value"),
|
||||
numpy.float32)
|
||||
head_y_size = abs(machine_head_coords).min() # safe margin to take off in all directions
|
||||
|
||||
if adhesion_type == "raft":
|
||||
extra_margin = max(0, self._global_stack.getProperty("raft_margin", "value") - head_y_size)
|
||||
elif adhesion_type == "brim":
|
||||
extra_margin = max(0, self._global_stack.getProperty("brim_width", "value") - head_y_size)
|
||||
elif adhesion_type == "skirt":
|
||||
extra_margin = max(
|
||||
0, self._global_stack.getProperty("skirt_gap", "value") +
|
||||
self._global_stack.getProperty("skirt_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value") -
|
||||
head_y_size)
|
||||
# adjust head_and_fans with extra margin
|
||||
if extra_margin > 0:
|
||||
# In Cura 2.2+, there is a function to create this circle-like polygon.
|
||||
extra_margin_polygon = Polygon(numpy.array([
|
||||
[-extra_margin, 0],
|
||||
[-extra_margin * 0.707, extra_margin * 0.707],
|
||||
[0, extra_margin],
|
||||
[extra_margin * 0.707, extra_margin * 0.707],
|
||||
[extra_margin, 0],
|
||||
[extra_margin * 0.707, -extra_margin * 0.707],
|
||||
[0, -extra_margin],
|
||||
[-extra_margin * 0.707, -extra_margin * 0.707]
|
||||
], numpy.float32))
|
||||
|
||||
poly = poly.getMinkowskiHull(extra_margin_polygon)
|
||||
return poly
|
||||
|
||||
def _roundHull(self, convex_hull):
|
||||
return convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32)))
|
||||
|
||||
def _onChanged(self, *args):
|
||||
self._raft_thickness = self._build_volume.getRaftThickness()
|
||||
self.recomputeConvexHull()
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
|
@ -235,3 +284,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
if root is node:
|
||||
return True
|
||||
return self.__isDescendant(root, node.getParent())
|
||||
|
||||
_affected_settings = [
|
||||
"adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers",
|
||||
"raft_surface_thickness", "raft_airgap", "print_sequence",
|
||||
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"]
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Resources import Resources
|
||||
from UM.Math.Color import Color
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the convex hull with.
|
||||
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
class ConvexHullNode(SceneNode):
|
||||
## Convex hull node is a special type of scene node that is used to display a 2D area, to indicate the
|
||||
## Convex hull node is a special type of scene node that is used to display an area, to indicate the
|
||||
# location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
|
||||
# then displayed as a transparent shadow.
|
||||
def __init__(self, node, hull, parent = None):
|
||||
# then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded
|
||||
# to represent the raft as well.
|
||||
def __init__(self, node, hull, thickness, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setCalculateBoundingBox(False)
|
||||
|
@ -23,55 +23,45 @@ class ConvexHullNode(SceneNode):
|
|||
self._original_parent = parent
|
||||
|
||||
# Color of the drawn convex hull
|
||||
self._color = Color(35, 35, 35, 128)
|
||||
self._color = Color(0.4, 0.4, 0.4, 1.0)
|
||||
|
||||
# The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
|
||||
self._mesh_height = 0.1
|
||||
|
||||
self._thickness = thickness
|
||||
|
||||
# The node this mesh is "watching"
|
||||
self._node = node
|
||||
self._convex_hull_head_mesh = None
|
||||
|
||||
self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
|
||||
self._onNodeDecoratorsChanged(self._node)
|
||||
|
||||
self._convex_hull_head_mesh = None
|
||||
self._hull = hull
|
||||
|
||||
if self._hull:
|
||||
hull_mesh = self.createHullMesh(self._hull.getPoints())
|
||||
if hull_mesh:
|
||||
hull_mesh_builder = MeshBuilder()
|
||||
|
||||
if hull_mesh_builder.addConvexPolygonExtrusion(
|
||||
self._hull.getPoints()[::-1], # bottom layer is reversed
|
||||
self._mesh_height-thickness, self._mesh_height, color=self._color):
|
||||
|
||||
hull_mesh = hull_mesh_builder.build()
|
||||
self.setMeshData(hull_mesh)
|
||||
convex_hull_head = self._node.callDecoration("getConvexHullHead")
|
||||
if convex_hull_head:
|
||||
self._convex_hull_head_mesh = self.createHullMesh(convex_hull_head.getPoints())
|
||||
|
||||
def getHull(self):
|
||||
return self._hull
|
||||
|
||||
## Actually create the mesh from the hullpoints
|
||||
# /param hull_points list of xy values
|
||||
# /return meshData
|
||||
def createHullMesh(self, hull_points):
|
||||
# Input checking.
|
||||
if len(hull_points) < 3:
|
||||
return None
|
||||
|
||||
mesh_builder = MeshBuilder()
|
||||
point_first = Vector(hull_points[0][0], self._mesh_height, hull_points[0][1])
|
||||
point_previous = Vector(hull_points[1][0], self._mesh_height, hull_points[1][1])
|
||||
for point in hull_points[2:]: # Add the faces in the order of a triangle fan.
|
||||
point_new = Vector(point[0], self._mesh_height, point[1])
|
||||
mesh_builder.addFace(point_first, point_previous, point_new, color = self._color)
|
||||
point_previous = point_new # Prepare point_previous for the next triangle.
|
||||
|
||||
return mesh_builder.build()
|
||||
def getThickness(self):
|
||||
return self._thickness
|
||||
|
||||
def getWatchedNode(self):
|
||||
return self._node
|
||||
|
||||
def render(self, renderer):
|
||||
if not self._shader:
|
||||
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader"))
|
||||
self._shader.setUniformValue("u_color", self._color)
|
||||
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
|
||||
self._shader.setUniformValue("u_diffuseColor", self._color)
|
||||
self._shader.setUniformValue("u_opacity", 0.6)
|
||||
|
||||
if self.getParent():
|
||||
if self.getMeshData():
|
||||
|
@ -84,5 +74,12 @@ class ConvexHullNode(SceneNode):
|
|||
def _onNodeDecoratorsChanged(self, node):
|
||||
self._color = Color(35, 35, 35, 0.5)
|
||||
|
||||
convex_hull_head = self._node.callDecoration("getConvexHullHead")
|
||||
if convex_hull_head:
|
||||
convex_hull_head_builder = MeshBuilder()
|
||||
convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height-self._thickness)
|
||||
self._convex_hull_head_mesh = convex_hull_head_builder.build()
|
||||
|
||||
if not node:
|
||||
return
|
||||
return
|
||||
|
||||
|
|
|
@ -97,7 +97,8 @@ class CuraApplication(QtApplication):
|
|||
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True)
|
||||
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True)
|
||||
SettingDefinition.addSettingType("extruder", int, str, Validator)
|
||||
SettingDefinition.addSupportedProperty("global_inherits_stack", DefinitionPropertyType.Function, default = "-1")
|
||||
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
||||
|
||||
## Add the 4 types of profiles to storage.
|
||||
Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality")
|
||||
|
@ -127,6 +128,8 @@ class CuraApplication(QtApplication):
|
|||
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
||||
self._machine_manager = None # This is initialized on demand.
|
||||
|
||||
self._additional_components = {} # Components to add to certain areas in the interface
|
||||
|
||||
super().__init__(name = "cura", version = CuraVersion, buildtype = CuraBuildType)
|
||||
|
||||
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
||||
|
@ -194,6 +197,14 @@ class CuraApplication(QtApplication):
|
|||
Preferences.getInstance().addPreference("view/center_on_select", True)
|
||||
Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
|
||||
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
|
||||
|
||||
for key in [
|
||||
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
|
||||
"dialog_profile_path",
|
||||
"dialog_material_path"]:
|
||||
|
||||
Preferences.getInstance().addPreference("local_file/%s" % key, "~/")
|
||||
|
||||
Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
|
||||
|
||||
Preferences.getInstance().setDefault("general/visible_settings", """
|
||||
|
@ -332,10 +343,15 @@ class CuraApplication(QtApplication):
|
|||
f.write(data)
|
||||
|
||||
|
||||
@pyqtSlot(result = QUrl)
|
||||
def getDefaultPath(self):
|
||||
return QUrl.fromLocalFile(os.path.expanduser("~/"))
|
||||
|
||||
@pyqtSlot(str, result = QUrl)
|
||||
def getDefaultPath(self, key):
|
||||
default_path = Preferences.getInstance().getValue("local_file/%s" % key)
|
||||
return QUrl.fromLocalFile(default_path)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def setDefaultPath(self, key, default_path):
|
||||
Preferences.getInstance().setValue("local_file/%s" % key, default_path)
|
||||
|
||||
## Handle loading of all plugin types (and the backend explicitly)
|
||||
# \sa PluginRegistery
|
||||
def _loadPlugins(self):
|
||||
|
@ -569,7 +585,8 @@ class CuraApplication(QtApplication):
|
|||
|
||||
op.push()
|
||||
if group_node:
|
||||
if len(group_node.getChildren()) == 1:
|
||||
if len(group_node.getChildren()) == 1 and group_node.callDecoration("isGroup"):
|
||||
group_node.getChildren()[0].translate(group_node.getPosition())
|
||||
group_node.getChildren()[0].setParent(group_node.getParent())
|
||||
op = RemoveSceneNodeOperation(group_node)
|
||||
op.push()
|
||||
|
@ -838,7 +855,11 @@ class CuraApplication(QtApplication):
|
|||
|
||||
def _reloadMeshFinished(self, job):
|
||||
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
|
||||
job._node.setMeshData(job.getResult().getMeshData())
|
||||
mesh_data = job.getResult().getMeshData()
|
||||
if mesh_data:
|
||||
job._node.setMeshData(mesh_data)
|
||||
else:
|
||||
Logger.log("w", "Could not find a mesh in reloaded node.")
|
||||
|
||||
def _openFile(self, file):
|
||||
job = ReadMeshJob(os.path.abspath(file))
|
||||
|
@ -857,4 +878,22 @@ class CuraApplication(QtApplication):
|
|||
self.getMainWindow().setMinimumSize(size)
|
||||
|
||||
def getBuildVolume(self):
|
||||
return self._volume
|
||||
return self._volume
|
||||
|
||||
additionalComponentsChanged = pyqtSignal(str, arguments = ["areaId"])
|
||||
|
||||
@pyqtProperty("QVariantMap", notify = additionalComponentsChanged)
|
||||
def additionalComponents(self):
|
||||
return self._additional_components
|
||||
|
||||
## Add a component to a list of components to be reparented to another area in the GUI.
|
||||
# The actual reparenting is done by the area itself.
|
||||
# \param area_id \type{str} Identifying name of the area to which the component should be reparented
|
||||
# \param component \type{QQuickComponent} The component that should be reparented
|
||||
@pyqtSlot(str, "QVariant")
|
||||
def addAdditionalComponent(self, area_id, component):
|
||||
if area_id not in self._additional_components:
|
||||
self._additional_components[area_id] = []
|
||||
self._additional_components[area_id].append(component)
|
||||
|
||||
self.additionalComponentsChanged.emit(area_id)
|
|
@ -35,24 +35,31 @@ class Layer:
|
|||
def setThickness(self, thickness):
|
||||
self._thickness = thickness
|
||||
|
||||
def vertexCount(self):
|
||||
def lineMeshVertexCount(self):
|
||||
result = 0
|
||||
for polygon in self._polygons:
|
||||
result += polygon.vertexCount()
|
||||
result += polygon.lineMeshVertexCount()
|
||||
|
||||
return result
|
||||
|
||||
def build(self, offset, vertices, colors, indices):
|
||||
result = offset
|
||||
def lineMeshElementCount(self):
|
||||
result = 0
|
||||
for polygon in self._polygons:
|
||||
if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType:
|
||||
continue
|
||||
result += polygon.lineMeshElementCount()
|
||||
|
||||
polygon.build(result, vertices, colors, indices)
|
||||
result += polygon.vertexCount()
|
||||
return result
|
||||
|
||||
def build(self, vertex_offset, index_offset, vertices, colors, indices):
|
||||
result_vertex_offset = vertex_offset
|
||||
result_index_offset = index_offset
|
||||
self._element_count = 0
|
||||
for polygon in self._polygons:
|
||||
polygon.build(result_vertex_offset, result_index_offset, vertices, colors, indices)
|
||||
result_vertex_offset += polygon.lineMeshVertexCount()
|
||||
result_index_offset += polygon.lineMeshElementCount()
|
||||
self._element_count += polygon.elementCount
|
||||
|
||||
return result
|
||||
return (result_vertex_offset, result_index_offset)
|
||||
|
||||
def createMesh(self):
|
||||
return self.createMeshOrJumps(True)
|
||||
|
@ -60,40 +67,52 @@ class Layer:
|
|||
def createJumps(self):
|
||||
return self.createMeshOrJumps(False)
|
||||
|
||||
# Defines the two triplets of local point indices to use to draw the two faces for each line segment in createMeshOrJump
|
||||
__index_pattern = numpy.array([[0, 3, 2, 0, 1, 3]], dtype = numpy.int32 )
|
||||
|
||||
def createMeshOrJumps(self, make_mesh):
|
||||
builder = MeshBuilder()
|
||||
|
||||
|
||||
line_count = 0
|
||||
if make_mesh:
|
||||
for polygon in self._polygons:
|
||||
line_count += polygon.meshLineCount
|
||||
else:
|
||||
for polygon in self._polygons:
|
||||
line_count += polygon.jumpCount
|
||||
|
||||
|
||||
# Reserve the neccesary space for the data upfront
|
||||
builder.reserveFaceAndVertexCount(2 * line_count, 4 * line_count)
|
||||
|
||||
for polygon in self._polygons:
|
||||
if make_mesh and (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
|
||||
continue
|
||||
if not make_mesh and not (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
|
||||
continue
|
||||
# Filter out the types of lines we are not interesed in depending on whether we are drawing the mesh or the jumps.
|
||||
index_mask = numpy.logical_not(polygon.jumpMask) if make_mesh else polygon.jumpMask
|
||||
|
||||
poly_color = polygon.getColor()
|
||||
# Create an array with rows [p p+1] and only keep those we whant to draw based on make_mesh
|
||||
points = numpy.concatenate((polygon.data[:-1], polygon.data[1:]), 1)[index_mask.ravel()]
|
||||
# Line types of the points we want to draw
|
||||
line_types = polygon.types[index_mask]
|
||||
|
||||
# Shift the z-axis according to previous implementation.
|
||||
if make_mesh:
|
||||
points[polygon.isInfillOrSkinType(line_types), 1::3] -= 0.01
|
||||
else:
|
||||
points[:, 1::3] += 0.01
|
||||
|
||||
points = numpy.copy(polygon.data)
|
||||
if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.SkinType or polygon.type == LayerPolygon.SupportInfillType:
|
||||
points[:,1] -= 0.01
|
||||
if polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType:
|
||||
points[:,1] += 0.01
|
||||
# Create an array with normals and tile 2 copies to match size of points variable
|
||||
normals = numpy.tile( polygon.getNormals()[index_mask.ravel()], (1, 2))
|
||||
|
||||
normals = polygon.getNormals()
|
||||
# Scale all normals by the line width of the current line so we can easily offset.
|
||||
normals *= (polygon.lineWidths[index_mask.ravel()] / 2)
|
||||
|
||||
# Scale all by the line width of the polygon so we can easily offset.
|
||||
normals *= (polygon.lineWidth / 2)
|
||||
# Create 4 points to draw each line segment, points +- normals results in 2 points each. Reshape to one point per line
|
||||
f_points = numpy.concatenate((points-normals, points+normals), 1).reshape((-1, 3))
|
||||
# __index_pattern defines which points to use to draw the two faces for each lines egment, the following linesegment is offset by 4
|
||||
f_indices = ( self.__index_pattern + numpy.arange(0, 4 * len(normals), 4, dtype=numpy.int32).reshape((-1, 1)) ).reshape((-1, 3))
|
||||
f_colors = numpy.repeat(polygon.mapLineTypeToColor(line_types), 4, 0)
|
||||
|
||||
#TODO: Use numpy magic to perform the vertex creation to speed up things.
|
||||
for i in range(len(points)):
|
||||
start = points[i - 1]
|
||||
end = points[i]
|
||||
builder.addFacesWithColor(f_points, f_indices, f_colors)
|
||||
|
||||
normal = normals[i - 1]
|
||||
|
||||
point1 = Vector(data = start - normal)
|
||||
point2 = Vector(data = start + normal)
|
||||
point3 = Vector(data = end + normal)
|
||||
point4 = Vector(data = end - normal)
|
||||
|
||||
builder.addQuad(point1, point2, point3, point4, color = poly_color)
|
||||
|
||||
return builder.build()
|
||||
|
||||
return builder.build()
|
|
@ -50,16 +50,19 @@ class LayerDataBuilder(MeshBuilder):
|
|||
|
||||
def build(self):
|
||||
vertex_count = 0
|
||||
index_count = 0
|
||||
for layer, data in self._layers.items():
|
||||
vertex_count += data.vertexCount()
|
||||
vertex_count += data.lineMeshVertexCount()
|
||||
index_count += data.lineMeshElementCount()
|
||||
|
||||
vertices = numpy.empty((vertex_count, 3), numpy.float32)
|
||||
colors = numpy.empty((vertex_count, 4), numpy.float32)
|
||||
indices = numpy.empty((vertex_count, 2), numpy.int32)
|
||||
indices = numpy.empty((index_count, 2), numpy.int32)
|
||||
|
||||
offset = 0
|
||||
vertex_offset = 0
|
||||
index_offset = 0
|
||||
for layer, data in self._layers.items():
|
||||
offset = data.build(offset, vertices, colors, indices)
|
||||
( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, indices)
|
||||
self._element_counts[layer] = data.elementCount
|
||||
|
||||
self.addVertices(vertices)
|
||||
|
|
|
@ -14,40 +14,113 @@ class LayerPolygon:
|
|||
SupportInfillType = 7
|
||||
MoveCombingType = 8
|
||||
MoveRetractionType = 9
|
||||
|
||||
def __init__(self, mesh, polygon_type, data, line_width):
|
||||
|
||||
__jump_map = numpy.logical_or( numpy.arange(10) == NoneType, numpy.arange(10) >= MoveCombingType )
|
||||
|
||||
def __init__(self, mesh, extruder, line_types, data, line_widths):
|
||||
self._mesh = mesh
|
||||
self._type = polygon_type
|
||||
self._extruder = extruder
|
||||
self._types = line_types
|
||||
self._data = data
|
||||
self._line_width = line_width / 1000
|
||||
self._begin = 0
|
||||
self._end = 0
|
||||
self._line_widths = line_widths
|
||||
|
||||
self._vertex_begin = 0
|
||||
self._vertex_end = 0
|
||||
self._index_begin = 0
|
||||
self._index_end = 0
|
||||
|
||||
self._jump_mask = self.__jump_map[self._types]
|
||||
self._jump_count = numpy.sum(self._jump_mask)
|
||||
self._mesh_line_count = len(self._types)-self._jump_count
|
||||
self._vertex_count = self._mesh_line_count + numpy.sum( self._types[1:] == self._types[:-1])
|
||||
|
||||
self._color = self.__color_map[polygon_type]
|
||||
# Buffering the colors shouldn't be necessary as it is not
|
||||
# re-used and can save alot of memory usage.
|
||||
self._colors = self.__color_map[self._types]
|
||||
self._color_map = self.__color_map
|
||||
|
||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
||||
# Should be generated in better way, not hardcoded.
|
||||
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0], dtype=numpy.bool)
|
||||
|
||||
self._build_cache_line_mesh_mask = None
|
||||
self._build_cache_needed_points = None
|
||||
|
||||
def buildCache(self):
|
||||
# For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
|
||||
self._build_cache_line_mesh_mask = numpy.logical_not(numpy.logical_or(self._jump_mask, self._types == LayerPolygon.InfillType ))
|
||||
mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask)
|
||||
self._index_begin = 0
|
||||
self._index_end = mesh_line_count
|
||||
|
||||
self._build_cache_needed_points = numpy.ones((len(self._types), 2), dtype=numpy.bool)
|
||||
# Only if the type of line segment changes do we need to add an extra vertex to change colors
|
||||
self._build_cache_needed_points[1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1]
|
||||
# Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask
|
||||
numpy.logical_and(self._build_cache_needed_points, self._build_cache_line_mesh_mask, self._build_cache_needed_points )
|
||||
|
||||
self._vertex_begin = 0
|
||||
self._vertex_end = numpy.sum( self._build_cache_needed_points )
|
||||
|
||||
|
||||
def build(self, offset, vertices, colors, indices):
|
||||
self._begin = offset
|
||||
self._end = self._begin + len(self._data) - 1
|
||||
def build(self, vertex_offset, index_offset, vertices, colors, indices):
|
||||
if (self._build_cache_line_mesh_mask is None) or (self._build_cache_needed_points is None ):
|
||||
self.buildCache()
|
||||
|
||||
line_mesh_mask = self._build_cache_line_mesh_mask
|
||||
needed_points_list = self._build_cache_needed_points
|
||||
|
||||
# Index to the points we need to represent the line mesh. This is constructed by generating simple
|
||||
# start and end points for each line. For line segment n these are points n and n+1. Row n reads [n n+1]
|
||||
# Then then the indices for the points we don't need are thrown away based on the pre-calculated list.
|
||||
index_list = ( numpy.arange(len(self._types)).reshape((-1, 1)) + numpy.array([[0, 1]]) ).reshape((-1, 1))[needed_points_list.reshape((-1, 1))]
|
||||
|
||||
# The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
|
||||
self._vertex_begin += vertex_offset
|
||||
self._vertex_end += vertex_offset
|
||||
|
||||
# Points are picked based on the index list to get the vertices needed.
|
||||
vertices[self._vertex_begin:self._vertex_end, :] = self._data[index_list, :]
|
||||
# Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
|
||||
colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()]
|
||||
colors[self._vertex_begin:self._vertex_end, :] *= numpy.array([[0.5, 0.5, 0.5, 1.0]], numpy.float32)
|
||||
|
||||
vertices[self._begin:self._end + 1, :] = self._data[:, :]
|
||||
colors[self._begin:self._end + 1, :] = numpy.array([self._color.r * 0.5, self._color.g * 0.5, self._color.b * 0.5, self._color.a], numpy.float32)
|
||||
# The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
|
||||
self._index_begin += index_offset
|
||||
self._index_end += index_offset
|
||||
|
||||
indices[self._index_begin:self._index_end, :] = numpy.arange(self._index_end-self._index_begin, dtype=numpy.int32).reshape((-1, 1))
|
||||
# When the line type changes the index needs to be increased by 2.
|
||||
indices[self._index_begin:self._index_end, :] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(), 0], dtype=numpy.int32).reshape((-1, 1))
|
||||
# Each line segment goes from it's starting point p to p+1, offset by the vertex index.
|
||||
# The -1 is to compensate for the neccecarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
|
||||
indices[self._index_begin:self._index_end, :] += numpy.array([self._vertex_begin - 1, self._vertex_begin])
|
||||
|
||||
self._build_cache_line_mesh_mask = None
|
||||
self._build_cache_needed_points = None
|
||||
|
||||
for i in range(self._begin, self._end):
|
||||
indices[i, 0] = i
|
||||
indices[i, 1] = i + 1
|
||||
def getColors(self):
|
||||
return self._colors
|
||||
|
||||
indices[self._end, 0] = self._end
|
||||
indices[self._end, 1] = self._begin
|
||||
def mapLineTypeToColor(self, line_types):
|
||||
return self._color_map[line_types]
|
||||
|
||||
def getColor(self):
|
||||
return self._color
|
||||
def isInfillOrSkinType(self, line_types):
|
||||
return self._isInfillOrSkinTypeMap[line_types]
|
||||
|
||||
def vertexCount(self):
|
||||
return len(self._data)
|
||||
def lineMeshVertexCount(self):
|
||||
return (self._vertex_end - self._vertex_begin)
|
||||
|
||||
def lineMeshElementCount(self):
|
||||
return (self._index_end - self._index_begin)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
def extruder(self):
|
||||
return self._extruder
|
||||
|
||||
@property
|
||||
def types(self):
|
||||
return self._types
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
|
@ -55,11 +128,23 @@ class LayerPolygon:
|
|||
|
||||
@property
|
||||
def elementCount(self):
|
||||
return ((self._end - self._begin) + 1) * 2 # The range of vertices multiplied by 2 since each vertex is used twice
|
||||
return (self._index_end - self._index_begin) * 2 # The range of vertices multiplied by 2 since each vertex is used twice
|
||||
|
||||
@property
|
||||
def lineWidth(self):
|
||||
return self._line_width
|
||||
def lineWidths(self):
|
||||
return self._line_widths
|
||||
|
||||
@property
|
||||
def jumpMask(self):
|
||||
return self._jump_mask
|
||||
|
||||
@property
|
||||
def meshLineCount(self):
|
||||
return self._mesh_line_count
|
||||
|
||||
@property
|
||||
def jumpCount(self):
|
||||
return self._jump_count
|
||||
|
||||
# Calculate normals for the entire polygon using numpy.
|
||||
def getNormals(self):
|
||||
|
@ -71,7 +156,8 @@ class LayerPolygon:
|
|||
# we end up subtracting each next point from the current, wrapping
|
||||
# around. This gives us the edges from the next point to the current
|
||||
# point.
|
||||
normals[:] = normals[:] - numpy.roll(normals, -1, axis = 0)
|
||||
normals = numpy.diff(normals, 1, 0)
|
||||
|
||||
# Calculate the length of each edge using standard Pythagoras
|
||||
lengths = numpy.sqrt(normals[:, 0] ** 2 + normals[:, 2] ** 2)
|
||||
# The normal of a 2D vector is equal to its x and y coordinates swapped
|
||||
|
@ -85,7 +171,7 @@ class LayerPolygon:
|
|||
|
||||
return normals
|
||||
|
||||
__color_map = {
|
||||
__color_mapping = {
|
||||
NoneType: Color(1.0, 1.0, 1.0, 1.0),
|
||||
Inset0Type: Color(1.0, 0.0, 0.0, 1.0),
|
||||
InsetXType: Color(0.0, 1.0, 0.0, 1.0),
|
||||
|
@ -97,3 +183,17 @@ class LayerPolygon:
|
|||
MoveCombingType: Color(0.0, 0.0, 1.0, 1.0),
|
||||
MoveRetractionType: Color(0.5, 0.5, 1.0, 1.0),
|
||||
}
|
||||
|
||||
# Should be generated in better way, not hardcoded.
|
||||
__color_map = numpy.array([
|
||||
[1.0, 1.0, 1.0, 1.0],
|
||||
[1.0, 0.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 0.0, 1.0],
|
||||
[1.0, 1.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[1.0, 0.74, 0.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[0.0, 0.0, 1.0, 1.0],
|
||||
[0.5, 0.5, 1.0, 1.0]
|
||||
])
|
|
@ -48,7 +48,14 @@ class PlatformPhysics:
|
|||
bbox = node.getBoundingBox()
|
||||
|
||||
# Ignore intersections with the bottom
|
||||
build_volume_bounding_box = self._build_volume.getBoundingBox().set(bottom=-9001)
|
||||
build_volume_bounding_box = self._build_volume.getBoundingBox()
|
||||
if build_volume_bounding_box:
|
||||
# It's over 9000!
|
||||
build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001)
|
||||
else:
|
||||
# No bounding box. This is triggered when running Cura from command line with a model for the first time
|
||||
# In that situation there is a model, but no machine (and therefore no build volume.
|
||||
return
|
||||
node._outside_buildarea = False
|
||||
|
||||
# Mark the node as outside the build volume if the bounding box test fails.
|
||||
|
@ -64,7 +71,6 @@ class PlatformPhysics:
|
|||
# If there is no convex hull for the node, start calculating it and continue.
|
||||
if not node.getDecorator(ConvexHullDecorator):
|
||||
node.addDecorator(ConvexHullDecorator())
|
||||
node.callDecoration("recomputeConvexHull")
|
||||
|
||||
if Preferences.getInstance().getValue("physics/automatic_push_free"):
|
||||
# Check for collisions between convex hulls
|
||||
|
@ -88,11 +94,11 @@ class PlatformPhysics:
|
|||
# Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
|
||||
head_hull = node.callDecoration("getConvexHullHead")
|
||||
if head_hull:
|
||||
overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull"))
|
||||
overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHullHead"))
|
||||
if not overlap:
|
||||
other_head_hull = other_node.callDecoration("getConvexHullHead")
|
||||
if other_head_hull:
|
||||
overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull)
|
||||
overlap = node.callDecoration("getConvexHullHead").intersectsPolygon(other_head_hull)
|
||||
else:
|
||||
own_convex_hull = node.callDecoration("getConvexHull")
|
||||
other_convex_hull = other_node.callDecoration("getConvexHull")
|
||||
|
|
|
@ -95,7 +95,7 @@ class PrintInformation(QObject):
|
|||
else: # Machine with no extruder stacks
|
||||
density = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("properties", {}).get("density", 0)
|
||||
|
||||
self._material_weights.append(float(amount) * float(density))
|
||||
self._material_weights.append(float(amount) * float(density) / 1000)
|
||||
self._material_lengths.append(round((amount / (math.pi * r ** 2)) / 1000, 2))
|
||||
self.materialLengthsChanged.emit()
|
||||
self.materialWeightsChanged.emit()
|
||||
|
|
|
@ -22,7 +22,7 @@ class ExtruderManager(QObject):
|
|||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
|
||||
self._active_extruder_index = -1
|
||||
self._active_extruder_index = 0
|
||||
UM.Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
|
||||
self._addCurrentMachineExtruders()
|
||||
|
||||
|
@ -41,6 +41,19 @@ class ExtruderManager(QObject):
|
|||
except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
|
||||
return None
|
||||
|
||||
@pyqtProperty(int, notify = extrudersChanged)
|
||||
def extruderCount(self):
|
||||
if not UM.Application.getInstance().getGlobalContainerStack():
|
||||
return 0 # No active machine, so no extruders.
|
||||
return len(self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()])
|
||||
|
||||
@pyqtProperty("QVariantMap", notify=extrudersChanged)
|
||||
def extruderIds(self):
|
||||
map = {}
|
||||
for position in self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]:
|
||||
map[position] = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position].getId()
|
||||
return map
|
||||
|
||||
## The instance of the singleton pattern.
|
||||
#
|
||||
# It's None if the extruder manager hasn't been created yet.
|
||||
|
@ -106,8 +119,11 @@ class ExtruderManager(QObject):
|
|||
for extruder_train in extruder_trains:
|
||||
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
|
||||
|
||||
# Ensure that the extruder train stacks are linked to global stack.
|
||||
extruder_train.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
|
||||
# Make sure the next stack is a stack that contains only the machine definition
|
||||
if not extruder_train.getNextStack():
|
||||
shallowStack = UM.Settings.ContainerStack(machine_id + "_shallow")
|
||||
shallowStack.addContainer(machine_definition)
|
||||
extruder_train.setNextStack(shallowStack)
|
||||
changed = True
|
||||
if changed:
|
||||
self.extrudersChanged.emit(machine_id)
|
||||
|
@ -220,7 +236,11 @@ class ExtruderManager(QObject):
|
|||
container_registry.addContainer(user_profile)
|
||||
container_stack.addContainer(user_profile)
|
||||
|
||||
container_stack.setNextStack(UM.Application.getInstance().getGlobalContainerStack())
|
||||
# Make sure the next stack is a stack that contains only the machine definition
|
||||
if not container_stack.getNextStack():
|
||||
shallowStack = UM.Settings.ContainerStack(machine_id + "_shallow")
|
||||
shallowStack.addContainer(machine_definition)
|
||||
container_stack.setNextStack(shallowStack)
|
||||
|
||||
container_registry.addContainer(container_stack)
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
material = extruder.findContainer({ "type": "material" })
|
||||
if material:
|
||||
extruder_name = "%s (%s)" % (material.getName(), extruder_name)
|
||||
position = extruder.getBottom().getMetaDataEntry("position", default = "0") #Position in the definition.
|
||||
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
||||
try:
|
||||
position = int(position)
|
||||
except ValueError: #Not a proper int.
|
||||
|
|
|
@ -50,6 +50,8 @@ class MachineManager(QObject):
|
|||
|
||||
Preferences.getInstance().addPreference("cura/active_machine", "")
|
||||
|
||||
self._global_event_keys = set()
|
||||
|
||||
active_machine_id = Preferences.getInstance().getValue("cura/active_machine")
|
||||
|
||||
self._printer_output_devices = []
|
||||
|
@ -58,11 +60,12 @@ class MachineManager(QObject):
|
|||
if active_machine_id != "":
|
||||
# An active machine was saved, so restore it.
|
||||
self.setActiveMachine(active_machine_id)
|
||||
pass
|
||||
if self._global_container_stack and self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
# Make sure _active_container_stack is properly initiated
|
||||
ExtruderManager.getInstance().setActiveExtruderIndex(0)
|
||||
|
||||
self._auto_change_material_hotend_flood_window = 10 # The minimum number of seconds between asking if the material or hotend on the machine should be used
|
||||
self._auto_change_material_hotend_flood_time = 0 # The last timestamp (in seconds) when the user was asked about changing the material or hotend to whatis loaded on the machine
|
||||
self._auto_change_material_hotend_flood_last_choice = None # The last choice that was made, so we can apply that choice again
|
||||
self._auto_materials_changed = {}
|
||||
self._auto_hotends_changed = {}
|
||||
|
||||
globalContainerChanged = pyqtSignal()
|
||||
activeMaterialChanged = pyqtSignal()
|
||||
|
@ -100,48 +103,46 @@ class MachineManager(QObject):
|
|||
if not self._global_container_stack:
|
||||
return
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = self._global_container_stack.getBottom().getId(), name = hotend_id)
|
||||
if containers:
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type="variant", definition=self._global_container_stack.getBottom().getId(), name=hotend_id)
|
||||
if containers: # New material ID is known
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
old_index = extruder_manager.activeExtruderIndex
|
||||
if old_index != index:
|
||||
extruder_manager.setActiveExtruderIndex(index)
|
||||
else:
|
||||
old_index = None
|
||||
extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId))
|
||||
matching_extruder = None
|
||||
for extruder in extruders:
|
||||
if str(index) == extruder.getMetaDataEntry("position"):
|
||||
matching_extruder = extruder
|
||||
break
|
||||
if matching_extruder and matching_extruder.findContainer({"type": "variant"}).getName() != hotend_id:
|
||||
# Save the material that needs to be changed. Multiple changes will be handled by the callback.
|
||||
self._auto_hotends_changed[str(index)] = containers[0].getId()
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"),
|
||||
catalog.i18nc("@label",
|
||||
"Do you want to change the materials and hotends to match the material in your printer?"),
|
||||
catalog.i18nc("@label",
|
||||
"The materials and / or hotends on your printer were changed. For best results always slice for the materials . hotends that are inserted in your printer."),
|
||||
buttons=QMessageBox.Yes + QMessageBox.No,
|
||||
icon=QMessageBox.Question,
|
||||
callback=self._materialHotendChangedCallback)
|
||||
|
||||
if self.activeVariantId != containers[0].getId():
|
||||
if time.time() - self._auto_change_material_hotend_flood_time > self._auto_change_material_hotend_flood_window:
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the hotend to match the hotend in your printer?"),
|
||||
catalog.i18nc("@label", "The hotend on your printer was changed. For best results always slice for the hotend that is inserted in your printer."),
|
||||
buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._hotendChangedDialogCallback, callback_arguments = [index, containers[0].getId()])
|
||||
else:
|
||||
self._hotendChangedDialogCallback(self._auto_change_material_hotend_flood_last_choice, index, containers[0].getId())
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
|
||||
else:
|
||||
Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id))
|
||||
|
||||
def _hotendChangedDialogCallback(self, button, index, hotend_id):
|
||||
self._auto_change_material_hotend_flood_time = time.time()
|
||||
self._auto_change_material_hotend_flood_last_choice = button
|
||||
|
||||
if button == QMessageBox.No:
|
||||
return
|
||||
|
||||
Logger.log("d", "Setting hotend variant of hotend %d to %s" % (index, hotend_id))
|
||||
|
||||
def _autoUpdateHotends(self):
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
old_index = extruder_manager.activeExtruderIndex
|
||||
if old_index != index:
|
||||
extruder_manager.setActiveExtruderIndex(index)
|
||||
else:
|
||||
old_index = None
|
||||
for position in self._auto_hotends_changed:
|
||||
hotend_id = self._auto_hotends_changed[position]
|
||||
old_index = extruder_manager.activeExtruderIndex
|
||||
|
||||
self.setActiveVariant(hotend_id)
|
||||
if old_index != int(position):
|
||||
extruder_manager.setActiveExtruderIndex(int(position))
|
||||
else:
|
||||
old_index = None
|
||||
Logger.log("d", "Setting hotend variant of hotend %s to %s" % (position, hotend_id))
|
||||
self.setActiveVariant(hotend_id)
|
||||
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
|
||||
def _onMaterialIdChanged(self, index, material_id):
|
||||
if not self._global_container_stack:
|
||||
|
@ -150,54 +151,135 @@ class MachineManager(QObject):
|
|||
definition_id = "fdmprinter"
|
||||
if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
|
||||
definition_id = self._global_container_stack.getBottom().getId()
|
||||
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id)
|
||||
if containers:
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
old_index = extruder_manager.activeExtruderIndex
|
||||
if old_index != index:
|
||||
extruder_manager.setActiveExtruderIndex(index)
|
||||
else:
|
||||
old_index = None
|
||||
if containers: # New material ID is known
|
||||
extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId))
|
||||
matching_extruder = None
|
||||
for extruder in extruders:
|
||||
if str(index) == extruder.getMetaDataEntry("position"):
|
||||
matching_extruder = extruder
|
||||
break
|
||||
|
||||
if self.activeMaterialId != containers[0].getId():
|
||||
if time.time() - self._auto_change_material_hotend_flood_time > self._auto_change_material_hotend_flood_window:
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the material to match the material in your printer?"),
|
||||
catalog.i18nc("@label", "The material on your printer was changed. For best results always slice for the material that is inserted in your printer."),
|
||||
buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._materialIdChangedDialogCallback, callback_arguments = [index, containers[0].getId()])
|
||||
else:
|
||||
self._materialIdChangedDialogCallback(self._auto_change_material_hotend_flood_last_choice, index, containers[0].getId())
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
if matching_extruder and matching_extruder.findContainer({"type":"material"}).getMetaDataEntry("GUID") != material_id:
|
||||
# Save the material that needs to be changed. Multiple changes will be handled by the callback.
|
||||
self._auto_materials_changed[str(index)] = containers[0].getId()
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the materials and hotends to match the material in your printer?"),
|
||||
catalog.i18nc("@label", "The materials and / or hotends on your printer were changed. For best results always slice for the materials . hotends that are inserted in your printer."),
|
||||
buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._materialHotendChangedCallback)
|
||||
|
||||
else:
|
||||
Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id))
|
||||
|
||||
def _materialIdChangedDialogCallback(self, button, index, material_id):
|
||||
self._auto_change_material_hotend_flood_time = time.time()
|
||||
self._auto_change_material_hotend_flood_last_choice = button
|
||||
|
||||
def _materialHotendChangedCallback(self, button):
|
||||
if button == QMessageBox.No:
|
||||
self._auto_materials_changed = {}
|
||||
self._auto_hotends_changed = {}
|
||||
return
|
||||
self._autoUpdateMaterials()
|
||||
self._autoUpdateHotends()
|
||||
|
||||
Logger.log("d", "Setting material of hotend %d to %s" % (index, material_id))
|
||||
|
||||
def _autoUpdateMaterials(self):
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
old_index = extruder_manager.activeExtruderIndex
|
||||
if old_index != index:
|
||||
extruder_manager.setActiveExtruderIndex(index)
|
||||
else:
|
||||
old_index = None
|
||||
for position in self._auto_materials_changed:
|
||||
material_id = self._auto_materials_changed[position]
|
||||
old_index = extruder_manager.activeExtruderIndex
|
||||
|
||||
self.setActiveMaterial(material_id)
|
||||
if old_index != int(position):
|
||||
extruder_manager.setActiveExtruderIndex(int(position))
|
||||
else:
|
||||
old_index = None
|
||||
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
Logger.log("d", "Setting material of hotend %s to %s" % (position, material_id))
|
||||
self.setActiveMaterial(material_id)
|
||||
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
|
||||
def _onGlobalPropertyChanged(self, key, property_name):
|
||||
if property_name == "value":
|
||||
## We can get recursion issues. So we store a list of keys that we are still handling to prevent this.
|
||||
if key in self._global_event_keys:
|
||||
return
|
||||
self._global_event_keys.add(key)
|
||||
self.globalValueChanged.emit()
|
||||
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
# Make the global current settings mirror the stack values appropriate for this setting
|
||||
if self._active_container_stack.getProperty("extruder_nr", "value") == int(self._active_container_stack.getProperty(key, "global_inherits_stack")):
|
||||
|
||||
new_value = self._active_container_stack.getProperty(key, "value")
|
||||
self._global_container_stack.getTop().setProperty(key, "value", new_value)
|
||||
|
||||
# Global-only setting values should be set on all extruders and the global stack
|
||||
if not self._global_container_stack.getProperty(key, "settable_per_extruder"):
|
||||
extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
target_stack_position = int(self._active_container_stack.getProperty(key, "global_inherits_stack"))
|
||||
if target_stack_position == -1: # Prevent -1 from selecting wrong stack.
|
||||
target_stack = self._active_container_stack
|
||||
else:
|
||||
target_stack = extruder_stacks[target_stack_position]
|
||||
new_value = target_stack.getProperty(key, "value")
|
||||
target_stack_has_user_value = target_stack.getTop().getInstance(key) != None
|
||||
for extruder_stack in extruder_stacks:
|
||||
if extruder_stack != target_stack:
|
||||
if target_stack_has_user_value:
|
||||
extruder_stack.getTop().setProperty(key, "value", new_value)
|
||||
else:
|
||||
# Remove from the value from the other stacks as well, unless the
|
||||
# top value from the other stacklevels is different than the new value
|
||||
for container in extruder_stack.getContainers():
|
||||
if container.__class__ == UM.Settings.InstanceContainer and container.getInstance(key) != None:
|
||||
if container.getProperty(key, "value") != new_value:
|
||||
# It could be that the setting needs to be removed instead of updated.
|
||||
temp = extruder_stack
|
||||
containers = extruder_stack.getContainers()
|
||||
# Ensure we have the entire 'chain'
|
||||
while temp.getNextStack():
|
||||
temp = temp.getNextStack()
|
||||
containers.extend(temp.getContainers())
|
||||
instance_needs_removal = False
|
||||
|
||||
if len(containers) > 1:
|
||||
for index in range(1, len(containers)):
|
||||
deeper_container = containers[index]
|
||||
if deeper_container.getProperty(key, "value") is None:
|
||||
continue # Deeper container does not have the value, so continue.
|
||||
if deeper_container.getProperty(key, "value") == new_value:
|
||||
# Removal will result in correct value, so do that.
|
||||
# We do this to prevent the reset from showing up unneeded.
|
||||
instance_needs_removal = True
|
||||
break
|
||||
else:
|
||||
# Container has the value, but it's not the same. Stop looking.
|
||||
break
|
||||
if instance_needs_removal:
|
||||
extruder_stack.getTop().removeInstance(key)
|
||||
else:
|
||||
extruder_stack.getTop().setProperty(key, "value", new_value)
|
||||
else:
|
||||
# Check if we really need to remove something.
|
||||
if extruder_stack.getProperty(key, "value") != new_value:
|
||||
extruder_stack.getTop().removeInstance(key)
|
||||
break
|
||||
if self._global_container_stack.getProperty(key, "value") != new_value:
|
||||
self._global_container_stack.getTop().setProperty(key, "value", new_value)
|
||||
self._global_event_keys.remove(key)
|
||||
|
||||
if property_name == "global_inherits_stack":
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
# Update the global user value when the "global_inherits_stack" function points to a different stack
|
||||
extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
target_stack_position = int(self._active_container_stack.getProperty(key, "global_inherits_stack"))
|
||||
if target_stack_position == -1: # Prevent -1 from selecting wrong stack.
|
||||
target_stack = self._active_container_stack
|
||||
else:
|
||||
target_stack = extruder_stacks[target_stack_position]
|
||||
|
||||
new_value = target_stack.getProperty(key, "value")
|
||||
if self._global_container_stack.getProperty(key, "value") != new_value:
|
||||
self._global_container_stack.getTop().setProperty(key, "value", new_value)
|
||||
|
||||
if property_name == "validationState":
|
||||
if self._global_stack_valid:
|
||||
changed_validation_state = self._active_container_stack.getProperty(key, property_name)
|
||||
|
@ -209,7 +291,6 @@ class MachineManager(QObject):
|
|||
if not has_errors:
|
||||
self._global_stack_valid = True
|
||||
self.globalValidationChanged.emit()
|
||||
|
||||
def _onGlobalContainerChanged(self):
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
|
||||
|
@ -233,7 +314,7 @@ class MachineManager(QObject):
|
|||
self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
|
||||
self._global_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
|
||||
self._global_stack_valid = not self._checkStackForErrors(self._global_container_stack)
|
||||
|
||||
self.globalValidationChanged.emit()
|
||||
material = self._global_container_stack.findContainer({"type": "material"})
|
||||
material.nameChanged.connect(self._onMaterialNameChanged)
|
||||
|
||||
|
@ -254,6 +335,18 @@ class MachineManager(QObject):
|
|||
|
||||
def _onInstanceContainersChanged(self, container):
|
||||
container_type = container.getMetaDataEntry("type")
|
||||
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
if int(self._active_container_stack.getProperty("extruder_nr", "value")) == 0:
|
||||
global_container = self._global_container_stack.findContainer({"type": container_type})
|
||||
if global_container and global_container != container:
|
||||
container_index = self._global_container_stack.getContainerIndex(global_container)
|
||||
self._global_container_stack.replaceContainer(container_index, container)
|
||||
|
||||
for key in container.getAllKeys():
|
||||
# Make sure the values in this profile are distributed to other stacks if necessary
|
||||
self._onGlobalPropertyChanged(key, "value")
|
||||
|
||||
if container_type == "material":
|
||||
self.activeMaterialChanged.emit()
|
||||
elif container_type == "variant":
|
||||
|
@ -269,13 +362,14 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtSlot(str, str)
|
||||
def addMachine(self, name, definition_id):
|
||||
definitions = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = definition_id)
|
||||
container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
definitions = container_registry.findDefinitionContainers(id = definition_id)
|
||||
if definitions:
|
||||
definition = definitions[0]
|
||||
name = self._createUniqueName("machine", "", name, definition.getName())
|
||||
new_global_stack = UM.Settings.ContainerStack(name)
|
||||
new_global_stack.addMetaDataEntry("type", "machine")
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_global_stack)
|
||||
container_registry.addContainer(new_global_stack)
|
||||
|
||||
variant_instance_container = self._updateVariantContainer(definition)
|
||||
material_instance_container = self._updateMaterialContainer(definition, variant_instance_container)
|
||||
|
@ -285,7 +379,7 @@ class MachineManager(QObject):
|
|||
current_settings_instance_container.addMetaDataEntry("machine", name)
|
||||
current_settings_instance_container.addMetaDataEntry("type", "user")
|
||||
current_settings_instance_container.setDefinition(definitions[0])
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(current_settings_instance_container)
|
||||
container_registry.addContainer(current_settings_instance_container)
|
||||
|
||||
# If a definition is found, its a list. Should only have one item.
|
||||
new_global_stack.addContainer(definition)
|
||||
|
@ -418,6 +512,19 @@ class MachineManager(QObject):
|
|||
return True
|
||||
return containers[0].isReadOnly()
|
||||
|
||||
## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
|
||||
@pyqtSlot(str)
|
||||
def copyValueToExtruders(self, key):
|
||||
if not self._active_container_stack or self._global_container_stack.getProperty("machine_extruder_count", "value") <= 1:
|
||||
return
|
||||
|
||||
new_value = self._active_container_stack.getProperty(key, "value")
|
||||
stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
||||
stacks.append(self._global_container_stack)
|
||||
for extruder_stack in stacks:
|
||||
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
|
||||
extruder_stack.getTop().setProperty(key, "value", new_value)
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def newQualityContainerFromQualityAndUser(self):
|
||||
new_container_id = self.duplicateContainer(self.activeQualityId)
|
||||
|
@ -436,21 +543,16 @@ class MachineManager(QObject):
|
|||
if containers:
|
||||
new_name = self._createUniqueName("quality", "", containers[0].getName(), catalog.i18nc("@label", "Custom profile"))
|
||||
|
||||
new_container = UM.Settings.InstanceContainer("")
|
||||
new_container = containers[0].duplicate(new_name, new_name)
|
||||
|
||||
## Copy all values
|
||||
new_container.deserialize(containers[0].serialize())
|
||||
|
||||
new_container.setReadOnly(False)
|
||||
new_container.setName(new_name)
|
||||
new_container._id = new_name
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_container)
|
||||
|
||||
return new_name
|
||||
|
||||
return ""
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def renameQualityContainer(self, container_id, new_name):
|
||||
def renameQualityContainer(self, container_id, nbalew_name):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality")
|
||||
if containers:
|
||||
new_name = self._createUniqueName("quality", containers[0].getName(), new_name,
|
||||
|
|
|
@ -111,6 +111,8 @@ class ThreeMFReader(MeshReader):
|
|||
if len(objects) > 1:
|
||||
group_decorator = GroupDecorator()
|
||||
result.addDecorator(group_decorator)
|
||||
elif len(objects) == 1:
|
||||
result = result.getChildren()[0] # Only one object found, return that.
|
||||
except Exception as e:
|
||||
Logger.log("e", "exception occured in 3mf reader: %s", e)
|
||||
|
||||
|
|
|
@ -61,6 +61,28 @@ message Polygon {
|
|||
float line_width = 3; // The width of the line being laid down
|
||||
}
|
||||
|
||||
message LayerOptimized {
|
||||
int32 id = 1;
|
||||
float height = 2; // Z position
|
||||
float thickness = 3; // height of a single layer
|
||||
|
||||
repeated PathSegment path_segment = 4; // layer data
|
||||
}
|
||||
|
||||
|
||||
message PathSegment {
|
||||
int32 extruder = 1; // The extruder used for this path segment
|
||||
enum PointType {
|
||||
Point2D = 0;
|
||||
Point3D = 1;
|
||||
}
|
||||
PointType point_type = 2;
|
||||
bytes points = 3; // The points defining the line segments, bytes of float[2/3] array of length N+1
|
||||
bytes line_type = 4; // Type of line segment as an unsigned char array of length 1 or N, where N is the number of line segments in this path
|
||||
bytes line_width = 5; // The widths of the line segments as bytes of a float array of length 1 or N
|
||||
}
|
||||
|
||||
|
||||
message GCodeLayer {
|
||||
bytes data = 2;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ from . import StartSliceJob
|
|||
|
||||
import os
|
||||
import sys
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
|
@ -56,6 +57,7 @@ class CuraEngineBackend(Backend):
|
|||
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
||||
self._onActiveViewChanged()
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
|
||||
#Triggers for when to (re)start slicing:
|
||||
self._global_container_stack = None
|
||||
|
@ -76,6 +78,7 @@ class CuraEngineBackend(Backend):
|
|||
|
||||
#Listeners for receiving messages from the back-end.
|
||||
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
|
||||
self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage
|
||||
self._message_handlers["cura.proto.Progress"] = self._onProgressMessage
|
||||
self._message_handlers["cura.proto.GCodeLayer"] = self._onGCodeLayerMessage
|
||||
self._message_handlers["cura.proto.GCodePrefix"] = self._onGCodePrefixMessage
|
||||
|
@ -89,7 +92,7 @@ class CuraEngineBackend(Backend):
|
|||
self._always_restart = True #Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
||||
self._process_layers_job = None #The currently active job to process layers, or None if it is not processing layers.
|
||||
|
||||
self._backend_log_max_lines = 200 # Maximal count of lines to buffer
|
||||
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
|
||||
self._error_message = None #Pop-up message that shows errors.
|
||||
|
||||
self.backendQuit.connect(self._onBackendQuit)
|
||||
|
@ -99,6 +102,8 @@ class CuraEngineBackend(Backend):
|
|||
Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted)
|
||||
Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped)
|
||||
|
||||
self._slice_start_time = None
|
||||
|
||||
## Called when closing the application.
|
||||
#
|
||||
# This function should terminate the engine process.
|
||||
|
@ -127,6 +132,7 @@ class CuraEngineBackend(Backend):
|
|||
|
||||
## Perform a slice of the scene.
|
||||
def slice(self):
|
||||
self._slice_start_time = time()
|
||||
if not self._enabled or not self._global_container_stack: #We shouldn't be slicing.
|
||||
# try again in a short time
|
||||
self._change_timer.start()
|
||||
|
@ -135,6 +141,7 @@ class CuraEngineBackend(Backend):
|
|||
self.printDurationMessage.emit(0, [0])
|
||||
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
|
||||
if self._slicing: #We were already slicing. Stop the old job.
|
||||
self._terminate()
|
||||
|
@ -163,6 +170,7 @@ class CuraEngineBackend(Backend):
|
|||
self._slicing = False
|
||||
self._restart = True
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
if self._start_slice_job is not None:
|
||||
self._start_slice_job.cancel()
|
||||
|
||||
|
@ -214,6 +222,7 @@ class CuraEngineBackend(Backend):
|
|||
|
||||
# Preparation completed, send it to the backend.
|
||||
self._socket.sendMessage(job.getSliceMessage())
|
||||
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
|
||||
|
||||
## Listener for when the scene has changed.
|
||||
#
|
||||
|
@ -243,6 +252,9 @@ class CuraEngineBackend(Backend):
|
|||
return
|
||||
|
||||
super()._onSocketError(error)
|
||||
if error.getErrorCode() == Arcus.ErrorCode.Debug:
|
||||
return
|
||||
|
||||
self._terminate()
|
||||
|
||||
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]:
|
||||
|
@ -262,6 +274,12 @@ class CuraEngineBackend(Backend):
|
|||
def _onLayerMessage(self, message):
|
||||
self._stored_layer_data.append(message)
|
||||
|
||||
## Called when an optimized sliced layer data message is received from the engine.
|
||||
#
|
||||
# \param message The protobuf message containing sliced layer data.
|
||||
def _onOptimizedLayerMessage(self, message):
|
||||
self._stored_optimized_layer_data.append(message)
|
||||
|
||||
## Called when a progress message is received from the engine.
|
||||
#
|
||||
# \param message The protobuf message containing the slicing progress.
|
||||
|
@ -277,11 +295,11 @@ class CuraEngineBackend(Backend):
|
|||
self.processingProgress.emit(1.0)
|
||||
|
||||
self._slicing = False
|
||||
|
||||
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
|
||||
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()):
|
||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_layer_data)
|
||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
|
||||
self._process_layers_job.start()
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
|
||||
## Called when a g-code message is received from the engine.
|
||||
#
|
||||
|
@ -352,10 +370,10 @@ class CuraEngineBackend(Backend):
|
|||
self._layer_view_active = True
|
||||
# There is data and we're not slicing at the moment
|
||||
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
|
||||
if self._stored_layer_data and not self._slicing:
|
||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_layer_data)
|
||||
if self._stored_optimized_layer_data and not self._slicing:
|
||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
|
||||
self._process_layers_job.start()
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
else:
|
||||
self._layer_view_active = False
|
||||
|
||||
|
|
|
@ -9,14 +9,16 @@ from UM.Mesh.MeshData import MeshData
|
|||
|
||||
from UM.Message import Message
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
|
||||
from cura import LayerDataBuilder
|
||||
from cura import LayerDataDecorator
|
||||
from cura import LayerPolygon
|
||||
|
||||
import numpy
|
||||
|
||||
from time import time
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
|
@ -38,6 +40,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
self._abort_requested = True
|
||||
|
||||
def run(self):
|
||||
start_time = time()
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
|
||||
self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
|
||||
self._progress.show()
|
||||
|
@ -80,26 +83,46 @@ class ProcessSlicedLayersJob(Job):
|
|||
abs_layer_number = layer.id + abs(min_layer_number)
|
||||
|
||||
layer_data.addLayer(abs_layer_number)
|
||||
this_layer = layer_data.getLayer(abs_layer_number)
|
||||
layer_data.setLayerHeight(abs_layer_number, layer.height)
|
||||
layer_data.setLayerThickness(abs_layer_number, layer.thickness)
|
||||
|
||||
for p in range(layer.repeatedMessageCount("polygons")):
|
||||
polygon = layer.getRepeatedMessage("polygons", p)
|
||||
for p in range(layer.repeatedMessageCount("path_segment")):
|
||||
polygon = layer.getRepeatedMessage("path_segment", p)
|
||||
|
||||
points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array
|
||||
points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||
extruder = polygon.extruder
|
||||
|
||||
line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array
|
||||
line_types = line_types.reshape((-1,1))
|
||||
|
||||
points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array
|
||||
if polygon.point_type == 0: # Point2D
|
||||
points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||
else: # Point3D
|
||||
points = points.reshape((-1,3))
|
||||
|
||||
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array
|
||||
line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||
|
||||
# Create a new 3D-array, copy the 2D points over and insert the right height.
|
||||
# This uses manual array creation + copy rather than numpy.insert since this is
|
||||
# faster.
|
||||
new_points = numpy.empty((len(points), 3), numpy.float32)
|
||||
new_points[:,0] = points[:,0]
|
||||
new_points[:,1] = layer.height
|
||||
new_points[:,2] = -points[:,1]
|
||||
if polygon.point_type == 0: # Point2D
|
||||
new_points[:,0] = points[:,0]
|
||||
new_points[:,1] = layer.height/1000 # layer height value is in backend representation
|
||||
new_points[:,2] = -points[:,1]
|
||||
else: # Point3D
|
||||
new_points[:,0] = points[:,0]
|
||||
new_points[:,1] = points[:,2]
|
||||
new_points[:,2] = -points[:,1]
|
||||
|
||||
|
||||
new_points /= 1000
|
||||
this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths)
|
||||
this_poly.buildCache()
|
||||
|
||||
this_layer.polygons.append(this_poly)
|
||||
|
||||
layer_data.addPolygon(abs_layer_number, polygon.type, new_points, polygon.line_width)
|
||||
Job.yieldThread()
|
||||
Job.yieldThread()
|
||||
current_layer += 1
|
||||
|
@ -150,6 +173,8 @@ class ProcessSlicedLayersJob(Job):
|
|||
# Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed.
|
||||
self._layers = None
|
||||
|
||||
Logger.log("d", "Processing layers took %s seconds", time() - start_time)
|
||||
|
||||
def _onActiveViewChanged(self):
|
||||
if self.isRunning():
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
|
||||
|
|
|
@ -78,22 +78,32 @@ class GCodeWriter(MeshWriter):
|
|||
|
||||
# Duplicate the current quality profile and update it with any user settings.
|
||||
flat_quality_id = machine_manager.duplicateContainer(container_with_profile.getId())
|
||||
|
||||
flat_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = flat_quality_id)[0]
|
||||
flat_quality._dirty = False
|
||||
user_settings = stack.getTop()
|
||||
|
||||
# We don't want to send out any signals, so disconnect them.
|
||||
flat_quality.propertyChanged.disconnectAll()
|
||||
|
||||
for key in user_settings.getAllKeys():
|
||||
flat_quality.setProperty(key, "value", user_settings.getProperty(key, "value"))
|
||||
|
||||
serialized = flat_quality.serialize()
|
||||
|
||||
data = {"global_quality": serialized}
|
||||
|
||||
manager = ExtruderManager.getInstance()
|
||||
for extruder in manager.getMachineExtruders(stack.getId()):
|
||||
extruder_quality = extruder.findContainer({"type": "quality"})
|
||||
|
||||
flat_extruder_quality_id = machine_manager.duplicateContainer(extruder_quality.getId())
|
||||
flat_extruder_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id=flat_extruder_quality_id)[0]
|
||||
flat_extruder_quality._dirty = False
|
||||
extruder_user_settings = extruder.getTop()
|
||||
|
||||
# We don't want to send out any signals, so disconnect them.
|
||||
flat_extruder_quality.propertyChanged.disconnectAll()
|
||||
|
||||
for key in extruder_user_settings.getAllKeys():
|
||||
flat_extruder_quality.setProperty(key, "value", extruder_user_settings.getProperty(key, "value"))
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ from . import LayerViewProxy
|
|||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
import numpy
|
||||
|
||||
## View used to display g-code paths.
|
||||
class LayerView(View):
|
||||
def __init__(self):
|
||||
|
@ -42,16 +44,11 @@ class LayerView(View):
|
|||
self._top_layers_job = None
|
||||
self._activity = False
|
||||
|
||||
Preferences.getInstance().addPreference("view/top_layer_count", 1)
|
||||
Preferences.getInstance().addPreference("view/top_layer_count", 5)
|
||||
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
|
||||
|
||||
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
|
||||
|
||||
self._top_layer_timer = QTimer()
|
||||
self._top_layer_timer.setInterval(50)
|
||||
self._top_layer_timer.setSingleShot(True)
|
||||
self._top_layer_timer.timeout.connect(self._startUpdateTopLayers)
|
||||
|
||||
self._busy = False
|
||||
|
||||
def getActivity(self):
|
||||
|
@ -130,8 +127,7 @@ class LayerView(View):
|
|||
self._current_layer_num = self._max_layers
|
||||
|
||||
self.resetLayerData()
|
||||
|
||||
self._top_layer_timer.start()
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
|
@ -163,7 +159,7 @@ class LayerView(View):
|
|||
else:
|
||||
self.setLayer(int(self._max_layers))
|
||||
self.maxLayersChanged.emit()
|
||||
self._top_layer_timer.start()
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
maxLayersChanged = Signal()
|
||||
currentLayerNumChanged = Signal()
|
||||
|
@ -217,7 +213,7 @@ class LayerView(View):
|
|||
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
|
||||
|
||||
self.resetLayerData()
|
||||
self._top_layer_timer.start()
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
class _CreateTopLayersJob(Job):
|
||||
def __init__(self, scene, layer_number, solid_layers):
|
||||
|
@ -253,11 +249,13 @@ class _CreateTopLayersJob(Job):
|
|||
if not layer or layer.getVertices() is None:
|
||||
continue
|
||||
|
||||
layer_mesh.addIndices(layer_mesh._vertex_count+layer.getIndices())
|
||||
layer_mesh.addVertices(layer.getVertices())
|
||||
|
||||
# Scale layer color by a brightness factor based on the current layer number
|
||||
# This will result in a range of 0.5 - 1.0 to multiply colors by.
|
||||
brightness = (2.0 - (i / self._solid_layers)) / 2.0
|
||||
brightness = numpy.ones((1,4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
|
||||
brightness[0, 3] = 1.0;
|
||||
layer_mesh.addColors(layer.getColors() * brightness)
|
||||
|
||||
if self._cancel:
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"adhesion_type": "\"skirt\" if (platform_adhesion == \"None\") else platform_adhesion.lower()",
|
||||
"skirt_line_count": "skirt_line_count",
|
||||
"skirt_gap": "skirt_gap",
|
||||
"skirt_minimal_length": "skirt_minimal_length",
|
||||
"skirt_brim_minimal_length": "skirt_minimal_length",
|
||||
"brim_line_count": "brim_line_count",
|
||||
"raft_margin": "raft_margin",
|
||||
"raft_airgap": "float(raft_airgap_all) + float(raft_airgap)",
|
||||
|
|
|
@ -47,7 +47,9 @@ Item {
|
|||
id: extruders_model
|
||||
onRowsInserted: extruderSelector.visible = extruders_model.rowCount() > 1
|
||||
onModelReset: extruderSelector.visible = extruders_model.rowCount() > 1
|
||||
onModelChanged: extruderSelector.color = extruders_model.getItem(extruderSelector.currentIndex).colour
|
||||
}
|
||||
property string color: extruders_model.getItem(extruderSelector.currentIndex).colour
|
||||
visible: extruders_model.rowCount() > 1
|
||||
textRole: "name"
|
||||
width: UM.Theme.getSize("setting_control").width
|
||||
|
@ -88,7 +90,7 @@ Item {
|
|||
anchors.leftMargin: UM.Theme.getSize("default_lining").width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
color: extruders_model.getItem(extruderSelector.currentIndex).colour
|
||||
color: extruderSelector.color
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border")
|
||||
}
|
||||
|
@ -136,6 +138,7 @@ Item {
|
|||
if(extruders_model.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder"))
|
||||
{
|
||||
extruderSelector.currentIndex = i;
|
||||
extruderSelector.color = extruders_model.getItem(i).colour;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +184,9 @@ Item {
|
|||
onLoaded: {
|
||||
settingLoader.item.showRevertButton = false
|
||||
settingLoader.item.showInheritButton = false
|
||||
settingLoader.item.showLinkedSettingIcon = false
|
||||
settingLoader.item.doDepthIndentation = false
|
||||
settingLoader.item.doQualityUserSettingEmphasis = false
|
||||
}
|
||||
|
||||
sourceComponent:
|
||||
|
@ -241,6 +246,7 @@ Item {
|
|||
key: model.key
|
||||
watchedProperties: [ "value", "enabled", "validationState" ]
|
||||
storeIndex: 0
|
||||
removeUnusedValue: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ from UM.Message import Message
|
|||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Platform import Platform
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
|
||||
import collections
|
||||
import json
|
||||
|
@ -90,7 +91,7 @@ class SliceInfo(Extension):
|
|||
"settings": global_container_stack.serialize(), # global_container with references on used containers
|
||||
"version": Application.getInstance().getVersion(),
|
||||
"modelhash": "None",
|
||||
"printtime": print_information.currentPrintTime.getDisplayString(),
|
||||
"printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601),
|
||||
"filament": material_used,
|
||||
"language": Preferences.getInstance().getValue("general/language"),
|
||||
}
|
||||
|
@ -126,4 +127,4 @@ class SliceInfo(Extension):
|
|||
except:
|
||||
# We really can't afford to have a mistake here, as this would break the sending of g-code to a device
|
||||
# (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
|
||||
pass
|
||||
pass
|
||||
|
|
|
@ -34,9 +34,10 @@ class SolidView(View):
|
|||
self._enabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
|
||||
|
||||
if not self._disabled_shader:
|
||||
self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
|
||||
self._disabled_shader.setUniformValue("u_diffuseColor", [0.68, 0.68, 0.68, 1.0])
|
||||
self._disabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
|
||||
self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
|
||||
self._disabled_shader.setUniformValue("u_diffuseColor1", [0.48, 0.48, 0.48, 1.0])
|
||||
self._disabled_shader.setUniformValue("u_diffuseColor2", [0.68, 0.68, 0.68, 1.0])
|
||||
self._disabled_shader.setUniformValue("u_width", 50.0)
|
||||
|
||||
if Application.getInstance().getGlobalContainerStack():
|
||||
if Preferences.getInstance().getValue("view/show_overhang"):
|
||||
|
|
|
@ -8,14 +8,12 @@ import time
|
|||
import queue
|
||||
import re
|
||||
import functools
|
||||
import os.path
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||
from UM.Message import Message
|
||||
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
@ -137,7 +135,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
# \param gcode_list List with gcode (strings).
|
||||
def printGCode(self, gcode_list):
|
||||
if self._progress or self._connection_state != ConnectionState.connected:
|
||||
self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is busy or not connected. Unable to start a new job."))
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Printer is busy or not connected. Unable to start a new job."))
|
||||
self._error_message.show()
|
||||
Logger.log("d", "Printer is busy or not connected, aborting print")
|
||||
self.writeError.emit(self)
|
||||
|
@ -504,6 +502,13 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
# It will be normalized (based on max_progress) to range 0 - 100
|
||||
def setProgress(self, progress, max_progress = 100):
|
||||
self._progress = (progress / max_progress) * 100 # Convert to scale of 0-100
|
||||
if self._progress == 100:
|
||||
# Printing is done, reset progress
|
||||
self._gcode_position = 0
|
||||
self.setProgress(0)
|
||||
self._is_printing = False
|
||||
self._is_paused = False
|
||||
self._updateJobState("ready")
|
||||
self.progressChanged.emit()
|
||||
|
||||
## Cancel the current print. Printer connection wil continue to listen.
|
||||
|
|
|
@ -12,6 +12,9 @@ class UMOUpgradeSelection(MachineAction):
|
|||
super().__init__("UMOUpgradeSelection", catalog.i18nc("@action", "Select upgrades"))
|
||||
self._qml_url = "UMOUpgradeSelectionMachineAction.qml"
|
||||
|
||||
def _reset(self):
|
||||
self.heatedBedChanged.emit()
|
||||
|
||||
heatedBedChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify = heatedBedChanged)
|
||||
|
|
|
@ -22,7 +22,7 @@ Cura.MachineAction
|
|||
{
|
||||
id: pageTitle
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Check Printer")
|
||||
text: catalog.i18nc("@title", "Select Printer Upgrades")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18;
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ Cura.MachineAction
|
|||
|
||||
text: catalog.i18nc("@label", "Heated bed (official kit or self-built)")
|
||||
checked: manager.hasHeatedBed
|
||||
onClicked: manager.hasHeatedBed ? manager.removeHeatedBed() : manager.addHeatedBed()
|
||||
onClicked: checked ? manager.addHeatedBed() : manager.removeHeatedBed()
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
|
|
@ -31,6 +31,9 @@ _setting_name_translations = {
|
|||
"remove_overlapping_walls_enabled": "travel_compensate_overlapping_walls_enabled",
|
||||
"remove_overlapping_walls_x_enabled": "travel_compensate_overlapping_walls_x_enabled",
|
||||
"retraction_hop": "retraction_hop_enabled",
|
||||
"skirt_line_width": "skirt_brim_line_width",
|
||||
"skirt_minimal_length": "skirt_brim_minimal_length",
|
||||
"skirt_speed": "skirt_brim_speed",
|
||||
"speed_support_lines": "speed_support_infill"
|
||||
}
|
||||
|
||||
|
@ -157,6 +160,15 @@ class VersionUpgrade21to22(VersionUpgrade):
|
|||
elif key == "retraction_hop": #Setting key was changed.
|
||||
del settings[key]
|
||||
settings["retraction_hop_enabled"] = value
|
||||
elif key == "skirt_minimal_length": #Setting key was changed.
|
||||
del settings[key]
|
||||
settings["skirt_brim_minimal_length"] = value
|
||||
elif key == "skirt_line_width": #Setting key was changed.
|
||||
del settings[key]
|
||||
settings["skirt_brim_line_width"] = value
|
||||
elif key == "skirt_speed": #Setting key was changed.
|
||||
del settings[key]
|
||||
settings["skirt_brim_speed"] = value
|
||||
elif key == "speed_support_lines": #Setting key was changed.
|
||||
del settings[key]
|
||||
settings["speed_support_infill"] = value
|
||||
|
|
|
@ -38,9 +38,9 @@
|
|||
"speed_wall_0": { "default_value": 30 },
|
||||
"speed_infill": { "default_value": 80 },
|
||||
"speed_topbottom": { "default_value": 35 },
|
||||
"skirt_speed": { "default_value": 35 },
|
||||
"skirt_brim_speed": { "default_value": 35 },
|
||||
"skirt_line_count": { "default_value": 4 },
|
||||
"skirt_minimal_length": { "default_value": 30 },
|
||||
"skirt_brim_minimal_length": { "default_value": 30 },
|
||||
"skirt_gap": { "default_value": 6 },
|
||||
"cool_fan_full_at_height": { "default_value": 0.4 }
|
||||
}
|
||||
|
|
|
@ -89,13 +89,13 @@
|
|||
"speed_topbottom": {
|
||||
"default_value": 35
|
||||
},
|
||||
"skirt_speed": {
|
||||
"skirt_brim_speed": {
|
||||
"default_value": 35
|
||||
},
|
||||
"skirt_line_count": {
|
||||
"default_value": 4
|
||||
},
|
||||
"skirt_minimal_length": {
|
||||
"skirt_brim_minimal_length": {
|
||||
"default_value": 30
|
||||
},
|
||||
"skirt_gap": {
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
"unit": "mm",
|
||||
"default_value": 0,
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "machine_height",
|
||||
"maximum_value": "machine_height",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
|
@ -175,7 +175,7 @@
|
|||
"unit": "mm",
|
||||
"default_value": 0,
|
||||
"minimum_value_warning": "machine_nozzle_offset_x",
|
||||
"maximum_value_warning": "machine_width",
|
||||
"maximum_value": "machine_width",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"enabled": false
|
||||
|
|
|
@ -342,7 +342,7 @@
|
|||
"unit": "mm",
|
||||
"default_value": 0,
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "machine_height",
|
||||
"maximum_value": "machine_height",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
@ -603,10 +603,10 @@
|
|||
"value": "line_width",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"skirt_line_width":
|
||||
"skirt_brim_line_width":
|
||||
{
|
||||
"label": "Skirt Line Width",
|
||||
"description": "Width of a single skirt line.",
|
||||
"label": "Skirt/Brim Line Width",
|
||||
"description": "Width of a single skirt or brim line.",
|
||||
"unit": "mm",
|
||||
"minimum_value": "0.0001",
|
||||
"minimum_value_warning": "0.2",
|
||||
|
@ -614,6 +614,7 @@
|
|||
"default_value": 0.4,
|
||||
"type": "float",
|
||||
"value": "line_width",
|
||||
"enabled": "adhesion_type == \"skirt\" or adhesion_type == \"brim\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
@ -630,7 +631,7 @@
|
|||
"enabled": "support_enable",
|
||||
"value": "line_width",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"support_roof_line_width":
|
||||
{
|
||||
|
@ -644,7 +645,7 @@
|
|||
"enabled": "support_roof_enable",
|
||||
"value": "line_width",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"prime_tower_line_width":
|
||||
{
|
||||
|
@ -706,7 +707,7 @@
|
|||
"default_value": 0.8,
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "0.6",
|
||||
"maximum_value_warning": "machine_height",
|
||||
"maximum_value": "machine_height",
|
||||
"type": "float",
|
||||
"settable_per_mesh": true,
|
||||
"children":
|
||||
|
@ -718,7 +719,7 @@
|
|||
"unit": "mm",
|
||||
"default_value": 0.8,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "machine_height",
|
||||
"maximum_value": "machine_height",
|
||||
"type": "float",
|
||||
"value": "top_bottom_thickness",
|
||||
"settable_per_mesh": true,
|
||||
|
@ -746,7 +747,7 @@
|
|||
"minimum_value": "0",
|
||||
"type": "float",
|
||||
"value": "top_bottom_thickness",
|
||||
"maximum_value_warning": "machine_height",
|
||||
"maximum_value": "machine_height",
|
||||
"settable_per_mesh": true,
|
||||
"children":
|
||||
{
|
||||
|
@ -1060,10 +1061,9 @@
|
|||
{
|
||||
"label": "Flow Temperature Graph",
|
||||
"description": "Data linking material flow (in mm3 per second) to temperature (degrees Celsius).",
|
||||
"unit": "",
|
||||
"unit": "[[mm³,°C]]",
|
||||
"type": "str",
|
||||
"default_value": "[[3.5,200],[7.0,240]]",
|
||||
"enabled": "False",
|
||||
"comments": "old enabled function: material_flow_dependent_temperature",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
|
@ -1444,7 +1444,7 @@
|
|||
"value": "speed_print",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
{
|
||||
"speed_support_infill":
|
||||
|
@ -1460,7 +1460,7 @@
|
|||
"value": "speed_support",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"speed_support_roof":
|
||||
{
|
||||
|
@ -1475,7 +1475,7 @@
|
|||
"enabled": "support_roof_enable and support_enable",
|
||||
"value": "speed_support / 1.5",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1550,9 +1550,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"skirt_speed": {
|
||||
"label": "Skirt Speed",
|
||||
"description": "The speed at which the skirt and brim are printed. Normally this is done at the initial layer speed, but sometimes you might want to print the skirt at a different speed.",
|
||||
"skirt_brim_speed": {
|
||||
"label": "Skirt/Brim Speed",
|
||||
"description": "The speed at which the skirt and brim are printed. Normally this is done at the initial layer speed, but sometimes you might want to print the skirt or brim at a different speed.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 30,
|
||||
|
@ -1560,6 +1560,7 @@
|
|||
"maximum_value": "299792458000",
|
||||
"maximum_value_warning": "300",
|
||||
"value": "speed_layer_0",
|
||||
"enabled": "adhesion_type == \"skirt\" or adhesion_type == \"brim\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
@ -1676,7 +1677,7 @@
|
|||
"value": "acceleration_print",
|
||||
"enabled": "acceleration_enabled and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_extruder": true,
|
||||
"children": {
|
||||
"acceleration_support_infill": {
|
||||
"label": "Support Infill Acceleration",
|
||||
|
@ -1690,7 +1691,7 @@
|
|||
"maximum_value_warning": "10000",
|
||||
"enabled": "acceleration_enabled and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"acceleration_support_roof": {
|
||||
"label": "Support Roof Acceleration",
|
||||
|
@ -1704,7 +1705,7 @@
|
|||
"maximum_value_warning": "10000",
|
||||
"enabled": "acceleration_enabled and support_roof_enable and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1749,9 +1750,9 @@
|
|||
"enabled": "acceleration_enabled",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"acceleration_skirt": {
|
||||
"label": "Skirt Acceleration",
|
||||
"description": "The acceleration with which the skirt and brim are printed. Normally this is done with the initial layer acceleration, but sometimes you might want to print the skirt at a different acceleration.",
|
||||
"acceleration_skirt_brim": {
|
||||
"label": "Skirt/Brim Acceleration",
|
||||
"description": "The acceleration with which the skirt and brim are printed. Normally this is done with the initial layer acceleration, but sometimes you might want to print the skirt or brim at a different acceleration.",
|
||||
"unit": "mm/s²",
|
||||
"type": "float",
|
||||
"default_value": 3000,
|
||||
|
@ -1864,7 +1865,7 @@
|
|||
"value": "jerk_print",
|
||||
"enabled": "jerk_enabled and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_extruder": true,
|
||||
"children": {
|
||||
"jerk_support_infill": {
|
||||
"label": "Support Infill Jerk",
|
||||
|
@ -1878,7 +1879,7 @@
|
|||
"maximum_value_warning": "50",
|
||||
"enabled": "jerk_enabled and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"jerk_support_roof": {
|
||||
"label": "Support Roof Jerk",
|
||||
|
@ -1892,7 +1893,7 @@
|
|||
"maximum_value_warning": "50",
|
||||
"enabled": "jerk_enabled and support_roof_enable and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1937,8 +1938,8 @@
|
|||
"enabled": "jerk_enabled",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"jerk_skirt": {
|
||||
"label": "Skirt Jerk",
|
||||
"jerk_skirt_brim": {
|
||||
"label": "Skirt/Brim Jerk",
|
||||
"description": "The maximum instantaneous velocity change with which the skirt and brim are printed.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
|
@ -2151,7 +2152,8 @@
|
|||
"description": "Enable support structures. These structures support parts of the model with severe overhangs.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"settable_per_mesh": true
|
||||
"settable_per_mesh": true,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
"support_type":
|
||||
{
|
||||
|
@ -2177,6 +2179,7 @@
|
|||
"minimum_value": "0",
|
||||
"maximum_value": "90",
|
||||
"default_value": 50,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
@ -2196,7 +2199,7 @@
|
|||
"default_value": "zigzag",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"support_connect_zigzags":
|
||||
{
|
||||
|
@ -2206,7 +2209,7 @@
|
|||
"default_value": true,
|
||||
"enabled": "support_enable and (support_pattern == \"zigzag\")",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"support_infill_rate":
|
||||
{
|
||||
|
@ -2219,7 +2222,7 @@
|
|||
"default_value": 15,
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_extruder": true,
|
||||
"children": {
|
||||
"support_line_distance":
|
||||
{
|
||||
|
@ -2232,7 +2235,7 @@
|
|||
"enabled": "support_enable",
|
||||
"value": "(support_line_width * 100) / support_infill_rate * (2 if support_pattern == \"grid\" else (3 if support_pattern == \"triangles\" else 1))",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2245,6 +2248,7 @@
|
|||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"default_value": 0.15,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true,
|
||||
"children":
|
||||
|
@ -2260,6 +2264,7 @@
|
|||
"type": "float",
|
||||
"enabled": "support_enable",
|
||||
"value": "support_z_distance",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_bottom_distance":
|
||||
|
@ -2271,6 +2276,7 @@
|
|||
"maximum_value_warning": "10",
|
||||
"default_value": 0.1,
|
||||
"value": "0.1 if support_type == 'everywhere' else 0",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"type": "float",
|
||||
"enabled": "support_enable and support_type == 'everywhere'",
|
||||
"settable_per_mesh": true
|
||||
|
@ -2286,6 +2292,7 @@
|
|||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"default_value": 0.7,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
@ -2298,6 +2305,7 @@
|
|||
"z_overrides_xy": "Z overrides X/Y"
|
||||
},
|
||||
"default_value": "z_overrides_xy",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
@ -2310,6 +2318,7 @@
|
|||
"maximum_value_warning": "10",
|
||||
"default_value": 0.2,
|
||||
"value": "machine_nozzle_size / 2",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable and support_xy_overrides_z=='z_overrides_xy'",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
@ -2320,6 +2329,7 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0.3,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "1.0",
|
||||
"enabled": "support_enable",
|
||||
|
@ -2332,6 +2342,7 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 2.0,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "support_enable",
|
||||
|
@ -2344,6 +2355,7 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0.2,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"minimum_value_warning": "-0.5",
|
||||
"maximum_value_warning": "5.0",
|
||||
"enabled": "support_enable",
|
||||
|
@ -2356,6 +2368,7 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0.6,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "1.0",
|
||||
"enabled": "support_enable",
|
||||
|
@ -2367,6 +2380,7 @@
|
|||
"description": "Generate a dense top skin at the top of the support on which the model is printed.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
@ -2378,6 +2392,7 @@
|
|||
"type": "float",
|
||||
"default_value": 1,
|
||||
"minimum_value": "0",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "support_roof_enable and support_enable",
|
||||
"settable_per_mesh": true
|
||||
|
@ -2393,7 +2408,7 @@
|
|||
"maximum_value_warning": "100",
|
||||
"enabled":"support_roof_enable and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
{
|
||||
"support_roof_line_distance":
|
||||
|
@ -2407,7 +2422,7 @@
|
|||
"value": "0 if support_roof_density == 0 else (support_roof_line_width * 100) / support_roof_density * (2 if support_roof_pattern == \"grid\" else (3 if support_roof_pattern == \"triangles\" else 1))",
|
||||
"enabled": "support_roof_enable and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2427,7 +2442,7 @@
|
|||
"default_value": "concentric",
|
||||
"enabled": "support_roof_enable and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"support_use_towers":
|
||||
{
|
||||
|
@ -2435,6 +2450,7 @@
|
|||
"description": "Use specialized towers to support tiny overhang areas. These towers have a larger diameter than the region they support. Near the overhang the towers' diameter decreases, forming a roof.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
@ -2445,6 +2461,7 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 3.0,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "support_enable and support_use_towers",
|
||||
|
@ -2457,6 +2474,7 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 3.0,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"maximum_value": "support_tower_diameter",
|
||||
|
@ -2472,6 +2490,7 @@
|
|||
"minimum_value": "0",
|
||||
"maximum_value": "90",
|
||||
"default_value": 65,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable and support_use_towers",
|
||||
"settable_per_mesh": true
|
||||
}
|
||||
|
@ -2551,17 +2570,17 @@
|
|||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"skirt_minimal_length":
|
||||
"skirt_brim_minimal_length":
|
||||
{
|
||||
"label": "Skirt Minimum Length",
|
||||
"description": "The minimum length of the skirt. If this length is not reached by the skirt line count, more skirt lines will be added until the minimum length is reached. Note: If the line count is set to 0 this is ignored.",
|
||||
"label": "Skirt/Brim Minimum Length",
|
||||
"description": "The minimum length of the skirt or brim. If this length is not reached by all skirt or brim lines together, more skirt or brim lines will be added until the minimum length is reached. Note: If the line count is set to 0 this is ignored.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 250,
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "25",
|
||||
"maximum_value_warning": "2500",
|
||||
"enabled": "adhesion_type == \"skirt\"",
|
||||
"enabled": "adhesion_type == \"skirt\" or adhesion_type == \"brim\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
@ -2587,7 +2606,7 @@
|
|||
"default_value": 20,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "300",
|
||||
"value": "math.ceil(brim_width / skirt_line_width)",
|
||||
"value": "math.ceil(brim_width / skirt_brim_line_width)",
|
||||
"enabled": "adhesion_type == \"brim\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
|
@ -2627,7 +2646,7 @@
|
|||
"value": "raft_airgap / 2",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "layer_height",
|
||||
"enabled": "adhesion_type == 'raft'",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
@ -3359,7 +3378,7 @@
|
|||
"maximum_value_warning": "9999",
|
||||
"default_value": 0,
|
||||
"value": "9999 if draft_shield_height_limitation == 'full' and draft_shield_enabled else 0.0",
|
||||
"enabled": "draft_shield_height_limitation == \"limited\"",
|
||||
"enabled": "draft_shield_enabled and draft_shield_height_limitation == \"limited\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"speed_layer_0": {
|
||||
"default_value": 20
|
||||
},
|
||||
"skirt_speed": {
|
||||
"skirt_brim_speed": {
|
||||
"default_value": 15
|
||||
},
|
||||
"speed_slowdown_layers": {
|
||||
|
|
|
@ -61,6 +61,6 @@
|
|||
"cool_fan_speed": { "default": 0 },
|
||||
"skirt_line_count": { "default": 3 },
|
||||
"skirt_gap": { "default": 4 },
|
||||
"skirt_minimal_length": { "default": 200 }
|
||||
"skirt_brim_minimal_length": { "default": 200 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"default_value": 4,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
},
|
||||
"skirt_minimal_length": {
|
||||
"skirt_brim_minimal_length": {
|
||||
"default_value": 200,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
"default_value": 4,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
},
|
||||
"skirt_minimal_length": {
|
||||
"skirt_brim_minimal_length": {
|
||||
"default_value": 200,
|
||||
"enabled": "adhesion_type == \"Skirt\""
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
"default_value": 2
|
||||
},
|
||||
"gantry_height": {
|
||||
"default_value": 55
|
||||
"default_value": 48
|
||||
},
|
||||
"machine_use_extruder_offset_to_offset_coords": {
|
||||
"default_value": true
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
"has_materials": true,
|
||||
"preferred_material": "*pla*",
|
||||
"preferred_quality": "*normal*",
|
||||
"supported_actions":["UMOCheckup", "UpgradeFirmware", "BedLevel", "UMOUpgradeSelection"]
|
||||
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOCheckup", "UpgradeFirmware", "BedLevel", "UMOUpgradeSelection"]
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -321,23 +321,7 @@ UM.MainWindow
|
|||
sourceSize.height: height;
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: viewModeButton
|
||||
|
||||
anchors
|
||||
{
|
||||
top: toolbar.bottom;
|
||||
topMargin: UM.Theme.getSize("window_margin").height;
|
||||
left: parent.left;
|
||||
}
|
||||
text: catalog.i18nc("@action:button","View Mode");
|
||||
iconSource: UM.Theme.getIcon("viewmode");
|
||||
|
||||
style: UM.Theme.styles.tool_button;
|
||||
tooltip: '';
|
||||
menu: ViewMenu { }
|
||||
}
|
||||
|
||||
Toolbar
|
||||
{
|
||||
|
@ -367,6 +351,24 @@ UM.MainWindow
|
|||
width: UM.Theme.getSize("sidebar").width;
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: viewModeButton
|
||||
|
||||
anchors
|
||||
{
|
||||
top: toolbar.bottom;
|
||||
topMargin: UM.Theme.getSize("window_margin").height;
|
||||
left: parent.left;
|
||||
}
|
||||
text: catalog.i18nc("@action:button","View Mode");
|
||||
iconSource: UM.Theme.getIcon("viewmode");
|
||||
|
||||
style: UM.Theme.styles.tool_button;
|
||||
tooltip: '';
|
||||
menu: ViewMenu { }
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: viewportOverlay
|
||||
|
@ -638,7 +640,7 @@ UM.MainWindow
|
|||
//TODO: Support multiple file selection, workaround bug in KDE file dialog
|
||||
//selectMultiple: true
|
||||
nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
|
||||
folder: Printer.getDefaultPath()
|
||||
folder: CuraApplication.getDefaultPath("dialog_load_path")
|
||||
onAccepted:
|
||||
{
|
||||
//Because several implementations of the file dialog only update the folder
|
||||
|
@ -646,6 +648,7 @@ UM.MainWindow
|
|||
var f = folder;
|
||||
folder = f;
|
||||
|
||||
CuraApplication.setDefaultPath("dialog_load_path", folder);
|
||||
UM.MeshFileHandler.readLocalFile(fileUrl)
|
||||
var meshName = backgroundItem.getMeshName(fileUrl.toString())
|
||||
backgroundItem.hasMesh(decodeURIComponent(meshName))
|
||||
|
|
|
@ -195,15 +195,21 @@ Rectangle {
|
|||
color: UM.Theme.getColor("text_subtext")
|
||||
text:
|
||||
{
|
||||
var amounts = [];
|
||||
var lengths = [];
|
||||
var weights = [];
|
||||
if(base.printMaterialLengths) {
|
||||
for(var index = 0; index < base.printMaterialLengths.length; index++) {
|
||||
amounts.push(base.printMaterialLengths[index].toFixed(2));
|
||||
if(base.printMaterialLengths[index] > 0) {
|
||||
lengths.push(base.printMaterialLengths[index].toFixed(2));
|
||||
weights.push(String(Math.floor(base.printMaterialWeights[index])));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
amounts = ["0.00"];
|
||||
}
|
||||
return catalog.i18nc("@label", "%1 m").arg(amounts.join(" + "));
|
||||
if(lengths.length == 0) {
|
||||
lengths = ["0.00"];
|
||||
weights = ["0"];
|
||||
}
|
||||
return catalog.i18nc("@label", "%1 m / %2 g").arg(lengths.join(" + ")).arg(weights.join(" + "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ Rectangle
|
|||
{
|
||||
if(!printerConnected)
|
||||
return UM.Theme.getColor("status_offline")
|
||||
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print")
|
||||
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print" || Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup" )
|
||||
return UM.Theme.getColor("status_busy")
|
||||
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "ready" || Cura.MachineManager.printerOutputDevices[0].jobState == "")
|
||||
return UM.Theme.getColor("status_ready")
|
||||
|
@ -53,6 +53,10 @@ Rectangle
|
|||
{
|
||||
return catalog.i18nc("@label:", "Preparing...")
|
||||
}
|
||||
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup")
|
||||
{
|
||||
return catalog.i18nc("@label:", "Waiting for cleanup...")
|
||||
}
|
||||
else
|
||||
{
|
||||
return " "
|
||||
|
|
|
@ -67,6 +67,8 @@ UM.ManagementPage
|
|||
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId
|
||||
onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id)
|
||||
},
|
||||
/*
|
||||
// disabled because it has a lot of issues
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Duplicate");
|
||||
|
@ -89,7 +91,7 @@ UM.ManagementPage
|
|||
|
||||
Cura.MachineManager.setActiveMaterial(material_id)
|
||||
}
|
||||
},
|
||||
}, */
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Remove");
|
||||
|
@ -200,7 +202,7 @@ UM.ManagementPage
|
|||
title: catalog.i18nc("@title:window", "Import Material");
|
||||
selectExisting: true;
|
||||
nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
|
||||
folder: CuraApplication.getDefaultPath()
|
||||
folder: CuraApplication.getDefaultPath("dialog_material_path")
|
||||
onAccepted:
|
||||
{
|
||||
var result = Cura.ContainerManager.importContainer(fileUrl)
|
||||
|
@ -221,6 +223,7 @@ UM.ManagementPage
|
|||
messageDialog.icon = StandardIcon.Critical
|
||||
}
|
||||
messageDialog.open()
|
||||
CuraApplication.setDefaultPath("dialog_material_path", folder)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +233,7 @@ UM.ManagementPage
|
|||
title: catalog.i18nc("@title:window", "Export Material");
|
||||
selectExisting: false;
|
||||
nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
|
||||
folder: CuraApplication.getDefaultPath()
|
||||
folder: CuraApplication.getDefaultPath("dialog_material_path")
|
||||
onAccepted:
|
||||
{
|
||||
if(base.currentItem.metadata.base_file)
|
||||
|
@ -255,6 +258,7 @@ UM.ManagementPage
|
|||
messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to <filename>%1</filename>").arg(fileUrl)
|
||||
messageDialog.open()
|
||||
}
|
||||
CuraApplication.setDefaultPath("dialog_material_path", folder)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -291,7 +291,7 @@ UM.ManagementPage
|
|||
title: catalog.i18nc("@title:window", "Import Profile");
|
||||
selectExisting: true;
|
||||
nameFilters: base.model.getFileNameFilters("profile_reader")
|
||||
folder: base.model.getDefaultPath()
|
||||
folder: CuraApplication.getDefaultPath("dialog_profile_path")
|
||||
onAccepted:
|
||||
{
|
||||
var result = base.model.importProfile(fileUrl)
|
||||
|
@ -309,6 +309,7 @@ UM.ManagementPage
|
|||
messageDialog.icon = StandardIcon.Critical
|
||||
}
|
||||
messageDialog.open()
|
||||
CuraApplication.setDefaultPath("dialog_profile_path", folder)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,7 +319,7 @@ UM.ManagementPage
|
|||
title: catalog.i18nc("@title:window", "Export Profile");
|
||||
selectExisting: false;
|
||||
nameFilters: base.model.getFileNameFilters("profile_writer")
|
||||
folder: base.model.getDefaultPath()
|
||||
folder: CuraApplication.getDefaultPath("dialog_profile_path")
|
||||
onAccepted:
|
||||
{
|
||||
var result = base.model.exportProfile(base.currentItem.id, fileUrl, selectedNameFilter)
|
||||
|
@ -329,6 +330,7 @@ UM.ManagementPage
|
|||
messageDialog.open()
|
||||
}
|
||||
// else pop-up Message thing from python code
|
||||
CuraApplication.setDefaultPath("dialog_profile_path", folder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,12 +62,6 @@ Column
|
|||
property string label: catalog.i18nc("@label", "Estimated time left")
|
||||
property string value: printerConnected ? getPrettyTime(Cura.MachineManager.printerOutputDevices[0].timeTotal - Cura.MachineManager.printerOutputDevices[0].timeElapsed) : ""
|
||||
}
|
||||
Loader
|
||||
{
|
||||
sourceComponent: monitorItem
|
||||
property string label: catalog.i18nc("@label", "Current Layer")
|
||||
property string value: printerConnected ? "0" : ""
|
||||
}
|
||||
|
||||
Component
|
||||
{
|
||||
|
|
|
@ -84,6 +84,27 @@ Rectangle {
|
|||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
|
||||
Row {
|
||||
id: additionalComponentsRow
|
||||
anchors.top: parent.top
|
||||
anchors.right: saveToButton.visible ? saveToButton.left : parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Printer
|
||||
onAdditionalComponentsChanged:
|
||||
{
|
||||
if(areaId == "saveButton") {
|
||||
for (var component in Printer.additionalComponents["saveButton"]) {
|
||||
Printer.additionalComponents["saveButton"][component].parent = additionalComponentsRow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: saveToButton
|
||||
|
||||
|
@ -102,8 +123,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
style: ButtonStyle {
|
||||
background:
|
||||
Rectangle
|
||||
background: Rectangle
|
||||
{
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") :
|
||||
|
@ -126,7 +146,7 @@ Rectangle {
|
|||
text: control.text;
|
||||
}
|
||||
}
|
||||
label: Item { }
|
||||
label: Item { }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,13 @@ Item {
|
|||
|
||||
property var showRevertButton: true
|
||||
property var showInheritButton: true
|
||||
property var showLinkedSettingIcon: true
|
||||
property var doDepthIndentation: true
|
||||
property var doQualityUserSettingEmphasis: true
|
||||
|
||||
// Create properties to put property provider stuff in (bindings break in qt 5.5.1 otherwise)
|
||||
property var state: propertyProvider.properties.state
|
||||
property var settablePerExtruder: propertyProvider.properties.settable_per_extruder
|
||||
property var stackLevels: propertyProvider.stackLevels
|
||||
property var stackLevel: stackLevels[0]
|
||||
|
||||
|
@ -115,7 +118,7 @@ Item {
|
|||
|
||||
color: UM.Theme.getColor("setting_control_text");
|
||||
// emphasize the setting if it has a value in the user or quality profile
|
||||
font: base.stackLevel != undefined && base.stackLevel <= 1 ? UM.Theme.getFont("default_italic") : UM.Theme.getFont("default")
|
||||
font: base.doQualityUserSettingEmphasis && base.stackLevel != undefined && base.stackLevel <= 1 ? UM.Theme.getFont("default_italic") : UM.Theme.getFont("default")
|
||||
}
|
||||
|
||||
Row
|
||||
|
@ -131,6 +134,26 @@ Item {
|
|||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
UM.SimpleButton
|
||||
{
|
||||
id: linkedSettingIcon;
|
||||
|
||||
visible: base.settablePerExtruder != "True" && base.showLinkedSettingIcon
|
||||
|
||||
height: parent.height;
|
||||
width: height;
|
||||
|
||||
backgroundColor: UM.Theme.getColor("setting_control");
|
||||
hoverBackgroundColor: UM.Theme.getColor("setting_control")
|
||||
color: UM.Theme.getColor("setting_control_button")
|
||||
hoverColor: UM.Theme.getColor("setting_control_button")
|
||||
|
||||
iconSource: UM.Theme.getIcon("link")
|
||||
|
||||
onEntered: { hoverTimer.stop(); base.showTooltip(catalog.i18nc("@label", "This setting is always shared between all extruders. Changing it here will change the value for all extruders")) }
|
||||
onExited: base.showTooltip(base.tooltipText);
|
||||
}
|
||||
|
||||
UM.SimpleButton
|
||||
{
|
||||
id: revertButton;
|
||||
|
@ -231,7 +254,6 @@ Item {
|
|||
onEntered: { hoverTimer.stop(); base.showTooltip(catalog.i18nc("@label", "This setting is normally calculated, but it currently has an absolute value set.\n\nClick to restore the calculated value.")) }
|
||||
onExited: base.showTooltip(base.tooltipText);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item
|
||||
|
|
|
@ -33,15 +33,6 @@ ScrollView
|
|||
exclude: ["machine_settings"]
|
||||
expanded: Printer.expandedCategories
|
||||
onExpandedChanged: Printer.setExpandedCategories(expanded)
|
||||
|
||||
filter:
|
||||
{
|
||||
if(ExtruderManager.activeExtruderStackId)
|
||||
{
|
||||
return { "settable_per_extruder": true }
|
||||
}
|
||||
return { }
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Loader
|
||||
|
@ -53,7 +44,15 @@ ScrollView
|
|||
Behavior on height { NumberAnimation { duration: 100 } }
|
||||
opacity: provider.properties.enabled == "True" ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
enabled: provider.properties.enabled == "True"
|
||||
enabled:
|
||||
{
|
||||
if(!ExtruderManager.activeExtruderStackId && ExtruderManager.extruderCount > 0)
|
||||
{
|
||||
// disable all controls on the global tab, except categories
|
||||
return model.type == "category"
|
||||
}
|
||||
return provider.properties.enabled == "True"
|
||||
}
|
||||
|
||||
property var definition: model
|
||||
property var settingDefinitionsModel: definitionsModel
|
||||
|
@ -88,20 +87,59 @@ ScrollView
|
|||
}
|
||||
}
|
||||
|
||||
// Binding to ensure that the right containerstack ID is set for the provider.
|
||||
// This ensures that if a setting has a global_inherits_stack id (for instance; Support speed points to the
|
||||
// extruder that actually prints the support, as that is the setting we need to use to calculate the value)
|
||||
Binding
|
||||
{
|
||||
target: provider
|
||||
property: "containerStackId"
|
||||
value:
|
||||
{
|
||||
if(inheritStackProvider.properties.global_inherits_stack == -1 || inheritStackProvider.properties.global_inherits_stack == null)
|
||||
{
|
||||
if( ExtruderManager.activeExtruderStackId)
|
||||
{
|
||||
return ExtruderManager.activeExtruderStackId
|
||||
}
|
||||
else
|
||||
{
|
||||
return Cura.MachineManager.activeMachineId
|
||||
}
|
||||
}
|
||||
return ExtruderManager.extruderIds[String(inheritStackProvider.properties.global_inherits_stack)]
|
||||
}
|
||||
}
|
||||
|
||||
// Specialty provider that only watches global_inherits (we cant filter on what property changed we get events
|
||||
// so we bypass that to make a dedicated provider.
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: inheritStackProvider
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: model.key
|
||||
watchedProperties: [ "global_inherits_stack"]
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: provider
|
||||
|
||||
containerStackId: ExtruderManager.activeExtruderStackId ? ExtruderManager.activeExtruderStackId : Cura.MachineManager.activeMachineId
|
||||
containerStackId: delegate.stackId
|
||||
key: model.key ? model.key : ""
|
||||
watchedProperties: [ "value", "enabled", "state", "validationState" ]
|
||||
watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder" ]
|
||||
storeIndex: 0
|
||||
}
|
||||
|
||||
Connections
|
||||
{
|
||||
target: item
|
||||
onContextMenuRequested: { contextMenu.key = model.key; contextMenu.popup() }
|
||||
onContextMenuRequested:
|
||||
{
|
||||
contextMenu.key = model.key;
|
||||
contextMenu.provider = provider
|
||||
contextMenu.popup();
|
||||
}
|
||||
onShowTooltip: base.showTooltip(delegate, { x: 0, y: delegate.height / 2 }, text)
|
||||
onHideTooltip: base.hideTooltip()
|
||||
}
|
||||
|
@ -133,9 +171,24 @@ ScrollView
|
|||
|
||||
Menu
|
||||
{
|
||||
id: contextMenu;
|
||||
id: contextMenu
|
||||
|
||||
property string key;
|
||||
property string key
|
||||
property var provider
|
||||
|
||||
MenuItem
|
||||
{
|
||||
//: Settings context menu action
|
||||
text: catalog.i18nc("@action:menu", "Copy value to all extruders")
|
||||
visible: machineExtruderCount.properties.value > 1
|
||||
enabled: contextMenu.provider != undefined && contextMenu.provider.properties.settable_per_extruder != "False"
|
||||
onTriggered: Cura.MachineManager.copyValueToExtruders(contextMenu.key)
|
||||
}
|
||||
|
||||
MenuSeparator
|
||||
{
|
||||
visible: machineExtruderCount.properties.value > 1
|
||||
}
|
||||
|
||||
MenuItem
|
||||
{
|
||||
|
@ -151,5 +204,15 @@ ScrollView
|
|||
onTriggered: Cura.Actions.configureSettingVisibility.trigger(contextMenu);
|
||||
}
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineExtruderCount
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_extruder_count"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ Rectangle
|
|||
iconSource: {
|
||||
if(!printerConnected)
|
||||
return UM.Theme.getIcon("tab_monitor")
|
||||
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print")
|
||||
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print" || Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup" )
|
||||
return UM.Theme.getIcon("tab_monitor_busy")
|
||||
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "ready" || Cura.MachineManager.printerOutputDevices[0].jobState == "")
|
||||
return UM.Theme.getIcon("tab_monitor_connected")
|
||||
|
|
|
@ -8,6 +8,4 @@ import QtQuick.Controls 1.2
|
|||
import "Settings"
|
||||
|
||||
SettingView {
|
||||
// expandedCategories: Printer.expandedCategories;
|
||||
// onExpandedCategoriesChanged: Printer.setExpandedCategories(expandedCategories);
|
||||
}
|
||||
|
|
|
@ -84,15 +84,15 @@ Column
|
|||
|
||||
orientation: ListView.Horizontal
|
||||
|
||||
model: Cura.ExtrudersModel { id: extrudersModel; addGlobal: true }
|
||||
model: Cura.ExtrudersModel { id: extrudersModel; addGlobal: false }
|
||||
|
||||
Connections
|
||||
{
|
||||
target: Cura.MachineManager
|
||||
onGlobalContainerChanged:
|
||||
{
|
||||
base.currentExtruderIndex = -1;
|
||||
forceActiveFocus()
|
||||
forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values.
|
||||
base.currentExtruderIndex = (machineExtruderCount.properties.value == 1) ? -1 : 0;
|
||||
ExtruderManager.setActiveExtruderIndex(base.currentExtruderIndex);
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ Column
|
|||
|
||||
onClicked:
|
||||
{
|
||||
forceActiveFocus() //Changing focus applies the currently-being-typed values so it can change the displayed setting values.
|
||||
forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values.
|
||||
base.currentExtruderIndex = index;
|
||||
ExtruderManager.setActiveExtruderIndex(index);
|
||||
}
|
||||
|
@ -258,6 +258,8 @@ Column
|
|||
{
|
||||
id: globalProfileSelection
|
||||
text: Cura.MachineManager.activeQualityName
|
||||
enabled: !extrudersList.visible || base.currentExtruderIndex > -1
|
||||
|
||||
width: parent.width * 0.55 + UM.Theme.getSize("default_margin").width
|
||||
height: UM.Theme.getSize("setting_control").height
|
||||
tooltip: Cura.MachineManager.activeQualityName
|
||||
|
|
|
@ -19,6 +19,7 @@ Item
|
|||
property Action configureSettings;
|
||||
property variant minimumPrintTime: PrintInformation.minimumPrintTime;
|
||||
property variant maximumPrintTime: PrintInformation.maximumPrintTime;
|
||||
property bool settingsEnabled: ExtruderManager.activeExtruderStackId || ExtruderManager.extruderCount == 0
|
||||
|
||||
Component.onCompleted: PrintInformation.enabled = true
|
||||
Component.onDestruction: PrintInformation.enabled = false
|
||||
|
@ -81,7 +82,11 @@ Item
|
|||
height: width
|
||||
|
||||
border.color: {
|
||||
if(infillListView.activeIndex == index)
|
||||
if(!base.settingsEnabled)
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_disabled_border")
|
||||
}
|
||||
else if(infillListView.activeIndex == index)
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_selected")
|
||||
}
|
||||
|
@ -92,7 +97,17 @@ Item
|
|||
return UM.Theme.getColor("setting_control_border")
|
||||
}
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
color: infillListView.activeIndex == index ? UM.Theme.getColor("setting_control_selected") : "transparent"
|
||||
color: {
|
||||
if(infillListView.activeIndex == index)
|
||||
{
|
||||
if(!base.settingsEnabled)
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_disabled_text")
|
||||
}
|
||||
return UM.Theme.getColor("setting_control_selected")
|
||||
}
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
UM.RecolorImage {
|
||||
id: infillIcon
|
||||
|
@ -102,13 +117,24 @@ Item
|
|||
sourceSize.width: width
|
||||
sourceSize.height: width
|
||||
source: UM.Theme.getIcon(model.icon);
|
||||
color: (infillListView.activeIndex == index) ? UM.Theme.getColor("text_white") : UM.Theme.getColor("text")
|
||||
color: {
|
||||
if(infillListView.activeIndex == index)
|
||||
{
|
||||
return UM.Theme.getColor("text_reversed")
|
||||
}
|
||||
if(!base.settingsEnabled)
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_disabled_text")
|
||||
}
|
||||
return UM.Theme.getColor("text")
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: infillMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: base.settingsEnabled
|
||||
onClicked: {
|
||||
if (infillListView.activeIndex != index)
|
||||
{
|
||||
|
@ -125,6 +151,7 @@ Item
|
|||
}
|
||||
Label{
|
||||
id: infillLabel
|
||||
font: UM.Theme.getFont("default")
|
||||
anchors.top: infillIconLining.bottom
|
||||
anchors.horizontalCenter: infillIconLining.horizontalCenter
|
||||
color: infillListView.activeIndex == index ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_border")
|
||||
|
@ -205,6 +232,7 @@ Item
|
|||
//: Setting enable skirt adhesion checkbox
|
||||
text: catalog.i18nc("@option:check", "Print Brim");
|
||||
style: UM.Theme.styles.checkbox;
|
||||
enabled: base.settingsEnabled
|
||||
|
||||
checked: platformAdhesionType.properties.value == "brim"
|
||||
|
||||
|
@ -212,6 +240,7 @@ Item
|
|||
id: brimMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: base.settingsEnabled
|
||||
onClicked:
|
||||
{
|
||||
platformAdhesionType.setPropertyValue("value", !parent.checked ? "brim" : "skirt")
|
||||
|
@ -253,12 +282,14 @@ Item
|
|||
//: Setting enable support checkbox
|
||||
text: catalog.i18nc("@option:check", "Print Support Structure");
|
||||
style: UM.Theme.styles.checkbox;
|
||||
enabled: base.settingsEnabled
|
||||
|
||||
checked: supportEnabled.properties.value == "True"
|
||||
MouseArea {
|
||||
id: supportMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: base.settingsEnabled
|
||||
onClicked:
|
||||
{
|
||||
supportEnabled.setPropertyValue("value", !parent.checked)
|
||||
|
@ -287,6 +318,7 @@ Item
|
|||
width: parent.width / 100 * 45
|
||||
|
||||
style: UM.Theme.styles.combobox
|
||||
enabled: base.settingsEnabled
|
||||
property alias _hovered: supportExtruderMouseArea.containsMouse
|
||||
|
||||
currentIndex: supportEnabled.properties.value == "True" ? parseFloat(supportExtruderNr.properties.value) + 1 : 0
|
||||
|
@ -302,6 +334,7 @@ Item
|
|||
id: supportExtruderMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: base.settingsEnabled
|
||||
acceptedButtons: Qt.NoButton
|
||||
onEntered:
|
||||
{
|
||||
|
@ -381,7 +414,7 @@ Item
|
|||
{
|
||||
id: platformAdhesionType
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: Cura.MachineManager.activeStackId
|
||||
key: "adhesion_type"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
|
@ -391,7 +424,7 @@ Item
|
|||
{
|
||||
id: supportEnabled
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: Cura.MachineManager.activeStackId
|
||||
key: "support_enable"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
|
@ -411,7 +444,7 @@ Item
|
|||
{
|
||||
id: supportExtruderNr
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: Cura.MachineManager.activeStackId
|
||||
key: "support_extruder_nr"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
|
|
84
resources/shaders/striped.shader
Normal file
84
resources/shaders/striped.shader
Normal file
|
@ -0,0 +1,84 @@
|
|||
[shaders]
|
||||
vertex =
|
||||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewProjectionMatrix;
|
||||
uniform highp mat4 u_normalMatrix;
|
||||
|
||||
attribute highp vec4 a_vertex;
|
||||
attribute highp vec4 a_normal;
|
||||
attribute highp vec2 a_uvs;
|
||||
|
||||
varying highp vec3 v_position;
|
||||
varying highp vec3 v_vertex;
|
||||
varying highp vec3 v_normal;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 world_space_vert = u_modelMatrix * a_vertex;
|
||||
gl_Position = u_viewProjectionMatrix * world_space_vert;
|
||||
|
||||
v_position = gl_Position.xyz;
|
||||
v_vertex = world_space_vert.xyz;
|
||||
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
|
||||
}
|
||||
|
||||
fragment =
|
||||
uniform mediump vec4 u_ambientColor;
|
||||
uniform mediump vec4 u_diffuseColor1;
|
||||
uniform mediump vec4 u_diffuseColor2;
|
||||
uniform mediump vec4 u_specularColor;
|
||||
uniform highp vec3 u_lightPosition;
|
||||
uniform mediump float u_shininess;
|
||||
uniform highp vec3 u_viewPosition;
|
||||
|
||||
uniform mediump float u_width;
|
||||
|
||||
varying highp vec3 v_position;
|
||||
varying highp vec3 v_vertex;
|
||||
varying highp vec3 v_normal;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 finalColor = vec4(0.0);
|
||||
mediump vec4 diffuseColor = (mod((-v_position.x + v_position.y), u_width) < (u_width / 2.)) ? u_diffuseColor1 : u_diffuseColor2;
|
||||
|
||||
/* Ambient Component */
|
||||
finalColor += u_ambientColor;
|
||||
|
||||
highp vec3 normal = normalize(v_normal);
|
||||
highp vec3 lightDir = normalize(u_lightPosition - v_vertex);
|
||||
|
||||
/* Diffuse Component */
|
||||
highp float NdotL = clamp(abs(dot(normal, lightDir)), 0.0, 1.0);
|
||||
finalColor += (NdotL * diffuseColor);
|
||||
|
||||
/* Specular Component */
|
||||
/* TODO: We should not do specularity for fragments facing away from the light.*/
|
||||
highp vec3 reflectedLight = reflect(-lightDir, normal);
|
||||
highp vec3 viewVector = normalize(u_viewPosition - v_vertex);
|
||||
highp float NdotR = clamp(dot(viewVector, reflectedLight), 0.0, 1.0);
|
||||
finalColor += pow(NdotR, u_shininess) * u_specularColor;
|
||||
|
||||
gl_FragColor = finalColor;
|
||||
gl_FragColor.a = 1.0;
|
||||
}
|
||||
|
||||
[defaults]
|
||||
u_ambientColor = [0.3, 0.3, 0.3, 1.0]
|
||||
u_diffuseColor1 = [1.0, 0.5, 0.5, 1.0]
|
||||
u_diffuseColor2 = [0.5, 0.5, 0.5, 1.0]
|
||||
u_specularColor = [0.4, 0.4, 0.4, 1.0]
|
||||
u_shininess = 20.0
|
||||
u_width = 5.0
|
||||
|
||||
[bindings]
|
||||
u_modelMatrix = model_matrix
|
||||
u_viewProjectionMatrix = view_projection_matrix
|
||||
u_normalMatrix = normal_matrix
|
||||
u_viewPosition = view_position
|
||||
u_lightPosition = light_0_position
|
||||
u_diffuseColor = diffuse_color
|
||||
|
||||
[attributes]
|
||||
a_vertex = vertex
|
||||
a_normal = normal
|
64
resources/shaders/transparent_object.shader
Normal file
64
resources/shaders/transparent_object.shader
Normal file
|
@ -0,0 +1,64 @@
|
|||
[shaders]
|
||||
vertex =
|
||||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewProjectionMatrix;
|
||||
uniform highp mat4 u_normalMatrix;
|
||||
|
||||
attribute highp vec4 a_vertex;
|
||||
attribute highp vec4 a_normal;
|
||||
attribute highp vec2 a_uvs;
|
||||
|
||||
varying highp vec3 v_vertex;
|
||||
varying highp vec3 v_normal;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 world_space_vert = u_modelMatrix * a_vertex;
|
||||
gl_Position = u_viewProjectionMatrix * world_space_vert;
|
||||
|
||||
v_vertex = world_space_vert.xyz;
|
||||
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
|
||||
}
|
||||
|
||||
fragment =
|
||||
uniform mediump vec4 u_ambientColor;
|
||||
uniform mediump vec4 u_diffuseColor;
|
||||
uniform highp vec3 u_lightPosition;
|
||||
|
||||
uniform mediump float u_opacity;
|
||||
|
||||
varying highp vec3 v_vertex;
|
||||
varying highp vec3 v_normal;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 finalColor = vec4(0.0);
|
||||
|
||||
/* Ambient Component */
|
||||
finalColor += u_ambientColor;
|
||||
|
||||
highp vec3 normal = normalize(v_normal);
|
||||
highp vec3 lightDir = normalize(u_lightPosition - v_vertex);
|
||||
|
||||
/* Diffuse Component */
|
||||
highp float NdotL = clamp(abs(dot(normal, lightDir)), 0.0, 1.0);
|
||||
finalColor += (NdotL * u_diffuseColor);
|
||||
|
||||
gl_FragColor = finalColor;
|
||||
gl_FragColor.a = u_opacity;
|
||||
}
|
||||
|
||||
[defaults]
|
||||
u_ambientColor = [0.1, 0.1, 0.1, 1.0]
|
||||
u_diffuseColor = [0.4, 0.4, 0.4, 1.0]
|
||||
u_opacity = 0.5
|
||||
|
||||
[bindings]
|
||||
u_modelMatrix = model_matrix
|
||||
u_viewProjectionMatrix = view_projection_matrix
|
||||
u_normalMatrix = normal_matrix
|
||||
u_lightPosition = light_0_position
|
||||
|
||||
[attributes]
|
||||
a_vertex = vertex
|
||||
a_normal = normal
|
43
resources/themes/cura/icons/link.svg
Normal file
43
resources/themes/cura/icons/link.svg
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.2"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 250 250"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="link.svg"><metadata
|
||||
id="metadata4192"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs4190" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1148"
|
||||
id="namedview4188"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.58272057"
|
||||
inkscape:cx="218.22031"
|
||||
inkscape:cy="76.27121"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><path
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 29.41175,183.82353 q 0,-6.12745 4.28922,-10.41667 l 31.86274,-31.86274 q 4.28922,-4.28921 10.41667,-4.28921 6.43382,0 11.02941,4.90196 -0.45956,0.45956 -2.91054,2.83394 -2.45098,2.37439 -3.2935,3.29351 -0.84253,0.91912 -2.2978,2.91054 -1.45527,1.99142 -1.99142,3.90625 -0.53615,1.91483 -0.53615,4.21262 0,6.12745 4.28921,10.41666 4.28922,4.28922 10.41667,4.28922 2.29779,0 4.21261,-0.53615 1.91483,-0.53616 3.90625,-1.99143 1.99143,-1.45527 2.91054,-2.29779 0.91912,-0.84252 3.29351,-3.2935 2.37439,-2.45098 2.83395,-2.91054 5.05514,4.74877 5.05514,11.18259 0,6.12746 -4.28921,10.41667 l -31.55637,31.70956 q -4.13603,4.13603 -10.41666,4.13603 -6.12746,0 -10.41667,-3.98284 l -22.51838,-22.3652 q -4.28922,-4.28922 -4.28922,-10.26348 z M 137.1017,75.827207 q 0,-6.12745 4.28922,-10.41666 l 31.55637,-31.70956 q 4.28922,-4.28922 10.41667,-4.28922 5.97426,0 10.41667,4.13603 l 22.51838,22.3652 q 4.28921,4.28921 4.28921,10.26348 0,6.12745 -4.28921,10.41667 l -31.86275,31.862743 q -4.13603,4.13603 -10.41666,4.13603 -6.43383,0 -11.02942,-4.74877 0.45956,-0.45956 2.91054,-2.83395 2.45098,-2.37439 3.29351,-3.2935 0.84252,-0.91912 2.29779,-2.910543 1.45527,-1.99143 1.99142,-3.90625 0.53616,-1.91483 0.53616,-4.21263 0,-6.12745 -4.28922,-10.41666 -4.28922,-4.28922 -10.41667,-4.28922 -2.29779,0 -4.21262,0.53615 -1.91483,0.53616 -3.90626,1.99142 -1.99141,1.45527 -2.91053,2.2978 -0.91912,0.84252 -3.29351,3.2935 -2.37438,2.45098 -2.83394,2.91054 -5.05515,-4.74877 -5.05515,-11.1826 z M 0,183.82353 q 0,18.38235 13.02082,31.09681 l 22.51838,22.3652 Q 48.25366,250 66.63602,250 q 18.53554,0 31.24999,-13.02083 l 31.55638,-31.70956 q 12.71446,-12.71446 12.71446,-31.09682 0,-18.8419 -13.48039,-32.01592 l 13.48039,-13.48039 q 13.17402,13.48039 31.86275,13.48039 18.38235,0 31.25,-12.86764 l 31.86274,-31.862753 q 12.86765,-12.86765 12.86765,-31.25 0,-18.38235 -13.02083,-31.09681 L 214.46077,12.714457 Q 201.74631,-2.8e-6 183.36396,-2.8e-6 q -18.53554,0 -31.25,13.0208298 l -31.55637,31.70957 q -12.71447,12.71446 -12.71447,31.09681 0,18.84192 13.4804,32.015943 l -13.4804,13.48039 Q 94.6691,107.84315 75.98038,107.84315 q -18.38235,0 -31.25,12.86764 L 12.86763,152.57354 Q 0,165.44118 0,183.82353 Z"
|
||||
id="path4173" /></svg>
|
After Width: | Height: | Size: 3.6 KiB |
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 762 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -56,7 +56,7 @@
|
|||
"text_inactive": [174, 174, 174, 255],
|
||||
"text_hover": [70, 84, 113, 255],
|
||||
"text_pressed": [12, 169, 227, 255],
|
||||
"text_white": [255, 255, 255, 255],
|
||||
"text_reversed": [255, 255, 255, 255],
|
||||
"text_subtext": [70, 84, 113, 255],
|
||||
|
||||
"error": [255, 140, 0, 255],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue