Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions client/dive-common/use/useModeManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import type { AnnotationId } from 'vue-media-annotator/BaseAnnotation';
import type { MarkChangesPending } from 'vue-media-annotator/BaseAnnotationStore';
import Track from 'vue-media-annotator/track';
import { ROTATION_ATTRIBUTE_NAME } from 'vue-media-annotator/utils';
import useModeManager from './useModeManager';
import { clientSettings } from 'dive-common/store/settings';
import useModeManager, { type StereoAnnotationCompleteParams } from './useModeManager';
import HeadTail from '../recipes/headtail';
import { headTailFeatures } from '../../src/headTail';
import type Recipe from '../../src/recipe';
Expand All @@ -24,7 +25,11 @@ function translation(tx: number, ty: number): Matrix3 {
return [[1, 0, tx], [0, 1, ty], [0, 0, 1]];
}

function makeHarness(markChangesPending: MarkChangesPending = () => undefined, recipes: Recipe[] = []) {
function makeHarness(
markChangesPending: MarkChangesPending = () => undefined,
recipes: Recipe[] = [],
onStereoAnnotationComplete: ((params: StereoAnnotationCompleteParams) => void) | undefined = undefined,
) {
const cameraStore = new CameraStore({ markChangesPending });
cameraStore.removeCamera('singleCam');
cameraStore.addCamera('left');
Expand Down Expand Up @@ -77,6 +82,7 @@ function makeHarness(markChangesPending: MarkChangesPending = () => undefined, r
readonlyState: ref(false),
recipes,
alignedView,
onStereoAnnotationComplete,
});
modeManager.selectedCamera.value = 'left';
return {
Expand Down Expand Up @@ -457,6 +463,29 @@ describe('centerline editing continuity', () => {
});
});

describe('stereo mapping of a line being drawn', () => {
it('waits for both ends instead of mapping the first one alone', () => {
const wasAutoCompute = clientSettings.stereoSettings.autoComputeOtherCamera;
clientSettings.stereoSettings.autoComputeOtherCamera = true;
try {
const recipe = new HeadTail();
const events: StereoAnnotationCompleteParams[] = [];
const { modeManager: manager } = makeHarness(undefined, [recipe], (params) => events.push(params));
manager.handler.trackAdd();
recipe.activate();
const draw = (coordinates: number[][]) => manager.handler.updateGeoJSON('in-progress', 0, 0, {
type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates },
}, 'HeadTails');
draw([[10, 10]]);
expect(events).toEqual([]);
draw([[10, 10], [90, 10]]);
expect(events.map((e) => e.type)).toEqual(['line']);
} finally {
clientSettings.stereoSettings.autoComputeOtherCamera = wasAutoCompute;
}
});
});

describe('entering polygon editing', () => {
const polygon = (key: string): GeoJSON.Feature<GeoJSON.Polygon> => ({
type: 'Feature',
Expand Down
6 changes: 4 additions & 2 deletions client/dive-common/use/useModeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,9 +972,11 @@ export default function useModeManager({

mirrorFeatureToAlignedCameras(track.id, frameNum);

// Emit persisted named points, including a head placed before its tail.
// Completed lines use their existing whole-line transfer event instead.
// Emit persisted named points. Completed lines use their existing
// whole-line transfer event instead, and the first end of a line still
// being drawn waits for it: mapping it mid-draw interrupts the draw.
if (onStereoAnnotationComplete && stereoInteractiveActive()
&& update.done.every((v) => v !== false)
&& !(data.geometry.type === 'LineString' && data.geometry.coordinates.length >= 2)) {
Object.entries(update.geoJsonFeatureRecord).forEach(([pointKey, geoms]) => {
geoms.forEach((geom) => {
Expand Down
21 changes: 13 additions & 8 deletions client/platform/desktop/frontend/components/ViewerLoader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@ export default defineComponent({
// line at this frame. Once set, interactive stereo never overwrites that
// side's geometry again — only the user can. Kept off the Attributes panel.
const STEREO_USER_LINE_ATTR = 'stereo_user_line';
const STEREO_LOADING_DIALOG_DELAY_MS = 300;
// How the length was set: 'stereo' = auto-computed from the warped lines,
// 'user_set' = locked by the user (auto-update leaves the length alone).
const STEREO_LENGTH_METHOD_ATTR = 'length_method';
Expand Down Expand Up @@ -1632,10 +1633,15 @@ export default defineComponent({

// Show loading indicator while waiting for stereo transfer (interactive
// single-transfer path only; bulk import owns the dialog itself).
if (!quiet) {
stereoLoadingMessage.value = 'Computing stereo correspondence...';
stereoLoadingError.value = '';
stereoLoadingDialog.value = true;
// Only once the wait is noticeable, so a fast transfer doesn't flash it,
// and never for a single point.
let loadingTimer: number | undefined;
if (!quiet && params.type !== 'point') {
loadingTimer = window.setTimeout(() => {
stereoLoadingMessage.value = 'Computing stereo correspondence...';
stereoLoadingError.value = '';
stereoLoadingDialog.value = true;
}, STEREO_LOADING_DIALOG_DELAY_MS);
}

const pointTargetBefore = params.type === 'point'
Expand Down Expand Up @@ -1916,10 +1922,6 @@ export default defineComponent({
}
}
}
// Success — hide loading dialog (interactive path only)
if (!quiet) {
stereoLoadingDialog.value = false;
}
return 'transferred';
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
Expand All @@ -1936,6 +1938,9 @@ export default defineComponent({
stereoLoadingError.value = `Failed to transfer annotation to the other camera. ${message}`;
stereoLoadingDialog.value = true;
return 'failed';
} finally {
window.clearTimeout(loadingTimer);
if (!quiet && !stereoLoadingError.value) stereoLoadingDialog.value = false;
}
}

Expand Down
Loading