fix:TextInput support hot update;feat:add zero color and update
algorithm
This commit is contained in:
@@ -31,7 +31,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
|||||||
|
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
|
|
||||||
add_executable(TactileIpc3D
|
add_executable(TactileIpc3D WIN32
|
||||||
main.cpp
|
main.cpp
|
||||||
resources.qrc
|
resources.qrc
|
||||||
src/translation_manager.h
|
src/translation_manager.h
|
||||||
@@ -57,8 +57,6 @@ add_executable(TactileIpc3D
|
|||||||
src/serial/serial_qt_transport.h
|
src/serial/serial_qt_transport.h
|
||||||
src/serial/serial_qt_transport.cpp
|
src/serial/serial_qt_transport.cpp
|
||||||
src/serial/serial_types.h
|
src/serial/serial_types.h
|
||||||
src/serial/piezoresistive_a_protocol.h
|
|
||||||
src/serial/piezoresistive_a_protocol.cpp
|
|
||||||
src/ringbuffer.h
|
src/ringbuffer.h
|
||||||
src/ringbuffer.cpp
|
src/ringbuffer.cpp
|
||||||
src/sparkline_plotitem.h
|
src/sparkline_plotitem.h
|
||||||
@@ -79,6 +77,19 @@ target_link_libraries(TactileIpc3D PRIVATE
|
|||||||
Qt6::QuickDialogs2
|
Qt6::QuickDialogs2
|
||||||
QXlsx::QXlsx
|
QXlsx::QXlsx
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_library(PiezoresistiveAPlugin SHARED
|
||||||
|
plugins/piezoresistive_a/piezoresistive_a_plugin.h
|
||||||
|
plugins/piezoresistive_a/piezoresistive_a_plugin.cpp
|
||||||
|
src/serial/piezoresistive_a_protocol.h
|
||||||
|
src/serial/piezoresistive_a_protocol.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(PiezoresistiveAPlugin PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
|
)
|
||||||
|
target_link_libraries(PiezoresistiveAPlugin PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
)
|
||||||
target_include_directories(TactileIpc3D PRIVATE
|
target_include_directories(TactileIpc3D PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/OpenCV/include
|
${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/OpenCV/include
|
||||||
)
|
)
|
||||||
@@ -132,8 +143,25 @@ set_target_properties(TactileIpc3D PROPERTIES
|
|||||||
RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${runtime_out_dir}/MinSizeRel"
|
RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${runtime_out_dir}/MinSizeRel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(PiezoresistiveAPlugin PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${runtime_out_dir}/plugins/decoders"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${runtime_out_dir}/Debug/plugins/decoders"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${runtime_out_dir}/Release/plugins/decoders"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${runtime_out_dir}/RelWithDebInfo/plugins/decoders"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${runtime_out_dir}/MinSizeRel/plugins/decoders"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${runtime_out_dir}/plugins/decoders"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY_DEBUG "${runtime_out_dir}/Debug/plugins/decoders"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY_RELEASE "${runtime_out_dir}/Release/plugins/decoders"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO "${runtime_out_dir}/RelWithDebInfo/plugins/decoders"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL "${runtime_out_dir}/MinSizeRel/plugins/decoders"
|
||||||
|
)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
install(TARGETS TactileIpc3D
|
install(TARGETS TactileIpc3D
|
||||||
RUNTIME DESTINATION bin
|
RUNTIME DESTINATION bin
|
||||||
)
|
)
|
||||||
|
install(TARGETS PiezoresistiveAPlugin
|
||||||
|
RUNTIME DESTINATION bin/plugins/decoders
|
||||||
|
LIBRARY DESTINATION bin/plugins/decoders
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.0.0
|
0.4.0
|
||||||
|
|||||||
@@ -461,6 +461,44 @@ classDiagram
|
|||||||
- `m_protocols`:协议字典。
|
- `m_protocols`:协议字典。
|
||||||
- `m_activeName`:当前协议名。
|
- `m_activeName`:当前协议名。
|
||||||
|
|
||||||
|
### 协议插件化(`plugins/decoders`)
|
||||||
|
- 目标:每个协议作为独立 DLL 插件,可按需安装与动态加载。
|
||||||
|
- 关键接口:
|
||||||
|
- `IProtocolPlugin`:暴露 `protocolName()` / `apiVersion()` / `createBundle()`。
|
||||||
|
- `ProtocolBundle`:`codec/decoder/format` 三件套。
|
||||||
|
- 加载流程:
|
||||||
|
- `SerialBackend` 启动时调用 `SerialManager::loadPlugins()`。
|
||||||
|
- `SerialManager` 扫描 `appDir/plugins/decoders` 目录,按平台过滤后缀(Windows: `.dll`)。
|
||||||
|
- 校验 `apiVersion`,创建并注册协议 bundle。
|
||||||
|
- 通过 `m_pluginLoaders` 保持插件常驻,避免对象失效。
|
||||||
|
|
||||||
|
#### 插件 UML (Mermaid)
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class IProtocolPlugin {
|
||||||
|
+protocolName()
|
||||||
|
+apiVersion()
|
||||||
|
+createBundle()
|
||||||
|
}
|
||||||
|
class ProtocolBundle {
|
||||||
|
+codec
|
||||||
|
+decoder
|
||||||
|
+format
|
||||||
|
}
|
||||||
|
class SerialManager {
|
||||||
|
+loadPlugins(dir)
|
||||||
|
+registerProtocol()
|
||||||
|
+setActiveProtocol()
|
||||||
|
}
|
||||||
|
class SerialBackend {
|
||||||
|
+initPlugins()
|
||||||
|
}
|
||||||
|
|
||||||
|
SerialBackend --> SerialManager
|
||||||
|
SerialManager --> IProtocolPlugin
|
||||||
|
IProtocolPlugin --> ProtocolBundle
|
||||||
|
```
|
||||||
|
|
||||||
### ISerialTransport (`src/serial/serial_transport.h`)
|
### ISerialTransport (`src/serial/serial_transport.h`)
|
||||||
- 作用:串口传输抽象层,屏蔽不同平台差异。
|
- 作用:串口传输抽象层,屏蔽不同平台差异。
|
||||||
- 接口:
|
- 接口:
|
||||||
@@ -633,6 +671,7 @@ classDiagram
|
|||||||
|
|
||||||
## 更新记录
|
## 更新记录
|
||||||
|
|
||||||
|
- 2026-01-20:协议解码插件化(DLL),SerialBackend 启动时动态加载 `plugins/decoders`,安装器支持按组件选择协议插件。
|
||||||
- 2026-01-11:新增 `QtSerialTransport`(`QSerialPort` 传输实现)并设为默认传输;补齐点选拾取逻辑;新增数据流/时序图并补充可视化 TODO 说明。
|
- 2026-01-11:新增 `QtSerialTransport`(`QSerialPort` 传输实现)并设为默认传输;补齐点选拾取逻辑;新增数据流/时序图并补充可视化 TODO 说明。
|
||||||
- 2026-01-12:`LeftPanel` 新增颜色映射参数;`AppBackend`/`GLWidget` 增加颜色与范围接口,shader 使用三色渐变映射数据值。
|
- 2026-01-12:`LeftPanel` 新增颜色映射参数;`AppBackend`/`GLWidget` 增加颜色与范围接口,shader 使用三色渐变映射数据值。
|
||||||
- 2026-01-11:补充串口配置流程图与配置接口说明(协议/参数/解码器绑定/打开流程)。
|
- 2026-01-11:补充串口配置流程图与配置接口说明(协议/参数/解码器绑定/打开流程)。
|
||||||
|
|||||||
@@ -184,6 +184,10 @@
|
|||||||
<source>显示网络</source>
|
<source>显示网络</source>
|
||||||
<translation>Show Grid</translation>
|
<translation>Show Grid</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>凹陷表面</source>
|
||||||
|
<translation>Dented Surface</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>显示坐标轴</source>
|
<source>显示坐标轴</source>
|
||||||
<translation>Show Axes</translation>
|
<translation>Show Axes</translation>
|
||||||
@@ -208,6 +212,14 @@
|
|||||||
<source>选择高色</source>
|
<source>选择高色</source>
|
||||||
<translation>Select High Color</translation>
|
<translation>Select High Color</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>零色</source>
|
||||||
|
<translation>Zero Color</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>选择零色</source>
|
||||||
|
<translation>Select Zero Color</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>OpenFileDialog</name>
|
<name>OpenFileDialog</name>
|
||||||
|
|||||||
@@ -148,6 +148,10 @@
|
|||||||
<source>显示网络</source>
|
<source>显示网络</source>
|
||||||
<translation>显示网络</translation>
|
<translation>显示网络</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>凹陷表面</source>
|
||||||
|
<translation>凹陷表面</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>显示坐标轴</source>
|
<source>显示坐标轴</source>
|
||||||
<translation>显示坐标轴</translation>
|
<translation>显示坐标轴</translation>
|
||||||
@@ -160,6 +164,14 @@
|
|||||||
<source>导出数据</source>
|
<source>导出数据</source>
|
||||||
<translation>导出数据</translation>
|
<translation>导出数据</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>零色</source>
|
||||||
|
<translation>零色</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>选择零色</source>
|
||||||
|
<translation>选择零色</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>RightPanel</name>
|
<name>RightPanel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#define MyAppName "TactileIpc3D"
|
#define MyAppName "TactileIpc3D"
|
||||||
#define MyAppVersion "0.2.0"
|
#define MyAppVersion "0.4.0"
|
||||||
#define MyAppPublisher "TactileIpc3D"
|
#define MyAppPublisher "TactileIpc3D"
|
||||||
#define MyAppExeName "TactileIpc3D.exe"
|
#define MyAppExeName "TactileIpc3D.exe"
|
||||||
#define SourceDir "..\\build\Desktop_Qt_6_8_3_MinGW_64_bit-Release\\out\Release"
|
#define SourceDir "..\\build\Desktop_Qt_6_8_3_MinGW_64_bit-Release\\out\Release"
|
||||||
@@ -41,8 +41,13 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
[Tasks]
|
[Tasks]
|
||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Components]
|
||||||
|
Name: "core"; Description: "Core Application"; Types: full compact custom; Flags: fixed
|
||||||
|
Name: "decoder_pzr_a"; Description: "Piezoresistive A Decoder (PZR-A)"; Types: full
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "{#SourceDir}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "{#SourceDir}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: core; Excludes: "plugins\\decoders\\*.dll"
|
||||||
|
Source: "{#SourceDir}\\plugins\\decoders\\libPiezoresistiveAPlugin.dll"; DestDir: "{app}\\plugins\\decoders"; Flags: ignoreversion; Components: decoder_pzr_a
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\\{#MyAppName}"; Filename: "{app}\\{#MyAppExeName}"
|
Name: "{group}\\{#MyAppName}"; Filename: "{app}\\{#MyAppExeName}"
|
||||||
|
|||||||
6
main.cpp
6
main.cpp
@@ -116,9 +116,11 @@ int main(int argc, char *argv[]) {
|
|||||||
glw->setSpec(12, 7, 0.1f, 0.03f);
|
glw->setSpec(12, 7, 0.1f, 0.03f);
|
||||||
glw->setPanelThickness(0.08f);
|
glw->setPanelThickness(0.08f);
|
||||||
glw->setRange(backend.rangeMin(), backend.rangeMax());
|
glw->setRange(backend.rangeMin(), backend.rangeMax());
|
||||||
|
glw->setColorZero(backend.colorZero());
|
||||||
glw->setColorLow(backend.colorLow());
|
glw->setColorLow(backend.colorLow());
|
||||||
glw->setColorMid(backend.colorMid());
|
glw->setColorMid(backend.colorMid());
|
||||||
glw->setColorHigh(backend.colorHigh());
|
glw->setColorHigh(backend.colorHigh());
|
||||||
|
glw->setUseHeatmap(backend.useHeatmap());
|
||||||
|
|
||||||
/* backend.data()->setLiveRenderCallback([glw](const DataFrame& frame) {
|
/* backend.data()->setLiveRenderCallback([glw](const DataFrame& frame) {
|
||||||
if (frame.data.size() != glw->dotCount())
|
if (frame.data.size() != glw->dotCount())
|
||||||
@@ -177,12 +179,16 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
QObject::connect(&backend, &AppBackend::showGridChanged, glw,
|
QObject::connect(&backend, &AppBackend::showGridChanged, glw,
|
||||||
&GLWidget::setShowGrid);
|
&GLWidget::setShowGrid);
|
||||||
|
QObject::connect(&backend, &AppBackend::useHeatmapChanged, glw,
|
||||||
|
&GLWidget::setUseHeatmap);
|
||||||
QObject::connect(&backend, &AppBackend::sensorRowChanged, glw,
|
QObject::connect(&backend, &AppBackend::sensorRowChanged, glw,
|
||||||
&GLWidget::setRow);
|
&GLWidget::setRow);
|
||||||
QObject::connect(&backend, &AppBackend::sensorColChanged, glw,
|
QObject::connect(&backend, &AppBackend::sensorColChanged, glw,
|
||||||
&GLWidget::setCol);
|
&GLWidget::setCol);
|
||||||
QObject::connect(&backend, &AppBackend::rangeChanged, glw,
|
QObject::connect(&backend, &AppBackend::rangeChanged, glw,
|
||||||
&GLWidget::setRange);
|
&GLWidget::setRange);
|
||||||
|
QObject::connect(&backend, &AppBackend::colorZeroChanged, glw,
|
||||||
|
&GLWidget::setColorZero);
|
||||||
QObject::connect(&backend, &AppBackend::colorLowChanged, glw,
|
QObject::connect(&backend, &AppBackend::colorLowChanged, glw,
|
||||||
&GLWidget::setColorLow);
|
&GLWidget::setColorLow);
|
||||||
QObject::connect(&backend, &AppBackend::colorMidChanged, glw,
|
QObject::connect(&backend, &AppBackend::colorMidChanged, glw,
|
||||||
|
|||||||
@@ -503,11 +503,20 @@ Window {
|
|||||||
border.color: root.controlBorder
|
border.color: root.controlBorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyIfValid() {
|
||||||
|
const c = root.parseHex(text)
|
||||||
|
if (c) root.syncFromColor(c)
|
||||||
|
}
|
||||||
|
onTextEdited: applyIfValid()
|
||||||
onEditingFinished: {
|
onEditingFinished: {
|
||||||
const c = root.parseHex(text)
|
const c = root.parseHex(text)
|
||||||
if (c) root.syncFromColor(c)
|
if (c) root.syncFromColor(c)
|
||||||
else text = root.colorToHexAARRGGBB(root.color)
|
else text = root.colorToHexAARRGGBB(root.color)
|
||||||
}
|
}
|
||||||
|
onAccepted: {
|
||||||
|
applyIfValid()
|
||||||
|
focus = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ Rectangle {
|
|||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 4
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -167,6 +168,7 @@ Rectangle {
|
|||||||
text: root.formatHexByte(Backend.serial.deviceAddress)
|
text: root.formatHexByte(Backend.serial.deviceAddress)
|
||||||
placeholderText: "0x01"
|
placeholderText: "0x01"
|
||||||
inputMethodHints: Qt.ImhPreferUppercase
|
inputMethodHints: Qt.ImhPreferUppercase
|
||||||
|
property bool _internalUpdate: false
|
||||||
validator: RegularExpressionValidator {
|
validator: RegularExpressionValidator {
|
||||||
regularExpression: /^(0x|0X)?[0-9a-fA-F]{1,2}$/
|
regularExpression: /^(0x|0X)?[0-9a-fA-F]{1,2}$/
|
||||||
}
|
}
|
||||||
@@ -183,7 +185,9 @@ Rectangle {
|
|||||||
Connections {
|
Connections {
|
||||||
target: Backend.serial
|
target: Backend.serial
|
||||||
function onDeviceAddressChanged() {
|
function onDeviceAddressChanged() {
|
||||||
|
addrField._internalUpdate = true
|
||||||
addrField.text = root.formatHexByte(Backend.serial.deviceAddress)
|
addrField.text = root.formatHexByte(Backend.serial.deviceAddress)
|
||||||
|
addrField._internalUpdate = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,12 +427,31 @@ Rectangle {
|
|||||||
color: root.textColor
|
color: root.textColor
|
||||||
}
|
}
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
id: rangeMinBox
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
from: -999999
|
from: -999999
|
||||||
to: 999999
|
to: 999999
|
||||||
editable: true
|
editable: true
|
||||||
value: Backend.rangeMin
|
value: Backend.rangeMin
|
||||||
|
function applyTextEdit() {
|
||||||
|
if (!contentItem) return
|
||||||
|
const parsed = valueFromText(contentItem.text, locale)
|
||||||
|
if (isNaN(parsed)) return
|
||||||
|
const clamped = Math.max(from, Math.min(to, parsed))
|
||||||
|
if (Backend.rangeMin !== clamped) {
|
||||||
|
Backend.rangeMin = clamped
|
||||||
|
}
|
||||||
|
}
|
||||||
onValueModified: Backend.rangeMin = value
|
onValueModified: Backend.rangeMin = value
|
||||||
|
Connections {
|
||||||
|
target: rangeMinBox.contentItem
|
||||||
|
function onTextEdited() { rangeMinBox.applyTextEdit() }
|
||||||
|
function onEditingFinished() { rangeMinBox.applyTextEdit() }
|
||||||
|
function onAccepted() {
|
||||||
|
rangeMinBox.applyTextEdit()
|
||||||
|
rangeMinBox.focus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,12 +464,62 @@ Rectangle {
|
|||||||
color: root.textColor
|
color: root.textColor
|
||||||
}
|
}
|
||||||
SpinBox {
|
SpinBox {
|
||||||
|
id: rangeMaxBox
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
from: -999999
|
from: -999999
|
||||||
to: 999999
|
to: 999999
|
||||||
editable: true
|
editable: true
|
||||||
value: Backend.rangeMax
|
value: Backend.rangeMax
|
||||||
|
function applyTextEdit() {
|
||||||
|
if (!contentItem) return
|
||||||
|
const parsed = valueFromText(contentItem.text, locale)
|
||||||
|
if (isNaN(parsed)) return
|
||||||
|
const clamped = Math.max(from, Math.min(to, parsed))
|
||||||
|
if (Backend.rangeMax !== clamped) {
|
||||||
|
Backend.rangeMax = clamped
|
||||||
|
}
|
||||||
|
}
|
||||||
onValueModified: Backend.rangeMax = value
|
onValueModified: Backend.rangeMax = value
|
||||||
|
Connections {
|
||||||
|
target: rangeMaxBox.contentItem
|
||||||
|
function onTextEdited() { rangeMaxBox.applyTextEdit() }
|
||||||
|
function onEditingFinished() { rangeMaxBox.applyTextEdit() }
|
||||||
|
function onAccepted() {
|
||||||
|
rangeMaxBox.applyTextEdit()
|
||||||
|
rangeMaxBox.focus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
Label {
|
||||||
|
text: root.tr("零色")
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
color: root.textColor
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
radius: 4
|
||||||
|
color: Backend.colorZero
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(0, 0, 0, 0.2)
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
zeroColorDialog.openWith(Backend.colorZero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: root.tr("选择")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onClicked: {
|
||||||
|
zeroColorDialog.openWith(Backend.colorZero)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,6 +630,11 @@ Rectangle {
|
|||||||
checked: Backend.showGrid
|
checked: Backend.showGrid
|
||||||
onToggled: Backend.showGrid = checked
|
onToggled: Backend.showGrid = checked
|
||||||
}
|
}
|
||||||
|
CheckBox {
|
||||||
|
text: root.tr("凹陷表面")
|
||||||
|
checked: Backend.useHeatmap
|
||||||
|
onToggled: Backend.useHeatmap = checked
|
||||||
|
}
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: root.tr("显示坐标轴")
|
text: root.tr("显示坐标轴")
|
||||||
checked: false
|
checked: false
|
||||||
@@ -590,6 +668,13 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColorPickerDialog {
|
||||||
|
id: zeroColorDialog
|
||||||
|
title: root.tr("选择零色")
|
||||||
|
onAccepted: Backend.colorZero = c
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ColorPickerDialog {
|
ColorPickerDialog {
|
||||||
id: lowColorDialog
|
id: lowColorDialog
|
||||||
title: root.tr("选择低色")
|
title: root.tr("选择低色")
|
||||||
|
|||||||
@@ -2,14 +2,51 @@
|
|||||||
|
|
||||||
in vec3 vNormal;
|
in vec3 vNormal;
|
||||||
in vec3 vWorldPos;
|
in vec3 vWorldPos;
|
||||||
|
in vec2 vUV;
|
||||||
out vec4 FragColor;
|
out vec4 FragColor;
|
||||||
|
|
||||||
uniform vec3 uCameraPos;
|
uniform vec3 uCameraPos;
|
||||||
uniform vec3 uLightDir;
|
uniform vec3 uLightDir;
|
||||||
uniform vec3 uColor;
|
uniform sampler2D uHeightTex;
|
||||||
|
uniform float uMinV;
|
||||||
|
uniform float uMaxV;
|
||||||
|
uniform vec2 uTexelSize;
|
||||||
|
uniform vec3 uColorZero;
|
||||||
|
uniform vec3 uColorLow;
|
||||||
|
uniform vec3 uColorMid;
|
||||||
|
uniform vec3 uColorHigh;
|
||||||
|
|
||||||
float saturate(float x) { return clamp(x, 0.0, 1.0); }
|
float saturate(float x) { return clamp(x, 0.0, 1.0); }
|
||||||
|
|
||||||
|
float value01(float v) {
|
||||||
|
return saturate((v - uMinV) / max(uMaxV - uMinV, 1e-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 colorRamp(float t) {
|
||||||
|
if (t < 0.5) {
|
||||||
|
return mix(uColorLow, uColorMid, t / 0.5);
|
||||||
|
}
|
||||||
|
return mix(uColorMid, uColorHigh, (t - 0.5) / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
float maxNeighborValue(vec2 uv) {
|
||||||
|
vec2 texSizeF = 1.0 / uTexelSize;
|
||||||
|
ivec2 texSizeI = ivec2(max(vec2(1.0), floor(texSizeF + 0.5)));
|
||||||
|
ivec2 maxCoord = texSizeI - ivec2(1);
|
||||||
|
|
||||||
|
vec2 texelF = clamp(uv * texSizeF, vec2(0.0), vec2(maxCoord));
|
||||||
|
ivec2 base = ivec2(floor(texelF));
|
||||||
|
ivec2 base10 = min(base + ivec2(1, 0), maxCoord);
|
||||||
|
ivec2 base01 = min(base + ivec2(0, 1), maxCoord);
|
||||||
|
ivec2 base11 = min(base + ivec2(1, 1), maxCoord);
|
||||||
|
|
||||||
|
float v00 = texelFetch(uHeightTex, base, 0).r;
|
||||||
|
float v10 = texelFetch(uHeightTex, base10, 0).r;
|
||||||
|
float v01 = texelFetch(uHeightTex, base01, 0).r;
|
||||||
|
float v11 = texelFetch(uHeightTex, base11, 0).r;
|
||||||
|
return max(max(v00, v10), max(v01, v11));
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 N = normalize(vNormal);
|
vec3 N = normalize(vNormal);
|
||||||
vec3 L = normalize(uLightDir);
|
vec3 L = normalize(uLightDir);
|
||||||
@@ -19,7 +56,16 @@ void main() {
|
|||||||
float diff = saturate(dot(N, L));
|
float diff = saturate(dot(N, L));
|
||||||
float spec = pow(saturate(dot(N, H)), 24.0);
|
float spec = pow(saturate(dot(N, H)), 24.0);
|
||||||
|
|
||||||
vec3 col = uColor * (0.35 + 0.65 * diff);
|
float vC = texture(uHeightTex, vUV).r;
|
||||||
|
bool isZero = abs(vC) <= 1e-6;
|
||||||
|
float t = value01(vC);
|
||||||
|
float m = maxNeighborValue(vUV);
|
||||||
|
float eps = max(1e-4, 0.001 * (uMaxV - uMinV));
|
||||||
|
float force = smoothstep(uMaxV - eps, uMaxV, m);
|
||||||
|
t = max(t, force);
|
||||||
|
vec3 base = isZero ? uColorZero : colorRamp(t);
|
||||||
|
|
||||||
|
vec3 col = base * (0.35 + 0.65 * diff);
|
||||||
col += vec3(1.0) * spec * 0.10;
|
col += vec3(1.0) * spec * 0.10;
|
||||||
FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
|
FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
layout(location = 0) in vec3 aPos;
|
layout(location = 0) in vec3 aPos;
|
||||||
layout(location = 1) in vec3 aNormal;
|
layout(location = 1) in vec3 aNormal;
|
||||||
|
layout(location = 2) in vec2 aUV;
|
||||||
|
|
||||||
out vec3 vNormal;
|
out vec3 vNormal;
|
||||||
out vec3 vWorldPos;
|
out vec3 vWorldPos;
|
||||||
|
out vec2 vUV;
|
||||||
|
|
||||||
uniform mat4 uMVP;
|
uniform mat4 uMVP;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vNormal = aNormal;
|
vNormal = aNormal;
|
||||||
vWorldPos = aPos;
|
vWorldPos = aPos;
|
||||||
|
vUV = aUV;
|
||||||
gl_Position = uMVP * vec4(aPos, 1.0);
|
gl_Position = uMVP * vec4(aPos, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ out vec4 FragColor;
|
|||||||
|
|
||||||
uniform float uMinV;
|
uniform float uMinV;
|
||||||
uniform float uMaxV;
|
uniform float uMaxV;
|
||||||
|
uniform vec3 uColorZero;
|
||||||
uniform vec3 uColorLow;
|
uniform vec3 uColorLow;
|
||||||
uniform vec3 uColorMid;
|
uniform vec3 uColorMid;
|
||||||
uniform vec3 uColorHigh;
|
uniform vec3 uColorHigh;
|
||||||
@@ -112,7 +113,8 @@ void main() {
|
|||||||
// data color directly (no remaining gold tint), while preserving depth cues.
|
// data color directly (no remaining gold tint), while preserving depth cues.
|
||||||
vec3 metalBase = vec3(0.98, 0.82, 0.30);
|
vec3 metalBase = vec3(0.98, 0.82, 0.30);
|
||||||
float value01 = clamp((vValue - uMinV) / max(1e-6, (uMaxV - uMinV)), 0.0, 1.0);
|
float value01 = clamp((vValue - uMinV) / max(1e-6, (uMaxV - uMinV)), 0.0, 1.0);
|
||||||
vec3 dataCol = dataColorRamp(value01);
|
bool isZero = abs(vValue) <= 1e-6;
|
||||||
|
vec3 dataCol = isZero ? uColorZero : dataColorRamp(value01);
|
||||||
|
|
||||||
bool hasData = (uHasData != 0);
|
bool hasData = (uHasData != 0);
|
||||||
vec3 baseColor = hasData ? dataCol : metalBase;
|
vec3 baseColor = hasData ? dataCol : metalBase;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ uniform vec2 uTexelSize;
|
|||||||
uniform vec2 uPlaneSize;
|
uniform vec2 uPlaneSize;
|
||||||
uniform vec3 uCameraPos;
|
uniform vec3 uCameraPos;
|
||||||
uniform vec3 uLightDir;
|
uniform vec3 uLightDir;
|
||||||
|
uniform vec3 uColorZero;
|
||||||
uniform vec3 uColorLow;
|
uniform vec3 uColorLow;
|
||||||
uniform vec3 uColorMid;
|
uniform vec3 uColorMid;
|
||||||
uniform vec3 uColorHigh;
|
uniform vec3 uColorHigh;
|
||||||
@@ -22,6 +23,24 @@ float value01(float v) {
|
|||||||
return saturate((v - uMinV) / max(uMaxV - uMinV, 1e-6));
|
return saturate((v - uMinV) / max(uMaxV - uMinV, 1e-6));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float maxNeighborValue(vec2 uv) {
|
||||||
|
vec2 texSizeF = 1.0 / uTexelSize;
|
||||||
|
ivec2 texSizeI = ivec2(max(vec2(1.0), floor(texSizeF + 0.5)));
|
||||||
|
ivec2 maxCoord = texSizeI - ivec2(1);
|
||||||
|
|
||||||
|
vec2 texelF = clamp(uv * texSizeF, vec2(0.0), vec2(maxCoord));
|
||||||
|
ivec2 base = ivec2(floor(texelF));
|
||||||
|
ivec2 base10 = min(base + ivec2(1, 0), maxCoord);
|
||||||
|
ivec2 base01 = min(base + ivec2(0, 1), maxCoord);
|
||||||
|
ivec2 base11 = min(base + ivec2(1, 1), maxCoord);
|
||||||
|
|
||||||
|
float v00 = texelFetch(uHeightTex, base, 0).r;
|
||||||
|
float v10 = texelFetch(uHeightTex, base10, 0).r;
|
||||||
|
float v01 = texelFetch(uHeightTex, base01, 0).r;
|
||||||
|
float v11 = texelFetch(uHeightTex, base11, 0).r;
|
||||||
|
return max(max(v00, v10), max(v01, v11));
|
||||||
|
}
|
||||||
|
|
||||||
vec3 colorRamp(float t) {
|
vec3 colorRamp(float t) {
|
||||||
if (t < 0.5) {
|
if (t < 0.5) {
|
||||||
return mix(uColorLow, uColorMid, t / 0.5);
|
return mix(uColorLow, uColorMid, t / 0.5);
|
||||||
@@ -31,8 +50,13 @@ vec3 colorRamp(float t) {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
float vC = texture(uHeightTex, vUV).r;
|
float vC = texture(uHeightTex, vUV).r;
|
||||||
|
bool isZero = abs(vC) <= 1e-6;
|
||||||
float t = value01(vC);
|
float t = value01(vC);
|
||||||
vec3 base = colorRamp(t);
|
float m = maxNeighborValue(vUV);
|
||||||
|
float eps = max(1e-4, 0.001 * (uMaxV - uMinV));
|
||||||
|
float force = smoothstep(uMaxV - eps, uMaxV, m);
|
||||||
|
t = max(t, force);
|
||||||
|
vec3 base = isZero ? uColorZero : colorRamp(t);
|
||||||
|
|
||||||
float vL = texture(uHeightTex, vUV - vec2(uTexelSize.x, 0.0)).r;
|
float vL = texture(uHeightTex, vUV - vec2(uTexelSize.x, 0.0)).r;
|
||||||
float vR = texture(uHeightTex, vUV + vec2(uTexelSize.x, 0.0)).r;
|
float vR = texture(uHeightTex, vUV + vec2(uTexelSize.x, 0.0)).r;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "backend.h"
|
#include "backend.h"
|
||||||
#include "data_backend.h"
|
#include "data_backend.h"
|
||||||
#include "serial/serial_backend.h"
|
#include "serial/serial_backend.h"
|
||||||
|
#include <qcolor.h>
|
||||||
#include <qnumeric.h>
|
#include <qnumeric.h>
|
||||||
|
|
||||||
AppBackend::AppBackend(QObject* parent)
|
AppBackend::AppBackend(QObject* parent)
|
||||||
@@ -51,6 +52,13 @@ void AppBackend::setShowGrid(bool on) {
|
|||||||
emit showGridChanged(on);
|
emit showGridChanged(on);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AppBackend::setUseHeatmap(bool on) {
|
||||||
|
if (m_useHeatmap == on)
|
||||||
|
return;
|
||||||
|
m_useHeatmap = on;
|
||||||
|
emit useHeatmapChanged(on);
|
||||||
|
}
|
||||||
|
|
||||||
void AppBackend::setSensorCol(int c) {
|
void AppBackend::setSensorCol(int c) {
|
||||||
if (m_serial->connected()) {
|
if (m_serial->connected()) {
|
||||||
return;
|
return;
|
||||||
@@ -93,6 +101,13 @@ void AppBackend::setRangeMax(int v) {
|
|||||||
emit rangeChanged(m_rangeMin, m_rangeMax);
|
emit rangeChanged(m_rangeMin, m_rangeMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AppBackend::setColorZero(const QColor& color) {
|
||||||
|
if (m_colorZero == color)
|
||||||
|
return;
|
||||||
|
m_colorZero = color;
|
||||||
|
emit colorZeroChanged(m_colorZero);
|
||||||
|
}
|
||||||
|
|
||||||
void AppBackend::setColorLow(const QColor& color) {
|
void AppBackend::setColorLow(const QColor& color) {
|
||||||
if (m_colorLow == color)
|
if (m_colorLow == color)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
#include <qcolor.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "data_backend.h"
|
#include "data_backend.h"
|
||||||
@@ -18,12 +19,14 @@ class AppBackend : public QObject {
|
|||||||
Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged)
|
Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged)
|
||||||
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
|
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
|
||||||
Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged);
|
Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged);
|
||||||
|
Q_PROPERTY(bool useHeatmap READ useHeatmap WRITE setUseHeatmap NOTIFY useHeatmapChanged);
|
||||||
Q_PROPERTY(SerialBackend* serial READ serial CONSTANT)
|
Q_PROPERTY(SerialBackend* serial READ serial CONSTANT)
|
||||||
Q_PROPERTY(DataBackend* data READ data CONSTANT)
|
Q_PROPERTY(DataBackend* data READ data CONSTANT)
|
||||||
Q_PROPERTY(int sensorCol READ sensorCol WRITE setSensorCol NOTIFY sensorColChanged);
|
Q_PROPERTY(int sensorCol READ sensorCol WRITE setSensorCol NOTIFY sensorColChanged);
|
||||||
Q_PROPERTY(int sensorRow READ sensorRow WRITE setSensorRow NOTIFY sensorRowChanged);
|
Q_PROPERTY(int sensorRow READ sensorRow WRITE setSensorRow NOTIFY sensorRowChanged);
|
||||||
Q_PROPERTY(int rangeMin READ rangeMin WRITE setRangeMin NOTIFY rangeMinChanged);
|
Q_PROPERTY(int rangeMin READ rangeMin WRITE setRangeMin NOTIFY rangeMinChanged);
|
||||||
Q_PROPERTY(int rangeMax READ rangeMax WRITE setRangeMax NOTIFY rangeMaxChanged);
|
Q_PROPERTY(int rangeMax READ rangeMax WRITE setRangeMax NOTIFY rangeMaxChanged);
|
||||||
|
Q_PROPERTY(QColor colorZero READ colorZero WRITE setColorZero NOTIFY colorZeroChanged);
|
||||||
Q_PROPERTY(QColor colorLow READ colorLow WRITE setColorLow NOTIFY colorLowChanged);
|
Q_PROPERTY(QColor colorLow READ colorLow WRITE setColorLow NOTIFY colorLowChanged);
|
||||||
Q_PROPERTY(QColor colorMid READ colorMid WRITE setColorMid NOTIFY colorMidChanged);
|
Q_PROPERTY(QColor colorMid READ colorMid WRITE setColorMid NOTIFY colorMidChanged);
|
||||||
Q_PROPERTY(QColor colorHigh READ colorHigh WRITE setColorHigh NOTIFY colorHighChanged);
|
Q_PROPERTY(QColor colorHigh READ colorHigh WRITE setColorHigh NOTIFY colorHighChanged);
|
||||||
@@ -43,6 +46,8 @@ public:
|
|||||||
|
|
||||||
bool showGrid() const { return m_showGrid; }
|
bool showGrid() const { return m_showGrid; }
|
||||||
void setShowGrid(bool on);
|
void setShowGrid(bool on);
|
||||||
|
bool useHeatmap() const { return m_useHeatmap; }
|
||||||
|
void setUseHeatmap(bool on);
|
||||||
int sensorCol() const { qInfo() << "col:" << m_sensorCol; return m_sensorCol; }
|
int sensorCol() const { qInfo() << "col:" << m_sensorCol; return m_sensorCol; }
|
||||||
int sensorRow() const { qInfo() << "row:" << m_sensorRow; return m_sensorRow; }
|
int sensorRow() const { qInfo() << "row:" << m_sensorRow; return m_sensorRow; }
|
||||||
void setSensorRow(int r);
|
void setSensorRow(int r);
|
||||||
@@ -51,9 +56,11 @@ public:
|
|||||||
int rangeMax() const { return m_rangeMax; }
|
int rangeMax() const { return m_rangeMax; }
|
||||||
void setRangeMin(int v);
|
void setRangeMin(int v);
|
||||||
void setRangeMax(int v);
|
void setRangeMax(int v);
|
||||||
|
QColor colorZero() const { return m_colorZero; }
|
||||||
QColor colorLow() const { return m_colorLow; }
|
QColor colorLow() const { return m_colorLow; }
|
||||||
QColor colorMid() const { return m_colorMid; }
|
QColor colorMid() const { return m_colorMid; }
|
||||||
QColor colorHigh() const { return m_colorHigh; }
|
QColor colorHigh() const { return m_colorHigh; }
|
||||||
|
void setColorZero(const QColor& color);
|
||||||
void setColorLow(const QColor& color);
|
void setColorLow(const QColor& color);
|
||||||
void setColorMid(const QColor& color);
|
void setColorMid(const QColor& color);
|
||||||
void setColorHigh(const QColor& color);
|
void setColorHigh(const QColor& color);
|
||||||
@@ -63,11 +70,13 @@ signals:
|
|||||||
void languageChanged();
|
void languageChanged();
|
||||||
void connectedChanged();
|
void connectedChanged();
|
||||||
void showGridChanged(bool on);
|
void showGridChanged(bool on);
|
||||||
|
void useHeatmapChanged(bool on);
|
||||||
void sensorColChanged(int c);
|
void sensorColChanged(int c);
|
||||||
void sensorRowChanged(int r);
|
void sensorRowChanged(int r);
|
||||||
void rangeMinChanged(int v);
|
void rangeMinChanged(int v);
|
||||||
void rangeMaxChanged(int v);
|
void rangeMaxChanged(int v);
|
||||||
void rangeChanged(int minV, int maxV);
|
void rangeChanged(int minV, int maxV);
|
||||||
|
void colorZeroChanged(const QColor& color);
|
||||||
void colorLowChanged(const QColor& color);
|
void colorLowChanged(const QColor& color);
|
||||||
void colorMidChanged(const QColor& color);
|
void colorMidChanged(const QColor& color);
|
||||||
void colorHighChanged(const QColor& color);
|
void colorHighChanged(const QColor& color);
|
||||||
@@ -78,10 +87,12 @@ private:
|
|||||||
QString m_language = QStringLiteral("zh_CN");
|
QString m_language = QStringLiteral("zh_CN");
|
||||||
|
|
||||||
bool m_showGrid = true;
|
bool m_showGrid = true;
|
||||||
|
bool m_useHeatmap = false;
|
||||||
int m_sensorRow = 12;
|
int m_sensorRow = 12;
|
||||||
int m_sensorCol = 7;
|
int m_sensorCol = 7;
|
||||||
int m_rangeMin = 0;
|
int m_rangeMin = 0;
|
||||||
int m_rangeMax = 1000;
|
int m_rangeMax = 1000;
|
||||||
|
QColor m_colorZero = QColor::fromRgbF(0.10, 0.75, 1.00);
|
||||||
QColor m_colorLow = QColor::fromRgbF(0.10, 0.75, 1.00);
|
QColor m_colorLow = QColor::fromRgbF(0.10, 0.75, 1.00);
|
||||||
QColor m_colorMid = QColor::fromRgbF(0.10, 0.95, 0.35);
|
QColor m_colorMid = QColor::fromRgbF(0.10, 0.95, 0.35);
|
||||||
QColor m_colorHigh = QColor::fromRgbF(1.00, 0.22, 0.10);
|
QColor m_colorHigh = QColor::fromRgbF(1.00, 0.22, 0.10);
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ bool DataBackend::importXlsx(const QString& path) {
|
|||||||
cells[r][c] = v;
|
cells[r][c] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataBackend::startPlayback(int intervalMs) {
|
void DataBackend::startPlayback(int intervalMs) {
|
||||||
|
|||||||
@@ -5,48 +5,217 @@
|
|||||||
#ifndef TACTILEIPC3D_GLOBALHELPER_H
|
#ifndef TACTILEIPC3D_GLOBALHELPER_H
|
||||||
#define TACTILEIPC3D_GLOBALHELPER_H
|
#define TACTILEIPC3D_GLOBALHELPER_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <opencv2/core.hpp>
|
#include <opencv2/core.hpp>
|
||||||
#include <opencv2/imgproc.hpp>
|
#include <opencv2/imgproc.hpp>
|
||||||
#include <algorithm>
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <qset.h>
|
||||||
|
#include <qsettings.h>
|
||||||
|
|
||||||
|
const QString APP_VERSION = "0.4.0";
|
||||||
|
|
||||||
class GlobalHelper {
|
class GlobalHelper {
|
||||||
public:
|
|
||||||
const std::string fuck = "fuck you lsp!!!";
|
const std::string fuck = "fuck you lsp!!!";
|
||||||
|
|
||||||
|
public :
|
||||||
static GlobalHelper *Instance() {
|
static GlobalHelper *Instance() {
|
||||||
static GlobalHelper ins;
|
static GlobalHelper ins;
|
||||||
return &ins;
|
return &ins;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void transToMultiMatrix(const cv::Mat &raw_data, float min_threshold,
|
static void transToMultiMatrix(const cv::Mat &raw_data, float min_threshold,
|
||||||
float max_range, cv::Size display_res, cv::Mat& out_matrix) {
|
float max_range, cv::Size display_res,
|
||||||
|
cv::Mat &out_matrix) {
|
||||||
CV_Assert(raw_data.type() == CV_32F);
|
CV_Assert(raw_data.type() == CV_32F);
|
||||||
|
|
||||||
|
const float safe_max = std::max(max_range, 1e-6f);
|
||||||
|
const float safe_span = std::max(safe_max - min_threshold, 1e-6f);
|
||||||
|
|
||||||
|
cv::Mat saturate_mask_u8;
|
||||||
|
cv::compare(raw_data, max_range, saturate_mask_u8, cv::CMP_GE);
|
||||||
|
cv::Mat zero_mask_u8;
|
||||||
|
cv::compare(raw_data, 0.0f, zero_mask_u8, cv::CMP_LE);
|
||||||
|
|
||||||
cv::Mat raw = raw_data.clone();
|
cv::Mat raw = raw_data.clone();
|
||||||
raw.setTo(0.0f, raw < min_threshold);
|
cv::min(raw, safe_max, raw);
|
||||||
|
|
||||||
double maxVal = 0.0;
|
cv::Mat peak_norm = (raw - min_threshold) / safe_span;
|
||||||
cv::minMaxLoc(raw, nullptr, &maxVal);
|
cv::max(peak_norm, 0.0f, peak_norm);
|
||||||
if (maxVal > 0.0) {
|
cv::min(peak_norm, 1.0f, peak_norm);
|
||||||
cv::GaussianBlur(raw, raw, cv::Size(3, 3), 0.8, 0.8, cv::BORDER_DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
const float safe_range = std::max(max_range, 1e-6f);
|
cv::medianBlur(raw, raw, 3);
|
||||||
cv::Mat norm_data = raw / safe_range;
|
cv::GaussianBlur(raw, raw, cv::Size(5, 5), 1.2, 1.2, cv::BORDER_DEFAULT);
|
||||||
cv::min(norm_data, 1.0f, norm_data);
|
|
||||||
|
cv::Mat norm_data = (raw - min_threshold) / safe_span;
|
||||||
cv::max(norm_data, 0.0f, norm_data);
|
cv::max(norm_data, 0.0f, norm_data);
|
||||||
|
cv::min(norm_data, 1.0f, norm_data);
|
||||||
|
|
||||||
cv::pow(norm_data, 0.7, norm_data);
|
cv::pow(norm_data, 1.4, norm_data);
|
||||||
|
|
||||||
cv::Mat smoothed;
|
cv::Mat smoothed;
|
||||||
cv::resize(norm_data, smoothed, display_res, 0.0, 0.0, cv::INTER_CUBIC);
|
cv::resize(norm_data, smoothed, display_res, 0.0, 0.0, cv::INTER_CUBIC);
|
||||||
|
cv::max(smoothed, 0.0f, smoothed);
|
||||||
|
cv::min(smoothed, 1.0f, smoothed);
|
||||||
|
|
||||||
cv::GaussianBlur(smoothed, smoothed, cv::Size(31, 31), 0.0, 0.0, cv::BORDER_DEFAULT);
|
const int min_dim =
|
||||||
|
std::max(1, std::min(display_res.width, display_res.height));
|
||||||
|
const double sigma = std::max(1.0, 0.02 * min_dim);
|
||||||
|
int ksize = std::max(3, (static_cast<int>(std::ceil(sigma * 6)) | 1));
|
||||||
|
cv::GaussianBlur(smoothed, smoothed, cv::Size(ksize, ksize), sigma, sigma,
|
||||||
|
cv::BORDER_DEFAULT);
|
||||||
|
cv::max(smoothed, 0.0f, smoothed);
|
||||||
|
cv::min(smoothed, 1.0f, smoothed);
|
||||||
|
|
||||||
smoothed *= safe_range;
|
cv::Mat peak_resized;
|
||||||
|
cv::resize(peak_norm, peak_resized, display_res, 0.0, 0.0,
|
||||||
|
cv::INTER_CUBIC);
|
||||||
|
cv::max(smoothed, peak_resized, smoothed);
|
||||||
|
|
||||||
|
if (!saturate_mask_u8.empty()) {
|
||||||
|
cv::Mat mask_f;
|
||||||
|
saturate_mask_u8.convertTo(mask_f, CV_32F, 1.0 / 255.0);
|
||||||
|
cv::Mat mask_resized;
|
||||||
|
cv::resize(mask_f, mask_resized, display_res, 0.0, 0.0,
|
||||||
|
cv::INTER_CUBIC);
|
||||||
|
// Soften the threshold to avoid blocky plateaus.
|
||||||
|
const double mask_sigma = std::max(0.5, 0.008 * min_dim);
|
||||||
|
int mask_ksize =
|
||||||
|
std::max(3, (static_cast<int>(std::ceil(mask_sigma * 6)) | 1));
|
||||||
|
cv::GaussianBlur(mask_resized, mask_resized,
|
||||||
|
cv::Size(mask_ksize, mask_ksize), mask_sigma,
|
||||||
|
mask_sigma, cv::BORDER_DEFAULT);
|
||||||
|
cv::max(mask_resized, 0.0f, mask_resized);
|
||||||
|
cv::min(mask_resized, 1.0f, mask_resized);
|
||||||
|
smoothed = cv::max(smoothed, mask_resized);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!zero_mask_u8.empty()) {
|
||||||
|
cv::Mat zero_f;
|
||||||
|
zero_mask_u8.convertTo(zero_f, CV_32F, 1.0 / 255.0);
|
||||||
|
cv::Mat zero_resized;
|
||||||
|
cv::resize(zero_f, zero_resized, display_res, 0.0, 0.0,
|
||||||
|
cv::INTER_CUBIC);
|
||||||
|
const double zero_sigma = std::max(0.5, 0.006 * min_dim);
|
||||||
|
int zero_ksize =
|
||||||
|
std::max(3, (static_cast<int>(std::ceil(zero_sigma * 6)) | 1));
|
||||||
|
cv::GaussianBlur(zero_resized, zero_resized,
|
||||||
|
cv::Size(zero_ksize, zero_ksize), zero_sigma,
|
||||||
|
zero_sigma, cv::BORDER_DEFAULT);
|
||||||
|
cv::max(zero_resized, 0.0f, zero_resized);
|
||||||
|
cv::min(zero_resized, 1.0f, zero_resized);
|
||||||
|
cv::Mat zero_bin;
|
||||||
|
cv::threshold(zero_resized, zero_bin, 0.5, 255.0,
|
||||||
|
cv::THRESH_BINARY);
|
||||||
|
zero_bin.convertTo(zero_bin, CV_8U);
|
||||||
|
smoothed.setTo(0.0f, zero_bin);
|
||||||
|
}
|
||||||
|
|
||||||
|
smoothed *= safe_max;
|
||||||
out_matrix = smoothed;
|
out_matrix = smoothed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SaveGridConfig(bool b) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("display/grid", b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SaveAxisConfig(bool b) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("display/axis", b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SaveSurfaceConfig(bool b) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("display/surface", b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetGridConfig(bool& b) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
b = settings.value("display/grid", b).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetAxisConfig(bool& b) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
b = settings.value("display/axis", b).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetSurfaceConfig(bool b) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
b = settings.value("display/surface", b).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SaveLastRow(int value) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("spec/row", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetLastRow(int& value) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
value = settings.value("spec/row", value).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetLastCol(int value) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("spec/col", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetLastCol(int& value) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
value = settings.value("spec/col", value).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SaveZeroColor(QString color) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("color/zero", color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetZeroColor(QString& color) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
color = settings.value("color/zero", color).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SaveLowColor(QString color) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("color/low", color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetLowColor(QString& color) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
color = settings.value("color/low", color).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SaveMidColor(QString color) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("color/mid", color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetMidColor(QString& color) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
color = settings.value("color/mid", color).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SaveHighColor(QString color) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
settings.setValue("color/high", color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetHighColor(QString& color) {
|
||||||
|
QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
|
||||||
|
color = settings.value("color/high", color).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString GetConfigFilePath() {
|
||||||
|
return QCoreApplication::applicationDirPath() + QDir::separator() + "conf.ini";
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString GetAppVersion() {
|
||||||
|
return APP_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GlobalHelper() {}
|
GlobalHelper() {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TACTILEIPC3D_GLOBALHELPER_H
|
#endif // TACTILEIPC3D_GLOBALHELPER_H
|
||||||
|
|||||||
543
src/glwidget.cpp
543
src/glwidget.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,11 @@
|
|||||||
#include "serial_backend.h"
|
#include "serial_backend.h"
|
||||||
|
|
||||||
#include "piezoresistive_a_protocol.h"
|
|
||||||
#include "serial_qt_transport.h"
|
#include "serial_qt_transport.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QStringList>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
@@ -14,13 +16,20 @@ SerialBackend::SerialBackend(QObject *parent)
|
|||||||
: QObject(parent), m_packetQueue(2048), m_frameQueue(2048), m_readThread(&m_packetQueue), m_decodeThread(&m_packetQueue, &m_frameQueue)
|
: QObject(parent), m_packetQueue(2048), m_frameQueue(2048), m_readThread(&m_packetQueue), m_decodeThread(&m_packetQueue, &m_frameQueue)
|
||||||
{
|
{
|
||||||
m_request.dataLength = 24;
|
m_request.dataLength = 24;
|
||||||
m_spec.model = QStringLiteral("PZR-A");
|
|
||||||
|
|
||||||
auto codec = std::make_shared<PiezoresistiveACodec>();
|
const QString pluginDir = QDir(QCoreApplication::applicationDirPath())
|
||||||
auto decoder = std::make_shared<PiezoresistiveADecoder>();
|
.filePath(QStringLiteral("plugins/decoders"));
|
||||||
auto format = std::make_shared<PiezoresistiveAFormat>();
|
QStringList errors;
|
||||||
m_manager.registerProtocol(codec->name(), {codec, decoder, format});
|
const QStringList loaded = m_manager.loadPlugins(pluginDir, &errors);
|
||||||
m_manager.setActiveProtocol(codec->name());
|
for (const QString& err : errors) {
|
||||||
|
qWarning().noquote() << err;
|
||||||
|
}
|
||||||
|
if (!loaded.isEmpty()) {
|
||||||
|
m_spec.model = m_manager.activeProtocol();
|
||||||
|
} else {
|
||||||
|
qWarning() << "No protocol plugins loaded.";
|
||||||
|
m_spec.model = QStringLiteral("UNKNOWN");
|
||||||
|
}
|
||||||
|
|
||||||
m_sendWorker = new SerialSendWorker();
|
m_sendWorker = new SerialSendWorker();
|
||||||
m_sendWorker->moveToThread(&m_sendThread);
|
m_sendWorker->moveToThread(&m_sendThread);
|
||||||
@@ -175,6 +184,10 @@ void SerialBackend::setProtocol(const QString &name)
|
|||||||
if (!m_manager.setActiveProtocol(name))
|
if (!m_manager.setActiveProtocol(name))
|
||||||
return;
|
return;
|
||||||
updateProtocolBindings_();
|
updateProtocolBindings_();
|
||||||
|
if (m_spec.model != name) {
|
||||||
|
m_spec.model = name;
|
||||||
|
emit sensorModelChanged();
|
||||||
|
}
|
||||||
emit protocolChanged();
|
emit protocolChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
#include "serial_manager.h"
|
#include "serial_manager.h"
|
||||||
|
#include "protocol_plugin.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QPluginLoader>
|
||||||
|
|
||||||
void SerialManager::registerProtocol(const QString& name, const ProtocolBundle& bundle) {
|
void SerialManager::registerProtocol(const QString& name, const ProtocolBundle& bundle) {
|
||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
@@ -15,8 +19,76 @@ bool SerialManager::setActiveProtocol(const QString& name) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SerialManager::ProtocolBundle SerialManager::activeBundle() const {
|
ProtocolBundle SerialManager::activeBundle() const {
|
||||||
if (!m_protocols.contains(m_activeName))
|
if (!m_protocols.contains(m_activeName))
|
||||||
return {};
|
return {};
|
||||||
return m_protocols.value(m_activeName);
|
return m_protocols.value(m_activeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList SerialManager::loadPlugins(const QString& dirPath, QStringList* errors) {
|
||||||
|
QStringList loaded;
|
||||||
|
if (!m_pluginLoaders.empty()) {
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir dir(dirPath);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
if (errors)
|
||||||
|
errors->append(QStringLiteral("Plugin dir not found: %1").arg(dirPath));
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList filters;
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
filters << QStringLiteral("*.dll");
|
||||||
|
#elif defined(Q_OS_MAC)
|
||||||
|
filters << QStringLiteral("*.dylib");
|
||||||
|
#else
|
||||||
|
filters << QStringLiteral("*.so");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const QStringList entries = dir.entryList(filters, QDir::Files);
|
||||||
|
for (const QString& fileName : entries) {
|
||||||
|
const QString path = dir.absoluteFilePath(fileName);
|
||||||
|
auto loader = std::make_unique<QPluginLoader>(path);
|
||||||
|
QObject* instance = loader->instance();
|
||||||
|
if (!instance) {
|
||||||
|
if (errors)
|
||||||
|
errors->append(QStringLiteral("Load failed: %1 (%2)").arg(path, loader->errorString()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* plugin = qobject_cast<IProtocolPlugin*>(instance);
|
||||||
|
if (!plugin) {
|
||||||
|
if (errors)
|
||||||
|
errors->append(QStringLiteral("Invalid plugin: %1").arg(path));
|
||||||
|
loader->unload();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (plugin->apiVersion() != kProtocolPluginApiVersion) {
|
||||||
|
if (errors)
|
||||||
|
errors->append(QStringLiteral("API mismatch: %1").arg(path));
|
||||||
|
loader->unload();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProtocolBundle bundle = plugin->createBundle();
|
||||||
|
if (!bundle.codec || !bundle.decoder || !bundle.format) {
|
||||||
|
if (errors)
|
||||||
|
errors->append(QStringLiteral("Missing components: %1").arg(path));
|
||||||
|
loader->unload();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString name = plugin->protocolName();
|
||||||
|
if (!m_protocols.contains(name)) {
|
||||||
|
registerProtocol(name, bundle);
|
||||||
|
loaded.append(name);
|
||||||
|
m_pluginLoaders.push_back(std::move(loader));
|
||||||
|
} else {
|
||||||
|
loader->unload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,22 +3,18 @@
|
|||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QPluginLoader>
|
||||||
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "serial_codec.h"
|
#include "protocol_bundle.h"
|
||||||
#include "serial_decoder.h"
|
|
||||||
#include "serial_format.h"
|
|
||||||
|
|
||||||
class SerialManager {
|
class SerialManager {
|
||||||
public:
|
public:
|
||||||
struct ProtocolBundle {
|
|
||||||
std::shared_ptr<ISerialCodec> codec;
|
|
||||||
std::shared_ptr<ISerialDecoder> decoder;
|
|
||||||
std::shared_ptr<ISerialFormat> format;
|
|
||||||
};
|
|
||||||
|
|
||||||
void registerProtocol(const QString& name, const ProtocolBundle& bundle);
|
void registerProtocol(const QString& name, const ProtocolBundle& bundle);
|
||||||
bool setActiveProtocol(const QString& name);
|
bool setActiveProtocol(const QString& name);
|
||||||
|
QStringList loadPlugins(const QString& dirPath, QStringList* errors = nullptr);
|
||||||
|
|
||||||
QString activeProtocol() const { return m_activeName; }
|
QString activeProtocol() const { return m_activeName; }
|
||||||
ProtocolBundle activeBundle() const;
|
ProtocolBundle activeBundle() const;
|
||||||
@@ -26,6 +22,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
QHash<QString, ProtocolBundle> m_protocols;
|
QHash<QString, ProtocolBundle> m_protocols;
|
||||||
QString m_activeName;
|
QString m_activeName;
|
||||||
|
std::vector<std::unique_ptr<QPluginLoader>> m_pluginLoaders;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TACTILEIPC3D_SERIAL_MANAGER_H
|
#endif // TACTILEIPC3D_SERIAL_MANAGER_H
|
||||||
|
|||||||
Reference in New Issue
Block a user