mirror of
https://github.com/Ultimaker/Cura.git
synced 2026-03-04 09:34:35 -07:00
Merge branch 'main' into Add-layer-height-to-fine-global-quality-for-fdm
This commit is contained in:
commit
f1afb69cfb
46 changed files with 1055 additions and 157 deletions
49
.github/workflows/find-packages.yml
vendored
Normal file
49
.github/workflows/find-packages.yml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
name: Find packages for Jira ticket and create installers
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
jira_ticket_number:
|
||||
description: 'Jira ticket number for Conan package discovery (e.g., cura_12345)'
|
||||
required: true
|
||||
type: string
|
||||
start_builds:
|
||||
default: false
|
||||
required: false
|
||||
type: boolean
|
||||
conan_args:
|
||||
description: 'Conan args'
|
||||
default: ''
|
||||
type: string
|
||||
enterprise:
|
||||
description: 'Build Cura as an Enterprise edition'
|
||||
default: false
|
||||
type: boolean
|
||||
staging:
|
||||
description: 'Use staging API'
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
find-packages:
|
||||
name: Find packages for Jira ticket
|
||||
uses: ultimaker/cura-workflows/.github/workflows/find-package-by-ticket.yml@main
|
||||
with:
|
||||
jira_ticket_number: ${{ inputs.jira_ticket_number }}
|
||||
secrets: inherit
|
||||
|
||||
installers:
|
||||
name: Create installers
|
||||
needs: find-packages
|
||||
if: ${{ inputs.start_builds == true && needs.find-packages.outputs.discovered_packages != '' }}
|
||||
uses: ultimaker/cura-workflows/.github/workflows/cura-installers.yml@main
|
||||
with:
|
||||
cura_conan_version: ${{ needs.find-packages.outputs.cura_package }}
|
||||
package_overrides: ${{ needs.find-packages.outputs.package_overrides }}
|
||||
conan_args: ${{ inputs.conan_args }}
|
||||
enterprise: ${{ inputs.enterprise }}
|
||||
staging: ${{ inputs.staging }}
|
||||
secrets: inherit
|
||||
|
|
@ -80,9 +80,13 @@ class LayerDataBuilder(MeshBuilder):
|
|||
material_colors = numpy.zeros((line_dimensions.shape[0], 4), dtype=numpy.float32)
|
||||
for extruder_nr in range(material_color_map.shape[0]):
|
||||
material_colors[extruders == extruder_nr] = material_color_map[extruder_nr]
|
||||
# Set material_colors with indices where line_types (also numpy array) == MoveCombingType
|
||||
material_colors[line_types == LayerPolygon.MoveCombingType] = colors[line_types == LayerPolygon.MoveCombingType]
|
||||
material_colors[line_types == LayerPolygon.MoveRetractionType] = colors[line_types == LayerPolygon.MoveRetractionType]
|
||||
# Set material_colors with indices where line_types (also numpy array) == MoveUnretractedType
|
||||
material_colors[line_types == LayerPolygon.MoveUnretractedType] = colors[line_types == LayerPolygon.MoveUnretractedType]
|
||||
material_colors[line_types == LayerPolygon.MoveRetractedType] = colors[line_types == LayerPolygon.MoveRetractedType]
|
||||
material_colors[line_types == LayerPolygon.MoveWhileRetractingType] = colors[
|
||||
line_types == LayerPolygon.MoveWhileRetractingType]
|
||||
material_colors[line_types == LayerPolygon.MoveWhileUnretractingType] = colors[
|
||||
line_types == LayerPolygon.MoveWhileUnretractingType]
|
||||
|
||||
attributes = {
|
||||
"line_dimensions": {
|
||||
|
|
|
|||
|
|
@ -19,15 +19,22 @@ class LayerPolygon:
|
|||
SkirtType = 5
|
||||
InfillType = 6
|
||||
SupportInfillType = 7
|
||||
MoveCombingType = 8
|
||||
MoveRetractionType = 9
|
||||
MoveUnretractedType = 8
|
||||
MoveRetractedType = 9
|
||||
SupportInterfaceType = 10
|
||||
PrimeTowerType = 11
|
||||
__number_of_types = 12
|
||||
MoveWhileRetractingType = 12
|
||||
MoveWhileUnretractingType = 13
|
||||
StationaryRetractUnretract = 14
|
||||
__number_of_types = 15
|
||||
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType,
|
||||
numpy.arange(__number_of_types) == MoveCombingType),
|
||||
numpy.arange(__number_of_types) == MoveRetractionType)
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.logical_or(
|
||||
numpy.arange(__number_of_types) == NoneType,
|
||||
numpy.arange(__number_of_types) == MoveUnretractedType),
|
||||
numpy.logical_or(
|
||||
numpy.arange(__number_of_types) == MoveRetractedType,
|
||||
numpy.arange(__number_of_types) == MoveWhileRetractingType)),
|
||||
numpy.arange(__number_of_types) == MoveWhileUnretractingType)
|
||||
|
||||
def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray,
|
||||
line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
|
||||
|
|
@ -269,10 +276,13 @@ class LayerPolygon:
|
|||
theme.getColor("layerview_skirt").getRgbF(), # SkirtType
|
||||
theme.getColor("layerview_infill").getRgbF(), # InfillType
|
||||
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveUnretractedType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractedType
|
||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||
theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
|
||||
theme.getColor("layerview_prime_tower").getRgbF(), # PrimeTowerType
|
||||
theme.getColor("layerview_move_while_retracting").getRgbF(), # MoveWhileRetracting
|
||||
theme.getColor("layerview_move_while_unretracting").getRgbF(), # MoveWhileUnretracting
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # StationaryRetractUnretract
|
||||
])
|
||||
|
||||
return cls.__color_map
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ class AuthState(IntEnum):
|
|||
class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
authenticationStateChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None) -> None:
|
||||
super().__init__(device_id = device_id, connection_type = connection_type, parent = parent)
|
||||
def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None, active: bool = True) -> None:
|
||||
super().__init__(device_id = device_id, connection_type = connection_type, parent = parent, active = active)
|
||||
self._manager = None # type: Optional[QNetworkAccessManager]
|
||||
self._timeout_time = 10 # After how many seconds of no response should a timeout occur?
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,10 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
# Signal to indicate that the configuration of one of the printers has changed.
|
||||
uniqueConfigurationsChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None) -> None:
|
||||
# Signal to indicate that the printer has become active or inactive
|
||||
activeChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None, active: bool = True) -> None:
|
||||
super().__init__(device_id = device_id, parent = parent) # type: ignore # MyPy complains with the multiple inheritance
|
||||
|
||||
self._printers = [] # type: List[PrinterOutputModel]
|
||||
|
|
@ -88,6 +91,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
self._accepts_commands = False # type: bool
|
||||
|
||||
self._active: bool = active
|
||||
|
||||
self._update_timer = QTimer() # type: QTimer
|
||||
self._update_timer.setInterval(2000) # TODO; Add preference for update interval
|
||||
self._update_timer.setSingleShot(False)
|
||||
|
|
@ -295,3 +300,17 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
return
|
||||
|
||||
self._firmware_updater.updateFirmware(firmware_file)
|
||||
|
||||
@pyqtProperty(bool, notify = activeChanged)
|
||||
def active(self) -> bool:
|
||||
"""
|
||||
Indicates whether the printer is active, which is not the same as "being the active printer". In this case,
|
||||
active means that the printer can be used. An example of an inactive printer is one that cannot be used because
|
||||
the user doesn't have enough seats on Digital Factory.
|
||||
"""
|
||||
return self._active
|
||||
|
||||
def _setActive(self, active: bool) -> None:
|
||||
if active != self._active:
|
||||
self._active = active
|
||||
self.activeChanged.emit()
|
||||
|
|
|
|||
|
|
@ -183,10 +183,14 @@ class MachineManager(QObject):
|
|||
self.setActiveMachine(active_machine_id)
|
||||
|
||||
def _onOutputDevicesChanged(self) -> None:
|
||||
for printer_output_device in self._printer_output_devices:
|
||||
printer_output_device.activeChanged.disconnect(self.printerConnectedStatusChanged)
|
||||
|
||||
self._printer_output_devices = []
|
||||
for printer_output_device in self._application.getOutputDeviceManager().getOutputDevices():
|
||||
if isinstance(printer_output_device, PrinterOutputDevice):
|
||||
self._printer_output_devices.append(printer_output_device)
|
||||
printer_output_device.activeChanged.connect(self.printerConnectedStatusChanged)
|
||||
|
||||
self.outputDevicesChanged.emit()
|
||||
|
||||
|
|
@ -569,6 +573,13 @@ class MachineManager(QObject):
|
|||
def activeMachineIsUsingCloudConnection(self) -> bool:
|
||||
return self.activeMachineHasCloudConnection and not self.activeMachineHasNetworkConnection
|
||||
|
||||
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
||||
def activeMachineIsActive(self) -> bool:
|
||||
if not self._printer_output_devices:
|
||||
return True
|
||||
|
||||
return self._printer_output_devices[0].active
|
||||
|
||||
def activeMachineNetworkKey(self) -> str:
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.getMetaDataEntry("um_network_key", "")
|
||||
|
|
|
|||
|
|
@ -78,10 +78,14 @@ message Polygon {
|
|||
SkirtType = 5;
|
||||
InfillType = 6;
|
||||
SupportInfillType = 7;
|
||||
MoveCombingType = 8;
|
||||
MoveRetractionType = 9;
|
||||
MoveUnretracted = 8;
|
||||
MoveRetracted = 9;
|
||||
SupportInterfaceType = 10;
|
||||
PrimeTowerType = 11;
|
||||
MoveWhileRetracting = 12;
|
||||
MoveWhileUnretracting = 13;
|
||||
StationaryRetractUnretract = 14;
|
||||
NumPrintFeatureTypes = 15;
|
||||
}
|
||||
Type type = 1; // Type of move
|
||||
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ Cura.RoundedRectangle
|
|||
width: parent.width
|
||||
height: projectImage.height + 2 * UM.Theme.getSize("default_margin").width
|
||||
cornerSide: Cura.RoundedRectangle.Direction.All
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
border.color: enabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("action_button_disabled_border")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
radius: UM.Theme.getSize("default_radius").width
|
||||
color: UM.Theme.getColor("main_background")
|
||||
color: getBackgroundColor()
|
||||
signal clicked()
|
||||
property alias imageSource: projectImage.source
|
||||
property alias projectNameText: displayNameLabel.text
|
||||
|
|
@ -22,17 +22,18 @@ Cura.RoundedRectangle
|
|||
property alias projectLastUpdatedText: lastUpdatedLabel.text
|
||||
property alias cardMouseAreaEnabled: cardMouseArea.enabled
|
||||
|
||||
onVisibleChanged: color = UM.Theme.getColor("main_background")
|
||||
onVisibleChanged: color = getBackgroundColor()
|
||||
|
||||
MouseArea
|
||||
{
|
||||
id: cardMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: base.color = UM.Theme.getColor("action_button_hovered")
|
||||
onExited: base.color = UM.Theme.getColor("main_background")
|
||||
hoverEnabled: base.enabled
|
||||
onEntered: color = getBackgroundColor()
|
||||
onExited: color = getBackgroundColor()
|
||||
onClicked: base.clicked()
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
id: projectInformationRow
|
||||
|
|
@ -73,7 +74,7 @@ Cura.RoundedRectangle
|
|||
width: parent.width
|
||||
height: Math.round(parent.height / 3)
|
||||
elide: Text.ElideRight
|
||||
color: UM.Theme.getColor("small_button_text")
|
||||
color: base.enabled ? UM.Theme.getColor("small_button_text") : UM.Theme.getColor("text_disabled")
|
||||
}
|
||||
|
||||
UM.Label
|
||||
|
|
@ -82,8 +83,27 @@ Cura.RoundedRectangle
|
|||
width: parent.width
|
||||
height: Math.round(parent.height / 3)
|
||||
elide: Text.ElideRight
|
||||
color: UM.Theme.getColor("small_button_text")
|
||||
color: base.enabled ? UM.Theme.getColor("small_button_text") : UM.Theme.getColor("text_disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBackgroundColor()
|
||||
{
|
||||
if(enabled)
|
||||
{
|
||||
if(cardMouseArea.containsMouse)
|
||||
{
|
||||
return UM.Theme.getColor("action_button_hovered")
|
||||
}
|
||||
else
|
||||
{
|
||||
return UM.Theme.getColor("main_background")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return UM.Theme.getColor("action_button_disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,17 +159,30 @@ Item
|
|||
Repeater
|
||||
{
|
||||
model: manager.digitalFactoryProjectModel
|
||||
delegate: ProjectSummaryCard
|
||||
delegate: Item
|
||||
{
|
||||
id: projectSummaryCard
|
||||
imageSource: model.thumbnailUrl || "../images/placeholder.svg"
|
||||
projectNameText: model.displayName
|
||||
projectUsernameText: model.username
|
||||
projectLastUpdatedText: "Last updated: " + model.lastUpdated
|
||||
width: parent.width
|
||||
height: projectSummaryCard.height
|
||||
|
||||
onClicked:
|
||||
UM.TooltipArea
|
||||
{
|
||||
manager.selectedProjectIndex = index
|
||||
anchors.fill: parent
|
||||
text: "This project is inactive and cannot be used."
|
||||
enabled: !model.active
|
||||
}
|
||||
|
||||
ProjectSummaryCard
|
||||
{
|
||||
id: projectSummaryCard
|
||||
imageSource: model.thumbnailUrl || "../images/placeholder.svg"
|
||||
projectNameText: model.displayName
|
||||
projectUsernameText: model.username
|
||||
projectLastUpdatedText: "Last updated: " + model.lastUpdated
|
||||
enabled: model.active
|
||||
|
||||
onClicked: {
|
||||
manager.selectedProjectIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class DigitalFactoryProjectModel(ListModel):
|
|||
ThumbnailUrlRole = Qt.ItemDataRole.UserRole + 5
|
||||
UsernameRole = Qt.ItemDataRole.UserRole + 6
|
||||
LastUpdatedRole = Qt.ItemDataRole.UserRole + 7
|
||||
ActiveRole = Qt.ItemDataRole.UserRole + 8
|
||||
|
||||
dfProjectModelChanged = pyqtSignal()
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ class DigitalFactoryProjectModel(ListModel):
|
|||
self.addRoleName(self.ThumbnailUrlRole, "thumbnailUrl")
|
||||
self.addRoleName(self.UsernameRole, "username")
|
||||
self.addRoleName(self.LastUpdatedRole, "lastUpdated")
|
||||
self.addRoleName(self.ActiveRole, "active")
|
||||
self._projects = [] # type: List[DigitalFactoryProjectResponse]
|
||||
|
||||
def setProjects(self, df_projects: List[DigitalFactoryProjectResponse]) -> None:
|
||||
|
|
@ -59,5 +61,6 @@ class DigitalFactoryProjectModel(ListModel):
|
|||
"thumbnailUrl": project.thumbnail_url,
|
||||
"username": project.username,
|
||||
"lastUpdated": project.last_updated.strftime(PROJECT_UPDATED_AT_DATETIME_FORMAT) if project.last_updated else "",
|
||||
"active": project.active,
|
||||
})
|
||||
self.dfProjectModelChanged.emit()
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class DigitalFactoryProjectResponse(BaseModel):
|
|||
team_ids: Optional[List[str]] = None,
|
||||
status: Optional[str] = None,
|
||||
technical_requirements: Optional[Dict[str, Any]] = None,
|
||||
is_inactive: bool = False,
|
||||
**kwargs) -> None:
|
||||
"""
|
||||
Creates a new digital factory project response object
|
||||
|
|
@ -56,6 +57,7 @@ class DigitalFactoryProjectResponse(BaseModel):
|
|||
self.last_updated = datetime.strptime(last_updated, DIGITAL_FACTORY_RESPONSE_DATETIME_FORMAT) if last_updated else None
|
||||
self.status = status
|
||||
self.technical_requirements = technical_requirements
|
||||
self.active = not is_inactive
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@ class FlavorParser:
|
|||
if i > 0:
|
||||
line_feedrates[i - 1] = point[3]
|
||||
line_types[i - 1] = point[5]
|
||||
if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
|
||||
if point[5] in [LayerPolygon.MoveUnretractedType,
|
||||
LayerPolygon.MoveRetractedType,
|
||||
LayerPolygon.MoveWhileRetractingType,
|
||||
LayerPolygon.MoveWhileUnretractingType]:
|
||||
line_widths[i - 1] = 0.1
|
||||
line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines
|
||||
else:
|
||||
|
|
@ -196,7 +199,7 @@ class FlavorParser:
|
|||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
|
||||
self._previous_extrusion_value = new_extrusion_value
|
||||
else:
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractedType]) # retraction
|
||||
e[self._extruder_number] = new_extrusion_value
|
||||
|
||||
# Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions
|
||||
|
|
@ -205,9 +208,9 @@ class FlavorParser:
|
|||
self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
|
||||
self._previous_z = z
|
||||
elif self._previous_extrusion_value > e[self._extruder_number]:
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType])
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractedType])
|
||||
else:
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveUnretractedType])
|
||||
return self._position(x, y, z, f, e)
|
||||
|
||||
|
||||
|
|
@ -419,7 +422,7 @@ class FlavorParser:
|
|||
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
|
||||
current_path.clear()
|
||||
# Start the new layer at the end position of the last layer
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType])
|
||||
|
||||
# When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior
|
||||
# as in ProcessSlicedLayersJob
|
||||
|
|
@ -461,9 +464,9 @@ class FlavorParser:
|
|||
|
||||
# When changing tool, store the end point of the previous path, then process the code and finally
|
||||
# add another point with the new position of the head.
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType])
|
||||
current_position = self.processTCode(global_stack, T, line, current_position, current_path)
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType])
|
||||
|
||||
if line.startswith("M"):
|
||||
M = self._getInt(line, "M")
|
||||
|
|
|
|||
|
|
@ -203,9 +203,9 @@ class SimulationPass(RenderPass):
|
|||
self._layer_shader.setUniformValue("u_next_vertex", not_a_vector)
|
||||
self._layer_shader.setUniformValue("u_last_line_ratio", 1.0)
|
||||
|
||||
# The first line does not have a previous line: add a MoveCombingType in front for start detection
|
||||
# The first line does not have a previous line: add a MoveUnretractedType in front for start detection
|
||||
# this way the first start of the layer can also be drawn
|
||||
prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveCombingType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]])
|
||||
prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveUnretractedType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]])
|
||||
# Remove the last element
|
||||
prev_line_types = prev_line_types[0:layer_data._attributes["line_types"]["value"].size]
|
||||
layer_data._attributes["prev_line_types"] = {'opengl_type': 'float', 'value': prev_line_types, 'opengl_name': 'a_prev_line_type'}
|
||||
|
|
|
|||
|
|
@ -608,8 +608,10 @@ class SimulationView(CuraView):
|
|||
visible_line_types.append(LayerPolygon.SupportInterfaceType)
|
||||
visible_line_types_with_extrusion = visible_line_types.copy() # Copy before travel moves are added
|
||||
if self.getShowTravelMoves():
|
||||
visible_line_types.append(LayerPolygon.MoveCombingType)
|
||||
visible_line_types.append(LayerPolygon.MoveRetractionType)
|
||||
visible_line_types.append(LayerPolygon.MoveUnretractedType)
|
||||
visible_line_types.append(LayerPolygon.MoveRetractedType)
|
||||
visible_line_types.append(LayerPolygon.MoveWhileRetractingType)
|
||||
visible_line_types.append(LayerPolygon.MoveWhileUnretractingType)
|
||||
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
|
|
|
|||
|
|
@ -227,29 +227,52 @@ Cura.ExpandableComponent
|
|||
id: typesLegendModel
|
||||
Component.onCompleted:
|
||||
{
|
||||
const travelsTypesModel = [
|
||||
{
|
||||
label: catalog.i18nc("@label", "Not retracted"),
|
||||
colorId: "layerview_move_combing"
|
||||
},
|
||||
{
|
||||
label: catalog.i18nc("@label", "Retracted"),
|
||||
colorId: "layerview_move_retraction"
|
||||
},
|
||||
{
|
||||
label: catalog.i18nc("@label", "Retracting"),
|
||||
colorId: "layerview_move_while_retracting"
|
||||
},
|
||||
{
|
||||
label: catalog.i18nc("@label", "Priming"),
|
||||
colorId: "layerview_move_while_unretracting"
|
||||
}
|
||||
];
|
||||
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Travels"),
|
||||
initialValue: viewSettings.show_travel_moves,
|
||||
preference: "layerview/show_travel_moves",
|
||||
colorId: "layerview_move_combing"
|
||||
colorId: "layerview_move_combing",
|
||||
subTypesModel: travelsTypesModel
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Helpers"),
|
||||
initialValue: viewSettings.show_helpers,
|
||||
preference: "layerview/show_helpers",
|
||||
colorId: "layerview_support"
|
||||
colorId: "layerview_support",
|
||||
subTypesModel: []
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Shell"),
|
||||
initialValue: viewSettings.show_skin,
|
||||
preference: "layerview/show_skin",
|
||||
colorId: "layerview_inset_0"
|
||||
colorId: "layerview_inset_0",
|
||||
subTypesModel: []
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Infill"),
|
||||
initialValue: viewSettings.show_infill,
|
||||
preference: "layerview/show_infill",
|
||||
colorId: "layerview_infill"
|
||||
colorId: "layerview_infill",
|
||||
subTypesModel: []
|
||||
});
|
||||
if (! UM.SimulationView.compatibilityMode)
|
||||
{
|
||||
|
|
@ -257,7 +280,8 @@ Cura.ExpandableComponent
|
|||
label: catalog.i18nc("@label", "Starts"),
|
||||
initialValue: viewSettings.show_starts,
|
||||
preference: "layerview/show_starts",
|
||||
colorId: "layerview_starts"
|
||||
colorId: "layerview_starts",
|
||||
subTypesModel: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -273,6 +297,7 @@ Cura.ExpandableComponent
|
|||
|
||||
Rectangle
|
||||
{
|
||||
id: rectangleColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: legendModelCheckBox.right
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
|
|
@ -281,6 +306,58 @@ Cura.ExpandableComponent
|
|||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
visible: viewSettings.show_legend
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
enabled: subTypesModel.count > 0
|
||||
|
||||
onEntered: tooltip.show()
|
||||
onExited: tooltip.hide()
|
||||
|
||||
UM.ToolTip
|
||||
{
|
||||
id: tooltip
|
||||
delay: 0
|
||||
width: subTypesColumn.implicitWidth + 2 * UM.Theme.getSize("thin_margin").width
|
||||
height: subTypesColumn.implicitHeight + 2 * UM.Theme.getSize("thin_margin").width
|
||||
|
||||
contentItem: Column
|
||||
{
|
||||
id: subTypesColumn
|
||||
padding: 0
|
||||
spacing: UM.Theme.getSize("layerview_row_spacing").height
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: subTypesModel
|
||||
UM.Label
|
||||
{
|
||||
text: label
|
||||
|
||||
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
width: UM.Theme.getSize("layerview_menu_size").width
|
||||
color: UM.Theme.getColor("tooltip_text")
|
||||
Rectangle
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
height: UM.Theme.getSize("layerview_legend_size").height
|
||||
|
||||
color: UM.Theme.getColor(model.colorId)
|
||||
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UM.Label
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ vertex =
|
|||
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||
// shade the color depending on the extruder index
|
||||
v_color = a_color;
|
||||
// 8 and 9 are travel moves
|
||||
if ((a_line_type != 8.0) && (a_line_type != 9.0)) {
|
||||
// 8, 9, 12 and 13 are travel moves
|
||||
if ((a_line_type != 8.0) && (a_line_type != 9.0) && (a_line_type != 12.0) && (a_line_type != 13.0)) {
|
||||
v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +48,9 @@ fragment =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
// travel moves: 8, 9, 12, 13
|
||||
if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) ||
|
||||
((v_line_type >= 11.5) && (v_line_type <= 13.5)))) {
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
|
@ -100,7 +102,7 @@ vertex41core =
|
|||
{
|
||||
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||
v_color = a_color;
|
||||
if ((a_line_type != 8) && (a_line_type != 9)) {
|
||||
if ((a_line_type != 8) && (a_line_type != 9) && (a_line_type != 12) && (a_line_type != 13)) {
|
||||
v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +122,9 @@ fragment41core =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
// travel moves: 8, 9, 12, 13
|
||||
if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) ||
|
||||
((v_line_type >= 11.5) && (v_line_type <= 13.5)))) {
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,22 +228,26 @@ geometry41core =
|
|||
{
|
||||
highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix;
|
||||
|
||||
vec4 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
||||
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
||||
// Vertices are declared as vec4 so that they can be used for calculations with gl_in[x].gl_Position
|
||||
vec3 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz;
|
||||
vec4 g_vertex_offset_horz;
|
||||
vec3 g_vertex_normal_vert;
|
||||
vec4 g_vertex_offset_vert;
|
||||
vec3 g_vertex_normal_horz_head;
|
||||
vec4 g_vertex_offset_horz_head;
|
||||
vec3 g_axial_plan_vector;
|
||||
vec3 g_radial_plan_vector;
|
||||
|
||||
float size_x;
|
||||
float size_y;
|
||||
|
||||
if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) {
|
||||
if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) &&
|
||||
(v_line_type[0] != 8) && (v_line_type[0] != 9) && (v_line_type[0] != 12) && (v_line_type[0] != 13)) {
|
||||
return;
|
||||
}
|
||||
// See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
|
||||
// See LayerPolygon; 8 is MoveUnretractedType, 9 is RetractionType, 12 is MoveWhileRetractingType, 13 is MoveWhileUnretractingType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13))) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10) || v_line_type[0] == 11)) {
|
||||
|
|
@ -256,7 +260,7 @@ geometry41core =
|
|||
return;
|
||||
}
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) {
|
||||
// fixed size for movements
|
||||
size_x = 0.05;
|
||||
} else {
|
||||
|
|
@ -264,26 +268,47 @@ geometry41core =
|
|||
}
|
||||
size_y = v_line_dim[1].y / 2 + 0.01;
|
||||
|
||||
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position; //Actual movement exhibited by the line.
|
||||
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z)); //Lengthwise normal vector pointing backwards.
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector pointing backwards.
|
||||
g_vertex_delta = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz; //Actual movement exhibited by the line.
|
||||
|
||||
g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x)); //Normal vector pointing right.
|
||||
if (g_vertex_delta == vec3(0.0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_vertex_delta.y == 0.0)
|
||||
{
|
||||
// vector is in the horizontal plan, radial vector is a simple rotation around Y axis
|
||||
g_radial_plan_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x);
|
||||
}
|
||||
else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0)
|
||||
{
|
||||
// delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views
|
||||
g_radial_plan_vector = vec3(1.0, 0.0, -1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// delta vector is completely 3D
|
||||
g_axial_plan_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plan
|
||||
g_radial_plan_vector = cross(g_vertex_delta, g_axial_plan_vector); // Radial vector in the horizontal plan, pointing right.
|
||||
}
|
||||
|
||||
g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector
|
||||
|
||||
g_vertex_normal_horz = normalize(g_radial_plan_vector); //Normal vector pointing right.
|
||||
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right.
|
||||
|
||||
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector.
|
||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); //Upwards offset vector. Goes up by half the layer thickness.
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { //Travel or retraction moves.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { //Travel or retraction moves.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
|
||||
// Travels: flat plane with pointy ends
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
|
|
@ -308,8 +333,8 @@ geometry41core =
|
|||
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); //Line end, right vertex.
|
||||
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); //Line start, bottom vertex.
|
||||
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); //Line end, bottom vertex.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head); //Line start, tip.
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head); //Line end, tip.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head); //Line start, tip.
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head); //Line end, tip.
|
||||
|
||||
// All normal lines are rendered as 3d tubes.
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
|
|
@ -328,14 +353,14 @@ geometry41core =
|
|||
// left side
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
|
@ -343,14 +368,14 @@ geometry41core =
|
|||
// right side
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
|
|
|||
|
|
@ -95,22 +95,26 @@ geometry41core =
|
|||
{
|
||||
highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix;
|
||||
|
||||
vec4 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
||||
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
||||
// Vertices are declared as vec4 so that they can be used for calculations with gl_in[x].gl_Position
|
||||
vec3 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz;
|
||||
vec4 g_vertex_offset_horz;
|
||||
vec3 g_vertex_normal_vert;
|
||||
vec4 g_vertex_offset_vert;
|
||||
vec3 g_vertex_normal_horz_head;
|
||||
vec4 g_vertex_offset_horz_head;
|
||||
vec3 g_axial_plane_vector;
|
||||
vec3 g_radial_plane_vector;
|
||||
|
||||
float size_x;
|
||||
float size_y;
|
||||
|
||||
if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) {
|
||||
if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) &&
|
||||
(v_line_type[0] != 8) && (v_line_type[0] != 9) && (v_line_type[0] != 12) && (v_line_type[0] != 13)) {
|
||||
return;
|
||||
}
|
||||
// See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
|
||||
// See LayerPolygon; 8 is MoveUnretractedType, 9 is RetractionType, 12 is MoveWhileRetractingType, 13 is MoveWhileUnretractingType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13))) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) {
|
||||
|
|
@ -123,7 +127,7 @@ geometry41core =
|
|||
return;
|
||||
}
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) {
|
||||
// fixed size for movements
|
||||
size_x = 0.05;
|
||||
} else {
|
||||
|
|
@ -131,93 +135,114 @@ geometry41core =
|
|||
}
|
||||
size_y = v_line_dim[1].y / 2 + 0.01;
|
||||
|
||||
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position;
|
||||
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z));
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0);
|
||||
g_vertex_delta = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz; //Actual movement exhibited by the line.
|
||||
|
||||
g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x));
|
||||
if (g_vertex_delta == vec3(0.0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz;
|
||||
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0);
|
||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
|
||||
if (g_vertex_delta.y == 0.0)
|
||||
{
|
||||
// vector is in the horizontal plane, radial vector is a simple rotation around Y axis
|
||||
g_radial_plane_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x);
|
||||
}
|
||||
else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0)
|
||||
{
|
||||
// delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views
|
||||
g_radial_plane_vector = vec3(1.0, 0.0, -1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// delta vector is completely 3D
|
||||
g_axial_plane_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plane
|
||||
g_radial_plane_vector = cross(g_vertex_delta, g_axial_plane_vector); // Radial vector in the horizontal plane, pointing right.
|
||||
}
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector
|
||||
|
||||
g_vertex_normal_horz = normalize(g_radial_plane_vector); //Normal vector pointing right.
|
||||
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right.
|
||||
|
||||
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector.
|
||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); //Upwards offset vector. Goes up by half the layer thickness.
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) {
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
|
||||
// Travels: flat plane with pointy ends
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_head);
|
||||
//And reverse so that the line is also visible from the back side.
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
|
||||
EndPrimitive();
|
||||
} else {
|
||||
vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz);
|
||||
vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz);
|
||||
vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert);
|
||||
vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert);
|
||||
vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz);
|
||||
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz);
|
||||
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert);
|
||||
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert);
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head);
|
||||
vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz); //Line start, left vertex.
|
||||
vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz); //Line end, left vertex.
|
||||
vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert); //Line start, top vertex.
|
||||
vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert); //Line end, top vertex.
|
||||
vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz); //Line start, right vertex.
|
||||
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); //Line end, right vertex.
|
||||
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); //Line start, bottom vertex.
|
||||
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); //Line end, bottom vertex.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head); //Line start, tip.
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head); //Line end, tip.
|
||||
|
||||
// All normal lines are rendered as 3d tubes.
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
// left side
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
// right side
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ fragment =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5))
|
||||
{ // actually, 8 and 9
|
||||
// travel moves: 8, 9, 12, 13
|
||||
if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) ||
|
||||
((v_line_type >= 11.5) && (v_line_type <= 13.5)))) {
|
||||
{
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
|
@ -124,7 +126,9 @@ fragment41core =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
// travel moves: 8, 9, 12, 13
|
||||
if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) ||
|
||||
((v_line_type >= 11.5) && (v_line_type <= 13.5)))) {
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ class CloudApiClient:
|
|||
scope=self._scope,
|
||||
data=b"",
|
||||
callback=self._parseCallback(on_finished, CloudPrintResponse),
|
||||
error_callback=on_error,
|
||||
error_callback=self._parseError(on_error),
|
||||
timeout=self.DEFAULT_REQUEST_TIMEOUT)
|
||||
|
||||
def doPrintJobAction(self, cluster_id: str, cluster_job_id: str, action: str,
|
||||
|
|
@ -256,7 +256,6 @@ class CloudApiClient:
|
|||
"""Creates a callback function so that it includes the parsing of the response into the correct model.
|
||||
|
||||
The callback is added to the 'finished' signal of the reply.
|
||||
:param reply: The reply that should be listened to.
|
||||
:param on_finished: The callback in case the response is successful. Depending on the endpoint it will be either
|
||||
a list or a single item.
|
||||
:param model: The type of the model to convert the response to.
|
||||
|
|
@ -281,6 +280,25 @@ class CloudApiClient:
|
|||
self._anti_gc_callbacks.append(parse)
|
||||
return parse
|
||||
|
||||
def _parseError(self,
|
||||
on_error: Callable[[CloudError, "QNetworkReply.NetworkError", int], None]) -> Callable[[QNetworkReply, "QNetworkReply.NetworkError"], None]:
|
||||
|
||||
"""Creates a callback function so that it includes the parsing of an explicit error response into the correct model.
|
||||
|
||||
:param on_error: The callback in case the response gives an explicit error
|
||||
"""
|
||||
|
||||
def parse(reply: QNetworkReply, error: "QNetworkReply.NetworkError") -> None:
|
||||
|
||||
self._anti_gc_callbacks.remove(parse)
|
||||
|
||||
http_code, response = self._parseReply(reply)
|
||||
result = CloudError(**response["errors"][0])
|
||||
on_error(result, error, http_code)
|
||||
|
||||
self._anti_gc_callbacks.append(parse)
|
||||
return parse
|
||||
|
||||
@classmethod
|
||||
def getMachineIDMap(cls) -> Dict[str, str]:
|
||||
if cls._machine_id_to_name is None:
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOut
|
|||
from ..Messages.PrintJobUploadBlockedMessage import PrintJobUploadBlockedMessage
|
||||
from ..Messages.PrintJobUploadErrorMessage import PrintJobUploadErrorMessage
|
||||
from ..Messages.PrintJobUploadQueueFullMessage import PrintJobUploadQueueFullMessage
|
||||
from ..Messages.PrintJobUploadPrinterInactiveMessage import PrintJobUploadPrinterInactiveMessage
|
||||
from ..Messages.PrintJobUploadSuccessMessage import PrintJobUploadSuccessMessage
|
||||
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
|
||||
from ..Models.Http.CloudClusterStatus import CloudClusterStatus
|
||||
from ..Models.Http.CloudError import CloudError
|
||||
from ..Models.Http.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest
|
||||
from ..Models.Http.CloudPrintResponse import CloudPrintResponse
|
||||
from ..Models.Http.CloudPrintJobResponse import CloudPrintJobResponse
|
||||
|
|
@ -87,7 +89,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
address="",
|
||||
connection_type=ConnectionType.CloudConnection,
|
||||
properties=properties,
|
||||
parent=parent
|
||||
parent=parent,
|
||||
active=cluster.display_status != "inactive"
|
||||
)
|
||||
|
||||
self._api = api_client
|
||||
|
|
@ -190,6 +193,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
self._received_print_jobs = status.print_jobs
|
||||
self._updatePrintJobs(status.print_jobs)
|
||||
|
||||
self._setActive(status.active)
|
||||
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
||||
file_handler: Optional[FileHandler] = None, filter_by_machine: bool = False, **kwargs) -> None:
|
||||
|
||||
|
|
@ -291,19 +296,21 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
|
||||
self.writeFinished.emit()
|
||||
|
||||
def _onPrintUploadSpecificError(self, reply: "QNetworkReply", _: "QNetworkReply.NetworkError"):
|
||||
def _onPrintUploadSpecificError(self, error: CloudError, _: "QNetworkReply.NetworkError", http_error: int):
|
||||
"""
|
||||
Displays a message when an error occurs specific to uploading print job (i.e. queue is full).
|
||||
"""
|
||||
error_code = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute)
|
||||
if error_code == 409:
|
||||
PrintJobUploadQueueFullMessage().show()
|
||||
if http_error == 409:
|
||||
if error.code == "printerInactive":
|
||||
PrintJobUploadPrinterInactiveMessage().show()
|
||||
else:
|
||||
PrintJobUploadQueueFullMessage().show()
|
||||
else:
|
||||
PrintJobUploadErrorMessage(I18N_CATALOG.i18nc("@error:send",
|
||||
"Unknown error code when uploading print job: {0}",
|
||||
error_code)).show()
|
||||
http_error)).show()
|
||||
|
||||
Logger.log("w", "Upload of print job failed specifically with error code {}".format(error_code))
|
||||
Logger.log("w", "Upload of print job failed specifically with error code {}".format(http_error))
|
||||
|
||||
self._progress.hide()
|
||||
self._pre_upload_print_job = None
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM import i18nCatalog
|
||||
from UM.Message import Message
|
||||
|
||||
|
||||
I18N_CATALOG = i18nCatalog("cura")
|
||||
|
||||
|
||||
class PrintJobUploadPrinterInactiveMessage(Message):
|
||||
"""Message shown when uploading a print job to a cluster and the printer is inactive."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
text = I18N_CATALOG.i18nc("@info:status", "The printer is inactive and cannot accept a new print job."),
|
||||
title = I18N_CATALOG.i18nc("@info:title", "Printer inactive"),
|
||||
lifetime = 10,
|
||||
message_type=Message.MessageType.ERROR
|
||||
)
|
||||
|
|
@ -10,7 +10,7 @@ class CloudClusterResponse(BaseModel):
|
|||
"""Class representing a cloud connected cluster."""
|
||||
|
||||
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
|
||||
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
|
||||
display_status: str, host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
|
||||
friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1,
|
||||
capabilities: Optional[List[str]] = None, **kwargs) -> None:
|
||||
"""Creates a new cluster response object.
|
||||
|
|
@ -20,6 +20,7 @@ class CloudClusterResponse(BaseModel):
|
|||
:param host_name: The name of the printer as configured during the Wi-Fi setup. Used as identifier for end users.
|
||||
:param is_online: Whether this cluster is currently connected to the cloud.
|
||||
:param status: The status of the cluster authentication (active or inactive).
|
||||
:param display_status: The display status of the cluster.
|
||||
:param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
|
||||
:param host_internal_ip: The internal IP address of the host printer.
|
||||
:param friendly_name: The human readable name of the host printer.
|
||||
|
|
@ -31,6 +32,7 @@ class CloudClusterResponse(BaseModel):
|
|||
self.host_guid = host_guid
|
||||
self.host_name = host_name
|
||||
self.status = status
|
||||
self.display_status = display_status
|
||||
self.is_online = is_online
|
||||
self.host_version = host_version
|
||||
self.host_internal_ip = host_internal_ip
|
||||
|
|
@ -51,5 +53,5 @@ class CloudClusterResponse(BaseModel):
|
|||
Convenience function for printing when debugging.
|
||||
:return: A human-readable representation of the data in this object.
|
||||
"""
|
||||
return str({k: v for k, v in self.__dict__.items() if k in {"cluster_id", "host_guid", "host_name", "status", "is_online", "host_version", "host_internal_ip", "friendly_name", "printer_type", "printer_count", "capabilities"}})
|
||||
return str({k: v for k, v in self.__dict__.items() if k in {"cluster_id", "host_guid", "host_name", "status", "display_status", "is_online", "host_version", "host_internal_ip", "friendly_name", "printer_type", "printer_count", "capabilities"}})
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class CloudClusterStatus(BaseModel):
|
|||
def __init__(self, printers: List[Union[ClusterPrinterStatus, Dict[str, Any]]],
|
||||
print_jobs: List[Union[ClusterPrintJobStatus, Dict[str, Any]]],
|
||||
generated_time: Union[str, datetime],
|
||||
unavailable: bool = False,
|
||||
**kwargs) -> None:
|
||||
"""Creates a new cluster status model object.
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ class CloudClusterStatus(BaseModel):
|
|||
"""
|
||||
|
||||
self.generated_time = self.parseDate(generated_time)
|
||||
self.active = not unavailable
|
||||
self.printers = self.parseModels(ClusterPrinterStatus, printers)
|
||||
self.print_jobs = self.parseModels(ClusterPrintJobStatus, print_jobs)
|
||||
super().__init__(**kwargs)
|
||||
|
|
|
|||
|
|
@ -20,13 +20,23 @@ from ..BaseModel import BaseModel
|
|||
class ClusterPrinterStatus(BaseModel):
|
||||
"""Class representing a cluster printer"""
|
||||
|
||||
def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str,
|
||||
status: str, unique_name: str, uuid: str,
|
||||
configuration: List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]],
|
||||
reserved_by: Optional[str] = None, maintenance_required: Optional[bool] = None,
|
||||
firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None,
|
||||
build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None,
|
||||
material_station: Union[Dict[str, Any], ClusterPrinterMaterialStation] = None, **kwargs) -> None:
|
||||
def __init__(self,
|
||||
enabled: Optional[bool] = True,
|
||||
friendly_name: Optional[str] = "",
|
||||
machine_variant: Optional[str] = "",
|
||||
status: Optional[str] = "unknown",
|
||||
unique_name: Optional[str] = "",
|
||||
uuid: Optional[str] = "",
|
||||
configuration: Optional[List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]]] = None,
|
||||
firmware_version: Optional[str] = None,
|
||||
ip_address: Optional[str] = None,
|
||||
reserved_by: Optional[str] = "",
|
||||
maintenance_required: Optional[bool] = False,
|
||||
firmware_update_status: Optional[str] = "",
|
||||
latest_available_firmware: Optional[str] = "",
|
||||
build_plate: Optional[Union[Dict[str, Any], ClusterBuildPlate]] = None,
|
||||
material_station: Optional[Union[Dict[str, Any], ClusterPrinterMaterialStation]] = None,
|
||||
**kwargs) -> None:
|
||||
"""
|
||||
Creates a new cluster printer status
|
||||
:param enabled: A printer can be disabled if it should not receive new jobs. By default, every printer is enabled.
|
||||
|
|
@ -47,7 +57,7 @@ class ClusterPrinterStatus(BaseModel):
|
|||
:param material_station: The material station that is on the printer.
|
||||
"""
|
||||
|
||||
self.configuration = self.parseModels(ClusterPrintCoreConfiguration, configuration)
|
||||
self.configuration = self.parseModels(ClusterPrintCoreConfiguration, configuration) if configuration else []
|
||||
self.enabled = enabled
|
||||
self.firmware_version = firmware_version
|
||||
self.friendly_name = friendly_name
|
||||
|
|
@ -70,7 +80,7 @@ class ClusterPrinterStatus(BaseModel):
|
|||
|
||||
:param controller: - The controller of the model.
|
||||
"""
|
||||
model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version)
|
||||
model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version or "")
|
||||
self.updateOutputModel(model)
|
||||
return model
|
||||
|
||||
|
|
@ -86,7 +96,8 @@ class ClusterPrinterStatus(BaseModel):
|
|||
model.updateType(self.machine_variant)
|
||||
model.updateState(self.status if self.enabled else "disabled")
|
||||
model.updateBuildplate(self.build_plate.type if self.build_plate else "glass")
|
||||
model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address)))
|
||||
if self.ip_address:
|
||||
model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address)))
|
||||
|
||||
if not model.printerConfiguration:
|
||||
# Prevent accessing printer configuration when not available.
|
||||
|
|
|
|||
|
|
@ -46,10 +46,10 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice):
|
|||
QUEUED_PRINT_JOBS_STATES = {"queued", "error"}
|
||||
|
||||
def __init__(self, device_id: str, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType,
|
||||
parent=None) -> None:
|
||||
parent=None, active: bool = True) -> None:
|
||||
|
||||
super().__init__(device_id=device_id, address=address, properties=properties, connection_type=connection_type,
|
||||
parent=parent)
|
||||
parent=parent, active=active)
|
||||
# Trigger the printersChanged signal when the private signal is triggered.
|
||||
self.printersChanged.connect(self._clusterPrintersChanged)
|
||||
|
||||
|
|
|
|||
52
resources/definitions/anycubic_kobra3v2.def.json
Normal file
52
resources/definitions/anycubic_kobra3v2.def.json
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Anycubic Kobra 3 v2",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata":
|
||||
{
|
||||
"visible": true,
|
||||
"author": "Sam Bonnekamp",
|
||||
"manufacturer": "Anycubic",
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "anycubic_kobra3v2_buildplate.stl",
|
||||
"has_textured_buildplate": true,
|
||||
"machine_extruder_trains": { "0": "anycubic_kobra3v2_extruder_0" }
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"adhesion_type": { "value": "'skirt'" },
|
||||
"layer_height": { "default_value": 0.2 },
|
||||
"machine_buildplate_type": { "default_value": "PEI Spring Steel" },
|
||||
"machine_center_is_zero": { "default_value": false },
|
||||
"machine_depth": { "default_value": 250 },
|
||||
"machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" },
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
"machine_height": { "default_value": 260 },
|
||||
"machine_name":
|
||||
{
|
||||
"default_value": "Anycubic Kobra 3 v2",
|
||||
"description": "Anycubic Kobra 3 v2"
|
||||
},
|
||||
"machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" },
|
||||
"machine_start_gcode_first": { "default_value": true },
|
||||
"machine_width": { "default_value": 250 },
|
||||
"material_bed_temperature":
|
||||
{
|
||||
"maximum_value": "110",
|
||||
"maximum_value_warning": "90"
|
||||
},
|
||||
"material_diameter": { "default_value": 1.75 },
|
||||
"material_initial_print_temperature":
|
||||
{
|
||||
"maximum_value_warning": 295,
|
||||
"value": "material_print_temperature + 5"
|
||||
},
|
||||
"material_print_temperature": { "maximum_value_warning": 250 },
|
||||
"material_print_temperature_layer_0":
|
||||
{
|
||||
"maximum_value_warning": 295,
|
||||
"value": "material_print_temperature + 5"
|
||||
},
|
||||
"relative_extrusion": { "value": true }
|
||||
}
|
||||
}
|
||||
61
resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json
Normal file
61
resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Anycubic Kobra 3 v2 ACE PRO",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata":
|
||||
{
|
||||
"visible": true,
|
||||
"author": "Sam Bonnekamp",
|
||||
"manufacturer": "Anycubic",
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "anycubic_kobra3v2_buildplate.stl",
|
||||
"has_textured_buildplate": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anycubic_kobra3v2_ACEPRO_extruder_0",
|
||||
"1": "anycubic_kobra3v2_ACEPRO_extruder_1",
|
||||
"2": "anycubic_kobra3v2_ACEPRO_extruder_2",
|
||||
"3": "anycubic_kobra3v2_ACEPRO_extruder_3"
|
||||
}
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"adhesion_type": { "value": "'skirt'" },
|
||||
"layer_height": { "default_value": 0.2 },
|
||||
"machine_buildplate_type": { "default_value": "PEI Spring Steel" },
|
||||
"machine_center_is_zero": { "default_value": false },
|
||||
"machine_depth": { "default_value": 250 },
|
||||
"machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" },
|
||||
"machine_extruder_count": { "default_value": 4 },
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
"machine_height": { "default_value": 260 },
|
||||
"machine_name":
|
||||
{
|
||||
"default_value": "Anycubic Kobra 3 v2",
|
||||
"description": "Anycubic Kobra 3 v2"
|
||||
},
|
||||
"machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" },
|
||||
"machine_start_gcode_first": { "default_value": true },
|
||||
"machine_width": { "default_value": 250 },
|
||||
"material_bed_temperature":
|
||||
{
|
||||
"maximum_value": "110",
|
||||
"maximum_value_warning": "90"
|
||||
},
|
||||
"material_diameter": { "default_value": 1.75 },
|
||||
"material_initial_print_temperature":
|
||||
{
|
||||
"maximum_value_warning": 295,
|
||||
"value": "material_print_temperature + 5"
|
||||
},
|
||||
"material_print_temp_wait": { "value": true },
|
||||
"material_print_temperature": { "maximum_value": 300 },
|
||||
"material_print_temperature_layer_0":
|
||||
{
|
||||
"maximum_value_warning": 295,
|
||||
"value": "material_print_temperature + 5"
|
||||
},
|
||||
"material_standby_temperature": { "default_value": "material_print_temperature" },
|
||||
"relative_extrusion": { "value": true }
|
||||
}
|
||||
}
|
||||
|
|
@ -9311,6 +9311,42 @@
|
|||
"default_value": true,
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"retraction_during_travel_ratio":
|
||||
{
|
||||
"label": "Retraction During Travel Move",
|
||||
"description": "<html>The ratio of retraction performed during the travel move, with the remainder completed while the nozzle is stationary, before traveling<ul><li>When 0, the entire retraction is performed while stationary, before the travel begins</li><li>When 100, the entire retraction is performed during the travel move, bypassing the stationary phase</li></ul></html>",
|
||||
"unit": "%",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 100,
|
||||
"enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"keep_retracting_during_travel":
|
||||
{
|
||||
"label": "Keep Retracting During Travel",
|
||||
"description": "When retraction during travel is enabled, and there is more than enough time to perform a full retract during a travel move, spread the retraction over the whole travel move with a lower retraction speed, so that we do not travel with a non-retracting nozzle. This can help reducing oozing.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\" and retraction_during_travel_ratio > 0",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"prime_during_travel_ratio":
|
||||
{
|
||||
"label": "Prime During Travel Move",
|
||||
"description": "<html>The ratio of priming performed during the travel move, with the remainder completed while the nozzle is stationary, after traveling<ul><li>When 0, the entire priming is performed while stationary, after the travel ends</li><li>When 100, the entire priming is performed during the travel move, allowing the print to start immediately</li></ul></html>",
|
||||
"unit": "%",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 100,
|
||||
"enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"scarf_joint_seam_length":
|
||||
{
|
||||
"label": "Scarf Seam Length",
|
||||
|
|
|
|||
130
resources/definitions/sovol_sv08.def.json
Normal file
130
resources/definitions/sovol_sv08.def.json
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Sovol SV08",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata":
|
||||
{
|
||||
"visible": true,
|
||||
"author": "Steinar H. Gunderson",
|
||||
"manufacturer": "Sovol 3D",
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "sovol_sv08_buildplate_model.stl",
|
||||
"exclude_materials": [],
|
||||
"first_start_actions": [ "MachineSettingsAction" ],
|
||||
"has_machine_quality": true,
|
||||
"has_materials": true,
|
||||
"has_variants": true,
|
||||
"machine_extruder_trains": { "0": "sovol_sv08_extruder" },
|
||||
"preferred_material": "generic_abs",
|
||||
"preferred_quality_type": "fast",
|
||||
"preferred_variant_name": "0.4mm Nozzle",
|
||||
"quality_definition": "sovol_sv08",
|
||||
"variants_name": "Nozzle Size"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"acceleration_enabled": { "default_value": false },
|
||||
"acceleration_layer_0": { "value": 1800 },
|
||||
"acceleration_print": { "default_value": 2200 },
|
||||
"acceleration_roofing": { "value": 1800 },
|
||||
"acceleration_travel_layer_0": { "value": 1800 },
|
||||
"acceleration_wall_0": { "value": 1800 },
|
||||
"adhesion_type": { "default_value": "skirt" },
|
||||
"alternate_extra_perimeter": { "default_value": true },
|
||||
"bridge_fan_speed": { "default_value": 100 },
|
||||
"bridge_fan_speed_2": { "resolve": "max(cool_fan_speed, 50)" },
|
||||
"bridge_fan_speed_3": { "resolve": "max(cool_fan_speed, 20)" },
|
||||
"bridge_settings_enabled": { "default_value": true },
|
||||
"bridge_wall_coast": { "default_value": 10 },
|
||||
"cool_fan_full_at_height": { "value": "resolveOrValue('layer_height_0') + resolveOrValue('layer_height') * max(1, cool_fan_full_layer - 1)" },
|
||||
"cool_fan_full_layer": { "value": 4 },
|
||||
"cool_fan_speed_min": { "value": "cool_fan_speed" },
|
||||
"cool_min_layer_time": { "default_value": 15 },
|
||||
"cool_min_layer_time_fan_speed_max": { "default_value": 20 },
|
||||
"fill_outline_gaps": { "default_value": true },
|
||||
"gantry_height": { "value": 30 },
|
||||
"infill_before_walls": { "default_value": false },
|
||||
"infill_enable_travel_optimization": { "default_value": true },
|
||||
"jerk_enabled": { "default_value": false },
|
||||
"jerk_roofing": { "value": 10 },
|
||||
"jerk_wall_0": { "value": 10 },
|
||||
"layer_height_0": { "resolve": "max(0.2, min(extruderValues('layer_height')))" },
|
||||
"line_width": { "value": "machine_nozzle_size * 1.125" },
|
||||
"machine_acceleration": { "default_value": 1500 },
|
||||
"machine_depth": { "default_value": 350 },
|
||||
"machine_end_gcode": { "default_value": "END_PRINT\n" },
|
||||
"machine_endstop_positive_direction_x": { "default_value": true },
|
||||
"machine_endstop_positive_direction_y": { "default_value": true },
|
||||
"machine_endstop_positive_direction_z": { "default_value": false },
|
||||
"machine_feeder_wheel_diameter": { "default_value": 7.5 },
|
||||
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||
"machine_head_with_fans_polygon":
|
||||
{
|
||||
"default_value": [
|
||||
[-35, 65],
|
||||
[-35, -50],
|
||||
[35, -50],
|
||||
[35, 65]
|
||||
]
|
||||
},
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
"machine_height": { "default_value": 345 },
|
||||
"machine_max_acceleration_e": { "default_value": 5000 },
|
||||
"machine_max_acceleration_x": { "default_value": 40000 },
|
||||
"machine_max_acceleration_y": { "default_value": 40000 },
|
||||
"machine_max_acceleration_z": { "default_value": 500 },
|
||||
"machine_max_feedrate_e": { "default_value": 50 },
|
||||
"machine_max_feedrate_x": { "default_value": 700 },
|
||||
"machine_max_feedrate_y": { "default_value": 700 },
|
||||
"machine_max_feedrate_z": { "default_value": 20 },
|
||||
"machine_max_jerk_e": { "default_value": 5 },
|
||||
"machine_max_jerk_xy": { "default_value": 20 },
|
||||
"machine_max_jerk_z": { "default_value": 0.5 },
|
||||
"machine_name": { "default_value": "SV08" },
|
||||
"machine_start_gcode": { "default_value": "G28 ; Move to zero\nG90 ; Absolute positioning\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nSTART_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0}\nG90 ; Absolute positioning (START_PRINT might have changed it)\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nM400\nG91 ; Relative positioning\nM83 ; Relative extrusion\nM140 S{material_bed_temperature_layer_0} ; Set bed temp\nM104 S{material_print_temperature_layer_0} ; Set extruder temp\nM190 S{material_bed_temperature_layer_0} ; Wait for bed temp\nM109 S{material_print_temperature_layer_0} ; Wait for extruder temp\n{if machine_nozzle_size >= 0.4}\n; Standard Sovol blob and purge line.\nG1 E25 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.200 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 Y1 E0.16 F1800 ; Small movement back for next line\nG1 X-87.000 E13.92 F1800 ; Purge line left\nG1 X-87.000 E20.88 F1800\nG1 Y1 E0.24 F1800 ; Small movement back for next line\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 E-0.200 Z1 F600\n{else}\n; The default start G-code uses too high flow for smaller nozzles,\n; which causes Klipper errors. Scale everything back by\n; (0.25/0.4)^2, i.e., for 0.25mm nozzle. This should be good\n; enough for 0.2mm as well.\nG1 E8 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.078 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 Y1 E0.063 F1800 ; Small movement back for next line\nG1 X-87.000 E5.44 F1800 ; Purge line left\nG1 X-87.000 E8.16 F1800\nG1 Y1 E0.094 F1800 ; Small movement back for next line\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 E-0.078 Z1 F600\n{endif}\nM400 ; Wait for moves to finish\nG90 ; Absolute positioning\nM82 ; Absolute extrusion mode\n" },
|
||||
"machine_steps_per_mm_x": { "default_value": 80 },
|
||||
"machine_steps_per_mm_y": { "default_value": 80 },
|
||||
"machine_steps_per_mm_z": { "default_value": 400 },
|
||||
"machine_width": { "default_value": 350 },
|
||||
"meshfix_maximum_resolution": { "default_value": 0.01 },
|
||||
"min_infill_area": { "default_value": 5.0 },
|
||||
"minimum_polygon_circumference": { "default_value": 0.2 },
|
||||
"optimize_wall_printing_order": { "default_value": true },
|
||||
"retraction_amount": { "default_value": 0.5 },
|
||||
"retraction_combing": { "value": "'noskin'" },
|
||||
"retraction_combing_max_distance": { "default_value": 10 },
|
||||
"retraction_hop": { "default_value": 0.4 },
|
||||
"retraction_hop_enabled": { "default_value": true },
|
||||
"retraction_prime_speed":
|
||||
{
|
||||
"maximum_value_warning": 130,
|
||||
"value": "math.ceil(retraction_speed * 0.4)"
|
||||
},
|
||||
"retraction_retract_speed": { "maximum_value_warning": 130 },
|
||||
"retraction_speed":
|
||||
{
|
||||
"default_value": 30,
|
||||
"maximum_value_warning": 130
|
||||
},
|
||||
"roofing_layer_count": { "value": 1 },
|
||||
"skirt_brim_minimal_length": { "default_value": 550 },
|
||||
"speed_layer_0": { "value": "math.ceil(speed_print * 0.25)" },
|
||||
"speed_roofing": { "value": "math.ceil(speed_print * 0.33)" },
|
||||
"speed_slowdown_layers": { "default_value": 4 },
|
||||
"speed_topbottom": { "value": "math.ceil(speed_print * 0.33)" },
|
||||
"speed_travel":
|
||||
{
|
||||
"maximum_value_warning": 501,
|
||||
"value": 300
|
||||
},
|
||||
"speed_travel_layer_0": { "value": "math.ceil(speed_travel * 0.4)" },
|
||||
"speed_wall": { "value": "math.ceil(speed_print * 0.33)" },
|
||||
"speed_wall_0": { "value": "math.ceil(speed_print * 0.33)" },
|
||||
"speed_wall_x": { "value": "math.ceil(speed_print * 0.66)" },
|
||||
"travel_avoid_other_parts": { "default_value": false },
|
||||
"wall_line_width": { "value": "machine_nozzle_size" },
|
||||
"wall_overhang_angle": { "default_value": 75 },
|
||||
"wall_overhang_speed_factors": { "default_value": "[50]" },
|
||||
"zig_zaggify_infill": { "value": true }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "ACE Pro Color 1",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata":
|
||||
{
|
||||
"machine": "anycubic_kobra3v2_ACE_PRO",
|
||||
"position": "0"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "ACE Pro Color 2",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata":
|
||||
{
|
||||
"machine": "anycubic_kobra3v2_ACE_PRO",
|
||||
"position": "1"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"extruder_nr": { "default_value": 1 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "ACE Pro Color 3",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata":
|
||||
{
|
||||
"machine": "anycubic_kobra3v2_ACE_PRO",
|
||||
"position": "2"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"extruder_nr": { "default_value": 2 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "ACE Pro Color 4",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata":
|
||||
{
|
||||
"machine": "anycubic_kobra3v2_ACE_PRO",
|
||||
"position": "3"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"extruder_nr": { "default_value": 3 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
16
resources/extruders/anycubic_kobra3v2_extruder_0.def.json
Normal file
16
resources/extruders/anycubic_kobra3v2_extruder_0.def.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Extruder 1",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata":
|
||||
{
|
||||
"machine": "anycubic_kobra3v2",
|
||||
"position": "0"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
19
resources/extruders/sovol_sv08_extruder.def.json
Normal file
19
resources/extruders/sovol_sv08_extruder.def.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Nozzle Size",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata":
|
||||
{
|
||||
"machine": "sovol_sv08",
|
||||
"position": "0"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"extruder_nr":
|
||||
{
|
||||
"default_value": 0,
|
||||
"maximum_value": 1
|
||||
},
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
BIN
resources/meshes/anycubic_kobra3v2_buildplate.stl
Normal file
BIN
resources/meshes/anycubic_kobra3v2_buildplate.stl
Normal file
Binary file not shown.
BIN
resources/meshes/sovol_sv08_buildplate_model.stl
Normal file
BIN
resources/meshes/sovol_sv08_buildplate_model.stl
Normal file
Binary file not shown.
|
|
@ -16,6 +16,7 @@ Cura.ExpandablePopup
|
|||
property bool isConnectedCloudPrinter: machineManager.activeMachineHasCloudConnection
|
||||
property bool isCloudRegistered: machineManager.activeMachineHasCloudRegistration
|
||||
property bool isGroup: machineManager.activeMachineIsGroup
|
||||
property bool isActive: machineManager.activeMachineIsActive
|
||||
property string machineName: {
|
||||
if (isNetworkPrinter && machineManager.activeMachineNetworkGroupName != "")
|
||||
{
|
||||
|
|
@ -40,7 +41,14 @@ Cura.ExpandablePopup
|
|||
}
|
||||
else if (isConnectedCloudPrinter && Cura.API.connectionStatus.isInternetReachable)
|
||||
{
|
||||
return "printer_cloud_connected"
|
||||
if (isActive)
|
||||
{
|
||||
return "printer_cloud_connected"
|
||||
}
|
||||
else
|
||||
{
|
||||
return "printer_cloud_inactive"
|
||||
}
|
||||
}
|
||||
else if (isCloudRegistered)
|
||||
{
|
||||
|
|
@ -53,7 +61,7 @@ Cura.ExpandablePopup
|
|||
}
|
||||
|
||||
function getConnectionStatusMessage() {
|
||||
if (connectionStatus == "printer_cloud_not_available")
|
||||
if (connectionStatus === "printer_cloud_not_available")
|
||||
{
|
||||
if(Cura.API.connectionStatus.isInternetReachable)
|
||||
{
|
||||
|
|
@ -78,6 +86,10 @@ Cura.ExpandablePopup
|
|||
return catalog.i18nc("@status", "The cloud connection is currently unavailable. Please check your internet connection.")
|
||||
}
|
||||
}
|
||||
else if(connectionStatus === "printer_cloud_inactive")
|
||||
{
|
||||
return catalog.i18nc("@status", "This printer is deactivated and can not accept commands or jobs.")
|
||||
}
|
||||
else
|
||||
{
|
||||
return ""
|
||||
|
|
@ -130,14 +142,18 @@ Cura.ExpandablePopup
|
|||
|
||||
source:
|
||||
{
|
||||
if (connectionStatus == "printer_connected")
|
||||
if (connectionStatus === "printer_connected")
|
||||
{
|
||||
return UM.Theme.getIcon("CheckBlueBG", "low")
|
||||
}
|
||||
else if (connectionStatus == "printer_cloud_connected" || connectionStatus == "printer_cloud_not_available")
|
||||
else if (connectionStatus === "printer_cloud_connected" || connectionStatus === "printer_cloud_not_available")
|
||||
{
|
||||
return UM.Theme.getIcon("CloudBadge", "low")
|
||||
}
|
||||
else if (connectionStatus === "printer_cloud_inactive")
|
||||
{
|
||||
return UM.Theme.getIcon("WarningBadge", "low")
|
||||
}
|
||||
else
|
||||
{
|
||||
return ""
|
||||
|
|
@ -147,7 +163,21 @@ Cura.ExpandablePopup
|
|||
width: UM.Theme.getSize("printer_status_icon").width
|
||||
height: UM.Theme.getSize("printer_status_icon").height
|
||||
|
||||
color: connectionStatus == "printer_cloud_not_available" ? UM.Theme.getColor("cloud_unavailable") : UM.Theme.getColor("primary")
|
||||
color:
|
||||
{
|
||||
if (connectionStatus === "printer_cloud_not_available")
|
||||
{
|
||||
return UM.Theme.getColor("cloud_unavailable")
|
||||
}
|
||||
else if(connectionStatus === "printer_cloud_inactive")
|
||||
{
|
||||
return UM.Theme.getColor("cloud_inactive")
|
||||
}
|
||||
else
|
||||
{
|
||||
return UM.Theme.getColor("primary")
|
||||
}
|
||||
}
|
||||
|
||||
visible: (isNetworkPrinter || isCloudRegistered) && source != ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
[general]
|
||||
definition = sovol_sv08
|
||||
name = Standard Quality
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
material = generic_abs
|
||||
quality_type = standard
|
||||
setting_version = 25
|
||||
type = quality
|
||||
variant = 0.4mm Nozzle
|
||||
|
||||
[values]
|
||||
bridge_fan_speed = 30
|
||||
bridge_settings_enabled = True
|
||||
cool_fan_enabled = True
|
||||
cool_fan_speed = 10
|
||||
cool_fan_speed_max = 30
|
||||
cool_min_layer_time = 4
|
||||
cool_min_layer_time_fan_speed_max = 30
|
||||
cool_min_speed = 10
|
||||
material_bed_temperature = 95
|
||||
material_flow = 98
|
||||
material_max_flowrate = 21
|
||||
material_print_temperature = 270
|
||||
material_print_temperature_layer_0 = 280
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
[general]
|
||||
definition = sovol_sv08
|
||||
name = Standard Quality
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
material = generic_petg
|
||||
quality_type = standard
|
||||
setting_version = 25
|
||||
type = quality
|
||||
variant = 0.4mm Nozzle
|
||||
|
||||
[values]
|
||||
bridge_fan_speed = 70
|
||||
bridge_settings_enabled = True
|
||||
cool_fan_enabled = True
|
||||
cool_fan_speed = 10
|
||||
cool_fan_speed_max = 30
|
||||
cool_min_layer_time = 5
|
||||
cool_min_layer_time_fan_speed_max = 30
|
||||
cool_min_speed = 10
|
||||
material_bed_temperature = 75
|
||||
material_flow = 98
|
||||
material_max_flowrate = 17
|
||||
material_print_temperature = 235
|
||||
material_print_temperature_layer_0 = 250
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
[general]
|
||||
definition = sovol_sv08
|
||||
name = Standard Quality
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
material = generic_pla
|
||||
quality_type = standard
|
||||
setting_version = 25
|
||||
type = quality
|
||||
variant = 0.4mm Nozzle
|
||||
|
||||
[values]
|
||||
bridge_fan_speed = 100
|
||||
bridge_settings_enabled = True
|
||||
cool_fan_enabled = True
|
||||
cool_fan_speed = 50
|
||||
cool_fan_speed_max = 70
|
||||
cool_min_layer_time = 5
|
||||
cool_min_layer_time_fan_speed_max = 50
|
||||
cool_min_speed = 10
|
||||
material_bed_temperature = 65
|
||||
material_flow = 98
|
||||
material_max_flowrate = 21
|
||||
material_print_temperature = 220
|
||||
material_print_temperature_layer_0 = 235
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
[general]
|
||||
definition = sovol_sv08
|
||||
name = Standard Quality
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
material = generic_tpu
|
||||
quality_type = standard
|
||||
setting_version = 25
|
||||
type = quality
|
||||
variant = 0.4mm Nozzle
|
||||
|
||||
[values]
|
||||
bridge_fan_speed = 100
|
||||
bridge_settings_enabled = True
|
||||
cool_fan_enabled = True
|
||||
cool_fan_speed = 80
|
||||
cool_fan_speed_max = 100
|
||||
cool_min_layer_time = 5
|
||||
cool_min_layer_time_fan_speed_max = 50
|
||||
cool_min_speed = 10
|
||||
material_bed_temperature = 65
|
||||
material_flow = 98
|
||||
material_max_flowrate = 3.6
|
||||
material_print_temperature = 240
|
||||
material_print_temperature_layer_0 = 235
|
||||
|
||||
31
resources/quality/sovol/sovol_sv08_global.inst.cfg
Normal file
31
resources/quality/sovol/sovol_sv08_global.inst.cfg
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
[general]
|
||||
definition = sovol_sv08
|
||||
name = 0.20mm Standard
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
global_quality = True
|
||||
quality_type = standard
|
||||
setting_version = 25
|
||||
type = quality
|
||||
|
||||
[values]
|
||||
acceleration_enabled = True
|
||||
acceleration_layer_0 = 3000
|
||||
acceleration_print = 20000
|
||||
acceleration_roofing = =acceleration_wall_0
|
||||
acceleration_topbottom = =acceleration_wall
|
||||
acceleration_travel = 40000
|
||||
acceleration_wall_0 = 8000
|
||||
acceleration_wall_x = 12000
|
||||
layer_height = 0.2
|
||||
skirt_brim_speed = 80
|
||||
speed_infill = 200
|
||||
speed_ironing = 15
|
||||
speed_layer_0 = 30
|
||||
speed_print = 600
|
||||
speed_slowdown_layers = 3
|
||||
speed_travel = =speed_print
|
||||
speed_wall_0 = 200
|
||||
speed_wall_x = 300
|
||||
|
||||
|
|
@ -463,6 +463,8 @@
|
|||
"layerview_support_infill": [0, 230, 230, 127],
|
||||
"layerview_move_combing": [0, 0, 255, 255],
|
||||
"layerview_move_retraction": [128, 127, 255, 255],
|
||||
"layerview_move_while_retracting": [127, 255, 255, 255],
|
||||
"layerview_move_while_unretracting": [255, 127, 255, 255],
|
||||
"layerview_support_interface": [63, 127, 255, 127],
|
||||
"layerview_prime_tower": [0, 255, 255, 255],
|
||||
"layerview_nozzle": [224, 192, 16, 64],
|
||||
|
|
@ -496,6 +498,7 @@
|
|||
"monitor_carousel_dot_current": [119, 119, 119, 255],
|
||||
|
||||
"cloud_unavailable": [153, 153, 153, 255],
|
||||
"cloud_inactive": [253, 209, 58, 255],
|
||||
"connection_badge_background": [255, 255, 255, 255],
|
||||
"warning_badge_background": [0, 0, 0, 255],
|
||||
"error_badge_background": [255, 255, 255, 255],
|
||||
|
|
|
|||
13
resources/variants/sovol/sovol_sv08_0.4.inst.cfg
Normal file
13
resources/variants/sovol/sovol_sv08_0.4.inst.cfg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[general]
|
||||
definition = sovol_sv08
|
||||
name = 0.4mm Nozzle
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
hardware_type = nozzle
|
||||
setting_version = 25
|
||||
type = variant
|
||||
|
||||
[values]
|
||||
machine_nozzle_size = 0.4
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue