Hide menu
Loading...
Searching...
No Matches
Measurement Creating Viewer Example

The Measurement Creating Viewer example demonstrates how to add interactive measurement creation capabilities to a 3D model viewer. This example is based on the Base Model Viewer and Selection Handling Viewer functionality, adding tools for creating bounding box, distance, and radius measurements directly in the 3D scene based on user selection.

User Interface Components

The Measurement Creating Viewer's user interface is built using React and Ant Design components. The UI architecture consists of several key components that work together to provide an interactive measurement experience:

  • Main Layout Component: Orchestrates the overall interface, model upload, selection and measurement mode switching, and notification display.
  • Selection Mode Selector: Dropdown for choosing between node and shape (face/edge) selection modes.
  • Measurement Mode Selector: Dropdown for choosing the measurement type (Bounding Box, Distance, Radius) with contextual tooltips.
  • Notification System: Displays contextual information about created measurements.
  • Model Upload Controls: Allows users to upload 3D models for viewing and measurement.
  • 3D Viewport: Renders the 3D scene and handles user interactions.

MeasurementCreatingViewer.tsx

Location: react/src/pages/measurement-creating-viewer/MeasurementCreatingViewer.tsx

This is the main React component for the Measurement Creating Viewer. It manages the viewer instance, handles model uploads, and provides UI controls for switching selection and measurement modes. The component listens for measurement events and uses a notification system to display details about the created measurement, including bounding box ranges, distances, or radii.

export const MeasurementCreatingViewer = () => {
const viewerRef = useRef<MeasurementCreatingViewerRef>(new MeasurementCreatingViewerRef());
const notification = useNotification();
const onUpload = useAsyncLock(async (files: File[]) => {
await viewerRef.current.loadAndDisplayModel(files);
});
const onSelectionModeChanged = (mode: number) => {
viewerRef.current.selectionManager.sceneSelectionMode = mode;
setMeasurementsTooltipText(getMeasurementsTooltipText());
};
const onMeasurementModeChanged = (mode: MeasurementMode) => {
viewerRef.current.measurementManager.measurementMode = mode;
setMeasurementsTooltipText(getMeasurementsTooltipText());
};
// ...
useEffect(() => {
viewerRef.current.measurementManager.addEventListener('measurementAdded', onMeasurementCreated);
viewerRef.current.measurementManager.addEventListener('allMeasurementsRemoved', onAllMeasurementsRemoved);
viewerRef.current.measurementManager.addEventListener(
'incorrectRadiusSource', showIncorrectMeasurementSourceWarning,
);
return () => {
viewerRef.current.selectionManager.removeEventListener('measurementAdded', onMeasurementCreated);
viewerRef.current.selectionManager.removeEventListener('allMeasurementsRemoved', onAllMeasurementsRemoved);
viewerRef.current.selectionManager.removeEventListener(
'incorrectRadiusSource', showIncorrectMeasurementSourceWarning,
);
};
}, [viewerRef]);
return (
<div className="viewer-page">
{notification.contextHolder}
<Flex vertical gap="small" className="overlay-controls-container">
<ModelUploadButtonGroup onUpload={onUpload} />
<LabeledSelect
title="Selection mode"
selectPlaceholder="Choose selection mode"
selectDefaultValue={viewerRef.current.selectionManager.sceneSelectionMode}
selectOptions={selectionModeOptions}
onSelectValueChanged={onSelectionModeChanged}
/>
<LabeledSelect
title="Measurement mode"
selectPlaceholder="Choose measurement mode"
selectDefaultValue={MeasurementMode.BOUNDING_BOX}
selectOptions={measurementModeOptions}
onSelectValueChanged={onMeasurementModeChanged}
tooltipText={measurementsTooltipText}
/>
</Flex>
<Viewport viewportRef={viewerRef.current.viewport} isShowGrid={false} />
</div>
);
};

Note: Some UI components used in this example, such as ModelUploadButtonGroup, Viewport, useAsyncLock, useNotification, and LabeledSelect, are shared with the Base Model Viewer and Selection Handling Viewer and have been documented in the Base Model Viewer documentation and Selection Handling Viewer documentation.

Core Logic Components

The core logic layer of the Measurement Creating Viewer provides advanced measurement management for both the model structure and the 3D viewport. This layer extends the selection handling functionality with measurement creation, visualization, and event-driven updates. The architecture is designed around several key component categories:

  • Viewer Management: Central orchestration of model loading, selection management, and measurement integration.
  • Selection Management: Handles selection logic for nodes, faces, and edges, including multi-selection and keyboard modifiers.
  • Measurement Management: Handles creation, visualization, and removal of bounding box, distance, and radius measurements.

MeasurementCreatingViewer.ts

Location: shared/src/viewers/measurement-creating-viewer/MeasurementCreatingViewer.ts

Extends the SelectionHandlingViewer and adds a MeasurementManager for advanced measurement handling. Manages the lifecycle of the measurement manager and integrates it with the viewer and selection manager.

export class MeasurementCreatingViewer extends SelectionHandlingViewer {
readonly measurementManager = new MeasurementManager();
// ...
}

MeasurementManager.ts

Location: shared/src/features/measurements/MeasurementManager.ts

Manages the creation and visualization of measurements. Supports three measurement modes: Bounding Box, Distance, and Radius. Handles event dispatching for measurement creation and removal, and manages the root 3D group for all measurement objects.

export class MeasurementManager extends EventTarget {
readonly root = new Group();
measurementMode = MeasurementMode.BOUNDING_BOX;
modelBBox = new Box();
add(measurementObjects: MeasurementObject | MeasurementObject[]) {
// ...
}
removeAll() {
this.root.clear();
this.dispatchEvent(new Event('allMeasurementsRemoved'));
}
// ...
}

Measurement.ts

Location: shared/src/features/measurements/Measurement.ts

Abstract base class for all measurement types. This class defines the interface for creating 3D measurement objects and manages common properties such as font size and depth test settings. For rendering text annotations (such as measurement values), the implementation uses the troika-three-text library, which enables high-quality, efficient 3D text rendering in the scene.

export abstract class Measurement {
readonly object3d = new Group();
protected fontSize: number;
protected isDepthTestEnabled: boolean;
constructor(protected sceneBbox: Box) {
this.isDepthTestEnabled = false;
this.fontSize = this.sceneBbox.minCorner.distance(this.sceneBbox.maxCorner) / 25;
}
abstract createObject3D(isDepthTestEnabled?: boolean, fontSize?: number): Promise<void>;
}

BoundingBoxMeasurement.ts

Location: shared/src/features/measurements/BoundingBoxMeasurement.ts

Implements bounding box measurement visualization. Renders a 3D bounding box and adds distance measurements for each axis.

export class BoundingBoxMeasurement extends Measurement {
constructor(public readonly bbox: Box, sceneBBox: Box) {
super(sceneBBox);
}
override async createObject3D(isDepthTestEnabled?: boolean, fontSize?: number) {
// ...
}
// ...
}

DistanceMeasurement.ts

Location: shared/src/features/measurements/DistanceMeasurement.ts

Implements distance measurement visualization. Renders a 3D distance indicator between two points, including text, lines, and arrows.

export class DistanceMeasurement extends Measurement {
constructor(public readonly distance: ValueWithAnchorPoints, sceneBBox: Box) {
super(sceneBBox);
// ...
}
override async createObject3D(isDepthTestEnabled?: boolean, fontSize?: number) {
// ...
}
// ...
}

RadiusMeasurement.ts

Location: shared/src/features/measurements/RadiusMeasurement.ts

Implements radius measurement visualization. Collects all possible radii for a given shape (edge or face) and renders annotations for each.

export class RadiusMeasurement extends Measurement {
constructor(private shape: Shape, private shapeTransformation: Transformation | null, sceneBBox: Box) {
super(sceneBBox);
this.collectRadii();
}
override async createObject3D(isDepthTestEnabled?: boolean, fontSize?: number) {
// ...
}
// ...
}

Note: Some core components used in this example, such as SelectionHandlingViewer, ModelSelectionManager, and basic viewport functionality, are shared with the Base Model Viewer and Selection Handling Viewer and have been documented in the Base Model Viewer documentation and Selection Handling Viewer documentation.

Viewer

Measurement Creating Viewer