diff --git a/Documentation/content/docs/gallery/CleanPolyData.jpg b/Documentation/content/docs/gallery/CleanPolyData.jpg
new file mode 100644
index 00000000000..dc6d1030d3a
Binary files /dev/null and b/Documentation/content/docs/gallery/CleanPolyData.jpg differ
diff --git a/Documentation/content/examples/index.md b/Documentation/content/examples/index.md
index 15badfea191..764f259be13 100644
--- a/Documentation/content/examples/index.md
+++ b/Documentation/content/examples/index.md
@@ -109,6 +109,7 @@ This will allow you to see the some live code running in your browser. Just pick
[![PolyDataNormals Example][PolyDataNormals]](./PolyDataNormals.html "PolyDataNormals")
[![ThresholdPoints Example][ThresholdPoints]](./ThresholdPoints.html "Cut/Treshold points with point data criteria")
[![ShrinkPolyData Example][ShrinkPolyData]](./ShrinkPolyData.html "ShrinkPolyData")
+[![CleanPolyData Example][CleanPolyData]](./CleanPolyData.html "CleanPolyData")
@@ -129,6 +130,7 @@ This will allow you to see the some live code running in your browser. Just pick
[PolyDataNormals]: ../docs/gallery/PolyDataNormals.jpg
[ThresholdPoints]: ../docs/gallery/ThresholdPoints.jpg
[ShrinkPolyData]: ../docs/gallery/ShrinkPolyData.jpg
+[CleanPolyData]: ../docs/gallery/CleanPolyData.jpg
# Sources
diff --git a/Sources/Common/Core/CellArray/index.d.ts b/Sources/Common/Core/CellArray/index.d.ts
index 17041c3ab90..f519b9f37e8 100755
--- a/Sources/Common/Core/CellArray/index.d.ts
+++ b/Sources/Common/Core/CellArray/index.d.ts
@@ -46,6 +46,11 @@ export interface vtkCellArray extends vtkDataArray {
* @returns {Number} Idx of where the cell was inserted
*/
insertNextCell(cellPointIds: number[]): number;
+
+ /**
+ * Get the maximum cell size.
+ */
+ getMaxCellSize(): number;
}
/**
diff --git a/Sources/Common/Core/CellArray/index.js b/Sources/Common/Core/CellArray/index.js
index f86178bc006..ce1ce087f24 100644
--- a/Sources/Common/Core/CellArray/index.js
+++ b/Sources/Common/Core/CellArray/index.js
@@ -117,6 +117,9 @@ function vtkCellArray(publicAPI, model) {
}
return cellId;
};
+
+ publicAPI.getMaxCellSize = () =>
+ publicAPI.getCellSizes().reduce((a, b) => Math.max(a, b), 0);
}
// ----------------------------------------------------------------------------
diff --git a/Sources/Common/DataModel/BoundingBox/index.js b/Sources/Common/DataModel/BoundingBox/index.js
index 485da08e5ae..652067e95fa 100644
--- a/Sources/Common/DataModel/BoundingBox/index.js
+++ b/Sources/Common/DataModel/BoundingBox/index.js
@@ -993,6 +993,7 @@ export const STATIC = {
getLengths,
getMaxLength,
getDiagonalLength,
+ getDiagonalLength2,
getMinPoint,
getMaxPoint,
getXRange,
diff --git a/Sources/Common/DataModel/DataSet/index.js b/Sources/Common/DataModel/DataSet/index.js
index 16a629b54dd..9418c832cac 100644
--- a/Sources/Common/DataModel/DataSet/index.js
+++ b/Sources/Common/DataModel/DataSet/index.js
@@ -1,39 +1,10 @@
import macro from 'vtk.js/Sources/macros';
import vtk from 'vtk.js/Sources/vtk';
+import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';
import vtkDataSetAttributes from 'vtk.js/Sources/Common/DataModel/DataSetAttributes';
+import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import Constants from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
-// import vtkBoundingBox from '../BoundingBox';
-// import * as vtkMath from '../../Core/Math';
-//
-// function getBounds(dataset) {
-// if (dataset.bounds) {
-// return dataset.bounds;
-// }
-// if (dataset.type && dataset[dataset.type]) {
-// const ds = dataset[dataset.type];
-// if (ds.bounds) {
-// return ds.bounds;
-// }
-// if (ds.Points && ds.Points.bounds) {
-// return ds.Points.bounds;
-// }
-
-// if (ds.Points && ds.Points.values) {
-// const array = ds.Points.values;
-// const bbox = [...vtkBoundingBox.INIT_BOUNDS];
-// const size = array.length;
-// const delta = ds.Points.numberOfComponents ? ds.Points.numberOfComponents : 3;
-// for (let idx = 0; idx < size; idx += delta) {
-// vtkBoundingBox.addPoint(bbox, array[idx * delta], array[(idx * delta) + 1], array[(idx * delta) + 2]);
-// }
-// ds.Points.bounds = bbox;
-// return ds.Points.bounds;
-// }
-// }
-// return vtkMath.createUninitializedBounds();
-// }
-
// ----------------------------------------------------------------------------
// Global methods
// ----------------------------------------------------------------------------
@@ -57,6 +28,73 @@ function vtkDataSet(publicAPI, model) {
}
});
+ //------------------------------------------------------------------------------
+ // Compute the data bounding box from data points.
+ publicAPI.computeBounds = () => {
+ if (
+ (model.modifiedTime &&
+ model.computeTime &&
+ model.modifiedTime > model.computeTime) ||
+ !model.computeTime
+ ) {
+ const points = publicAPI.getPoints();
+ if (points?.getNumberOfPoints()) {
+ // Compute bounds from points
+ vtkBoundingBox.setBounds(model.bounds, points.getBoundsByReference());
+ } else {
+ model.bounds = vtkMath.createUninitializedBounds();
+ }
+ // Update computeTime
+ model.computeTime = macro.getCurrentGlobalMTime();
+ }
+ };
+
+ /**
+ * Returns the squared length of the diagonal of the bounding box
+ */
+ publicAPI.getLength2 = () => {
+ const bounds = publicAPI.getBoundsByReference();
+ if (!bounds || bounds.length !== 6) return 0;
+ return vtkBoundingBox.getDiagonalLength2(bounds);
+ };
+
+ /**
+ * Returns the length of the diagonal of the bounding box
+ */
+ publicAPI.getLength = () => Math.sqrt(publicAPI.getLength2());
+
+ /**
+ * Returns the center of the bounding box as [x, y, z]
+ */
+ publicAPI.getCenter = () => {
+ const bounds = publicAPI.getBoundsByReference();
+ if (!bounds || bounds.length !== 6) return [0, 0, 0];
+ return vtkBoundingBox.getCenter(bounds);
+ };
+
+ /**
+ * Get the bounding box of a cell with the given cellId
+ * @param {Number} cellId - The id of the cell
+ * @returns {Number[]} - The bounds as [xmin, xmax, ymin, ymax, zmin, zmax]
+ */
+ publicAPI.getCellBounds = (cellId) => {
+ const cell = publicAPI.getCell(cellId);
+ if (cell) {
+ return cell.getBounds();
+ }
+ return vtkMath.createUninitializedBounds();
+ };
+
+ publicAPI.getBounds = macro.chain(
+ () => publicAPI.computeBounds,
+ publicAPI.getBounds
+ );
+
+ publicAPI.getBoundsByReference = macro.chain(
+ () => publicAPI.computeBounds,
+ publicAPI.getBoundsByReference
+ );
+
const superShallowCopy = publicAPI.shallowCopy;
publicAPI.shallowCopy = (other, debug = false) => {
superShallowCopy(other, debug);
@@ -98,7 +136,7 @@ export function extend(publicAPI, model, initialValues = {}) {
// Object methods
macro.obj(publicAPI, model);
macro.setGet(publicAPI, model, DATASET_FIELDS);
-
+ macro.getArray(publicAPI, model, ['bounds'], 6);
// Object specific methods
vtkDataSet(publicAPI, model);
}
diff --git a/Sources/Common/DataModel/DataSetAttributes/FieldData.js b/Sources/Common/DataModel/DataSetAttributes/FieldData.js
index 350e4985172..8039843dacc 100644
--- a/Sources/Common/DataModel/DataSetAttributes/FieldData.js
+++ b/Sources/Common/DataModel/DataSetAttributes/FieldData.js
@@ -32,7 +32,7 @@ function vtkFieldData(publicAPI, model) {
publicAPI.copyStructure = (other) => {
publicAPI.initializeFields();
model.copyFieldFlags = other.getCopyFieldFlags().map((x) => x); // Deep-copy
- model.arrays = other.arrays().map((x) => ({ array: x })); // Deep-copy
+ model.arrays = other.getArrays().map((x) => ({ data: x })); // Deep-copy
// TODO: Copy array information objects (once we support information objects)
};
diff --git a/Sources/Common/DataModel/PolyData/index.d.ts b/Sources/Common/DataModel/PolyData/index.d.ts
index e11be352691..d364a3f6f15 100755
--- a/Sources/Common/DataModel/PolyData/index.d.ts
+++ b/Sources/Common/DataModel/PolyData/index.d.ts
@@ -66,9 +66,15 @@ export interface vtkPolyData extends vtkPointSet {
getLines(): vtkCellArray;
/**
- *
+ * Get the links between points and cells.
*/
- getLinks(): any;
+ getLinks(): any; // vtkCellLinks
+
+ /**
+ * Get the maximum cell size.
+ * Returns 0 if there is no cell.
+ */
+ getMaxCellSize(): number;
/**
* Determine the number of cells composing the polydata.
@@ -104,7 +110,7 @@ export interface vtkPolyData extends vtkPointSet {
* Topological inquiry to get cells using point.
* @param ptId
*/
- getPointCells(ptId: any): void;
+ getPointCells(ptId: number): void;
/**
* Get the cell array defining polys.
diff --git a/Sources/Common/DataModel/PolyData/index.js b/Sources/Common/DataModel/PolyData/index.js
index b81cf62cc66..be145e5c60e 100644
--- a/Sources/Common/DataModel/PolyData/index.js
+++ b/Sources/Common/DataModel/PolyData/index.js
@@ -5,8 +5,11 @@ import vtkCellLinks from 'vtk.js/Sources/Common/DataModel/CellLinks';
import vtkCellTypes from 'vtk.js/Sources/Common/DataModel/CellTypes';
import vtkLine from 'vtk.js/Sources/Common/DataModel/Line';
import vtkPointSet from 'vtk.js/Sources/Common/DataModel/PointSet';
+import vtkPolyLine from 'vtk.js/Sources/Common/DataModel/PolyLine';
+import vtkPolygon from 'vtk.js/Sources/Common/DataModel/Polygon';
+import vtkQuad from 'vtk.js/Sources/Common/DataModel/Quad';
import vtkTriangle from 'vtk.js/Sources/Common/DataModel/Triangle';
-
+import vtkTriangleStrip from 'vtk.js/Sources/Common/DataModel/TriangleStrip';
import { CellType } from 'vtk.js/Sources/Common/DataModel/CellTypes/Constants';
import { POLYDATA_FIELDS } from 'vtk.js/Sources/Common/DataModel/PolyData/Constants';
@@ -14,8 +17,12 @@ const { vtkWarningMacro } = macro;
export const CELL_FACTORY = {
[CellType.VTK_LINE]: vtkLine,
+ [CellType.VTK_QUAD]: vtkQuad,
[CellType.VTK_POLY_LINE]: vtkLine,
[CellType.VTK_TRIANGLE]: vtkTriangle,
+ [CellType.VTK_TRIANGLE_STRIP]: vtkTriangleStrip,
+ [CellType.VTK_POLY_LINE]: vtkPolyLine,
+ [CellType.VTK_POLYGON]: vtkPolygon,
};
// ----------------------------------------------------------------------------
@@ -242,6 +249,12 @@ function vtkPolyData(publicAPI, model) {
cell.initialize(publicAPI.getPoints(), cellInfo.cellPointIds);
return cell;
};
+
+ publicAPI.getMaxCellSize = () =>
+ POLYDATA_FIELDS.reduce(
+ (max, type) => Math.max(max, model[type]?.getMaxCellSize?.() ?? 0),
+ 0
+ );
}
// ----------------------------------------------------------------------------
diff --git a/Sources/Filters/Core/CleanPolyData/example/controlPanel.html b/Sources/Filters/Core/CleanPolyData/example/controlPanel.html
new file mode 100644
index 00000000000..c0b4627394b
--- /dev/null
+++ b/Sources/Filters/Core/CleanPolyData/example/controlPanel.html
@@ -0,0 +1,22 @@
+
+
+ | Before(Left Cube) |
+
+
+ | Points : 0 |
+ Cells : 0 |
+ Lines : 0 |
+ Polys : 0 |
+ Strips : 0 |
+
+
+ | After(Right Cube) |
+
+
+ | Points : 0 |
+ Cells : 0 |
+ Lines : 0 |
+ Polys : 0 |
+ Strips : 0 |
+
+
diff --git a/Sources/Filters/Core/CleanPolyData/example/index.js b/Sources/Filters/Core/CleanPolyData/example/index.js
new file mode 100644
index 00000000000..f5925d653c4
--- /dev/null
+++ b/Sources/Filters/Core/CleanPolyData/example/index.js
@@ -0,0 +1,116 @@
+import '@kitware/vtk.js/favicon';
+
+// Load the rendering pieces we want to use (for both WebGL and WebGPU)
+import '@kitware/vtk.js/Rendering/Profiles/Geometry';
+import '@kitware/vtk.js/Rendering/Profiles/Glyph';
+
+import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper';
+
+import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
+import vtkCleanPolyData from '@kitware/vtk.js/Filters/Core/CleanPolyData';
+import vtkCubeSource from '@kitware/vtk.js/Filters/Sources/CubeSource';
+import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
+import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';
+import vtkGlyph3DMapper from '@kitware/vtk.js/Rendering/Core/Glyph3DMapper';
+import vtkArrowSource from '@kitware/vtk.js/Filters/Sources/ArrowSource';
+
+import controlPanel from './controlPanel.html';
+
+// ----------------------------------------------------------------------------
+// Standard rendering code setup
+// ----------------------------------------------------------------------------
+
+const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance();
+const renderer = fullScreenRenderer.getRenderer();
+const renderWindow = fullScreenRenderer.getRenderWindow();
+
+// -----------------------------------------------------------
+// UI control handling
+// -----------------------------------------------------------
+
+fullScreenRenderer.addController(controlPanel);
+
+// ----------------------------------------------------------------------------
+// Example code
+// ----------------------------------------------------------------------------
+
+const cubeSource1 = vtkCubeSource.newInstance();
+const cubeActor1 = vtkActor.newInstance();
+const cubeMapper1 = vtkMapper.newInstance();
+cubeActor1.setMapper(cubeMapper1);
+cubeMapper1.setInputConnection(cubeSource1.getOutputPort());
+renderer.addActor(cubeActor1);
+
+const arrowSource1 = vtkArrowSource.newInstance();
+const glyphMapper1 = vtkGlyph3DMapper.newInstance();
+glyphMapper1.setInputConnection(cubeSource1.getOutputPort());
+glyphMapper1.setSourceConnection(arrowSource1.getOutputPort());
+glyphMapper1.setOrientationModeToDirection();
+glyphMapper1.setOrientationArray('Normals');
+glyphMapper1.setScaleModeToScaleByMagnitude();
+glyphMapper1.setScaleArray('Normals');
+glyphMapper1.setScaleFactor(0.1);
+
+const glyphActor1 = vtkActor.newInstance();
+glyphActor1.setMapper(glyphMapper1);
+renderer.addActor(glyphActor1);
+
+const cubeSource2 = vtkCubeSource.newInstance();
+const cubeActor2 = vtkActor.newInstance();
+const cubeMapper2 = vtkMapper.newInstance();
+
+cubeActor2.setMapper(cubeMapper2);
+cubeMapper2.setInputConnection(cubeSource2.getOutputPort());
+cubeActor2.setPosition(2, 0, 0);
+renderer.addActor(cubeActor2);
+
+const cleanPolyData = vtkCleanPolyData.newInstance();
+cleanPolyData.setInputConnection(cubeSource2.getOutputPort());
+
+const arrowSource2 = vtkArrowSource.newInstance();
+const glyphMapper2 = vtkGlyph3DMapper.newInstance();
+glyphMapper2.setInputConnection(cleanPolyData.getOutputPort());
+glyphMapper2.setSourceConnection(arrowSource2.getOutputPort());
+glyphMapper2.setOrientationModeToDirection();
+glyphMapper2.setOrientationArray('Normals');
+glyphMapper2.setScaleModeToScaleByMagnitude();
+glyphMapper2.setScaleArray('Normals');
+glyphMapper2.setScaleFactor(0.1);
+
+const glyphActor2 = vtkActor.newInstance();
+glyphActor2.setMapper(glyphMapper2);
+glyphActor2.setPosition(2, 0, 0);
+renderer.addActor(glyphActor2);
+
+// --- Render ---
+renderer.resetCamera();
+renderWindow.render();
+
+// -----------------------------------------------------------
+// Display initial and final polydata stats
+// -----------------------------------------------------------
+const initialPolyData = cubeSource1.getOutputData();
+const initialPoints = initialPolyData.getNumberOfPoints();
+const initialCells = initialPolyData.getNumberOfCells();
+const initialLines = initialPolyData.getLines().getNumberOfCells();
+const initialPolys = initialPolyData.getPolys().getNumberOfCells();
+const initialStrips = initialPolyData.getStrips().getNumberOfCells();
+
+document.querySelector('.initial-points').textContent = initialPoints;
+document.querySelector('.initial-cells').textContent = initialCells;
+document.querySelector('.initial-lines').textContent = initialLines;
+document.querySelector('.initial-polys').textContent = initialPolys;
+document.querySelector('.initial-strips').textContent = initialStrips;
+
+const finalPolyData = cleanPolyData.getOutputData();
+const finalPoints = finalPolyData.getNumberOfPoints();
+const finalCells = finalPolyData.getNumberOfCells();
+const finalLines = finalPolyData.getLines().getNumberOfCells();
+const finalPolys = finalPolyData.getPolys().getNumberOfCells();
+const finalStrips = finalPolyData.getStrips().getNumberOfCells();
+
+document.querySelector('.final-points').textContent = finalPoints;
+document.querySelector('.final-cells').textContent = finalCells;
+document.querySelector('.final-lines').textContent = finalLines;
+document.querySelector('.final-polys').textContent = finalPolys;
+document.querySelector('.final-strips').textContent = finalStrips;
diff --git a/Sources/Filters/Core/CleanPolyData/index.d.ts b/Sources/Filters/Core/CleanPolyData/index.d.ts
new file mode 100644
index 00000000000..ec40d3c3691
--- /dev/null
+++ b/Sources/Filters/Core/CleanPolyData/index.d.ts
@@ -0,0 +1,208 @@
+import { DesiredOutputPrecision } from '../../../Common/DataModel/DataSetAttributes';
+import { vtkAlgorithm, vtkObject } from '../../../interfaces';
+import { Bounds, Vector3 } from '../../../types';
+
+/**
+ * Initial values for vtkCleanPolyData.
+ */
+export interface ICleanPolyDataInitialValues {
+ /**
+ * The tolerance used for point merging.
+ */
+ tolerance?: number;
+
+ /**
+ * Whether the tolerance is absolute or relative.
+ */
+ toleranceIsAbsolute?: boolean;
+
+ /**
+ * The absolute tolerance value.
+ */
+ absoluteTolerance?: number;
+
+ /**
+ * The desired output precision for points.
+ */
+ outputPointsPrecision?: DesiredOutputPrecision;
+
+ /**
+ * Whether to merge points.
+ */
+ pointMerging?: boolean;
+
+ /**
+ * Whether to convert lines to points.
+ */
+ convertLinesToPoints?: boolean;
+
+ /**
+ * Whether to convert polygons to lines.
+ */
+ convertPolysToLines?: boolean;
+
+ /**
+ * Whether to convert strips to polygons.
+ */
+ convertStripsToPolys?: boolean;
+}
+
+type vtkCleanPolyDataBase = vtkObject & vtkAlgorithm;
+
+export interface vtkCleanPolyData extends vtkCleanPolyDataBase {
+ /**
+ * Create default locator.
+ */
+ createDefaultLocator(): void;
+
+ /**
+ * Get the absolute tolerance value.
+ */
+ getAbsoluteTolerance(): number;
+
+ /**
+ * Get whether to convert lines to points.
+ */
+ getConvertLinesToPoints(): boolean;
+
+ /**
+ * Get whether to convert polygons to lines.
+ */
+ getConvertPolysToLines(): boolean;
+
+ /**
+ * Get whether to convert strips to polygons.
+ */
+ getConvertStripsToPolys(): boolean;
+
+ /**
+ * Get the output points precision.
+ */
+ getOutputPointsPrecision(): DesiredOutputPrecision;
+
+ /**
+ * Get whether to merge points.
+ */
+ getPointMerging(): boolean;
+
+ /**
+ * Get the tolerance used for point merging.
+ */
+ getTolerance(): number;
+
+ /**
+ * Get whether the tolerance is absolute or relative.
+ */
+ getToleranceIsAbsolute(): boolean;
+
+ /**
+ * Operate on a bounding box by applying a transformation.
+ *
+ * @param {Bounds} inBounds The input bounding box.
+ * @param {Bounds} outBounds The output bounding box.
+ */
+ operateOnBounds(inBounds: Bounds, outBounds: Bounds): void;
+
+ /**
+ * Operate on a point by applying a transformation.
+ *
+ * @param {Vector3} point The point to operate on.
+ */
+ operateOnPoint(point: Vector3): void;
+
+ /**
+ *
+ * @param inData
+ * @param outData
+ */
+ requestData(inData: any, outData: any): void;
+
+ /**
+ * Set the absolute tolerance value.
+ * This is only used if ToleranceIsAbsolute is true.
+ * Initial value is 0.0
+ * @param {Number} absoluteTolerance The absolute tolerance value.
+ */
+ setAbsoluteTolerance(absoluteTolerance: number): boolean;
+
+ /**
+ * Set whether to convert lines to points.
+ * @param {Boolean} convertLinesToPoints
+ */
+ setConvertLinesToPoints(convertLinesToPoints: boolean): boolean;
+
+ /**
+ * Set whether to convert polygons to lines.
+ * @param {Boolean} convertPolysToLines
+ */
+ setConvertPolysToLines(convertPolysToLines: boolean): boolean;
+
+ /**
+ * Set whether to convert strips to polygons.
+ * @param {Boolean} convertStripsToPolys
+ */
+ setConvertStripsToPolys(convertStripsToPolys: boolean): boolean;
+
+ /**
+ * Set the desired output precision for points.
+ * Initial value is DEFAULT_PRECISION.
+ * @param {DesiredOutputPrecision} outputPointsPrecision The outputPointsPrecision value.
+ */
+ setOutputPointsPrecision(
+ outputPointsPrecision: DesiredOutputPrecision
+ ): boolean;
+
+ /**
+ * Set whether to merge points.
+ * Initial value is false.
+ * @param {Boolean} pointMerging The pointMerging value.
+ */
+ setPointMerging(pointMerging: boolean): boolean;
+
+ /**
+ * Set the tolerance used for point merging.
+ * This is ignored if ToleranceIsAbsolute is true.
+ * Initial value is 0.0
+ * @param {Number} tolerance The tolerance value.
+ */
+ setTolerance(tolerance: number): boolean;
+
+ /**
+ * Set whether the tolerance is absolute or relative.
+ * Initial value is false (relative).
+ * @param {Boolean} toleranceIsAbsolute The toleranceIsAbsolute value.
+ */
+ setToleranceIsAbsolute(toleranceIsAbsolute: boolean): boolean;
+}
+
+/**
+ * Method used to decorate a given object (publicAPI+model) with vtkCleanPolyData characteristics.
+ *
+ * @param publicAPI object on which methods will be bounds (public)
+ * @param model object on which data structure will be bounds (protected)
+ * @param {ICleanPolyDataInitialValues} [initialValues] (default: {})
+ */
+export function extend(
+ publicAPI: object,
+ model: object,
+ initialValues?: ICleanPolyDataInitialValues
+): void;
+
+/**
+ * Method used to create a new instance of vtkCleanPolyData.
+ * @param {ICleanPolyDataInitialValues} [initialValues] for pre-setting some of its content
+ */
+export function newInstance(
+ initialValues?: ICleanPolyDataInitialValues
+): vtkCleanPolyData;
+
+/**
+ * vtkCleanPolyData merge exactly coincident points.
+ *
+ * vtkCleanPolyData is a locator object to quickly locate points in 3D.
+ */
+export declare const vtkCleanPolyData: {
+ newInstance: typeof newInstance;
+ extend: typeof extend;
+};
+export default vtkCleanPolyData;
diff --git a/Sources/Filters/Core/CleanPolyData/index.js b/Sources/Filters/Core/CleanPolyData/index.js
new file mode 100644
index 00000000000..0e54bdad102
--- /dev/null
+++ b/Sources/Filters/Core/CleanPolyData/index.js
@@ -0,0 +1,486 @@
+import macro from 'vtk.js/Sources/macros';
+import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';
+import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray';
+import vtkMergePoints from 'vtk.js/Sources/Common/DataModel/MergePoints';
+import vtkPointLocator from 'vtk.js/Sources/Common/DataModel/PointLocator';
+import vtkPoints from 'vtk.js/Sources/Common/Core/Points';
+import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData/index';
+import { DesiredOutputPrecision } from 'vtk.js/Sources/Common/DataModel/DataSetAttributes/Constants';
+import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants';
+
+// ----------------------------------------------------------------------------
+// vtkCleanPolyData methods
+// ----------------------------------------------------------------------------
+
+function vtkCleanPolyData(publicAPI, model) {
+ // Set our classname
+ model.classHierarchy.push('vtkCleanPolyData');
+
+ const tempX = [];
+
+ // Point processing
+ function processPoint(
+ ptId,
+ inPts,
+ newPts,
+ inputPD,
+ outputPD,
+ pointMap,
+ numUsedPts
+ ) {
+ const newX = [0, 0, 0];
+
+ inPts.getPoint(ptId, tempX);
+ publicAPI.operateOnPoint(tempX, newX);
+
+ if (!model.pointMerging) {
+ if (pointMap[ptId] === -1) {
+ pointMap[ptId] = numUsedPts.value++;
+ newPts.setPoint(pointMap[ptId], newX);
+ outputPD.passData(inputPD, ptId, pointMap[ptId]);
+ }
+ return pointMap[ptId];
+ }
+ const newPtId = model._locator.insertUniquePoint(newX).id;
+ if (!model.copiedPoints.has(newPtId)) {
+ model.copiedPoints.add(newPtId);
+ outputPD.passData(inputPD, ptId, newPtId);
+ }
+ return newPtId;
+ }
+
+ publicAPI.operateOnPoint = (inPt, outPt) => {
+ outPt[0] = inPt[0];
+ outPt[1] = inPt[1];
+ outPt[2] = inPt[2];
+ };
+
+ publicAPI.operateOnBounds = (inBounds, outBounds) => {
+ vtkBoundingBox.setBounds(outBounds, inBounds);
+ };
+
+ publicAPI.createDefaultLocator = (input) => {
+ let tol;
+ if (model.toleranceIsAbsolute) {
+ tol = model.absoluteTolerance;
+ } else if (input) {
+ tol = model.tolerance * input.getLength();
+ } else {
+ tol = model.tolerance;
+ }
+
+ if (!model._locator) {
+ model._locator =
+ tol === 0.0
+ ? vtkMergePoints.newInstance()
+ : vtkPointLocator.newInstance();
+ return;
+ }
+
+ if (tol === 0.0 && model._locator?.getTolerance() !== 0.0) {
+ model._locator = vtkMergePoints.newInstance();
+ } else if (tol > 0.0 && !(model._locator?.getTolerance() > 0.0)) {
+ model._locator = vtkPointLocator.newInstance();
+ }
+ };
+
+ publicAPI.requestData = (inData, outData) => {
+ const input = inData[0];
+ const output = outData[0]?.initialize() || vtkPolyData.newInstance();
+ outData[0] = output;
+
+ const inPts = input.getPoints();
+ const numPts = input.getNumberOfPoints();
+
+ if (!inPts || numPts < 1) {
+ return;
+ }
+
+ const updatedPts = new Array(input.getMaxCellSize());
+ const numUsedPts = { value: 0 };
+
+ const precision = model.outputPointsPrecision;
+ let pointType = inPts.getDataType();
+ if (precision) {
+ pointType =
+ precision === DesiredOutputPrecision.DOUBLE
+ ? VtkDataTypes.DOUBLE
+ : VtkDataTypes.FLOAT;
+ }
+ const newPts = vtkPoints.newInstance({ dataType: pointType });
+ const inVerts = input.getVerts();
+ const inLines = input.getLines();
+ const inPolys = input.getPolys();
+ const inStrips = input.getStrips();
+
+ let newVerts = null;
+ let newLines = null;
+ let newPolys = null;
+ let newStrips = null;
+
+ const inputPD = input.getPointData();
+ const inputCD = input.getCellData();
+ const outputPD = output.getPointData();
+ const outputCD = output.getCellData();
+
+ let pointMap = null;
+ if (model.pointMerging) {
+ publicAPI.createDefaultLocator(input);
+
+ if (model.toleranceIsAbsolute) {
+ model._locator.setTolerance(model.absoluteTolerance);
+ } else {
+ model._locator.setTolerance(model.tolerance * input.getLength());
+ }
+
+ const originalBounds = input.getBounds();
+ const mappedBounds = [];
+ publicAPI.operateOnBounds(originalBounds, mappedBounds);
+ model._locator.initPointInsertion(newPts, mappedBounds);
+ } else {
+ pointMap = new Array(numPts).fill(-1);
+ }
+
+ // Copy data attributes setup
+ outputPD.copyStructure(inputPD);
+ outputCD.copyStructure(inputCD);
+
+ model.copiedPoints.clear();
+
+ let outLineData = null;
+ let outPolyData = null;
+ let outStrpData = null;
+ let vertIDcounter = 0;
+ let lineIDcounter = 0;
+ let polyIDcounter = 0;
+ let strpIDcounter = 0;
+
+ // Process vertices
+ let inCellID = 0;
+ if (inVerts && inVerts.getNumberOfCells() > 0) {
+ newVerts = vtkCellArray.newInstance();
+
+ let currentIdx = 0;
+ const cellData = inVerts.getData();
+ while (currentIdx < cellData.length) {
+ const npts = cellData[currentIdx++];
+ const inputPointIds = cellData.slice(currentIdx, currentIdx + npts);
+ currentIdx += npts;
+
+ let numNewPts = 0;
+
+ for (let i = 0; i < inputPointIds.length; i++) {
+ const ptId = inputPointIds[i];
+ const newPtId = processPoint(
+ ptId,
+ inPts,
+ newPts,
+ inputPD,
+ outputPD,
+ pointMap,
+ numUsedPts
+ );
+ updatedPts[numNewPts++] = newPtId;
+ }
+
+ if (numNewPts > 0) {
+ newVerts.insertNextCell(updatedPts.slice(0, numNewPts));
+ outputCD.passData(inputCD, inCellID, vertIDcounter);
+ vertIDcounter++;
+ }
+ inCellID++;
+ }
+ }
+
+ // Process lines
+ if (inLines && inLines.getNumberOfCells() > 0) {
+ newLines = vtkCellArray.newInstance();
+
+ let currentIdx = 0;
+ const cellData = inLines.getData();
+ while (currentIdx < cellData.length) {
+ const npts = cellData[currentIdx++];
+ const inputPointIds = cellData.slice(currentIdx, currentIdx + npts);
+ currentIdx += npts;
+
+ let numNewPts = 0;
+
+ for (let i = 0; i < inputPointIds.length; i++) {
+ const ptId = inputPointIds[i];
+ const newPtId = processPoint(
+ ptId,
+ inPts,
+ newPts,
+ inputPD,
+ outputPD,
+ pointMap,
+ numUsedPts
+ );
+
+ if (i === 0 || newPtId !== updatedPts[numNewPts - 1]) {
+ updatedPts[numNewPts++] = newPtId;
+ }
+ }
+
+ if (numNewPts >= 2) {
+ newLines.insertNextCell(updatedPts.slice(0, numNewPts));
+ if (!outLineData) {
+ outLineData = [];
+ }
+ outLineData.push({ inputId: inCellID, outputId: lineIDcounter });
+ lineIDcounter++;
+ } else if (
+ numNewPts === 1 &&
+ (inputPointIds.length === numNewPts || model.convertLinesToPoints)
+ ) {
+ if (!newVerts) {
+ newVerts = vtkCellArray.newInstance();
+ }
+ newVerts.insertNextCell(updatedPts.slice(0, numNewPts));
+ outputCD.passData(inputCD, inCellID, vertIDcounter);
+ vertIDcounter++;
+ }
+ inCellID++;
+ }
+ }
+
+ // Process polygons
+ if (inPolys && inPolys.getNumberOfCells() > 0) {
+ newPolys = vtkCellArray.newInstance();
+
+ let currentIdx = 0;
+ const cellData = inPolys.getData();
+ while (currentIdx < cellData.length) {
+ const npts = cellData[currentIdx++];
+ const inputPointIds = cellData.slice(currentIdx, currentIdx + npts);
+ currentIdx += npts;
+
+ let numNewPts = 0;
+
+ for (let i = 0; i < inputPointIds.length; i++) {
+ const ptId = inputPointIds[i];
+ const newPtId = processPoint(
+ ptId,
+ inPts,
+ newPts,
+ inputPD,
+ outputPD,
+ pointMap,
+ numUsedPts
+ );
+
+ if (i === 0 || newPtId !== updatedPts[numNewPts - 1]) {
+ updatedPts[numNewPts++] = newPtId;
+ }
+ }
+
+ // Remove duplicate last point if it matches first
+ if (numNewPts > 2 && updatedPts[0] === updatedPts[numNewPts - 1]) {
+ numNewPts--;
+ }
+
+ if (numNewPts > 2) {
+ newPolys.insertNextCell(updatedPts.slice(0, numNewPts));
+ if (!outPolyData) {
+ outPolyData = [];
+ }
+ outPolyData.push({ inputId: inCellID, outputId: polyIDcounter });
+ polyIDcounter++;
+ } else if (
+ numNewPts === 2 &&
+ (inputPointIds.length === numNewPts || model.convertPolysToLines)
+ ) {
+ if (!newLines) {
+ newLines = vtkCellArray.newInstance();
+ outLineData = [];
+ }
+ newLines.insertNextCell(updatedPts.slice(0, numNewPts));
+ outLineData.push({ inputId: inCellID, outputId: lineIDcounter });
+ lineIDcounter++;
+ } else if (
+ numNewPts === 1 &&
+ (inputPointIds.length === numNewPts || model.convertLinesToPoints)
+ ) {
+ if (!newVerts) {
+ newVerts = vtkCellArray.newInstance();
+ }
+ newVerts.insertNextCell(updatedPts.slice(0, numNewPts));
+ outputCD.passData(inputCD, inCellID, vertIDcounter);
+ vertIDcounter++;
+ }
+ inCellID++;
+ }
+ }
+
+ // Process triangle strips
+ if (inStrips && inStrips.getNumberOfCells() > 0) {
+ newStrips = vtkCellArray.newInstance();
+
+ let currentIdx = 0;
+ const cellData = inStrips.getData();
+ while (currentIdx < cellData.length) {
+ const npts = cellData[currentIdx++];
+ const inputPointIds = cellData.slice(currentIdx, currentIdx + npts);
+ currentIdx += npts;
+
+ let numNewPts = 0;
+
+ for (let i = 0; i < inputPointIds.length; i++) {
+ const ptId = inputPointIds[i];
+ const newPtId = processPoint(
+ ptId,
+ inPts,
+ newPts,
+ inputPD,
+ outputPD,
+ pointMap,
+ numUsedPts
+ );
+
+ if (i === 0 || newPtId !== updatedPts[numNewPts - 1]) {
+ updatedPts[numNewPts++] = newPtId;
+ }
+ }
+
+ // Remove duplicate last point if it matches first
+ if (numNewPts > 1 && updatedPts[0] === updatedPts[numNewPts - 1]) {
+ numNewPts--;
+ }
+
+ if (numNewPts > 3) {
+ newStrips.insertNextCell(updatedPts.slice(0, numNewPts));
+ if (!outStrpData) {
+ outStrpData = [];
+ }
+ outStrpData.push({ inputId: inCellID, outputId: strpIDcounter });
+ strpIDcounter++;
+ } else if (
+ numNewPts === 3 &&
+ (inputPointIds.length === numNewPts || model.convertStripsToPolys)
+ ) {
+ if (!newPolys) {
+ newPolys = vtkCellArray.newInstance();
+ outPolyData = [];
+ }
+ newPolys.insertNextCell(updatedPts.slice(0, numNewPts));
+ outPolyData.push({ inputId: inCellID, outputId: polyIDcounter });
+ polyIDcounter++;
+ } else if (
+ numNewPts === 2 &&
+ (inputPointIds.length === numNewPts || model.convertPolysToLines)
+ ) {
+ if (!newLines) {
+ newLines = vtkCellArray.newInstance();
+ outLineData = [];
+ }
+ newLines.insertNextCell(updatedPts.slice(0, numNewPts));
+ outLineData.push({ inputId: inCellID, outputId: lineIDcounter });
+ lineIDcounter++;
+ } else if (
+ numNewPts === 1 &&
+ (inputPointIds.length === numNewPts || model.convertLinesToPoints)
+ ) {
+ if (!newVerts) {
+ newVerts = vtkCellArray.newInstance();
+ }
+ newVerts.insertNextCell(updatedPts.slice(0, numNewPts));
+ outputCD.passData(inputCD, inCellID, vertIDcounter);
+ vertIDcounter++;
+ }
+ inCellID++;
+ }
+ }
+
+ // Clean up
+ if (model.pointMerging) {
+ model._locator.initialize();
+ } else {
+ newPts.setNumberOfPoints(numUsedPts.value);
+ }
+
+ // Copy cell data in correct order
+ let combinedCellID = vertIDcounter;
+
+ if (outLineData) {
+ outLineData.forEach((item) => {
+ outputCD.passData(inputCD, item.inputId, combinedCellID);
+ combinedCellID++;
+ });
+ }
+
+ if (outPolyData) {
+ outPolyData.forEach((item) => {
+ outputCD.passData(inputCD, item.inputId, combinedCellID);
+ combinedCellID++;
+ });
+ }
+
+ if (outStrpData) {
+ outStrpData.forEach((item) => {
+ outputCD.passData(inputCD, item.inputId, combinedCellID);
+ combinedCellID++;
+ });
+ }
+
+ // Set output
+ output.setPoints(newPts);
+ if (newVerts) output.setVerts(newVerts);
+ if (newLines) output.setLines(newLines);
+ if (newPolys) output.setPolys(newPolys);
+ if (newStrips) output.setStrips(newStrips);
+ };
+}
+
+// ----------------------------------------------------------------------------
+// Object factory
+// ----------------------------------------------------------------------------
+
+const DEFAULT_VALUES = {
+ pointMerging: true,
+ toleranceIsAbsolute: false,
+ tolerance: 0.0,
+ absoluteTolerance: 1.0,
+ convertLinesToPoints: true,
+ convertPolysToLines: true,
+ convertStripsToPolys: true,
+ locator: null,
+ outputPointsPrecision: DesiredOutputPrecision.DEFAULT,
+};
+
+// ----------------------------------------------------------------------------
+
+export function extend(publicAPI, model, initialValues = {}) {
+ Object.assign(model, DEFAULT_VALUES, initialValues);
+
+ // Make this a VTK object
+ macro.obj(publicAPI, model);
+
+ // Also make it an algorithm with one input and one output
+ macro.algo(publicAPI, model, 1, 1);
+
+ // Generate macros for properties
+ macro.setGet(publicAPI, model, [
+ 'pointMerging',
+ 'toleranceIsAbsolute',
+ 'tolerance',
+ 'absoluteTolerance',
+ 'convertPolysToLines',
+ 'convertLinesToPoints',
+ 'convertStripsToPolys',
+ 'outputPointsPrecision',
+ ]);
+
+ // Internal state
+ model.copiedPoints = new Set();
+
+ // Object methods
+ vtkCleanPolyData(publicAPI, model);
+}
+
+// ----------------------------------------------------------------------------
+
+export const newInstance = macro.newInstance(extend, 'vtkCleanPolyData');
+
+// ----------------------------------------------------------------------------
+
+export default { newInstance, extend };
diff --git a/Sources/Filters/Core/CleanPolyData/test/testCleanPolyData.js b/Sources/Filters/Core/CleanPolyData/test/testCleanPolyData.js
new file mode 100644
index 00000000000..7e9dcfab90a
--- /dev/null
+++ b/Sources/Filters/Core/CleanPolyData/test/testCleanPolyData.js
@@ -0,0 +1,229 @@
+import test from 'tape';
+import vtkPoints from 'vtk.js/Sources/Common/Core/Points';
+import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray';
+import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
+import vtkCleanPolyData from 'vtk.js/Sources/Filters/Core/CleanPolyData';
+
+function constructLines() {
+ const pts = vtkPoints.newInstance();
+ pts.insertNextTuple([0, 0, 0]);
+ pts.insertNextTuple([1, 0, 0]);
+ pts.insertNextTuple([1, 1, 0]);
+ pts.insertNextTuple([0, 0, 0]); // repeated
+
+ const lines = vtkCellArray.newInstance();
+ lines.insertNextCell([0, 1]); // valid line
+ lines.insertNextCell([0, 0]); // degenerate → vertex
+ lines.insertNextCell([0, 3]); // repeated pts → vertex if merging
+ lines.insertNextCell([0, 1, 2]); // polyline
+ lines.insertNextCell([0, 1, 1]); // degenerate → line
+ lines.insertNextCell([0, 3, 0]); // cycling → vertex if merging
+
+ const pd = vtkPolyData.newInstance();
+ pd.setPoints(pts);
+ pd.setLines(lines);
+
+ return pd;
+}
+
+function constructPolys() {
+ const pts = vtkPoints.newInstance();
+ pts.insertNextTuple([0, 0, 0]);
+ pts.insertNextTuple([1, 0, 0]);
+ pts.insertNextTuple([1, 1, 0]);
+ pts.insertNextTuple([1, 1, 1]); // unused
+ pts.insertNextTuple([0, 0, 0]); // repeated
+ pts.insertNextTuple([1, 0, 0]); // repeated
+
+ const polys = vtkCellArray.newInstance();
+ polys.insertNextCell([0, 1, 2]); // normal tri
+ polys.insertNextCell([0, 0, 0]); // degenerate → vertex
+ polys.insertNextCell([0, 1, 1]); // degenerate → line
+ polys.insertNextCell([0, 1, 5]); // repeated id → line if merging
+ polys.insertNextCell([0, 4, 0]); // vertex if merging
+ polys.insertNextCell([1, 1, 1, 1]); // quad→vertex
+ polys.insertNextCell([0, 1, 1, 0]); // quad→line
+
+ const pd = vtkPolyData.newInstance();
+ pd.setPoints(pts);
+ pd.setPolys(polys);
+ return pd;
+}
+
+function constructStrips() {
+ const pts = vtkPoints.newInstance();
+ pts.insertNextTuple([0, 0, 0]);
+ pts.insertNextTuple([1, 0, 0]);
+ pts.insertNextTuple([1, 1, 0]);
+ pts.insertNextTuple([0, 1, 0]);
+ pts.insertNextTuple([1, 1, 1]); // unused
+ pts.insertNextTuple([0, 0, 0]); // repeated
+ pts.insertNextTuple([1, 0, 0]); // repeated
+ pts.insertNextTuple([1, 1, 0]); // repeated
+
+ const strips = vtkCellArray.newInstance();
+ strips.insertNextCell([0, 1, 2, 3]); // normal strip
+ strips.insertNextCell([0, 1, 2, 2]); // tri if no merging
+ strips.insertNextCell([0, 1, 2, 7]); // repeated→tri if merging
+ strips.insertNextCell([0, 1, 1, 1]); // line
+ strips.insertNextCell([0, 0, 6, 5]); // line or tri
+ strips.insertNextCell([2, 2, 2, 2]); // vertex
+ strips.insertNextCell([0, 0, 0, 5]); // vertex or line
+
+ const pd = vtkPolyData.newInstance();
+ pd.setPoints(pts);
+ pd.setStrips(strips);
+ return pd;
+}
+
+function runTest(clean, inputPD, expected, t) {
+ clean.setInputData(inputPD);
+ // clean.update();
+ const out = clean.getOutputData();
+
+ t.equal(
+ out.getNumberOfPoints(),
+ expected.points,
+ `expected ${expected.points} points but got ${out.getNumberOfPoints()}`
+ );
+ t.equal(
+ out.getNumberOfVerts(),
+ expected.verts,
+ `expected ${expected.verts} verts but got ${out.getNumberOfVerts()}`
+ );
+ t.equal(
+ out.getNumberOfLines(),
+ expected.lines,
+ `expected ${expected.lines} lines but got ${out.getNumberOfLines()}`
+ );
+ t.equal(
+ out.getNumberOfPolys(),
+ expected.polys,
+ `expected ${expected.polys} polys but got ${out.getNumberOfPolys()}`
+ );
+ t.equal(
+ out.getNumberOfStrips(),
+ expected.strips,
+ `expected ${expected.strips} strips but got ${out.getNumberOfStrips()}`
+ );
+}
+
+test('vtkCleanPolyData: degenerate conversions without merging', (t) => {
+ const clean = vtkCleanPolyData.newInstance({
+ pointMerging: false,
+ convertLinesToPoints: true,
+ convertPolysToLines: true,
+ convertStripsToPolys: true,
+ });
+
+ runTest(
+ clean,
+ constructLines(),
+ { points: 4, verts: 1, lines: 5, polys: 0, strips: 0 },
+ t
+ );
+ runTest(
+ clean,
+ constructPolys(),
+ { points: 5, verts: 2, lines: 3, polys: 2, strips: 0 },
+ t
+ );
+ runTest(
+ clean,
+ constructStrips(),
+ { points: 7, verts: 1, lines: 2, polys: 2, strips: 2 },
+ t
+ );
+
+ t.end();
+});
+
+test('vtkCleanPolyData: degenerate elimination without merging', (t) => {
+ const clean = vtkCleanPolyData.newInstance({
+ pointMerging: false,
+ convertLinesToPoints: false,
+ convertPolysToLines: false,
+ convertStripsToPolys: false,
+ });
+
+ runTest(
+ clean,
+ constructLines(),
+ { points: 4, verts: 0, lines: 5, polys: 0, strips: 0 },
+ t
+ );
+ runTest(
+ clean,
+ constructPolys(),
+ { points: 5, verts: 0, lines: 0, polys: 2, strips: 0 },
+ t
+ );
+ runTest(
+ clean,
+ constructStrips(),
+ { points: 7, verts: 0, lines: 0, polys: 0, strips: 2 },
+ t
+ );
+
+ t.end();
+});
+
+test('vtkCleanPolyData: degenerate conversions with merging', (t) => {
+ const clean = vtkCleanPolyData.newInstance({
+ pointMerging: true,
+ convertLinesToPoints: true,
+ convertPolysToLines: true,
+ convertStripsToPolys: true,
+ });
+
+ runTest(
+ clean,
+ constructLines(),
+ { points: 3, verts: 3, lines: 3, polys: 0, strips: 0 },
+ t
+ );
+ runTest(
+ clean,
+ constructPolys(),
+ { points: 3, verts: 3, lines: 3, polys: 1, strips: 0 },
+ t
+ );
+ runTest(
+ clean,
+ constructStrips(),
+ { points: 4, verts: 2, lines: 2, polys: 2, strips: 1 },
+ t
+ );
+
+ t.end();
+});
+
+test('vtkCleanPolyData: degenerate elimination with merging', (t) => {
+ const clean = vtkCleanPolyData.newInstance({
+ pointMerging: true,
+ convertLinesToPoints: false,
+ convertPolysToLines: false,
+ convertStripsToPolys: false,
+ });
+
+ runTest(
+ clean,
+ constructLines(),
+ { points: 3, verts: 0, lines: 3, polys: 0, strips: 0 },
+ t
+ );
+ runTest(
+ clean,
+ constructPolys(),
+ { points: 3, verts: 0, lines: 0, polys: 1, strips: 0 },
+ t
+ );
+ runTest(
+ clean,
+ constructStrips(),
+ { points: 4, verts: 0, lines: 0, polys: 0, strips: 1 },
+ t
+ );
+
+ t.end();
+});
diff --git a/Sources/Filters/Core/index.js b/Sources/Filters/Core/index.js
index d8663f345c3..1ab64b17774 100644
--- a/Sources/Filters/Core/index.js
+++ b/Sources/Filters/Core/index.js
@@ -1,7 +1,11 @@
+import vtkCleanPolyData from './CleanPolyData';
import vtkCutter from './Cutter';
import vtkPolyDataNormals from './PolyDataNormals';
+import vtkThresholdPoints from './ThresholdPoints';
export default {
+ vtkCleanPolyData,
vtkCutter,
vtkPolyDataNormals,
+ vtkThresholdPoints,
};