Skip to content

Commit fc22a82

Browse files
committed
feat(objwriter): add support for vtkOBJWriter
fix #2404
1 parent 5294bbb commit fc22a82

File tree

3 files changed

+300
-0
lines changed

3 files changed

+300
-0
lines changed
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import 'vtk.js/Sources/favicon';
2+
3+
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
4+
import 'vtk.js/Sources/Rendering/Profiles/Geometry';
5+
6+
import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
7+
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
8+
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
9+
10+
import vtkOBJWriter from 'vtk.js/Sources/IO/Misc/OBJWriter';
11+
import vtkOBJReader from 'vtk.js/Sources/IO/Misc/OBJReader';
12+
import vtkPolyDataReader from 'vtk.js/Sources/IO/Legacy/PolyDataReader';
13+
// ----------------------------------------------------------------------------
14+
// Standard rendering code setup
15+
// ----------------------------------------------------------------------------
16+
17+
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
18+
background: [0.5, 0.5, 0.5],
19+
});
20+
const renderer = fullScreenRenderer.getRenderer();
21+
const renderWindow = fullScreenRenderer.getRenderWindow();
22+
23+
const resetCamera = renderer.resetCamera;
24+
const render = renderWindow.render;
25+
26+
const reader = vtkPolyDataReader.newInstance();
27+
const writerReader = vtkOBJReader.newInstance();
28+
const writer = vtkOBJWriter.newInstance();
29+
30+
reader
31+
.setUrl(`${__BASE_PATH__}/data/legacy/sphere.vtk`, { loadData: true })
32+
.then(() => {
33+
writer.setInputData(reader.getOutputData());
34+
const fileContents = writer.getOutputData();
35+
// Can also use a static function to write to OBJ:
36+
// const fileContents = vtkOBJWriter.writeOBJ(reader.getOutputData());
37+
38+
// Display the resulting OBJ
39+
writerReader.parseAsText(fileContents);
40+
41+
const polydata = reader.getOutputData(0);
42+
const mapper = vtkMapper.newInstance();
43+
const actor = vtkActor.newInstance();
44+
actor.setMapper(mapper);
45+
mapper.setInputData(polydata);
46+
47+
renderer.addActor(actor);
48+
resetCamera();
49+
render();
50+
51+
// Add a download link for it
52+
const blob = new Blob([fileContents], { type: 'application/octet-steam' });
53+
const a = window.document.createElement('a');
54+
a.href = window.URL.createObjectURL(blob, {
55+
type: 'application/octet-steam',
56+
});
57+
a.download = 'sphere.obj';
58+
a.text = 'Download';
59+
a.style.position = 'absolute';
60+
a.style.left = '50%';
61+
a.style.bottom = '10px';
62+
document.body.appendChild(a);
63+
a.style.background = 'white';
64+
a.style.padding = '5px';
65+
});
66+
67+
global.writer = writer;
68+
global.writerReader = writerReader;
69+
global.fullScreenRenderer = fullScreenRenderer;

Sources/IO/Misc/OBJWriter/index.d.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import vtkPolyData from "../../../Common/DataModel/PolyData";
2+
import { vtkAlgorithm, vtkObject } from "../../../interfaces";
3+
4+
/**
5+
*
6+
*/
7+
export interface IOBJWriterInitialValues {}
8+
9+
type vtkOBJWriterBase = vtkObject & vtkAlgorithm;
10+
11+
export interface vtkOBJWriter extends vtkOBJWriterBase {
12+
13+
/**
14+
*
15+
* @param inData
16+
* @param outData
17+
*/
18+
requestData(inData: any, outData: any): void;
19+
}
20+
21+
/**
22+
* Method used to decorate a given object (publicAPI+model) with vtkOBJWriter characteristics.
23+
*
24+
* @param publicAPI object on which methods will be bounds (public)
25+
* @param model object on which data structure will be bounds (protected)
26+
* @param {IOBJWriterInitialValues} [initialValues] (default: {})
27+
*/
28+
export function extend(publicAPI: object, model: object, initialValues?: IOBJWriterInitialValues): void;
29+
30+
/**
31+
* Method used to create a new instance of vtkOBJWriter
32+
* @param {IOBJWriterInitialValues} [initialValues] for pre-setting some of its content
33+
*/
34+
export function newInstance(initialValues?: IOBJWriterInitialValues): vtkOBJWriter;
35+
36+
/**
37+
*
38+
* @param {vktPolyData} polyData
39+
*/
40+
export function writeOBJ(polyData: vtkPolyData): vtkPolyData;
41+
42+
/**
43+
* vtkOBJWriter writes wavefront obj (.obj) files in ASCII form. OBJ files
44+
* contain the geometry including lines, triangles and polygons. Normals and
45+
* texture coordinates on points are also written if they exist.
46+
*/
47+
export declare const vtkOBJWriter: {
48+
newInstance: typeof newInstance;
49+
extend: typeof extend;
50+
writeOBJ: typeof writeOBJ;
51+
}
52+
export default vtkOBJWriter;

Sources/IO/Misc/OBJWriter/index.js

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import macro from 'vtk.js/Sources/macros';
2+
3+
const { vtkErrorMacro } = macro;
4+
5+
// ----------------------------------------------------------------------------
6+
// Global methods
7+
// ----------------------------------------------------------------------------
8+
9+
// ----------------------------------------------------------------------------
10+
// vtkOBJWriter methods
11+
// ----------------------------------------------------------------------------
12+
13+
const writeFaces = (faces, withNormals, withTCoords) => {
14+
let outputData = '';
15+
const fd = faces.getData();
16+
17+
let offset = 0;
18+
while (offset < fd.length) {
19+
const faceSize = fd[offset++];
20+
outputData += 'f';
21+
for (let i = 0; i < faceSize; i++) {
22+
outputData += ` ${fd[offset + i] + 1}`;
23+
if (withTCoords) {
24+
outputData += `/${fd[offset + i] + 1}`;
25+
if (withNormals) {
26+
outputData += `//${fd[offset + i] + 1}`;
27+
}
28+
} else if (withNormals) {
29+
outputData += `//${fd[offset + i] + 1}`;
30+
}
31+
}
32+
offset += faceSize;
33+
outputData += '\n';
34+
}
35+
return outputData;
36+
};
37+
38+
const writeLines = (lines) => {
39+
let outputData = '';
40+
const ld = lines.getData();
41+
42+
let offset = 0;
43+
while (offset < ld.length) {
44+
const lineSize = ld[offset++];
45+
outputData += 'l';
46+
for (let i = 0; i < lineSize; i++) {
47+
outputData += ` ${ld[offset + i] + 1}`;
48+
}
49+
offset += lineSize;
50+
outputData += '\n';
51+
}
52+
53+
return outputData;
54+
};
55+
56+
const writePoints = (pts, normals, tcoords) => {
57+
let outputData = '';
58+
const nbPts = pts.getNumberOfPoints();
59+
60+
let p;
61+
62+
// Positions
63+
for (let i = 0; i < nbPts; i++) {
64+
p = pts.getPoint(i, p);
65+
outputData += `v ${p[0]} ${p[1]} ${p[2]}\n`;
66+
}
67+
68+
// Normals
69+
if (normals) {
70+
for (let i = 0; i < nbPts; i++) {
71+
p = normals.getTuple(i, p);
72+
outputData += `vn ${p[0]} ${p[1]} ${p[2]}\n`;
73+
}
74+
}
75+
76+
// Textures
77+
if (tcoords) {
78+
for (let i = 0; i < nbPts; i++) {
79+
p = tcoords.getTuple(i, p);
80+
outputData += `vt ${p[0]} ${p[1]}\n`;
81+
}
82+
}
83+
return outputData;
84+
};
85+
86+
const writeOBJ = (polyData) => {
87+
let outputData = '# VTK.js generated OBJ File\n';
88+
const pts = polyData.getPoints();
89+
const polys = polyData.getPolys();
90+
const strips = polyData.getStrips() ? polyData.getStrips().getData() : null;
91+
const lines = polyData.getLines();
92+
93+
const normals = polyData.getPointData().getNormals();
94+
const tcoords = polyData.getPointData().getTCoords();
95+
96+
const hasPtNormals = normals !== null;
97+
const hasPtTCoords = tcoords !== null;
98+
99+
if (!pts) {
100+
vtkErrorMacro('No data to write!');
101+
}
102+
103+
// Write points
104+
outputData += writePoints(pts, normals, tcoords);
105+
106+
// Unsupported triangle strips
107+
if (strips && strips.length > 0) {
108+
vtkErrorMacro('Unsupported strips');
109+
}
110+
111+
// Write polygons.
112+
if (polys) {
113+
outputData += writeFaces(polys, hasPtNormals, hasPtTCoords);
114+
}
115+
116+
// Write lines.
117+
if (lines) {
118+
outputData += writeLines(lines);
119+
}
120+
121+
return outputData;
122+
};
123+
124+
// ----------------------------------------------------------------------------
125+
// Static API
126+
// ----------------------------------------------------------------------------
127+
128+
export const STATIC = {
129+
writeOBJ,
130+
};
131+
132+
// ----------------------------------------------------------------------------
133+
// vtkOBJWriter methods
134+
// ----------------------------------------------------------------------------
135+
136+
function vtkOBJWriter(publicAPI, model) {
137+
// Set our className
138+
model.classHierarchy.push('vtkOBJWriter');
139+
140+
publicAPI.requestData = (inData, outData) => {
141+
const input = inData[0];
142+
143+
if (!input || !input.isA('vtkPolyData')) {
144+
vtkErrorMacro('Invalid or missing input');
145+
return;
146+
}
147+
148+
outData[0] = writeOBJ(input);
149+
};
150+
}
151+
152+
// ----------------------------------------------------------------------------
153+
// Object factory
154+
// ----------------------------------------------------------------------------
155+
156+
const DEFAULT_VALUES = {};
157+
158+
// ----------------------------------------------------------------------------
159+
160+
export function extend(publicAPI, model, initialValues = {}) {
161+
Object.assign(model, DEFAULT_VALUES, initialValues);
162+
163+
// Make this a VTK object
164+
macro.obj(publicAPI, model);
165+
166+
// Also make it an algorithm with one input and one output
167+
macro.algo(publicAPI, model, 1, 1);
168+
169+
// Object specific methods
170+
vtkOBJWriter(publicAPI, model);
171+
}
172+
173+
// ----------------------------------------------------------------------------
174+
175+
export const newInstance = macro.newInstance(extend, 'vtkOBJWriter');
176+
177+
// ----------------------------------------------------------------------------
178+
179+
export default { newInstance, extend, ...STATIC };

0 commit comments

Comments
 (0)