Cache measurement snap candidates

This commit is contained in:
flash555588 2026-07-06 19:33:36 +08:00
parent d20fffbba7
commit 18b78e8e9d
4 changed files with 42 additions and 17 deletions

View file

@ -6,6 +6,7 @@
- Stability: make the direct-view load gate settings-aware for preferred OBJ conversion and keep mobile direct-format reads on Obsidian vault APIs instead of desktop Node path helpers.
- UI: upgrade measurement calibration so users can scale the loaded model uniformly from the latest ruler distance, keep overlays anchored in model space, apply locked model-size scaling from one known axis, and verify the workflow through preview automation.
- UI: make distance measurement lock the selected object by default and snap endpoints to mesh vertices or visible crease/perimeter triangle edges, filtering coplanar face diagonals and preferring edge projection outside the corner radius, with first-click target selection when needed and Alt/Option-click preserving the older free surface pick as a backup.
- Performance: cache selected-target measurement snap candidates while measuring so hover previews do not repeatedly rebuild vertex and edge lists for the same object.
- UI: restyle distance measurements as orthographic drawing-style dimension callouts with extension lines, arrowheads, and compact drafting labels instead of thick freehand ruler lines.
- UI: remove the preview canvas native hover tooltip while preserving keyboard shortcut metadata for accessibility.
- UI: clarify render scale versus measurement scale by showing render resolution as percentages, syncing the toolbar to the active renderer budget, and applying the configured scale to `3dgrid`.

File diff suppressed because one or more lines are too long

View file

@ -116,6 +116,7 @@ import {
setMeasurementCanvasActive,
snapMeasurementPointToGeometry,
unscaleMeasurementPointToBase,
type MeasurementGeometrySnapInput,
type MeasurementSnapEdgeCandidate,
type MeasurementSnapVertexCandidate,
type MeasurementReading,
@ -312,6 +313,8 @@ export class BabylonModelPreview implements WorkbenchPreview {
private measurementMarkerPoints: Vector3[] = [];
private measurementTargetNode: BabylonSelectablePartNode | null = null;
private measurementTargetMeshes: AbstractMesh[] = [];
private measurementSnapInputCache: MeasurementGeometrySnapInput | null = null;
private measurementSnapInputCacheTargetId: number | null = null;
private measurementSnapKind: MeasurementSnapKind | null = null;
private readonly measurementObservers = new Set<() => void>();
private pendingPoint: Vector3 | null = null;
@ -1079,6 +1082,7 @@ export class BabylonModelPreview implements WorkbenchPreview {
setMeasurementScale(scale: MeasurementScale): void {
this.measurementScale = sanitizeMeasurementScale(scale);
this.invalidateMeasurementSnapInputCache();
this.applyMeasurementModelScale();
this.updateMeasurementOverlayPositions();
this.updateMeasurementLabels();
@ -1167,6 +1171,7 @@ export class BabylonModelPreview implements WorkbenchPreview {
this.measurementBaseRootScaling = new Vector3(1, 1, 1);
this.measurementBaseBounds = null;
this.measurementMarkerPoints = [];
this.invalidateMeasurementSnapInputCache();
}
private applyMeasurementModelScale(): void {
@ -1878,6 +1883,7 @@ export class BabylonModelPreview implements WorkbenchPreview {
private setMeasurementTargetNode(node: BabylonSelectablePartNode | null, notify = true): void {
this.clearMeasurementTargetVisual(false);
this.invalidateMeasurementSnapInputCache();
const target = node && !isBabylonNodeDisposed(node) ? this.findSelectableNode(node) : null;
if (!target || isBabylonNodeDisposed(target)) {
this.measurementTargetNode = null;
@ -1940,6 +1946,11 @@ export class BabylonModelPreview implements WorkbenchPreview {
}
}
private invalidateMeasurementSnapInputCache(): void {
this.measurementSnapInputCache = null;
this.measurementSnapInputCacheTargetId = null;
}
private getMeasurementTargetName(): string | null {
const target = this.measurementTargetNode;
if (!target || isBabylonNodeDisposed(target)) return null;
@ -1977,14 +1988,12 @@ export class BabylonModelPreview implements WorkbenchPreview {
return toBabylonVector3(snapped.point);
}
private createMeasurementGeometrySnapInput(): {
vertices: MeasurementSnapVertexCandidate[];
edges: MeasurementSnapEdgeCandidate[];
targetId?: string;
vertexRadius: number;
} | null {
private createMeasurementGeometrySnapInput(): MeasurementGeometrySnapInput | null {
const target = this.measurementTargetNode;
if (!this.rootMesh || !target || isBabylonNodeDisposed(target)) return null;
if (this.measurementSnapInputCache && this.measurementSnapInputCacheTargetId === target.uniqueId) {
return this.measurementSnapInputCache;
}
const meshes = this.getMeasurementTargetMeshes(target);
if (meshes.length === 0) return null;
@ -2013,12 +2022,15 @@ export class BabylonModelPreview implements WorkbenchPreview {
if (vertices.length === 0 && edges.length === 0) return null;
const bounds = this.getMeasurementTargetBounds();
const size = bounds ? getPreviewBoundsSize(bounds) : { x: 1, y: 1, z: 1 };
return {
const input = {
vertices,
edges,
targetId,
vertexRadius: Math.max(size.x, size.y, size.z, 0.001) * 0.045,
};
this.measurementSnapInputCache = input;
this.measurementSnapInputCacheTargetId = target.uniqueId;
return input;
}
private getMeasurementTargetMeshes(target = this.measurementTargetNode): AbstractMesh[] {

View file

@ -112,6 +112,7 @@ import {
setMeasurementCanvasActive,
snapMeasurementPointToGeometry,
unscaleMeasurementPointToBase,
type MeasurementGeometrySnapInput,
type MeasurementSnapEdgeCandidate,
type MeasurementSnapVertexCandidate,
type MeasurementReading,
@ -299,6 +300,8 @@ export class ThreeModelPreview implements WorkbenchPreview {
private measurementMarkerPoints: Vector3[] = [];
private measurementTargetObject: Object3D | null = null;
private measurementTargetHelper: BoxHelper | null = null;
private measurementSnapInputCache: MeasurementGeometrySnapInput | null = null;
private measurementSnapInputCacheTarget: Object3D | null = null;
private measurementSnapKind: MeasurementSnapKind | null = null;
private readonly measurementObservers = new Set<() => void>();
private pendingPoint: Vector3 | null = null;
@ -938,6 +941,7 @@ export class ThreeModelPreview implements WorkbenchPreview {
setMeasurementScale(scale: MeasurementScale): void {
this.measurementScale = sanitizeMeasurementScale(scale);
this.invalidateMeasurementSnapInputCache();
this.applyMeasurementModelScale();
this.updateMeasurementOverlayPositions();
this.updateMeasurementLabels();
@ -1026,6 +1030,7 @@ export class ThreeModelPreview implements WorkbenchPreview {
this.measurementBaseRootScale.set(1, 1, 1);
this.measurementBaseBounds = null;
this.measurementMarkerPoints = [];
this.invalidateMeasurementSnapInputCache();
}
private applyMeasurementModelScale(): void {
@ -2607,6 +2612,7 @@ export class ThreeModelPreview implements WorkbenchPreview {
private setMeasurementTargetObject(object: Object3D | null, notify = true): void {
this.clearMeasurementTargetHelper(false);
this.invalidateMeasurementSnapInputCache();
const target = object && this.isObjectInLoadedRoot(object) ? object : null;
this.measurementTargetObject = target;
this.setMeasurementSnapKind(null, false);
@ -2633,6 +2639,11 @@ export class ThreeModelPreview implements WorkbenchPreview {
this.measurementTargetHelper.update();
}
private invalidateMeasurementSnapInputCache(): void {
this.measurementSnapInputCache = null;
this.measurementSnapInputCacheTarget = null;
}
private setMeasurementSnapKind(kind: MeasurementSnapKind | null, notify = true): void {
if (this.measurementSnapKind === kind) return;
this.measurementSnapKind = kind;
@ -2692,14 +2703,12 @@ export class ThreeModelPreview implements WorkbenchPreview {
return new Vector3(snapped.point.x, snapped.point.y, snapped.point.z);
}
private createMeasurementGeometrySnapInput(): {
vertices: MeasurementSnapVertexCandidate[];
edges: MeasurementSnapEdgeCandidate[];
targetId?: string;
vertexRadius: number;
} | null {
private createMeasurementGeometrySnapInput(): MeasurementGeometrySnapInput | null {
const target = this.measurementTargetObject;
if (!target || !this.isObjectInLoadedRoot(target)) return null;
if (this.measurementSnapInputCache && this.measurementSnapInputCacheTarget === target) {
return this.measurementSnapInputCache;
}
const renderables = this.getMeasurementTargetRenderables();
if (renderables.length === 0) return null;
const vertices: MeasurementSnapVertexCandidate[] = [];
@ -2726,12 +2735,15 @@ export class ThreeModelPreview implements WorkbenchPreview {
if (vertices.length === 0 && edges.length === 0) return null;
const bounds = this.getMeasurementTargetBounds();
const size = bounds ? getPreviewBoundsSize(bounds) : { x: 1, y: 1, z: 1 };
return {
const input = {
vertices,
edges,
targetId,
vertexRadius: Math.max(size.x, size.y, size.z, 0.001) * 0.045,
};
this.measurementSnapInputCache = input;
this.measurementSnapInputCacheTarget = target;
return input;
}
private getMeasurementTargetRenderables(): ThreeRenderableObject[] {