From ba878ccff085365e0cf6f420a7c20d3e309ccd26 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 12:08:16 +0200 Subject: [PATCH 01/34] fix luminance computation --- plugins/ImageReader/ImageReader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index e720ce4854..c59151f1cb 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -99,8 +99,8 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255) - height_data[y, x] = avg + luminance = (0.2126 * qRed(qrgb) + 0.7152 * qGreen(qrgb) + 0.0722 * qBlue(qrgb)) / 255 # fast computation ignoring gamma + height_data[y, x] = luminance Job.yieldThread() From 31683287505372d20d0e9488427c60af8d76a0f8 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 12:10:18 +0200 Subject: [PATCH 02/34] feat: use logarithmic conversion for lithophanes --- plugins/ImageReader/ConfigUI.qml | 22 ++++++++++++++++++++++ plugins/ImageReader/ImageReader.py | 16 ++++++++++++---- plugins/ImageReader/ImageReaderUI.py | 6 ++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 47ba10778c..ef28c174f2 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -143,6 +143,28 @@ UM.Dialog } } + UM.TooltipArea { + Layout.fillWidth:true + height: childrenRect.height + text: catalog.i18nc("@info:tooltip","For lithophanes a logarithmic function is more appropriate for most materials. For height maps the pixel values correspond to heights linearly.") + Row { + width: parent.width + + Label { + text: "Conversion" + width: 150 * screenScaleFactor + anchors.verticalCenter: parent.verticalCenter + } + ComboBox { + id: conversion + objectName: "Conversion" + model: [ catalog.i18nc("@item:inlistbox","Logarithmic"), catalog.i18nc("@item:inlistbox","Linear") ] + width: 180 * screenScaleFactor + onCurrentIndexChanged: { manager.onConvertFunctionChanged(currentIndex) } + } + } + } + UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index c59151f1cb..bfaa6eb48c 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -3,6 +3,8 @@ import numpy +import math + from PyQt5.QtGui import QImage, qRed, qGreen, qBlue from PyQt5.QtCore import Qt @@ -46,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function): scene_node = SceneNode() mesh = MeshBuilder() @@ -124,8 +126,14 @@ class ImageReader(MeshReader): Job.yieldThread() - height_data *= scale_vector.y - height_data += base_height + if use_logarithmic_function: + min_luminance = 2.0 ** (peak_height - base_height) + for (y, x) in numpy.ndindex(height_data.shape): + mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] + height_data[y, x] = peak_height - math.log(mapped_luminance, 2) + else: + height_data *= scale_vector.y + height_data += base_height heightmap_face_count = 2 * height_minus_one * width_minus_one total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2 diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 213468a2ab..c769a8c264 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -34,6 +34,7 @@ class ImageReaderUI(QObject): self.peak_height = 2.5 self.smoothing = 1 self.lighter_is_higher = False; + self.use_logarithmic_function = False; self._ui_lock = threading.Lock() self._cancelled = False @@ -144,3 +145,8 @@ class ImageReaderUI(QObject): @pyqtSlot(int) def onImageColorInvertChanged(self, value): self.lighter_is_higher = (value == 1) + + @pyqtSlot(int) + def onConvertFunctionChanged(self, value): + self.use_logarithmic_function = (value == 0) + From 9066f5f6d4f484cb2e3cfd8e83a79cfa39fb294f Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:18:07 +0200 Subject: [PATCH 03/34] fix translucency model using new permittance setting --- plugins/ImageReader/ConfigUI.qml | 23 +++++++++++++++++++++++ plugins/ImageReader/ImageReader.py | 9 +++++---- plugins/ImageReader/ImageReaderUI.py | 5 +++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index ef28c174f2..491594fa58 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -165,6 +165,29 @@ UM.Dialog } } + UM.TooltipArea { + Layout.fillWidth:true + height: childrenRect.height + text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter.") + Row { + width: parent.width + + Label { + text: catalog.i18nc("@action:label", "1mm Transmittance (%)") + width: 150 * screenScaleFactor + anchors.verticalCenter: parent.verticalCenter + } + TextField { + id: transmittance + objectName: "Transmittance" + focus: true + validator: RegExpValidator {regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/} + width: 180 * screenScaleFactor + onTextChanged: { manager.onTransmittanceChanged(text) } + } + } + } + UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index bfaa6eb48c..ce3cab0b8f 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -48,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function, self._ui.transmittance_1mm) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function, transmittance_1mm): scene_node = SceneNode() mesh = MeshBuilder() @@ -127,10 +127,11 @@ class ImageReader(MeshReader): Job.yieldThread() if use_logarithmic_function: - min_luminance = 2.0 ** (peak_height - base_height) + p = 1.0 / math.log(transmittance_1mm / 100.0, 2) + min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = peak_height - math.log(mapped_luminance, 2) + height_data[y, x] = peak_height - p * math.log(mapped_luminance, 2) else: height_data *= scale_vector.y height_data += base_height diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index c769a8c264..67d6444538 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,6 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_logarithmic_function = False; + self.transmittance_1mm = 40.0; self._ui_lock = threading.Lock() self._cancelled = False @@ -76,6 +77,7 @@ class ImageReaderUI(QObject): self._ui_view.findChild(QObject, "Base_Height").setProperty("text", str(self.base_height)) self._ui_view.findChild(QObject, "Peak_Height").setProperty("text", str(self.peak_height)) + self._ui_view.findChild(QObject, "Transmittance").setProperty("text", str(self.transmittance_1mm)) self._ui_view.findChild(QObject, "Smoothing").setProperty("value", self.smoothing) def _createConfigUI(self): @@ -150,3 +152,6 @@ class ImageReaderUI(QObject): def onConvertFunctionChanged(self, value): self.use_logarithmic_function = (value == 0) + @pyqtSlot(int) + def onTransmittanceChanged(self, value): + self.transmittance_1mm = value From beaa5e0b7a300e3eab1eeaff79749ce4fb934632 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:21:05 +0200 Subject: [PATCH 04/34] fix luminance computation --- plugins/ImageReader/ImageReader.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index ce3cab0b8f..50825f2464 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -101,8 +101,10 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - luminance = (0.2126 * qRed(qrgb) + 0.7152 * qGreen(qrgb) + 0.0722 * qBlue(qrgb)) / 255 # fast computation ignoring gamma - height_data[y, x] = luminance + if use_logarithmic_function: + height_data[y, x] = (0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2)) + else: + height_data[y, x] = (0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb)) / 255 # fast computation ignoring gamma and degamma Job.yieldThread() From 5b9a18f5dfc0b1fd15cc5cec3bed015b7f394d1c Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:27:51 +0200 Subject: [PATCH 05/34] make naming of Logarithmic Conversion Function intelligible --- plugins/ImageReader/ConfigUI.qml | 12 ++++++------ plugins/ImageReader/ImageReader.py | 8 ++++---- plugins/ImageReader/ImageReaderUI.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 491594fa58..8a4ac67b3e 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -146,21 +146,21 @@ UM.Dialog UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height - text: catalog.i18nc("@info:tooltip","For lithophanes a logarithmic function is more appropriate for most materials. For height maps the pixel values correspond to heights linearly.") + text: catalog.i18nc("@info:tooltip","For lithophanes a simple logarithmic model for translucency is available. For height maps the pixel values correspond to heights linearly.") Row { width: parent.width Label { - text: "Conversion" + text: "Color Model" width: 150 * screenScaleFactor anchors.verticalCenter: parent.verticalCenter } ComboBox { - id: conversion - objectName: "Conversion" - model: [ catalog.i18nc("@item:inlistbox","Logarithmic"), catalog.i18nc("@item:inlistbox","Linear") ] + id: color_model + objectName: "ColorModel" + model: [ catalog.i18nc("@item:inlistbox","Translucency"), catalog.i18nc("@item:inlistbox","Linear") ] width: 180 * screenScaleFactor - onCurrentIndexChanged: { manager.onConvertFunctionChanged(currentIndex) } + onCurrentIndexChanged: { manager.onColorModelChanged(currentIndex) } } } } diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 50825f2464..0086736f6d 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -48,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function, self._ui.transmittance_1mm) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_transparency_model, self._ui.transmittance_1mm) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function, transmittance_1mm): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_transparency_model, transmittance_1mm): scene_node = SceneNode() mesh = MeshBuilder() @@ -101,7 +101,7 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - if use_logarithmic_function: + if use_transparency_model: height_data[y, x] = (0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2)) else: height_data[y, x] = (0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb)) / 255 # fast computation ignoring gamma and degamma @@ -128,7 +128,7 @@ class ImageReader(MeshReader): Job.yieldThread() - if use_logarithmic_function: + if use_transparency_model: p = 1.0 / math.log(transmittance_1mm / 100.0, 2) min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 67d6444538..41d8741b38 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -34,7 +34,7 @@ class ImageReaderUI(QObject): self.peak_height = 2.5 self.smoothing = 1 self.lighter_is_higher = False; - self.use_logarithmic_function = False; + self.use_transparency_model = True; self.transmittance_1mm = 40.0; self._ui_lock = threading.Lock() @@ -149,8 +149,8 @@ class ImageReaderUI(QObject): self.lighter_is_higher = (value == 1) @pyqtSlot(int) - def onConvertFunctionChanged(self, value): - self.use_logarithmic_function = (value == 0) + def onColorModelChanged(self, value): + self.use_transparency_model = (value == 0) @pyqtSlot(int) def onTransmittanceChanged(self, value): From 3e0b756a6d9654ed180f97d47a8b0412dc063a66 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:37:14 +0200 Subject: [PATCH 06/34] explain litho transmittance better --- plugins/ImageReader/ConfigUI.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 8a4ac67b3e..842ca612d9 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -168,7 +168,7 @@ UM.Dialog UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height - text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter.") + text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter. Lowering this value increases the contrast in dark regions and decreases the contrast in light regions of the image.") Row { width: parent.width From 236b7574c0bf5415535fde6ca559ebfa7bc8eeb7 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:55:10 +0200 Subject: [PATCH 07/34] fix litho thickness computation --- plugins/ImageReader/ImageReader.py | 4 ++-- plugins/ImageReader/ImageReaderUI.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 0086736f6d..2084844548 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -108,7 +108,7 @@ class ImageReader(MeshReader): Job.yieldThread() - if not lighter_is_higher: + if lighter_is_higher is use_transparency_model: height_data = 1 - height_data for _ in range(0, blur_iterations): @@ -133,7 +133,7 @@ class ImageReader(MeshReader): min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = peak_height - p * math.log(mapped_luminance, 2) + height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) else: height_data *= scale_vector.y height_data += base_height diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 41d8741b38..0fb9ea78de 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,7 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_transparency_model = True; - self.transmittance_1mm = 40.0; + self.transmittance_1mm = 20.0; # based on pearl PLA self._ui_lock = threading.Lock() self._cancelled = False From e59641eb67663c302f0139421c94fde29db64de1 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 20:19:29 +0200 Subject: [PATCH 08/34] based transmittance on measurements on print ratio of luminance of 1.4mm thickness to luminance at 0.4mm --- plugins/ImageReader/ImageReaderUI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 0fb9ea78de..a61fabb742 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,7 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_transparency_model = True; - self.transmittance_1mm = 20.0; # based on pearl PLA + self.transmittance_1mm = 50.0; # based on pearl PLA self._ui_lock = threading.Lock() self._cancelled = False From b3d7887d4d6b60a061a2fb7097a57a0988d15b26 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 12:08:16 +0200 Subject: [PATCH 09/34] fix luminance computation --- plugins/ImageReader/ImageReader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index e720ce4854..c59151f1cb 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -99,8 +99,8 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255) - height_data[y, x] = avg + luminance = (0.2126 * qRed(qrgb) + 0.7152 * qGreen(qrgb) + 0.0722 * qBlue(qrgb)) / 255 # fast computation ignoring gamma + height_data[y, x] = luminance Job.yieldThread() From 88b424d36aa43d863a3e937187337af378905908 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 12:10:18 +0200 Subject: [PATCH 10/34] feat: use logarithmic conversion for lithophanes --- plugins/ImageReader/ConfigUI.qml | 22 ++++++++++++++++++++++ plugins/ImageReader/ImageReader.py | 16 ++++++++++++---- plugins/ImageReader/ImageReaderUI.py | 6 ++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 47ba10778c..ef28c174f2 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -143,6 +143,28 @@ UM.Dialog } } + UM.TooltipArea { + Layout.fillWidth:true + height: childrenRect.height + text: catalog.i18nc("@info:tooltip","For lithophanes a logarithmic function is more appropriate for most materials. For height maps the pixel values correspond to heights linearly.") + Row { + width: parent.width + + Label { + text: "Conversion" + width: 150 * screenScaleFactor + anchors.verticalCenter: parent.verticalCenter + } + ComboBox { + id: conversion + objectName: "Conversion" + model: [ catalog.i18nc("@item:inlistbox","Logarithmic"), catalog.i18nc("@item:inlistbox","Linear") ] + width: 180 * screenScaleFactor + onCurrentIndexChanged: { manager.onConvertFunctionChanged(currentIndex) } + } + } + } + UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index c59151f1cb..bfaa6eb48c 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -3,6 +3,8 @@ import numpy +import math + from PyQt5.QtGui import QImage, qRed, qGreen, qBlue from PyQt5.QtCore import Qt @@ -46,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function): scene_node = SceneNode() mesh = MeshBuilder() @@ -124,8 +126,14 @@ class ImageReader(MeshReader): Job.yieldThread() - height_data *= scale_vector.y - height_data += base_height + if use_logarithmic_function: + min_luminance = 2.0 ** (peak_height - base_height) + for (y, x) in numpy.ndindex(height_data.shape): + mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] + height_data[y, x] = peak_height - math.log(mapped_luminance, 2) + else: + height_data *= scale_vector.y + height_data += base_height heightmap_face_count = 2 * height_minus_one * width_minus_one total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2 diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 213468a2ab..c769a8c264 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -34,6 +34,7 @@ class ImageReaderUI(QObject): self.peak_height = 2.5 self.smoothing = 1 self.lighter_is_higher = False; + self.use_logarithmic_function = False; self._ui_lock = threading.Lock() self._cancelled = False @@ -144,3 +145,8 @@ class ImageReaderUI(QObject): @pyqtSlot(int) def onImageColorInvertChanged(self, value): self.lighter_is_higher = (value == 1) + + @pyqtSlot(int) + def onConvertFunctionChanged(self, value): + self.use_logarithmic_function = (value == 0) + From b88183f4a1f218b7c87d9ccd64a2fa1d95a68f9e Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:18:07 +0200 Subject: [PATCH 11/34] fix translucency model using new permittance setting --- plugins/ImageReader/ConfigUI.qml | 23 +++++++++++++++++++++++ plugins/ImageReader/ImageReader.py | 9 +++++---- plugins/ImageReader/ImageReaderUI.py | 5 +++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index ef28c174f2..491594fa58 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -165,6 +165,29 @@ UM.Dialog } } + UM.TooltipArea { + Layout.fillWidth:true + height: childrenRect.height + text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter.") + Row { + width: parent.width + + Label { + text: catalog.i18nc("@action:label", "1mm Transmittance (%)") + width: 150 * screenScaleFactor + anchors.verticalCenter: parent.verticalCenter + } + TextField { + id: transmittance + objectName: "Transmittance" + focus: true + validator: RegExpValidator {regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/} + width: 180 * screenScaleFactor + onTextChanged: { manager.onTransmittanceChanged(text) } + } + } + } + UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index bfaa6eb48c..ce3cab0b8f 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -48,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function, self._ui.transmittance_1mm) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function, transmittance_1mm): scene_node = SceneNode() mesh = MeshBuilder() @@ -127,10 +127,11 @@ class ImageReader(MeshReader): Job.yieldThread() if use_logarithmic_function: - min_luminance = 2.0 ** (peak_height - base_height) + p = 1.0 / math.log(transmittance_1mm / 100.0, 2) + min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = peak_height - math.log(mapped_luminance, 2) + height_data[y, x] = peak_height - p * math.log(mapped_luminance, 2) else: height_data *= scale_vector.y height_data += base_height diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index c769a8c264..67d6444538 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,6 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_logarithmic_function = False; + self.transmittance_1mm = 40.0; self._ui_lock = threading.Lock() self._cancelled = False @@ -76,6 +77,7 @@ class ImageReaderUI(QObject): self._ui_view.findChild(QObject, "Base_Height").setProperty("text", str(self.base_height)) self._ui_view.findChild(QObject, "Peak_Height").setProperty("text", str(self.peak_height)) + self._ui_view.findChild(QObject, "Transmittance").setProperty("text", str(self.transmittance_1mm)) self._ui_view.findChild(QObject, "Smoothing").setProperty("value", self.smoothing) def _createConfigUI(self): @@ -150,3 +152,6 @@ class ImageReaderUI(QObject): def onConvertFunctionChanged(self, value): self.use_logarithmic_function = (value == 0) + @pyqtSlot(int) + def onTransmittanceChanged(self, value): + self.transmittance_1mm = value From 2b55b85a1234791babf7700a362c940d4ca42473 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:21:05 +0200 Subject: [PATCH 12/34] fix luminance computation --- plugins/ImageReader/ImageReader.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index ce3cab0b8f..50825f2464 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -101,8 +101,10 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - luminance = (0.2126 * qRed(qrgb) + 0.7152 * qGreen(qrgb) + 0.0722 * qBlue(qrgb)) / 255 # fast computation ignoring gamma - height_data[y, x] = luminance + if use_logarithmic_function: + height_data[y, x] = (0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2)) + else: + height_data[y, x] = (0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb)) / 255 # fast computation ignoring gamma and degamma Job.yieldThread() From a8b3d7e49ddcbc33df41dd6c4ec71b619511b966 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:27:51 +0200 Subject: [PATCH 13/34] make naming of Logarithmic Conversion Function intelligible --- plugins/ImageReader/ConfigUI.qml | 12 ++++++------ plugins/ImageReader/ImageReader.py | 8 ++++---- plugins/ImageReader/ImageReaderUI.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 491594fa58..8a4ac67b3e 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -146,21 +146,21 @@ UM.Dialog UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height - text: catalog.i18nc("@info:tooltip","For lithophanes a logarithmic function is more appropriate for most materials. For height maps the pixel values correspond to heights linearly.") + text: catalog.i18nc("@info:tooltip","For lithophanes a simple logarithmic model for translucency is available. For height maps the pixel values correspond to heights linearly.") Row { width: parent.width Label { - text: "Conversion" + text: "Color Model" width: 150 * screenScaleFactor anchors.verticalCenter: parent.verticalCenter } ComboBox { - id: conversion - objectName: "Conversion" - model: [ catalog.i18nc("@item:inlistbox","Logarithmic"), catalog.i18nc("@item:inlistbox","Linear") ] + id: color_model + objectName: "ColorModel" + model: [ catalog.i18nc("@item:inlistbox","Translucency"), catalog.i18nc("@item:inlistbox","Linear") ] width: 180 * screenScaleFactor - onCurrentIndexChanged: { manager.onConvertFunctionChanged(currentIndex) } + onCurrentIndexChanged: { manager.onColorModelChanged(currentIndex) } } } } diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 50825f2464..0086736f6d 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -48,9 +48,9 @@ class ImageReader(MeshReader): def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) - return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_logarithmic_function, self._ui.transmittance_1mm) + return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_transparency_model, self._ui.transmittance_1mm) - def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_logarithmic_function, transmittance_1mm): + def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_transparency_model, transmittance_1mm): scene_node = SceneNode() mesh = MeshBuilder() @@ -101,7 +101,7 @@ class ImageReader(MeshReader): for x in range(0, width): for y in range(0, height): qrgb = img.pixel(x, y) - if use_logarithmic_function: + if use_transparency_model: height_data[y, x] = (0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2)) else: height_data[y, x] = (0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb)) / 255 # fast computation ignoring gamma and degamma @@ -128,7 +128,7 @@ class ImageReader(MeshReader): Job.yieldThread() - if use_logarithmic_function: + if use_transparency_model: p = 1.0 / math.log(transmittance_1mm / 100.0, 2) min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 67d6444538..41d8741b38 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -34,7 +34,7 @@ class ImageReaderUI(QObject): self.peak_height = 2.5 self.smoothing = 1 self.lighter_is_higher = False; - self.use_logarithmic_function = False; + self.use_transparency_model = True; self.transmittance_1mm = 40.0; self._ui_lock = threading.Lock() @@ -149,8 +149,8 @@ class ImageReaderUI(QObject): self.lighter_is_higher = (value == 1) @pyqtSlot(int) - def onConvertFunctionChanged(self, value): - self.use_logarithmic_function = (value == 0) + def onColorModelChanged(self, value): + self.use_transparency_model = (value == 0) @pyqtSlot(int) def onTransmittanceChanged(self, value): From 364483f6533905b710b5c2d8f2ff3d77b5d73598 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:37:14 +0200 Subject: [PATCH 14/34] explain litho transmittance better --- plugins/ImageReader/ConfigUI.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 8a4ac67b3e..842ca612d9 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -168,7 +168,7 @@ UM.Dialog UM.TooltipArea { Layout.fillWidth:true height: childrenRect.height - text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter.") + text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter. Lowering this value increases the contrast in dark regions and decreases the contrast in light regions of the image.") Row { width: parent.width From 5915947a7a54502b48367a9ba5e47e5888d5351b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 14:55:10 +0200 Subject: [PATCH 15/34] fix litho thickness computation --- plugins/ImageReader/ImageReader.py | 4 ++-- plugins/ImageReader/ImageReaderUI.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 0086736f6d..2084844548 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -108,7 +108,7 @@ class ImageReader(MeshReader): Job.yieldThread() - if not lighter_is_higher: + if lighter_is_higher is use_transparency_model: height_data = 1 - height_data for _ in range(0, blur_iterations): @@ -133,7 +133,7 @@ class ImageReader(MeshReader): min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = peak_height - p * math.log(mapped_luminance, 2) + height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) else: height_data *= scale_vector.y height_data += base_height diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 41d8741b38..0fb9ea78de 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,7 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_transparency_model = True; - self.transmittance_1mm = 40.0; + self.transmittance_1mm = 20.0; # based on pearl PLA self._ui_lock = threading.Lock() self._cancelled = False From 449ad198225bb3df6c68a699b71e02660ad233ba Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Mon, 20 May 2019 20:19:29 +0200 Subject: [PATCH 16/34] based transmittance on measurements on print ratio of luminance of 1.4mm thickness to luminance at 0.4mm --- plugins/ImageReader/ImageReaderUI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ImageReader/ImageReaderUI.py b/plugins/ImageReader/ImageReaderUI.py index 0fb9ea78de..a61fabb742 100644 --- a/plugins/ImageReader/ImageReaderUI.py +++ b/plugins/ImageReader/ImageReaderUI.py @@ -35,7 +35,7 @@ class ImageReaderUI(QObject): self.smoothing = 1 self.lighter_is_higher = False; self.use_transparency_model = True; - self.transmittance_1mm = 20.0; # based on pearl PLA + self.transmittance_1mm = 50.0; # based on pearl PLA self._ui_lock = threading.Lock() self._cancelled = False From 03f7fab1247cd50ea409a4129feb0b92174ca402 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 24 Oct 2019 17:26:20 +0200 Subject: [PATCH 17/34] lil fix --- plugins/ImageReader/ImageReader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 2084844548..a77cc9b0ed 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -108,7 +108,7 @@ class ImageReader(MeshReader): Job.yieldThread() - if lighter_is_higher is use_transparency_model: + if lighter_is_higher == use_transparency_model: height_data = 1 - height_data for _ in range(0, blur_iterations): @@ -129,11 +129,11 @@ class ImageReader(MeshReader): Job.yieldThread() if use_transparency_model: - p = 1.0 / math.log(transmittance_1mm / 100.0, 2) + p = 1.0 / math.log(transmittance_1mm / 100.0, 2) # base doesn't matter here. use base 2 for fast computation min_luminance = 2.0 ** ((peak_height - base_height) / p) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) + height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) # use same base as a couple lines above this else: height_data *= scale_vector.y height_data += base_height From 6e65fe57727acf62cf8669557d7242b1ab9a1059 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Fri, 25 Oct 2019 09:57:39 +0200 Subject: [PATCH 18/34] Only show the transmittance input when the color model is Translucency CURA-6540 --- plugins/ImageReader/ConfigUI.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 842ca612d9..72ad79c05e 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -169,6 +169,7 @@ UM.Dialog Layout.fillWidth:true height: childrenRect.height text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter. Lowering this value increases the contrast in dark regions and decreases the contrast in light regions of the image.") + visible: color_model.currentText == catalog.i18nc("@item:inlistbox","Translucency") Row { width: parent.width From 76a538322dba877cc520224932daf6fa93b12d03 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Fri, 25 Oct 2019 10:36:20 +0200 Subject: [PATCH 19/34] simplify formula to make it more numerically stable --- plugins/ImageReader/ImageReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index a77cc9b0ed..babd9f45cb 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -130,7 +130,7 @@ class ImageReader(MeshReader): if use_transparency_model: p = 1.0 / math.log(transmittance_1mm / 100.0, 2) # base doesn't matter here. use base 2 for fast computation - min_luminance = 2.0 ** ((peak_height - base_height) / p) + min_luminance = (transmittance_1mm / 100.0) ** (peak_height - base_height) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) # use same base as a couple lines above this From a01f91d4e366d1d1dbec2eb04ceb0e8f6398550d Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Fri, 25 Oct 2019 10:36:55 +0200 Subject: [PATCH 20/34] omit irrelevant log base --- plugins/ImageReader/ImageReader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index babd9f45cb..9bcd245615 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -129,11 +129,11 @@ class ImageReader(MeshReader): Job.yieldThread() if use_transparency_model: - p = 1.0 / math.log(transmittance_1mm / 100.0, 2) # base doesn't matter here. use base 2 for fast computation + p = 1.0 / math.log(transmittance_1mm / 100.0) # log-base doesn't matter here. Precompute this value for faster computation of each pixel. min_luminance = (transmittance_1mm / 100.0) ** (peak_height - base_height) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = base_height + p * math.log(mapped_luminance, 2) # use same base as a couple lines above this + height_data[y, x] = base_height + p * math.log(mapped_luminance) # use same base as a couple lines above this else: height_data *= scale_vector.y height_data += base_height From 1c134026706d270041a7e97d043cc6d93f505dfb Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Fri, 25 Oct 2019 10:38:23 +0200 Subject: [PATCH 21/34] rename single letter variable --- plugins/ImageReader/ImageReader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 9bcd245615..d6c2827d16 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -129,11 +129,11 @@ class ImageReader(MeshReader): Job.yieldThread() if use_transparency_model: - p = 1.0 / math.log(transmittance_1mm / 100.0) # log-base doesn't matter here. Precompute this value for faster computation of each pixel. + divisor = 1.0 / math.log(transmittance_1mm / 100.0) # log-base doesn't matter here. Precompute this value for faster computation of each pixel. min_luminance = (transmittance_1mm / 100.0) ** (peak_height - base_height) for (y, x) in numpy.ndindex(height_data.shape): mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x] - height_data[y, x] = base_height + p * math.log(mapped_luminance) # use same base as a couple lines above this + height_data[y, x] = base_height + divisor * math.log(mapped_luminance) # use same base as a couple lines above this else: height_data *= scale_vector.y height_data += base_height From 355ebd4e71f332cb97a1b3965b47636488a92cb7 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 31 Oct 2019 13:20:35 +0100 Subject: [PATCH 22/34] Remove override for prime tower position The formula in the fdmprinter definition does it's job, so no need to change it! CURA-6943 --- resources/definitions/ultimaker_s3.def.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/definitions/ultimaker_s3.def.json b/resources/definitions/ultimaker_s3.def.json index 7348c14a2a..efdc7cca0a 100644 --- a/resources/definitions/ultimaker_s3.def.json +++ b/resources/definitions/ultimaker_s3.def.json @@ -67,8 +67,6 @@ "extruder_prime_pos_abs": { "default_value": true }, "machine_start_gcode": { "default_value": "" }, "machine_end_gcode": { "default_value": "" }, - "prime_tower_position_x": { "value": "345" }, - "prime_tower_position_y": { "value": "222.5" }, "prime_blob_enable": { "enabled": true, "default_value": false }, "speed_travel": From 1af1a8ddcb13ab2a472ace4dc7b1f4ab59d7802f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 31 Oct 2019 13:26:49 +0100 Subject: [PATCH 23/34] Rename "smooth" intent to draft CURA-6890 --- cura/Machines/Models/IntentCategoryModel.py | 8 ++++---- .../um_s3_aa0.4_ABS_Draft_Print_Quick.inst.cfg | 2 +- .../um_s3_aa0.4_PLA_Draft_Print_Quick.inst.cfg | 2 +- .../um_s3_aa0.4_TPLA_Draft_Print_Quick.inst.cfg | 2 +- .../um_s5_aa0.4_ABS_Draft_Print_Quick.inst.cfg | 2 +- .../um_s5_aa0.4_PLA_Draft_Print_Quick.inst.cfg | 2 +- .../um_s5_aa0.4_TPLA_Draft_Print_Quick.inst.cfg | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cura/Machines/Models/IntentCategoryModel.py b/cura/Machines/Models/IntentCategoryModel.py index 20d07864bf..a968d12b7a 100644 --- a/cura/Machines/Models/IntentCategoryModel.py +++ b/cura/Machines/Models/IntentCategoryModel.py @@ -41,11 +41,11 @@ class IntentCategoryModel(ListModel): } _translations["engineering"] = { "name": catalog.i18nc("@label", "Engineering"), - "description": catalog.i18nc("@text", "Suitable for engineering work") + "description": catalog.i18nc("@text", "Optimized for higher accuracy") } - _translations["smooth"] = { - "name": catalog.i18nc("@label", "Smooth"), - "description": catalog.i18nc("@text", "Optimized for a smooth surfaces") + _translations["quick"] = { + "name": catalog.i18nc("@label", "Draft"), + "description": catalog.i18nc("@text", "Optimized for fast results") } diff --git a/resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Print_Quick.inst.cfg b/resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Print_Quick.inst.cfg index b41f636e63..92d91840b5 100644 --- a/resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Print_Quick.inst.cfg +++ b/resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Print_Quick.inst.cfg @@ -6,7 +6,7 @@ definition = ultimaker_s3 [metadata] setting_version = 10 type = intent -intent_category = smooth +intent_category = quick quality_type = draft material = generic_abs variant = AA 0.4 diff --git a/resources/intent/ultimaker_s3/um_s3_aa0.4_PLA_Draft_Print_Quick.inst.cfg b/resources/intent/ultimaker_s3/um_s3_aa0.4_PLA_Draft_Print_Quick.inst.cfg index 8095a53141..1d990b83c0 100644 --- a/resources/intent/ultimaker_s3/um_s3_aa0.4_PLA_Draft_Print_Quick.inst.cfg +++ b/resources/intent/ultimaker_s3/um_s3_aa0.4_PLA_Draft_Print_Quick.inst.cfg @@ -6,7 +6,7 @@ definition = ultimaker_s3 [metadata] setting_version = 10 type = intent -intent_category = smooth +intent_category = quick quality_type = draft material = generic_pla variant = AA 0.4 diff --git a/resources/intent/ultimaker_s3/um_s3_aa0.4_TPLA_Draft_Print_Quick.inst.cfg b/resources/intent/ultimaker_s3/um_s3_aa0.4_TPLA_Draft_Print_Quick.inst.cfg index edae808491..020a4bbc99 100644 --- a/resources/intent/ultimaker_s3/um_s3_aa0.4_TPLA_Draft_Print_Quick.inst.cfg +++ b/resources/intent/ultimaker_s3/um_s3_aa0.4_TPLA_Draft_Print_Quick.inst.cfg @@ -6,7 +6,7 @@ definition = ultimaker_s3 [metadata] setting_version = 10 type = intent -intent_category = smooth +intent_category = quick quality_type = draft material = generic_tough_pla variant = AA 0.4 diff --git a/resources/intent/ultimaker_s5/um_s5_aa0.4_ABS_Draft_Print_Quick.inst.cfg b/resources/intent/ultimaker_s5/um_s5_aa0.4_ABS_Draft_Print_Quick.inst.cfg index f627fbf74b..14bb0e0f54 100644 --- a/resources/intent/ultimaker_s5/um_s5_aa0.4_ABS_Draft_Print_Quick.inst.cfg +++ b/resources/intent/ultimaker_s5/um_s5_aa0.4_ABS_Draft_Print_Quick.inst.cfg @@ -6,7 +6,7 @@ definition = ultimaker_s5 [metadata] setting_version = 10 type = intent -intent_category = smooth +intent_category = quick quality_type = draft material = generic_abs variant = AA 0.4 diff --git a/resources/intent/ultimaker_s5/um_s5_aa0.4_PLA_Draft_Print_Quick.inst.cfg b/resources/intent/ultimaker_s5/um_s5_aa0.4_PLA_Draft_Print_Quick.inst.cfg index 553a68201d..86062ecf19 100644 --- a/resources/intent/ultimaker_s5/um_s5_aa0.4_PLA_Draft_Print_Quick.inst.cfg +++ b/resources/intent/ultimaker_s5/um_s5_aa0.4_PLA_Draft_Print_Quick.inst.cfg @@ -6,7 +6,7 @@ definition = ultimaker_s5 [metadata] setting_version = 10 type = intent -intent_category = smooth +intent_category = quick quality_type = draft material = generic_pla variant = AA 0.4 diff --git a/resources/intent/ultimaker_s5/um_s5_aa0.4_TPLA_Draft_Print_Quick.inst.cfg b/resources/intent/ultimaker_s5/um_s5_aa0.4_TPLA_Draft_Print_Quick.inst.cfg index 458b283dd8..3a6e19c64c 100644 --- a/resources/intent/ultimaker_s5/um_s5_aa0.4_TPLA_Draft_Print_Quick.inst.cfg +++ b/resources/intent/ultimaker_s5/um_s5_aa0.4_TPLA_Draft_Print_Quick.inst.cfg @@ -6,7 +6,7 @@ definition = ultimaker_s5 [metadata] setting_version = 10 type = intent -intent_category = smooth +intent_category = quick quality_type = draft material = generic_tough_pla variant = AA 0.4 From 05b1ce965889b330fc713a44e4de33995565ec94 Mon Sep 17 00:00:00 2001 From: THeijmans Date: Thu, 31 Oct 2019 17:06:05 +0100 Subject: [PATCH 24/34] Removes 0.2mm layer height visual intent profiles --- .../um_s3_aa0.4_ABS_Draft_Visual.inst.cfg | 17 ----------------- .../um_s3_aa0.4_PLA_Draft_Visual.inst.cfg | 17 ----------------- .../um_s3_aa0.4_TPLA_Draft_Visual.inst.cfg | 17 ----------------- .../um_s5_aa0.4_ABS_Draft_Visual.inst.cfg | 16 ---------------- .../um_s5_aa0.4_PLA_Draft_Visual.inst.cfg | 17 ----------------- .../um_s5_aa0.4_TPLA_Draft_Visual.inst.cfg | 16 ---------------- 6 files changed, 100 deletions(-) delete mode 100644 resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Visual.inst.cfg delete mode 100644 resources/intent/ultimaker_s3/um_s3_aa0.4_PLA_Draft_Visual.inst.cfg delete mode 100644 resources/intent/ultimaker_s3/um_s3_aa0.4_TPLA_Draft_Visual.inst.cfg delete mode 100644 resources/intent/ultimaker_s5/um_s5_aa0.4_ABS_Draft_Visual.inst.cfg delete mode 100644 resources/intent/ultimaker_s5/um_s5_aa0.4_PLA_Draft_Visual.inst.cfg delete mode 100644 resources/intent/ultimaker_s5/um_s5_aa0.4_TPLA_Draft_Visual.inst.cfg diff --git a/resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Visual.inst.cfg b/resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Visual.inst.cfg deleted file mode 100644 index 49672fcb72..0000000000 --- a/resources/intent/ultimaker_s3/um_s3_aa0.4_ABS_Draft_Visual.inst.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[general] -version = 4 -name = Visual -definition = ultimaker_s3 - -[metadata] -setting_version = 10 -type = intent -intent_category = visual -quality_type = draft -material = generic_abs -variant = AA 0.4 - -[values] -speed_infill = 50 -wall_thickness = =wall_line_width * 3 -top_bottom_thickness = =wall_thickness diff --git a/resources/intent/ultimaker_s3/um_s3_aa0.4_PLA_Draft_Visual.inst.cfg b/resources/intent/ultimaker_s3/um_s3_aa0.4_PLA_Draft_Visual.inst.cfg deleted file mode 100644 index 0c39d43c6a..0000000000 --- a/resources/intent/ultimaker_s3/um_s3_aa0.4_PLA_Draft_Visual.inst.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[general] -version = 4 -name = Visual -definition = ultimaker_s3 - -[metadata] -setting_version = 10 -type = intent -quality_type = draft -intent_category = visual -material = generic_pla -variant = AA 0.4 - -[values] -speed_infill = 50 -wall_thickness = =wall_line_width * 3 -top_bottom_thickness = =wall_thickness diff --git a/resources/intent/ultimaker_s3/um_s3_aa0.4_TPLA_Draft_Visual.inst.cfg b/resources/intent/ultimaker_s3/um_s3_aa0.4_TPLA_Draft_Visual.inst.cfg deleted file mode 100644 index f52eb22ec2..0000000000 --- a/resources/intent/ultimaker_s3/um_s3_aa0.4_TPLA_Draft_Visual.inst.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[general] -version = 4 -name = Visual -definition = ultimaker_s3 - -[metadata] -setting_version = 10 -type = intent -quality_type = draft -intent_category = visual -material = generic_tough_pla -variant = AA 0.4 - -[values] -speed_infill = 50 -wall_thickness = =wall_line_width * 3 -top_bottom_thickness = =wall_thickness diff --git a/resources/intent/ultimaker_s5/um_s5_aa0.4_ABS_Draft_Visual.inst.cfg b/resources/intent/ultimaker_s5/um_s5_aa0.4_ABS_Draft_Visual.inst.cfg deleted file mode 100644 index 4f7ee148f9..0000000000 --- a/resources/intent/ultimaker_s5/um_s5_aa0.4_ABS_Draft_Visual.inst.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[general] -version = 4 -name = Visual -definition = ultimaker_s5 - -[metadata] -setting_version = 10 -type = intent -intent_category = visualquality_type = draft -material = generic_abs -variant = AA 0.4 - -[values] -speed_infill = 50 -wall_thickness = =wall_line_width * 3 -top_bottom_thickness = =wall_thickness diff --git a/resources/intent/ultimaker_s5/um_s5_aa0.4_PLA_Draft_Visual.inst.cfg b/resources/intent/ultimaker_s5/um_s5_aa0.4_PLA_Draft_Visual.inst.cfg deleted file mode 100644 index a71bb898d3..0000000000 --- a/resources/intent/ultimaker_s5/um_s5_aa0.4_PLA_Draft_Visual.inst.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[general] -version = 4 -name = Visual -definition = ultimaker_s5 - -[metadata] -setting_version = 10 -type = intent -quality_type = draft -intent_category = visual -material = generic_pla -variant = AA 0.4 - -[values] -speed_infill = 50 -wall_thickness = =wall_line_width * 3 -top_bottom_thickness = =wall_thickness diff --git a/resources/intent/ultimaker_s5/um_s5_aa0.4_TPLA_Draft_Visual.inst.cfg b/resources/intent/ultimaker_s5/um_s5_aa0.4_TPLA_Draft_Visual.inst.cfg deleted file mode 100644 index b522d262af..0000000000 --- a/resources/intent/ultimaker_s5/um_s5_aa0.4_TPLA_Draft_Visual.inst.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[general] -version = 4 -name = Visual -definition = ultimaker_s5 - -[metadata] -setting_version = 10 -type = intent -intent_category = visual -material = generic_tough_pla -variant = AA 0.4 - -[values] -speed_infill = 50 -wall_thickness = =wall_line_width * 3 -top_bottom_thickness = =wall_thickness From 90f580494b2445abd013bf73ddd1a5762b2e5517 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Fri, 1 Nov 2019 10:31:07 +0100 Subject: [PATCH 25/34] Change default image reader algorithm from logarithmic to linear The log curve might have some benefits but is also harder to understand and configure, so let's keep the default at linear. CURA-6540 --- plugins/ImageReader/ConfigUI.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ImageReader/ConfigUI.qml b/plugins/ImageReader/ConfigUI.qml index 72ad79c05e..0429fae4e1 100644 --- a/plugins/ImageReader/ConfigUI.qml +++ b/plugins/ImageReader/ConfigUI.qml @@ -158,7 +158,7 @@ UM.Dialog ComboBox { id: color_model objectName: "ColorModel" - model: [ catalog.i18nc("@item:inlistbox","Translucency"), catalog.i18nc("@item:inlistbox","Linear") ] + model: [ catalog.i18nc("@item:inlistbox","Linear"), catalog.i18nc("@item:inlistbox","Translucency") ] width: 180 * screenScaleFactor onCurrentIndexChanged: { manager.onColorModelChanged(currentIndex) } } From eaedca191743513095a8b2fc82fde42b32561f29 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Nov 2019 11:54:17 +0100 Subject: [PATCH 26/34] Convert unit of adaptive layers threshold and change name This usage is hopefully more intuitive than the previous one. --- .../VersionUpgrade43to44/VersionUpgrade43to44.py | 7 +++++++ resources/definitions/fdmprinter.def.json | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade43to44/VersionUpgrade43to44.py b/plugins/VersionUpgrade/VersionUpgrade43to44/VersionUpgrade43to44.py index e8eefb1bb0..40927fe3a0 100644 --- a/plugins/VersionUpgrade/VersionUpgrade43to44/VersionUpgrade43to44.py +++ b/plugins/VersionUpgrade/VersionUpgrade43to44/VersionUpgrade43to44.py @@ -66,6 +66,13 @@ class VersionUpgrade43to44(VersionUpgrade): # Alternate skin rotation should be translated to top/bottom line directions. if "skin_alternate_rotation" in parser["values"] and parseBool(parser["values"]["skin_alternate_rotation"]): parser["values"]["skin_angles"] = "[45, 135, 0, 90]" + # Unit of adaptive layers topography size changed. + if "adaptive_layer_height_threshold" in parser["values"]: + val = parser["values"]["adaptive_layer_height_threshold"] + if val.startswith("="): + val = val[1:] + val = "=({val}) / 1000".format(val = val) # Convert microns to millimetres. Works even if the profile contained a formula. + parser["values"]["adaptive_layer_height_threshold"] = val result = io.StringIO() parser.write(result) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 8511d9e969..90d6c8e2d8 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -7074,11 +7074,12 @@ }, "adaptive_layer_height_threshold": { - "label": "Adaptive Layers Threshold", - "description": "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer.", + "label": "Adaptive Layers Topography Size", + "description": "Target horizontal distance between two adjacent layers. Reducing this setting causes thinner layers to be used to bring the edges of the layers closer together.", "type": "float", "enabled": "adaptive_layer_height_enabled", - "default_value": 200.0, + "default_value": 0.2, + "unit": "mm", "settable_per_mesh": false, "settable_per_extruder": false, "settable_per_meshgroup": false From 36d4162f35a26a8fa738cca07ea3f12df33d3439 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Nov 2019 13:19:16 +0100 Subject: [PATCH 27/34] Fix fallback to global if extruder_nr doesn't exist Fixes #6590. --- plugins/CuraEngineBackend/StartSliceJob.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 29a6d8ff30..43d54d8b12 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2019 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import numpy @@ -72,7 +72,7 @@ class GcodeStartEndFormatter(Formatter): value = default_value_str # "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value. if key in kwargs["-1"]: - value = kwargs["-1"] + value = kwargs["-1"][key] if str(extruder_nr) in kwargs and key in kwargs[str(extruder_nr)]: value = kwargs[str(extruder_nr)][key] From 315b93a1525ba53f520f12b09669668266f12c6c Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Fri, 1 Nov 2019 13:54:52 +0100 Subject: [PATCH 28/34] Draw the NoIntentIcon over the intent description hover area. This fixes a bug where the NoIntent tooltip was hidden by the intent description CURA-6936 --- .../RecommendedQualityProfileSelector.qml | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml index 3c7caad470..5e58faec78 100644 --- a/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml +++ b/resources/qml/PrintSetupSelector/Recommended/RecommendedQualityProfileSelector.qml @@ -124,18 +124,6 @@ Item elide: Text.ElideRight } - NoIntentIcon - { - affected_extruders: Cura.MachineManager.extruderPositionsWithNonActiveIntent - intent_type: model.name - anchors.right: intentCategoryLabel.right - anchors.rightMargin: UM.Theme.getSize("narrow_margin").width - width: intentCategoryLabel.height * 0.75 - anchors.verticalCenter: parent.verticalCenter - height: width - visible: Cura.MachineManager.activeIntentCategory == model.intent_category && affected_extruders.length - } - Cura.RadioCheckbar { anchors @@ -164,8 +152,9 @@ Item isCheckedFunction: checkedFunction } - MouseArea // tooltip hover area + MouseArea // Intent description tooltip hover area { + id: intentDescriptionHoverArea anchors.fill: parent hoverEnabled: true enabled: model.description !== undefined @@ -181,6 +170,20 @@ Item } onExited: base.hideTooltip() } + + NoIntentIcon // This icon has hover priority over intentDescriptionHoverArea, so draw it above it. + { + affected_extruders: Cura.MachineManager.extruderPositionsWithNonActiveIntent + intent_type: model.name + anchors.right: intentCategoryLabel.right + anchors.rightMargin: UM.Theme.getSize("narrow_margin").width + width: intentCategoryLabel.height * 0.75 + anchors.verticalCenter: parent.verticalCenter + height: width + visible: Cura.MachineManager.activeIntentCategory == model.intent_category && affected_extruders.length + } + + } } From 852da51f6550aa5bad9257bcfacc3bbde369fc3e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Nov 2019 13:56:10 +0100 Subject: [PATCH 29/34] Fix tests for exact matching on preferred material I don't know why the CI system was only complaining about it several commits later. Could be troublesome. Fixes a bug introduced by 1284d9fe8d726e46b556caa8f2f390dc7d65220c. --- tests/Machines/TestVariantNode.py | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/tests/Machines/TestVariantNode.py b/tests/Machines/TestVariantNode.py index bc860058a1..8ccde9bc74 100644 --- a/tests/Machines/TestVariantNode.py +++ b/tests/Machines/TestVariantNode.py @@ -137,38 +137,17 @@ def test_materialAdded_update(container_registry, machine_node, metadata, change def test_preferredMaterialExactMatch(empty_variant_node): empty_variant_node.materials = { "some_different_material": MagicMock(getMetaDataEntry = lambda x: 3), - "preferred_material_base_file": MagicMock(getMetaDataEntry = lambda x: 3) # Exact match. + "preferred_material": MagicMock(getMetaDataEntry = lambda x: 3) # Exact match. } - assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material_base_file"], "It should match exactly on this one since it's the preferred material." - -## Tests the preferred material when a submaterial is available in the -# materials list for this node. -def test_preferredMaterialSubmaterial(empty_variant_node): - empty_variant_node.materials = { - "some_different_material": MagicMock(getMetaDataEntry = lambda x: 3), - "preferred_material_base_file_aa04": MagicMock(getMetaDataEntry = lambda x: 3) # This is a submaterial of the preferred material. - } - - assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material_base_file_aa04"], "It should match on the submaterial just as well." - -## Tests the preferred material matching on the approximate diameter of the -# filament. -def test_preferredMaterialDiameter(empty_variant_node): - empty_variant_node.materials = { - "some_different_material": MagicMock(getMetaDataEntry = lambda x: 3), - "preferred_material_wrong_diameter": MagicMock(getMetaDataEntry = lambda x: 2), # Approximate diameter is 2 instead of 3. - "preferred_material_correct_diameter": MagicMock(getMetaDataEntry = lambda x: 3) # Correct approximate diameter. - } - - assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material_correct_diameter"], "It should match only on the material with correct diameter." + assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material"], "It should match exactly on this one since it's the preferred material." ## Tests the preferred material matching on a different material if the # diameter is wrong. def test_preferredMaterialDiameterNoMatch(empty_variant_node): empty_variant_node.materials = collections.OrderedDict() empty_variant_node.materials["some_different_material"] = MagicMock(getMetaDataEntry = lambda x: 3) # This one first so that it gets iterated over first. - empty_variant_node.materials["preferred_material_wrong_diameter"] = MagicMock(getMetaDataEntry = lambda x: 2) + empty_variant_node.materials["preferred_material"] = MagicMock(getMetaDataEntry = lambda x: 2) # Wrong diameter. assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["some_different_material"], "It should match on another material with the correct diameter if the preferred one is unavailable." @@ -177,7 +156,7 @@ def test_preferredMaterialDiameterNoMatch(empty_variant_node): def test_preferredMaterialDiameterPreference(empty_variant_node): empty_variant_node.materials = collections.OrderedDict() empty_variant_node.materials["some_different_material"] = MagicMock(getMetaDataEntry = lambda x: 2) # This one first so that it gets iterated over first. - empty_variant_node.materials["preferred_material_wrong_diameter"] = MagicMock(getMetaDataEntry = lambda x: 2) # Matches on ID but not diameter. + empty_variant_node.materials["preferred_material"] = MagicMock(getMetaDataEntry = lambda x: 2) # Matches on ID but not diameter. empty_variant_node.materials["not_preferred_but_correct_diameter"] = MagicMock(getMetaDataEntry = lambda x: 3) # Matches diameter but not ID. assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["not_preferred_but_correct_diameter"], "It should match on the correct diameter even if it's not the preferred one." \ No newline at end of file From 174b326f579b7da41974c70a0c8d2fa5bcd4a789 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Nov 2019 14:18:35 +0100 Subject: [PATCH 30/34] Also test validity and correctness of extruder definitions This makes a few things slightly simpler as well since it now parameterises the tests with the entire path of the definition file so we don't have to reconstruct that in every test. --- tests/Settings/TestDefinitionContainer.py | 33 ++++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/Settings/TestDefinitionContainer.py b/tests/Settings/TestDefinitionContainer.py index 6e502280cd..2e0675f691 100644 --- a/tests/Settings/TestDefinitionContainer.py +++ b/tests/Settings/TestDefinitionContainer.py @@ -17,6 +17,10 @@ Resources.addSearchPath(os.path.abspath(os.path.join(os.path.dirname(__file__), machine_filepaths = sorted(os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions"))) +machine_filepaths = [os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions", filename) for filename in machine_filepaths] +extruder_filepaths = sorted(os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "extruders"))) +extruder_filepaths = [os.path.join(os.path.dirname(__file__), "..", "..", "resources", "extruders", filename) for filename in extruder_filepaths] +definition_filepaths = machine_filepaths + extruder_filepaths all_meshes = os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "meshes")) all_images = os.listdir(os.path.join(os.path.dirname(__file__), "..", "..", "resources", "images")) @@ -29,17 +33,16 @@ def definition_container(): ## Tests all definition containers -@pytest.mark.parametrize("file_name", machine_filepaths) -def test_validateMachineDefinitionContainer(file_name, definition_container): +@pytest.mark.parametrize("file_path", definition_filepaths) +def test_validateMachineDefinitionContainer(file_path, definition_container): + file_name = os.path.basename(file_path) if file_name == "fdmprinter.def.json" or file_name == "fdmextruder.def.json": return # Stop checking, these are root files. - definition_path = os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions") - assertIsDefinitionValid(definition_container, definition_path, file_name) + assertIsDefinitionValid(definition_container, file_path) - -def assertIsDefinitionValid(definition_container, path, file_name): - with open(os.path.join(path, file_name), encoding = "utf-8") as data: +def assertIsDefinitionValid(definition_container, file_path): + with open(file_path, encoding = "utf-8") as data: json = data.read() parser, is_valid = definition_container.readAndValidateSerialized(json) assert is_valid #The definition has invalid JSON structure. @@ -57,10 +60,9 @@ def assertIsDefinitionValid(definition_container, path, file_name): # When a definition container defines a "default_value" but inherits from a # definition that defines a "value", the "default_value" is ineffective. This # test fails on those things. -@pytest.mark.parametrize("file_name", machine_filepaths) -def test_validateOverridingDefaultValue(file_name: str): - definition_path = os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions", file_name) - with open(definition_path, encoding = "utf-8") as f: +@pytest.mark.parametrize("file_path", machine_filepaths) +def test_validateOverridingDefaultValue(file_path: str): + with open(file_path, encoding = "utf-8") as f: doc = json.load(f) if "inherits" not in doc: @@ -70,7 +72,7 @@ def test_validateOverridingDefaultValue(file_name: str): parent_settings = getInheritedSettings(doc["inherits"]) for key, val in doc["overrides"].items(): if "value" in parent_settings[key]: - assert "default_value" not in val, "Unnecessary default_value for {key} in {file_name}".format(key = key, file_name = file_name) # If there is a value in the parent settings, then the default_value is not effective. + assert "default_value" not in val, "Unnecessary default_value for {key} in {file_name}".format(key = key, file_name = file_path) # If there is a value in the parent settings, then the default_value is not effective. ## Get all settings and their properties from a definition we're inheriting # from. @@ -130,10 +132,9 @@ def merge_dicts(base: Dict[str, Any], overrides: Dict[str, Any]) -> Dict[str, An # # ID fields are legacy. They should not be used any more. This is legacy that # people don't seem to be able to get used to. -@pytest.mark.parametrize("file_name", machine_filepaths) -def test_noId(file_name): - definition_path = os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions", file_name) - with open(definition_path, encoding = "utf-8") as f: +@pytest.mark.parametrize("file_path", machine_filepaths) +def test_noId(file_path: str): + with open(file_path, encoding = "utf-8") as f: doc = json.load(f) assert "id" not in doc, "Definitions should not have an ID field." \ No newline at end of file From 6e6c510dcd50d60b798342b7d5c8cffe4824a3f9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Nov 2019 14:34:07 +0100 Subject: [PATCH 31/34] Test extruders for correctness but not for validity The validity can't be tested using the built-in validator since that one checks if there are no settings that 'override' non-existing settings. And some of the settings overridden in an extruder are not in the inheritance stack since fdmextruder doesn't inherit from fdmprinter. We'll check though that all settings that are overridden don't override a default_value while there is a value, and whether they don't have IDs. --- tests/Settings/TestDefinitionContainer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Settings/TestDefinitionContainer.py b/tests/Settings/TestDefinitionContainer.py index 2e0675f691..6590c488fc 100644 --- a/tests/Settings/TestDefinitionContainer.py +++ b/tests/Settings/TestDefinitionContainer.py @@ -33,7 +33,7 @@ def definition_container(): ## Tests all definition containers -@pytest.mark.parametrize("file_path", definition_filepaths) +@pytest.mark.parametrize("file_path", machine_filepaths) def test_validateMachineDefinitionContainer(file_path, definition_container): file_name = os.path.basename(file_path) if file_name == "fdmprinter.def.json" or file_name == "fdmextruder.def.json": @@ -60,7 +60,7 @@ def assertIsDefinitionValid(definition_container, file_path): # When a definition container defines a "default_value" but inherits from a # definition that defines a "value", the "default_value" is ineffective. This # test fails on those things. -@pytest.mark.parametrize("file_path", machine_filepaths) +@pytest.mark.parametrize("file_path", definition_filepaths) def test_validateOverridingDefaultValue(file_path: str): with open(file_path, encoding = "utf-8") as f: doc = json.load(f) @@ -71,7 +71,7 @@ def test_validateOverridingDefaultValue(file_path: str): return # No settings are being overridden. No need to check anything. parent_settings = getInheritedSettings(doc["inherits"]) for key, val in doc["overrides"].items(): - if "value" in parent_settings[key]: + if key in parent_settings and "value" in parent_settings[key]: assert "default_value" not in val, "Unnecessary default_value for {key} in {file_name}".format(key = key, file_name = file_path) # If there is a value in the parent settings, then the default_value is not effective. ## Get all settings and their properties from a definition we're inheriting @@ -132,7 +132,7 @@ def merge_dicts(base: Dict[str, Any], overrides: Dict[str, Any]) -> Dict[str, An # # ID fields are legacy. They should not be used any more. This is legacy that # people don't seem to be able to get used to. -@pytest.mark.parametrize("file_path", machine_filepaths) +@pytest.mark.parametrize("file_path", definition_filepaths) def test_noId(file_path: str): with open(file_path, encoding = "utf-8") as f: doc = json.load(f) From 069dc6e65d35fcc4f1167619d1e101d9dec0a5be Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Nov 2019 14:52:41 +0100 Subject: [PATCH 32/34] Add test to see if definition's extruder number matches up ...with its machine definition's metadata. --- tests/Settings/TestDefinitionContainer.py | 34 ++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/Settings/TestDefinitionContainer.py b/tests/Settings/TestDefinitionContainer.py index 6590c488fc..8bb7a754d3 100644 --- a/tests/Settings/TestDefinitionContainer.py +++ b/tests/Settings/TestDefinitionContainer.py @@ -137,4 +137,36 @@ def test_noId(file_path: str): with open(file_path, encoding = "utf-8") as f: doc = json.load(f) - assert "id" not in doc, "Definitions should not have an ID field." \ No newline at end of file + assert "id" not in doc, "Definitions should not have an ID field." + +## Verifies that extruders say that they work on the same extruder_nr as what +# is listed in their machine definition. +@pytest.mark.parametrize("file_path", extruder_filepaths) +def test_extruderMatch(file_path: str): + extruder_id = os.path.basename(file_path).split(".")[0] + with open(file_path, encoding = "utf-8") as f: + doc = json.load(f) + + if "metadata" not in doc: + return # May not be desirable either, but it's probably unfinished then. + if "machine" not in doc["metadata"] or "position" not in doc["metadata"]: + return # FDMextruder doesn't have this since it's not linked to a particular printer. + machine = doc["metadata"]["machine"] + position = doc["metadata"]["position"] + + # Find the machine definition. + for machine_filepath in machine_filepaths: + machine_id = os.path.basename(machine_filepath).split(".")[0] + if machine_id == machine: + break + else: + assert False, "The machine ID {machine} is not found.".format(machine = machine) + with open(machine_filepath, encoding = "utf-8") as f: + machine_doc = json.load(f) + + # Make sure that the two match up. + assert "metadata" in machine_doc, "Machine definition missing metadata entry." + assert "machine_extruder_trains" in machine_doc["metadata"], "Machine must define extruder trains." + extruder_trains = machine_doc["metadata"]["machine_extruder_trains"] + assert position in extruder_trains, "There must be a reference to the extruder in the machine definition." + assert extruder_trains[position] == extruder_id, "The extruder referenced in the machine definition must match up." \ No newline at end of file From d145945b25ba80273d0df9adeafe902522374924 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Nov 2019 15:02:14 +0100 Subject: [PATCH 33/34] Fix mistakes in extruder-definition inter-referencing --- resources/definitions/kupido.def.json | 4 +- .../extruders/bibo2_dual_extruder_0.def.json | 86 +++++++++---------- .../extruders/bibo2_dual_extruder_1.def.json | 86 +++++++++---------- resources/extruders/cr-x_extruder_0.def.json | 2 +- resources/extruders/cr-x_extruder_1.def.json | 2 +- .../creatable_d3_extruder_0.def.json | 2 +- .../hellbot_magna_i_extruder.def.json | 2 +- .../extruders/key3d_tyro_extruder_0.def.json | 2 +- 8 files changed, 93 insertions(+), 93 deletions(-) diff --git a/resources/definitions/kupido.def.json b/resources/definitions/kupido.def.json index a81a40542b..ad0182a5f6 100644 --- a/resources/definitions/kupido.def.json +++ b/resources/definitions/kupido.def.json @@ -18,12 +18,12 @@ "supports_usb_connection": false, "machine_extruder_trains": { - "0": "alya3dp_extruder_0" + "0": "kupido_extruder_0" } }, "overrides": { - "machine_name": { "default_value": "ALYA 3DP" }, + "machine_name": { "default_value": "KUPIDO" }, "machine_heated_bed": { "default_value": true }, "machine_width": { "default_value": 195 }, "machine_height": { "default_value": 190 }, diff --git a/resources/extruders/bibo2_dual_extruder_0.def.json b/resources/extruders/bibo2_dual_extruder_0.def.json index b918d070be..91508ecc6d 100644 --- a/resources/extruders/bibo2_dual_extruder_0.def.json +++ b/resources/extruders/bibo2_dual_extruder_0.def.json @@ -1,45 +1,45 @@ { - "version": 2, - "name": "Extruder 1", - "inherits": "fdmextruder", - "metadata": { - "machine": "BIBO2 dual", - "position": "0" - }, - "overrides": { - "extruder_nr": { - "default_value": 0, - "maximum_value": "1" - }, - "material_diameter": { - "default_value": 1.75 - }, - "machine_nozzle_size": { - "default_value": 0.4 - }, - "machine_nozzle_offset_x": { - "default_value": 0.0 - }, - "machine_nozzle_offset_y": { - "default_value": 0.0 - }, - "machine_extruder_start_pos_abs": { - "default_value": true - }, - "machine_extruder_start_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_start_pos_y": { - "value": "prime_tower_position_y" - }, - "machine_extruder_end_pos_abs": { - "default_value": true - }, - "machine_extruder_end_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_end_pos_y": { - "value": "prime_tower_position_y" - } - } + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": { + "machine": "bibo2_dual", + "position": "0" + }, + "overrides": { + "extruder_nr": { + "default_value": 0, + "maximum_value": "1" + }, + "material_diameter": { + "default_value": 1.75 + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, + "machine_nozzle_offset_x": { + "default_value": 0.0 + }, + "machine_nozzle_offset_y": { + "default_value": 0.0 + }, + "machine_extruder_start_pos_abs": { + "default_value": true + }, + "machine_extruder_start_pos_x": { + "value": "prime_tower_position_x" + }, + "machine_extruder_start_pos_y": { + "value": "prime_tower_position_y" + }, + "machine_extruder_end_pos_abs": { + "default_value": true + }, + "machine_extruder_end_pos_x": { + "value": "prime_tower_position_x" + }, + "machine_extruder_end_pos_y": { + "value": "prime_tower_position_y" + } + } } diff --git a/resources/extruders/bibo2_dual_extruder_1.def.json b/resources/extruders/bibo2_dual_extruder_1.def.json index e0386188bb..129ad27273 100644 --- a/resources/extruders/bibo2_dual_extruder_1.def.json +++ b/resources/extruders/bibo2_dual_extruder_1.def.json @@ -1,45 +1,45 @@ { - "version": 2, - "name": "Extruder 2", - "inherits": "fdmextruder", - "metadata": { - "machine": "BIBO2 dual", - "position": "1" - }, - "overrides": { - "extruder_nr": { - "default_value": 1, - "maximum_value": "1" - }, - "material_diameter": { - "default_value": 1.75 - }, - "machine_nozzle_size": { - "default_value": 0.4 - }, - "machine_nozzle_offset_x": { - "default_value": 0.0 - }, - "machine_nozzle_offset_y": { - "default_value": 0.0 - }, - "machine_extruder_start_pos_abs": { - "default_value": true - }, - "machine_extruder_start_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_start_pos_y": { - "value": "prime_tower_position_y" - }, - "machine_extruder_end_pos_abs": { - "default_value": true - }, - "machine_extruder_end_pos_x": { - "value": "prime_tower_position_x" - }, - "machine_extruder_end_pos_y": { - "value": "prime_tower_position_y" - } - } + "version": 2, + "name": "Extruder 2", + "inherits": "fdmextruder", + "metadata": { + "machine": "bibo2_dual", + "position": "1" + }, + "overrides": { + "extruder_nr": { + "default_value": 1, + "maximum_value": "1" + }, + "material_diameter": { + "default_value": 1.75 + }, + "machine_nozzle_size": { + "default_value": 0.4 + }, + "machine_nozzle_offset_x": { + "default_value": 0.0 + }, + "machine_nozzle_offset_y": { + "default_value": 0.0 + }, + "machine_extruder_start_pos_abs": { + "default_value": true + }, + "machine_extruder_start_pos_x": { + "value": "prime_tower_position_x" + }, + "machine_extruder_start_pos_y": { + "value": "prime_tower_position_y" + }, + "machine_extruder_end_pos_abs": { + "default_value": true + }, + "machine_extruder_end_pos_x": { + "value": "prime_tower_position_x" + }, + "machine_extruder_end_pos_y": { + "value": "prime_tower_position_y" + } + } } diff --git a/resources/extruders/cr-x_extruder_0.def.json b/resources/extruders/cr-x_extruder_0.def.json index 4e73fd0e8e..d9b7b03021 100644 --- a/resources/extruders/cr-x_extruder_0.def.json +++ b/resources/extruders/cr-x_extruder_0.def.json @@ -3,7 +3,7 @@ "name": "Left Extruder", "inherits": "fdmextruder", "metadata": { - "machine": "Creality CR-X", + "machine": "creality_cr-x", "position": "0" }, diff --git a/resources/extruders/cr-x_extruder_1.def.json b/resources/extruders/cr-x_extruder_1.def.json index ed6056dab9..8e763df64f 100644 --- a/resources/extruders/cr-x_extruder_1.def.json +++ b/resources/extruders/cr-x_extruder_1.def.json @@ -3,7 +3,7 @@ "name": "Right Extruder", "inherits": "fdmextruder", "metadata": { - "machine": "Creality CR-X", + "machine": "creality_cr-x", "position": "1" }, diff --git a/resources/extruders/creatable_d3_extruder_0.def.json b/resources/extruders/creatable_d3_extruder_0.def.json index 6a805febd5..95883d0f69 100644 --- a/resources/extruders/creatable_d3_extruder_0.def.json +++ b/resources/extruders/creatable_d3_extruder_0.def.json @@ -3,7 +3,7 @@ "name": "Extruder 1", "inherits": "fdmextruder", "metadata": { - "machine": "Creatable_D3", + "machine": "creatable_d3", "position": "0" }, diff --git a/resources/extruders/hellbot_magna_i_extruder.def.json b/resources/extruders/hellbot_magna_i_extruder.def.json index dc63081bd2..70117c2aed 100644 --- a/resources/extruders/hellbot_magna_i_extruder.def.json +++ b/resources/extruders/hellbot_magna_i_extruder.def.json @@ -3,7 +3,7 @@ "name": "Extruder 1", "inherits": "fdmextruder", "metadata": { - "machine": "hellbot_magna_i", + "machine": "hellbot_magna_I", "position": "0" }, diff --git a/resources/extruders/key3d_tyro_extruder_0.def.json b/resources/extruders/key3d_tyro_extruder_0.def.json index 11619da332..f08ae351ab 100644 --- a/resources/extruders/key3d_tyro_extruder_0.def.json +++ b/resources/extruders/key3d_tyro_extruder_0.def.json @@ -3,7 +3,7 @@ "name": "0.4mm Nozzle", "inherits": "fdmextruder", "metadata": { - "machine": "Tyro", + "machine": "key3d_tyro", "position": "0" }, From 1f4c863683c7c4eaaa88d3bdcc3e6e55fe5308ea Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 1 Nov 2019 15:08:09 +0100 Subject: [PATCH 34/34] Also assert that the extruder_nr setting matches --- tests/Settings/TestDefinitionContainer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Settings/TestDefinitionContainer.py b/tests/Settings/TestDefinitionContainer.py index 8bb7a754d3..4487388b86 100644 --- a/tests/Settings/TestDefinitionContainer.py +++ b/tests/Settings/TestDefinitionContainer.py @@ -169,4 +169,9 @@ def test_extruderMatch(file_path: str): assert "machine_extruder_trains" in machine_doc["metadata"], "Machine must define extruder trains." extruder_trains = machine_doc["metadata"]["machine_extruder_trains"] assert position in extruder_trains, "There must be a reference to the extruder in the machine definition." - assert extruder_trains[position] == extruder_id, "The extruder referenced in the machine definition must match up." \ No newline at end of file + assert extruder_trains[position] == extruder_id, "The extruder referenced in the machine definition must match up." + + # Also test if the extruder_nr setting is properly overridden. + if "overrides" not in doc or "extruder_nr" not in doc["overrides"] or "default_value" not in doc["overrides"]["extruder_nr"]: + assert position == "0" # Default to 0 is allowed. + assert doc["overrides"]["extruder_nr"]["default_value"] == int(position) \ No newline at end of file