1 /* 2 Copyright 2008-2025 3 Matthias Ehmann, 4 Carsten Miller, 5 Andreas Walter, 6 Alfred Wassermann 7 8 This file is part of JSXGraph. 9 10 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 11 12 You can redistribute it and/or modify it under the terms of the 13 14 * GNU Lesser General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version 17 OR 18 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 19 20 JSXGraph is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 GNU Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public License and 26 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 27 and <https://opensource.org/licenses/MIT/>. 28 */ 29 30 /*global JXG: true, define: true, Float32Array: true */ 31 /*jslint nomen: true, plusplus: true, bitwise: true*/ 32 33 /** 34 * @fileoverview In this file the namespace JXG.Parse3D is defined. 35 */ 36 import JXG from "../jxg.js"; 37 // import Type from "../utils/type.js"; 38 39 /** 40 * Namespace Parse3D. Contains parsers for 3D models like STL. 41 * @namespace 42 */ 43 JXG.Parse3D = { 44 45 /** 46 * Parser for the ASCII STL format, see https://en.wikipedia.org/wiki/STL_(file_format). 47 * STL stands for stereo-lithography. 48 * 49 * @param {String} str String containing STL file format 50 * @returns {Array} [[vertices, faces], ...] as list of polyhedra. Each entry is the input for a polyhedron3d. 51 * @example 52 * const board = JXG.JSXGraph.initBoard( 53 * 'jxgbox', 54 * { 55 * boundingbox: [-8, 8, 8, -8], 56 * minimizeReflow: 'svg', 57 * axis: false, 58 * showNavigation: false, 59 * zoom: { 60 * enabled: false 61 * }, 62 * pan: { 63 * enabled: false 64 * } 65 * } 66 * ); 67 * 68 * var bound = [-1, 2]; 69 * var view = board.create( 70 * 'view3d', 71 * [[-5, -3], [8, 8], 72 * [bound, bound, bound]], 73 * { 74 * axesPosition: 'none', 75 * projection: 'central', 76 * trackball: { enabled: true }, 77 * depthOrder: { enabled: true }, 78 * xPlaneRear: { visible: false }, 79 * yPlaneRear: { visible: false }, 80 * zPlaneRear: { fillOpacity: 0.2, visible: true }, 81 * az: { 82 * slider: { 83 * visible: true, 84 * start: 1.54 85 * } 86 * } 87 * 88 * } 89 * ); 90 * 91 * // Tetrahedron 92 * var model = `solid m 93 * facet normal 0 0 0 94 * outer loop 95 * vertex 0 0 0 96 * vertex 1 0 0 97 * vertex 1 1 0 98 * endloop 99 * endfacet 100 * facet normal 0 0 0 101 * outer loop 102 * vertex 0 0 0 103 * vertex 1 0 0 104 * vertex 0.5 0.5 1 105 * endloop 106 * endfacet 107 * facet normal 0 0 0 108 * outer loop 109 * vertex 0 0 0 110 * vertex 1 1 0 111 * vertex 0.5 0.5 1 112 * endloop 113 * endfacet 114 * facet normal 0 0 0 115 * outer loop 116 * vertex 1 0 0 117 * vertex 1 1 0 118 * vertex 0.5 0.5 1 119 * endloop 120 * endfacet 121 * endsolid m`; 122 * 123 * var m = JXG.Parse3D.STL(model); 124 * 125 * for (let i = 0; i < m.length; i++) { 126 * view.create('polyhedron3d', m[i], { 127 * fillColorArray: [], // ['yellow', 'red', 'green', 'blue'], 128 * layer: 12, 129 * strokeWidth: 0, 130 * shader: { 131 * enabled: true, 132 * type: 'angle', 133 * hue: 0 + 60 * i, 134 * saturation: 90, 135 * minlightness: 60, 136 * maxLightness: 80 137 * }, 138 * fillOpacity: 0.8 139 * }); 140 * } 141 * 142 * </pre><div id="JXG8fa8ce22-3613-452f-9775-69588a1c1e34" class="jxgbox" style="width: 300px; height: 300px;"></div> 143 * <script type="text/javascript"> 144 * (function() { 145 * var board = JXG.JSXGraph.initBoard('JXG8fa8ce22-3613-452f-9775-69588a1c1e34', { 146 * showcopyright: false, shownavigation: false, 147 * boundingbox: [-8, 8, 8, -8], 148 * minimizeReflow: 'svg', 149 * axis: false, 150 * showNavigation: false, 151 * zoom: { 152 * enabled: false 153 * }, 154 * pan: { 155 * enabled: false 156 * } 157 * } 158 * ); 159 * 160 * var bound = [-1, 2]; // Tetrahedron 161 * var view = board.create( 162 * 'view3d', 163 * [[-5, -3], [8, 8], 164 * [bound, bound, bound]], 165 * { 166 * axesPosition: 'none', 167 * projection: 'central', 168 * trackball: { enabled: true }, 169 * depthOrder: { enabled: true }, 170 * xPlaneRear: { visible: false }, 171 * yPlaneRear: { visible: false }, 172 * zPlaneRear: { fillOpacity: 0.2, visible: true }, 173 * az: { 174 * slider: { 175 * visible: true, 176 * start: 1.54 177 * } 178 * } 179 * 180 * } 181 * ); 182 * 183 * // Tetrahedron 184 * var model = `solid m 185 * facet normal 0 0 0 186 * outer loop 187 * vertex 0 0 0 188 * vertex 1 0 0 189 * vertex 1 1 0 190 * endloop 191 * endfacet 192 * facet normal 0 0 0 193 * outer loop 194 * vertex 0 0 0 195 * vertex 1 0 0 196 * vertex 0.5 0.5 1 197 * endloop 198 * endfacet 199 * facet normal 0 0 0 200 * outer loop 201 * vertex 0 0 0 202 * vertex 1 1 0 203 * vertex 0.5 0.5 1 204 * endloop 205 * endfacet 206 * facet normal 0 0 0 207 * outer loop 208 * vertex 1 0 0 209 * vertex 1 1 0 210 * vertex 0.5 0.5 1 211 * endloop 212 * endfacet 213 * endsolid m`; 214 * 215 * var m = JXG.Parse3D.STL(model); 216 * 217 * for (let i = 0; i < m.length; i++) { 218 * view.create('polyhedron3d', m[i], { 219 * fillColorArray: [], // ['yellow', 'red', 'green', 'blue'], 220 * layer: 12, 221 * strokeWidth: 0, 222 * shader: { 223 * enabled: true, 224 * type: 'angle', 225 * hue: 0 + 60 * i, 226 * saturation: 90, 227 * minlightness: 60, 228 * maxLightness: 80 229 * }, 230 * fillOpacity: 0.8 231 * }); 232 * } 233 * })(); 234 * 235 * </script><pre> 236 * 237 */ 238 STL: function (str) { 239 var i, j, pos, le, 240 li, 241 lines, 242 coords, 243 face_num, found, 244 polyhedra = [], 245 vertices = [], 246 faces = []; 247 248 lines = str.split('\n'); 249 250 le = lines.length; 251 for (i = 0; i < le; i++) { 252 li = lines[i].trim(); 253 254 if (li.indexOf('solid') === 0) { 255 // New model 256 face_num = -1; 257 vertices = []; 258 faces = []; 259 } else if (li.indexOf('endsolid') === 0) { 260 polyhedra.push([vertices.slice(), faces.slice()]); 261 // break; 262 } else if (li.indexOf('facet') === 0) { 263 face_num++; 264 faces.push([]); 265 } else if (li.indexOf('outer loop') === 0 || li.indexOf('endloop') === 0) { 266 continue; 267 } else if (li.indexOf('vertex') === 0) { 268 coords = li.split(' ').slice(1).map((x) => parseFloat(x)); 269 found = false; 270 for (j = 0; j < vertices.length; j++) { 271 if (JXG.Math.Geometry.distance(vertices[j], coords, 3) < JXG.Math.eps) { 272 // Debug: 273 // console.log("Point already defined") 274 found = true; 275 pos = j; 276 break; 277 } 278 } 279 if (found === false) { 280 pos = vertices.length; 281 vertices.push(coords); 282 } 283 faces[face_num].push(pos); 284 } 285 } 286 // console.log('v:', vertices.length, 'f:', faces.length) 287 288 // return [vertices, faces]; 289 return polyhedra; 290 } 291 292 }; 293 294 295 export default JXG.Parse3D; 296