mirror of
https://github.com/Ultimaker/Cura.git
synced 2026-01-24 14:16:46 -07:00
Merge branch 'main' into voron-volumetric-speed
This commit is contained in:
commit
c7bad7465e
109 changed files with 3104 additions and 392 deletions
51
.github/workflows/find-packages.yml
vendored
Normal file
51
.github/workflows/find-packages.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
name: All installers (based on Jira ticket)
|
||||
run-name: ${{ inputs.jira_ticket_number }} by @${{ github.actor }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
jira_ticket_number:
|
||||
description: 'Jira ticket number (e.g. CURA-15432 or cura_12345)'
|
||||
required: true
|
||||
type: string
|
||||
start_builds:
|
||||
description: 'Start installers build based on found packages'
|
||||
default: true
|
||||
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
|
||||
3
.github/workflows/nightly-stable.yml
vendored
3
.github/workflows/nightly-stable.yml
vendored
|
|
@ -1,7 +1,8 @@
|
|||
name: Nightly build - stable release
|
||||
run-name: Nightly build - stable release
|
||||
|
||||
# on:
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# # Daily at 5:15 CET
|
||||
# - cron: '15 4 * * *'
|
||||
|
|
|
|||
7
.github/workflows/nightly-testing.yml
vendored
7
.github/workflows/nightly-testing.yml
vendored
|
|
@ -1,10 +1,11 @@
|
|||
name: Nightly build - dev release
|
||||
run-name: Nightly build - dev release
|
||||
|
||||
# on:
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# # Daily at 4:15 CET
|
||||
# - cron: '15 3 * * *'
|
||||
# # Daily at 5:15 CET
|
||||
# - cron: '15 4 * * *'
|
||||
|
||||
jobs:
|
||||
build-nightly:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ name: printer-linter-pr-diagnose
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
path:
|
||||
paths:
|
||||
- "resources/**"
|
||||
|
||||
permissions:
|
||||
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
path: printer-linter-result/
|
||||
|
||||
- name: Run clang-tidy-pr-comments action
|
||||
uses: platisd/clang-tidy-pr-comments@bc0bb7da034a8317d54e7fe1e819159002f4cc40
|
||||
uses: platisd/clang-tidy-pr-comments@v1.8.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clang_tidy_fixes: result.yml
|
||||
|
|
|
|||
65
.github/workflows/slicing-error-check.yml
vendored
Normal file
65
.github/workflows/slicing-error-check.yml
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
name: Slicing Error Check
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
processSlicingError:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for project file and set output
|
||||
id: check_issue_details
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const issueNumber = issue.number;
|
||||
|
||||
console.log(`Processing issue #${issueNumber}: "${issue.title}"`);
|
||||
|
||||
const hasSlicingErrorLabel = issue.labels.some(label => label.name.toLowerCase().includes('slicing error'));
|
||||
const titleContainsSliceFailed = issue.title.toLowerCase().includes('slice failed');
|
||||
const bodyText = issue.body || "";
|
||||
const bodyContainsSliceFailed = bodyText.toLowerCase().includes('slice failed');
|
||||
let setNeedsInfoOutput = false;
|
||||
|
||||
if (hasSlicingErrorLabel || titleContainsSliceFailed || bodyContainsSliceFailed) {
|
||||
console.log(`Issue #${issueNumber} matches slicing error criteria.`);
|
||||
|
||||
const zipRegex = /(\[[^\]]*?\]\(.*?\.zip\)|https?:\/\/[^\s]*?\.zip)/i;
|
||||
let hasZipAttachment = zipRegex.test(bodyText);
|
||||
|
||||
if (hasZipAttachment) {
|
||||
console.log(`Issue #${issueNumber} appears to have a .zip file linked in the body.`);
|
||||
} else {
|
||||
console.log(`Issue #${issueNumber} does not appear to have a .zip file linked in the body. Flagging for further action.`);
|
||||
setNeedsInfoOutput = true;
|
||||
}
|
||||
} else {
|
||||
console.log(`Issue #${issueNumber} does not match slicing error criteria. No action needed.`);
|
||||
}
|
||||
core.setOutput('needs_info', setNeedsInfoOutput.toString());
|
||||
|
||||
- name: Add comment if project file is missing
|
||||
if: ${{ steps.check_issue_details.outputs.needs_info == 'true' }}
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
This issue is related to a slicing error, but it seems a project file (`.zip`) is missing.
|
||||
Please attach a `.zip` file containing your project (including models and profiles) so we can reproduce the issue.
|
||||
This will help us investigate and resolve the problem more effectively.
|
||||
Have Cura open with your project that fails to slice, go to `Help` > `Export Package For Technical Support`, and save the package.
|
||||
Then create a .zip file with the package, attach the `.zip` file to this issue.
|
||||
If you have already attached a `.zip` file, please ensure it is correctly linked in the issue body.
|
||||
|
||||
- name: Add Status Needs Info Label
|
||||
if: ${{ steps.check_issue_details.outputs.needs_info == 'true' }}
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: |
|
||||
Status: Needs Info
|
||||
4
.github/workflows/update-translation.yml
vendored
4
.github/workflows/update-translation.yml
vendored
|
|
@ -11,5 +11,5 @@ on:
|
|||
jobs:
|
||||
update-translations:
|
||||
uses: ultimaker/cura-workflows/.github/workflows/update-translations.yml@main
|
||||
with:
|
||||
branch: ${{ inputs.branch }}
|
||||
with:
|
||||
branch: ${{ inputs.branch }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
version: "5.11.0-alpha.0"
|
||||
commit: "unknown"
|
||||
requirements:
|
||||
- "cura_resources/5.11.0-alpha.0@ultimaker/testing"
|
||||
- "uranium/5.11.0-alpha.0@ultimaker/testing"
|
||||
|
|
@ -99,6 +100,7 @@ pyinstaller:
|
|||
- "pyArcus"
|
||||
- "pyDulcificum"
|
||||
- "pynest2d"
|
||||
- "pyUvula"
|
||||
- "PyQt6"
|
||||
- "PyQt6.QtNetwork"
|
||||
- "PyQt6.sip"
|
||||
|
|
|
|||
20
conanfile.py
20
conanfile.py
|
|
@ -16,7 +16,7 @@ from conan import ConanFile
|
|||
from conan.tools.files import copy, rmdir, save, mkdir, rm, update_conandata
|
||||
from conan.tools.microsoft import unix_path
|
||||
from conan.tools.env import VirtualRunEnv, Environment, VirtualBuildEnv
|
||||
from conan.tools.scm import Version
|
||||
from conan.tools.scm import Version, Git
|
||||
from conan.errors import ConanInvalidConfiguration, ConanException
|
||||
|
||||
required_conan_version = ">=2.7.0" # When changing the version, also change the one in conandata.yml/extra_dependencies
|
||||
|
|
@ -329,10 +329,16 @@ class CuraConan(ConanFile):
|
|||
# If you want a specific Cura version to show up on the splash screen add the user configuration `user.cura:version=VERSION`
|
||||
# the global.conf, profile, package_info (of dependency) or via the cmd line `-c user.cura:version=VERSION`
|
||||
cura_version = Version(self.conf.get("user.cura:version", default = self.version, check_type = str))
|
||||
pre_tag = f"-{cura_version.pre}" if cura_version.pre else ""
|
||||
build_tag = f"+{cura_version.build}" if cura_version.build else ""
|
||||
internal_tag = f"+internal" if self.options.internal else ""
|
||||
cura_version = f"{cura_version.major}.{cura_version.minor}.{cura_version.patch}{pre_tag}{build_tag}{internal_tag}"
|
||||
extra_build_identifiers = []
|
||||
|
||||
if self.options.internal:
|
||||
extra_build_identifiers.append("internal")
|
||||
if str(cura_version.pre).startswith("alpha") and self.conan_data["commit"] != "unknown":
|
||||
extra_build_identifiers.append(self.conan_data["commit"][:6])
|
||||
|
||||
if extra_build_identifiers:
|
||||
separator = "+" if not cura_version.build else "."
|
||||
cura_version = Version(f"{cura_version}{separator}{'.'.join(extra_build_identifiers)}")
|
||||
|
||||
self.output.info(f"Write CuraVersion.py to {self.recipe_folder}")
|
||||
|
||||
|
|
@ -340,7 +346,7 @@ class CuraConan(ConanFile):
|
|||
f.write(cura_version_py.render(
|
||||
cura_app_name = self.name,
|
||||
cura_app_display_name = self._app_name,
|
||||
cura_version = cura_version,
|
||||
cura_version = str(cura_version),
|
||||
cura_version_full = self.version,
|
||||
cura_build_type = "Enterprise" if self.options.enterprise else "",
|
||||
cura_debug_mode = self.options.cura_debug_mode,
|
||||
|
|
@ -527,7 +533,7 @@ class CuraConan(ConanFile):
|
|||
))
|
||||
|
||||
def export(self):
|
||||
update_conandata(self, {"version": self.version})
|
||||
update_conandata(self, {"version": self.version, "commit": Git(self).get_commit()})
|
||||
|
||||
def export_sources(self):
|
||||
copy(self, "*", os.path.join(self.recipe_folder, "plugins"), os.path.join(self.export_sources_folder, "plugins"))
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import time
|
|||
import platform
|
||||
from pathlib import Path
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any, Dict
|
||||
import requests
|
||||
|
||||
import numpy
|
||||
from PyQt6.QtCore import QObject, QTimer, QUrl, QUrlQuery, pyqtSignal, pyqtProperty, QEvent, pyqtEnum, QCoreApplication, \
|
||||
|
|
@ -1645,14 +1644,10 @@ class CuraApplication(QtApplication):
|
|||
Logger.log("w", "Unable to reload data because we don't have a filename.")
|
||||
|
||||
for file_name, nodes in objects_in_filename.items():
|
||||
file_path = os.path.normpath(os.path.dirname(file_name))
|
||||
job = ReadMeshJob(file_name,
|
||||
add_to_recent_files=file_path != tempfile.gettempdir()) # Don't add temp files to the recent files list
|
||||
job._nodes = nodes # type: ignore
|
||||
job.finished.connect(self._reloadMeshFinished)
|
||||
on_done = None
|
||||
if has_merged_nodes:
|
||||
job.finished.connect(self.updateOriginOfMergedMeshes)
|
||||
job.start()
|
||||
on_done = self.updateOriginOfMergedMeshes
|
||||
self.getController().getScene().reloadNodes(nodes, file_name, on_done)
|
||||
|
||||
@pyqtSlot("QStringList")
|
||||
def setExpandedCategories(self, categories: List[str]) -> None:
|
||||
|
|
@ -1835,53 +1830,6 @@ class CuraApplication(QtApplication):
|
|||
fileLoaded = pyqtSignal(str)
|
||||
fileCompleted = pyqtSignal(str)
|
||||
|
||||
def _reloadMeshFinished(self, job) -> None:
|
||||
"""
|
||||
Function called when ReadMeshJob finishes reloading a file in the background, then update node objects in the
|
||||
scene from its source file. The function gets all the nodes that exist in the file through the job result, and
|
||||
then finds the scene nodes that need to be refreshed by their name. Each job refreshes all nodes of a file.
|
||||
Nodes that are not present in the updated file are kept in the scene.
|
||||
|
||||
:param job: The :py:class:`Uranium.UM.ReadMeshJob.ReadMeshJob` running in the background that reads all the
|
||||
meshes in a file
|
||||
"""
|
||||
|
||||
job_result = job.getResult() # nodes that exist inside the file read by this job
|
||||
if len(job_result) == 0:
|
||||
Logger.log("e", "Reloading the mesh failed.")
|
||||
return
|
||||
renamed_nodes = {} # type: Dict[str, int]
|
||||
# Find the node to be refreshed based on its id
|
||||
for job_result_node in job_result:
|
||||
mesh_data = job_result_node.getMeshData()
|
||||
if not mesh_data:
|
||||
Logger.log("w", "Could not find a mesh in reloaded node.")
|
||||
continue
|
||||
|
||||
# Solves issues with object naming
|
||||
result_node_name = job_result_node.getName()
|
||||
if not result_node_name:
|
||||
result_node_name = os.path.basename(mesh_data.getFileName())
|
||||
if result_node_name in renamed_nodes: # objects may get renamed by ObjectsModel._renameNodes() when loaded
|
||||
renamed_nodes[result_node_name] += 1
|
||||
result_node_name = "{0}({1})".format(result_node_name, renamed_nodes[result_node_name])
|
||||
else:
|
||||
renamed_nodes[job_result_node.getName()] = 0
|
||||
|
||||
# Find the matching scene node to replace
|
||||
scene_node = None
|
||||
for replaced_node in job._nodes:
|
||||
if replaced_node.getName() == result_node_name:
|
||||
scene_node = replaced_node
|
||||
break
|
||||
|
||||
if scene_node:
|
||||
scene_node.setMeshData(mesh_data)
|
||||
else:
|
||||
# Current node is a new one in the file, or it's name has changed
|
||||
# TODO: Load this mesh into the scene. Also alter the "_reloadJobFinished" action in UM.Scene
|
||||
Logger.log("w", "Could not find matching node for object '{0}' in the scene.".format(result_node_name))
|
||||
|
||||
def _openFile(self, filename):
|
||||
self.readLocalFile(QUrl.fromLocalFile(filename))
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,63 @@
|
|||
import copy
|
||||
import json
|
||||
|
||||
from typing import Optional, Dict
|
||||
|
||||
from PyQt6.QtCore import QBuffer
|
||||
from PyQt6.QtGui import QImage, QImageWriter
|
||||
|
||||
import UM.View.GL.Texture
|
||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.View.GL.Texture import Texture
|
||||
|
||||
|
||||
class SliceableObjectDecorator(SceneNodeDecorator):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._paint_texture = None
|
||||
self._texture_data_mapping: Dict[str, tuple[int, int]] = {}
|
||||
|
||||
def isSliceable(self) -> bool:
|
||||
return True
|
||||
|
||||
def getPaintTexture(self) -> Optional[Texture]:
|
||||
return self._paint_texture
|
||||
|
||||
def setPaintTexture(self, texture: Texture) -> None:
|
||||
self._paint_texture = texture
|
||||
|
||||
def getTextureDataMapping(self) -> Dict[str, tuple[int, int]]:
|
||||
return self._texture_data_mapping
|
||||
|
||||
def setTextureDataMapping(self, mapping: Dict[str, tuple[int, int]]) -> None:
|
||||
self._texture_data_mapping = mapping
|
||||
|
||||
def prepareTexture(self, width: int, height: int) -> None:
|
||||
if self._paint_texture is None:
|
||||
self._paint_texture = OpenGL.getInstance().createTexture(width, height)
|
||||
image = QImage(width, height, QImage.Format.Format_RGB32)
|
||||
image.fill(0)
|
||||
self._paint_texture.setImage(image)
|
||||
|
||||
def packTexture(self) -> Optional[bytearray]:
|
||||
if self._paint_texture is None:
|
||||
return None
|
||||
|
||||
texture_image = self._paint_texture.getImage()
|
||||
if texture_image is None:
|
||||
return None
|
||||
|
||||
texture_buffer = QBuffer()
|
||||
texture_buffer.open(QBuffer.OpenModeFlag.ReadWrite)
|
||||
image_writer = QImageWriter(texture_buffer, b"png")
|
||||
image_writer.setText("Description", json.dumps(self._texture_data_mapping))
|
||||
image_writer.write(texture_image)
|
||||
|
||||
return texture_buffer.data()
|
||||
|
||||
def __deepcopy__(self, memo) -> "SliceableObjectDecorator":
|
||||
return type(self)()
|
||||
copied_decorator = SliceableObjectDecorator()
|
||||
copied_decorator.setPaintTexture(copy.deepcopy(self.getPaintTexture()))
|
||||
copied_decorator.setTextureDataMapping(copy.deepcopy(self.getTextureDataMapping()))
|
||||
return copied_decorator
|
||||
|
|
|
|||
|
|
@ -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", "")
|
||||
|
|
|
|||
|
|
@ -77,3 +77,4 @@ AppImage:
|
|||
arch: {{ arch }}
|
||||
file_name: {{ file_name }}
|
||||
update-information: guess
|
||||
comp: gzip
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
# Copyright (c) 2021-2022 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
import os.path
|
||||
import zipfile
|
||||
from typing import List, Optional, Union, TYPE_CHECKING, cast
|
||||
|
||||
import pySavitar as Savitar
|
||||
import numpy
|
||||
from PyQt6.QtCore import QBuffer
|
||||
from PyQt6.QtGui import QImage, QImageReader
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Matrix import Matrix
|
||||
|
|
@ -18,6 +20,8 @@ from UM.Scene.GroupDecorator import GroupDecorator
|
|||
from UM.Scene.SceneNode import SceneNode # For typing.
|
||||
from UM.Scene.SceneNodeSettings import SceneNodeSettings
|
||||
from UM.Util import parseBool
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.View.GL.Texture import Texture
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
|
|
@ -94,14 +98,14 @@ class ThreeMFReader(MeshReader):
|
|||
return temp_mat
|
||||
|
||||
@staticmethod
|
||||
def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "", archive: zipfile.ZipFile = None) -> Optional[SceneNode]:
|
||||
def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "", archive: zipfile.ZipFile = None, scene: Savitar.Scene = None) -> Optional[SceneNode]:
|
||||
"""Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.
|
||||
|
||||
:returns: Scene node.
|
||||
"""
|
||||
try:
|
||||
node_name = savitar_node.getName()
|
||||
node_id = savitar_node.getId()
|
||||
node_id = str(savitar_node.getId())
|
||||
except AttributeError:
|
||||
Logger.log("e", "Outdated version of libSavitar detected! Please update to the newest version!")
|
||||
node_name = ""
|
||||
|
|
@ -131,23 +135,31 @@ class ThreeMFReader(MeshReader):
|
|||
um_node.setTransformation(transformation)
|
||||
mesh_builder = MeshBuilder()
|
||||
|
||||
data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32)
|
||||
mesh_data = savitar_node.getMeshData()
|
||||
|
||||
vertices_data = numpy.fromstring(mesh_data.getFlatVerticesAsBytes(), dtype=numpy.float32)
|
||||
vertices = numpy.resize(vertices_data, (int(vertices_data.size / 3), 3))
|
||||
|
||||
texture_path = mesh_data.getTexturePath(scene)
|
||||
uv_data = numpy.fromstring(mesh_data.getUVCoordinatesPerVertexAsBytes(scene), dtype=numpy.float32)
|
||||
uv_coordinates = numpy.resize(uv_data, (int(uv_data.size / 2), 2))
|
||||
|
||||
vertices = numpy.resize(data, (int(data.size / 3), 3))
|
||||
mesh_builder.setVertices(vertices)
|
||||
mesh_builder.calculateNormals(fast=True)
|
||||
mesh_builder.setMeshId(node_id)
|
||||
mesh_builder.setUVCoordinates(uv_coordinates)
|
||||
if file_name:
|
||||
# The filename is used to give the user the option to reload the file if it is changed on disk
|
||||
# It is only set for the root node of the 3mf file
|
||||
mesh_builder.setFileName(file_name)
|
||||
|
||||
mesh_data = mesh_builder.build()
|
||||
|
||||
if len(mesh_data.getVertices()):
|
||||
um_node.setMeshData(mesh_data)
|
||||
|
||||
for child in savitar_node.getChildren():
|
||||
child_node = ThreeMFReader._convertSavitarNodeToUMNode(child, archive=archive)
|
||||
child_node = ThreeMFReader._convertSavitarNodeToUMNode(child, archive=archive, scene=scene)
|
||||
if child_node:
|
||||
um_node.addChild(child_node)
|
||||
|
||||
|
|
@ -219,6 +231,30 @@ class ThreeMFReader(MeshReader):
|
|||
# affects (auto) slicing
|
||||
sliceable_decorator = SliceableObjectDecorator()
|
||||
um_node.addDecorator(sliceable_decorator)
|
||||
|
||||
if texture_path != "" and archive is not None:
|
||||
texture_data = archive.open(texture_path).read()
|
||||
texture_buffer = QBuffer()
|
||||
texture_buffer.open(QBuffer.OpenModeFlag.ReadWrite)
|
||||
texture_buffer.write(texture_data)
|
||||
|
||||
image_reader = QImageReader(texture_buffer, b"png")
|
||||
|
||||
texture_buffer.seek(0)
|
||||
texture_image = image_reader.read()
|
||||
texture = Texture(OpenGL.getInstance())
|
||||
texture.setImage(texture_image)
|
||||
sliceable_decorator.setPaintTexture(texture)
|
||||
|
||||
texture_buffer.seek(0)
|
||||
data_mapping_desc = image_reader.text("Description")
|
||||
if data_mapping_desc != "":
|
||||
data_mapping = json.loads(data_mapping_desc)
|
||||
for key, value in data_mapping.items():
|
||||
# Tuples are stored as lists in json, restore them back to tuples
|
||||
data_mapping[key] = tuple(value)
|
||||
sliceable_decorator.setTextureDataMapping(data_mapping)
|
||||
|
||||
return um_node
|
||||
|
||||
def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
|
||||
|
|
@ -236,7 +272,7 @@ class ThreeMFReader(MeshReader):
|
|||
CuraApplication.getInstance().getController().getScene().setMetaDataEntry(key, value)
|
||||
|
||||
for node in scene_3mf.getSceneNodes():
|
||||
um_node = ThreeMFReader._convertSavitarNodeToUMNode(node, file_name, archive)
|
||||
um_node = ThreeMFReader._convertSavitarNodeToUMNode(node, file_name, archive, scene_3mf)
|
||||
if um_node is None:
|
||||
continue
|
||||
|
||||
|
|
@ -336,7 +372,7 @@ class ThreeMFReader(MeshReader):
|
|||
# Convert the scene to scene nodes
|
||||
nodes = []
|
||||
for savitar_node in scene.getSceneNodes():
|
||||
scene_node = ThreeMFReader._convertSavitarNodeToUMNode(savitar_node, "file_name")
|
||||
scene_node = ThreeMFReader._convertSavitarNodeToUMNode(savitar_node, "file_name", scene=scene)
|
||||
if scene_node is None:
|
||||
continue
|
||||
nodes.append(scene_node)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ catalog = i18nCatalog("cura")
|
|||
|
||||
MODEL_PATH = "3D/3dmodel.model"
|
||||
PACKAGE_METADATA_PATH = "Cura/packages.json"
|
||||
TEXTURES_PATH = "3D/Textures"
|
||||
MODEL_RELATIONS_PATH = "3D/_rels/3dmodel.model.rels"
|
||||
|
||||
class ThreeMFWriter(MeshWriter):
|
||||
def __init__(self):
|
||||
|
|
@ -109,7 +111,11 @@ class ThreeMFWriter(MeshWriter):
|
|||
def _convertUMNodeToSavitarNode(um_node,
|
||||
transformation = Matrix(),
|
||||
exported_settings: Optional[Dict[str, Set[str]]] = None,
|
||||
center_mesh = False):
|
||||
center_mesh = False,
|
||||
scene: Savitar.Scene = None,
|
||||
archive: zipfile.ZipFile = None,
|
||||
model_relations_element: ET.Element = None,
|
||||
content_types_element: ET.Element = None):
|
||||
"""Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
|
||||
|
||||
:returns: Uranium Scene node.
|
||||
|
|
@ -150,7 +156,28 @@ class ThreeMFWriter(MeshWriter):
|
|||
if indices_array is not None:
|
||||
savitar_node.getMeshData().setFacesFromBytes(indices_array)
|
||||
else:
|
||||
savitar_node.getMeshData().setFacesFromBytes(numpy.arange(mesh_data.getVertices().size / 3, dtype=numpy.int32).tostring())
|
||||
savitar_node.getMeshData().setFacesFromBytes(numpy.arange(mesh_data.getVertices().size / 3, dtype=numpy.int32).tobytes())
|
||||
|
||||
packed_texture = um_node.callDecoration("packTexture")
|
||||
uv_coordinates_array = mesh_data.getUVCoordinatesAsByteArray()
|
||||
if packed_texture is not None and archive is not None and uv_coordinates_array is not None and len(uv_coordinates_array) > 0:
|
||||
texture_path = f"{TEXTURES_PATH}/{id(um_node)}.png"
|
||||
texture_file = zipfile.ZipInfo(texture_path)
|
||||
# Don't try to compress texture file, because the PNG is pretty much as compact as it will get
|
||||
archive.writestr(texture_file, packed_texture)
|
||||
|
||||
savitar_node.getMeshData().setUVCoordinatesPerVertexAsBytes(uv_coordinates_array, texture_path, scene)
|
||||
|
||||
# Add texture relation to model relations file
|
||||
if model_relations_element is not None:
|
||||
ET.SubElement(model_relations_element, "Relationship",
|
||||
Target=texture_path, Id=f"rel{len(model_relations_element)+1}",
|
||||
Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture")
|
||||
|
||||
if content_types_element is not None:
|
||||
ET.SubElement(content_types_element, "Override", PartName=texture_path,
|
||||
ContentType="application/vnd.ms-package.3dmanufacturing-3dmodeltexture")
|
||||
|
||||
|
||||
# Handle per object settings (if any)
|
||||
stack = um_node.callDecoration("getStack")
|
||||
|
|
@ -187,7 +214,11 @@ class ThreeMFWriter(MeshWriter):
|
|||
if child_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr:
|
||||
continue
|
||||
savitar_child_node = ThreeMFWriter._convertUMNodeToSavitarNode(child_node,
|
||||
exported_settings = exported_settings)
|
||||
exported_settings = exported_settings,
|
||||
scene = scene,
|
||||
archive = archive,
|
||||
model_relations_element = model_relations_element,
|
||||
content_types_element = content_types_element)
|
||||
if savitar_child_node is not None:
|
||||
savitar_node.addChild(savitar_child_node)
|
||||
|
||||
|
|
@ -249,6 +280,9 @@ class ThreeMFWriter(MeshWriter):
|
|||
# Create Metadata/_rels/model_settings.config.rels
|
||||
metadata_relations_element = self._makeRelationsTree()
|
||||
|
||||
# Create model relations
|
||||
model_relations_element = self._makeRelationsTree()
|
||||
|
||||
# Let the variant add its specific files
|
||||
variant.add_extra_files(archive, metadata_relations_element)
|
||||
|
||||
|
|
@ -320,13 +354,21 @@ class ThreeMFWriter(MeshWriter):
|
|||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(root_child,
|
||||
transformation_matrix,
|
||||
exported_model_settings,
|
||||
center_mesh = True)
|
||||
center_mesh = True,
|
||||
scene = savitar_scene,
|
||||
archive = archive,
|
||||
model_relations_element = model_relations_element,
|
||||
content_types_element = content_types)
|
||||
if savitar_node:
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
else:
|
||||
savitar_node = self._convertUMNodeToSavitarNode(node,
|
||||
transformation_matrix,
|
||||
exported_model_settings)
|
||||
exported_model_settings,
|
||||
scene = savitar_scene,
|
||||
archive = archive,
|
||||
model_relations_element = model_relations_element,
|
||||
content_types_element = content_types)
|
||||
if savitar_node:
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
|
||||
|
|
@ -338,6 +380,8 @@ class ThreeMFWriter(MeshWriter):
|
|||
self._storeElementTree(archive, "_rels/.rels", relations_element)
|
||||
if len(metadata_relations_element) > 0:
|
||||
self._storeElementTree(archive, "Metadata/_rels/model_settings.config.rels", metadata_relations_element)
|
||||
if len(model_relations_element) > 0:
|
||||
self._storeElementTree(archive, MODEL_RELATIONS_PATH, model_relations_element)
|
||||
except Exception as error:
|
||||
Logger.logException("e", "Error writing zip file")
|
||||
self.setInformation(str(error))
|
||||
|
|
@ -500,7 +544,7 @@ class ThreeMFWriter(MeshWriter):
|
|||
def sceneNodesToString(scene_nodes: [SceneNode]) -> str:
|
||||
savitar_scene = Savitar.Scene()
|
||||
for scene_node in scene_nodes:
|
||||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node, center_mesh = True)
|
||||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node, center_mesh = True, scene = savitar_scene)
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
parser = Savitar.ThreeMFParser()
|
||||
scene_string = parser.sceneToString(savitar_scene)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ message Object
|
|||
bytes indices = 4; //An array of ints.
|
||||
repeated Setting settings = 5; // Setting override per object, overruling the global settings.
|
||||
string name = 6; //Mesh name
|
||||
bytes uv_coordinates = 7; //An array of 2 floats.
|
||||
bytes texture = 8; //PNG-encoded texture data
|
||||
}
|
||||
|
||||
message Progress
|
||||
|
|
@ -78,10 +80,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)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import argparse #To run the engine in debug mode if the front-end is in debug mode.
|
||||
from cmath import isnan
|
||||
from collections import defaultdict
|
||||
import os
|
||||
from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtSlot
|
||||
|
|
@ -158,6 +159,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
|
||||
self._backend_log_max_lines: int = 20000 # Maximum number of lines to buffer
|
||||
self._error_message: Optional[Message] = None # Pop-up message that shows errors.
|
||||
self._unused_extruders: list[int] = [] # Extruder numbers of found unused extruders
|
||||
|
||||
# Count number of objects to see if there is something changed
|
||||
self._last_num_objects: Dict[int, int] = defaultdict(int)
|
||||
|
|
@ -960,12 +962,44 @@ class CuraEngineBackend(QObject, Backend):
|
|||
"""
|
||||
|
||||
material_amounts = []
|
||||
self._unused_extruders = []
|
||||
for index in range(message.repeatedMessageCount("materialEstimates")):
|
||||
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
|
||||
material_use_for_tool = message.getRepeatedMessage("materialEstimates", index).material_amount
|
||||
if isnan(material_use_for_tool):
|
||||
material_amounts.append(0.0)
|
||||
if self._global_container_stack.extruderList[int(index)].isEnabled:
|
||||
self._unused_extruders.append(index)
|
||||
else:
|
||||
material_amounts.append(material_use_for_tool)
|
||||
|
||||
if self._unused_extruders:
|
||||
extruder_names = [self._global_container_stack.extruderList[int(idx)].definition.getName() for idx in self._unused_extruders]
|
||||
unused_extruders = [f"<li>{extruder_name}</li>" for extruder_name in extruder_names]
|
||||
warning_message = Message(
|
||||
text=catalog.i18nc("@message", "<html>At least one extruder remains unused in this print:"
|
||||
f"<ul><b>{"".join(unused_extruders)}</b></ul><br/>This can sometimes become a problem, "
|
||||
"for example when the bed temperature is adjusted for the material present in the unused extruder. "
|
||||
"It might be desirable to disable these unused extruders.</html>"),
|
||||
title=catalog.i18nc("@message:title", "Unused Extruder(s)"),
|
||||
message_type=Message.MessageType.WARNING
|
||||
)
|
||||
warning_message.addAction("disable_extruders",
|
||||
name=catalog.i18nc("@button", "Disable unused extruder(s)"),
|
||||
icon="",
|
||||
description=catalog.i18nc("@label", "Automatically disable the unused extruder(s)")
|
||||
)
|
||||
warning_message.actionTriggered.connect(self._onMessageActionTriggered)
|
||||
warning_message.show()
|
||||
|
||||
times = self._parseMessagePrintTimes(message)
|
||||
self.printDurationMessage.emit(self._start_slice_job_build_plate, times, material_amounts)
|
||||
|
||||
def _onMessageActionTriggered(self, message: Message, message_action: str) -> None:
|
||||
if message_action == "disable_extruders":
|
||||
message.hide()
|
||||
for unused_extruder in self._unused_extruders:
|
||||
CuraApplication.getInstance().getMachineManager().setExtruderEnabled(unused_extruder, False)
|
||||
|
||||
def _parseMessagePrintTimes(self, message: Arcus.PythonMessage) -> Dict[str, float]:
|
||||
"""Called for parsing message to retrieve estimated time per feature
|
||||
|
||||
|
|
|
|||
|
|
@ -509,6 +509,14 @@ class StartSliceJob(Job):
|
|||
|
||||
obj.vertices = flat_verts
|
||||
|
||||
uv_coordinates = mesh_data.getUVCoordinates()
|
||||
if uv_coordinates is not None:
|
||||
obj.uv_coordinates = uv_coordinates.flatten()
|
||||
|
||||
packed_texture = object.callDecoration("packTexture")
|
||||
if packed_texture is not None:
|
||||
obj.texture = packed_texture
|
||||
|
||||
self._handlePerObjectSettings(cast(CuraSceneNode, object), obj)
|
||||
|
||||
Job.yieldThread()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
25
plugins/PaintTool/BrushColorButton.qml
Normal file
25
plugins/PaintTool/BrushColorButton.qml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2025 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick
|
||||
|
||||
import UM 1.7 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: buttonBrushColor
|
||||
|
||||
property string color
|
||||
|
||||
checked: base.selectedColor === buttonBrushColor.color
|
||||
|
||||
onClicked: setColor()
|
||||
|
||||
function setColor()
|
||||
{
|
||||
base.selectedColor = buttonBrushColor.color
|
||||
UM.Controller.triggerActionWithData("setBrushColor", buttonBrushColor.color)
|
||||
}
|
||||
}
|
||||
25
plugins/PaintTool/BrushShapeButton.qml
Normal file
25
plugins/PaintTool/BrushShapeButton.qml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2025 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick
|
||||
|
||||
import UM 1.7 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: buttonBrushShape
|
||||
|
||||
property int shape
|
||||
|
||||
checked: base.selectedShape === buttonBrushShape.shape
|
||||
|
||||
onClicked: setShape()
|
||||
|
||||
function setShape()
|
||||
{
|
||||
base.selectedShape = buttonBrushShape.shape
|
||||
UM.Controller.triggerActionWithData("setBrushShape", buttonBrushShape.shape)
|
||||
}
|
||||
}
|
||||
24
plugins/PaintTool/PaintModeButton.qml
Normal file
24
plugins/PaintTool/PaintModeButton.qml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2025 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick
|
||||
|
||||
import UM 1.7 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Cura.ModeSelectorButton
|
||||
{
|
||||
id: modeSelectorButton
|
||||
|
||||
property string mode
|
||||
|
||||
selected: base.selectedMode === modeSelectorButton.mode
|
||||
|
||||
onClicked: setMode()
|
||||
|
||||
function setMode()
|
||||
{
|
||||
base.selectedMode = modeSelectorButton.mode
|
||||
UM.Controller.triggerActionWithData("setPaintType", modeSelectorButton.mode)
|
||||
}
|
||||
}
|
||||
351
plugins/PaintTool/PaintTool.py
Normal file
351
plugins/PaintTool/PaintTool.py
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from enum import IntEnum
|
||||
import numpy
|
||||
from PyQt6.QtCore import Qt, QObject, pyqtEnum
|
||||
from PyQt6.QtGui import QImage, QPainter, QColor, QPen
|
||||
from PyQt6 import QtWidgets
|
||||
from typing import cast, Dict, List, Optional, Tuple
|
||||
|
||||
from numpy import ndarray
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Event import Event, MouseEvent, KeyEvent
|
||||
from UM.Logger import Logger
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Tool import Tool
|
||||
|
||||
from cura.PickingPass import PickingPass
|
||||
from .PaintView import PaintView
|
||||
|
||||
|
||||
class PaintTool(Tool):
|
||||
"""Provides the tool to paint meshes."""
|
||||
|
||||
class Brush(QObject):
|
||||
@pyqtEnum
|
||||
class Shape(IntEnum):
|
||||
SQUARE = 0
|
||||
CIRCLE = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._picking_pass: Optional[PickingPass] = None
|
||||
|
||||
self._shortcut_key: Qt.Key = Qt.Key.Key_P
|
||||
|
||||
self._node_cache: Optional[SceneNode] = None
|
||||
self._mesh_transformed_cache = None
|
||||
self._cache_dirty: bool = True
|
||||
|
||||
self._brush_size: int = 10
|
||||
self._brush_color: str = ""
|
||||
self._brush_shape: PaintTool.Brush.Shape = PaintTool.Brush.Shape.SQUARE
|
||||
self._brush_pen: QPen = self._createBrushPen()
|
||||
|
||||
self._mouse_held: bool = False
|
||||
|
||||
self._last_text_coords: Optional[numpy.ndarray] = None
|
||||
self._last_mouse_coords: Optional[Tuple[int, int]] = None
|
||||
self._last_face_id: Optional[int] = None
|
||||
|
||||
def _createBrushPen(self) -> QPen:
|
||||
pen = QPen()
|
||||
pen.setWidth(self._brush_size)
|
||||
pen.setColor(Qt.GlobalColor.white)
|
||||
|
||||
match self._brush_shape:
|
||||
case PaintTool.Brush.Shape.SQUARE:
|
||||
pen.setCapStyle(Qt.PenCapStyle.SquareCap)
|
||||
case PaintTool.Brush.Shape.CIRCLE:
|
||||
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
||||
return pen
|
||||
|
||||
def _createStrokeImage(self, x0: float, y0: float, x1: float, y1: float) -> Tuple[QImage, Tuple[int, int]]:
|
||||
xdiff = int(x1 - x0)
|
||||
ydiff = int(y1 - y0)
|
||||
|
||||
half_brush_size = self._brush_size // 2
|
||||
start_x = int(min(x0, x1) - half_brush_size)
|
||||
start_y = int(min(y0, y1) - half_brush_size)
|
||||
|
||||
stroke_image = QImage(abs(xdiff) + self._brush_size, abs(ydiff) + self._brush_size, QImage.Format.Format_RGB32)
|
||||
stroke_image.fill(0)
|
||||
|
||||
painter = QPainter(stroke_image)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setPen(self._brush_pen)
|
||||
if xdiff == 0 and ydiff == 0:
|
||||
painter.drawPoint(int(x0 - start_x), int(y0 - start_y))
|
||||
else:
|
||||
painter.drawLine(int(x0 - start_x), int(y0 - start_y), int(x1 - start_x), int(y1 - start_y))
|
||||
painter.end()
|
||||
|
||||
return stroke_image, (start_x, start_y)
|
||||
|
||||
def setPaintType(self, paint_type: str) -> None:
|
||||
paint_view = self._get_paint_view()
|
||||
if paint_view is None:
|
||||
return
|
||||
|
||||
paint_view.setPaintType(paint_type)
|
||||
|
||||
self._brush_pen = self._createBrushPen()
|
||||
self._updateScene()
|
||||
|
||||
def setBrushSize(self, brush_size: float) -> None:
|
||||
if brush_size != self._brush_size:
|
||||
self._brush_size = int(brush_size)
|
||||
self._brush_pen = self._createBrushPen()
|
||||
|
||||
def setBrushColor(self, brush_color: str) -> None:
|
||||
self._brush_color = brush_color
|
||||
|
||||
def setBrushShape(self, brush_shape: int) -> None:
|
||||
if brush_shape != self._brush_shape:
|
||||
self._brush_shape = brush_shape
|
||||
self._brush_pen = self._createBrushPen()
|
||||
|
||||
def undoStackAction(self, redo_instead: bool) -> bool:
|
||||
paint_view = self._get_paint_view()
|
||||
if paint_view is None:
|
||||
return False
|
||||
|
||||
if redo_instead:
|
||||
paint_view.redoStroke()
|
||||
else:
|
||||
paint_view.undoStroke()
|
||||
|
||||
self._updateScene()
|
||||
return True
|
||||
|
||||
def clear(self) -> None:
|
||||
paintview = self._get_paint_view()
|
||||
if paintview is None:
|
||||
return
|
||||
|
||||
width, height = paintview.getUvTexDimensions()
|
||||
clear_image = QImage(width, height, QImage.Format.Format_RGB32)
|
||||
clear_image.fill(Qt.GlobalColor.white)
|
||||
paintview.addStroke(clear_image, 0, 0, "none")
|
||||
|
||||
self._updateScene()
|
||||
|
||||
@staticmethod
|
||||
def _get_paint_view() -> Optional[PaintView]:
|
||||
paint_view = Application.getInstance().getController().getActiveView()
|
||||
if paint_view is None or paint_view.getPluginId() != "PaintTool":
|
||||
return None
|
||||
return cast(PaintView, paint_view)
|
||||
|
||||
@staticmethod
|
||||
def _get_intersect_ratio_via_pt(a: numpy.ndarray, pt: numpy.ndarray, b: numpy.ndarray, c: numpy.ndarray) -> float:
|
||||
# compute the intersection of (param) A - pt with (param) B - (param) C
|
||||
if all(a == pt) or all(b == c) or all(a == c) or all(a == b):
|
||||
return 1.0
|
||||
|
||||
# compute unit vectors of directions of lines A and B
|
||||
udir_a = a - pt
|
||||
udir_a /= numpy.linalg.norm(udir_a)
|
||||
udir_b = b - c
|
||||
udir_b /= numpy.linalg.norm(udir_b)
|
||||
|
||||
# find unit direction vector for line C, which is perpendicular to lines A and B
|
||||
udir_res = numpy.cross(udir_b, udir_a)
|
||||
udir_res_len = numpy.linalg.norm(udir_res)
|
||||
if udir_res_len == 0:
|
||||
return 1.0
|
||||
udir_res /= udir_res_len
|
||||
|
||||
# solve system of equations
|
||||
rhs = b - a
|
||||
lhs = numpy.array([udir_a, -udir_b, udir_res]).T
|
||||
try:
|
||||
solved = numpy.linalg.solve(lhs, rhs)
|
||||
except numpy.linalg.LinAlgError:
|
||||
return 1.0
|
||||
|
||||
# get the ratio
|
||||
intersect = ((a + solved[0] * udir_a) + (b + solved[1] * udir_b)) * 0.5
|
||||
a_intersect_dist = numpy.linalg.norm(a - intersect)
|
||||
if a_intersect_dist == 0:
|
||||
return 1.0
|
||||
return numpy.linalg.norm(pt - intersect) / a_intersect_dist
|
||||
|
||||
def _nodeTransformChanged(self, *args) -> None:
|
||||
self._cache_dirty = True
|
||||
|
||||
def _getTexCoordsFromClick(self, node: SceneNode, x: float, y: float) -> Tuple[int, Optional[numpy.ndarray]]:
|
||||
face_id = self._selection_pass.getFaceIdAtPosition(x, y)
|
||||
if face_id < 0 or face_id >= node.getMeshData().getFaceCount():
|
||||
return face_id, None
|
||||
|
||||
pt = self._picking_pass.getPickedPosition(x, y).getData()
|
||||
|
||||
va, vb, vc = self._mesh_transformed_cache.getFaceNodes(face_id)
|
||||
|
||||
face_uv_coordinates = node.getMeshData().getFaceUvCoords(face_id)
|
||||
if face_uv_coordinates is None:
|
||||
return face_id, None
|
||||
ta, tb, tc = face_uv_coordinates
|
||||
|
||||
# 'Weight' of each vertex that would produce point pt, so we can generate the texture coordinates from the uv ones of the vertices.
|
||||
# See (also) https://mathworld.wolfram.com/BarycentricCoordinates.html
|
||||
wa = PaintTool._get_intersect_ratio_via_pt(va, pt, vb, vc)
|
||||
wb = PaintTool._get_intersect_ratio_via_pt(vb, pt, vc, va)
|
||||
wc = PaintTool._get_intersect_ratio_via_pt(vc, pt, va, vb)
|
||||
wt = wa + wb + wc
|
||||
if wt == 0:
|
||||
return face_id, None
|
||||
wa /= wt
|
||||
wb /= wt
|
||||
wc /= wt
|
||||
texcoords = wa * ta + wb * tb + wc * tc
|
||||
return face_id, texcoords
|
||||
|
||||
def _iteratateSplitSubstroke(self, node, substrokes,
|
||||
info_a: Tuple[Tuple[float, float], Tuple[int, Optional[numpy.ndarray]]],
|
||||
info_b: Tuple[Tuple[float, float], Tuple[int, Optional[numpy.ndarray]]]) -> None:
|
||||
click_a, (face_a, texcoords_a) = info_a
|
||||
click_b, (face_b, texcoords_b) = info_b
|
||||
|
||||
if (abs(click_a[0] - click_b[0]) < 0.0001 and abs(click_a[1] - click_b[1]) < 0.0001) or (face_a < 0 and face_b < 0):
|
||||
return
|
||||
if face_b < 0 or face_a == face_b:
|
||||
substrokes.append((self._last_text_coords, texcoords_a))
|
||||
return
|
||||
if face_a < 0:
|
||||
substrokes.append((self._last_text_coords, texcoords_b))
|
||||
return
|
||||
|
||||
mouse_mid = (click_a[0] + click_b[0]) / 2.0, (click_a[1] + click_b[1]) / 2.0
|
||||
face_mid, texcoords_mid = self._getTexCoordsFromClick(node, mouse_mid[0], mouse_mid[1])
|
||||
mid_struct = (mouse_mid, (face_mid, texcoords_mid))
|
||||
if face_mid == face_a:
|
||||
substrokes.append((texcoords_a, texcoords_mid))
|
||||
self._iteratateSplitSubstroke(node, substrokes, mid_struct, info_b)
|
||||
elif face_mid == face_b:
|
||||
substrokes.append((texcoords_mid, texcoords_b))
|
||||
self._iteratateSplitSubstroke(node, substrokes, info_a, mid_struct)
|
||||
else:
|
||||
self._iteratateSplitSubstroke(node, substrokes, mid_struct, info_b)
|
||||
self._iteratateSplitSubstroke(node, substrokes, info_a, mid_struct)
|
||||
|
||||
def event(self, event: Event) -> bool:
|
||||
"""Handle mouse and keyboard events.
|
||||
|
||||
:param event: The event to handle.
|
||||
:return: Whether this event has been caught by this tool (True) or should
|
||||
be passed on (False).
|
||||
"""
|
||||
super().event(event)
|
||||
|
||||
controller = Application.getInstance().getController()
|
||||
node = Selection.getSelectedObject(0)
|
||||
if node is None:
|
||||
return False
|
||||
|
||||
# Make sure the displayed values are updated if the bounding box of the selected mesh(es) changes
|
||||
if event.type == Event.ToolActivateEvent:
|
||||
controller.setActiveStage("PrepareStage")
|
||||
controller.setActiveView("PaintTool") # Because that's the plugin-name, and the view is registered to it.
|
||||
return True
|
||||
|
||||
if event.type == Event.ToolDeactivateEvent:
|
||||
controller.setActiveStage("PrepareStage")
|
||||
controller.setActiveView("SolidView")
|
||||
return True
|
||||
|
||||
if event.type == Event.MouseReleaseEvent and self._controller.getToolsEnabled():
|
||||
if MouseEvent.LeftButton not in cast(MouseEvent, event).buttons:
|
||||
return False
|
||||
self._mouse_held = False
|
||||
self._last_text_coords = None
|
||||
self._last_mouse_coords = None
|
||||
self._last_face_id = None
|
||||
return True
|
||||
|
||||
is_moved = event.type == Event.MouseMoveEvent
|
||||
is_pressed = event.type == Event.MousePressEvent
|
||||
if (is_moved or is_pressed) and self._controller.getToolsEnabled():
|
||||
if is_moved and not self._mouse_held:
|
||||
return False
|
||||
|
||||
mouse_evt = cast(MouseEvent, event)
|
||||
if is_pressed:
|
||||
if MouseEvent.LeftButton not in mouse_evt.buttons:
|
||||
return False
|
||||
else:
|
||||
self._mouse_held = True
|
||||
|
||||
paintview = self._get_paint_view()
|
||||
if paintview is None:
|
||||
return False
|
||||
|
||||
if not self._selection_pass:
|
||||
return False
|
||||
|
||||
camera = self._controller.getScene().getActiveCamera()
|
||||
if not camera:
|
||||
return False
|
||||
|
||||
if node != self._node_cache:
|
||||
if self._node_cache is not None:
|
||||
self._node_cache.transformationChanged.disconnect(self._nodeTransformChanged)
|
||||
self._node_cache = node
|
||||
self._node_cache.transformationChanged.connect(self._nodeTransformChanged)
|
||||
if self._cache_dirty:
|
||||
self._cache_dirty = False
|
||||
self._mesh_transformed_cache = self._node_cache.getMeshDataTransformed()
|
||||
if not self._mesh_transformed_cache:
|
||||
return False
|
||||
|
||||
if not self._picking_pass:
|
||||
self._picking_pass = PickingPass(camera.getViewportWidth(), camera.getViewportHeight())
|
||||
self._picking_pass.render()
|
||||
|
||||
self._selection_pass.renderFacesMode()
|
||||
|
||||
face_id, texcoords = self._getTexCoordsFromClick(node, mouse_evt.x, mouse_evt.y)
|
||||
if texcoords is None:
|
||||
return False
|
||||
if self._last_text_coords is None:
|
||||
self._last_text_coords = texcoords
|
||||
self._last_mouse_coords = (mouse_evt.x, mouse_evt.y)
|
||||
self._last_face_id = face_id
|
||||
|
||||
substrokes = []
|
||||
if face_id == self._last_face_id:
|
||||
substrokes.append((self._last_text_coords, texcoords))
|
||||
else:
|
||||
self._iteratateSplitSubstroke(node, substrokes,
|
||||
(self._last_mouse_coords, (self._last_face_id, self._last_text_coords)),
|
||||
((mouse_evt.x, mouse_evt.y), (face_id, texcoords)))
|
||||
|
||||
w, h = paintview.getUvTexDimensions()
|
||||
for start_coords, end_coords in substrokes:
|
||||
sub_image, (start_x, start_y) = self._createStrokeImage(
|
||||
start_coords[0] * w,
|
||||
start_coords[1] * h,
|
||||
end_coords[0] * w,
|
||||
end_coords[1] * h
|
||||
)
|
||||
paintview.addStroke(sub_image, start_x, start_y, self._brush_color)
|
||||
|
||||
self._last_text_coords = texcoords
|
||||
self._last_mouse_coords = (mouse_evt.x, mouse_evt.y)
|
||||
self._last_face_id = face_id
|
||||
self._updateScene(node)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _updateScene(node: SceneNode = None):
|
||||
if node is None:
|
||||
node = Selection.getSelectedObject(0)
|
||||
if node is not None:
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
243
plugins/PaintTool/PaintTool.qml
Normal file
243
plugins/PaintTool/PaintTool.qml
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
// Copyright (c) 2025 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import UM 1.7 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
UM.I18nCatalog { id: catalog; name: "cura"}
|
||||
|
||||
property string selectedMode: ""
|
||||
property string selectedColor: ""
|
||||
property int selectedShape: 0
|
||||
|
||||
Action
|
||||
{
|
||||
id: undoAction
|
||||
shortcut: "Ctrl+L"
|
||||
onTriggered: UM.Controller.triggerActionWithData("undoStackAction", false)
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: redoAction
|
||||
shortcut: "Ctrl+Shift+L"
|
||||
onTriggered: UM.Controller.triggerActionWithData("undoStackAction", true)
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: mainColumn
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: rowPaintMode
|
||||
width: parent.width
|
||||
|
||||
PaintModeButton
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Seam")
|
||||
icon: "Seam"
|
||||
tooltipText: catalog.i18nc("@tooltip", "Refine seam placement by defining preferred/avoidance areas")
|
||||
mode: "seam"
|
||||
}
|
||||
|
||||
PaintModeButton
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Support")
|
||||
icon: "Support"
|
||||
tooltipText: catalog.i18nc("@tooltip", "Refine support placement by defining preferred/avoidance areas")
|
||||
mode: "support"
|
||||
}
|
||||
}
|
||||
|
||||
//Line between the sections.
|
||||
Rectangle
|
||||
{
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
color: UM.Theme.getColor("lining")
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: rowBrushColor
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Mark as")
|
||||
}
|
||||
|
||||
BrushColorButton
|
||||
{
|
||||
id: buttonPreferredArea
|
||||
color: "preferred"
|
||||
|
||||
text: catalog.i18nc("@action:button", "Preferred")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("CheckBadge", "low")
|
||||
color: UM.Theme.getColor("paint_preferred_area")
|
||||
}
|
||||
}
|
||||
|
||||
BrushColorButton
|
||||
{
|
||||
id: buttonAvoidArea
|
||||
color: "avoid"
|
||||
|
||||
text: catalog.i18nc("@action:button", "Avoid")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("CancelBadge", "low")
|
||||
color: UM.Theme.getColor("paint_avoid_area")
|
||||
}
|
||||
}
|
||||
|
||||
BrushColorButton
|
||||
{
|
||||
id: buttonEraseArea
|
||||
color: "none"
|
||||
|
||||
text: catalog.i18nc("@action:button", "Erase")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("Eraser")
|
||||
color: UM.Theme.getColor("icon")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: rowBrushShape
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Brush Shape")
|
||||
}
|
||||
|
||||
BrushShapeButton
|
||||
{
|
||||
id: buttonBrushCircle
|
||||
shape: Cura.PaintToolBrush.CIRCLE
|
||||
|
||||
text: catalog.i18nc("@action:button", "Circle")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("Circle")
|
||||
color: UM.Theme.getColor("icon")
|
||||
}
|
||||
}
|
||||
|
||||
BrushShapeButton
|
||||
{
|
||||
id: buttonBrushSquare
|
||||
shape: Cura.PaintToolBrush.SQUARE
|
||||
|
||||
text: catalog.i18nc("@action:button", "Square")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("MeshTypeNormal")
|
||||
color: UM.Theme.getColor("icon")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Brush Size")
|
||||
}
|
||||
|
||||
UM.Slider
|
||||
{
|
||||
id: shapeSizeSlider
|
||||
width: parent.width
|
||||
indicatorVisible: false
|
||||
|
||||
from: 10
|
||||
to: 1000
|
||||
value: 200
|
||||
|
||||
onPressedChanged: function(pressed)
|
||||
{
|
||||
if(! pressed)
|
||||
{
|
||||
setBrushSize()
|
||||
}
|
||||
}
|
||||
|
||||
function setBrushSize()
|
||||
{
|
||||
UM.Controller.triggerActionWithData("setBrushSize", shapeSizeSlider.value)
|
||||
}
|
||||
}
|
||||
|
||||
//Line between the sections.
|
||||
Rectangle
|
||||
{
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
color: UM.Theme.getColor("lining")
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: undoButton
|
||||
|
||||
text: catalog.i18nc("@action:button", "Undo Stroke")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("ArrowReset")
|
||||
color: UM.Theme.getColor("icon")
|
||||
}
|
||||
|
||||
onClicked: undoAction.trigger()
|
||||
}
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: redoButton
|
||||
|
||||
text: catalog.i18nc("@action:button", "Redo Stroke")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("ArrowReset")
|
||||
color: UM.Theme.getColor("icon")
|
||||
transform: [
|
||||
Scale { xScale: -1; origin.x: width/2 }
|
||||
]
|
||||
}
|
||||
|
||||
onClicked: redoAction.trigger()
|
||||
}
|
||||
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
id: clearButton
|
||||
text: catalog.i18nc("@button", "Clear all")
|
||||
onClicked: UM.Controller.triggerAction("clear")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
// Force first types for consistency, otherwise UI may become different from controller
|
||||
rowPaintMode.children[0].setMode()
|
||||
rowBrushColor.children[1].setColor()
|
||||
rowBrushShape.children[1].setShape()
|
||||
shapeSizeSlider.setBrushSize()
|
||||
}
|
||||
}
|
||||
196
plugins/PaintTool/PaintView.py
Normal file
196
plugins/PaintTool/PaintView.py
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from PyQt6.QtCore import QRect
|
||||
from typing import Optional, List, Tuple, Dict
|
||||
|
||||
from PyQt6.QtGui import QImage, QColor, QPainter
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||
from UM.View.GL.Texture import Texture
|
||||
from UM.View.View import View
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Math.Color import Color
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class PaintView(View):
|
||||
"""View for model-painting."""
|
||||
|
||||
UNDO_STACK_SIZE = 1024
|
||||
|
||||
class PaintType:
|
||||
def __init__(self, display_color: Color, value: int):
|
||||
self.display_color: Color = display_color
|
||||
self.value: int = value
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._paint_shader: Optional[ShaderProgram] = None
|
||||
self._current_paint_texture: Optional[Texture] = None
|
||||
self._current_bits_ranges: tuple[int, int] = (0, 0)
|
||||
self._current_paint_type = ""
|
||||
self._paint_modes: Dict[str, Dict[str, "PaintView.PaintType"]] = {}
|
||||
|
||||
self._stroke_undo_stack: List[Tuple[QImage, int, int]] = []
|
||||
self._stroke_redo_stack: List[Tuple[QImage, int, int]] = []
|
||||
|
||||
self._force_opaque_mask = QImage(2, 2, QImage.Format.Format_Mono)
|
||||
self._force_opaque_mask.fill(1)
|
||||
|
||||
CuraApplication.getInstance().engineCreatedSignal.connect(self._makePaintModes)
|
||||
|
||||
def _makePaintModes(self):
|
||||
theme = CuraApplication.getInstance().getTheme()
|
||||
usual_types = {"none": self.PaintType(Color(*theme.getColor("paint_normal_area").getRgb()), 0),
|
||||
"preferred": self.PaintType(Color(*theme.getColor("paint_preferred_area").getRgb()), 1),
|
||||
"avoid": self.PaintType(Color(*theme.getColor("paint_avoid_area").getRgb()), 2)}
|
||||
self._paint_modes = {
|
||||
"seam": usual_types,
|
||||
"support": usual_types,
|
||||
}
|
||||
|
||||
def _checkSetup(self):
|
||||
if not self._paint_shader:
|
||||
shader_filename = os.path.join(PluginRegistry.getInstance().getPluginPath("PaintTool"), "paint.shader")
|
||||
self._paint_shader = OpenGL.getInstance().createShaderProgram(shader_filename)
|
||||
|
||||
def _forceOpaqueDeepCopy(self, image: QImage):
|
||||
res = QImage(image.width(), image.height(), QImage.Format.Format_RGBA8888)
|
||||
res.fill(QColor(255, 255, 255, 255))
|
||||
painter = QPainter(res)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
|
||||
painter.drawImage(0, 0, image)
|
||||
painter.end()
|
||||
res.setAlphaChannel(self._force_opaque_mask.scaled(image.width(), image.height()))
|
||||
return res
|
||||
|
||||
def addStroke(self, stroke_mask: QImage, start_x: int, start_y: int, brush_color: str) -> None:
|
||||
if self._current_paint_texture is None or self._current_paint_texture.getImage() is None:
|
||||
return
|
||||
|
||||
actual_image = self._current_paint_texture.getImage()
|
||||
|
||||
bit_range_start, bit_range_end = self._current_bits_ranges
|
||||
set_value = self._paint_modes[self._current_paint_type][brush_color].value << self._current_bits_ranges[0]
|
||||
full_int32 = 0xffffffff
|
||||
clear_mask = full_int32 ^ (((full_int32 << (32 - 1 - (bit_range_end - bit_range_start))) & full_int32) >> (32 - 1 - bit_range_end))
|
||||
image_rect = QRect(0, 0, stroke_mask.width(), stroke_mask.height())
|
||||
|
||||
clear_bits_image = stroke_mask.copy()
|
||||
clear_bits_image.invertPixels()
|
||||
painter = QPainter(clear_bits_image)
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Lighten)
|
||||
painter.fillRect(image_rect, clear_mask)
|
||||
painter.end()
|
||||
|
||||
set_value_image = stroke_mask.copy()
|
||||
painter = QPainter(set_value_image)
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Multiply)
|
||||
painter.fillRect(image_rect, set_value)
|
||||
painter.end()
|
||||
|
||||
stroked_image = actual_image.copy(start_x, start_y, stroke_mask.width(), stroke_mask.height())
|
||||
painter = QPainter(stroked_image)
|
||||
painter.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceAndDestination)
|
||||
painter.drawImage(0, 0, clear_bits_image)
|
||||
painter.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceOrDestination)
|
||||
painter.drawImage(0, 0, set_value_image)
|
||||
painter.end()
|
||||
|
||||
self._stroke_redo_stack.clear()
|
||||
if len(self._stroke_undo_stack) >= PaintView.UNDO_STACK_SIZE:
|
||||
self._stroke_undo_stack.pop(0)
|
||||
undo_image = self._forceOpaqueDeepCopy(self._current_paint_texture.setSubImage(stroked_image, start_x, start_y))
|
||||
if undo_image is not None:
|
||||
self._stroke_undo_stack.append((undo_image, start_x, start_y))
|
||||
|
||||
def _applyUndoStacksAction(self, from_stack: List[Tuple[QImage, int, int]], to_stack: List[Tuple[QImage, int, int]]) -> bool:
|
||||
if len(from_stack) <= 0 or self._current_paint_texture is None:
|
||||
return False
|
||||
from_image, x, y = from_stack.pop()
|
||||
to_image = self._forceOpaqueDeepCopy(self._current_paint_texture.setSubImage(from_image, x, y))
|
||||
if to_image is None:
|
||||
return False
|
||||
if len(to_stack) >= PaintView.UNDO_STACK_SIZE:
|
||||
to_stack.pop(0)
|
||||
to_stack.append((to_image, x, y))
|
||||
return True
|
||||
|
||||
def undoStroke(self) -> bool:
|
||||
return self._applyUndoStacksAction(self._stroke_undo_stack, self._stroke_redo_stack)
|
||||
|
||||
def redoStroke(self) -> bool:
|
||||
return self._applyUndoStacksAction(self._stroke_redo_stack, self._stroke_undo_stack)
|
||||
|
||||
def getUvTexDimensions(self):
|
||||
if self._current_paint_texture is not None:
|
||||
return self._current_paint_texture.getWidth(), self._current_paint_texture.getHeight()
|
||||
return 0, 0
|
||||
|
||||
def setPaintType(self, paint_type: str) -> None:
|
||||
node = Selection.getAllSelectedObjects()[0]
|
||||
if node is None:
|
||||
return
|
||||
|
||||
paint_data_mapping = node.callDecoration("getTextureDataMapping")
|
||||
|
||||
if paint_type not in paint_data_mapping:
|
||||
new_mapping = self._add_mapping(paint_data_mapping, len(self._paint_modes[paint_type]))
|
||||
paint_data_mapping[paint_type] = new_mapping
|
||||
node.callDecoration("setTextureDataMapping", paint_data_mapping)
|
||||
|
||||
mesh = node.getMeshData()
|
||||
if not mesh.hasUVCoordinates():
|
||||
texture_width, texture_height = mesh.calculateUnwrappedUVCoordinates()
|
||||
if texture_width > 0 and texture_height > 0:
|
||||
node.callDecoration("prepareTexture", texture_width, texture_height)
|
||||
|
||||
if hasattr(mesh, OpenGL.VertexBufferProperty):
|
||||
# Force clear OpenGL buffer so that new UV coordinates will be sent
|
||||
delattr(mesh, OpenGL.VertexBufferProperty)
|
||||
|
||||
self._current_paint_type = paint_type
|
||||
self._current_bits_ranges = paint_data_mapping[paint_type]
|
||||
|
||||
@staticmethod
|
||||
def _add_mapping(actual_mapping: Dict[str, tuple[int, int]], nb_storable_values: int) -> tuple[int, int]:
|
||||
start_index = 0
|
||||
if actual_mapping:
|
||||
start_index = max(end_index for _, end_index in actual_mapping.values()) + 1
|
||||
|
||||
end_index = start_index + int.bit_length(nb_storable_values - 1) - 1
|
||||
|
||||
return start_index, end_index
|
||||
|
||||
def beginRendering(self) -> None:
|
||||
renderer = self.getRenderer()
|
||||
self._checkSetup()
|
||||
paint_batch = renderer.createRenderBatch(shader=self._paint_shader)
|
||||
renderer.addRenderBatch(paint_batch)
|
||||
|
||||
node = Selection.getSelectedObject(0)
|
||||
if node is None:
|
||||
return
|
||||
|
||||
if self._current_paint_type == "":
|
||||
return
|
||||
|
||||
self._paint_shader.setUniformValue("u_bitsRangesStart", self._current_bits_ranges[0])
|
||||
self._paint_shader.setUniformValue("u_bitsRangesEnd", self._current_bits_ranges[1])
|
||||
|
||||
colors = [paint_type_obj.display_color for paint_type_obj in self._paint_modes[self._current_paint_type].values()]
|
||||
colors_values = [[int(color_part * 255) for color_part in [color.r, color.g, color.b]] for color in colors]
|
||||
self._paint_shader.setUniformValueArray("u_renderColors", colors_values)
|
||||
|
||||
self._current_paint_texture = node.callDecoration("getPaintTexture")
|
||||
self._paint_shader.setTexture(0, self._current_paint_texture)
|
||||
|
||||
paint_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix())
|
||||
33
plugins/PaintTool/__init__.py
Normal file
33
plugins/PaintTool/__init__.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import PaintTool
|
||||
from . import PaintView
|
||||
|
||||
from PyQt6.QtQml import qmlRegisterUncreatableType
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"tool": {
|
||||
"name": i18n_catalog.i18nc("@action:button", "Paint"),
|
||||
"description": i18n_catalog.i18nc("@info:tooltip", "Paint Model"),
|
||||
"icon": "Visual",
|
||||
"tool_panel": "PaintTool.qml",
|
||||
"weight": 0
|
||||
},
|
||||
"view": {
|
||||
"name": i18n_catalog.i18nc("@item:inmenu", "Paint view"),
|
||||
"weight": 0,
|
||||
"visible": False
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
qmlRegisterUncreatableType(PaintTool.PaintTool.Brush, "Cura", 1, 0, "This is an enumeration class", "PaintToolBrush")
|
||||
return {
|
||||
"tool": PaintTool.PaintTool(),
|
||||
"view": PaintView.PaintView()
|
||||
}
|
||||
149
plugins/PaintTool/paint.shader
Normal file
149
plugins/PaintTool/paint.shader
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
[shaders]
|
||||
vertex =
|
||||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewMatrix;
|
||||
uniform highp mat4 u_projectionMatrix;
|
||||
|
||||
uniform highp mat4 u_normalMatrix;
|
||||
|
||||
attribute highp vec4 a_vertex;
|
||||
attribute highp vec4 a_normal;
|
||||
attribute highp vec2 a_uvs;
|
||||
|
||||
varying highp vec3 v_vertex;
|
||||
varying highp vec3 v_normal;
|
||||
varying highp vec2 v_uvs;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 world_space_vert = u_modelMatrix * a_vertex;
|
||||
gl_Position = u_projectionMatrix * u_viewMatrix * world_space_vert;
|
||||
|
||||
v_vertex = world_space_vert.xyz;
|
||||
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
|
||||
|
||||
v_uvs = a_uvs;
|
||||
}
|
||||
|
||||
fragment =
|
||||
uniform mediump vec4 u_ambientColor;
|
||||
uniform highp vec3 u_lightPosition;
|
||||
uniform highp vec3 u_viewPosition;
|
||||
uniform mediump float u_opacity;
|
||||
uniform sampler2D u_texture;
|
||||
uniform mediump int u_bitsRangesStart;
|
||||
uniform mediump int u_bitsRangesEnd;
|
||||
uniform mediump vec3 u_renderColors[16];
|
||||
|
||||
varying highp vec3 v_vertex;
|
||||
varying highp vec3 v_normal;
|
||||
varying highp vec2 v_uvs;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 final_color = vec4(0.0);
|
||||
|
||||
/* Ambient Component */
|
||||
final_color += u_ambientColor;
|
||||
|
||||
highp vec3 normal = normalize(v_normal);
|
||||
highp vec3 light_dir = normalize(u_lightPosition - v_vertex);
|
||||
|
||||
/* Diffuse Component */
|
||||
ivec4 texture = ivec4(texture(u_texture, v_uvs) * 255.0);
|
||||
uint color_index = (texture.r << 16) | (texture.g << 8) | texture.b;
|
||||
color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart);
|
||||
|
||||
vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0);
|
||||
highp float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0);
|
||||
final_color += (n_dot_l * diffuse_color);
|
||||
|
||||
final_color.a = u_opacity;
|
||||
|
||||
frag_color = final_color;
|
||||
}
|
||||
|
||||
vertex41core =
|
||||
#version 410
|
||||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewMatrix;
|
||||
uniform highp mat4 u_projectionMatrix;
|
||||
|
||||
uniform highp mat4 u_normalMatrix;
|
||||
|
||||
in highp vec4 a_vertex;
|
||||
in highp vec4 a_normal;
|
||||
in highp vec2 a_uvs;
|
||||
|
||||
out highp vec3 v_vertex;
|
||||
out highp vec3 v_normal;
|
||||
out highp vec2 v_uvs;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 world_space_vert = u_modelMatrix * a_vertex;
|
||||
gl_Position = u_projectionMatrix * u_viewMatrix * world_space_vert;
|
||||
|
||||
v_vertex = world_space_vert.xyz;
|
||||
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
|
||||
|
||||
v_uvs = a_uvs;
|
||||
}
|
||||
|
||||
fragment41core =
|
||||
#version 410
|
||||
uniform mediump vec4 u_ambientColor;
|
||||
uniform highp vec3 u_lightPosition;
|
||||
uniform highp vec3 u_viewPosition;
|
||||
uniform mediump float u_opacity;
|
||||
uniform sampler2D u_texture;
|
||||
uniform mediump int u_bitsRangesStart;
|
||||
uniform mediump int u_bitsRangesEnd;
|
||||
uniform mediump vec3 u_renderColors[16];
|
||||
|
||||
in highp vec3 v_vertex;
|
||||
in highp vec3 v_normal;
|
||||
in highp vec2 v_uvs;
|
||||
out vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 final_color = vec4(0.0);
|
||||
|
||||
/* Ambient Component */
|
||||
final_color += u_ambientColor;
|
||||
|
||||
highp vec3 normal = normalize(v_normal);
|
||||
highp vec3 light_dir = normalize(u_lightPosition - v_vertex);
|
||||
|
||||
/* Diffuse Component */
|
||||
ivec4 texture = ivec4(texture(u_texture, v_uvs) * 255.0);
|
||||
uint color_index = (texture.r << 16) | (texture.g << 8) | texture.b;
|
||||
color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart);
|
||||
|
||||
vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0);
|
||||
highp float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0);
|
||||
final_color += (n_dot_l * diffuse_color);
|
||||
|
||||
final_color.a = u_opacity;
|
||||
|
||||
frag_color = final_color;
|
||||
}
|
||||
|
||||
[defaults]
|
||||
u_ambientColor = [0.3, 0.3, 0.3, 1.0]
|
||||
u_opacity = 0.5
|
||||
u_texture = 0
|
||||
|
||||
[bindings]
|
||||
u_modelMatrix = model_matrix
|
||||
u_viewMatrix = view_matrix
|
||||
u_projectionMatrix = projection_matrix
|
||||
u_normalMatrix = normal_matrix
|
||||
u_lightPosition = light_0_position
|
||||
u_viewPosition = camera_position
|
||||
|
||||
[attributes]
|
||||
a_vertex = vertex
|
||||
a_normal = normal
|
||||
a_uvs = uv0
|
||||
8
plugins/PaintTool/plugin.json
Normal file
8
plugins/PaintTool/plugin.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Paint Tools",
|
||||
"author": "UltiMaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the paint tools.",
|
||||
"api": 8,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -1,16 +1,21 @@
|
|||
# Designed in January 2023 by GregValiant (Greg Foresi)
|
||||
## My design intent was to make this as full featured and "industrial strength" as I could. People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print. This script allows that.
|
||||
# Functions:
|
||||
## Remove all fan speed lines from the file (optional). This should be enabled for the first instance of the script. It is disabled by default in any following instances.
|
||||
## "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print. "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.).
|
||||
## If 'By Feature' then a Start Layer and/or an End Layer can be defined.
|
||||
## Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}.
|
||||
## A minimum fan speed of 12% is enforced. It is the slowest speed that my cooling fan will turn on so that's what I used. 'M106 S14' (as Cura might insert) was pretty useless.
|
||||
## If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting. There is support for up to 4 layer cooling fan circuits.
|
||||
## My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir. A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script.
|
||||
## 9/14/23 (Greg Foresi) Added support for One-at-a-Time print sequence.
|
||||
## 12/15/23 (Greg Foresi) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
|
||||
## 1/5/24 (Greg Foresi) Revised the regex replacements.
|
||||
"""
|
||||
Designed in January 2023 by GregValiant (Greg Foresi)
|
||||
My design intent was to make this as full featured and "industrial strength" as I could. People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print. This script allows that.
|
||||
Functions:
|
||||
Remove all fan speed lines from the file (optional). This should be enabled for the first instance of the script. It is disabled by default in any following instances.
|
||||
"By Layer" allows the user to adjust the fan speed up, or down, or off, within the print. "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.).
|
||||
If 'By Feature' then a Start Layer and/or an End Layer can be defined.
|
||||
Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}.
|
||||
A minimum fan speed of 12% is enforced. It is the slowest speed that my cooling fan will turn on so that's what I used. 'M106 S14' (as Cura might insert) was pretty useless.
|
||||
If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting. There is support for up to 4 layer cooling fan circuits.
|
||||
My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir. A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script.
|
||||
Changes:
|
||||
09/14/23 (GV) Added support for One-at-a-Time print sequence.
|
||||
12/15/23 (GV) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
|
||||
01/05/24 (GV) Revised the regex replacements.
|
||||
12/11/24 (GV) Added 'off_fan_speed' for the idle nozzle layer cooling fan. It does not have to go to 0%.
|
||||
03/22/25 (GV) Added 'Chamber Cooling Fan / Auxiliary Fan' control.
|
||||
"""
|
||||
|
||||
from ..Script import Script
|
||||
from UM.Application import Application
|
||||
|
|
@ -43,7 +48,8 @@ class AddCoolingProfile(Script):
|
|||
"type": "bool",
|
||||
"enabled": true,
|
||||
"value": true,
|
||||
"default_value": true
|
||||
"default_value": true,
|
||||
"read_only": true
|
||||
},
|
||||
"feature_fan_start_layer":
|
||||
{
|
||||
|
|
@ -273,67 +279,180 @@ class AddCoolingProfile(Script):
|
|||
"maximum_value": 100,
|
||||
"unit": "% ",
|
||||
"enabled": "fan_enable_raft"
|
||||
},
|
||||
"enable_off_fan_speed":
|
||||
{
|
||||
"label": "Enable 'Off speed' of the idle fan",
|
||||
"description": "For machines with independent layer cooling fans. Leaving a fan running while the other nozzle is printing can help with oozing. You can pick the speed % for the idle nozzle layer cooling fan to hold at.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "enable_off_fan_speed_enable and self.extruder_count > 1"
|
||||
},
|
||||
"off_fan_speed":
|
||||
{
|
||||
"label": " 'Off' speed of idle nozzle fan",
|
||||
"description": "This is the speed that the 'idle nozzle' layer cooling fan will maintain rather than being turned off completely.",
|
||||
"type": "int",
|
||||
"default_value": 35,
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 100,
|
||||
"unit": "% ",
|
||||
"enabled": "enable_off_fan_speed_enable and enable_off_fan_speed and self.extruder_count > 1"
|
||||
},
|
||||
"enable_off_fan_speed_enable":
|
||||
{
|
||||
"label": "Hidden setting",
|
||||
"description": "For dual extruder printers, this enables 'enable_off_fan_speed'.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": false
|
||||
},
|
||||
"bv_fan_speed_control_enable":
|
||||
{
|
||||
"label": "Enable 'Chamber/Aux Fan' control",
|
||||
"description": "Controls the 'Build Volume Fan' or an 'Auxiliary Fan' on printers with that hardware. Provides: 'On' layer, 'Off' layer, and PWM speed control of a secondary fan.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "enable_bv_fan"
|
||||
},
|
||||
"bv_fan_nr":
|
||||
{
|
||||
"label": " Chamber/Aux Fan Number",
|
||||
"description": "The mainboard circuit number of the Chamber or Auxiliary Fan.",
|
||||
"type": "int",
|
||||
"unit": "# ",
|
||||
"default_value": 0,
|
||||
"minimum_value": 0,
|
||||
"enabled": "enable_bv_fan and bv_fan_speed_control_enable"
|
||||
},
|
||||
"bv_fan_speed":
|
||||
{
|
||||
"label": " Chamber/Aux Fan Speed %",
|
||||
"description": "The speed of the Chamber or Auxiliary Fan. This will be converted to PWM Duty Cycle (0-255) or (RepRap 0-1 if that is enabled in Cura). If your specified fan does not operate on variable speeds then set this to '100'.",
|
||||
"type": "int",
|
||||
"unit": "% ",
|
||||
"default_value": 50,
|
||||
"maximum_value": 100,
|
||||
"minimum_value": 0,
|
||||
"enabled": "enable_bv_fan and bv_fan_speed_control_enable"
|
||||
},
|
||||
"bv_fan_start_layer":
|
||||
{
|
||||
"label": " Chamber/Aux Fan Start Layer",
|
||||
"description": "The layer to start the Chamber or Auxiliary Fan. Use the Cura preview layer number and the fan will start at the beginning of the layer.",
|
||||
"type": "int",
|
||||
"unit": "Layer# ",
|
||||
"default_value": 1,
|
||||
"minimum_value": 1,
|
||||
"enabled": "enable_bv_fan and bv_fan_speed_control_enable"
|
||||
},
|
||||
"bv_fan_end_layer":
|
||||
{
|
||||
"label": " Chamber/Aux Fan End Layer",
|
||||
"description": "The layer number for Chamber or Auxiliary Fan to turn off. Use the Cura preview layer number or '-1' to indicate the end of the print. The fan will run until the end of the layer",
|
||||
"type": "int",
|
||||
"unit": "Layer# ",
|
||||
"default_value": -1,
|
||||
"minimum_value": -1,
|
||||
"enabled": "enable_bv_fan and bv_fan_speed_control_enable"
|
||||
},
|
||||
"enable_bv_fan":
|
||||
{
|
||||
"label": "Hidden setting",
|
||||
"description": "This is enabled when machine_heated_bed is true, and in turn this enables 'bv_fan_speed_control_enable'.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
def initialize(self) -> None:
|
||||
super().initialize()
|
||||
scripts = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("post_processing_scripts")
|
||||
self.global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
self.extruder_list = self.global_stack.extruderList
|
||||
self.extruder_count = self.global_stack.getProperty("machine_extruder_count", "value")
|
||||
scripts = self.global_stack.getMetaDataEntry("post_processing_scripts")
|
||||
if scripts != None:
|
||||
script_count = scripts.count("AddCoolingProfile")
|
||||
if script_count > 0:
|
||||
## Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
|
||||
# Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
|
||||
self._instance.setProperty("delete_existing_m106", "value", False)
|
||||
self._instance.setProperty("enable_off_fan_speed_enable", "value", False)
|
||||
if self.extruder_count > 1:
|
||||
if self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value") != self.extruder_list[1].getProperty("machine_extruder_cooling_fan_number", "value"):
|
||||
self._instance.setProperty("enable_off_fan_speed_enable", "value", True)
|
||||
if bool(self.global_stack.getProperty("machine_heated_bed", "value")):
|
||||
self._instance.setProperty("enable_bv_fan", "value", True)
|
||||
|
||||
def execute(self, data):
|
||||
#Initialize variables that are buried in if statements.
|
||||
mycura = Application.getInstance().getGlobalContainerStack()
|
||||
"""
|
||||
Collect the settings from Cura and from this script
|
||||
params:
|
||||
t0_fan thru t3_fan: The fan numbers for up to 4 layer cooling circuits
|
||||
fan_mode: Whether the fan scale will be 0-255 PWM (when true) or 0-1 RepRap (when false)
|
||||
bed_adhesion: Is only important if a raft is enabled
|
||||
print_seuence: Options are slightly different if in One-at-a-Time mode
|
||||
is_multi-fan: Used to distinguish between a multi-extruder with a single fan for each nozzle, or one fan for both nozzles.
|
||||
is_multi_extr_print: For the slight difference in handling a multi-extruder printer and a print that only uses one of the extruders.
|
||||
fan_list: A list of fan speeds (even numbered items) and layer numbers (odd numbered items)
|
||||
feature_speed_list: A list of the speeds for each ';TYPE:' in the gcode
|
||||
feature_name_list: The list of each 'TYPE' in the gcode
|
||||
off_fan_speed: The speed that will be maintained by the fan for the inactive extruder (for an anti-oozing effect)
|
||||
init_fan: The fan number of the first extruder used in a print
|
||||
delete_existing_m106: The first instance of the script in the post processing list should remove the CUra M106 lines. Following instances should not delete the changes made by the first instance.
|
||||
feature_fan_combing: Whether or not to shut the cooling fan off during travel moves.
|
||||
the_start_layer: When in By Feature this is the user selected start of the fan changes.
|
||||
the_end_layer: When in By Feature this is the user selected end of the fan changes
|
||||
the_end_is_enabled: When in By Feature, if the fan control ends before the print ends, then this will enable the Final Fan Speed to carry through to the print end.
|
||||
|
||||
"""
|
||||
# Exit if the gcode has been previously post-processed.
|
||||
if ";POSTPROCESSED" in data[0]:
|
||||
return data
|
||||
# Initialize variables that are buried in if statements.
|
||||
t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
|
||||
|
||||
#Get some information from Cura-----------------------------------
|
||||
extruder = mycura.extruderList
|
||||
|
||||
#This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
|
||||
# This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
|
||||
fan_mode = True
|
||||
##For 4.x versions that don't have the 0-1 option
|
||||
# For 4.x versions that don't have the 0-1 option
|
||||
try:
|
||||
fan_mode = not bool(extruder[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
|
||||
except:
|
||||
fan_mode = not bool(self.extruder_list[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
|
||||
except AttributeError:
|
||||
pass
|
||||
bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
|
||||
extruder_count = mycura.getProperty("machine_extruder_count", "value")
|
||||
print_sequence = str(mycura.getProperty("print_sequence", "value"))
|
||||
|
||||
#Assign the fan numbers to the tools------------------------------
|
||||
if extruder_count == 1:
|
||||
bed_adhesion = (self.extruder_list[0].getProperty("adhesion_type", "value"))
|
||||
print_sequence = str(self.global_stack.getProperty("print_sequence", "value"))
|
||||
|
||||
# Assign the fan numbers to the tools
|
||||
if self.extruder_count == 1:
|
||||
is_multi_fan = False
|
||||
is_multi_extr_print = False
|
||||
if int((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0:
|
||||
t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
if int((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0:
|
||||
t0_fan = " P" + str((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
else:
|
||||
#No P parameter if there is a single fan circuit------------------
|
||||
# No P parameter if there is a single fan circuit
|
||||
t0_fan = ""
|
||||
|
||||
#Get the cooling fan numbers for each extruder if the printer has multiple extruders
|
||||
elif extruder_count > 1:
|
||||
# Get the cooling fan numbers for each extruder if the printer has multiple extruders
|
||||
elif self.extruder_count > 1:
|
||||
is_multi_fan = True
|
||||
t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
t0_fan = " P" + str((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
if is_multi_fan:
|
||||
if extruder_count > 1: t1_fan = " P" + str((extruder[1].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
if extruder_count > 2: t2_fan = " P" + str((extruder[2].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
if extruder_count > 3: t3_fan = " P" + str((extruder[3].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
if self.extruder_count > 1: t1_fan = " P" + str((self.extruder_list[1].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
if self.extruder_count > 2: t2_fan = " P" + str((self.extruder_list[2].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
if self.extruder_count > 3: t3_fan = " P" + str((self.extruder_list[3].getProperty("machine_extruder_cooling_fan_number", "value")))
|
||||
|
||||
#Initialize the fan_list with defaults----------------------------
|
||||
# Initialize the fan_list with defaults
|
||||
fan_list = ["z"] * 16
|
||||
for num in range(0,15,2):
|
||||
fan_list[num] = len(data)
|
||||
fan_list[num + 1] = "M106 S0"
|
||||
|
||||
#Assign the variable values if "By Layer"-------------------------
|
||||
# Assign the variable values if "By Layer"
|
||||
by_layer_or_feature = self.getSettingValueByKey("fan_layer_or_feature")
|
||||
if by_layer_or_feature == "by_layer":
|
||||
## By layer doesn't do any feature search so there is no need to look for combing moves
|
||||
# By layer doesn't do any feature search so there is no need to look for combing moves
|
||||
feature_fan_combing = False
|
||||
fan_list[0] = self.getSettingValueByKey("layer_fan_1")
|
||||
fan_list[2] = self.getSettingValueByKey("layer_fan_2")
|
||||
|
|
@ -343,25 +462,25 @@ class AddCoolingProfile(Script):
|
|||
fan_list[10] = self.getSettingValueByKey("layer_fan_6")
|
||||
fan_list[12] = self.getSettingValueByKey("layer_fan_7")
|
||||
fan_list[14] = self.getSettingValueByKey("layer_fan_8")
|
||||
## If there is no '/' delimiter then ignore the line else put the settings in a list
|
||||
# If there is no '/' delimiter then ignore the line else put the settings in a list
|
||||
for num in range(0,15,2):
|
||||
if "/" in fan_list[num]:
|
||||
fan_list[num + 1] = self._layer_checker(fan_list[num], "p", fan_mode)
|
||||
fan_list[num] = self._layer_checker(fan_list[num], "l", fan_mode)
|
||||
|
||||
## Assign the variable values if "By Feature"
|
||||
# Assign the variable values if "By Feature"
|
||||
elif by_layer_or_feature == "by_feature":
|
||||
the_start_layer = self.getSettingValueByKey("feature_fan_start_layer") - 1
|
||||
the_end_layer = self.getSettingValueByKey("feature_fan_end_layer")
|
||||
try:
|
||||
if int(the_end_layer) != -1:
|
||||
## Catch a possible input error.
|
||||
# Catch a possible input error.
|
||||
if the_end_layer < the_start_layer:
|
||||
the_end_layer = the_start_layer
|
||||
except:
|
||||
the_end_layer = -1 ## If there is an input error default to the entire gcode file.
|
||||
except ValueError:
|
||||
the_end_layer = -1 # If there is an input error then default to the entire gcode file.
|
||||
|
||||
## Get the speed for each feature
|
||||
# Get the speed for each feature
|
||||
feature_name_list = []
|
||||
feature_speed_list = []
|
||||
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_skirt"), fan_mode)); feature_name_list.append(";TYPE:SKIRT")
|
||||
|
|
@ -376,20 +495,29 @@ class AddCoolingProfile(Script):
|
|||
feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_feature_final"), fan_mode)); feature_name_list.append("FINAL_FAN")
|
||||
feature_fan_combing = self.getSettingValueByKey("feature_fan_combing")
|
||||
if the_end_layer > -1 and by_layer_or_feature == "by_feature":
|
||||
## Required so the final speed input can be determined
|
||||
# Required so the final speed input can be determined
|
||||
the_end_is_enabled = True
|
||||
else:
|
||||
## There is no ending layer so do the whole file
|
||||
# There is no ending layer so do the whole file
|
||||
the_end_is_enabled = False
|
||||
if the_end_layer == -1 or the_end_is_enabled == False:
|
||||
the_end_layer = len(data) + 2
|
||||
|
||||
## Find the Layer0Index and the RaftIndex
|
||||
# For multi-extruder printers with separate cooling fans the 'idle' nozzle fan can be left on for ooze control
|
||||
off_fan_speed = 0
|
||||
if self.extruder_count > 1:
|
||||
if self.getSettingValueByKey("enable_off_fan_speed"):
|
||||
if fan_mode:
|
||||
off_fan_speed = round(int(self.getSettingValueByKey("off_fan_speed")) * 2.55)
|
||||
else:
|
||||
off_fan_speed = round(int(self.getSettingValueByKey("off_fan_speed")) * .01, 2)
|
||||
|
||||
# Find the Layer0Index and the RaftIndex
|
||||
raft_start_index = 0
|
||||
number_of_raft_layers = 0
|
||||
layer_0_index = 0
|
||||
## Catch the number of raft layers.
|
||||
for l_num in range(1,10,1):
|
||||
# Catch the number of raft layers.
|
||||
for l_num in range(1,len(data) - 1):
|
||||
layer = data[l_num]
|
||||
if ";LAYER:-" in layer:
|
||||
number_of_raft_layers += 1
|
||||
|
|
@ -399,14 +527,14 @@ class AddCoolingProfile(Script):
|
|||
layer_0_index = l_num
|
||||
break
|
||||
|
||||
## Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used.
|
||||
# Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used.
|
||||
if is_multi_fan:
|
||||
T0_used = False
|
||||
T1_used = False
|
||||
T2_used = False
|
||||
T3_used = False
|
||||
## Bypass the file header and ending gcode.
|
||||
for num in range(1,len(data)-1,1):
|
||||
# Bypass the file header and ending gcode.
|
||||
for num in range(1,len(data)-1):
|
||||
lines = data[num]
|
||||
if "T0" in lines:
|
||||
T0_used = True
|
||||
|
|
@ -418,7 +546,7 @@ class AddCoolingProfile(Script):
|
|||
T3_used = True
|
||||
is_multi_extr_print = True if sum([T0_used, T1_used, T2_used, T3_used]) > 1 else False
|
||||
|
||||
## On a multi-extruder printer and single extruder print find out which extruder starts the file.
|
||||
# On a multi-extruder printer and single extruder print find out which extruder starts the file.
|
||||
init_fan = t0_fan
|
||||
if not is_multi_extr_print:
|
||||
startup = data[1]
|
||||
|
|
@ -431,7 +559,7 @@ class AddCoolingProfile(Script):
|
|||
elif line == "T3":
|
||||
t0_fan = t3_fan
|
||||
elif is_multi_extr_print:
|
||||
## On a multi-extruder printer and multi extruder print find out which extruder starts the file.
|
||||
# On a multi-extruder printer and multi extruder print find out which extruder starts the file.
|
||||
startup = data[1]
|
||||
lines = startup.split("\n")
|
||||
for line in lines:
|
||||
|
|
@ -445,23 +573,23 @@ class AddCoolingProfile(Script):
|
|||
init_fan = t3_fan
|
||||
else:
|
||||
init_fan = ""
|
||||
## Assign the variable values if "Raft Enabled"
|
||||
# Assign the variable values if "Raft Enabled"
|
||||
raft_enabled = self.getSettingValueByKey("fan_enable_raft")
|
||||
if raft_enabled and bed_adhesion == "raft":
|
||||
fan_sp_raft = self._feature_checker(self.getSettingValueByKey("fan_raft_percent"), fan_mode)
|
||||
else:
|
||||
fan_sp_raft = "M106 S0"
|
||||
|
||||
# Start to alter the data-----------------------------------------
|
||||
## Strip the existing M106 lines from the file up to the end of the last layer. If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option.
|
||||
# Start to alter the data
|
||||
# Strip the existing M106 lines from the file up to the end of the last layer. If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option.
|
||||
delete_existing_m106 = self.getSettingValueByKey("delete_existing_m106")
|
||||
if delete_existing_m106:
|
||||
## Start deleting from the beginning
|
||||
# Start deleting from the beginning
|
||||
start_from = int(raft_start_index)
|
||||
else:
|
||||
if by_layer_or_feature == "by_layer":
|
||||
altered_start_layer = str(len(data))
|
||||
## The fan list layers don't need to be in ascending order. Get the lowest.
|
||||
# The fan list layers don't need to be in ascending order. Get the lowest.
|
||||
for num in range(0,15,2):
|
||||
try:
|
||||
if int(fan_list[num]) < int(altered_start_layer):
|
||||
|
|
@ -471,12 +599,12 @@ class AddCoolingProfile(Script):
|
|||
elif by_layer_or_feature == "by_feature":
|
||||
altered_start_layer = int(the_start_layer) - 1
|
||||
start_from = int(layer_0_index) + int(altered_start_layer)
|
||||
## Strip the M106 and M107 lines from the file
|
||||
# Strip the M106 and M107 lines from the file
|
||||
for l_index in range(int(start_from), len(data) - 1, 1):
|
||||
data[l_index] = re.sub(re.compile("M106(.*)\n"), "", data[l_index])
|
||||
data[l_index] = re.sub(re.compile("M107(.*)\n"), "", data[l_index])
|
||||
|
||||
## Deal with a raft and with One-At-A-Time print sequence
|
||||
# Deal with a raft and with One-At-A-Time print sequence
|
||||
if raft_enabled and bed_adhesion == "raft":
|
||||
if print_sequence == "one_at_a_time":
|
||||
for r_index in range(2,len(data)-2,1):
|
||||
|
|
@ -486,9 +614,9 @@ class AddCoolingProfile(Script):
|
|||
lines.insert(1, "M106 S0" + str(t0_fan))
|
||||
if raft_enabled and bed_adhesion == "raft":
|
||||
if ";LAYER:-" in data[r_index]:
|
||||
## Turn the raft fan on
|
||||
# Turn the raft fan on
|
||||
lines.insert(1, fan_sp_raft + str(t0_fan))
|
||||
## Shut the raft fan off at layer 0
|
||||
# Shut the raft fan off at layer 0
|
||||
if ";LAYER:0" in data[r_index]:
|
||||
lines.insert(1,"M106 S0" + str(t0_fan))
|
||||
data[r_index] = "\n".join(lines)
|
||||
|
|
@ -496,13 +624,13 @@ class AddCoolingProfile(Script):
|
|||
layer = data[raft_start_index]
|
||||
lines = layer.split("\n")
|
||||
if ";LAYER:-" in layer:
|
||||
## Turn the raft fan on
|
||||
# Turn the raft fan on
|
||||
lines.insert(1, fan_sp_raft + str(init_fan))
|
||||
layer = "\n".join(lines)
|
||||
data[raft_start_index] = layer
|
||||
layer = data[layer_0_index]
|
||||
lines = layer.split("\n")
|
||||
## Shut the raft fan off
|
||||
# Shut the raft fan off
|
||||
lines.insert(1, "M106 S0" + str(init_fan))
|
||||
data[layer_0_index] = "\n".join(lines)
|
||||
else:
|
||||
|
|
@ -513,41 +641,44 @@ class AddCoolingProfile(Script):
|
|||
lines.insert(1, "M106 S0" + str(t0_fan))
|
||||
data[r_index] = "\n".join(lines)
|
||||
|
||||
## Turn off all fans at the end of data[1]. If more than one instance of this script is running then this will result in multiple M106 lines.
|
||||
# Turn off all fans at the end of data[1]. If more than one instance of this script is running then this will result in multiple M106 lines.
|
||||
temp_startup = data[1].split("\n")
|
||||
temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t0_fan))
|
||||
## If there are multiple cooling fans shut them all off
|
||||
# If there are multiple cooling fans shut them all off
|
||||
if is_multi_fan:
|
||||
if extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan))
|
||||
if extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan))
|
||||
if extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan))
|
||||
if self.extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan))
|
||||
if self.extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan))
|
||||
if self.extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan))
|
||||
data[1] = "\n".join(temp_startup)
|
||||
|
||||
## If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long
|
||||
## For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH.
|
||||
# If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long
|
||||
# For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH.
|
||||
if feature_fan_combing:
|
||||
for layer_num in range(2,len(data)):
|
||||
layer = data[layer_num]
|
||||
data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer)
|
||||
data = self._add_travel_comment(data, layer_0_index)
|
||||
|
||||
# Single Fan "By Layer"--------------------------------------------
|
||||
if bool(self.getSettingValueByKey("bv_fan_speed_control_enable")):
|
||||
data = self._control_bv_fan(data)
|
||||
|
||||
# Single Fan "By Layer"
|
||||
if by_layer_or_feature == "by_layer" and not is_multi_fan:
|
||||
return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan)
|
||||
|
||||
# Multi-Fan "By Layer"---------------------------------------------
|
||||
# Multi-Fan "By Layer"
|
||||
if by_layer_or_feature == "by_layer" and is_multi_fan:
|
||||
return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan)
|
||||
return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, fan_mode, off_fan_speed)
|
||||
|
||||
#Single Fan "By Feature"------------------------------------------
|
||||
# Single Fan "By Feature"
|
||||
if by_layer_or_feature == "by_feature" and (not is_multi_fan or not is_multi_extr_print):
|
||||
return self._single_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, feature_speed_list, feature_name_list, feature_fan_combing)
|
||||
|
||||
#Multi Fan "By Feature"-------------------------------------------
|
||||
# Multi Fan "By Feature"
|
||||
if by_layer_or_feature == "by_feature" and is_multi_fan:
|
||||
return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing)
|
||||
return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing, fan_mode, off_fan_speed)
|
||||
|
||||
# The Single Fan "By Layer"----------------------------------------
|
||||
# The Single Fan "By Layer"
|
||||
def _single_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str)->str:
|
||||
layer_number = "0"
|
||||
single_fan_data = data
|
||||
|
|
@ -557,15 +688,15 @@ class AddCoolingProfile(Script):
|
|||
for fan_line in fan_lines:
|
||||
if ";LAYER:" in fan_line:
|
||||
layer_number = str(fan_line.split(":")[1])
|
||||
## If there is a match for the current layer number make the insertion
|
||||
# If there is a match for the current layer number make the insertion
|
||||
for num in range(0,15,2):
|
||||
if layer_number == str(fan_list[num]):
|
||||
layer = layer.replace(fan_lines[0],fan_lines[0] + "\n" + fan_list[num + 1] + str(t0_fan))
|
||||
single_fan_data[l_index] = layer
|
||||
return single_fan_data
|
||||
|
||||
# Multi-Fan "By Layer"-----------------------------------------
|
||||
def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str)->str:
|
||||
# Multi-Fan "By Layer"
|
||||
def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, fan_mode: bool, off_fan_speed: str)->str:
|
||||
multi_fan_data = data
|
||||
layer_number = "0"
|
||||
current_fan_speed = "0"
|
||||
|
|
@ -573,15 +704,16 @@ class AddCoolingProfile(Script):
|
|||
this_fan = str(t0_fan)
|
||||
start_index = str(len(multi_fan_data))
|
||||
for num in range(0,15,2):
|
||||
## The fan_list may not be in ascending order. Get the lowest layer number
|
||||
|
||||
# The fan_list may not be in ascending order. Get the lowest layer number
|
||||
try:
|
||||
if int(fan_list[num]) < int(start_index):
|
||||
start_index = str(fan_list[num])
|
||||
except:
|
||||
except ValueError:
|
||||
pass
|
||||
## Move the start point if delete_existing_m106 is false
|
||||
# Move the start point if delete_existing_m106 is false
|
||||
start_index = int(start_index) + int(layer_0_index)
|
||||
## Track the tool number
|
||||
# Track the tool number
|
||||
for num in range(1,int(start_index),1):
|
||||
layer = multi_fan_data[num]
|
||||
lines = layer.split("\n")
|
||||
|
|
@ -598,18 +730,19 @@ class AddCoolingProfile(Script):
|
|||
elif line == "T3":
|
||||
prev_fan = this_fan
|
||||
this_fan = t3_fan
|
||||
# With Active Tool tracked - now the body of changes can start
|
||||
for l_index in range(int(start_index),len(multi_fan_data)-1,1):
|
||||
modified_data = ""
|
||||
layer = multi_fan_data[l_index]
|
||||
fan_lines = layer.split("\n")
|
||||
for fan_line in fan_lines:
|
||||
## Prepare to shut down the previous fan and start the next one.
|
||||
# Prepare to turn off the previous fan and start the next one.
|
||||
if fan_line.startswith("T"):
|
||||
if fan_line == "T0": this_fan = str(t0_fan)
|
||||
if fan_line == "T1": this_fan = str(t1_fan)
|
||||
if fan_line == "T2": this_fan = str(t2_fan)
|
||||
if fan_line == "T3": this_fan = str(t3_fan)
|
||||
modified_data += "M106 S0" + prev_fan + "\n"
|
||||
modified_data += f"M106 S{off_fan_speed}" + prev_fan + "\n"
|
||||
modified_data += fan_line + "\n"
|
||||
modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
|
||||
prev_fan = this_fan
|
||||
|
|
@ -620,19 +753,22 @@ class AddCoolingProfile(Script):
|
|||
if layer_number == str(fan_list[num]):
|
||||
modified_data += fan_list[num + 1] + this_fan + "\n"
|
||||
current_fan_speed = str(fan_list[num + 1].split("S")[1])
|
||||
current_fan_speed = str(current_fan_speed.split(" ")[0]) ## Just in case
|
||||
current_fan_speed = str(current_fan_speed.split(" ")[0]) # Just in case
|
||||
else:
|
||||
modified_data += fan_line + "\n"
|
||||
if modified_data.endswith("\n"): modified_data = modified_data[0:-1]
|
||||
multi_fan_data[l_index] = modified_data
|
||||
# Insure the fans get shut off if 'off_fan_speed' was enabled
|
||||
if self.extruder_count > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
|
||||
multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
|
||||
return multi_fan_data
|
||||
|
||||
# Single fan by feature-----------------------------------------------
|
||||
# Single fan by feature
|
||||
def _single_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str:
|
||||
single_fan_data = data
|
||||
layer_number = "0"
|
||||
index = 1
|
||||
## Start with layer:0
|
||||
# Start with layer:0
|
||||
for l_index in range(layer_0_index,len(single_fan_data)-1,1):
|
||||
modified_data = ""
|
||||
layer = single_fan_data[l_index]
|
||||
|
|
@ -652,15 +788,16 @@ class AddCoolingProfile(Script):
|
|||
if feature_fan_combing == True:
|
||||
modified_data += "M106 S0" + t0_fan + "\n"
|
||||
modified_data += line + "\n"
|
||||
## If an End Layer is defined and is less than the last layer then insert the Final Speed
|
||||
|
||||
# If an End Layer is defined and is less than the last layer then insert the Final Speed
|
||||
if line == ";LAYER:" + str(the_end_layer) and the_end_is_enabled == True:
|
||||
modified_data += feature_speed_list[len(feature_speed_list) - 1] + t0_fan + "\n"
|
||||
if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
|
||||
single_fan_data[l_index] = modified_data
|
||||
return single_fan_data
|
||||
|
||||
# Multi-fan by feature------------------------------------------------
|
||||
def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str:
|
||||
# Multi-fan by feature
|
||||
def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool, fan_mode: bool, off_fan_speed: str)->str:
|
||||
multi_fan_data = data
|
||||
layer_number = "0"
|
||||
start_index = 1
|
||||
|
|
@ -673,7 +810,7 @@ class AddCoolingProfile(Script):
|
|||
if ";LAYER:" + str(the_start_layer) + "\n" in layer:
|
||||
start_index = int(my_index) - 1
|
||||
break
|
||||
## Track the previous tool changes
|
||||
# Track the previous tool changes
|
||||
for num in range(1,start_index,1):
|
||||
layer = multi_fan_data[num]
|
||||
lines = layer.split("\n")
|
||||
|
|
@ -690,7 +827,7 @@ class AddCoolingProfile(Script):
|
|||
elif line == "T3":
|
||||
prev_fan = this_fan
|
||||
this_fan = t3_fan
|
||||
## Get the current tool.
|
||||
# Get the current tool.
|
||||
for l_index in range(start_index,start_index + 1,1):
|
||||
layer = multi_fan_data[l_index]
|
||||
lines = layer.split("\n")
|
||||
|
|
@ -702,7 +839,7 @@ class AddCoolingProfile(Script):
|
|||
if line == "T3": this_fan = t3_fan
|
||||
prev_fan = this_fan
|
||||
|
||||
## Start to make insertions-------------------------------------
|
||||
# Start to make insertions
|
||||
for l_index in range(start_index+1,len(multi_fan_data)-1,1):
|
||||
layer = multi_fan_data[l_index]
|
||||
lines = layer.split("\n")
|
||||
|
|
@ -712,10 +849,10 @@ class AddCoolingProfile(Script):
|
|||
if line == "T1": this_fan = t1_fan
|
||||
if line == "T2": this_fan = t2_fan
|
||||
if line == "T3": this_fan = t3_fan
|
||||
## Turn off the prev fan
|
||||
modified_data += "M106 S0" + prev_fan + "\n"
|
||||
# Turn off the prev fan
|
||||
modified_data += f"M106 S{off_fan_speed}" + prev_fan + "\n"
|
||||
modified_data += line + "\n"
|
||||
## Turn on the current fan
|
||||
# Turn on the current fan
|
||||
modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
|
||||
prev_fan = this_fan
|
||||
if ";LAYER:" in line:
|
||||
|
|
@ -725,34 +862,39 @@ class AddCoolingProfile(Script):
|
|||
temp = line.split(" ")[0]
|
||||
try:
|
||||
name_index = feature_name_list.index(temp)
|
||||
except:
|
||||
except IndexError:
|
||||
name_index = -1
|
||||
|
||||
if name_index != -1:
|
||||
modified_data += line + "\n" + feature_speed_list[name_index] + this_fan + "\n"
|
||||
#modified_data += feature_speed_list[name_index] + this_fan + "\n"
|
||||
current_fan_speed = str(feature_speed_list[name_index].split("S")[1])
|
||||
elif ";MESH:NONMESH" in line:
|
||||
if feature_fan_combing == True:
|
||||
modified_data += line + "\n"
|
||||
modified_data += "M106 S0" + this_fan + "\n"
|
||||
modified_data += f"M106 S{off_fan_speed}" + this_fan + "\n"
|
||||
current_fan_speed = "0"
|
||||
else:
|
||||
modified_data += line + "\n"
|
||||
## If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file
|
||||
## There cannot be a break here because if there are multiple fan numbers they still need to be shut off and turned on.
|
||||
|
||||
# If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file
|
||||
# There cannot be a 'break' here because if there are multiple fan numbers they still need to be shut off and turned on.
|
||||
elif line == ";LAYER:" + str(the_end_layer):
|
||||
modified_data += feature_speed_list[len(feature_speed_list) - 1] + this_fan + "\n"
|
||||
for set_speed in range(0, len(feature_speed_list) - 2):
|
||||
feature_speed_list[set_speed] = feature_speed_list[len(feature_speed_list) - 1]
|
||||
else:
|
||||
## Layer and Tool get inserted into modified_data above. All other lines go into modified_data here
|
||||
# Layer and Tool get inserted into modified_data above. All other lines go into modified_data here
|
||||
if not line.startswith("T") and not line.startswith(";LAYER:"): modified_data += line + "\n"
|
||||
|
||||
if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
|
||||
multi_fan_data[l_index] = modified_data
|
||||
modified_data = ""
|
||||
# Insure the fans get shut off if 'off_fan_speed' was enabled
|
||||
if self.extruder_count > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
|
||||
multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
|
||||
return multi_fan_data
|
||||
|
||||
#Try to catch layer input errors, set the minimum speed to 12%, and put the strings together
|
||||
# Try to catch layer input errors, set the minimum speed to 12%, and put the strings together
|
||||
def _layer_checker(self, fan_string: str, ty_pe: str, fan_mode: bool) -> str:
|
||||
fan_string_l = str(fan_string.split("/")[0])
|
||||
try:
|
||||
|
|
@ -768,7 +910,7 @@ class AddCoolingProfile(Script):
|
|||
if int(fan_string_p) > 100: fan_string_p = "100"
|
||||
except ValueError:
|
||||
fan_string_p = "0"
|
||||
## Set the minimum fan speed to 12%
|
||||
# Set the minimum fan speed to 12%
|
||||
if int(fan_string_p) < 12 and int(fan_string_p) != 0:
|
||||
fan_string_p = "12"
|
||||
fan_layer_line = str(fan_string_l)
|
||||
|
|
@ -784,7 +926,7 @@ class AddCoolingProfile(Script):
|
|||
#Try to catch feature input errors, set the minimum speed to 12%, and put the strings together when 'By Feature'
|
||||
def _feature_checker(self, fan_feat_string: int, fan_mode: bool) -> str:
|
||||
if fan_feat_string < 0: fan_feat_string = 0
|
||||
## Set the minimum fan speed to 12%
|
||||
# Set the minimum fan speed to 12%
|
||||
if fan_feat_string > 0 and fan_feat_string < 12: fan_feat_string = 12
|
||||
if fan_feat_string > 100: fan_feat_string = 100
|
||||
if fan_mode:
|
||||
|
|
@ -798,7 +940,7 @@ class AddCoolingProfile(Script):
|
|||
for lay_num in range(int(lay_0_index), len(comment_data)-1,1):
|
||||
layer = comment_data[lay_num]
|
||||
lines = layer.split("\n")
|
||||
## Copy the data to new_data and make the insertions there
|
||||
# Copy the data to new_data and make the insertions there
|
||||
new_data = lines
|
||||
g0_count = 0
|
||||
g0_index = -1
|
||||
|
|
@ -818,12 +960,12 @@ class AddCoolingProfile(Script):
|
|||
if g0_index == -1:
|
||||
g0_index = lines.index(line)
|
||||
elif not line.startswith("G0 ") and not is_travel:
|
||||
## Add additional 'NONMESH' lines to shut the fan off during long combing moves--------
|
||||
# Add additional 'NONMESH' lines to shut the fan off during long combing moves
|
||||
if g0_count > 5:
|
||||
if not is_travel:
|
||||
new_data.insert(g0_index + insert_index, ";MESH:NONMESH")
|
||||
insert_index += 1
|
||||
## Add the feature_type at the end of the combing move to turn the fan back on
|
||||
# Add the feature_type at the end of the combing move to turn the fan back on
|
||||
new_data.insert(g0_index + g0_count + 1, feature_type)
|
||||
insert_index += 1
|
||||
g0_count = 0
|
||||
|
|
@ -834,4 +976,35 @@ class AddCoolingProfile(Script):
|
|||
g0_index = -1
|
||||
is_travel = False
|
||||
comment_data[lay_num] = "\n".join(new_data)
|
||||
return comment_data
|
||||
return comment_data
|
||||
|
||||
def _control_bv_fan(self, bv_data: str) -> str:
|
||||
# Control any secondary fan. Can be used for an Auxilliary/Chamber fan
|
||||
bv_start_layer = self.getSettingValueByKey("bv_fan_start_layer") - 1
|
||||
bv_end_layer = self.getSettingValueByKey("bv_fan_end_layer")
|
||||
bv_fan_nr = self.getSettingValueByKey("bv_fan_nr")
|
||||
if bv_end_layer != -1:
|
||||
bv_end_layer -= 1
|
||||
# Get the PWM speed or if RepRap then the 0-1 speed
|
||||
if self.extruder_list[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"):
|
||||
bv_fan_speed = round(self.getSettingValueByKey("bv_fan_speed") * .01, 1)
|
||||
else:
|
||||
bv_fan_speed = int(self.getSettingValueByKey("bv_fan_speed") * 2.55)
|
||||
# Turn the chamber fan on
|
||||
for index, layer in enumerate(bv_data):
|
||||
if f";LAYER:{bv_start_layer}\n" in layer:
|
||||
bv_data[index] = re.sub(f";LAYER:{bv_start_layer}", f";LAYER:{bv_start_layer}\nM106 S{bv_fan_speed} P{bv_fan_nr}",layer)
|
||||
break
|
||||
# Turn the chamber fan off
|
||||
if bv_end_layer == -1:
|
||||
bv_data[len(bv_data)-2] += f"M106 S0 P{bv_fan_nr}\n"
|
||||
else:
|
||||
for index, layer in enumerate(bv_data):
|
||||
if f";LAYER:{bv_end_layer}\n" in layer:
|
||||
lines = layer.split("\n")
|
||||
for fdex, line in enumerate(lines):
|
||||
if ";TIME_ELAPSED:" in line:
|
||||
lines[fdex] = f"M106 S0 P{bv_fan_nr}\n" + line
|
||||
bv_data[index] = "\n".join(lines)
|
||||
break
|
||||
return bv_data
|
||||
|
|
@ -35,16 +35,21 @@ class CreateThumbnail(Script):
|
|||
def _convertSnapshotToGcode(self, encoded_snapshot, width, height, chunk_size=78):
|
||||
gcode = []
|
||||
|
||||
use_thumbnail = self.getSettingValueByKey("use_thumbnail")
|
||||
use_star = self.getSettingValueByKey("use_star")
|
||||
|
||||
encoded_snapshot_length = len(encoded_snapshot)
|
||||
image_type = "thumbnail" if use_thumbnail else "png"
|
||||
resolution_symbol = '*' if use_star else 'x'
|
||||
gcode.append(";")
|
||||
gcode.append("; thumbnail begin {}x{} {}".format(
|
||||
width, height, encoded_snapshot_length))
|
||||
gcode.append("; {} begin {}{}{} {}".format(
|
||||
image_type, width, resolution_symbol, height, encoded_snapshot_length))
|
||||
|
||||
chunks = ["; {}".format(encoded_snapshot[i:i+chunk_size])
|
||||
for i in range(0, len(encoded_snapshot), chunk_size)]
|
||||
gcode.extend(chunks)
|
||||
|
||||
gcode.append("; thumbnail end")
|
||||
gcode.append("; {} end".format(image_type))
|
||||
gcode.append(";")
|
||||
gcode.append("")
|
||||
|
||||
|
|
@ -79,6 +84,20 @@ class CreateThumbnail(Script):
|
|||
"minimum_value": "0",
|
||||
"minimum_value_warning": "12",
|
||||
"maximum_value_warning": "600"
|
||||
},
|
||||
"use_thumbnail":
|
||||
{
|
||||
"label": "Thumbnail Begin/End",
|
||||
"description": "Use Thumbnail Begin/End rather than PNG",
|
||||
"type": "bool",
|
||||
"default_value": true
|
||||
},
|
||||
"use_star":
|
||||
{
|
||||
"label": "Use '*' for size of image",
|
||||
"description": "Use '*' instead of 'x' for size of image as Width '*' Height",
|
||||
"type": "bool",
|
||||
"default_value": false
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class FilamentChange(Script):
|
|||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value": 0,
|
||||
"enabled": "enabled"
|
||||
"enabled": "enabled and not firmware_config"
|
||||
},
|
||||
"retract_method":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Created by Wayne Porter
|
||||
# Re-write in April of 2024 by GregValiant (Greg Foresi)
|
||||
# Made convert inserted text to upper-case optional March 2025 by HellAholic
|
||||
# Changes:
|
||||
# Added an 'Enable' setting
|
||||
# Added support for multi-line insertions (comma delimited)
|
||||
|
|
@ -82,6 +83,14 @@ class InsertAtLayerChange(Script):
|
|||
"type": "str",
|
||||
"default_value": "",
|
||||
"enabled": "enabled"
|
||||
},
|
||||
"convert_to_upper":
|
||||
{
|
||||
"label": "Convert to upper-case",
|
||||
"description": "Convert all inserted text to upper-case as some firmwares don't understand lower-case.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": "enabled"
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
|
@ -91,7 +100,7 @@ class InsertAtLayerChange(Script):
|
|||
if not bool(self.getSettingValueByKey("enabled")):
|
||||
return data
|
||||
#Initialize variables
|
||||
mycode = self.getSettingValueByKey("gcode_to_add").upper()
|
||||
mycode = self.getSettingValueByKey("gcode_to_add").upper() if self.getSettingValueByKey("convert_to_upper") else self.getSettingValueByKey("gcode_to_add")
|
||||
start_layer = int(self.getSettingValueByKey("start_layer"))
|
||||
end_layer = int(self.getSettingValueByKey("end_layer"))
|
||||
when_to_insert = self.getSettingValueByKey("insert_frequency")
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@ class Position(tuple, Enum):
|
|||
|
||||
class PurgeLinesAndUnload(Script):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def initialize(self) -> None:
|
||||
super().initialize()
|
||||
# Get required values from the global stack and set default values for the script
|
||||
self.global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
self.extruder = self.global_stack.extruderList
|
||||
self.end_purge_location = None
|
||||
|
|
@ -56,9 +57,6 @@ class PurgeLinesAndUnload(Script):
|
|||
self.machine_back = self.machine_depth - 1.0
|
||||
self.start_x = None
|
||||
self.start_y = None
|
||||
|
||||
def initialize(self) -> None:
|
||||
super().initialize()
|
||||
# Get the StartUp Gcode from Cura and attempt to catch if it contains purge lines. Message the user if an extrusion is in the startup.
|
||||
startup_gcode = self.global_stack.getProperty("machine_start_gcode", "value")
|
||||
start_lines = startup_gcode.splitlines()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os.path
|
||||
from UM.View.View import View
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Resources import Resources
|
||||
from PyQt6.QtGui import QOpenGLContext, QDesktopServices, QImage
|
||||
from PyQt6.QtCore import QSize, QUrl
|
||||
from PyQt6.QtGui import QDesktopServices, QImage
|
||||
from PyQt6.QtCore import QUrl
|
||||
|
||||
import numpy as np
|
||||
import time
|
||||
|
|
@ -36,11 +35,12 @@ class SolidView(View):
|
|||
"""Standard view for mesh models."""
|
||||
|
||||
_show_xray_warning_preference = "view/show_xray_warning"
|
||||
_show_overhang_preference = "view/show_overhang"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
application = Application.getInstance()
|
||||
application.getPreferences().addPreference("view/show_overhang", True)
|
||||
application.getPreferences().addPreference(self._show_overhang_preference, True)
|
||||
application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
self._enabled_shader = None
|
||||
self._disabled_shader = None
|
||||
|
|
@ -212,7 +212,7 @@ class SolidView(View):
|
|||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
if Application.getInstance().getPreferences().getValue("view/show_overhang"):
|
||||
if Application.getInstance().getPreferences().getValue(self._show_overhang_preference):
|
||||
# Make sure the overhang angle is valid before passing it to the shader
|
||||
if self._support_angle >= 0 and self._support_angle <= 90:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - self._support_angle)))
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
0,
|
||||
5
|
||||
],
|
||||
"platform_texture": "hellbot_hidra.png"
|
||||
"platform_texture": "Hellbot_Hidra_and_Hidra_Plus_V2.png"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
0,
|
||||
5
|
||||
],
|
||||
"platform_texture": "hellbot_hidra_plus.png"
|
||||
"platform_texture": "Hellbot_Hidra_and_Hidra_Plus_V2.png"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
|
|
|
|||
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/images/Hellbot_Hidra_and_Hidra_Plus_V2.png
Normal file
BIN
resources/images/Hellbot_Hidra_and_Hidra_Plus_V2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
material = generic_cpe_plus
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA+ 0.4
|
||||
|
||||
[values]
|
||||
infill_sparse_density = 20
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 4
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
material = generic_pc
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA+ 0.4
|
||||
|
||||
[values]
|
||||
infill_sparse_density = 20
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 4
|
||||
|
||||
|
|
@ -5,6 +5,7 @@ version = 4
|
|||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_cpe_plus
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ version = 4
|
|||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_nylon-cf-slide
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ version = 4
|
|||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_pc
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ version = 4
|
|||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_petcf
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
material = generic_nylon-cf-slide
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = CC+ 0.6
|
||||
|
||||
[values]
|
||||
infill_sparse_density = 20
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 4
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
material = generic_petcf
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = CC+ 0.6
|
||||
|
||||
[values]
|
||||
infill_sparse_density = 20
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 4
|
||||
|
||||
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.
|
|
@ -17,7 +17,7 @@ Rectangle
|
|||
color: mouseArea.containsMouse || selected ? UM.Theme.getColor("background_3") : UM.Theme.getColor("background_1")
|
||||
|
||||
property bool selected: false
|
||||
property string profileName: ""
|
||||
property alias text: mainLabel.text
|
||||
property string icon: ""
|
||||
property string custom_icon: ""
|
||||
property alias tooltipText: tooltip.text
|
||||
|
|
@ -42,18 +42,18 @@ Rectangle
|
|||
|
||||
Item
|
||||
{
|
||||
width: intentIcon.width
|
||||
width: mainIcon.width
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: qualityLabel.top
|
||||
bottom: mainLabel.top
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
topMargin: UM.Theme.getSize("narrow_margin").height
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: intentIcon
|
||||
id: mainIcon
|
||||
width: UM.Theme.getSize("recommended_button_icon").width
|
||||
height: UM.Theme.getSize("recommended_button_icon").height
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ Rectangle
|
|||
{
|
||||
id: initialLabel
|
||||
anchors.centerIn: parent
|
||||
text: profileName.charAt(0).toUpperCase()
|
||||
text: base.text.charAt(0).toUpperCase()
|
||||
font: UM.Theme.getFont("small_bold")
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
|
@ -102,8 +102,7 @@ Rectangle
|
|||
|
||||
UM.Label
|
||||
{
|
||||
id: qualityLabel
|
||||
text: profileName
|
||||
id: mainLabel
|
||||
anchors
|
||||
{
|
||||
bottom: parent.bottom
|
||||
|
|
@ -7,7 +7,6 @@ import QtQuick.Layouts 2.10
|
|||
|
||||
import UM 1.5 as UM
|
||||
import Cura 1.7 as Cura
|
||||
import ".."
|
||||
|
||||
Item
|
||||
{
|
||||
|
|
@ -28,9 +27,9 @@ Item
|
|||
id: intentSelectionRepeater
|
||||
model: Cura.IntentSelectionModel {}
|
||||
|
||||
RecommendedQualityProfileSelectorButton
|
||||
Cura.ModeSelectorButton
|
||||
{
|
||||
profileName: model.name
|
||||
text: model.name
|
||||
icon: model.icon ? model.icon : ""
|
||||
custom_icon: model.custom_icon ? model.custom_icon : ""
|
||||
tooltipText: model.description ? model.description : ""
|
||||
|
|
|
|||
|
|
@ -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 != ""
|
||||
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ type = quality
|
|||
weight = 0
|
||||
|
||||
[values]
|
||||
layer_height = 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Fast
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
material = generic_cpe_plus
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = quality
|
||||
variant = AA+ 0.4
|
||||
weight = -2
|
||||
|
||||
[values]
|
||||
adhesion_type = brim
|
||||
material_alternate_walls = True
|
||||
material_final_print_temperature = =material_print_temperature - 15
|
||||
material_initial_print_temperature = =material_print_temperature - 15
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Fast
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
material = generic_pc
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = quality
|
||||
variant = AA+ 0.4
|
||||
weight = -2
|
||||
|
||||
[values]
|
||||
adhesion_type = brim
|
||||
cool_min_layer_time = 6
|
||||
cool_min_layer_time_fan_speed_max = 12
|
||||
inset_direction = inside_out
|
||||
material_alternate_walls = True
|
||||
material_final_print_temperature = =material_print_temperature - 15
|
||||
material_flow = 95
|
||||
material_initial_print_temperature = =material_print_temperature - 15
|
||||
retraction_prime_speed = 15
|
||||
speed_wall_x = =speed_wall_0
|
||||
|
||||
|
|
@ -15,4 +15,5 @@ weight = -2
|
|||
cool_min_layer_time = 4
|
||||
material_print_temperature = =default_material_print_temperature + 5
|
||||
retraction_prime_speed = 15
|
||||
support_structure = tree
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Fast
|
||||
name = Fast - Experimental
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
is_experimental = True
|
||||
material = generic_cpe_plus
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Fast
|
||||
name = Fast - Experimental
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
is_experimental = True
|
||||
material = generic_nylon-cf-slide
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
|
|
@ -12,6 +13,12 @@ variant = CC+ 0.4
|
|||
weight = -2
|
||||
|
||||
[values]
|
||||
bridge_skin_material_flow = 100
|
||||
bridge_skin_speed = 30
|
||||
bridge_wall_material_flow = 100
|
||||
bridge_wall_speed = 30
|
||||
cool_min_layer_time_fan_speed_max = 11
|
||||
retraction_prime_speed = 15
|
||||
support_structure = tree
|
||||
wall_overhang_speed_factors = [100,90,80,70,60,50]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Fast
|
||||
name = Fast - Experimental
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
is_experimental = True
|
||||
material = generic_pc
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Fast
|
||||
name = Fast - Experimental
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
is_experimental = True
|
||||
material = generic_petcf
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
|
|
@ -12,5 +13,12 @@ variant = CC+ 0.4
|
|||
weight = -2
|
||||
|
||||
[values]
|
||||
adhesion_type = skirt
|
||||
bridge_skin_material_flow = 100
|
||||
bridge_skin_speed = 30
|
||||
bridge_wall_material_flow = 100
|
||||
bridge_wall_speed = 30
|
||||
support_structure = tree
|
||||
switch_extruder_retraction_amount = 16
|
||||
wall_overhang_speed_factors = [100,90,80,70,60,50]
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Fast
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
material = generic_nylon-cf-slide
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = quality
|
||||
variant = CC+ 0.6
|
||||
weight = -2
|
||||
|
||||
[values]
|
||||
acceleration_roofing = =acceleration_topbottom/2
|
||||
bridge_enable_more_layers = True
|
||||
bridge_skin_density = 70
|
||||
bridge_skin_material_flow = 100
|
||||
bridge_skin_material_flow_2 = 70
|
||||
bridge_skin_speed = 30
|
||||
bridge_skin_speed_2 = =speed_print*2/3
|
||||
bridge_wall_material_flow = 100
|
||||
bridge_wall_min_length = 2
|
||||
bridge_wall_speed = 30
|
||||
cool_min_layer_time = 6
|
||||
cool_min_layer_time_fan_speed_max = 11
|
||||
cool_min_layer_time_overhang = 11
|
||||
cool_min_temperature = =material_print_temperature-10
|
||||
flooring_monotonic = False
|
||||
infill_material_flow = =material_flow if infill_sparse_density < 95 else 95
|
||||
infill_pattern = ='zigzag' if infill_sparse_density > 50 else 'grid'
|
||||
jerk_roofing = =jerk_print
|
||||
material_flow = 95
|
||||
retraction_hop_enabled = False
|
||||
retraction_prime_speed = 25
|
||||
roofing_line_width = 0.5
|
||||
roofing_material_flow = =skin_material_flow
|
||||
roofing_monotonic = False
|
||||
skin_material_flow = =0.95*material_flow
|
||||
skin_outline_count = 0
|
||||
speed_print = 80
|
||||
speed_roofing = 50
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0_roofing = =speed_roofing
|
||||
speed_wall_x_roofing = =speed_roofing
|
||||
support_bottom_distance = =support_z_distance
|
||||
support_line_width = 0.6
|
||||
support_structure = tree
|
||||
support_tree_tip_diameter = 2.0
|
||||
support_tree_top_rate = 10
|
||||
support_xy_distance = 1.2
|
||||
support_xy_distance_overhang = =1.5*machine_nozzle_size
|
||||
support_z_distance = =min(2*layer_height, 0.4)
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_0_inset = =0.05
|
||||
wall_line_width_0 = 0.5
|
||||
wall_overhang_speed_factors = [100,90,80,70,60,50]
|
||||
wall_x_material_flow = =material_flow
|
||||
xy_offset = 0.075
|
||||
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Fast
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
material = generic_petcf
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = quality
|
||||
variant = CC+ 0.6
|
||||
weight = -2
|
||||
|
||||
[values]
|
||||
acceleration_roofing = =acceleration_topbottom/2
|
||||
adhesion_type = skirt
|
||||
bridge_enable_more_layers = True
|
||||
bridge_skin_density = 70
|
||||
bridge_skin_material_flow = 100
|
||||
bridge_skin_material_flow_2 = 70
|
||||
bridge_skin_speed = 30
|
||||
bridge_skin_speed_2 = =speed_print*2/3
|
||||
bridge_wall_material_flow = 100
|
||||
bridge_wall_min_length = 2
|
||||
bridge_wall_speed = 30
|
||||
cool_min_layer_time = 6
|
||||
cool_min_layer_time_overhang = 11
|
||||
cool_min_temperature = =material_print_temperature-10
|
||||
flooring_monotonic = False
|
||||
infill_material_flow = =material_flow if infill_sparse_density < 95 else 95
|
||||
infill_pattern = ='zigzag' if infill_sparse_density > 50 else 'grid'
|
||||
jerk_roofing = =jerk_print
|
||||
material_pressure_advance_factor = 0.25
|
||||
retraction_hop_enabled = False
|
||||
retraction_prime_speed = 15
|
||||
roofing_line_width = 0.5
|
||||
roofing_material_flow = =skin_material_flow
|
||||
roofing_monotonic = False
|
||||
skin_material_flow = =0.95*material_flow
|
||||
skin_outline_count = 0
|
||||
skirt_height = 5
|
||||
speed_print = 80
|
||||
speed_roofing = 50
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0_roofing = =speed_roofing
|
||||
speed_wall_x_roofing = =speed_roofing
|
||||
support_bottom_distance = =support_z_distance
|
||||
support_interface_enable = False
|
||||
support_line_width = 0.6
|
||||
support_structure = tree
|
||||
support_tree_tip_diameter = 2.0
|
||||
support_tree_top_rate = 10
|
||||
support_xy_distance = 1.2
|
||||
support_xy_distance_overhang = =1.5*machine_nozzle_size
|
||||
support_z_distance = =min(2*layer_height, 0.4)
|
||||
switch_extruder_retraction_amount = 16
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_0_inset = =0.05
|
||||
wall_line_width_0 = 0.5
|
||||
wall_overhang_speed_factors = [100,90,80,70,60,50]
|
||||
wall_x_material_flow = =material_flow
|
||||
xy_offset = 0.075
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Extra Fine
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
global_quality = True
|
||||
quality_type = high
|
||||
setting_version = 25
|
||||
type = quality
|
||||
weight = 1
|
||||
|
||||
[values]
|
||||
layer_height = =round(0.06 * material_shrinkage_percentage_z / 100, 5)
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Sprint
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
global_quality = True
|
||||
quality_type = superdraft
|
||||
setting_version = 25
|
||||
type = quality
|
||||
weight = -4
|
||||
|
||||
[values]
|
||||
layer_height = =round(0.4 * material_shrinkage_percentage_z / 100, 5)
|
||||
|
||||
|
|
@ -1,3 +1,10 @@
|
|||
[5.10.2]
|
||||
|
||||
* UltiMaker S6 and S8 improvements:
|
||||
- Introduced the CC+ 0.6 core to the UltiMaker S6 and S8. This core delivers better results for demanding applications and will be replacing the CC+ 0.4 core.
|
||||
- Added new profiles for PC and CPE+ on UltiMaker S6 and S8
|
||||
- Updated the default support type for the PETG material for UltiMaker S6 and S8
|
||||
|
||||
[5.10.1]
|
||||
|
||||
* New features and improvements:
|
||||
|
|
|
|||
5
resources/themes/cura-light/icons/default/Circle.svg
Normal file
5
resources/themes/cura-light/icons/default/Circle.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M 12,2 C 6.5,2 2,6.5 2,12 2,17.5 6.5,22 12,22 17.5,22 22,17.5 22,12 22,6.5 17.5,2 12,2 Z m 0,18 C 7.6,20 4,16.4 4,12 4,7.6 7.6,4 12,4 c 4.4,0 8,3.6 8,8 0,4.4 -3.6,8 -8,8 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 313 B |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue