feat: 增加轨迹

This commit is contained in:
tongtong committed 2026-09-17 09:09:06 +08:00
1 parent b9fd289caf
commit e74cf6d28b
42 files changed
+4010 -570

No files matched your search

@@ -4,6 +4,7 @@ import { afterEach, beforeEach, vi } from "vitest";
import { clearViewportCaptureHandler, setViewportCaptureHandler } from "../io/captureBridge";
import { BODY_TYPE_OPTIONS } from "../runtime/mannequin/bodyTypes";
import { createInitialDirectorState, useDirectorStore } from "../store/directorStore";
import { usePlaybackStore } from "../runtime/playbackStore";
import { getCameraRigPositionFromViewSnapshot, getCameraViewSnapshotFromShot } from "../schema/cameraGeometry";
import { ViewportToolbar } from "./ViewportToolbar";
import { getModelLibraryItems } from "../modelLibrary/modelLibraryCatalog";
@@ -71,6 +72,8 @@ it("renders the viewport capsule as project icon-system buttons", () => {
"四方位截图",
"十二方位截图",
"全屏",
"添加轨迹",
"录制",
];
expectedActions.forEach((label) => {
@@ -86,6 +89,8 @@ it("renders the viewport capsule as project icon-system buttons", () => {
button.getAttribute("aria-label")
);
expect(toolbarButtonLabels.indexOf("模型库")).toBe(toolbarButtonLabels.indexOf("导入本地模型") + 1);
// 打点 / 录制固定在工具条最右侧
expect(toolbarButtonLabels.slice(-2)).toEqual(["添加轨迹", "录制"]);
});
it("renders custom hover labels instead of native title tooltips", () => {
@@ -229,6 +234,80 @@ it("stores viewport capsule screenshots in the current camera while already in c
expect(activeCamera?.lastCaptureUrl).toBe("data:image/png;base64,camera-view-4");
});
it("disables the add-trajectory action until a character is selected", () => {
render(<ViewportToolbar />);
const button = screen.getByRole("button", { name: "添加轨迹" });
expect(button).toBeDisabled();
expect(button).toHaveClass("viewport-toolbar-button");
expect(button.querySelector("svg")).toBeInTheDocument();
});
it("keeps the add-trajectory action disabled for non-character objects", () => {
useDirectorStore.getState().selectObject("cam_object_1");
render(<ViewportToolbar />);
expect(screen.getByRole("button", { name: "添加轨迹" })).toBeDisabled();
});
it("enters and leaves the canvas placement mode from the add-trajectory action", async () => {
const user = userEvent.setup();
useDirectorStore.getState().selectObject("char_default_a");
render(<ViewportToolbar />);
const button = screen.getByRole("button", { name: "添加轨迹" });
expect(button).not.toBeDisabled();
expect(button).toHaveAttribute("aria-pressed", "false");
await user.click(button);
expect(useDirectorStore.getState().trajectoryPlacementObjectId).toBe("char_default_a");
expect(button).toHaveAttribute("aria-pressed", "true");
await user.click(button);
expect(useDirectorStore.getState().trajectoryPlacementObjectId).toBeNull();
expect(button).toHaveAttribute("aria-pressed", "false");
});
it("stops placing trajectory points when Escape is pressed", async () => {
const user = userEvent.setup();
useDirectorStore.getState().selectObject("char_default_a");
render(<ViewportToolbar />);
await user.click(screen.getByRole("button", { name: "添加轨迹" }));
expect(useDirectorStore.getState().trajectoryPlacementObjectId).toBe("char_default_a");
await user.keyboard("{Escape}");
expect(useDirectorStore.getState().trajectoryPlacementObjectId).toBeNull();
});
it("hides and shows the floating timeline from the record action", async () => {
const user = userEvent.setup();
useDirectorStore.getState().setTimelineVisible(true);
render(<ViewportToolbar />);
const recordButton = screen.getByRole("button", { name: "录制" });
expect(recordButton).toHaveAttribute("aria-pressed", "true");
expect(recordButton).toHaveClass("is-recording");
await user.click(recordButton);
expect(useDirectorStore.getState().timelineVisible).toBe(false);
expect(recordButton).toHaveAttribute("aria-pressed", "false");
expect(recordButton).not.toHaveClass("is-recording");
await user.click(screen.getByRole("button", { name: "录制" }));
expect(useDirectorStore.getState().timelineVisible).toBe(true);
expect(screen.getByRole("button", { name: "录制" })).toHaveAttribute("aria-pressed", "true");
});
it("switches the active transform control mode from the viewport capsule", async () => {
const user = userEvent.setup();
render(<ViewportToolbar />);