├── .DS_Store
├── .babelrc
├── .gitignore
├── .gitmodules
├── .idea
├── misc.xml
├── modules.xml
├── npm-react-component-starter.iml
├── vcs.xml
└── workspace.xml
├── README.md
├── build
└── index.js
├── package-lock.json
├── package.json
├── src
├── OBJViewer.js
├── STLViewer.js
└── index.js
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bohdanbirdie/react-stl-obj-viewer/90b4ceaa71f70126b61fd8be77bce4d3abeaf7f5/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env", "es2015", "stage-0", "react"],
3 | "plugins": [
4 | "transform-object-rest-spread",
5 | "transform-react-jsx"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | node_modules/
3 | example/
4 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "example"]
2 | path = example
3 | url = https://github.com/bohdanbirdie/stl-obj-demo.git
4 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/npm-react-component-starter.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | solid =
79 | SOLID
80 | rotate
81 | orbitRender
82 | home
83 | orbitControls
84 | serv
85 | solid
86 | webg
87 | onSceneRendered
88 | console
89 |
90 |
91 |
92 |
93 |
94 |
95 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | true
119 | DEFINITION_ORDER
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | $USER_HOME$/.subversion
187 |
188 |
189 |
190 |
191 | 1519204916884
192 |
193 |
194 | 1519204916884
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-stl-obj-viewer
2 |
3 | React components to view STL and OBJ models.
4 | Based on THREE.js and [react-stl-viewer](https://github.com/chiedolabs/react-stl-viewer)
5 |
6 | ### [Live Demo](https://bohdanbirdie.github.io/stl-obj-demo/).
7 |
8 | #### [Example](https://github.com/bohdanbirdie/stl-obj-demo/tree/master).
9 |
10 | ## How to use
11 | 1. Install the app
12 | - via npm ```npm install react-stl-obj-viewer --save```
13 | - via yarn ```yarn add react-stl-obj-viewer```
14 |
15 | 2. Use OBJ or STL loader
16 |
17 | ```import {OBJViewer, STLViewer} from 'npm-react-component-starter';```
18 | 3. Pass props to viewers:
19 |
20 | ```
21 | static propTypes = {
22 | className: PropTypes.string, // Class name for viewer wrapper
23 | url: PropTypes.string, // Url for STL or OBJ model
24 | file: PropTypes.object, // File object of STL or OBJ model,
25 | // when passed *url* prop will be ingonred
26 | width: PropTypes.number, // Width of rendered area
27 | height: PropTypes.number, // Height of rendered area
28 | backgroundColor: PropTypes.string, // Scene background color
29 | modelColor: PropTypes.string,// Model color(textures unsupported)
30 | sceneClassName: PropTypes.string, // Class name for rendered canvas scene
31 | onSceneRendered: PropTypes.func, // Callback for rendered scene ready
32 | };
33 | ```
34 | Default props
35 | ```
36 | static defaultProps = {
37 | backgroundColor: '#EAEAEA',
38 | modelColor: '#B92C2C',
39 | height: 400,
40 | width: 400,
41 | sceneClassName: '',
42 | };
43 | ```
44 |
45 | ## Convert scene to screenshot
46 |
47 | Simply get your element _(canvas scene)_ by any selector and call `.toDataURL("image/png")` on it.
48 | ___
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-stl-obj-viewer",
3 | "version": "1.2.0",
4 | "description": "React components to view STL and OBJ models.",
5 | "main": "build/index.js",
6 | "peerDependencies": {
7 | "react": "^16.0.0"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git@github.com:bohdanbirdie/react-stl-obj-viewer.git"
12 | },
13 | "bugs": {
14 | "url": "git@github.com:bohdanbirdie/react-stl-obj-viewer.git"
15 | },
16 | "scripts": {
17 | "start": "webpack --watch",
18 | "build": "webpack",
19 | "test": "echo \"Error: no test specified\" && exit 1"
20 | },
21 | "keywords": [
22 | "react-component",
23 | "react",
24 | "webpack",
25 | "npm"
26 | ],
27 | "author": "Bohdan Birdie",
28 | "license": "ISC",
29 | "devDependencies": {
30 | "babel-cli": "^6.26.0",
31 | "babel-core": "^6.26.0",
32 | "babel-loader": "^7.1.2",
33 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
34 | "babel-plugin-transform-react-jsx": "^6.24.1",
35 | "babel-preset-env": "^1.6.0",
36 | "babel-preset-es2015": "^6.3.13",
37 | "babel-preset-react": "^6.3.13",
38 | "babel-preset-stage-0": "^6.3.13",
39 | "css-loader": "^0.28.7",
40 | "file-loader": "^1.1.3",
41 | "node-sass": "^4.11.0",
42 | "sass-loader": "^6.0.6",
43 | "style-loader": "^0.18.2",
44 | "url-loader": "^1.1.2"
45 | },
46 | "dependencies": {
47 | "prop-types": "^15.6.2",
48 | "react": "^16.7.0",
49 | "react-dom": "^16.7.0",
50 | "react-spinners": "^0.5.1",
51 | "three": "^0.100.0",
52 | "three-addons": "^1.2.0",
53 | "three-orbit-controls": "^82.1.0",
54 | "three-stl-loader": "git+https://github.com/bohdanbirdie/three-stl-loader.git",
55 | "webpack": "^3.6.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/OBJViewer.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types'
3 | import ReactDOM from 'react-dom';
4 | import * as THREE from 'three';
5 | import OrbitControlsModule from 'three-orbit-controls'
6 | import {OBJLoader} from 'three-addons';
7 | import {ScaleLoader} from 'react-spinners';
8 |
9 | const OrbitControls = OrbitControlsModule(THREE);
10 |
11 | class OBJViewer extends Component {
12 | static propTypes = {
13 | className: PropTypes.string,
14 | url: PropTypes.string,
15 | file: PropTypes.object,
16 | width: PropTypes.number,
17 | height: PropTypes.number,
18 | backgroundColor: PropTypes.string,
19 | modelColor: PropTypes.string,
20 | sceneClassName: PropTypes.string,
21 | onSceneRendered: PropTypes.func,
22 | };
23 |
24 | static defaultProps = {
25 | backgroundColor: '#EAEAEA',
26 | modelColor: '#B92C2C',
27 | height: 400,
28 | width: 400,
29 | orbitControls: true,
30 | sceneClassName: '',
31 | };
32 |
33 | constructor(props) {
34 | super(props);
35 |
36 | }
37 |
38 | componentDidMount() {
39 | this.renderModel(this.props)
40 | }
41 |
42 | componentDidUpdate(nextProps) {
43 | this.renderModel(nextProps)
44 | }
45 |
46 | renderModel(props) {
47 | let camera, scene, renderer, controls;
48 | const {url, file, width, height, modelColor, backgroundColor, orbitControls, onSceneRendered, sceneClassName} = props;
49 | let xDims, yDims, zDims;
50 |
51 | camera = new THREE.PerspectiveCamera(30, width / height, 1, 10000);
52 | scene = new THREE.Scene();
53 |
54 | const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
55 | directionalLight.position.x = 0;
56 | directionalLight.position.y = 1;
57 | directionalLight.position.z = 0;
58 | directionalLight.position.normalize();
59 | scene.add(directionalLight);
60 |
61 | const ambientLight = new THREE.AmbientLight(0x404040); // soft white light
62 |
63 | scene.add(ambientLight);
64 | scene.add(camera);
65 |
66 | const loader = new OBJLoader();
67 |
68 |
69 | const onProgress = function (xhr) {
70 | if (xhr.lengthComputable) {
71 | // var percentComplete = xhr.loaded / xhr.total * 100;
72 | }
73 | };
74 |
75 | const onLoad = (object) => {
76 | const bbox = new THREE.Box3().setFromObject(object);
77 |
78 | xDims = bbox.max.x - bbox.min.x;
79 | yDims = bbox.max.y - bbox.min.y;
80 | zDims = bbox.max.z - bbox.min.z;
81 | camera.position.set(0, 0, Math.max(xDims * 3, yDims * 3, zDims * 3));
82 |
83 | object.traverse(function (child) {
84 | if (child.isMesh) {
85 | child.material.color.setStyle(modelColor);
86 | }
87 | });
88 |
89 | object.position.y = -95;
90 | scene.add(object);
91 |
92 | renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true, antialias: true});
93 | renderer.setPixelRatio(window.devicePixelRatio);
94 | renderer.setClearColor(backgroundColor, 1);
95 | renderer.setSize(width, height);
96 | renderer.domElement.className = sceneClassName;
97 |
98 | if (orbitControls) {
99 | controls = new OrbitControls(camera, ReactDOM.findDOMNode(this));
100 | controls.enableKeys = false;
101 | controls.addEventListener('change', orbitRender);
102 | }
103 |
104 |
105 | ReactDOM.findDOMNode(this).replaceChild(renderer.domElement,
106 | ReactDOM.findDOMNode(this).firstChild);
107 |
108 |
109 | render();
110 |
111 | if (typeof onSceneRendered === "function") {
112 | onSceneRendered(ReactDOM.findDOMNode(renderer.domElement))
113 | }
114 | };
115 |
116 | const onError = function (xhr) {
117 | };
118 |
119 |
120 | if (file) {
121 | const reader = new FileReader();
122 |
123 | reader.onload = function (evt) {
124 | if (evt.target.readyState != 2) return;
125 | if (evt.target.error) {
126 | alert('Error while reading file');
127 | return;
128 | }
129 |
130 | onLoad(loader.parse(evt.target.result))
131 |
132 | };
133 |
134 | reader.readAsText(file);
135 | } else {
136 | loader.load(url, onLoad, onProgress, onError);
137 | }
138 |
139 |
140 | const render = () => {
141 | renderer.render(scene, camera);
142 | };
143 |
144 | const orbitRender = () => {
145 | render();
146 | };
147 | }
148 |
149 | componentDidUpdate() {
150 | this.setState({allowUpdate: true})
151 | }
152 |
153 | shouldComponentUpdate(nextProps, nextState) {
154 | if (JSON.stringify(nextProps) === JSON.stringify(this.props)) {
155 | return false
156 | }
157 | return true
158 | }
159 |
160 | componentDidCatch(error, info) {
161 | console.log(error, info)
162 | }
163 |
164 | render() {
165 | return (
166 |
186 | );
187 | };
188 | };
189 |
190 | module.exports = OBJViewer;
191 |
--------------------------------------------------------------------------------
/src/STLViewer.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types'
3 | import ReactDOM from 'react-dom';
4 | import * as THREE from 'three';
5 | import STLLoaderModule from 'three-stl-loader'
6 | import OrbitControlsModule from 'three-orbit-controls'
7 | import {ScaleLoader} from 'react-spinners';
8 |
9 | const STLLoader = STLLoaderModule(THREE);
10 | const OrbitControls = OrbitControlsModule(THREE);
11 |
12 | class STLViewer extends Component {
13 | static propTypes = {
14 | className: PropTypes.string,
15 | url: PropTypes.string,
16 | file: PropTypes.object,
17 | width: PropTypes.number,
18 | height: PropTypes.number,
19 | backgroundColor: PropTypes.string,
20 | modelColor: PropTypes.string,
21 | sceneClassName: PropTypes.string,
22 | onSceneRendered: PropTypes.func,
23 | };
24 |
25 | static defaultProps = {
26 | backgroundColor: '#EAEAEA',
27 | modelColor: '#B92C2C',
28 | height: 400,
29 | width: 400,
30 | rotate: true,
31 | orbitControls: true,
32 | sceneClassName: '',
33 | };
34 |
35 | componentDidMount() {
36 | this.renderModel(this.props);
37 | }
38 |
39 | renderModel(props) {
40 | let camera, scene, renderer, mesh, distance, controls;
41 | const {url, file, width, height, modelColor, backgroundColor, orbitControls, sceneClassName, onSceneRendered} = props;
42 | let xDims, yDims, zDims;
43 | let component = this;
44 |
45 |
46 | scene = new THREE.Scene();
47 | distance = 10000;
48 | const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
49 | directionalLight.position.x = 0;
50 | directionalLight.position.y = 1;
51 | directionalLight.position.z = 0;
52 | directionalLight.position.normalize();
53 | scene.add(directionalLight);
54 |
55 | const ambientLight = new THREE.AmbientLight(0x404040); // soft white light
56 | scene.add(ambientLight);
57 |
58 | const onLoad = geometry => {
59 | geometry.computeFaceNormals();
60 | geometry.computeVertexNormals();
61 | geometry.center();
62 |
63 | mesh = new THREE.Mesh(
64 | geometry,
65 | new THREE.MeshLambertMaterial({
66 | overdraw: true,
67 | color: modelColor,
68 | }
69 | ));
70 |
71 | geometry.computeBoundingBox();
72 | xDims = geometry.boundingBox.max.x - geometry.boundingBox.min.x;
73 | yDims = geometry.boundingBox.max.y - geometry.boundingBox.min.y;
74 | zDims = geometry.boundingBox.max.z - geometry.boundingBox.min.z;
75 |
76 | scene.add(mesh);
77 |
78 | camera = new THREE.PerspectiveCamera(30, width / height, 1, distance);
79 | camera.position.set(0, 0, Math.max(xDims * 3, yDims * 3, zDims * 3));
80 |
81 | scene.add(camera);
82 |
83 | renderer = new THREE.WebGLRenderer({
84 | preserveDrawingBuffer: true,
85 | antialias: true
86 | });
87 | renderer.setSize(width, height);
88 | renderer.setClearColor(backgroundColor, 1);
89 | renderer.domElement.className = sceneClassName;
90 |
91 |
92 | if (orbitControls) {
93 | controls = new OrbitControls(camera, ReactDOM.findDOMNode(component));
94 | controls.enableKeys = false;
95 | controls.addEventListener('change', orbitRender);
96 | }
97 |
98 | ReactDOM.findDOMNode(this).replaceChild(renderer.domElement,
99 | ReactDOM.findDOMNode(this).firstChild);
100 |
101 | render();
102 |
103 | if (typeof onSceneRendered === "function") {
104 | onSceneRendered(ReactDOM.findDOMNode(renderer.domElement))
105 | }
106 | };
107 |
108 | const onProgress = (xhr) => {
109 | if (xhr.lengthComputable) {
110 | let percentComplete = xhr.loaded / xhr.total * 100;
111 | }
112 | };
113 |
114 | const loader = new STLLoader();
115 |
116 | if (file) {
117 | loader.loadFile(file, onLoad, onProgress);
118 | } else {
119 | loader.load(url, onLoad, onProgress);
120 | }
121 |
122 | const render = () => {
123 | renderer.render(scene, camera);
124 | };
125 |
126 | const orbitRender = () => {
127 | render();
128 | };
129 | }
130 |
131 | shouldComponentUpdate(nextProps, nextState) {
132 | if (JSON.stringify(nextProps) === JSON.stringify(this.props)) {
133 | return false
134 | }
135 | return true
136 | }
137 |
138 | componentDidUpdate(nextProps, nextState) {
139 | this.renderModel(nextProps);
140 | }
141 |
142 | componentDidCatch(error, info) {
143 | console.log(error, info)
144 | }
145 |
146 | render() {
147 | return (
148 |
168 | );
169 | };
170 | };
171 |
172 | module.exports = STLViewer;
173 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import OBJViewer from './OBJViewer';
2 | import STLViewer from './STLViewer';
3 |
4 | export {
5 | OBJViewer,
6 | STLViewer
7 | }
8 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | module.exports = {
3 | entry: './src/index.js',
4 | output: {
5 | path: path.resolve(__dirname, 'build'),
6 | filename: 'index.js',
7 | libraryTarget: 'commonjs2'
8 | },
9 | module: {
10 | loaders: [
11 | {
12 | test: /\.jsx?$/,
13 | include: path.resolve(__dirname, 'src'),
14 | exclude: /(node_modules|bower_components|build)/,
15 | loader: 'babel-loader'
16 | },
17 | {
18 | test: /\.scss$/,
19 | loaders: ['style-loader', 'css-loader', 'sass-loader']
20 | },
21 | {
22 | test: /\.(jpg|png|gif)$/,
23 | loader: 'url-loader',
24 | options: {
25 | limit: 25000,
26 | },
27 | },
28 | ],
29 | },
30 | externals: {
31 | 'react': 'commonjs react' // this line is just to use the React dependency of our parent-testing-project instead of using our own React.
32 | }
33 | };
34 |
--------------------------------------------------------------------------------