update model
This commit is contained in:
+183
-67
@@ -2549,7 +2549,14 @@ function updateSeatTwin(actuatorState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const REFERENCE_VEHICLE_ROOT = "/assets/models/byd-tang/";
|
const REFERENCE_VEHICLE_ROOT = "/assets/models/byd-tang/";
|
||||||
const REFERENCE_VEHICLE_URL = `${REFERENCE_VEHICLE_ROOT}byd-tang-2024.fbx`;
|
const REFERENCE_VEHICLE_ASSET_VERSION = "77a0fae7";
|
||||||
|
const REFERENCE_VEHICLE_LOAD_TIMEOUT_MS = 30000;
|
||||||
|
|
||||||
|
function referenceVehicleAssetUrl(fileName) {
|
||||||
|
return `${REFERENCE_VEHICLE_ROOT}${encodeURIComponent(fileName)}?v=${REFERENCE_VEHICLE_ASSET_VERSION}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const REFERENCE_VEHICLE_URL = referenceVehicleAssetUrl("byd-tang-2024.fbx");
|
||||||
|
|
||||||
function referenceMaterialDescriptor(material) {
|
function referenceMaterialDescriptor(material) {
|
||||||
const textureNames = [
|
const textureNames = [
|
||||||
@@ -3643,73 +3650,92 @@ function loadReferenceVehicleModel(THREE, scene, vehicleSystems, fallback) {
|
|||||||
reject(new Error("FBXLoader is unavailable"));
|
reject(new Error("FBXLoader is unavailable"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let settled = false;
|
||||||
|
const settle = (callback, value) => {
|
||||||
|
if (settled) return false;
|
||||||
|
settled = true;
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
callback(value);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const timeoutId = window.setTimeout(() => {
|
||||||
|
settle(
|
||||||
|
reject,
|
||||||
|
new Error(`Reference vehicle load timed out after ${REFERENCE_VEHICLE_LOAD_TIMEOUT_MS} ms`),
|
||||||
|
);
|
||||||
|
}, REFERENCE_VEHICLE_LOAD_TIMEOUT_MS);
|
||||||
const manager = new THREE.LoadingManager();
|
const manager = new THREE.LoadingManager();
|
||||||
manager.setURLModifier((url) => {
|
manager.setURLModifier((url) => {
|
||||||
const cleanUrl = String(url).replaceAll("\\", "/");
|
const cleanUrl = String(url).replaceAll("\\", "/").split(/[?#]/, 1)[0];
|
||||||
const fileName = decodeURIComponent(cleanUrl.split("/").pop() || "");
|
const fileName = decodeURIComponent(cleanUrl.split("/").pop() || "");
|
||||||
if (!/\.(png|jpe?g)$/i.test(fileName)) return url;
|
if (!/\.(png|jpe?g)$/i.test(fileName)) return url;
|
||||||
return `${REFERENCE_VEHICLE_ROOT}${encodeURIComponent(fileName)}`;
|
return referenceVehicleAssetUrl(fileName);
|
||||||
});
|
});
|
||||||
const loader = new FBXLoaderClass(manager);
|
const loader = new FBXLoaderClass(manager);
|
||||||
loader.setResourcePath(REFERENCE_VEHICLE_ROOT);
|
loader.setResourcePath(REFERENCE_VEHICLE_ROOT);
|
||||||
loader.load(
|
loader.load(
|
||||||
REFERENCE_VEHICLE_URL,
|
REFERENCE_VEHICLE_URL,
|
||||||
(vehicle) => {
|
(vehicle) => {
|
||||||
const normalized = normalizeReferenceVehicle(THREE, vehicle);
|
if (settled) return;
|
||||||
const driverCushion = measureReferenceDriverCushion(THREE, vehicle);
|
try {
|
||||||
const passengerCushion = measureReferencePassengerCushion(THREE, vehicle);
|
const normalized = normalizeReferenceVehicle(THREE, vehicle);
|
||||||
const removedFrontSeatTriangles = carveReferenceFrontSeats(THREE, vehicle);
|
const driverCushion = measureReferenceDriverCushion(THREE, vehicle);
|
||||||
const referenceWindowPanes = extractReferenceWindowPanes(THREE, vehicle);
|
const passengerCushion = measureReferencePassengerCushion(THREE, vehicle);
|
||||||
const report = configureReferenceVehicleModel(THREE, vehicle);
|
const removedFrontSeatTriangles = carveReferenceFrontSeats(THREE, vehicle);
|
||||||
styleReferencePassiveGlass(THREE, vehicle);
|
const referenceWindowPanes = extractReferenceWindowPanes(THREE, vehicle);
|
||||||
const passiveRearExterior = styleReferencePassiveRearExterior(THREE, vehicle);
|
const report = configureReferenceVehicleModel(THREE, vehicle);
|
||||||
const referenceFrontLampMeshes = collectReferenceFrontLampMeshes(vehicle);
|
styleReferencePassiveGlass(THREE, vehicle);
|
||||||
emphasizeReferenceWindowPanes(THREE, referenceWindowPanes);
|
const passiveRearExterior = styleReferencePassiveRearExterior(THREE, vehicle);
|
||||||
const referenceFrontLampMaterials = emphasizeReferenceFrontLampMeshes(
|
const referenceFrontLampMeshes = collectReferenceFrontLampMeshes(vehicle);
|
||||||
THREE,
|
emphasizeReferenceWindowPanes(THREE, referenceWindowPanes);
|
||||||
referenceFrontLampMeshes,
|
const referenceFrontLampMaterials = emphasizeReferenceFrontLampMeshes(
|
||||||
);
|
THREE,
|
||||||
const referenceFrontLampHighlights = createReferenceFrontLampHighlights(
|
referenceFrontLampMeshes,
|
||||||
THREE,
|
);
|
||||||
referenceFrontLampMeshes,
|
const referenceFrontLampHighlights = createReferenceFrontLampHighlights(
|
||||||
);
|
THREE,
|
||||||
const referenceFrontLampLensOutlines = createReferenceFrontLampLensOutlines(
|
referenceFrontLampMeshes,
|
||||||
THREE,
|
);
|
||||||
vehicle,
|
const referenceFrontLampLensOutlines = createReferenceFrontLampLensOutlines(
|
||||||
);
|
THREE,
|
||||||
vehicleSystems.referenceWindowPanes = referenceWindowPanes;
|
vehicle,
|
||||||
vehicleSystems.referenceFrontLampMaterials = referenceFrontLampMaterials;
|
);
|
||||||
vehicleSystems.referenceFrontLampMeshes = referenceFrontLampMeshes;
|
vehicleSystems.referenceWindowPanes = referenceWindowPanes;
|
||||||
vehicleSystems.referenceFrontLampHighlights = referenceFrontLampHighlights;
|
vehicleSystems.referenceFrontLampMaterials = referenceFrontLampMaterials;
|
||||||
vehicleSystems.referenceFrontLampLensOutlines = referenceFrontLampLensOutlines;
|
vehicleSystems.referenceFrontLampMeshes = referenceFrontLampMeshes;
|
||||||
applyTwinVehicleTheme(vehicleSystems);
|
vehicleSystems.referenceFrontLampHighlights = referenceFrontLampHighlights;
|
||||||
vehicle.name = "REFERENCE_VEHICLE_OUTLINE";
|
vehicleSystems.referenceFrontLampLensOutlines = referenceFrontLampLensOutlines;
|
||||||
vehicle.userData.twinRole = "reference_vehicle";
|
applyTwinVehicleTheme(vehicleSystems);
|
||||||
vehicle.traverse((object) => object.layers.set(1));
|
vehicle.name = "REFERENCE_VEHICLE_OUTLINE";
|
||||||
scene.add(vehicle);
|
vehicle.userData.twinRole = "reference_vehicle";
|
||||||
vehicleSystems.fallbackExteriorObjects.forEach((object) => {
|
vehicle.traverse((object) => object.layers.set(1));
|
||||||
object.visible = false;
|
scene.add(vehicle);
|
||||||
});
|
vehicleSystems.fallbackExteriorObjects.forEach((object) => {
|
||||||
vehicleSystems.fallbackInteriorObjects.forEach((object) => {
|
object.visible = false;
|
||||||
object.visible = false;
|
});
|
||||||
});
|
vehicleSystems.fallbackInteriorObjects.forEach((object) => {
|
||||||
console.info("[vehicle-twin] reference vehicle model loaded", JSON.stringify({
|
object.visible = false;
|
||||||
meshes: report.meshes,
|
});
|
||||||
size: normalized.size.toArray().map((value) => Number(value.toFixed(3))),
|
console.info("[vehicle-twin] reference vehicle model loaded", JSON.stringify({
|
||||||
removedFrontSeatTriangles,
|
meshes: report.meshes,
|
||||||
windowPanes: Object.fromEntries(
|
size: normalized.size.toArray().map((value) => Number(value.toFixed(3))),
|
||||||
Object.entries(referenceWindowPanes).map(([key, value]) => [key, value.length]),
|
removedFrontSeatTriangles,
|
||||||
),
|
windowPanes: Object.fromEntries(
|
||||||
frontLampMaterials: referenceFrontLampMaterials.length,
|
Object.entries(referenceWindowPanes).map(([key, value]) => [key, value.length]),
|
||||||
frontLampMeshes: referenceFrontLampMeshes.length,
|
),
|
||||||
frontLampHighlights: referenceFrontLampHighlights.length,
|
frontLampMaterials: referenceFrontLampMaterials.length,
|
||||||
frontLampLensOutlines: referenceFrontLampLensOutlines.length,
|
frontLampMeshes: referenceFrontLampMeshes.length,
|
||||||
rearLampMeshes: passiveRearExterior.rearLampMeshes.length,
|
frontLampHighlights: referenceFrontLampHighlights.length,
|
||||||
rearBumperMeshes: passiveRearExterior.rearBumperMeshes.length,
|
frontLampLensOutlines: referenceFrontLampLensOutlines.length,
|
||||||
driverCushion,
|
rearLampMeshes: passiveRearExterior.rearLampMeshes.length,
|
||||||
passengerCushion,
|
rearBumperMeshes: passiveRearExterior.rearBumperMeshes.length,
|
||||||
}));
|
driverCushion,
|
||||||
resolve({ vehicle, report, normalized, driverCushion, passengerCushion });
|
passengerCushion,
|
||||||
|
}));
|
||||||
|
settle(resolve, { vehicle, report, normalized, driverCushion, passengerCushion });
|
||||||
|
} catch (error) {
|
||||||
|
settle(reject, error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(event) => {
|
(event) => {
|
||||||
if (!fallback || !event.total) return;
|
if (!fallback || !event.total) return;
|
||||||
@@ -3717,7 +3743,7 @@ function loadReferenceVehicleModel(THREE, scene, vehicleSystems, fallback) {
|
|||||||
const label = fallback.querySelector("span");
|
const label = fallback.querySelector("span");
|
||||||
if (label) label.textContent = `正在加载整车参考模型 · ${progress}%`;
|
if (label) label.textContent = `正在加载整车参考模型 · ${progress}%`;
|
||||||
},
|
},
|
||||||
reject,
|
(error) => settle(reject, error),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3726,7 +3752,33 @@ function initSeatTwin() {
|
|||||||
const canvas = $("#seatTwinCanvas");
|
const canvas = $("#seatTwinCanvas");
|
||||||
const stage = $("#seatTwin");
|
const stage = $("#seatTwin");
|
||||||
const fallback = $("#twinFallback");
|
const fallback = $("#twinFallback");
|
||||||
if (!canvas || !stage) return;
|
if (!canvas || !stage || state.seatTwin) return;
|
||||||
|
const stageHasLayout = () => {
|
||||||
|
const bounds = stage.getBoundingClientRect();
|
||||||
|
return stage.isConnected && bounds.width >= 2 && bounds.height >= 2;
|
||||||
|
};
|
||||||
|
if (!stageHasLayout()) {
|
||||||
|
if (stage.dataset.twinInitPending === "true") return;
|
||||||
|
stage.dataset.twinInitPending = "true";
|
||||||
|
let pendingObserver = null;
|
||||||
|
const retryInitialization = () => {
|
||||||
|
if (!stageHasLayout()) return;
|
||||||
|
pendingObserver?.disconnect();
|
||||||
|
window.removeEventListener("resize", scheduleRetry);
|
||||||
|
window.removeEventListener("cognitive-agent:module-change", scheduleRetry);
|
||||||
|
delete stage.dataset.twinInitPending;
|
||||||
|
initSeatTwin();
|
||||||
|
};
|
||||||
|
const scheduleRetry = () => window.requestAnimationFrame(retryInitialization);
|
||||||
|
if (window.ResizeObserver) {
|
||||||
|
pendingObserver = new ResizeObserver(retryInitialization);
|
||||||
|
pendingObserver.observe(stage);
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", scheduleRetry);
|
||||||
|
window.addEventListener("cognitive-agent:module-change", scheduleRetry);
|
||||||
|
scheduleRetry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!window.THREE) {
|
if (!window.THREE) {
|
||||||
fallback.classList.add("error");
|
fallback.classList.add("error");
|
||||||
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>3D 引擎加载失败,精确状态仍可用</span>';
|
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>3D 引擎加载失败,精确状态仍可用</span>';
|
||||||
@@ -3735,7 +3787,16 @@ function initSeatTwin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const THREE = window.THREE;
|
const THREE = window.THREE;
|
||||||
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: "high-performance" });
|
let renderer;
|
||||||
|
try {
|
||||||
|
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: "high-performance" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[vehicle-twin] failed to initialize WebGL", error);
|
||||||
|
fallback.classList.add("error");
|
||||||
|
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>3D 渲染器初始化失败,状态数据仍可用</span>';
|
||||||
|
refreshIcons();
|
||||||
|
return;
|
||||||
|
}
|
||||||
renderer.autoClear = false;
|
renderer.autoClear = false;
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
||||||
if ("outputColorSpace" in renderer) renderer.outputColorSpace = THREE.SRGBColorSpace;
|
if ("outputColorSpace" in renderer) renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
@@ -3883,6 +3944,7 @@ function initSeatTwin() {
|
|||||||
applyTwinSceneTheme(state.seatTwin);
|
applyTwinSceneTheme(state.seatTwin);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
canvas.dataset.referenceModelState = "loading";
|
||||||
loadReferenceVehicleModel(THREE, scene, vehicleSystems, fallback)
|
loadReferenceVehicleModel(THREE, scene, vehicleSystems, fallback)
|
||||||
.then(({ vehicle, report, normalized, driverCushion, passengerCushion }) => {
|
.then(({ vehicle, report, normalized, driverCushion, passengerCushion }) => {
|
||||||
if (!state.seatTwin) return;
|
if (!state.seatTwin) return;
|
||||||
@@ -3920,11 +3982,12 @@ function initSeatTwin() {
|
|||||||
.toArray()
|
.toArray()
|
||||||
.map((value) => value.toFixed(2))
|
.map((value) => value.toFixed(2))
|
||||||
.join("x");
|
.join("x");
|
||||||
canvas.dataset.referenceModelLoaded = "true";
|
canvas.dataset.referenceModelState = "loaded";
|
||||||
fallback.hidden = true;
|
fallback.hidden = true;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("[vehicle-twin] failed to load reference vehicle model", error);
|
console.error("[vehicle-twin] failed to load reference vehicle model", error);
|
||||||
|
canvas.dataset.referenceModelState = "failed";
|
||||||
fallback.classList.add("error");
|
fallback.classList.add("error");
|
||||||
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>实车模型加载失败,已切换轻量回退模型</span>';
|
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>实车模型加载失败,已切换轻量回退模型</span>';
|
||||||
refreshIcons();
|
refreshIcons();
|
||||||
@@ -3940,17 +4003,64 @@ function initSeatTwin() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stageWasVisible = false;
|
||||||
function resize() {
|
function resize() {
|
||||||
const width = Math.max(1, canvas.clientWidth);
|
const width = Math.max(1, Math.round(stage.clientWidth));
|
||||||
const height = Math.max(1, canvas.clientHeight);
|
const height = Math.max(1, Math.round(stage.clientHeight));
|
||||||
renderer.setSize(width, height, false);
|
renderer.setSize(width, height, false);
|
||||||
camera.aspect = width / height;
|
camera.aspect = width / height;
|
||||||
camera.updateProjectionMatrix();
|
camera.updateProjectionMatrix();
|
||||||
state.seatTwin.narrowViewport = width < 600;
|
state.seatTwin.narrowViewport = width < 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
new ResizeObserver(resize).observe(stage);
|
function synchronizeTwinViewport({ reset = false } = {}) {
|
||||||
resize();
|
if (!stageHasLayout()) {
|
||||||
|
stageWasVisible = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
resize();
|
||||||
|
if (reset || !stageWasVisible) resetView();
|
||||||
|
stageWasVisible = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleViewportSync = () => {
|
||||||
|
window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
|
||||||
|
synchronizeTwinViewport();
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
if (window.ResizeObserver) {
|
||||||
|
const stageObserver = new ResizeObserver(() => synchronizeTwinViewport());
|
||||||
|
stageObserver.observe(stage);
|
||||||
|
state.seatTwin.resizeObserver = stageObserver;
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", scheduleViewportSync);
|
||||||
|
window.addEventListener("cognitive-agent:module-change", scheduleViewportSync);
|
||||||
|
canvas.addEventListener("webglcontextlost", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
canvas.dataset.contextLost = "true";
|
||||||
|
fallback.hidden = false;
|
||||||
|
fallback.classList.add("error");
|
||||||
|
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>3D 上下文已中断,正在等待恢复</span>';
|
||||||
|
refreshIcons();
|
||||||
|
});
|
||||||
|
canvas.addEventListener("webglcontextrestored", () => {
|
||||||
|
delete canvas.dataset.contextLost;
|
||||||
|
synchronizeTwinViewport({ reset: true });
|
||||||
|
const modelState = canvas.dataset.referenceModelState;
|
||||||
|
fallback.hidden = modelState === "loaded";
|
||||||
|
if (modelState === "loading") {
|
||||||
|
fallback.classList.remove("error");
|
||||||
|
fallback.innerHTML = '<i data-lucide="loader-circle"></i><span>正在加载整车参考模型</span>';
|
||||||
|
refreshIcons();
|
||||||
|
} else if (modelState === "failed") {
|
||||||
|
fallback.classList.add("error");
|
||||||
|
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>实车模型加载失败,已切换轻量回退模型</span>';
|
||||||
|
refreshIcons();
|
||||||
|
window.setTimeout(() => { fallback.hidden = true; }, 3200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
synchronizeTwinViewport({ reset: true });
|
||||||
stage.addEventListener("pointerdown", (event) => {
|
stage.addEventListener("pointerdown", (event) => {
|
||||||
state.seatTwin.dragging = true;
|
state.seatTwin.dragging = true;
|
||||||
state.seatTwin.pointer = { x: event.clientX, y: event.clientY };
|
state.seatTwin.pointer = { x: event.clientX, y: event.clientY };
|
||||||
@@ -4052,6 +4162,12 @@ function initSeatTwin() {
|
|||||||
function animate(timestamp = 0) {
|
function animate(timestamp = 0) {
|
||||||
const twin = state.seatTwin;
|
const twin = state.seatTwin;
|
||||||
if (!twin) return;
|
if (!twin) return;
|
||||||
|
if (!stageHasLayout() || canvas.dataset.contextLost === "true") {
|
||||||
|
stageWasVisible = false;
|
||||||
|
window.requestAnimationFrame(animate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!stageWasVisible) synchronizeTwinViewport({ reset: true });
|
||||||
Object.values(twin.models).forEach((model) => {
|
Object.values(twin.models).forEach((model) => {
|
||||||
Object.keys(model.positions).forEach((axis) => {
|
Object.keys(model.positions).forEach((axis) => {
|
||||||
const delta = model.targets[axis] - model.positions[axis];
|
const delta = model.targets[axis] - model.positions[axis];
|
||||||
@@ -6556,7 +6672,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
initJointTwinLayout();
|
initJointTwinLayout();
|
||||||
initResultWorkspace();
|
initResultWorkspace();
|
||||||
refreshIcons();
|
refreshIcons();
|
||||||
initSeatTwin();
|
window.requestAnimationFrame(initSeatTwin);
|
||||||
$("#requestForm").addEventListener("submit", submitRequest);
|
$("#requestForm").addEventListener("submit", submitRequest);
|
||||||
$("#utterance").addEventListener("keydown", (event) => {
|
$("#utterance").addEventListener("keydown", (event) => {
|
||||||
if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
|
if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
|
||||||
|
|||||||
@@ -19,14 +19,14 @@
|
|||||||
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
||||||
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
||||||
<script src="/network.js?v=android-pad-1" defer></script>
|
<script src="/network.js?v=android-pad-1" defer></script>
|
||||||
<script src="/app.js?v=dual-seat-80" defer></script>
|
<script src="/app.js?v=dual-seat-81" defer></script>
|
||||||
<script type="module" src="/gesture-control.js?v=android-pad-7"></script>
|
<script type="module" src="/gesture-control.js?v=android-pad-7"></script>
|
||||||
<script src="/pwa.js?v=android-pad-1" defer></script>
|
<script src="/pwa.js?v=android-pad-1" defer></script>
|
||||||
<script src="/console-v2.js?v=desktop-refactor-35" defer></script>
|
<script src="/console-v2.js?v=desktop-refactor-35" defer></script>
|
||||||
<script src="/self-extension.js?v=extension-20" defer></script>
|
<script src="/self-extension.js?v=extension-20" defer></script>
|
||||||
<script src="/self-calibration.js?v=calibration-6" defer></script>
|
<script src="/self-calibration.js?v=calibration-6" defer></script>
|
||||||
<script src="/self-repair.js?v=repair-20" defer></script>
|
<script src="/self-repair.js?v=repair-20" defer></script>
|
||||||
<script src="/self-repair-seat3d.js?v=repair-18" defer></script>
|
<script src="/self-repair-seat3d.js?v=repair-19" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
|
|||||||
@@ -1,6 +1,33 @@
|
|||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
function viewerHasLayout(viewer) {
|
||||||
|
const bounds = viewer.getBoundingClientRect();
|
||||||
|
return viewer.isConnected && bounds.width >= 2 && bounds.height >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deferSeat3dUntilVisible(viewer, options) {
|
||||||
|
if (viewer.dataset.seat3dPending === "true") return;
|
||||||
|
viewer.dataset.seat3dPending = "true";
|
||||||
|
let observer = null;
|
||||||
|
const retry = () => {
|
||||||
|
if (!viewerHasLayout(viewer)) return;
|
||||||
|
observer?.disconnect();
|
||||||
|
window.removeEventListener("resize", scheduleRetry);
|
||||||
|
window.removeEventListener("cognitive-agent:module-change", scheduleRetry);
|
||||||
|
delete viewer.dataset.seat3dPending;
|
||||||
|
initSeat3d(options);
|
||||||
|
};
|
||||||
|
const scheduleRetry = () => window.requestAnimationFrame(retry);
|
||||||
|
if (window.ResizeObserver) {
|
||||||
|
observer = new ResizeObserver(retry);
|
||||||
|
observer.observe(viewer);
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", scheduleRetry);
|
||||||
|
window.addEventListener("cognitive-agent:module-change", scheduleRetry);
|
||||||
|
scheduleRetry();
|
||||||
|
}
|
||||||
|
|
||||||
function initSeat3d(options) {
|
function initSeat3d(options) {
|
||||||
const {
|
const {
|
||||||
canvasId,
|
canvasId,
|
||||||
@@ -15,6 +42,10 @@
|
|||||||
const canvas = document.getElementById(canvasId);
|
const canvas = document.getElementById(canvasId);
|
||||||
const viewer = document.getElementById(viewerId);
|
const viewer = document.getElementById(viewerId);
|
||||||
if (!canvas || !viewer || viewer.dataset.seat3dInitialized === "true") return;
|
if (!canvas || !viewer || viewer.dataset.seat3dInitialized === "true") return;
|
||||||
|
if (!viewerHasLayout(viewer)) {
|
||||||
|
deferSeat3dUntilVisible(viewer, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
viewer.dataset.seat3dInitialized = "true";
|
viewer.dataset.seat3dInitialized = "true";
|
||||||
|
|
||||||
const markReady = (fallback = false) => {
|
const markReady = (fallback = false) => {
|
||||||
@@ -387,6 +418,24 @@
|
|||||||
camera.updateProjectionMatrix();
|
camera.updateProjectionMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let viewerWasVisible = false;
|
||||||
|
function synchronizeViewport({ reset = false } = {}) {
|
||||||
|
if (!viewerHasLayout(viewer)) {
|
||||||
|
viewerWasVisible = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
resize();
|
||||||
|
if (reset || !viewerWasVisible) resetOrbit();
|
||||||
|
viewerWasVisible = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleViewportSync = () => {
|
||||||
|
window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
|
||||||
|
synchronizeViewport();
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
function onHealingState(event) {
|
function onHealingState(event) {
|
||||||
const detail = event.detail || {};
|
const detail = event.detail || {};
|
||||||
(detail.axes || []).forEach((axis) => {
|
(detail.axes || []).forEach((axis) => {
|
||||||
@@ -404,6 +453,11 @@
|
|||||||
|
|
||||||
function animate() {
|
function animate() {
|
||||||
window.requestAnimationFrame(animate);
|
window.requestAnimationFrame(animate);
|
||||||
|
if (!viewerHasLayout(viewer) || canvas.dataset.contextLost === "true") {
|
||||||
|
viewerWasVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!viewerWasVisible) synchronizeViewport({ reset: true });
|
||||||
for (let index = 0; index < current.axes.length; index += 1) {
|
for (let index = 0; index < current.axes.length; index += 1) {
|
||||||
current.axes[index] += (desired.axes[index] - current.axes[index]) * 0.055;
|
current.axes[index] += (desired.axes[index] - current.axes[index]) * 0.055;
|
||||||
}
|
}
|
||||||
@@ -486,10 +540,23 @@
|
|||||||
window.addEventListener(stateEvent, onHealingState);
|
window.addEventListener(stateEvent, onHealingState);
|
||||||
window.addEventListener(resetEvent, resetOrbit);
|
window.addEventListener(resetEvent, resetOrbit);
|
||||||
|
|
||||||
if (window.ResizeObserver) new ResizeObserver(resize).observe(viewer);
|
if (window.ResizeObserver) {
|
||||||
window.addEventListener("resize", resize);
|
const resizeObserver = new ResizeObserver(() => synchronizeViewport());
|
||||||
resize();
|
resizeObserver.observe(viewer);
|
||||||
resetOrbit();
|
}
|
||||||
|
window.addEventListener("resize", scheduleViewportSync);
|
||||||
|
window.addEventListener("cognitive-agent:module-change", scheduleViewportSync);
|
||||||
|
canvas.addEventListener("webglcontextlost", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
canvas.dataset.contextLost = "true";
|
||||||
|
viewer.classList.add("context-lost");
|
||||||
|
});
|
||||||
|
canvas.addEventListener("webglcontextrestored", () => {
|
||||||
|
delete canvas.dataset.contextLost;
|
||||||
|
viewer.classList.remove("context-lost");
|
||||||
|
synchronizeViewport({ reset: true });
|
||||||
|
});
|
||||||
|
synchronizeViewport({ reset: true });
|
||||||
animate();
|
animate();
|
||||||
window.requestAnimationFrame(() => markReady());
|
window.requestAnimationFrame(() => markReady());
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE_VERSION = "cognitive-agent-pad-v116";
|
const CACHE_VERSION = "cognitive-agent-pad-v117";
|
||||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||||
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
@@ -6,7 +6,7 @@ const APP_SHELL = [
|
|||||||
"/index.html",
|
"/index.html",
|
||||||
"/styles.css?v=dual-seat-9",
|
"/styles.css?v=dual-seat-9",
|
||||||
"/network.js?v=android-pad-1",
|
"/network.js?v=android-pad-1",
|
||||||
"/app.js?v=dual-seat-80",
|
"/app.js?v=dual-seat-81",
|
||||||
"/gesture-control.js?v=android-pad-7",
|
"/gesture-control.js?v=android-pad-7",
|
||||||
"/pwa.js?v=android-pad-1",
|
"/pwa.js?v=android-pad-1",
|
||||||
"/console-v2.css?v=desktop-refactor-106",
|
"/console-v2.css?v=desktop-refactor-106",
|
||||||
@@ -17,8 +17,8 @@ const APP_SHELL = [
|
|||||||
"/self-calibration.js?v=calibration-6",
|
"/self-calibration.js?v=calibration-6",
|
||||||
"/self-repair.css?v=repair-21",
|
"/self-repair.css?v=repair-21",
|
||||||
"/self-repair.js?v=repair-20",
|
"/self-repair.js?v=repair-20",
|
||||||
"/self-repair-seat3d.js?v=repair-18",
|
"/self-repair-seat3d.js?v=repair-19",
|
||||||
"/assets/models/n7_seat.glb",
|
"/assets/models/n7_seat.glb?v=e7f50f03",
|
||||||
"/manifest.webmanifest",
|
"/manifest.webmanifest",
|
||||||
"/icons/app-icon-192.png",
|
"/icons/app-icon-192.png",
|
||||||
"/icons/app-icon-512.png",
|
"/icons/app-icon-512.png",
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
9136
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
id: k-c78dff
|
|
||||||
name: K歌模式
|
|
||||||
aliases:
|
|
||||||
- K歌模式
|
|
||||||
- K歌
|
|
||||||
description: 用户固化的长期模式配置。
|
|
||||||
icon: bookmark
|
|
||||||
scope:
|
|
||||||
vehicle_ids:
|
|
||||||
- denza_n7
|
|
||||||
operations:
|
|
||||||
- domain: ambient_light
|
|
||||||
capability: ambient_light.set_power
|
|
||||||
target_scope: vehicle
|
|
||||||
operation: activate
|
|
||||||
parameters:
|
|
||||||
state: 'on'
|
|
||||||
reason: 开启氛围灯以营造K歌场景。
|
|
||||||
- domain: ambient_light
|
|
||||||
capability: ambient_light.set_illumination
|
|
||||||
target_scope: vehicle
|
|
||||||
operation: set
|
|
||||||
parameters:
|
|
||||||
color_hex: '#B84DFF'
|
|
||||||
brightness_pct: 65
|
|
||||||
reason: 使用明快紫色和中高亮度营造活跃舞台氛围。
|
|
||||||
- domain: climate
|
|
||||||
capability: climate.set_power
|
|
||||||
target_scope: vehicle
|
|
||||||
operation: activate
|
|
||||||
parameters:
|
|
||||||
state: 'on'
|
|
||||||
reason: 空调当前关闭,需先开启以维持K歌时的座舱舒适度。
|
|
||||||
- domain: climate
|
|
||||||
capability: climate.set_temperature
|
|
||||||
target_scope: driver
|
|
||||||
operation: set
|
|
||||||
parameters:
|
|
||||||
temperature_c: 24
|
|
||||||
reason: 采用舒适且保守的座舱温度。
|
|
||||||
- domain: climate
|
|
||||||
capability: climate.set_fan_level
|
|
||||||
target_scope: vehicle
|
|
||||||
operation: set
|
|
||||||
parameters:
|
|
||||||
level: 2
|
|
||||||
reason: 采用低风量,兼顾通风并减少对歌声的干扰。
|
|
||||||
- domain: climate
|
|
||||||
capability: climate.set_ac
|
|
||||||
target_scope: vehicle
|
|
||||||
operation: activate
|
|
||||||
parameters:
|
|
||||||
state: 'on'
|
|
||||||
reason: 启用制冷除湿以维持多人演唱时的舒适度。
|
|
||||||
- domain: climate
|
|
||||||
capability: climate.set_cycle_mode
|
|
||||||
target_scope: vehicle
|
|
||||||
operation: set
|
|
||||||
parameters:
|
|
||||||
mode: internal
|
|
||||||
reason: 采用内循环以稳定座舱温度并减少外部噪声和气味。
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
id: mode-ceff921663
|
||||||
|
name: 躺平模式
|
||||||
|
aliases:
|
||||||
|
- 躺平模式
|
||||||
|
- 躺平
|
||||||
|
description: 用户固化的长期模式配置。
|
||||||
|
icon: bookmark
|
||||||
|
scope:
|
||||||
|
vehicle_ids:
|
||||||
|
- denza_n7
|
||||||
|
operations:
|
||||||
|
- domain: seat
|
||||||
|
capability: seat.set_longitudinal_position
|
||||||
|
target_scope: passenger
|
||||||
|
operation: set
|
||||||
|
parameters:
|
||||||
|
position_pct: 90
|
||||||
|
reason: 用户固化的座椅位置模式动作。
|
||||||
|
- domain: seat
|
||||||
|
capability: seat.set_cushion_tilt
|
||||||
|
target_scope: passenger
|
||||||
|
operation: set
|
||||||
|
parameters:
|
||||||
|
position_pct: 40
|
||||||
|
reason: 用户固化的座椅位置模式动作。
|
||||||
|
- domain: seat
|
||||||
|
capability: seat.set_backrest_recline
|
||||||
|
target_scope: passenger
|
||||||
|
operation: set
|
||||||
|
parameters:
|
||||||
|
position_pct: 80
|
||||||
|
reason: 用户固化的座椅位置模式动作。
|
||||||
|
- domain: seat
|
||||||
|
capability: seat.set_legrest_height
|
||||||
|
target_scope: passenger
|
||||||
|
operation: set
|
||||||
|
parameters:
|
||||||
|
position_pct: 75
|
||||||
|
reason: 用户固化的座椅位置模式动作。
|
||||||
|
- domain: seat
|
||||||
|
capability: seat.set_legrest_extension
|
||||||
|
target_scope: passenger
|
||||||
|
operation: set
|
||||||
|
parameters:
|
||||||
|
position_pct: 80
|
||||||
|
reason: 用户固化的座椅位置模式动作。
|
||||||
@@ -243,7 +243,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
|||||||
if path.startswith("/api/requests/"):
|
if path.startswith("/api/requests/"):
|
||||||
self._serve_request(path.removeprefix("/api/requests/"))
|
self._serve_request(path.removeprefix("/api/requests/"))
|
||||||
return
|
return
|
||||||
self._serve_static(path)
|
self._serve_static(path, parsed_url.query)
|
||||||
|
|
||||||
def do_POST(self) -> None: # noqa: N802 - stdlib handler API
|
def do_POST(self) -> None: # noqa: N802 - stdlib handler API
|
||||||
path = unquote(urlparse(self.path).path)
|
path = unquote(urlparse(self.path).path)
|
||||||
@@ -942,17 +942,24 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
|||||||
return
|
return
|
||||||
self._json(json.loads(result_path.read_text(encoding="utf-8")))
|
self._json(json.loads(result_path.read_text(encoding="utf-8")))
|
||||||
|
|
||||||
def _serve_static(self, path: str) -> None:
|
def _serve_static(self, path: str, query: str = "") -> None:
|
||||||
relative_path = "index.html" if path in {"", "/"} else path.lstrip("/")
|
relative_path = "index.html" if path in {"", "/"} else path.lstrip("/")
|
||||||
self._serve_static_file(relative_path)
|
versioned = bool(parse_qs(query).get("v"))
|
||||||
|
self._serve_static_file(relative_path, versioned=versioned)
|
||||||
|
|
||||||
def _serve_static_file(self, relative_path: str) -> None:
|
def _serve_static_file(self, relative_path: str, *, versioned: bool = False) -> None:
|
||||||
candidate = (STATIC_ROOT / relative_path).resolve()
|
candidate = (STATIC_ROOT / relative_path).resolve()
|
||||||
try:
|
try:
|
||||||
candidate.relative_to(STATIC_ROOT.resolve())
|
candidate.relative_to(STATIC_ROOT.resolve())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
|
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
|
||||||
return
|
return
|
||||||
|
if not candidate.is_file() and (
|
||||||
|
Path(relative_path).suffix
|
||||||
|
or relative_path.startswith(("assets/", "vendor/", "icons/"))
|
||||||
|
):
|
||||||
|
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
|
||||||
|
return
|
||||||
if not candidate.is_file():
|
if not candidate.is_file():
|
||||||
candidate = STATIC_ROOT / "index.html"
|
candidate = STATIC_ROOT / "index.html"
|
||||||
content_type, _ = mimetypes.guess_type(candidate.name)
|
content_type, _ = mimetypes.guess_type(candidate.name)
|
||||||
@@ -972,8 +979,12 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
|||||||
)
|
)
|
||||||
self.send_header("Content-Length", str(len(content)))
|
self.send_header("Content-Length", str(len(content)))
|
||||||
cache_control = "no-cache"
|
cache_control = "no-cache"
|
||||||
if relative_path.startswith(("vendor/", "assets/", "icons/")):
|
if relative_path.startswith(("vendor/", "icons/")) or (
|
||||||
|
relative_path.startswith("assets/") and versioned
|
||||||
|
):
|
||||||
cache_control = "public, max-age=31536000, immutable"
|
cache_control = "public, max-age=31536000, immutable"
|
||||||
|
elif relative_path.startswith("assets/"):
|
||||||
|
cache_control = "no-cache"
|
||||||
elif candidate.name in {"index.html", "login.html", "service-worker.js"}:
|
elif candidate.name in {"index.html", "login.html", "service-worker.js"}:
|
||||||
cache_control = "no-cache"
|
cache_control = "no-cache"
|
||||||
self.send_header("Cache-Control", cache_control)
|
self.send_header("Cache-Control", cache_control)
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ operations:
|
|||||||
def test_static_console_and_runtime_endpoint(self) -> None:
|
def test_static_console_and_runtime_endpoint(self) -> None:
|
||||||
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
||||||
html = response.read().decode("utf-8")
|
html = response.read().decode("utf-8")
|
||||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-80", timeout=3) as response:
|
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-81", timeout=3) as response:
|
||||||
app_script = response.read().decode("utf-8")
|
app_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/gesture-control.js?v=android-pad-7", timeout=3
|
f"{self.base_url}/gesture-control.js?v=android-pad-7", timeout=3
|
||||||
@@ -199,6 +199,10 @@ operations:
|
|||||||
f"{self.base_url}/self-extension.css?v=extension-18", timeout=3
|
f"{self.base_url}/self-extension.css?v=extension-18", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
|
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
|
||||||
|
with self.opener.open(
|
||||||
|
f"{self.base_url}/service-worker.js", timeout=3
|
||||||
|
) as response:
|
||||||
|
service_worker = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/api/extension/health", timeout=3
|
f"{self.base_url}/api/extension/health", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
@@ -219,7 +223,7 @@ operations:
|
|||||||
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
||||||
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
||||||
self.assertNotIn("透明车体 · 实体设备", html)
|
self.assertNotIn("透明车体 · 实体设备", html)
|
||||||
self.assertIn('src="/app.js?v=dual-seat-80"', html)
|
self.assertIn('src="/app.js?v=dual-seat-81"', html)
|
||||||
self.assertIn('href="/styles.css?v=dual-seat-9"', html)
|
self.assertIn('href="/styles.css?v=dual-seat-9"', html)
|
||||||
self.assertIn('src="/gesture-control.js?v=android-pad-7"', html)
|
self.assertIn('src="/gesture-control.js?v=android-pad-7"', html)
|
||||||
self.assertNotIn('class="vehicle-system-hud"', html)
|
self.assertNotIn('class="vehicle-system-hud"', html)
|
||||||
@@ -438,6 +442,23 @@ operations:
|
|||||||
self.assertIn("function createReferenceFrontLampHighlights", app_script)
|
self.assertIn("function createReferenceFrontLampHighlights", app_script)
|
||||||
self.assertIn("referenceFrontLampMaterials", app_script)
|
self.assertIn("referenceFrontLampMaterials", app_script)
|
||||||
self.assertIn("targets.windowPositions", app_script)
|
self.assertIn("targets.windowPositions", app_script)
|
||||||
|
self.assertIn(
|
||||||
|
'const REFERENCE_VEHICLE_ASSET_VERSION = "77a0fae7"', app_script
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"const REFERENCE_VEHICLE_LOAD_TIMEOUT_MS = 30000", app_script
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
'referenceVehicleAssetUrl("byd-tang-2024.fbx")', app_script
|
||||||
|
)
|
||||||
|
self.assertIn("const stageHasLayout = () =>", app_script)
|
||||||
|
self.assertIn("window.requestAnimationFrame(initSeatTwin)", app_script)
|
||||||
|
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
||||||
|
self.assertIn('"cognitive-agent:module-change"', app_script)
|
||||||
|
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v117"', service_worker)
|
||||||
|
self.assertIn(
|
||||||
|
'"/assets/models/n7_seat.glb?v=e7f50f03"', service_worker
|
||||||
|
)
|
||||||
self.assertIn("hardware_access_required", app_script)
|
self.assertIn("hardware_access_required", app_script)
|
||||||
self.assertIn('button.setAttribute("aria-label", "关闭摄像头")', gesture_script)
|
self.assertIn('button.setAttribute("aria-label", "关闭摄像头")', gesture_script)
|
||||||
self.assertIn("function refreshCameraAvailability", gesture_script)
|
self.assertIn("function refreshCameraAvailability", gesture_script)
|
||||||
@@ -647,7 +668,7 @@ operations:
|
|||||||
) as response:
|
) as response:
|
||||||
extension_script = response.read().decode("utf-8")
|
extension_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/app.js?v=dual-seat-80", timeout=3
|
f"{self.base_url}/app.js?v=dual-seat-81", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
app_script = response.read().decode("utf-8")
|
app_script = response.read().decode("utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,18 @@ class TestSelfCalibrationApi:
|
|||||||
model = response.read()
|
model = response.read()
|
||||||
content_type = response.headers["Content-Type"]
|
content_type = response.headers["Content-Type"]
|
||||||
content_security_policy = response.headers["Content-Security-Policy"]
|
content_security_policy = response.headers["Content-Security-Policy"]
|
||||||
|
unversioned_cache_control = response.headers["Cache-Control"]
|
||||||
|
with urllib.request.urlopen(
|
||||||
|
f"{self.base_url}/assets/models/n7_seat.glb?v=e7f50f03",
|
||||||
|
timeout=3,
|
||||||
|
) as response:
|
||||||
|
versioned_model_prefix = response.read(4)
|
||||||
|
versioned_cache_control = response.headers["Cache-Control"]
|
||||||
|
with pytest.raises(urllib.error.HTTPError) as missing_model:
|
||||||
|
urllib.request.urlopen(
|
||||||
|
f"{self.base_url}/assets/models/missing-seat.glb?v=missing",
|
||||||
|
timeout=3,
|
||||||
|
)
|
||||||
|
|
||||||
assert 'href="/self-calibration.css?v=calibration-10"' in html
|
assert 'href="/self-calibration.css?v=calibration-10"' in html
|
||||||
assert 'src="/self-calibration.js?v=calibration-6"' in html
|
assert 'src="/self-calibration.js?v=calibration-6"' in html
|
||||||
@@ -173,6 +185,10 @@ class TestSelfCalibrationApi:
|
|||||||
assert '["self-calibration", "self-repair"].includes(module)' in console_script
|
assert '["self-calibration", "self-repair"].includes(module)' in console_script
|
||||||
assert 'setRole("admin", { allowModuleTransition: true })' in console_script
|
assert 'setRole("admin", { allowModuleTransition: true })' in console_script
|
||||||
assert len(model) == 845_992
|
assert len(model) == 845_992
|
||||||
|
assert versioned_model_prefix == model[:4]
|
||||||
assert content_type.startswith("model/gltf-binary")
|
assert content_type.startswith("model/gltf-binary")
|
||||||
|
assert unversioned_cache_control == "no-cache"
|
||||||
|
assert versioned_cache_control == "public, max-age=31536000, immutable"
|
||||||
|
assert missing_model.value.code == 404
|
||||||
assert "img-src 'self' data: blob:;" in content_security_policy
|
assert "img-src 'self' data: blob:;" in content_security_policy
|
||||||
assert "connect-src 'self' blob:;" in content_security_policy
|
assert "connect-src 'self' blob:;" in content_security_policy
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ class TestSelfRepairApi:
|
|||||||
) as response:
|
) as response:
|
||||||
styles = response.read().decode("utf-8")
|
styles = response.read().decode("utf-8")
|
||||||
with urllib.request.urlopen(
|
with urllib.request.urlopen(
|
||||||
f"{self.base_url}/self-repair-seat3d.js?v=repair-18", timeout=3
|
f"{self.base_url}/self-repair-seat3d.js?v=repair-19", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
seat3d = response.read().decode("utf-8")
|
seat3d = response.read().decode("utf-8")
|
||||||
with urllib.request.urlopen(
|
with urllib.request.urlopen(
|
||||||
@@ -168,7 +168,7 @@ class TestSelfRepairApi:
|
|||||||
assert 'SELF-REPAIR' in script
|
assert 'SELF-REPAIR' in script
|
||||||
assert 'padding: var(--v2-module-title-block) 0 0' in styles
|
assert 'padding: var(--v2-module-title-block) 0 0' in styles
|
||||||
assert 'margin-inline: var(--v2-module-title-inline)' in styles
|
assert 'margin-inline: var(--v2-module-title-inline)' in styles
|
||||||
assert 'src="/self-repair-seat3d.js?v=repair-18"' in html
|
assert 'src="/self-repair-seat3d.js?v=repair-19"' in html
|
||||||
assert "量产故障注入" in script
|
assert "量产故障注入" in script
|
||||||
assert "运行与自修复日志" in script
|
assert "运行与自修复日志" in script
|
||||||
assert "启动座椅运行" in script
|
assert "启动座椅运行" in script
|
||||||
@@ -203,6 +203,11 @@ class TestSelfRepairApi:
|
|||||||
assert 'viewer.addEventListener("pointerdown"' in seat3d
|
assert 'viewer.addEventListener("pointerdown"' in seat3d
|
||||||
assert 'window.addEventListener("pointermove"' in seat3d
|
assert 'window.addEventListener("pointermove"' in seat3d
|
||||||
assert "window.addEventListener(resetEvent" in seat3d
|
assert "window.addEventListener(resetEvent" in seat3d
|
||||||
|
assert "function viewerHasLayout(viewer)" in seat3d
|
||||||
|
assert "function deferSeat3dUntilVisible(viewer, options)" in seat3d
|
||||||
|
assert "if (!viewerHasLayout(viewer))" in seat3d
|
||||||
|
assert 'window.addEventListener("cognitive-agent:module-change"' in seat3d
|
||||||
|
assert 'canvas.addEventListener("webglcontextlost"' in seat3d
|
||||||
assert '["self-calibration", "self-repair"].includes(module)' in console_script
|
assert '["self-calibration", "self-repair"].includes(module)' in console_script
|
||||||
assert 'roleSelect.disabled = ui.activeModule === "self-repair"' in console_script
|
assert 'roleSelect.disabled = ui.activeModule === "self-repair"' in console_script
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"heartbeat_epoch": 1786612896.954201, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
BRIDGE_VERSION = 'runtime-test-v1'
|
||||||
|
def run_bridge(transport=None, stop_event=None):
|
||||||
|
stop_event.wait(2.0)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786612902.0072305, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786612902.0072305, "0x302": 1786612902.0072305, "0x303": 1786612902.0072305, "0x304": 1786612902.0072305, "0x305": 1786612902.0072305, "0x306": 1786612902.0072305, "0x307": 1786612902.0072305}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786612902.1554296, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786612902.1554296, "0x302": 1786612902.1554296, "0x303": 1786612902.1554296, "0x304": 1786612902.1554296, "0x305": 1786612902.1554296, "0x306": 1786612902.1554296, "0x307": 1786612902.1554296}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"heartbeat_epoch": 1786614297.8563914, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
BRIDGE_VERSION = 'runtime-test-v1'
|
||||||
|
def run_bridge(transport=None, stop_event=None):
|
||||||
|
stop_event.wait(2.0)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786614302.9093528, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786614302.9093528, "0x302": 1786614302.9093528, "0x303": 1786614302.9093528, "0x304": 1786614302.9093528, "0x305": 1786614302.9093528, "0x306": 1786614302.9093528, "0x307": 1786614302.9093528}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786614303.057585, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786614303.057585, "0x302": 1786614303.057585, "0x303": 1786614303.057585, "0x304": 1786614303.057585, "0x305": 1786614303.057585, "0x306": 1786614303.057585, "0x307": 1786614303.057585}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"heartbeat_epoch": 1786614211.5981486, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
BRIDGE_VERSION = 'runtime-test-v1'
|
||||||
|
def run_bridge(transport=None, stop_event=None):
|
||||||
|
stop_event.wait(2.0)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786614216.6407876, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786614216.6407876, "0x302": 1786614216.6407876, "0x303": 1786614216.6407876, "0x304": 1786614216.6407876, "0x305": 1786614216.6407876, "0x306": 1786614216.6407876, "0x307": 1786614216.6407876}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786614216.7809346, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786614216.7809346, "0x302": 1786614216.7809346, "0x303": 1786614216.7809346, "0x304": 1786614216.7809346, "0x305": 1786614216.7809346, "0x306": 1786614216.7809346, "0x307": 1786614216.7809346}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"heartbeat_epoch": 1786628979.608127, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
BRIDGE_VERSION = 'runtime-test-v1'
|
||||||
|
def run_bridge(transport=None, stop_event=None):
|
||||||
|
stop_event.wait(2.0)
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786628984.6589825, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786628984.6589825, "0x302": 1786628984.6589825, "0x303": 1786628984.6589825, "0x304": 1786628984.6589825, "0x305": 1786628984.6589825, "0x306": 1786628984.6589825, "0x307": 1786628984.6589825}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786628984.8052018, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786628984.8052018, "0x302": 1786628984.8052018, "0x303": 1786628984.8052018, "0x304": 1786628984.8052018, "0x305": 1786628984.8052018, "0x306": 1786628984.8052018, "0x307": 1786628984.8052018}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"heartbeat_epoch": 1786608369.3573637, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
BRIDGE_VERSION = 'runtime-test-v1'
|
||||||
|
def run_bridge(transport=None, stop_event=None):
|
||||||
|
stop_event.wait(2.0)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786608374.4077742, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786608374.4077742, "0x302": 1786608374.4077742, "0x303": 1786608374.4077742, "0x304": 1786608374.4077742, "0x305": 1786608374.4077742, "0x306": 1786608374.4077742, "0x307": 1786608374.4077742}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786608374.5544136, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786608374.5544136, "0x302": 1786608374.5544136, "0x303": 1786608374.5544136, "0x304": 1786608374.5544136, "0x305": 1786608374.5544136, "0x306": 1786608374.5544136, "0x307": 1786608374.5544136}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"heartbeat_epoch": 1786607635.6544752, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
BRIDGE_VERSION = 'runtime-test-v1'
|
||||||
|
def run_bridge(transport=None, stop_event=None):
|
||||||
|
stop_event.wait(2.0)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786607640.7259998, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786607640.7259998, "0x302": 1786607640.7259998, "0x303": 1786607640.7259998, "0x304": 1786607640.7259998, "0x305": 1786607640.7259998, "0x306": 1786607640.7259998, "0x307": 1786607640.7259998}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786607640.8686898, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786607640.8686898, "0x302": 1786607640.8686898, "0x303": 1786607640.8686898, "0x304": 1786607640.8686898, "0x305": 1786607640.8686898, "0x306": 1786607640.8686898, "0x307": 1786607640.8686898}}
|
||||||
Reference in New Issue
Block a user