Cap measurement corner snap radius

This commit is contained in:
flash555588 2026-07-06 21:08:49 +08:00
parent daadb83c23
commit 30c8037574
6 changed files with 625 additions and 586 deletions

View file

@ -10,6 +10,7 @@
- UI: require the locked measurement target to be the frontmost normal pick before snapping, preventing hidden target geometry behind another part from receiving ruler endpoints.
- UI: clear stale measurement snap status after cancelling a pending endpoint so the selected-object ruler returns to a neutral ready state.
- UI: keep the measurement strip in Free pick status while Alt/Option previewing the backup free ruler path, even when the pointer is over empty canvas space.
- UI: cap selected-object corner snapping by target edge scale so large grouped selections do not over-prioritize distant vertices when the pointer is closer to an edge.
- Performance: cache selected-target measurement snap candidates while measuring so hover previews do not repeatedly rebuild vertex and edge lists for the same object.
- Performance: validate selected-target measurement snap caches against target mesh signatures so transformed or updated geometry rebuilds snap candidates instead of reusing stale vertex and edge lists.
- UI: restyle distance measurements as orthographic drawing-style dimension callouts with extension lines, arrowheads, and compact drafting labels instead of thick freehand ruler lines.

1168
main.js

File diff suppressed because one or more lines are too long

View file

@ -108,6 +108,7 @@ import {
createMeasurementReading as buildMeasurementReading,
createMeasurementState,
createMeasurementTrianglesFromIndices,
createMeasurementVertexSnapRadius,
drawMeasurementLabelCanvas,
MEASUREMENT_LABEL_CANVAS,
normalizeMeasurementUnit,
@ -2039,7 +2040,7 @@ export class BabylonModelPreview implements WorkbenchPreview {
vertices,
edges,
targetId,
vertexRadius: Math.max(size.x, size.y, size.z, 0.001) * 0.045,
vertexRadius: createMeasurementVertexSnapRadius(size, edges),
};
this.measurementSnapInputCache = input;
this.measurementSnapInputCacheTargetId = target.uniqueId;

View file

@ -9,6 +9,7 @@ import {
createMeasurementReading,
createMeasurementState,
createMeasurementTrianglesFromIndices,
createMeasurementVertexSnapRadius,
createReferenceMeasurementScale,
drawMeasurementLabelCanvas,
formatMeasurementNumber,
@ -176,6 +177,18 @@ describe("measurement helpers", () => {
expect(snapped).toBeNull();
});
it("caps the vertex snap radius by selected-target edge scale", () => {
expect(createMeasurementVertexSnapRadius(
{ x: 10, y: 1, z: 1 },
[{ start: { x: 0, y: 0, z: 0 }, end: { x: 1, y: 0, z: 0 } }],
)).toBeCloseTo(0.1);
expect(createMeasurementVertexSnapRadius(
{ x: 1, y: 1, z: 1 },
[{ start: { x: 0, y: 0, z: 0 }, end: { x: 1, y: 0, z: 0 } }],
)).toBeCloseTo(0.045);
expect(createMeasurementVertexSnapRadius({ x: 10, y: 1, z: 1 }, [])).toBeCloseTo(0.45);
});
it("creates geometry snap edges from indexed and non-indexed triangles without face diagonals", () => {
const squareVertices = [
{ x: 0, y: 0, z: 0 },

View file

@ -34,6 +34,8 @@ const UNIT_FACTORS_TO_METERS: Record<MeasurementUnit, number> = {
const MIN_MEASUREMENT_SIZE = 1e-9;
const MEASUREMENT_EDGE_KEY_SCALE = 1_000_000;
const MEASUREMENT_COPLANAR_EDGE_DOT = 0.9995;
const MEASUREMENT_VERTEX_RADIUS_SPAN_FACTOR = 0.045;
const MEASUREMENT_VERTEX_RADIUS_EDGE_FACTOR = 0.1;
export interface MeasurementSnapVertexCandidate {
point: PreviewWorldPoint;
@ -211,6 +213,27 @@ export function snapMeasurementPointToGeometry(
};
}
export function createMeasurementVertexSnapRadius(
boundsSize: PreviewWorldPoint,
edges: readonly MeasurementSnapEdgeCandidate[],
): number {
const span = Math.max(boundsSize.x, boundsSize.y, boundsSize.z, 0.001);
const spanRadius = span * MEASUREMENT_VERTEX_RADIUS_SPAN_FACTOR;
const edgeLengths = edges
.map((edge) => distanceMeasurementPoints(edge.start, edge.end))
.filter((length) => Number.isFinite(length) && length > MIN_MEASUREMENT_SIZE)
.sort((left, right) => left - right);
if (edgeLengths.length === 0) {
return spanRadius;
}
const referenceEdgeLength = edgeLengths[Math.floor(edgeLengths.length / 2)];
return Math.max(
Math.min(spanRadius, referenceEdgeLength * MEASUREMENT_VERTEX_RADIUS_EDGE_FACTOR),
MIN_MEASUREMENT_SIZE,
);
}
export function createMeasurementGeometryEdgesFromTriangles(
vertices: readonly PreviewWorldPoint[],
triangles: readonly [number, number, number][],

View file

@ -104,6 +104,7 @@ import {
createMeasurementReading as buildMeasurementReading,
createMeasurementState,
createMeasurementTrianglesFromIndices,
createMeasurementVertexSnapRadius,
drawMeasurementLabelCanvas,
MEASUREMENT_LABEL_CANVAS,
normalizeMeasurementUnit,
@ -2752,7 +2753,7 @@ export class ThreeModelPreview implements WorkbenchPreview {
vertices,
edges,
targetId,
vertexRadius: Math.max(size.x, size.y, size.z, 0.001) * 0.045,
vertexRadius: createMeasurementVertexSnapRadius(size, edges),
};
this.measurementSnapInputCache = input;
this.measurementSnapInputCacheTarget = target;