update devkit server and model files

This commit is contained in:
lenn
2026-06-02 09:43:05 +08:00
parent 0812142359
commit 78c4445b93
40 changed files with 66301 additions and 316 deletions

View File

@@ -2,7 +2,6 @@
import { createEventDispatcher } from "svelte";
import type {
ConnectionState,
HudConfigLink,
HudNoticeTone,
LocaleCode,
StageViewMode,
@@ -28,8 +27,6 @@
export let serialPortValue = "";
export let serialPortOptions: string[] = [];
export let refreshPortsLabel = "";
export let configLinksLabel = "";
export let configLinks: HudConfigLink[] = [];
export let matrixViewLabel = "";
export let matrixViewDotsLabel = "";
export let stageViewMode: StageViewMode = "webgl";
@@ -53,7 +50,6 @@
const dispatch = createEventDispatcher<{
windowcontrol: WindowControlAction;
localechange: LocaleCode;
configlink: string;
matrixdisplaytoggle: boolean;
stagemodechange: StageViewMode;
portchange: string;
@@ -97,10 +93,6 @@
dispatch("localechange", nextLocale);
}
function emitConfigLink(linkId: string): void {
dispatch("configlink", linkId);
}
function emitStageModeChange(nextMode: StageViewMode): void {
dispatch("stagemodechange", nextMode);
}
@@ -185,20 +177,6 @@
<div class="control-bar">
<div class="control-main-row">
<section class="config-links" aria-label={configLinksLabel}>
{#each configLinks as link (link.id)}
<button
type="button"
class="config-link tone-{link.tone ?? 'neutral'}"
class:is-active={Boolean(link.active)}
on:click={() => emitConfigLink(link.id)}
>
<span class="config-indicator" aria-hidden="true"></span>
<span class="config-label">{link.label}</span>
</button>
{/each}
</section>
<section class="state-card" aria-label={connectionLabel}>
<span class="state-dot" class:ok={connectionTone === "ok"} class:warn={connectionTone === "warn"}></span>
<span class="state-label">{connectionLabel}</span>
@@ -930,93 +908,6 @@
background: rgb(10 16 20 / 0.7);
}
.config-links {
display: inline-flex;
align-items: center;
gap: 0.2rem;
min-block-size: 2rem;
border: 1px solid rgb(95 132 158 / 0.36);
border-radius: 999px;
padding: 0.17rem 0.2rem;
background: linear-gradient(180deg, rgb(9 15 19 / 0.9), rgb(4 8 12 / 0.86));
box-shadow: inset 0 0 0 1px rgb(140 184 210 / 0.06);
}
.config-link {
display: inline-flex;
align-items: center;
gap: 0.34rem;
border: 1px solid transparent;
border-radius: 999px;
padding: 0.26rem 0.64rem;
background: transparent;
color: rgb(164 188 208 / 0.9);
font-size: 0.81rem;
letter-spacing: 0.05em;
cursor: pointer;
transition:
color 180ms ease,
border-color 180ms ease,
background-color 180ms ease,
box-shadow 220ms ease;
}
.config-indicator {
inline-size: 0.34rem;
block-size: 0.34rem;
border-radius: 999px;
background: rgb(136 157 174 / 0.88);
box-shadow: 0 0 0 2px rgb(136 157 174 / 0.16);
transition:
background-color 180ms ease,
box-shadow 200ms ease;
}
.config-label {
line-height: 1;
}
.config-link:hover {
color: #d7edfb;
border-color: rgb(62 232 255 / 0.26);
}
.config-link.is-active {
color: #f1fdff;
border-color: rgb(106 150 180 / 0.56);
background: rgb(18 27 35 / 0.9);
box-shadow:
inset 0 0 0 1px rgb(167 218 252 / 0.08),
0 0 10px rgb(62 232 255 / 0.12);
}
.config-link.tone-cyan.is-active {
border-color: rgb(62 232 255 / 0.48);
}
.config-link.tone-lime.is-active {
border-color: rgb(133 255 68 / 0.52);
}
.config-link.tone-orange.is-active {
border-color: rgb(255 91 63 / 0.52);
}
.config-link.tone-cyan.is-active .config-indicator {
background: var(--hud-cyan);
box-shadow: 0 0 0 2px rgb(62 232 255 / 0.17);
}
.config-link.tone-lime.is-active .config-indicator {
background: var(--hud-lime);
box-shadow: 0 0 0 2px rgb(133 255 68 / 0.17);
}
.config-link.tone-orange.is-active .config-indicator {
background: var(--hud-orange);
box-shadow: 0 0 0 2px rgb(255 91 63 / 0.18);
}
.locale-btn {
border: 1px solid transparent;
border-radius: 999px;
@@ -1043,21 +934,11 @@
background: rgb(24 31 25 / 0.9);
}
@media (max-width: 1080px) {
.config-links {
flex-wrap: wrap;
}
}
@media (max-width: 820px) {
.control-main-row {
gap: 0.44rem;
}
.config-links {
flex-wrap: wrap;
}
.serial-select {
padding-inline-start: 0.45rem;
}

View File

@@ -9,7 +9,7 @@
type ModelLoadState = "loading" | "ready" | "missing" | "error";
export let locale: LocaleCode = "zh-CN";
export let modelUrl = "/models/je-skin-model.glb";
export let modelUrl = "/models/je-skin-model.gltf";
let rootEl: HTMLDivElement | undefined;
let canvasEl: HTMLCanvasElement | undefined;

View File

@@ -14,8 +14,6 @@ export type MatrixDisplayMode = "numeric" | "dots";
export type SignalPanelSide = "left" | "right";
export type HudConfigTone = "neutral" | "cyan" | "lime" | "orange";
export interface HudSignalSeries {
id: string;
tone: SignalTone;
@@ -64,13 +62,6 @@ export interface HudSummary {
max: number | null;
}
export interface HudConfigLink {
id: string;
label: string;
tone?: HudConfigTone;
active?: boolean;
}
export interface HudColorMapOption {
id: PressureColorMapPreset;
label: string;
@@ -107,7 +98,6 @@ export interface HudCopy {
deviceLabel: string;
sampleRateLabel: string;
channelsLabel: string;
configLinksLabel: string;
refreshPortsLabel: string;
connectActionLabel: string;
disconnectActionLabel: string;
@@ -169,6 +159,7 @@ export interface SerialRecordStateResult {
export interface SerialImportFrameResult {
data: number[];
dtsMs: number;
spatialForce: HudSpatialForce | null;
}
export interface SerialImportResult {

View File

@@ -19,7 +19,6 @@
FileExplorerRoot,
HudColorMapOption,
HudCopy,
HudConfigLink,
HudNoticeTone,
HudPacket,
HudSpatialForce,
@@ -43,6 +42,7 @@
interface ReplayFrame {
values: number[];
dtsMs: number;
spatialForce?: HudSpatialForce | null;
}
interface DevKitPztAngleEvent {
@@ -50,6 +50,20 @@
timestampMs: number;
dtsMs: number;
angle: number;
magnitude: number;
state: number;
copX: number;
copY: number;
baseX: number;
baseY: number;
totalPress: number;
threshold: number;
}
interface DevKitReplayFramePushResult {
seq: number;
timestampMs: number;
dtsMs: number;
}
const copyByLocale: Record<LocaleCode, HudCopy> = {
@@ -83,7 +97,6 @@
deviceLabel: "设备",
sampleRateLabel: "采样率",
channelsLabel: "通道",
configLinksLabel: "配置链接",
refreshPortsLabel: "刷新",
connectActionLabel: "连接",
disconnectActionLabel: "断开",
@@ -145,7 +158,6 @@
deviceLabel: "Device",
sampleRateLabel: "Sample Rate",
channelsLabel: "Channels",
configLinksLabel: "Config Links",
refreshPortsLabel: "Refresh",
connectActionLabel: "Connect",
disconnectActionLabel: "Disconnect",
@@ -228,8 +240,6 @@
let sampleRateValue = "100Hz";
let channelsValue = "84";
let isWindowMaximized = false;
let activeConfigLinkId = "stream-on";
let isConfigPanelOpen = false;
let hasSignalData = false;
let signalPanels: HudSignalPanel[] = buildInactivePanels();
let summary: HudSummary = buildEmptySummary();
@@ -250,6 +260,8 @@
let replayProgress = 0;
let replayFileName = "";
let replayTimerId: number | null = null;
let replayPendingDevkitSeq: number | null = null;
let replayDevkitSeqCounter = 0;
let fileExplorerOpen = false;
let fileExplorerMode: FileExplorerMode = "open";
let fileExplorerBusy = false;
@@ -279,13 +291,6 @@
let sessionStartedAt: number = Date.now();
$: uiCopy = copyByLocale[locale];
$: configLinks = buildConfigLinks(
locale,
activeConfigLinkId,
isConfigPanelOpen,
devkitEnabled,
isDevKitConfigOpen
);
$: leftSignalPanels = signalPanels.filter((panel) => panel.side === "left");
$: rightSignalPanels = signalPanels.filter((panel) => panel.side === "right");
$: rangeTicks = buildRangeTicks(rangeMin, rangeMax);
@@ -679,7 +684,8 @@
});
frames = result.frames.map((frame) => ({
values: frame.data,
dtsMs: frame.dtsMs
dtsMs: frame.dtsMs,
spatialForce: frame.spatialForce
}));
importedFrameCount = result.frameCount;
importedChannelCount = result.channelCount;
@@ -709,7 +715,8 @@
const frames = result.frames.map((frame) => ({
values: frame.data,
dtsMs: frame.dtsMs
dtsMs: frame.dtsMs,
spatialForce: frame.spatialForce
}));
applyImportedFrames(result.fileName, frames, result.frameCount, result.channelCount);
@@ -730,6 +737,43 @@
replayTimerId = null;
}
function buildReplayDevkitSeq(frameIndex: number): number {
replayDevkitSeqCounter = (replayDevkitSeqCounter + 1) % 1000;
return Date.now() * 1000 + replayDevkitSeqCounter + frameIndex;
}
function canPushReplayFrameToDevkit(frame: ReplayFrame): boolean {
return (
isTauriRuntime() &&
devkitEnabled &&
devkitRunning &&
frame.values.length === 84
);
}
function pushReplayFrameToDevkit(frame: ReplayFrame, frameIndex: number): void {
if (!canPushReplayFrameToDevkit(frame)) {
replayPendingDevkitSeq = null;
clearDevkitSpatialForce();
return;
}
const seq = buildReplayDevkitSeq(frameIndex);
replayPendingDevkitSeq = seq;
clearDevkitSpatialForce();
void invoke<DevKitReplayFramePushResult>("devkit_push_replay_frame", {
values: frame.values.map((value) => Math.max(0, Math.round(Number(value) || 0))),
dtsMs: clamp(Math.round(frame.dtsMs), 0, 4_294_967_295),
seq
}).catch((error) => {
if (replayPendingDevkitSeq === seq) {
replayPendingDevkitSeq = null;
}
console.error("Failed to push replay frame to DevKit:", error);
});
}
function frameValuesToMatrix(values: number[]): number[] {
const totalCells = Math.max(matrixRows * matrixCols, 1);
const matrix = new Array<number>(totalCells).fill(0);
@@ -750,6 +794,7 @@
function resetReplayVisualState(): void {
pressureMatrix = buildZeroMatrix();
spatialForce = null;
replayPendingDevkitSeq = null;
clearDevkitSpatialForce();
signalPanels = buildInactivePanels();
summary = buildEmptySummary();
@@ -785,9 +830,10 @@
replayCurrentIndex = safeIndex;
replayHasDisplayedFrame = true;
replayProgress = replayFrames.length > 1 ? safeIndex / (replayFrames.length - 1) : 1;
pressureMatrix = frameValuesToMatrix(replayFrames[safeIndex].values);
spatialForce = null;
clearDevkitSpatialForce();
const frame = replayFrames[safeIndex];
pressureMatrix = frameValuesToMatrix(frame.values);
spatialForce = frame.spatialForce ?? null;
pushReplayFrameToDevkit(frame, safeIndex);
signalPanels = buildInactivePanels();
summary = buildReplaySummaryAt(safeIndex);
hasSignalData = true;
@@ -1081,52 +1127,6 @@
});
}
function buildConfigLinks(
currentLocale: LocaleCode,
activeId: string,
isSettingsOpen: boolean,
isDevKitEnabled: boolean,
isDevKitOpen: boolean
): HudConfigLink[] {
const labels =
currentLocale === "zh-CN"
? {
streamOn: "打开",
settings: "参数"
}
: {
streamOn: "Open",
settings: "Setup"
};
const devkitLabel = currentLocale === "zh-CN" ? "开发工具" : "DevKit";
const links: HudConfigLink[] = [
{
id: "stream-on",
label: labels.streamOn,
tone: "lime",
active: activeId === "stream-on"
},
{
id: "settings",
label: labels.settings,
tone: "neutral",
active: isSettingsOpen
}
];
if (isDevKitEnabled) {
links.push({
id: "devkit",
label: devkitLabel,
tone: "cyan",
active: isDevKitOpen
});
}
return links;
}
async function ensureDefaultWindowSize(): Promise<void> {
if (!isTauriRuntime()) {
return;
@@ -1676,26 +1676,6 @@
resetReplayVisualState();
}
function handleConfigLink(event: CustomEvent<string>): void {
if (event.detail === "settings") {
stageViewMode = "webgl";
isConfigPanelOpen = !isConfigPanelOpen;
isDevKitConfigOpen = false;
return;
}
if (event.detail === "devkit") {
isConfigPanelOpen = false;
isDevKitConfigOpen = !isDevKitConfigOpen;
return;
}
isConfigPanelOpen = false;
isDevKitConfigOpen = false;
activeConfigLinkId = event.detail;
console.info("[hud] config link clicked:", event.detail);
}
async function handleWindowControl(event: CustomEvent<WindowControlAction>): Promise<void> {
if (!isTauriRuntime()) {
return;
@@ -1766,9 +1746,6 @@
function handleStageModeChange(event: CustomEvent<StageViewMode>): void {
stageViewMode = event.detail;
if (stageViewMode === "model3d") {
isConfigPanelOpen = false;
}
}
onMount(() => {
@@ -1799,6 +1776,13 @@
console.error("Failed to listen for hud_stream:", error);
});
void listen<DevKitPztAngleEvent>("devkit_pzt_angle", (event) => {
if (replayHasData) {
if (replayPendingDevkitSeq == null || event.payload.seq !== replayPendingDevkitSeq) {
return;
}
replayPendingDevkitSeq = null;
}
const angleDeg = Number(event.payload.angle);
if (!Number.isFinite(angleDeg)) {
clearDevkitSpatialForce();
@@ -1807,8 +1791,8 @@
devkitSpatialForce = {
angleDeg,
magnitude: 0,
confidence: 0
magnitude: Number.isFinite(event.payload.magnitude) ? event.payload.magnitude : 0,
confidence: event.payload.state
};
scheduleDevkitSpatialForceClear();
hasSignalData =
@@ -1874,7 +1858,6 @@
sampleRateValue={sampleRateValue}
channelsLabel={uiCopy.channelsLabel}
channelsValue={channelsValue}
configLinksLabel={uiCopy.configLinksLabel}
refreshPortsLabel={uiCopy.refreshPortsLabel}
matrixViewLabel={uiCopy.matrixViewLabel}
matrixViewDotsLabel={uiCopy.matrixViewDotsLabel}
@@ -1890,7 +1873,6 @@
noticeCancelLabel={locale === "zh-CN" ? "取消" : "Cancel"}
noticeShowActions={updateNoticeVisible}
noticeActionBusy={updateInstallBusy}
{configLinks}
{isRefreshingPorts}
{isExporting}
isConnectDisabled={!serialPortValue || connectionState === "connecting"}
@@ -1899,7 +1881,6 @@
on:windowcontrol={handleWindowControl}
on:localechange={handleLocaleChange}
on:portchange={handlePortChange}
on:configlink={handleConfigLink}
on:stagemodechange={handleStageModeChange}
on:serialrefresh={handleSerialRefresh}
on:serialconnect={handleSerialConnect}
@@ -1949,14 +1930,13 @@
{pressureMatrix}
{spatialForce}
{devkitSpatialForce}
showConfigPanel={isConfigPanelOpen}
showConfigPanel={false}
{summary}
on:replaytoggle={handleReplayToggle}
on:replaystop={handleReplayStop}
on:replayseek={handleReplaySeek}
on:replayspeed={handleReplaySpeed}
on:replayclose={handleReplayClose}
on:configclose={() => (isConfigPanelOpen = false)}
>
{#if stageViewMode === "webgl"}
<section class="range-scale" aria-label="Signal Range">