The visualization layer connects MTK Web model data to an interactive Three.js scene. This guide explains how to build your own model display, choose the components for each task, and add selection, measurements, PMI, and manufacturing results. The existing applications show how these components work together, but you do not need to reuse a complete example viewer.
For your first display, read Before You Start and Load and Display a Model. Then choose only the workflows your application needs. Each example explains what it owns and which loaded data or viewport to pass in.
| I want to... | Go to |
|---|---|
| Open a model or drawing | Load a model or display a drawing sheet |
| List parts and control visibility | Show, Hide, and Inspect Parts |
| Select faces, edges, or other geometry | Select Geometry |
| Add distance or other measurement annotations | Add Measurements |
| Change the camera or background | Adjust the View |
| Apply scene changes or save an image | Refresh the Scene or Save an Image |
| Display supplied PMI or analysis | Display PMI or Explore Manufacturing Results |
| Add custom objects or rendering effects | Add Your Own Objects or Customize Three.js Rendering |
| Close a view safely | Release resources |
The visualization components are supplied under shared/src; they are not exports of the @mtk/web package. Include the relevant shared modules and dependencies in your project. The snippets either compose those components directly or shorten real example-viewer code. They use the example application's shared/* import alias; configure that alias to point to shared/src/*, or replace it with your own paths. Each snippet is an excerpt that shows the code that matters for the described workflow; unrelated UI event wiring, routing, panels, and application-specific error reporting stay out of the sample.
Follow the installation instructions. Create the viewer in the browser after its container is mounted, and give that container a nonzero size, such as width: 100%; height: 600px.
For ordinary model loading, select the main .mtkweb file and any external files it references. A LocalFileSource preserves folder-relative paths from a directory picker. A RemoteFileSource can supply server-hosted files. Manufacturing results also require analysis data and the conversion output's folder layout, as explained below.
When a snippet introduces a class, that class owns one small part of the application. Finish one asynchronous load or view switch before starting another. Call dispose() when removing the corresponding feature, after its pending operations finish.
A typical display follows this sequence:
| Your task | Components to use | Result |
|---|---|---|
| Read a model | FileSource, ModelLoader | An MTK Model, independent of the displayed scene. |
| Display model geometry and retain part identities | ModelStructureBuilder, ModelStructure | Model, element, and body nodes connected to Three.js objects. |
| Display drawing sheets | DrawingStructureBuilder, DrawingStructure | Sheet and view nodes containing converted drawing elements. |
| Render and navigate | Viewport | Canvas, scene, camera, controls, picking, and materials. |
| Select model geometry | ModelHighlightManager | Selected nodes or shapes with matching scene highlighting. |
| Create measurement annotations | MeasurementManager, model target adapters | Measurements associated with model data. |
| Display PMI | PMIStructureBuilder, PMIStructure | PMI nodes alongside the model, with saved-view data available. |
| Display manufacturing results | AnalysisSession, ManufacturingAnalysis, AnalysisItemTree | Analyses, process diagnostics, recognized features, and DFM issues. |
Start with Load and Display a Model and add the components your application needs. Ready-made viewers are integration references; links to their applications are collected at the end of this guide.
Use the model workflow for a 3D display. Add the drawing workflow when the loaded model contains sheets that you want to show in 2D.
The excerpt from BaseModelViewer owns the viewport, loader, builders, and current visualization state. Pass a FileSource to load(); for a browser file selection, create new LocalFileSource(files) from the non-null input.files. A remote source uses the same method.
ModelLoader returns model data without displaying anything. ModelStructureBuilder converts that data and returns a root node. addModelNode() puts it into the structure; attaching sceneRoot to the viewport makes it visible. The builder uses the viewport's material manager so converted objects follow the same material ownership rules.
Location: shared/src/viewers/base-model-viewer/BaseModelViewer.ts
prepare() records the loaded model and detects whether it can be shown as model geometry, drawing sheets, or both. setup() selects the first available visualization. A model without renderable geometry can produce an empty display; loading success alone does not guarantee visible content.
ModelLoader.load() rejects invalid inputs. The viewer catches that error in load(), logs and alerts the user, and resets the viewer with prepare(null) and setup(). Do not overlap calls or dispose while loading.
Before replacing a model, remove tools and scene objects referring to its old nodes. Rebuild controls from the current state's structure roots after each load.
Start with a loaded Model from Load and Display a Model. Use its drawing when your application offers a 2D view. The drawing builder is asynchronous because text and dimension conversion may need to finish before the drawing is ready. In BaseModelViewer.build(), the same state machine switches between model and drawing structures:
Location: shared/src/viewers/base-model-viewer/BaseModelViewer.ts
Populate a sheet selector from viewer.sheets; assigning viewer.currentSheet changes which sheet node is visible. The viewer hides the previous structure before adding the next one, so model and drawing roots do not overlap unless your application deliberately shows them together.
build() clears the active structure, removes its old sceneRoot, adds a model or drawing root, and attaches the rebuilt sceneRoot again. For drawings, isSheetNode() finds the sheet nodes that can be exposed in your UI. releaseUnusedResources() reclaims materials that are no longer referenced by the scene, and update() applies the viewport mode and camera direction. Rebuild sheet references after each load or visualization switch.
These workflows use an existing viewport. Add each feature independently; selection is optional for measurements, and saving an image does not refresh the scene.
The model structure connects your controls to the currently displayed data: ModelNode represents the loaded model, ModelElementNode an assembly, instance, or part, and BodyNode a body. Each carries its MTK data and a Three.js object3d. Read the current ModelStructure from the viewer state to populate a body list and change visibility:
Location: shared/src/viewers/model-structure-viewer/ModelStructureViewer.ts
Each structure node provides name, its MTK data, and its Three.js object3d. One body can contain several meshes and lines. Hiding it changes the display, not the source model. An unchecked parent also hides its children.
collectCheckStatesForDescendantsAndSelf() computes the effective tree state so descendants follow their parents. The model and drawing type guards keep the handler connected to the current visualization kind, and updateState() refreshes scene bounds after visibility changes.
Get fresh nodes after loading another model or rebuilding a view. Previous nodes belong to the old display and must not remain selected in application controls. To remove the model, clear the viewer state after disposing tools that still refer to it.
The viewport's raycaster finds Three.js intersections. SelectionHandlingViewer creates a highlight manager with the current structure when the active visualization changes:
Location: shared/src/viewers/selection-handling-viewer/SelectionHandlingViewer.ts
Read model selections through the active ModelHighlightManager, for example with getHighlightedShapes(IntersectType.PRESS). The returned map groups selected shapes by body node. Use HOVER instead of PRESS for hover feedback. Model selection modes also include NODE, EDGE, VERTEX, and POINT; modes are bit flags and can be combined.
The constructor enables hover raycast events. createState() extends the base model or drawing state with the matching highlight manager, using the current press and hover colors. setState() retains the model highlight mode and releases the previous manager during the state transition. When reading model-only selections in application code, first check that the active state has VisualizationStatus.MODEL, then call the model highlight manager.
Start with a displayed model and the body nodes you want to measure. You can choose them through your own controls or the optional geometry selection workflow.
A measurement needs model targets, not raw Three.js intersections. getModelMeasurementTarget() preserves body identity and instance transformation. Pass a shape as well when measuring a selected face or edge. MeasurementCreatingViewer creates a MeasurementManager for model visualization state and attaches it to the viewport:
Location: shared/src/viewers/measurement-creating-viewer/MeasurementCreatingViewer.ts
Use bodies from the current structure. collectHighlightedMeasurementObjects() chooses the currently selected points, shapes, or body nodes, and getModelMeasurementTarget() adapts them to measurement inputs. add() returns the measurement or null when the targets cannot produce one, for example when they are unsupported or degenerate. Subscribe to the manager's error event for UI messages, and handle rejected operations as well.
Radial measurements pass the selected notation as an option. The model state owns a MeasurementManager; the drawing state has no measurement manager.
The manager also supports bounding boxes, angles, coordinates, polyline lengths, and radial measurements. Use setVisible() to hide a measurement, delete() to remove one, or clear() to remove all. Dispose measurements before removing the source model or viewport.
Measurements are drawn in viewport.measurementsScene, so they stay separate from the model roots while still sharing the frame depth. This lets the model occlude depth-tested measurement labels and lines, keeps measurements in fitAll framing, and avoids making overlay passes infer that measurement objects should be drawn above the model.
Use the viewport controls to choose a camera direction and fit visible content. The real viewport exposes the operations directly:
Location: shared/src/features/viewport/Viewport.ts
The default viewport uses an orthographic camera. Model views use a Z-up orientation; drawing views use a top view with rotation disabled. fitAll() fits the objects, measurements when enabled, and optional shadow root. It animates by default when the viewport is attached, subject to reduced motion settings; pass { isAnimated: false } when the fitted camera is needed immediately. setCameraPosition() computes a camera distance from scene objects and measurements when enabled, then can call fitAll(). Change the viewport mode and camera together when switching between model and drawing layers so the scene and interaction stay consistent.
To change the background, assign a tagged value to viewport.scene.background and then call viewport.updateScene(). For a solid color, use ‘{ type: 'single’, color: new Color(0xededed) }, whereColor` is from Three.js. The setter also accepts a gradient background.
Location: shared/src/features/viewport/scene/Scene.ts
Choose the refresh operation according to the change:
| Change | Operation |
|---|---|
| Color, visibility, or background | updateScene() requests a new frame. |
| Add, move, replace, or remove geometry | updateState() updates bounds, camera limits, shadows, and the frame. |
updateState() does not automatically fit the camera, and its scene bounds can include hidden objects. Call fitAll() when you want to fit the content into the view.
To capture the displayed canvas, call captureSceneImage(callback); the callback receives a Blob or null. Capture after the requested frame has rendered if the image needs to include the latest changes.
The following helper is written for this guide and is not implemented in the repository.
PMI uses the loaded model. Manufacturing analysis uses a separate conversion output containing both geometry and process data; choose the matching workflow.
PMI (Product Manufacturing Information) is displayed beside model geometry. The PMIExplorerViewer example keeps one ModelStructure and one PMIStructure, then attaches both roots to the same viewport:
Location: shared/src/viewers/pmi-explorer-viewer/PMIExplorerViewer.ts
Collect only model-element nodes that actually contain PMI, then rebuild the PMI structure from those sources:
Location: shared/src/viewers/pmi-explorer-viewer/PMIExplorerViewer.ts
Use isModelElementNode while traversing the model tree to collect candidate PMI sources. PMIStructureBuilder.hasPMI() filters out nodes without PMI data, and build() converts graphical PMI into Three.js objects. Source toggles schedule a serialized structure rebuild. The rebuild clears stale PMI before adding new roots, validates the active saved view, reapplies visibility, refreshes the viewport, and emits pmiChanged. Await the rebuild before using the resulting PMI nodes. A PMI element without graphical data will not automatically gain a visible representation.
The builder collects saved views. The viewer applies saved-view visibility and camera state, clears the previous PMI selection and highlights, then emits pmiChanged. Use PMIHighlightManager for PMI-specific picking.
Manufacturing results take a different route from ordinary model loading. MTKConverter produces model folders, process-data documents, diagnostics, and optional companion models. AnalysisSession reads those files into ManufacturingAnalysis objects. Each analysis owns its active view, visual object, process parameters, analysis item trees, and any import error reported for that target.
Supply the complete conversion output: process-data documents, model directories with .mtkweb path segments, thumbnails or companion files where present, and the referenced MTKWEB files. For a folder selected with <input type="file" webkitdirectory>, LocalFileSource preserves the paths needed by the session. An ordinary standalone model file is insufficient because it does not include process data.
To integrate manufacturing results into your own viewport, use the components through the shared/features/mtk import alias in this order:
| Step | Call | Visible result | State to keep |
|---|---|---|---|
| Read output | await session.load(source) or await viewer.load(source) | Analyses and diagnostics are available. | analyses, diagnostics, and current analysis index. |
| Choose analysis | viewer.currentAnalysis = analysis | The analysis visual object is attached to the analysis root. | Current analysis, subscriptions, and diagnostics shown in the UI. |
| Choose view | analysis.activeRole = role | The active analysis view changes between origin, flat-pattern, nested, or extra models. | Current view role and unavailable-view message. |
| Choose item tree | viewer.setCurrentTreeKind(kind) | Feature-recognition or DFM tree data becomes active. | Current tree kind, tree revision, and category filter. |
| Display overlay | AnalysisItemOverlay.update(...) | Checked analysis items are drawn over the active model geometry. | Category filter and item tree revision. |
| Connect highlighting | AnalysisItemHighlightManager.setAnalysisItems(...) | Picking maps scene objects to analysis item nodes. | Current tree, analysis geometry, and filter state. |
| Refresh view | refreshSceneContent() for tree/filter changes, or the inherited scene transition for analysis/view changes | Overlay updates without rebuilding analysis geometry; full transitions update ghosting, camera mode, and viewport state. | No stale tree bindings or subscriptions. |
FeaturesAndDFMExplorerViewer is the reference for this flow. It extends BaseAnalysisViewer, which owns the viewport, an AnalysisSession, the current analysis, the root for the active analysis visual object, and the scene transition that detaches old analysis content without disposing geometry the session still owns.
Location: shared/src/viewers/mtk/features-and-dfm-explorer-viewer/FeaturesAndDFMExplorerViewer.ts
Pass a category node to setCategoryFilter() to limit the overlay to one group, or pass null to show all checked items. setCurrentTreeKind() switches between the built-in feature-recognition and DFM trees, or any custom tree kind carried by the analysis. The excerpt shows how updateOverlay() drops a category filter that no longer belongs to the current tree and keeps highlighting in sync. It uses the transformation for each model role because an item can refer to geometry from a model that is not the active view. Other class members are omitted.
The React page keeps the UI state that belongs outside the viewer: selected analysis, selected view role, current tree kind, unavailable-view message, and per-tree revision counters. It listens for analysisItemsChanged so a panel is reset only when the tree it displays was cleared or refilled by an update.
Location: react/src/pages/mtk/features-and-dfm-explorer-viewer/FeaturesAndDFMExplorerViewer.tsx
The example owns one session per viewer. Do not attach the same analysis visual object to two viewports: Three.js objects have one parent, and BaseAnalysisViewer rejects that case. Detach analysis-owned geometry when switching analyses or views; dispose only the resources the viewer itself owns, such as overlay passes and highlight managers. Let AnalysisSession.clear() or dispose() release analysis geometry when the session drops it.
Keep the active analysis root separate from overlay roots. Changing a category filter should update the overlay without detaching the active analysis visual object. The Features & DFM example ghosts the analysis visual object while an item tree is drawn over it so feature and issue geometry remains readable.
The MTK-related examples cover different analysis outputs:
Open them from the Examples page; the example cards are the user-facing entry points for these applications.
Use these sections when adding your own geometry or changing rendering. The conversion reference explains when to use a complete structure builder or a lower-level converter.
You can add ordinary Three.js meshes, lines, and points alongside the model. Use a DisposingGroup when the objects should be released as a unit, and keep owned materials explicit:
The following layer is written for this guide and is not implemented in the repository.
Create custom objects with positive dimensions in the same coordinates and units as your scene. Remove them before disposing the viewer. To replace an object, release the old object and construct a new one.
roots.objects includes the custom object in scene bounds and picking. It does not automatically become an MTK model node: if users must select it through your own tools, map raycast hits to your feature using viewport.raycaster.
The constructor creates a box mesh, owns its material through getExclusive(), adds the layer root to the viewport, and updates scene bounds. moveTo() changes the object's transform and updates scene bounds. setVisible() only changes visibility, so it only needs a frame refresh. dispose() removes the root and releases the exclusive material owned by the layer.
Removing a child from a DisposingGroup releases disposable objects or their geometry. It does not release ordinary Three.js materials or textures. The example releases its exclusive material explicitly; feature-owned textures also need explicit disposal after their last use. Avoid sharing a disposable geometry between independently removed features.
Use viewport.scene.roots.objects for custom geometry, markers, and analysis overlays. The viewport also exposes its Three.js renderer and camera for advanced customization. Prefer the viewport's camera and resize operations so its controls and rendering remain in sync.
For custom material changes, follow these ownership rules:
| Material | How to use it |
|---|---|
Shared material from getShared() | Reusable across objects; do not mutate it after acquisition. |
Exclusive material from getExclusive() | Owned by one feature and safe to edit; release it when that feature ends. |
| MTK material data | Converted by the shared material/conversion helpers; keep it distinct from Three.js materials. |
The following helper is written for this guide and is not implemented in the repository.
getExclusive() takes ownership of the material you pass; it does not clone it. Pass a new material or an owned clone. MTK's Color and Three.js Color are different classes; use the class expected by the API you call. Call viewport.releaseUnusedResources() only after the objects you still need are reachable from the viewport scenes. It releases shared materials that no scene object references; exclusive materials remain yours to release with materialManager.release().
For image-wide effects, use viewport.composer.addPass(pass). Custom passes run before the manipulator overlay and final output pass. When removing an effect, call removePass(pass), dispose the pass, and request updateScene(). Keep PMI, measurements, and other scene markers as scene objects rather than rendering effects.
The following wrapper is written for this guide and is not implemented in the repository.
ModelStructureBuilder walks model elements, carries inherited materials, and applies instance transformations. It uses ModelDataConverter to turn bodies into meshes and lines. The resulting nodes preserve the connection between MTK objects and their Three.js representations.
Use the builder for a complete model with identities, transforms, and selection. Use ModelDataConverter.convertBody() or convertPart() for a smaller display when you already manage placement, material inheritance, and ownership yourself. convertTransformation() converts MTK transformations to Three.js matrices.
The following composition is written for this guide and is not implemented in the repository. The ModelDataConverter and convertTransformation helpers are defined in shared/src/common/helpers/ThreeHelper.ts.
B-Rep display uses triangulated data. Converted objects retain metadata that connects picked primitives to model shapes. Custom Three.js meshes do not gain those connections automatically; provide your own mapping when a custom object represents an application entity.
Drawing and PMI builders follow the same pattern with their own converters. Put a structure's sceneRoot under viewport.scene.roots.objects. Helpers, lights, shadows, and manipulators have separate roots/scenes, allowing you to manage application geometry without replacing the entire rendering setup.
Keep MTK data as the authoritative model. Display colors and visibility belong to the Three.js representation and may be lost on rebuild; store user choices in your application if they must survive reloading.
| Resource | Release rule |
|---|---|
| Custom features, listeners, and effects | Dispose them before disposing their viewer. |
| Viewport DOM attachment | Detach the viewport from its container before disposing it. |
| Structure nodes and selection managers | Reacquire them after loading or switching views; old references belong to removed objects. |
| Shared materials | Call releaseUnusedResources() only when no off-scene content must be shown later. |
| Layer-owned structures, geometry, and materials | Give each layer one owner; share a viewport only when layers must appear together. |
| Viewport | Dispose it after the last feature using it has finished. |
shared/... are example application helpers, not public @mtk/web package exports. Copy or adapt them as application code when needed.The applications show UI and lifecycle integration around the components above:
See Examples for installation and running the applications.