├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── bundle.5f6951b3191a2214dc44.js ├── bundle.5f6951b3191a2214dc44.js.LICENSE.txt ├── bundle.5f6951b3191a2214dc44.js.map ├── index.html ├── main.css ├── main.css.map ├── models ├── .DS_Store ├── test.stl └── test2.stl └── src ├── index.html ├── script.js └── style.css /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewSink/STL-to-ASCII-Generator/0fffc1fcb05f9b06676a63b791836a03216a4e23/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 AndrewSink 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STL to ASCII Generator 2 | 3 | Try it out: [STL to ASCII Generator](https://andrewsink.github.io/STL-to-ASCII-Generator/) 4 | 5 | 6 | 7 | UPDATE: Huge thanks to [Oskitone](https://github.com/oskitone) for their help in adding a text export option! 8 | 9 | The STL to ASCII Generator is a lightweight and easy way to convert an STL file (3D model) into an ASCII image. Just upload your STL file and select a character set to generate the image. 10 | 11 | 12 | You can enter your own custom text to change the characters used in the ASCII image, and reset them to the default character set: ' .:-+*=%@#' 13 | 14 | 15 | Future Development: 16 | 17 | - [x] Copy ASCII image to clipboard (thanks [donno2048](https://github.com/donno2048)!) 18 | - [ ] Change lighting orientation 19 | - [ ] Add screenshot on mobile 20 | 21 | Find this project useful? You can buy me a [coffee on Ko-Fi](https://ko-fi.com/andrewsink)! 22 | -------------------------------------------------------------------------------- /bundle.5f6951b3191a2214dc44.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * html2canvas 1.4.0 3 | * Copyright (c) 2022 Niklas von Hertzen 4 | * Released under MIT License 5 | */ 6 | 7 | /*! ***************************************************************************** 8 | Copyright (c) Microsoft Corporation. 9 | 10 | Permission to use, copy, modify, and/or distribute this software for any 11 | purpose with or without fee is hereby granted. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 14 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 15 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 16 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 17 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 18 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 19 | PERFORMANCE OF THIS SOFTWARE. 20 | ***************************************************************************** */ 21 | 22 | /** 23 | * @license 24 | * Copyright 2010-2021 Three.js Authors 25 | * SPDX-License-Identifier: MIT 26 | */ 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | STL To ASCII Generator 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 33 | 34 | STL to ASCII Generator 35 | Andrew Sink - 2022 36 | Full Code Available on Github 37 | 38 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /main.css: -------------------------------------------------------------------------------- 1 | * 2 | { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | html, 7 | 8 | body 9 | 10 | 11 | { 12 | overflow: hidden; 13 | background-color: black; 14 | text-align: center; 15 | 16 | } 17 | 18 | .webgl 19 | { 20 | position: fixed; 21 | top: 0; 22 | left: 0; 23 | outline: none; 24 | } 25 | 26 | form { 27 | 28 | font-family: 'Courier New'; 29 | /* position: absolute; */ 30 | display: block; 31 | color: whitesmoke; 32 | /* z-index: 99; */ 33 | left: 0%; 34 | top: 0%; 35 | } 36 | 37 | #kofi { 38 | text-align: left; 39 | position: fixed; 40 | left: 0; 41 | bottom: 0; 42 | } 43 | 44 | input[type="button"] { 45 | color: black; 46 | margin: 6px; 47 | } 48 | 49 | 50 | 51 | 52 | /*# sourceMappingURL=main.css.map*/ -------------------------------------------------------------------------------- /main.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/style.css"],"names":[],"mappings":"AAAA;;IAEI,SAAS;IACT,UAAU;AACd;AACA;;;;;;IAMI,gBAAgB;IAChB,uBAAuB;IACvB,kBAAkB;;AAEtB;;AAEA;;IAEI,eAAe;IACf,MAAM;IACN,OAAO;IACP,aAAa;AACjB;;AAEA;;IAEI,0BAA0B;IAC1B,wBAAwB;IACxB,cAAc;IACd,iBAAiB;IACjB,iBAAiB;IACjB,QAAQ;IACR,OAAO;IACP;;AAEJ;IACI,gBAAgB;IAChB,eAAe;IACf,OAAO;IACP,SAAS;AACb;;AAEA;EACE,YAAY;EACZ,WAAW;AACb","file":"main.css","sourcesContent":["*\n{\n margin: 0;\n padding: 0;\n}\nhtml, \n\nbody\n\n\n{\n overflow: hidden;\n background-color: black;\n text-align: center; \n \n}\n\n.webgl\n{\n position: fixed;\n top: 0;\n left: 0;\n outline: none;\n}\n\nform {\n \n font-family: 'Courier New';\n /* position: absolute; */\n display: block;\n color: whitesmoke;\n /* z-index: 99; */\n left: 0%;\n top: 0%;\n }\n\n#kofi {\n text-align: left; \n position: fixed;\n left: 0;\n bottom: 0;\n}\n\ninput[type=\"button\"] {\n color: black;\n margin: 6px;\n}\n\n\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /models/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewSink/STL-to-ASCII-Generator/0fffc1fcb05f9b06676a63b791836a03216a4e23/models/.DS_Store -------------------------------------------------------------------------------- /models/test2.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrewSink/STL-to-ASCII-Generator/0fffc1fcb05f9b06676a63b791836a03216a4e23/models/test2.stl -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | STL To ASCII Generator 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | STL to ASCII Generator 59 | Andrew Sink - 2022 60 | Full Code Available on Github 62 | 63 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/script.js: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import * as THREE from 'three' 3 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' 4 | import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js' 5 | import { AsciiEffect } from 'three/examples/jsm/effects/AsciiEffect.js'; 6 | import html2canvas from 'html2canvas'; 7 | 8 | //LightMode 9 | let lightMode = true 10 | 11 | //Create a clock for rotation 12 | const clock = new THREE.Clock() 13 | 14 | // Set rotate boolean variable 15 | let rotateModel = false 16 | 17 | //Ugh, don't ask about this stuff 18 | var userUploaded = false 19 | let controls 20 | 21 | // Creates empty mesh container 22 | const myMesh = new THREE.Mesh(); 23 | 24 | // Scene 25 | const scene = new THREE.Scene() 26 | scene.background = new THREE.Color(0, 0, 0); 27 | 28 | //Lights 29 | const pointLight1 = new THREE.PointLight(0xffffff, 1, 0, 0); 30 | pointLight1.position.set(100, 100, 400); 31 | scene.add(pointLight1); 32 | 33 | const pointLight2 = new THREE.PointLight(0xffffff, .5); 34 | pointLight2.position.set(-500, 100, -400); 35 | scene.add(pointLight2); 36 | 37 | // Parameters 38 | const stlLoader = new STLLoader() 39 | 40 | //Material 41 | const material = new THREE.MeshStandardMaterial() 42 | material.flatShading = true 43 | material.side = THREE.DoubleSide; 44 | 45 | // Sizes 46 | const sizes = { 47 | width: window.innerWidth, 48 | height: window.innerHeight 49 | } 50 | 51 | // Camera 52 | const camera = new THREE.PerspectiveCamera(45, sizes.width / sizes.height, 0.1, 2000) 53 | 54 | // Renderer 55 | const renderer = new THREE.WebGLRenderer() 56 | 57 | let effect; 58 | 59 | let characters = ' .:-+*=%@#' 60 | const effectSize = { amount: .205 } 61 | let backgroundColor = 'black' 62 | let ASCIIColor = 'white' 63 | 64 | function createEffect() { 65 | effect = new AsciiEffect(renderer, characters, { invert: true, resolution: effectSize.amount }); 66 | effect.setSize(sizes.width, sizes.height); 67 | effect.domElement.style.color = ASCIIColor; 68 | effect.domElement.style.backgroundColor = backgroundColor; 69 | } 70 | 71 | createEffect() 72 | 73 | document.body.appendChild(effect.domElement) 74 | 75 | document.getElementById("ascii").style.whiteSpace = "prewrap" 76 | 77 | stlLoader.load( 78 | './models/test2.stl', 79 | function (geometry) { 80 | 81 | myMesh.material = material; 82 | myMesh.geometry = geometry; 83 | 84 | var tempGeometry = new THREE.Mesh(geometry, material) 85 | myMesh.position.copy = (tempGeometry.position) 86 | 87 | geometry.computeVertexNormals(); 88 | myMesh.geometry.center() 89 | 90 | myMesh.rotation.x = -90 * Math.PI / 180; 91 | 92 | myMesh.geometry.computeBoundingBox(); 93 | var bbox = myMesh.geometry.boundingBox; 94 | 95 | myMesh.position.y = ((bbox.max.z - bbox.min.z) / 5) 96 | 97 | camera.position.x = ((bbox.max.x * 4)); 98 | camera.position.y = ((bbox.max.y)); 99 | camera.position.z = ((bbox.max.z * 3)); 100 | 101 | scene.add(myMesh); 102 | 103 | 104 | controls = new OrbitControls(camera, effect.domElement) 105 | 106 | 107 | function tick() { 108 | if (rotateModel == true) { 109 | const elapsedTime = clock.getElapsedTime() 110 | myMesh.rotation.z = (elapsedTime) / 3 111 | render() 112 | window.requestAnimationFrame(tick) 113 | } else { 114 | render() 115 | window.requestAnimationFrame(tick) 116 | } 117 | } 118 | 119 | function render() { 120 | effect.render(scene, camera); 121 | } 122 | 123 | tick() 124 | 125 | document.getElementById('file-selector').addEventListener('change', openFile, false); 126 | 127 | 128 | function openFile(evt) { 129 | const fileObject = evt.target.files[0]; 130 | 131 | const reader = new FileReader(); 132 | reader.readAsArrayBuffer(fileObject); 133 | reader.onload = function () { 134 | if (userUploaded == false) { 135 | userUploaded = true; 136 | } 137 | const geometry = stlLoader.parse(this.result); 138 | tempGeometry = geometry; 139 | myMesh.geometry = geometry; 140 | myMesh.geometry.center() 141 | 142 | myMesh.rotation.x = -90 * Math.PI / 180; 143 | 144 | myMesh.geometry.computeBoundingBox(); 145 | var bbox = myMesh.geometry.boundingBox; 146 | 147 | // camera.position.x = ((bbox.max.x * 4)); 148 | // camera.position.y = ((bbox.max.y)); 149 | // camera.position.z = ((bbox.max.z * 3)); 150 | 151 | myMesh.position.y = ((bbox.max.z - bbox.min.z) / 6) 152 | 153 | scene.add(myMesh); 154 | }; 155 | }; 156 | } 157 | ) 158 | 159 | 160 | document.getElementById('screenshotButton').addEventListener('click', takeScreenshot); 161 | 162 | function takeScreenshot() { 163 | var container = document.body; // full page 164 | html2canvas(container).then(function (canvas) { 165 | 166 | var link = document.createElement("a"); 167 | document.body.appendChild(link); 168 | link.download = "ASCII.jpg"; 169 | link.href = canvas.toDataURL("image/jpg"); 170 | console.log(link.href); 171 | // link.target = '_blank'; 172 | link.click(); 173 | }); 174 | } 175 | 176 | document.getElementById('rotateButton').addEventListener('click', rotateMode); 177 | 178 | function rotateMode() { 179 | rotateModel = !rotateModel 180 | } 181 | 182 | document.getElementById('updateASCII').addEventListener('click', updateASCII); 183 | 184 | function updateASCII() { 185 | 186 | document.body.removeChild(effect.domElement) 187 | 188 | characters = " " + "." + document.getElementById('newASCII').value; 189 | 190 | createEffect() 191 | onWindowResize() 192 | 193 | document.body.appendChild(effect.domElement) 194 | 195 | controls = new OrbitControls(camera, effect.domElement) 196 | 197 | } 198 | 199 | document.getElementById('resetASCII').addEventListener('click', resetASCII); 200 | 201 | function resetASCII() { 202 | 203 | document.body.removeChild(effect.domElement) 204 | 205 | characters = ' .:-+*=%@#' 206 | 207 | createEffect() 208 | onWindowResize() 209 | 210 | document.body.appendChild(effect.domElement) 211 | 212 | controls = new OrbitControls(camera, effect.domElement) 213 | } 214 | 215 | document.getElementById('lightDark').addEventListener('click', lightDark); 216 | 217 | function lightDark() { 218 | lightMode = !lightMode 219 | if (lightMode === true) { 220 | document.getElementById("kofi").style.color = "white"; 221 | document.body.style.backgroundColor = 'black'; 222 | 223 | backgroundColor = 'black' 224 | ASCIIColor = 'white' 225 | 226 | effect.domElement.style.color = ASCIIColor; 227 | effect.domElement.style.backgroundColor = backgroundColor; 228 | } else { 229 | document.getElementById("kofi").style.color = "black"; 230 | document.body.style.backgroundColor = 'white'; 231 | 232 | backgroundColor = 'white' 233 | ASCIIColor = 'black' 234 | 235 | effect.domElement.style.color = ASCIIColor; 236 | effect.domElement.style.backgroundColor = backgroundColor; 237 | } 238 | } 239 | 240 | window.addEventListener('resize', onWindowResize); 241 | 242 | function onWindowResize() { 243 | camera.aspect = window.innerWidth / window.innerHeight; 244 | camera.updateProjectionMatrix(); 245 | 246 | renderer.setSize(window.innerWidth, window.innerHeight); 247 | effect.setSize(window.innerWidth, window.innerHeight); 248 | } 249 | 250 | function download(filename, text) { 251 | var element = document.createElement('a'); 252 | element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); 253 | element.setAttribute('download', filename); 254 | 255 | element.style.display = 'none'; 256 | document.body.appendChild(element); 257 | 258 | element.click(); 259 | 260 | document.body.removeChild(element); 261 | } 262 | 263 | document.getElementById("copyASCII").addEventListener("click", function () { 264 | var text = document.getElementsByTagName("table")[0].innerText 265 | var filename = "ASCII.txt"; 266 | 267 | download(filename, text); 268 | }, false); 269 | 270 | document.getElementById("clipboardASCII").addEventListener("click", function () { 271 | const textArea = document.createElement("textarea"); 272 | textArea.textContent = document.getElementsByTagName("td")[0].innerText; 273 | document.body.appendChild(textArea); 274 | textArea.select(); 275 | document.execCommand('copy'); 276 | document.body.removeChild(textArea); 277 | window.alert("ASCII copied to clipboard"); 278 | }, false); 279 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | * 2 | { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | html, 7 | 8 | body 9 | 10 | 11 | { 12 | overflow: hidden; 13 | background-color: black; 14 | text-align: center; 15 | 16 | } 17 | 18 | .webgl 19 | { 20 | position: fixed; 21 | top: 0; 22 | left: 0; 23 | outline: none; 24 | } 25 | 26 | form { 27 | 28 | font-family: 'Courier New'; 29 | /* position: absolute; */ 30 | display: block; 31 | color: whitesmoke; 32 | /* z-index: 99; */ 33 | left: 0%; 34 | top: 0%; 35 | } 36 | 37 | #kofi { 38 | text-align: left; 39 | position: fixed; 40 | left: 0; 41 | bottom: 0; 42 | } 43 | 44 | input[type="button"] { 45 | color: black; 46 | margin: 6px; 47 | } 48 | 49 | 50 | --------------------------------------------------------------------------------
STL to ASCII Generator
Andrew Sink - 2022
Full Code Available on Github