update devkit server and model files
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user