fix:TextInput support hot update;feat:add zero color and update

algorithm
This commit is contained in:
2026-01-29 09:56:10 +08:00
parent db0580ead1
commit 01b988fcd7
22 changed files with 2022 additions and 1452 deletions

View File

@@ -31,7 +31,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
qt_standard_project_setup()
add_executable(TactileIpc3D
add_executable(TactileIpc3D WIN32
main.cpp
resources.qrc
src/translation_manager.h
@@ -57,8 +57,6 @@ add_executable(TactileIpc3D
src/serial/serial_qt_transport.h
src/serial/serial_qt_transport.cpp
src/serial/serial_types.h
src/serial/piezoresistive_a_protocol.h
src/serial/piezoresistive_a_protocol.cpp
src/ringbuffer.h
src/ringbuffer.cpp
src/sparkline_plotitem.h
@@ -79,6 +77,19 @@ target_link_libraries(TactileIpc3D PRIVATE
Qt6::QuickDialogs2
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
${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/OpenCV/include
)
@@ -132,8 +143,25 @@ set_target_properties(TactileIpc3D PROPERTIES
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)
install(TARGETS TactileIpc3D
RUNTIME DESTINATION bin
)
install(TARGETS PiezoresistiveAPlugin
RUNTIME DESTINATION bin/plugins/decoders
LIBRARY DESTINATION bin/plugins/decoders
)

View File

@@ -1 +0,0 @@
==

View File

@@ -1 +1 @@
1.0.0
0.4.0

View File

@@ -461,6 +461,44 @@ classDiagram
- `m_protocols`:协议字典。
- `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`)
- 作用:串口传输抽象层,屏蔽不同平台差异。
- 接口:
@@ -633,6 +671,7 @@ classDiagram
## 更新记录
- 2026-01-20协议解码插件化DLLSerialBackend 启动时动态加载 `plugins/decoders`,安装器支持按组件选择协议插件。
- 2026-01-11新增 `QtSerialTransport``QSerialPort` 传输实现)并设为默认传输;补齐点选拾取逻辑;新增数据流/时序图并补充可视化 TODO 说明。
- 2026-01-12`LeftPanel` 新增颜色映射参数;`AppBackend`/`GLWidget` 增加颜色与范围接口shader 使用三色渐变映射数据值。
- 2026-01-11补充串口配置流程图与配置接口说明协议/参数/解码器绑定/打开流程)。

View File

@@ -184,6 +184,10 @@
<source></source>
<translation>Show Grid</translation>
</message>
<message>
<source></source>
<translation>Dented Surface</translation>
</message>
<message>
<source></source>
<translation>Show Axes</translation>
@@ -208,6 +212,14 @@
<source></source>
<translation>Select High Color</translation>
</message>
<message>
<source></source>
<translation>Zero Color</translation>
</message>
<message>
<source></source>
<translation>Select Zero Color</translation>
</message>
</context>
<context>
<name>OpenFileDialog</name>

View File

@@ -148,6 +148,10 @@
<source></source>
<translation></translation>
</message>
<message>
<source></source>
<translation></translation>
</message>
<message>
<source></source>
<translation></translation>
@@ -160,6 +164,14 @@
<source></source>
<translation></translation>
</message>
<message>
<source></source>
<translation></translation>
</message>
<message>
<source></source>
<translation></translation>
</message>
</context>
<context>
<name>RightPanel</name>

View File

@@ -1,5 +1,5 @@
#define MyAppName "TactileIpc3D"
#define MyAppVersion "0.2.0"
#define MyAppVersion "0.4.0"
#define MyAppPublisher "TactileIpc3D"
#define MyAppExeName "TactileIpc3D.exe"
#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]
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]
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]
Name: "{group}\\{#MyAppName}"; Filename: "{app}\\{#MyAppExeName}"

View File

@@ -116,9 +116,11 @@ int main(int argc, char *argv[]) {
glw->setSpec(12, 7, 0.1f, 0.03f);
glw->setPanelThickness(0.08f);
glw->setRange(backend.rangeMin(), backend.rangeMax());
glw->setColorZero(backend.colorZero());
glw->setColorLow(backend.colorLow());
glw->setColorMid(backend.colorMid());
glw->setColorHigh(backend.colorHigh());
glw->setUseHeatmap(backend.useHeatmap());
/* backend.data()->setLiveRenderCallback([glw](const DataFrame& frame) {
if (frame.data.size() != glw->dotCount())
@@ -177,12 +179,16 @@ int main(int argc, char *argv[]) {
QObject::connect(&backend, &AppBackend::showGridChanged, glw,
&GLWidget::setShowGrid);
QObject::connect(&backend, &AppBackend::useHeatmapChanged, glw,
&GLWidget::setUseHeatmap);
QObject::connect(&backend, &AppBackend::sensorRowChanged, glw,
&GLWidget::setRow);
QObject::connect(&backend, &AppBackend::sensorColChanged, glw,
&GLWidget::setCol);
QObject::connect(&backend, &AppBackend::rangeChanged, glw,
&GLWidget::setRange);
QObject::connect(&backend, &AppBackend::colorZeroChanged, glw,
&GLWidget::setColorZero);
QObject::connect(&backend, &AppBackend::colorLowChanged, glw,
&GLWidget::setColorLow);
QObject::connect(&backend, &AppBackend::colorMidChanged, glw,

View File

@@ -503,11 +503,20 @@ Window {
border.color: root.controlBorder
}
function applyIfValid() {
const c = root.parseHex(text)
if (c) root.syncFromColor(c)
}
onTextEdited: applyIfValid()
onEditingFinished: {
const c = root.parseHex(text)
if (c) root.syncFromColor(c)
else text = root.colorToHexAARRGGBB(root.color)
}
onAccepted: {
applyIfValid()
focus = false
}
}

View File

@@ -63,6 +63,7 @@ Rectangle {
RowLayout {
Layout.fillWidth: true
Layout.topMargin: 4
spacing: 8
Label {
@@ -167,6 +168,7 @@ Rectangle {
text: root.formatHexByte(Backend.serial.deviceAddress)
placeholderText: "0x01"
inputMethodHints: Qt.ImhPreferUppercase
property bool _internalUpdate: false
validator: RegularExpressionValidator {
regularExpression: /^(0x|0X)?[0-9a-fA-F]{1,2}$/
}
@@ -183,7 +185,9 @@ Rectangle {
Connections {
target: Backend.serial
function onDeviceAddressChanged() {
addrField._internalUpdate = true
addrField.text = root.formatHexByte(Backend.serial.deviceAddress)
addrField._internalUpdate = false
}
}
}
@@ -423,12 +427,31 @@ Rectangle {
color: root.textColor
}
SpinBox {
id: rangeMinBox
Layout.fillWidth: true
from: -999999
to: 999999
editable: true
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
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
}
SpinBox {
id: rangeMaxBox
Layout.fillWidth: true
from: -999999
to: 999999
editable: true
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
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
onToggled: Backend.showGrid = checked
}
CheckBox {
text: root.tr("凹陷表面")
checked: Backend.useHeatmap
onToggled: Backend.useHeatmap = checked
}
CheckBox {
text: root.tr("显示坐标轴")
checked: false
@@ -590,6 +668,13 @@ Rectangle {
}
}
ColorPickerDialog {
id: zeroColorDialog
title: root.tr("选择零色")
onAccepted: Backend.colorZero = c
}
ColorPickerDialog {
id: lowColorDialog
title: root.tr("选择低色")

View File

@@ -2,14 +2,51 @@
in vec3 vNormal;
in vec3 vWorldPos;
in vec2 vUV;
out vec4 FragColor;
uniform vec3 uCameraPos;
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 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() {
vec3 N = normalize(vNormal);
vec3 L = normalize(uLightDir);
@@ -19,7 +56,16 @@ void main() {
float diff = saturate(dot(N, L));
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;
FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}

View File

@@ -2,14 +2,17 @@
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aUV;
out vec3 vNormal;
out vec3 vWorldPos;
out vec2 vUV;
uniform mat4 uMVP;
void main() {
vNormal = aNormal;
vWorldPos = aPos;
vUV = aUV;
gl_Position = uMVP * vec4(aPos, 1.0);
}

View File

@@ -6,6 +6,7 @@ out vec4 FragColor;
uniform float uMinV;
uniform float uMaxV;
uniform vec3 uColorZero;
uniform vec3 uColorLow;
uniform vec3 uColorMid;
uniform vec3 uColorHigh;
@@ -112,7 +113,8 @@ void main() {
// data color directly (no remaining gold tint), while preserving depth cues.
vec3 metalBase = vec3(0.98, 0.82, 0.30);
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);
vec3 baseColor = hasData ? dataCol : metalBase;

View File

@@ -12,6 +12,7 @@ uniform vec2 uTexelSize;
uniform vec2 uPlaneSize;
uniform vec3 uCameraPos;
uniform vec3 uLightDir;
uniform vec3 uColorZero;
uniform vec3 uColorLow;
uniform vec3 uColorMid;
uniform vec3 uColorHigh;
@@ -22,6 +23,24 @@ float value01(float v) {
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) {
if (t < 0.5) {
return mix(uColorLow, uColorMid, t / 0.5);
@@ -31,8 +50,13 @@ vec3 colorRamp(float t) {
void main() {
float vC = texture(uHeightTex, vUV).r;
bool isZero = abs(vC) <= 1e-6;
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 vR = texture(uHeightTex, vUV + vec2(uTexelSize.x, 0.0)).r;

View File

@@ -5,6 +5,7 @@
#include "backend.h"
#include "data_backend.h"
#include "serial/serial_backend.h"
#include <qcolor.h>
#include <qnumeric.h>
AppBackend::AppBackend(QObject* parent)
@@ -51,6 +52,13 @@ void AppBackend::setShowGrid(bool 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) {
if (m_serial->connected()) {
return;
@@ -93,6 +101,13 @@ void AppBackend::setRangeMax(int v) {
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) {
if (m_colorLow == color)
return;

View File

@@ -7,6 +7,7 @@
#include <QObject>
#include <QString>
#include <QColor>
#include <qcolor.h>
#include <qtmetamacros.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(bool connected READ connected NOTIFY connectedChanged)
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(DataBackend* data READ data CONSTANT)
Q_PROPERTY(int sensorCol READ sensorCol WRITE setSensorCol NOTIFY sensorColChanged);
Q_PROPERTY(int sensorRow READ sensorRow WRITE setSensorRow NOTIFY sensorRowChanged);
Q_PROPERTY(int rangeMin READ rangeMin WRITE setRangeMin NOTIFY rangeMinChanged);
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 colorMid READ colorMid WRITE setColorMid NOTIFY colorMidChanged);
Q_PROPERTY(QColor colorHigh READ colorHigh WRITE setColorHigh NOTIFY colorHighChanged);
@@ -43,6 +46,8 @@ public:
bool showGrid() const { return m_showGrid; }
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 sensorRow() const { qInfo() << "row:" << m_sensorRow; return m_sensorRow; }
void setSensorRow(int r);
@@ -51,9 +56,11 @@ public:
int rangeMax() const { return m_rangeMax; }
void setRangeMin(int v);
void setRangeMax(int v);
QColor colorZero() const { return m_colorZero; }
QColor colorLow() const { return m_colorLow; }
QColor colorMid() const { return m_colorMid; }
QColor colorHigh() const { return m_colorHigh; }
void setColorZero(const QColor& color);
void setColorLow(const QColor& color);
void setColorMid(const QColor& color);
void setColorHigh(const QColor& color);
@@ -63,11 +70,13 @@ signals:
void languageChanged();
void connectedChanged();
void showGridChanged(bool on);
void useHeatmapChanged(bool on);
void sensorColChanged(int c);
void sensorRowChanged(int r);
void rangeMinChanged(int v);
void rangeMaxChanged(int v);
void rangeChanged(int minV, int maxV);
void colorZeroChanged(const QColor& color);
void colorLowChanged(const QColor& color);
void colorMidChanged(const QColor& color);
void colorHighChanged(const QColor& color);
@@ -78,10 +87,12 @@ private:
QString m_language = QStringLiteral("zh_CN");
bool m_showGrid = true;
bool m_useHeatmap = false;
int m_sensorRow = 12;
int m_sensorCol = 7;
int m_rangeMin = 0;
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_colorMid = QColor::fromRgbF(0.10, 0.95, 0.35);
QColor m_colorHigh = QColor::fromRgbF(1.00, 0.22, 0.10);

View File

@@ -127,6 +127,8 @@ bool DataBackend::importXlsx(const QString& path) {
cells[r][c] = v;
}
}
return true;
}
void DataBackend::startPlayback(int intervalMs) {

View File

@@ -5,48 +5,217 @@
#ifndef TACTILEIPC3D_GLOBALHELPER_H
#define TACTILEIPC3D_GLOBALHELPER_H
#include <algorithm>
#include <opencv2/core.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 {
public:
const std::string fuck = "fuck you lsp!!!";
public :
static GlobalHelper *Instance() {
static GlobalHelper ins;
return &ins;
}
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);
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();
raw.setTo(0.0f, raw < min_threshold);
cv::min(raw, safe_max, raw);
double maxVal = 0.0;
cv::minMaxLoc(raw, nullptr, &maxVal);
if (maxVal > 0.0) {
cv::GaussianBlur(raw, raw, cv::Size(3, 3), 0.8, 0.8, cv::BORDER_DEFAULT);
}
cv::Mat peak_norm = (raw - min_threshold) / safe_span;
cv::max(peak_norm, 0.0f, peak_norm);
cv::min(peak_norm, 1.0f, peak_norm);
const float safe_range = std::max(max_range, 1e-6f);
cv::Mat norm_data = raw / safe_range;
cv::min(norm_data, 1.0f, norm_data);
cv::medianBlur(raw, raw, 3);
cv::GaussianBlur(raw, raw, cv::Size(5, 5), 1.2, 1.2, cv::BORDER_DEFAULT);
cv::Mat norm_data = (raw - min_threshold) / safe_span;
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::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;
}
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:
GlobalHelper() {}
GlobalHelper() {
}
};
#endif // TACTILEIPC3D_GLOBALHELPER_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,11 @@
#include "serial_backend.h"
#include "piezoresistive_a_protocol.h"
#include "serial_qt_transport.h"
#include <QDebug>
#include <QCoreApplication>
#include <QDir>
#include <QStringList>
#include <QMetaObject>
#include <QtGlobal>
#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)
{
m_request.dataLength = 24;
m_spec.model = QStringLiteral("PZR-A");
auto codec = std::make_shared<PiezoresistiveACodec>();
auto decoder = std::make_shared<PiezoresistiveADecoder>();
auto format = std::make_shared<PiezoresistiveAFormat>();
m_manager.registerProtocol(codec->name(), {codec, decoder, format});
m_manager.setActiveProtocol(codec->name());
const QString pluginDir = QDir(QCoreApplication::applicationDirPath())
.filePath(QStringLiteral("plugins/decoders"));
QStringList errors;
const QStringList loaded = m_manager.loadPlugins(pluginDir, &errors);
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->moveToThread(&m_sendThread);
@@ -175,6 +184,10 @@ void SerialBackend::setProtocol(const QString &name)
if (!m_manager.setActiveProtocol(name))
return;
updateProtocolBindings_();
if (m_spec.model != name) {
m_spec.model = name;
emit sensorModelChanged();
}
emit protocolChanged();
}

View File

@@ -1,4 +1,8 @@
#include "serial_manager.h"
#include "protocol_plugin.h"
#include <QDir>
#include <QPluginLoader>
void SerialManager::registerProtocol(const QString& name, const ProtocolBundle& bundle) {
if (name.isEmpty())
@@ -15,8 +19,76 @@ bool SerialManager::setActiveProtocol(const QString& name) {
return true;
}
SerialManager::ProtocolBundle SerialManager::activeBundle() const {
ProtocolBundle SerialManager::activeBundle() const {
if (!m_protocols.contains(m_activeName))
return {};
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;
}

View File

@@ -3,22 +3,18 @@
#include <QHash>
#include <QString>
#include <QStringList>
#include <QPluginLoader>
#include <vector>
#include <memory>
#include "serial_codec.h"
#include "serial_decoder.h"
#include "serial_format.h"
#include "protocol_bundle.h"
class SerialManager {
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);
bool setActiveProtocol(const QString& name);
QStringList loadPlugins(const QString& dirPath, QStringList* errors = nullptr);
QString activeProtocol() const { return m_activeName; }
ProtocolBundle activeBundle() const;
@@ -26,6 +22,7 @@ public:
private:
QHash<QString, ProtocolBundle> m_protocols;
QString m_activeName;
std::vector<std::unique_ptr<QPluginLoader>> m_pluginLoaders;
};
#endif // TACTILEIPC3D_SERIAL_MANAGER_H