├── .gitignore ├── .gitmodules ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── anthill ├── ants-for-expressive-larger.gcode ├── ants-for-expressive-larger.js ├── ants-for-expressive.gcode └── ants-for-expressive.js ├── build.sh ├── doc ├── comms.md ├── editor.md ├── printer.md └── ui.md ├── js ├── constants │ ├── AsyncFunctionsConstants.js │ └── priority.js ├── fext.js ├── language │ ├── compile.js │ ├── liveprinter.html │ ├── liveprinter.ne │ ├── lpgrammar.js │ └── lpmode.js ├── liveprinter.comms.js ├── liveprinter.editor.js ├── liveprinter.printer.js ├── liveprinter.ui.js ├── main.js ├── parsers │ └── MarlinParsers.js └── svg │ ├── SVGReader.js │ └── svg2gcode.js ├── jsdoc.conf ├── liveprinter.sln ├── liveprinter ├── AutoDetectBaudJob.py ├── ConnectionState.py ├── LivePrinterServer.py ├── SerialDevice.py ├── UM │ ├── Decorators.py │ ├── Event.py │ ├── FlameProfiler.py │ ├── Job.py │ ├── JobQueue.py │ ├── Logger.py │ ├── OutputDevice.py │ ├── Platform.py │ ├── PluginObject.py │ ├── Signal.py │ └── __init__.py ├── __init__.py ├── anthill │ ├── anthill-2-layer.gcode │ ├── ants-for-expressive-larger.gcode │ ├── ants-for-expressive-larger.js │ ├── ants-for-expressive.gcode │ └── ants-for-expressive.js ├── avr_isp │ ├── __init__.py │ ├── chipDB.py │ ├── intelHex.py │ ├── ispBase.py │ └── stk500v2.py ├── dummyserial │ ├── __init__.py │ ├── classes.py │ ├── constants.py │ └── exceptions.py ├── printerreponse.py ├── static │ ├── examples │ │ ├── airprinting.js │ │ ├── algorave.js │ │ ├── circle.js │ │ ├── circle.py │ │ ├── corbusier.js │ │ ├── cyber-ants.js │ │ ├── default.js │ │ ├── hilbert.js │ │ ├── lsystems.js │ │ ├── lsystems2.js │ │ ├── manualhilbert.js │ │ ├── maryhadalittlelamb.js │ │ ├── maryhadalittlelamb.py │ │ ├── minigrammar.js │ │ ├── musicalevents.js │ │ ├── pythontest.py │ │ ├── sending-gcode.js │ │ └── using-functions.js │ ├── imgs │ │ ├── cat.svg │ │ ├── dreidel.svg │ │ ├── menorah.svg │ │ ├── menorah2.svg │ │ ├── penguin-simp.svg │ │ ├── penguin.svg │ │ ├── sailor-face.svg │ │ └── whitebolt.svg │ ├── lib │ │ ├── gridlib.js │ │ ├── main.js │ │ └── main.js.map │ ├── misc │ │ └── lp_ws.html │ ├── style │ │ ├── bootstrap.min.css │ │ ├── codemirror_all.css │ │ └── liveprinter.css │ └── util │ │ └── svgrenderer.html ├── templates │ ├── index.html │ ├── info.html │ ├── test-bottleneck.html │ └── test.html └── tornado_test.py ├── misc ├── LivePrinter4Max │ ├── LivePrinter4Max.maxproj │ ├── code │ │ └── liveprinter.js │ └── patchers │ │ └── LivePrinter.maxpat ├── WebsocketsMaxMSP │ ├── lp_ws.html │ ├── printerSpatialized.maxpat │ └── websockets.maxpat ├── printerSpatialized.maxpat └── websockets.maxpat ├── package.json ├── profile_stats.py ├── reference ├── Quickstart.md ├── Retraction_heuristics.md ├── extrudeto.dot ├── extrudeto.png ├── extrudeto.svg ├── highlevel.dot ├── highlevel.svg ├── img │ ├── LivePrinter-Architecture.png │ └── LivePrinter_overview_diagram.jpg ├── print.dot ├── print.svg ├── retraction.dot ├── retraction.svg ├── unretraction.dot ├── unretraction.svg ├── use_cases.dot └── use_cases.svg ├── test ├── test.html └── test.js └── testing ├── .gitignore ├── 8x8anthill.gcode.txt ├── antgrid-01_2019-03-13.gcode.txt ├── antgrid-02-16x16_2019-03-13.gcode.txt ├── antgrid-02-8x8_2019-03-13.gcode.txt ├── antgrid-2019-03-13-tests.js ├── antgrid-6x6_2019.gcode.txt ├── antgrid_12x12_layered.gcode ├── antgrid_12x12_layered.js ├── antgrid_12x24_layered.gcode ├── antgrid_12x24_layered.js ├── antgrid_16x16_sm.gcode ├── antgrid_16x16_sm.js ├── antgrid_inc.js ├── async_script_attach.html ├── cat.js ├── dreidel.js ├── gcodegrammar.js ├── grammar_test.js ├── hilbert_livecoding ├── hilbert-experiment-01.js └── hilbert-experiment-02.js ├── iterator-apr-14-1.js ├── iterator-closed-may19-hex.js ├── iterator-closed-symmetry-M-creatlity-big.js ├── iterator-closed-symmetry-M-creatlity.js ├── iterator-closed-symmetry-M.js ├── iterator-closed-symmetry-W+may13.js ├── iterator-closed-symmetry.js ├── iteratorgrid-ambient-long.js ├── iteratorgrid-smooth.js ├── iteratorgrid-two-whites.js ├── iteratorgrid-wavy.js ├── iteratorgrid.js ├── jams └── task-jam-aug-7-2019.js ├── menorah.js ├── penguin.js ├── seashell-warp-01.js ├── spirals.js ├── stresstest.js ├── test_gcode.txt └── warp-tests-initial.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | ._* 3 | 4 | # Visual Studio 2015 cache directory 5 | /.vs/ 6 | .venv 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | venv/ 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | #lib/ 25 | lib64/ 26 | lib64 27 | parts/ 28 | sdist/ 29 | var/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | bin/ 34 | lib/python* 35 | pyvenv.cfg 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *,cover 56 | liveprinter/static/test/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # PyBuilder 63 | target/ 64 | docs/ 65 | node_modules/ 66 | 67 | .parcel-cache/ 68 | 69 | # npm/nodejs 70 | 71 | *.lock 72 | 73 | # misc 74 | goprosettings_donotcommit.txt 75 | /liveprinter/static/imgs/simple-cloud.svg 76 | /liveprinter/static/imgs/diskette.svg 77 | /liveprinter/static/imgs/9D61BE7C.jpg 78 | /testing/saved 79 | .vscode/* 80 | package-lock.json 81 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "liveprinter/static/misc/the-cyber-anthill"] 2 | path = liveprinter/static/misc/the-cyber-anthill 3 | url = https://github.com/pixelpusher/the-cyber-anthill 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Current File", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "integratedTerminal", 10 | "justMyCode": true 11 | }, 12 | { 13 | "name": "Python: LivePrinter server", 14 | "type": "python", 15 | "request": "launch", 16 | "program": "${workspaceFolder}/liveprinter/LivePrinterServer.py", 17 | "console": "integratedTerminal" 18 | }, 19 | { 20 | "name":"Profile LivePrinter server", 21 | "type": "python", 22 | "request": "launch", 23 | "program": "python3 -m cProfile -o ${workspaceFolder}/server_profile ${workspaceFolder}/liveprinter/LivePrinterServer.py", 24 | "console": "integratedTerminal" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /anthill/ants-for-expressive-larger.js: -------------------------------------------------------------------------------- 1 | attachScript("the-cyber-anthill/ant.js"); // run this line first by itself 2 | attachScript("the-cyber-anthill/grid.js"); // run this line second by itself 3 | attachScript("the-cyber-anthill/antgrid-api.js"); // run this line 3rd by itself 4 | 5 | lp.start(190); // start up printer, home axes, set print head temperature and bed temp 6 | 7 | lp.sync() 8 | 9 | lp.extrude({ e: 20, speed: 12 }) 10 | 11 | lp.temp(195); 12 | 13 | lp.bed(55) 14 | // z = 0.8 15 | // x = 218, 25 16 | // y = 8, 170 17 | lp.down(0.1).go() 18 | 19 | lp.maxy = 173; 20 | lp.maxx = 218; 21 | lp.miny = 8; 22 | lp.maxy = lp.miny + 162; 23 | lp.minz = 0.8; 24 | lp.minx = 25; 25 | 26 | lp.moveto({ x: lp.minx + lp.cx, y: lp.maxy }) 27 | 28 | lp.moveto({ x: lp.maxx - 5, y: lp.miny + 8 + 165 }) 29 | 30 | lp.speed(50); 31 | 32 | lp.sync() 33 | 34 | const minZ = 4; 35 | lp.moveto({ z: minZ }) 36 | 37 | lp.down(0.1).go(); 38 | lp.retract() 39 | lp.extrude({ e: 30, speed: 12 }) 40 | 41 | lp.temp(205) 42 | 43 | lp.retract(9) 44 | 45 | 46 | lp.extrude({ e: 9, speed: 20 }) 47 | 48 | lp.downto(5).go() 49 | 50 | lp.fan(0) 51 | 52 | lp.bed(40); 53 | 54 | lp.upto(3.2).go() 55 | 56 | lp.moveto({ x: lp.cx, y: lp.cy }) 57 | 58 | lp.turnto(180).dist(20).go() 59 | 60 | lp.dist(10).go() 61 | 62 | lp.moveto({ x: 171.5, y: 12, z: 3.6 }) 63 | // 171.5, 12, 3.6 64 | 65 | //x 30, y 188 min, 220 max -- overhang is 21mm 66 | lp.minx = 41; 67 | lp.maxy = 188; 68 | lp.miny = 15; 69 | loginfo(lp.cx) 70 | 71 | lp.moveto({ x: lp.cx + lp.minx }) 72 | 73 | lp.moveto({ x: lp.maxx }) 74 | 75 | lp.speed(20); // set print speed to a bit slower, 35 for higher layers 76 | 77 | lp.lh(0.3); //.2, 0.15, .1 78 | 79 | lp.fan(80); 80 | 81 | // 10,16,22 82 | 83 | let dims = 24; // dimensions of underlying grid - careful not to make too big! 84 | let grid = new Grid(dims, dims * 2); // grid for ants to fill up 85 | let ant = null; // our current ant walker 86 | let paths = []; // array of paths walked 87 | let layers = 6; // layers per ant trail 88 | let iteration = 1 // how many time's we've run this before (start at 0) 89 | const w = lp.maxx - lp.minx; 90 | const h = lp.maxy - lp.miny; 91 | const sectW = 3 * w / 4; 92 | const sectH = 3 * h / 4; 93 | const gridH = 3 * h / 4; 94 | 95 | 96 | const gridW = sectW; 97 | const offsetx = (w - sectW) / 2; // 1/3 of 1/4 98 | const offsety = (h - sectH) / 2; 99 | 100 | const index = 0; //0,1,2 for all 3 101 | const startx = lp.minx + offsetx + index * (2 * offsetx + gridW); 102 | const starty = lp.miny + offsety; 103 | const z = lp.minz - lp.layerHeight / 2; // adjust for first layer 104 | 105 | lp.extraUnretract = 0.2; 106 | lp.sendFirmwareRetractSettings(); 107 | 108 | while (ant = createAnt(grid)) { 109 | if (ant) { 110 | ant.maxLife = 24; 111 | while (ant.alive) { 112 | move(ant, grid); 113 | } 114 | paths.push(ant.path); 115 | } 116 | } 117 | 118 | //lp.unretract(); 119 | //print all paths 120 | lp.printPaths({ paths: paths, x: startx, y: starty, z: z + lp.layerHeight * layers * iteration, w: gridW, h: gridH, passes: layers }); 121 | 122 | 123 | lp.up(100).go(); // move up at end 124 | 125 | loginfo(lp.layerHeight) 126 | 127 | /////////////////// next versions 128 | 129 | 130 | lp.speed(35); // set print speed to a bit slower, 35 for higher layers 131 | 132 | lp.lh(0.3); //.2, 0.15, .1 133 | 134 | lp.fan(80); 135 | 136 | // 10,16,22 137 | 138 | let dims = 12; // dimensions of underlying grid - careful not to make too big! 139 | let grid = new Grid(dims, dims * 2); // grid for ants to fill up 140 | let ant = null; // our current ant walker 141 | let paths = []; // array of paths walked 142 | let layers = 6; // layers per ant trail 143 | let iteration = 5; // how many time's we've run this before (start at 0) 144 | const w = lp.maxx - lp.minx; 145 | const h = lp.maxy - lp.miny; 146 | const sectW = 3 * w / 4; 147 | const sectH = 3 * h / 4; 148 | const gridH = 3 * h / 4; 149 | 150 | 151 | const gridW = sectW; 152 | const offsetx = (w - sectW) / 2; // 1/3 of 1/4 153 | const offsety = (h - sectH) / 2; 154 | 155 | const index = 0; //0,1,2 for all 3 156 | const startx = lp.minx + offsetx + index * (2 * offsetx + gridW); 157 | const starty = lp.miny + offsety; 158 | const z = lp.minz; // adjust for first layer 159 | 160 | lp.extraUnretract = 0.2; 161 | lp.sendFirmwareRetractSettings(); 162 | 163 | while (ant = createAnt(grid)) { 164 | if (ant) { 165 | ant.maxLife = dims; 166 | while (ant.alive) { 167 | move(ant, grid); 168 | } 169 | paths.push(ant.path); 170 | } 171 | } 172 | 173 | //lp.unretract(); 174 | //print all paths 175 | lp.printPaths({ paths: paths, x: startx, y: starty, z: z + lp.layerHeight * layers * iteration, w: gridW, h: gridH, passes: layers }); 176 | 177 | 178 | lp.up(100).go(); // move up at end -------------------------------------------------------------------------------- /anthill/ants-for-expressive.js: -------------------------------------------------------------------------------- 1 | // The CyberAntHill 2 | // By Evan Raskob < info@pixelist.info> 3 | // 4 | // Copyright 2019, all rights reserved 5 | 6 | attachScript("the-cyber-anthill/ant.js"); // run this line first by itself 7 | attachScript("the-cyber-anthill/grid.js"); // run this line second by itself 8 | attachScript("the-cyber-anthill/antgrid-api.js"); // run this line 3rd by itself 9 | 10 | lp.start(190); // start up printer, home axes, set print head temperature and bed temp 11 | 12 | lp.moveto({ x: lp.cx + 10, y: lp.miny + 32 }) 13 | lp.speed(50); 14 | 15 | lp.sync() 16 | 17 | const minZ = 4; 18 | lp.moveto({ z: minZ }) 19 | 20 | lp.down(0.1).go(); 21 | lp.extrude({ e: 20, speed: 8 }) 22 | 23 | lp.retract(9) 24 | 25 | 26 | lp.extrude({ e: 9, speed: 20 }) 27 | 28 | lp.downto(5).go() 29 | 30 | lp.fan(0) 31 | 32 | lp.bed(40); 33 | 34 | lp.upto(3.2).go() 35 | 36 | lp.moveto({ x: lp.cx, y: lp.cy }) 37 | 38 | lp.turnto(180).dist(20).go() 39 | 40 | lp.dist(10).go() 41 | 42 | lp.moveto({ x: 171.5, y: 12, z: 3.6 }) 43 | // 171.5, 12, 3.6 44 | 45 | //x 30, y 188 min, 220 max -- overhang is 21mm 46 | lp.minx = 41; 47 | lp.maxy = 188; 48 | lp.miny = 15; 49 | loginfo(lp.cx) 50 | 51 | lp.moveto({ x: lp.cx + lp.minx }) 52 | 53 | lp.moveto({ x: lp.maxx }) 54 | 55 | lp.speed(14); // set print speed to a bit slower, 35 for higher layers 56 | 57 | lp.lh(0.1); //.2, 0.15, .1 58 | 59 | lp.fan(60) 60 | 61 | // 10,16,22 62 | 63 | let dims = 22; // dimensions of underlying grid - careful not to make too big! 64 | let grid = new Grid(dims, dims * 2); // grid for ants to fill up 65 | let ant = null; // our current ant walker 66 | let paths = []; // array of paths walked 67 | let layers = 3; // layers per ant trail 68 | let iteration = 0; // how many time's we've run this before (start at 0) 69 | const w = lp.maxx - lp.minx; 70 | const h = lp.maxy - lp.miny; 71 | const sectW = w / 3; 72 | const gridH = 2 * h / 3; 73 | 74 | const gridW = sectW / 2; 75 | const offsetx = sectW / 4; // 1/3 of 1/4 76 | const offsety = h / 6; 77 | 78 | const index = 2; //0,1,2 for all 3 79 | const startx = lp.minx + offsetx + index * (2 * offsetx + gridW); 80 | const starty = lp.miny + offsety; 81 | const z = 3 + lp.layerHeight; 82 | 83 | while (ant = createAnt(grid)) { 84 | if (ant) { 85 | ant.maxLife = 36; 86 | while (ant.alive) { 87 | move(ant, grid); 88 | } 89 | paths.push(ant.path); 90 | } 91 | } 92 | 93 | //lp.unretract(); 94 | //print all paths 95 | lp.printPaths({ paths: paths, x: startx, y: starty, z: z + lp.layerHeight * layers * iteration, w: gridW, h: gridH, passes: layers }); 96 | 97 | 98 | lp.up(100).go(); // move up at end 99 | 100 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | npm install 4 | npm run build 5 | rm -rf js 6 | rm -rf anthill 7 | rm *.json 8 | rm *.sln 9 | rm -rf testing 10 | rm -rf reference 11 | rm -rf liveprinter/anthill 12 | rm -rf liveprinter/avr_isp 13 | rm -rf node_modules 14 | 15 | cd .. 16 | zip -9 -o -v -r liveprinter-1.0.x.zip liveprinter/ -------------------------------------------------------------------------------- /doc/editor.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Editor 4 | Code editing functionality for LivePrinter. 5 | 6 | 7 | * [Editor](#module_Editor) 8 | * [~$](#module_Editor..$) 9 | * [~recordCode(gcode)](#module_Editor..recordCode) 10 | * [~recordGCode(editor, gcode)](#module_Editor..recordGCode) 11 | * [~runCode(editor, callback)](#module_Editor..runCode) ⇒ Boolean 12 | * [~storageAvailable(type)](#module_Editor..storageAvailable) ⇒ Boolean 13 | * [~init()](#module_Editor..init) ⇒ PromiseFulfilledResult 14 | 15 | 16 | 17 | ### Editor~$ 18 | JQuery reference 19 | 20 | **Kind**: inner constant of [Editor](#module_Editor) 21 | 22 | 23 | ### Editor~recordCode(gcode) 24 | Log code log to history editor window of choice 25 | 26 | **Kind**: inner method of [Editor](#module_Editor) 27 | 28 | | Param | Type | 29 | | --- | --- | 30 | | gcode | String | 31 | 32 | 33 | 34 | ### Editor~recordGCode(editor, gcode) 35 | Log GCode log to history window of choice 36 | 37 | **Kind**: inner method of [Editor](#module_Editor) 38 | 39 | | Param | Type | 40 | | --- | --- | 41 | | editor | Editor | 42 | | gcode | Array \| String | 43 | 44 | 45 | 46 | ### Editor~runCode(editor, callback) ⇒ Boolean 47 | This function takes the highlighted "local" code from the editor and runs the compiling and error-checking functions. 48 | 49 | **Kind**: inner method of [Editor](#module_Editor) 50 | **Returns**: Boolean - success 51 | 52 | | Param | Type | 53 | | --- | --- | 54 | | editor | Editor | 55 | | callback | function | 56 | 57 | 58 | 59 | ### Editor~storageAvailable(type) ⇒ Boolean 60 | Local Storage for saving/loading documents. 61 | Default behaviour is loading the last edited session. 62 | 63 | **Kind**: inner method of [Editor](#module_Editor) 64 | **Returns**: Boolean - true or false, if storage is available 65 | **See**: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API 66 | 67 | | Param | Type | Description | 68 | | --- | --- | --- | 69 | | type | String | type (global key in window object) for storage object | 70 | 71 | 72 | 73 | ### Editor~init() ⇒ PromiseFulfilledResult 74 | Initialise editors and events, etc. 75 | 76 | **Kind**: inner method of [Editor](#module_Editor) 77 | -------------------------------------------------------------------------------- /doc/ui.md: -------------------------------------------------------------------------------- 1 | ## Functions 2 | 3 |
4 |
clearError()
5 |

Clear HTML of all displayed code errors

6 |
7 |
requestRepeat(jsonObject, activeElem, delay, func, priority)Object
8 |

RequestRepeat: 9 | Utility to send a JSON-RPC request repeatedly whilst a "button" is pressed (i.e. it has an "active" CSS class)

10 |
11 |
tempHandler(data)Boolean
12 |

Parse temperature response from printer firmware (Marlin)

13 |
14 |
attachScript(url)
15 |

Attach an external script (and remove it quickly). Useful for adding outside libraries.

16 |
17 |
init(_scheduler)
18 |
19 |
20 | 21 | 22 | 23 | ## clearError() 24 | Clear HTML of all displayed code errors 25 | 26 | **Kind**: global function 27 | 28 | 29 | ## requestRepeat(jsonObject, activeElem, delay, func, priority) ⇒ Object 30 | RequestRepeat: Utility to send a JSON-RPC request repeatedly whilst a "button" is pressed (i.e. it has an "active" CSS class) 31 | 32 | **Kind**: global function 33 | **Returns**: Object - JsonRPC response object 34 | 35 | | Param | Type | Description | 36 | | --- | --- | --- | 37 | | jsonObject | Object | JSON-RPC to repeat | 38 | | activeElem | JQuery | JQuery element to check for active class (will keep running whist is has an "active" class) | 39 | | delay | Integer | Delay between repeated successful calls in millis | 40 | | func | function | Callback to run on result | 41 | | priority | Integer | Priority of request in queue (0-9 where 0 is highest) | 42 | 43 | 44 | 45 | ## tempHandler(data) ⇒ Boolean 46 | Parse temperature response from printer firmware (Marlin) 47 | 48 | **Kind**: global function 49 | **Returns**: Boolean - true or false if parsed or not 50 | 51 | | Param | Type | Description | 52 | | --- | --- | --- | 53 | | data | String | serial response from printer firmware (Marlin) | 54 | 55 | 56 | 57 | ## attachScript(url) 58 | Attach an external script (and remove it quickly). Useful for adding outside libraries. 59 | 60 | **Kind**: global function 61 | 62 | | Param | Type | Description | 63 | | --- | --- | --- | 64 | | url | String | Url of script (or name, if in the static/misc folder) | 65 | 66 | 67 | 68 | ## init(_scheduler) 69 | **Kind**: global function 70 | 71 | | Param | Type | Description | 72 | | --- | --- | --- | 73 | | _scheduler | Scheduler | Scheduler object to use for tasks, repeating events, etc. If undefined, will crearte new one. | 74 | 75 | -------------------------------------------------------------------------------- /js/constants/AsyncFunctionsConstants.js: -------------------------------------------------------------------------------- 1 | // TODO: automate this file! Hard to maintain 2 | 3 | // names of all async functions in API for replacement in minigrammar later on in RunCode 4 | const asyncFunctionsInAPI = "stop|prime|mov2|ext|gcodeEvent|gcode|errorEvent|retractspeed|sendFirmwareRetractSettings|retract|unretract|start|temp|bed|fan|drawtime|draw|up|drawup|dup|upto|downto|down|drawdown|dd|travel|traveltime|fwretract|polygon|rect|extrudeto|sendExtrusionGCode|sendArcExtrusionGCode|extrude|move|moveto|drawfill|sync|fill|wait|pause|resume|printPaths|printPathsThick|_extrude".split('|'); 5 | 6 | // regular expression used to highlight async syntax in codemirror 7 | const asyncFunctionsInAPICMRegex = /^(stop|prime|mov2|ext|gcodeEvent|gcode|errorEvent|retractspeed|sendFirmwareRetractSettings|retract|unretract|start|temp|bed|fan|drawtime|draw|up|drawup|dup|upto|downto|down|drawdown|dd|travel|traveltime|fwretract|polygon|rect|extrudeto|sendExtrusionGCode|sendArcExtrusionGCode|extrude|move|moveto|drawfill|sync|fill|wait|pause|resume|printPaths|printPathsThick|_extrude)[^a-zA-Z0-9\_]/; 8 | 9 | const asyncFunctionsInAPIRegex = /^stop|prime|mov2|ext|gcodeEvent|gcode|errorEvent|retractspeed|sendFirmwareRetractSettings|retract|unretract|start|temp|bed|fan|drawtime|draw|up|drawup|dup|upto|downto|down|drawdown|dd|travel|traveltime|fwretract|polygon|rect|extrudeto|sendExtrusionGCode|sendArcExtrusionGCode|extrude|move|moveto|drawfill|sync|fill|wait|pause|resume|printPaths|printPathsThick|_extrude$/; 10 | 11 | module.exports.asyncFunctionsInAPI = asyncFunctionsInAPI; 12 | module.exports.asyncFunctionsInAPIRegex = asyncFunctionsInAPIRegex; 13 | module.exports.asyncFunctionsInAPICMRegex = asyncFunctionsInAPICMRegex; -------------------------------------------------------------------------------- /js/constants/priority.js: -------------------------------------------------------------------------------- 1 | 2 | // priority of sent commands in async queue 3 | exports.highestPriority = 2; 4 | exports.highPriority = 3; 5 | exports.normalPriority = 4; 6 | exports.lowPriority = 5; 7 | exports.lowestPriority = 6; 8 | 9 | 10 | -------------------------------------------------------------------------------- /js/language/compile.js: -------------------------------------------------------------------------------- 1 | import { Logger } from 'liveprinter-utils'; 2 | 3 | const nearley = require('nearley'); // grammar parser 4 | const grammar = require('./lpgrammar'); 5 | 6 | // in code, find blocks inside ## ## and feed to grammar 7 | 8 | const grammarBlockRegex = /(?:\n|\t|\s)*\#{2}\s*((?:.|\n|\t|\s)*)\#{2}/g; 9 | 10 | const grammarOneLineRegex = /(?:\n|\t|\s)*\#\s*((?:[^\n])*)/g; 11 | 12 | function compile(code) { 13 | 14 | // Create a Parser object from our grammar. 15 | // global var grammar created by /static/lib/nearley/lpgrammar.js 16 | // global var nearley created by /static/lib/nearley/nearley.js 17 | 18 | 19 | 20 | // 21 | // try block element grammar replacement FIRST because one-liner matches part 22 | // 23 | //code = code.replace(/([\r\n]+)/gm, "|").substring(^\s*(\|), "").replace(grammarFinderRegex, (match, p1) => { 24 | // TODO: fix multiline (split?) 25 | 26 | const blockparser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar)); // parser for entire block 27 | 28 | code = code.replace(grammarBlockRegex, (match, p1) => { 29 | Logger.debug("Match: " + p1); 30 | 31 | let result = ""; 32 | let lines = p1.split(/[\r\n]/); 33 | 34 | lines.map((line) => { 35 | // get ride of remaining line breaks and leading spaces 36 | line = line.replace(/([\r\n]+)/gm, "").replace(/(^[\s]+)/, ""); 37 | if (line.length === 0) { 38 | return; 39 | } else { 40 | // errors bubble up to calling function 41 | blockparser.feed(line + '\n'); // EOL terminates command 42 | } 43 | }); // end compiling line by line 44 | 45 | result += blockparser.results[0]; 46 | 47 | return ' ' + result + "\n"; // need leading space 48 | }); 49 | 50 | Logger.info("code AFTER block-grammar processing -------------------------------"); 51 | Logger.info(code); 52 | Logger.info("========================= -------------------------------"); 53 | 54 | 55 | // 56 | // try one liner grammar replacement 57 | // 58 | let grammarFound = false; // if this line contains the lp grammar 59 | // note: p3 is the optional trailing # that can be ignored 60 | code = code.replace(grammarOneLineRegex, (match, p1, p2) => { 61 | const lineparser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar)); 62 | 63 | Logger.debug("!!!"+match+"!!!"); 64 | Logger.debug("!!!"+p1+"!!!"); 65 | grammarFound = true; // found! 66 | let result = ""; 67 | let fail = false; // if not successful 68 | // .replace(/(^[\s]+)/, "") 69 | let line = p1.replace(/([\r\n]+)/gm, "").replace(/([\s]+)$/, ""); 70 | 71 | //Logger.debug("LINE::" + line + "::LINE"); 72 | if (line) { 73 | lineparser.feed(line + '\n'); 74 | //result += "/*ERROR IN PARSE: [" + fail + "] + offending code: [" + line + "]" + "*/\n"; 75 | 76 | result = lineparser.results[0]; 77 | //Logger.debug(result); 78 | } 79 | return ' ' + result; 80 | }); 81 | 82 | Logger.debug("code AFTER one-line-grammar processing -------------------------------"); 83 | Logger.debug(code); 84 | Logger.debug("========================= -------------------------------"); 85 | 86 | return code; 87 | } 88 | 89 | module.exports = compile; 90 | -------------------------------------------------------------------------------- /js/language/liveprinter.ne: -------------------------------------------------------------------------------- 1 | # transpiles mini language into JavaScript 2 | # compile with "nearleyc liveprinter.ne -o lpgrammar.js" 3 | # nearley 2.16.0 https://nearley.js.org 4 | # transpiles mini language into JavaScript 5 | 6 | # A complete line (terminated by EOL) 7 | 8 | Main -> Chain EOL:+ Space Main {% d => [d[0]].concat(d[3]).join(";") %} 9 | | Chain Space EOL:? {% d => d[0] + ';' %} 10 | 11 | Chain -> FunctionStatement Space PIPE Space Chain {% d => [d[0]].concat(d[4]).join(";") %} 12 | | FunctionStatement Space PIPE:? {% d => d[0] %} 13 | 14 | #Statement -> (Number {% id %} 15 | # | ObjArg {% id %} 16 | # | FunctionName {% id %} 17 | # | FunctionStatement {% id %} 18 | # ) {% id %} 19 | 20 | FunctionStatement -> (FunctionName {% 21 | ([name]) => { 22 | const asyncFunctionsInAPIRegex = /^(stop|prime|mov2|ext|gcodeEvent|gcode|errorEvent|retractspeed|sendFirmwareRetractSettings|retract|unretract|start|temp|bed|fan|drawtime|draw|up|drawup|dup|upto|downto|down|drawdown|dd|travel|traveltime|fwretract|polygon|rect|extrudeto|sendExtrusionGCode|sendArcExtrusionGCode|extrude|move|moveto|drawfill|sync|fill|wait|pause|resume|printPaths|printPathsThick|_extrude)$/; 23 | 24 | const asyncFuncCall = asyncFunctionsInAPIRegex.test(name); 25 | 26 | if (asyncFuncCall) name = "await lp." + name; 27 | else name = "lp." + name; 28 | return name += "("; 29 | } 30 | %}) ( Spaces 31 | 32 | ( FunctionName Space "(" Space AnyArgs:? Space ")" {% ([fn,s1,p1,s2,args,s3,p2]) => fn + p1 + (Array.isArray(args) ? args.join(',') : args) + p2 %} 33 | | ObjArgs 34 | {% 35 | function ([args]) { 36 | let fstr = "{"; 37 | if (typeof args !== "string" ) { 38 | for (let i=0; i { let str=""; for (let dd of d) { if (dd) str+=dd}; return str; } %}):? {% d => d.join('') + ")" %} 65 | # 66 | FunctionName -> PlainVariable | ObjectVariable {% id %} 67 | 68 | AnyArgs -> AnyArg Spaces AnyArgs {% ([arg, ws, args]) => [arg].concat(args) %} 69 | | AnyArg {% id %} 70 | 71 | ObjArgs -> ObjArg Spaces ObjArgs {% ([arg, ws, args]) => [arg].concat(args) %} 72 | | ObjArg {% id %} 73 | 74 | # object arguments inside the curly braces, like { x:32 } 75 | ObjArg -> Letter:+ Space ArgSeparator Space AnyArg {% ([argname, ws1, separator, ws2, argVal]) => argname.join('') + separator + argVal %} 76 | 77 | # valid arguments for functions 78 | AnyArg -> AnyVar 79 | | ParenthesisStatement # these are for passing js directly in brackets 80 | | MathFuncs 81 | 82 | ParenthesisStatement -> "(" Space BasicStatement Space ")" {% ([lparen, sp, statement, sp2, rparen]) => lparen+statement+rparen %} 83 | # "(" (AnyValidCharacter | DOT | MathOps | [()\s]):+ ")" {% ([lparen, statement, rparen]) => statement.join('') %} 84 | 85 | BasicStatement -> AnyVar | MathFuncs 86 | 87 | MathFuncs -> MathFunc Space MathFuncs {% ([arg, ws, args]) => [arg].concat(args).join('') %} 88 | | MathFunc {% id %} 89 | 90 | # math functions 91 | MathFunc -> AnyVar:? Space MathOps Space AnyVar {% ([var1,sp1,op,sp2,var2]) => (var1 ? var1 : "")+op+var2 %} 92 | #{% ([var1,sp1,op,sp2,var2]) => (var1 ? var1 : "") + op + var2 } %} 93 | 94 | # valid variable types for math ops etc 95 | AnyVar -> Number # int or float 96 | | PlainVariable # named variable 97 | | ObjectVariable # something.something 98 | | StringLiteral 99 | | ParenthesisStatement 100 | 101 | ObjectVariable -> PlainVariable DOT PlainVariable {% ([pv1, dot, pv2])=> pv1 + dot + pv2 %} 102 | 103 | PlainVariable -> CharOrLetter AnyValidCharacter:* {% ([first, second])=> first + second.join('') %} 104 | 105 | StringLiteral -> QUOTE [^|]:* QUOTE {% ([lquote, statement, rquote]) => lquote + statement.join('') + rquote %} 106 | 107 | Number -> Integer {% id %} 108 | | Float {% id %} 109 | 110 | Float -> Integer "." [0-9]:+ {% ([num1, dot, num2]) => num1 + dot + num2.join('') %} 111 | 112 | Integer -> "-":? Zero {% ([sign, num1]) => (sign ? "-" : "") + num1 %} | 113 | "-":? NonzeroNumber Digit:* {% ([sign, num1, num2]) => (sign ? "-" : "") + num1 + num2.join('') %} 114 | #{% d => ({ d:d[0] + d[1].join(''), v: parseInt(d[0] + d[1].join('')) }) %} 115 | 116 | MathOps -> [*+-/] 117 | 118 | ArgSeparator -> ":" 119 | 120 | Zero -> "0" 121 | 122 | AnyValidCharacter -> Letter | UsableCharacter | Digit 123 | #Letter {% id %} | Digit {% id %} | UsableCharacters {% id %} 124 | 125 | CharOrLetter -> UsableCharacter | Letter 126 | 127 | UsableCharacter -> [\$\£\&\^\*\_\#] 128 | 129 | Letter -> [a-zA-Z] 130 | 131 | Digit -> [0-9] 132 | 133 | NonzeroNumber -> [1-9] 134 | 135 | ObjectLeftBrace -> "{" 136 | 137 | ObjectRightBrace -> "}" 138 | 139 | EOLPIPE -> EOL | PIPE {% function(d) {return null } %} 140 | 141 | PIPE -> "|" 142 | 143 | DOT -> "." 144 | 145 | QUOTE -> "\"" | "'" 146 | 147 | # Whitespace. The important thing here is that the postprocessor 148 | # is a null-returning function. This is a memory efficiency trick. 149 | _ -> [\s]:* {% function(d) {return null } %} 150 | 151 | __ -> [\s]:+ {% function(d) {return null } %} 152 | 153 | EOL -> [\r\n] {% function(d) {return null } %} 154 | 155 | Space -> [ ]:* {% function(d) {return null } %} 156 | 157 | Spaces -> [ ]:+ {% function(d) {return null } %} -------------------------------------------------------------------------------- /js/language/lpmode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines new LivePrinter editor linting mode 3 | */ 4 | 5 | const AsyncFunctionsConstants = require('../constants/AsyncFunctionsConstants'); 6 | 7 | const CodeMirror = require('codemirror'); 8 | 9 | 10 | CodeMirror.defineMode("lp", function (config, parserConfig) { 11 | const liveprinterOverlay = { 12 | token: function (stream, state) { 13 | let ch = ""; 14 | 15 | if (!stream.eol()) { 16 | let matches = stream.match(AsyncFunctionsConstants.asyncFunctionsInAPICMRegex, false); 17 | if (matches) { 18 | //Logger.log("MATCH::**" + matches[1] + "**"); 19 | let i = matches[1].length; 20 | while (i--) stream.eat(() => true); 21 | return "lp"; 22 | } 23 | } 24 | stream.eatSpace(); 25 | if (stream.match(AsyncFunctionsConstants.asyncFunctionsInAPICMRegex, false)) return null; 26 | while (ch = stream.eat(/[^\s]/)) { 27 | if (ch === ".") break; 28 | }; 29 | return null; 30 | } 31 | }; 32 | return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || "javascript"), liveprinterOverlay); 33 | }); -------------------------------------------------------------------------------- /js/parsers/MarlinParsers.js: -------------------------------------------------------------------------------- 1 | import { decimalPlaces } from "liveprinter-utils"; 2 | 3 | // from https://github.com/cncjs/cncjs/blob/30c294f0ffb304441304aaa6b75a728f3a096827/src/server/controllers/Marlin/MarlinLineParserResultPosition.js 4 | 5 | export class MarlinLineParserResultPosition { 6 | // X:0.00 Y:0.00 Z:0.00 E:0.00 Count X:0 Y:0 Z:0 7 | static parse(line) { 8 | const r = line.match(/^(?:(?:X|Y|Z|E):[0-9\.\-]+\s*)+/i); 9 | if (!r) { 10 | return null; 11 | } 12 | 13 | const payload = { 14 | pos: {} 15 | }; 16 | const pattern = /((X|Y|Z|E):[0-9\.\-]+)/gi; 17 | const params = r[0].match(pattern); 18 | 19 | for (let param of params) { 20 | const nv = param.match(/([a-z]+):([0-9\.\-]+)/i); 21 | if (nv) { 22 | const axis = nv[1].toLowerCase(); 23 | const pos = nv[2]; 24 | const digits = decimalPlaces(pos); 25 | payload.pos[axis] = Number(pos).toFixed(digits); 26 | } 27 | } 28 | 29 | return { 30 | type: MarlinLineParserResultPosition, 31 | payload: payload 32 | }; 33 | } 34 | } 35 | 36 | // from https://github.com/cncjs/cncjs/blob/f33e6464e93de65b53aa4160676b8ee51ed4dcc6/src/server/controllers/Marlin/MarlinLineParserResultTemperature.js 37 | export class MarlinLineParserResultTemperature { 38 | // ok T:0 39 | // ok T:293.0 /0.0 B:25.9 /0.0 @:0 B@:0 40 | // ok T:293.0 /0.0 B:25.9 /0.0 T0:293.0 /0.0 T1:100.0 /0.0 @:0 B@:0 @0:0 @1:0 41 | // ok T:293.0 /0.0 (0.0) B:25.9 /0.0 T0:293.0 /0.0 (0.0) T1:100.0 /0.0 (0.0) @:0 B@:0 @0:0 @1:0 42 | // ok T:293.0 /0.0 (0.0) B:25.9 /0.0 T0:293.0 /0.0 (0.0) T1:100.0 /0.0 (0.0) @:0 B@:0 @0:0 @1:0 W:? 43 | // ok T:293.0 /0.0 (0.0) B:25.9 /0.0 T0:293.0 /0.0 (0.0) T1:100.0 /0.0 (0.0) @:0 B@:0 @0:0 @1:0 W:0 44 | // T:293.0 /0.0 B:25.9 /0.0 @:0 B@:0 45 | // T:293.0 /0.0 B:25.9 /0.0 T0:293.0 /0.0 T1:100.0 /0.0 @:0 B@:0 @0:0 @1:0 46 | // T:293.0 /0.0 (0.0) B:25.9 /0.0 T0:293.0 /0.0 (0.0) T1:100.0 /0.0 (0.0) @:0 B@:0 @0:0 @1:0 47 | static parse(line) { 48 | let r = line.match(/^(ok)?\s+T:[0-9\.\-]+/i); 49 | if (!r) { 50 | return null; 51 | } 52 | 53 | const payload = { 54 | ok: line.startsWith('ok'), 55 | extruder: {}, 56 | heatedBed: {} 57 | }; 58 | 59 | const re = /(?:(?:(T|B|T\d+):([0-9\.\-]+)\s+\/([0-9\.\-]+)(?:\s+\((?:[0-9\.\-]+)\))?)|(?:(@|B@|@\d+):([0-9\.\-]+))|(?:(W):(\?|[0-9]+)))/ig; 60 | 61 | while ((r = re.exec(line))) { 62 | const key = r[1] || r[4] || r[6]; 63 | 64 | if (key === 'T') { // T:293.0 /0.0 65 | payload.extruder.deg = r[2]; 66 | payload.extruder.degTarget = r[3]; 67 | continue; 68 | } 69 | 70 | if (key === 'B') { // B:60.0 /0.0 71 | payload.heatedBed.deg = r[2]; 72 | payload.heatedBed.degTarget = r[3]; 73 | continue; 74 | } 75 | 76 | if (key === '@') { // @:127 77 | payload.extruder.power = r[5]; 78 | continue; 79 | } 80 | 81 | if (key === 'B@') { // B@:127 82 | payload.heatedBed.power = r[5]; 83 | continue; 84 | } 85 | 86 | // M109, M190: Print temp & remaining time every 1s while waiting 87 | if (key === 'W') { // W:?, W:9, ..., W:0 88 | payload.wait = r[7]; 89 | continue; 90 | } 91 | 92 | // Hotends: T0, T1, ... 93 | // TODO 94 | } 95 | 96 | return { 97 | type: MarlinLineParserResultTemperature, 98 | payload: payload 99 | }; 100 | } 101 | }; -------------------------------------------------------------------------------- /jsdoc.conf: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "includePattern": ".+\\.(js(doc|x)?|mjs)$" 4 | } 5 | } -------------------------------------------------------------------------------- /liveprinter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "liveprinter", "liveprinter.pyproj", "{2A3167FA-4C3F-4625-871E-9E654346C8DF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {2A3167FA-4C3F-4625-871E-9E654346C8DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {2A3167FA-4C3F-4625-871E-9E654346C8DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ExtensibilityGlobals) = postSolution 21 | SolutionGuid = {479719E8-9D5F-4D37-B5FD-0DF35325D5E9} 22 | EndGlobalSection 23 | EndGlobal 24 | -------------------------------------------------------------------------------- /liveprinter/AutoDetectBaudJob.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Ultimaker B.V. 2 | # Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Job import Job 5 | from UM.Logger import Logger 6 | 7 | from avr_isp.stk500v2 import Stk500v2 8 | 9 | from time import time, sleep 10 | from serial import Serial, SerialException 11 | 12 | 13 | # An async job that attempts to find the correct baud rate for a USB printer. 14 | # It tries a pre-set list of baud rates. All these baud rates are validated by requesting the temperature a few times 15 | # and checking if the results make sense. If getResult() is not None, it was able to find a correct baud rate. 16 | class AutoDetectBaudJob(Job): 17 | def __init__(self, serial_port): 18 | super().__init__() 19 | self._serial_port = serial_port 20 | self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600] 21 | 22 | def run(self): 23 | Logger.log("d", "Auto detect baud rate started.") 24 | timeout = 3 25 | tries = 2 26 | 27 | programmer = Stk500v2() 28 | serial = None 29 | try: 30 | programmer.connect(self._serial_port) 31 | serial = programmer.leaveISP() 32 | except: 33 | programmer.close() 34 | 35 | for retry in range(tries): 36 | for baud_rate in self._all_baud_rates: 37 | Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate)) 38 | 39 | if serial is None: 40 | try: 41 | serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout) 42 | except SerialException as e: 43 | Logger.logException("w", "Unable to create serial") 44 | continue 45 | else: 46 | # We already have a serial connection, just change the baud rate. 47 | try: 48 | serial.baudrate = baud_rate 49 | except: 50 | continue 51 | sleep(1.5) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number 52 | successful_responses = 0 53 | 54 | serial.write(b"\n") # Ensure we clear out previous responses 55 | serial.write(b"M105\n") 56 | 57 | timeout_time = time() + timeout 58 | 59 | while timeout_time > time(): 60 | line = serial.readline() 61 | if b"ok T:" in line: 62 | successful_responses += 1 63 | if successful_responses >= 3: 64 | self.setResult(baud_rate) 65 | return 66 | 67 | serial.write(b"M105\n") 68 | sleep(15) # Give the printer some time to init and try again. 69 | self.setResult(None) # Unable to detect the correct baudrate. 70 | -------------------------------------------------------------------------------- /liveprinter/ConnectionState.py: -------------------------------------------------------------------------------- 1 | # 2 | # The current processing state of the backend. 3 | # 4 | 5 | from enum import IntEnum 6 | 7 | class ConnectionState(IntEnum): 8 | closed = 0 9 | connecting = 1 10 | connected = 2 11 | busy = 3 12 | error = 4 13 | -------------------------------------------------------------------------------- /liveprinter/UM/Decorators.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import copy 5 | import warnings 6 | import inspect 7 | 8 | from UM.Logger import Logger 9 | 10 | 11 | ## Decorator that can be used to indicate a method has been deprecated 12 | # 13 | # \param message The message to display when the method is called. Should include a suggestion about what to use. 14 | # \param since A version since when this method has been deprecated. 15 | def deprecated(message, since = "Unknown"): #pylint: disable=bad-whitespace 16 | def deprecated_decorator(function): 17 | def deprecated_function(*args, **kwargs): 18 | warning = "{0} is deprecated (since {1}): {2}".format(function, since, message) 19 | Logger.log("w", warning) 20 | warnings.warn(warning, DeprecationWarning, stacklevel=2) 21 | return function(*args, **kwargs) 22 | return deprecated_function 23 | return deprecated_decorator 24 | 25 | 26 | ## Decorator to ensure the returned value is always a copy and never a direct reference 27 | # 28 | # "Everything is a Reference" is not nice when dealing with value-types like a Vector or Matrix. 29 | # Since you hardly ever want to manipulate internal state of for example a SceneNode, most get methods 30 | # should return a copy instead of the actual object. This decorator ensures that happens. 31 | def ascopy(function): 32 | def copy_function(*args, **kwargs): 33 | return copy.deepcopy(function(*args, **kwargs)) 34 | 35 | return copy_function 36 | 37 | 38 | ## Decorator to conditionally call an extra function before calling the actual function. 39 | # 40 | # This is primarily intended for conditional debugging, to make it possible to add extra 41 | # debugging before calling a function that is only enabled when you actually want to 42 | # debug things. 43 | def call_if_enabled(function, condition): 44 | if condition: 45 | def call_decorated(decorated_function): 46 | def call_function(*args, **kwargs): 47 | if hasattr(decorated_function, "__self__"): 48 | function(decorated_function.__self__, *args, **kwargs) 49 | else: 50 | function(*args, **kwargs) 51 | return decorated_function(*args, **kwargs) 52 | return call_function 53 | return call_decorated 54 | else: 55 | def call_direct(decorated_function): 56 | return decorated_function 57 | return call_direct 58 | 59 | ## Raised when the override decorator does not find the function it claims to override. 60 | class InvalidOverrideError(Exception): 61 | pass 62 | 63 | ## Function decorator that can be used to mark a function as an override. 64 | # 65 | # This works basically the same as the override attribute in C++ functions. 66 | # 67 | # \param cls The class this function overrides a function from. 68 | def override(cls): 69 | def override_decorator(function): 70 | function_signature = inspect.signature(function) 71 | if not inspect.getmembers(cls, lambda i: inspect.isfunction(i) and sameSignature(inspect.signature(i), function_signature)): 72 | raise InvalidOverrideError("Method {method} is marked as override but was not found in base class {cls}".format(method = function.__qualname__, cls = cls.__qualname__)) 73 | return function 74 | return override_decorator 75 | 76 | ## Class decorator that checks to see if all methods of the base class have been reimplemented 77 | # 78 | # This is meant as a simple sanity check. An interface here is defined as a class with 79 | # only functions. Any subclass is expected to reimplement all functions defined in the class, 80 | # excluding builtin functions like __getattr__. It is also expected to match the signature of 81 | # those functions. 82 | def interface(cls): 83 | # Then, replace the new method with a method that checks if all methods have been reimplemented 84 | old_new = cls.__new__ 85 | def new_new(subclass, *args, **kwargs): 86 | for method in filter(lambda i: inspect.isfunction(i[1]) and not i[1].__name__.startswith("__") and not i[0].startswith("__"), inspect.getmembers(cls)): 87 | sub_method = getattr(subclass, method[0]) 88 | if sub_method == method[1]: 89 | raise NotImplementedError("Class {0} does not implement the complete interface of {1}: Missing method {2}".format(subclass, cls, method[0])) 90 | 91 | if not sameSignature(inspect.signature(sub_method), inspect.signature(method[1])): 92 | raise NotImplementedError("Method {0} of class {1} does not have the same signature as method {2} in interface {3}: {4} vs {5}".format(sub_method, subclass, method[1], cls, inspect.signature(sub_method), inspect.signature(method[1]))) 93 | 94 | if old_new == object.__new__: 95 | return object.__new__(subclass) # Because object.__new__() complains if we pass it *args and **kwargs 96 | else: 97 | return old_new(*args, **kwargs) 98 | 99 | cls.__new__ = new_new 100 | return cls 101 | 102 | 103 | def immutable(cls): 104 | property_names = list(filter(lambda i: isinstance(i, property), inspect.getmembers(cls))) 105 | cls.__slots__ = property_names 106 | return cls 107 | 108 | 109 | def sameSignature(a: inspect.Signature, b: inspect.Signature) -> bool: 110 | return len(a.parameters) == len(b.parameters) 111 | -------------------------------------------------------------------------------- /liveprinter/UM/Event.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | ## \file Event.py 5 | # Contains the Event class and important subclasses used throughout UM. 6 | 7 | 8 | ## Base event class. 9 | # Defines the most basic interface for events and several constants to identify event types. 10 | from typing import List, Any, Callable 11 | 12 | 13 | class Event: 14 | MousePressEvent = 1 15 | MouseMoveEvent = 2 16 | MouseReleaseEvent = 3 17 | KeyPressEvent = 4 18 | KeyReleaseEvent = 5 19 | SceneChangeEvent = 6 20 | ToolActivateEvent = 7 21 | ToolDeactivateEvent = 8 22 | MouseWheelEvent = 9 23 | CallFunctionEvent = 10 24 | ViewActivateEvent = 11 25 | ViewDeactivateEvent = 12 26 | 27 | def __init__(self, event_type: int) -> None: 28 | super().__init__() 29 | self._type = event_type 30 | 31 | ## The type of event. 32 | @property 33 | def type(self) -> int: 34 | return self._type 35 | 36 | 37 | ## Mouse Event class. 38 | # This class represents a mouse event. It has properties corresponding to important mouse 39 | # event properties and constants for mouse buttons. 40 | class MouseEvent(Event): 41 | ## Left mouse button. 42 | LeftButton = "left" 43 | RightButton = "right" 44 | MiddleButton = "middle" 45 | 46 | ## Raise a new mouse event. 47 | # \param type The type of event. \sa Event 48 | # \param x The X coordinate of the event. 49 | # \param y The Y coordinate of the event. 50 | # \param last_x The X coordinate of the previous mouse event. Can be None. It is used to calculate deltaX. 51 | # \param last_y The Y coordinate of the previous mouse event. Cam be None. It is used to calculate deltaY. 52 | # \param buttons The buttons that are associated with this event. 53 | def __init__(self, event_type: int, x: int = 0, y: int = 0, last_x: int = None, last_y: int = None, buttons: List = None) -> None: #pylint: disable=bad-whitespace 54 | super().__init__(event_type) 55 | self._x = x 56 | self._y = y 57 | self._last_x = last_x 58 | self._last_y = last_y 59 | self._buttons = [] # type: List 60 | if buttons: 61 | self._buttons = buttons 62 | 63 | ## The X coordinate of the event. 64 | @property 65 | def x(self) -> int: 66 | return self._x 67 | 68 | ## The Y coordinate of the event. 69 | @property 70 | def y(self) -> int: 71 | return self._y 72 | 73 | ## The X coordinate of the previous event. 74 | @property 75 | def lastX(self) -> int: 76 | return self._last_x 77 | 78 | ## The Y coordinate of the previous event. 79 | @property 80 | def lastY(self) -> int: 81 | return self._last_y 82 | 83 | ## The change in X position between this event and the previous event. 84 | @property 85 | def deltaX(self) -> int: 86 | if self._last_x is not None: 87 | return self._x - self._last_x 88 | 89 | return 0 90 | 91 | ## The change in Y position between this event and the previous event. 92 | @property 93 | def deltaY(self) -> int: 94 | if self._last_y is not None: 95 | return self._y - self._last_y 96 | 97 | return 0 98 | 99 | ## The list of buttons associated with this event. 100 | @property 101 | def buttons(self) -> List: 102 | return self._buttons 103 | 104 | 105 | ## Event relating to what's happening with the scroll wheel of a mouse. 106 | class WheelEvent(MouseEvent): 107 | ## Create a new scroll wheel event. 108 | # 109 | # \param horizontal How far the scroll wheel scrolled horizontally, in 110 | # eighths of a degree. To the right is positive. To the left is negative. 111 | # \param vertical How far the scroll wheel scrolled vertically, in eighths 112 | # of a degree. Up is positive. Down is negative. 113 | def __init__(self, horizontal: int, vertical: int, x: int = 0, y: int = 0) -> None: 114 | super().__init__(Event.MouseWheelEvent, x, y) 115 | self._horizontal = horizontal 116 | self._vertical = vertical 117 | 118 | ## How far the scroll wheel was scrolled horizontally, in eighths of a 119 | # degree. 120 | # 121 | # To the right is positive. To the left is negative. 122 | @property 123 | def horizontal(self) -> int: 124 | return self._horizontal 125 | 126 | ## How far the scroll wheel was scrolled vertically, in eighths of a 127 | # degree. 128 | # 129 | # Up is positive. Down is negative. 130 | @property 131 | def vertical(self) -> int: 132 | return self._vertical 133 | 134 | 135 | ## Event regarding the keyboard. 136 | # 137 | # These events are raised when anything changes in the keyboard state. They 138 | # keep track of the event type that was given by Qt, for instance whether it 139 | # was a KeyPressEvent or a KeyReleaseEvent, and they keep track of which key 140 | # it was. 141 | # 142 | # Only the special keys are tracked (Shirt, Space, Escape, etc.), not the 143 | # normal letter keys. 144 | class KeyEvent(Event): 145 | ShiftKey = 1 146 | ControlKey = 2 147 | AltKey = 3 148 | MetaKey = 4 149 | SpaceKey = 5 150 | EnterKey = 6 151 | UpKey = 7 152 | DownKey = 8 153 | LeftKey = 9 154 | RightKey = 10 155 | EscapeKey = 11 156 | MinusKey = 12 157 | UnderscoreKey = 13 158 | PlusKey = 14 159 | EqualKey = 15 160 | 161 | ## Creates a new key event, passing the event type on to the ``Event`` 162 | # parent class. 163 | def __init__(self, event_type: int, key: int) -> None: 164 | super().__init__(event_type) 165 | self._key = key 166 | 167 | ## Which key was pressed. 168 | # 169 | # Compare this with ``KeyEvent.AltKey``, ``KeyEvent.EnterKey``, etc. 170 | @property 171 | def key(self) -> int: 172 | return self._key 173 | 174 | 175 | ## Tool related event class. 176 | class ToolEvent(Event): 177 | pass 178 | 179 | 180 | ## Event used to call a function. 181 | class CallFunctionEvent(Event): 182 | def __init__(self, func: Callable, args: Any, kwargs: Any) -> None: 183 | super().__init__(Event.CallFunctionEvent) 184 | self._function = func 185 | self._args = args 186 | self._kwargs = kwargs 187 | 188 | def call(self) -> None: 189 | self._function(*self._args, **self._kwargs) 190 | 191 | 192 | ## View related event class. 193 | class ViewEvent(Event): 194 | pass 195 | -------------------------------------------------------------------------------- /liveprinter/UM/Job.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import time 5 | 6 | from UM.Signal import Signal, signalemitter 7 | 8 | from UM.JobQueue import JobQueue 9 | 10 | 11 | ## Base class for things that should be performed in a thread. 12 | # 13 | # The Job class provides a basic interface for a 'job', that is a 14 | # self-contained task that should be performed in a thread. It makes 15 | # use of the JobQueue for the actual threading. 16 | # \sa JobQueue 17 | @signalemitter 18 | class Job: 19 | def __init__(self): 20 | super().__init__() 21 | self._running = False # type: bool 22 | self._finished = False # type: bool 23 | self._result = None # type: any 24 | self._error = None 25 | 26 | ## Perform the actual task of this job. Should be reimplemented by subclasses. 27 | # \exception NotImplementedError 28 | def run(self): 29 | raise NotImplementedError() 30 | 31 | ## Get the result of the job. 32 | # 33 | # The actual result object returned by this method is dependant on the implementation. 34 | def getResult(self): 35 | return self._result 36 | 37 | ## Set the result of this job. 38 | # 39 | # This should be called by run() to set the actual result of the Job. 40 | def setResult(self, result: any): 41 | self._result = result 42 | 43 | ## Set an exception that was thrown while the job was being executed. 44 | # 45 | # Setting error to something else than None implies the Job failed 46 | # to execute properly. 47 | # 48 | # \param error \type{Exception} The exception to set. 49 | def setError(self, error: Exception): 50 | self._error = error 51 | 52 | ## Start the job. 53 | # 54 | # This will put the Job into the JobQueue to be processed whenever a thread is available. 55 | # 56 | # \sa JobQueue::add() 57 | def start(self): 58 | JobQueue.getInstance().add(self) 59 | 60 | ## Cancel the job. 61 | # 62 | # This will remove the Job from the JobQueue. If the run() function has already been called, 63 | # this will do nothing. 64 | def cancel(self): 65 | JobQueue.getInstance().remove(self) 66 | 67 | ## Check whether the job is currently running. 68 | # 69 | # \return \type{bool} 70 | def isRunning(self) -> bool: 71 | return self._running 72 | 73 | ## Check whether the job has finished processing. 74 | # 75 | # \return \type{bool} 76 | def isFinished(self) -> bool: 77 | return self._finished 78 | 79 | ## Check whether the Job has encountered an error during execution. 80 | # 81 | # \return \type{bool} True if an error was set, False if not. 82 | def hasError(self) -> bool: 83 | return self._error is not None 84 | 85 | ## Get the error that was encountered during execution. 86 | # 87 | # \return \type{Exception} The error encountered during execution or None if there was no error. 88 | def getError(self) -> Exception: 89 | return self._error 90 | 91 | ## Emitted when the job has finished processing. 92 | # 93 | # \param job \type{Job} The finished job. 94 | finished = Signal() 95 | 96 | ## Emitted when the job processing has progressed. 97 | # 98 | # \param job \type{Job} The job reporting progress. 99 | # \param amount \type{int} The amount of progress made, from 0 to 100. 100 | progress = Signal() 101 | 102 | ## Utility function that allows us to yield thread processing. 103 | # 104 | # This is mostly a workaround for broken python threads. This function 105 | # forces a GIL release and allows a different thread to start processing 106 | # if it is waiting. 107 | @staticmethod 108 | def yieldThread(): 109 | time.sleep(0) # Sleeping for 0 introduces no delay but does allow context switching. 110 | -------------------------------------------------------------------------------- /liveprinter/UM/JobQueue.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import multiprocessing 5 | import threading 6 | 7 | from UM.Signal import Signal, signalemitter 8 | from UM.Logger import Logger 9 | 10 | from typing import TYPE_CHECKING, List, Callable, Any 11 | if TYPE_CHECKING: 12 | from UM.Job import Job 13 | 14 | ## A thread pool and queue manager for Jobs. 15 | # 16 | # The JobQueue class manages a queue of Job objects and a set of threads that 17 | # can take things from this queue to process them. 18 | # \sa Job 19 | @signalemitter 20 | class JobQueue(): 21 | ## Initialize. 22 | # 23 | # \param thread_count The amount of threads to use. Can be a positive integer or 'auto'. 24 | # When 'auto', the number of threads is based on the number of processors and cores on the machine. 25 | def __init__(self, thread_count: (str, int) = "auto"): #pylint: disable=bad-whitespace 26 | if JobQueue._instance is None: 27 | JobQueue._instance = self 28 | else: 29 | raise RuntimeError("Attempted to create multiple instances of JobQueue") 30 | 31 | super().__init__() 32 | 33 | if thread_count == "auto": 34 | try: 35 | thread_count = multiprocessing.cpu_count() 36 | except NotImplementedError: 37 | thread_count = 0 38 | 39 | if thread_count <= 0: 40 | thread_count = 2 # Assume we can run at least two threads in parallel. 41 | 42 | self._threads = [_Worker(self) for t in range(thread_count)] 43 | 44 | self._semaphore = threading.Semaphore(0) 45 | self._jobs = [] # type: List[Job] 46 | self._jobs_lock = threading.Lock() 47 | 48 | for thread in self._threads: 49 | thread.daemon = True 50 | thread.start() 51 | 52 | ## Add a Job to the queue. 53 | # 54 | # \param job \type{Job} The Job to add. 55 | def add(self, job: "Job"): 56 | with self._jobs_lock: 57 | self._jobs.append(job) 58 | self._semaphore.release() 59 | 60 | ## Remove a waiting Job from the queue. 61 | # 62 | # \param job \type{Job} The Job to remove. 63 | # 64 | # \note If a job has already begun processing it is already removed from the queue 65 | # and thus can no longer be cancelled. 66 | def remove(self, job: "Job"): 67 | with self._jobs_lock: 68 | if job in self._jobs: 69 | self._jobs.remove(job) 70 | 71 | ## Emitted whenever a job starts processing. 72 | # 73 | # \param job \type{Job} The job that has started processing. 74 | jobStarted = Signal() 75 | 76 | ## Emitted whenever a job has finished processing. 77 | # 78 | # \param job \type{Job} The job that has finished processing. 79 | jobFinished = Signal() 80 | 81 | ## protected: 82 | 83 | # Get the next job off the queue. 84 | # Note that this will block until a job is available. 85 | def _nextJob(self): 86 | self._semaphore.acquire() 87 | with self._jobs_lock: 88 | # Semaphore release() can apparently cause all waiting threads to continue. 89 | # So to prevent issues, double check whether we actually have waiting jobs. 90 | if not self._jobs: 91 | return None 92 | return self._jobs.pop(0) 93 | 94 | ## Get the singleton instance of the JobQueue. 95 | @classmethod 96 | def getInstance(cls) -> "JobQueue": 97 | if not cls._instance: 98 | cls._instance = JobQueue() 99 | 100 | return cls._instance 101 | 102 | _instance = None # type: JobQueue 103 | 104 | 105 | ## Internal 106 | # 107 | # A worker thread that can process jobs from the JobQueue. 108 | class _Worker(threading.Thread): 109 | def __init__(self, queue): 110 | super().__init__() 111 | self._queue = queue 112 | 113 | def run(self): 114 | while True: 115 | # Get the next job from the queue. Note that this blocks until a new job is available. 116 | job = self._queue._nextJob() 117 | if not job: 118 | continue 119 | 120 | # Process the job. 121 | self._queue.jobStarted.emit(job) 122 | job._running = True 123 | 124 | try: 125 | job.run() 126 | except Exception as e: 127 | Logger.logException("e", "Job %s caused an exception", str(job)) 128 | job.setError(e) 129 | 130 | job._running = False 131 | job._finished = True 132 | job.finished.emit(job) 133 | self._queue.jobFinished.emit(job) 134 | -------------------------------------------------------------------------------- /liveprinter/UM/Logger.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import threading 5 | import traceback 6 | import inspect 7 | from typing import List 8 | 9 | from UM.PluginObject import PluginObject 10 | 11 | 12 | ## Static class used for logging purposes. This class is only meant to be used as a static class. 13 | class Logger: 14 | __loggers = [] # type: List[Logger] 15 | 16 | def __init__(self): 17 | raise Exception("This class is static only") 18 | 19 | ## Add a logger to the list. 20 | # \param logger \type{Logger} 21 | @classmethod 22 | def addLogger(cls, logger: "Logger"): 23 | cls.__loggers.append(logger) 24 | 25 | ## Get all loggers 26 | # \returns \type{list} List of Loggers 27 | @classmethod 28 | def getLoggers(cls) -> List["Logger"]: 29 | return cls.__loggers 30 | 31 | ## Send a message of certain type to all loggers to be handled. 32 | # 33 | # This method supports placeholders in either str.format() style or % style. For more details see 34 | # the respective Python documentation pages. 35 | # 36 | # Note that only str.format() supports keyword argument placeholders. Additionally, if str.format() 37 | # makes any changes, % formatting will not be applied. 38 | # 39 | # \param log_type \type{string} Values must be; 'e' (error) , 'i'(info), 'd'(debug) or 'w'(warning). 40 | # \param message \type{string} containing message to be logged 41 | # 42 | # \param *args \type{list} List of placeholder replacements that will be passed to str.format() or %. 43 | # \param **kwargs \type{dict} List of placeholder replacements that will be passed to str.format(). 44 | @classmethod 45 | def log(cls, log_type: str, message: str, *args, **kwargs): 46 | caller_frame = inspect.currentframe().f_back 47 | frame_info = inspect.getframeinfo(caller_frame) 48 | try: 49 | if args or kwargs: # Only format the message if there are args 50 | new_message = message.format(*args, **kwargs) 51 | 52 | if new_message == message: 53 | new_message = message % args # Replace all the %s with the variables. Python formatting is magic. 54 | 55 | message = new_message 56 | 57 | current_thread = threading.current_thread() 58 | message = "[{thread}] {class_name}.{function} [{line}]: {message}".format(thread = current_thread.name, 59 | class_name = caller_frame.f_globals["__name__"], 60 | function = frame_info.function, 61 | line = frame_info.lineno, 62 | message = message) 63 | 64 | for logger in cls.__loggers: 65 | logger.log(log_type, message) 66 | except Exception as e: 67 | print("FAILED TO LOG: ", log_type, message, e) 68 | 69 | if not cls.__loggers: 70 | print(message) 71 | 72 | ## Logs that an exception occurs. 73 | # 74 | # It'll include the traceback of the exception in the log message. The 75 | # traceback is obtained from the current execution state. 76 | # 77 | # \param log_type The importance level of the log (warning, info, etc.). 78 | # \param message The message to go along with the exception. 79 | @classmethod 80 | def logException(cls, log_type: str, message: str, *args): 81 | cls.log(log_type, "Exception: " + message, *args) 82 | # The function traceback.format_exception gives a list of strings, but those are not properly split on newlines. 83 | # traceback.format_exc only gives back a single string, but we can properly split that. It does add an extra newline at the end, so strip that. 84 | for line in traceback.format_exc().rstrip().split("\n"): 85 | cls.log(log_type, line) 86 | 87 | 88 | ## Abstract base class for log output classes. 89 | class LogOutput(PluginObject): 90 | ## Create the log output. 91 | # 92 | # This is called during the plug-in loading stage. 93 | def __init__(self): 94 | super().__init__() # Call super to make multiple inheritance work. 95 | self._name = type(self).__name__ # Set name of the logger to it's class name 96 | 97 | ## Log a message. 98 | # 99 | # The possible message types are: 100 | # - "d", debug 101 | # - "i", info 102 | # - "w", warning 103 | # - "e", error 104 | # - "c", critical 105 | # 106 | # \param log_type \type{string} A value describing the type of message. 107 | # \param message \type{string} The message to log. 108 | # \exception NotImplementedError 109 | def log(self, log_type: str, message: str): 110 | raise NotImplementedError("Logger was not correctly implemented") 111 | -------------------------------------------------------------------------------- /liveprinter/UM/OutputDevice.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Signal import Signal, signalemitter 5 | 6 | 7 | ## Base class for output devices. 8 | # 9 | # This class provides a base class for output devices. An output device can be 10 | # anything we want to output to, like a local file, an USB connected printer but 11 | # also an HTTP web service. 12 | # 13 | # Each subclass must implement requestWrite(). requestWrite() is expected to raise 14 | # errors from OutputDeviceError when certain conditions occur, like insufficient 15 | # permissions. For the rest, output device subclasses are completely free to implement 16 | # writing however they want, though you should emit writeStarted and related signals 17 | # whenever certain events happen related to the write process. 18 | # 19 | # For example, when implementing a web service as output device, it would be completely 20 | # acceptable to show a login dialog when calling requestWrite() if there are no saved 21 | # login credentials. 22 | @signalemitter 23 | class OutputDevice(): 24 | def __init__(self, device_id, **kwargs): 25 | super().__init__() 26 | 27 | self._id = device_id 28 | self._name = "Unknown Device" 29 | self._short_description = "Unknown Device" 30 | self._description = "Do something with an unknown device" 31 | self._icon_name = "generic_device" 32 | self._priority = 0 33 | 34 | metaDataChanged = Signal() 35 | 36 | ## Get the device id 37 | def getId(self): 38 | return self._id 39 | 40 | ## Get a human-readable name for this device. 41 | def getName(self): 42 | return self._name 43 | 44 | ## Set the human-readable name of this device. 45 | # 46 | # \param name The new name of this device. 47 | def setName(self, name): 48 | if name != self._name: 49 | self._name = name 50 | self.metaDataChanged.emit(self) 51 | 52 | ## Get a short description for this device. 53 | # 54 | # The short description can be used as a button label or similar 55 | # and should thus be only a few words at most. For example, 56 | # "Save to File", "Print with USB". 57 | def getShortDescription(self): 58 | return self._short_description 59 | 60 | ## Set the short description for this device. 61 | # 62 | # \param description The new short description to set. 63 | def setShortDescription(self, description): 64 | if description != self._short_description: 65 | self._short_description = description 66 | self.metaDataChanged.emit(self) 67 | 68 | ## Get a full description for this device. 69 | # 70 | # The full description describes what would happen when writing 71 | # to this device. For example, "Save to Removable Drive /media/sdcard", 72 | # "Upload to YouMagine with account User". 73 | def getDescription(self): 74 | return self._description 75 | 76 | ## Set the full description for this device. 77 | # 78 | # \param description The description of this device. 79 | def setDescription(self, description): 80 | if description != self._description: 81 | self._description = description 82 | self.metaDataChanged.emit(self) 83 | 84 | ## Get the name of an icon that can be used to identify this device. 85 | # 86 | # This icon should be available in the theme. 87 | def getIconName(self): 88 | return self._icon_name 89 | 90 | ## Set the name of an icon to identify this device. 91 | # 92 | # \param name The name of the icon to use. 93 | def setIconName(self, name): 94 | if name != self._icon_name: 95 | self._icon_name = name 96 | self.metaDataChanged.emit(self) 97 | 98 | ## The priority of this device. 99 | # 100 | # Priority indicates which device is most likely to be used as the 101 | # default device to write to. It should be a number and higher numbers 102 | # indicate that the device should be preferred over devices with 103 | # lower numbers. 104 | def getPriority(self): 105 | return self._priority 106 | 107 | ## Set the priority of this device. 108 | # 109 | # \param priority \type{int} The priority to use. 110 | def setPriority(self, priority): 111 | if priority != self._priority: 112 | self._priority = priority 113 | self.metaDataChanged.emit(self) 114 | 115 | ## Request performing a write operation on this device. 116 | # 117 | # This method should be implemented by subclasses. It should write the 118 | # given SceneNode forest to a destination relevant for the device. It is 119 | # recommended to perform the actual writing asynchronously and rely on 120 | # the write signals to properly indicate state. 121 | # 122 | # \param nodes A collection of scene nodes that should be written to the 123 | # device. 124 | # \param file_name \type{string} A suggestion for the file name to write 125 | # to. Can be freely ignored if providing a file name makes no sense. 126 | # \param limit_mimetype Limit output to these mime types. 127 | # \param file_handler The filehandler to use to write the file with. 128 | # \param kwargs Keyword arguments. 129 | # \exception OutputDeviceError.WriteRequestFailedError 130 | def requestWrite(self, nodes, file_name = None, limit_mimetypes = False, file_handler = None, **kwargs): 131 | raise NotImplementedError("requestWrite needs to be implemented") 132 | 133 | writeStarted = Signal() 134 | writeProgress = Signal() 135 | writeFinished = Signal() 136 | writeError = Signal() 137 | writeSuccess = Signal() 138 | -------------------------------------------------------------------------------- /liveprinter/UM/Platform.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | import sys 5 | 6 | 7 | ## Convenience class to simplify OS checking and similar platform-specific handling. 8 | class Platform: 9 | class PlatformType: 10 | Windows = 1 11 | Linux = 2 12 | OSX = 3 13 | Other = 4 14 | 15 | ## Check to see if we are currently running on OSX. 16 | @classmethod 17 | def isOSX(cls) -> bool: 18 | return cls.__platform_type == cls.PlatformType.OSX 19 | 20 | ## Check to see if we are currently running on Windows. 21 | @classmethod 22 | def isWindows(cls) -> bool: 23 | return cls.__platform_type == cls.PlatformType.Windows 24 | 25 | ## Check to see if we are currently running on Linux. 26 | @classmethod 27 | def isLinux(cls) -> bool: 28 | return cls.__platform_type == cls.PlatformType.Linux 29 | 30 | ## Get the platform type. 31 | @classmethod 32 | def getType(cls) -> int: 33 | return cls.__platform_type 34 | 35 | __platform_type = PlatformType.Other 36 | if sys.platform == "win32": 37 | __platform_type = PlatformType.Windows 38 | elif sys.platform == "linux": 39 | __platform_type = PlatformType.Linux 40 | elif sys.platform == "darwin": 41 | __platform_type = PlatformType.OSX 42 | -------------------------------------------------------------------------------- /liveprinter/UM/PluginObject.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | 5 | ## Base class for objects that can be provided by a plugin. 6 | # 7 | # This class should be inherited by any class that can be provided 8 | # by a plugin. Its only function is to serve as a mapping between 9 | # the plugin and the object. 10 | 11 | class PluginObject: 12 | def __init__(self): 13 | self._plugin_id = None 14 | self._version = None 15 | 16 | def getPluginId(self): 17 | return self._plugin_id 18 | 19 | def setPluginId(self, plugin_id): 20 | self._plugin_id = plugin_id 21 | 22 | def setVersion(self, version: str): 23 | self._version = version 24 | 25 | def getVersion(self) -> str: 26 | return self._version 27 | -------------------------------------------------------------------------------- /liveprinter/UM/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoop 5 | 6 | ## \package UM 7 | # This is the main library for Uranium applications. 8 | 9 | 10 | #Temporary translation entries. 11 | #We add these translated strings so that they enter into our translation templates. 12 | #These translated strings are for features that may yet be merged into 3.3. 13 | #from UM.i18n import i18nCatalog 14 | #i18n_catalog = i18nCatalog("uranium") 15 | #_ = i18n_catalog.i18nc("@info:status", "Your configuration seems to be corrupt. Something seems to be wrong with the following profiles:\n- {profiles}\nWould you like to reset to factory defaults?") 16 | #_ = i18n_catalog.i18nc("@info:status", "Your configuration seems to be corrupt.") 17 | #_ = i18n_catalog.i18nc("@info:title", "Configuration errors") 18 | #_ = i18n_catalog.i18nc("@info:button", "Reset your configuration to factory defaults.") 19 | -------------------------------------------------------------------------------- /liveprinter/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # LivePrinter server. 5 | 6 | """ 7 | LivePrinter server. 8 | ~~~~ 9 | 10 | :author: Evan Raskob 11 | :copyright: Copyright 2021 Evan Raskob 12 | :license: AGPL 3+ 13 | :source: 14 | 15 | """ -------------------------------------------------------------------------------- /liveprinter/anthill/ants-for-expressive-larger.js: -------------------------------------------------------------------------------- 1 | attachScript("the-cyber-anthill/ant.js"); // run this line first by itself 2 | attachScript("the-cyber-anthill/grid.js"); // run this line second by itself 3 | attachScript("the-cyber-anthill/antgrid-api.js"); // run this line 3rd by itself 4 | 5 | lp.start(190); // start up printer, home axes, set print head temperature and bed temp 6 | 7 | lp.sync() 8 | 9 | lp.extrude({ e: 20, speed: 12 }) 10 | 11 | lp.temp(195); 12 | 13 | lp.bed(55) 14 | // z = 0.8 15 | // x = 218, 25 16 | // y = 8, 170 17 | lp.down(0.1).go() 18 | 19 | lp.maxy = 173; 20 | lp.maxx = 218; 21 | lp.miny = 8; 22 | lp.maxy = lp.miny + 162; 23 | lp.minz = 0.8; 24 | lp.minx = 25; 25 | 26 | lp.moveto({ x: lp.minx + lp.cx, y: lp.maxy }) 27 | 28 | lp.moveto({ x: lp.maxx - 5, y: lp.miny + 8 + 165 }) 29 | 30 | lp.speed(50); 31 | 32 | lp.sync() 33 | 34 | const minZ = 4; 35 | lp.moveto({ z: minZ }) 36 | 37 | lp.down(0.1).go(); 38 | lp.retract() 39 | lp.extrude({ e: 30, speed: 12 }) 40 | 41 | lp.temp(205) 42 | 43 | lp.retract(9) 44 | 45 | 46 | lp.extrude({ e: 9, speed: 20 }) 47 | 48 | lp.downto(5).go() 49 | 50 | lp.fan(0) 51 | 52 | lp.bed(40); 53 | 54 | lp.upto(3.2).go() 55 | 56 | lp.moveto({ x: lp.cx, y: lp.cy }) 57 | 58 | lp.turnto(180).dist(20).go() 59 | 60 | lp.dist(10).go() 61 | 62 | lp.moveto({ x: 171.5, y: 12, z: 3.6 }) 63 | // 171.5, 12, 3.6 64 | 65 | //x 30, y 188 min, 220 max -- overhang is 21mm 66 | lp.minx = 41; 67 | lp.maxy = 188; 68 | lp.miny = 15; 69 | loginfo(lp.cx) 70 | 71 | lp.moveto({ x: lp.cx + lp.minx }) 72 | 73 | lp.moveto({ x: lp.maxx }) 74 | 75 | lp.speed(20); // set print speed to a bit slower, 35 for higher layers 76 | 77 | lp.lh(0.3); //.2, 0.15, .1 78 | 79 | lp.fan(80); 80 | 81 | // 10,16,22 82 | 83 | let dims = 24; // dimensions of underlying grid - careful not to make too big! 84 | let grid = new Grid(dims, dims * 2); // grid for ants to fill up 85 | let ant = null; // our current ant walker 86 | let paths = []; // array of paths walked 87 | let layers = 6; // layers per ant trail 88 | let iteration = 1 // how many time's we've run this before (start at 0) 89 | const w = lp.maxx - lp.minx; 90 | const h = lp.maxy - lp.miny; 91 | const sectW = 3 * w / 4; 92 | const sectH = 3 * h / 4; 93 | const gridH = 3 * h / 4; 94 | 95 | 96 | const gridW = sectW; 97 | const offsetx = (w - sectW) / 2; // 1/3 of 1/4 98 | const offsety = (h - sectH) / 2; 99 | 100 | const index = 0; //0,1,2 for all 3 101 | const startx = lp.minx + offsetx + index * (2 * offsetx + gridW); 102 | const starty = lp.miny + offsety; 103 | const z = lp.minz - lp.layerHeight / 2; // adjust for first layer 104 | 105 | lp.extraUnretract = 0.2; 106 | lp.sendFirmwareRetractSettings(); 107 | 108 | while (ant = createAnt(grid)) { 109 | if (ant) { 110 | ant.maxLife = 24; 111 | while (ant.alive) { 112 | move(ant, grid); 113 | } 114 | paths.push(ant.path); 115 | } 116 | } 117 | 118 | //lp.unretract(); 119 | //print all paths 120 | lp.printPaths({ paths: paths, x: startx, y: starty, z: z + lp.layerHeight * layers * iteration, w: gridW, h: gridH, passes: layers }); 121 | 122 | 123 | lp.up(100).go(); // move up at end 124 | 125 | loginfo(lp.layerHeight) 126 | 127 | /////////////////// next versions 128 | 129 | 130 | lp.speed(35); // set print speed to a bit slower, 35 for higher layers 131 | 132 | lp.lh(0.3); //.2, 0.15, .1 133 | 134 | lp.fan(80); 135 | 136 | // 10,16,22 137 | 138 | let dims = 12; // dimensions of underlying grid - careful not to make too big! 139 | let grid = new Grid(dims, dims * 2); // grid for ants to fill up 140 | let ant = null; // our current ant walker 141 | let paths = []; // array of paths walked 142 | let layers = 6; // layers per ant trail 143 | let iteration = 5; // how many time's we've run this before (start at 0) 144 | const w = lp.maxx - lp.minx; 145 | const h = lp.maxy - lp.miny; 146 | const sectW = 3 * w / 4; 147 | const sectH = 3 * h / 4; 148 | const gridH = 3 * h / 4; 149 | 150 | 151 | const gridW = sectW; 152 | const offsetx = (w - sectW) / 2; // 1/3 of 1/4 153 | const offsety = (h - sectH) / 2; 154 | 155 | const index = 0; //0,1,2 for all 3 156 | const startx = lp.minx + offsetx + index * (2 * offsetx + gridW); 157 | const starty = lp.miny + offsety; 158 | const z = lp.minz; // adjust for first layer 159 | 160 | lp.extraUnretract = 0.2; 161 | lp.sendFirmwareRetractSettings(); 162 | 163 | while (ant = createAnt(grid)) { 164 | if (ant) { 165 | ant.maxLife = dims; 166 | while (ant.alive) { 167 | move(ant, grid); 168 | } 169 | paths.push(ant.path); 170 | } 171 | } 172 | 173 | //lp.unretract(); 174 | //print all paths 175 | lp.printPaths({ paths: paths, x: startx, y: starty, z: z + lp.layerHeight * layers * iteration, w: gridW, h: gridH, passes: layers }); 176 | 177 | 178 | lp.up(100).go(); // move up at end -------------------------------------------------------------------------------- /liveprinter/anthill/ants-for-expressive.js: -------------------------------------------------------------------------------- 1 | // The CyberAntHill 2 | // By Evan Raskob < info@pixelist.info> 3 | // 4 | // Copyright 2019, all rights reserved 5 | 6 | attachScript("the-cyber-anthill/ant.js"); // run this line first by itself 7 | attachScript("the-cyber-anthill/grid.js"); // run this line second by itself 8 | attachScript("the-cyber-anthill/antgrid-api.js"); // run this line 3rd by itself 9 | 10 | lp.start(190); // start up printer, home axes, set print head temperature and bed temp 11 | 12 | lp.moveto({ x: lp.cx + 10, y: lp.miny + 32 }) 13 | lp.speed(50); 14 | 15 | lp.sync() 16 | 17 | const minZ = 4; 18 | lp.moveto({ z: minZ }) 19 | 20 | lp.down(0.1).go(); 21 | lp.extrude({ e: 20, speed: 8 }) 22 | 23 | lp.retract(9) 24 | 25 | 26 | lp.extrude({ e: 9, speed: 20 }) 27 | 28 | lp.downto(5).go() 29 | 30 | lp.fan(0) 31 | 32 | lp.bed(40); 33 | 34 | lp.upto(3.2).go() 35 | 36 | lp.moveto({ x: lp.cx, y: lp.cy }) 37 | 38 | lp.turnto(180).dist(20).go() 39 | 40 | lp.dist(10).go() 41 | 42 | lp.moveto({ x: 171.5, y: 12, z: 3.6 }) 43 | // 171.5, 12, 3.6 44 | 45 | //x 30, y 188 min, 220 max -- overhang is 21mm 46 | lp.minx = 41; 47 | lp.maxy = 188; 48 | lp.miny = 15; 49 | loginfo(lp.cx) 50 | 51 | lp.moveto({ x: lp.cx + lp.minx }) 52 | 53 | lp.moveto({ x: lp.maxx }) 54 | 55 | lp.speed(14); // set print speed to a bit slower, 35 for higher layers 56 | 57 | lp.lh(0.1); //.2, 0.15, .1 58 | 59 | lp.fan(60) 60 | 61 | // 10,16,22 62 | 63 | let dims = 22; // dimensions of underlying grid - careful not to make too big! 64 | let grid = new Grid(dims, dims * 2); // grid for ants to fill up 65 | let ant = null; // our current ant walker 66 | let paths = []; // array of paths walked 67 | let layers = 3; // layers per ant trail 68 | let iteration = 0; // how many time's we've run this before (start at 0) 69 | const w = lp.maxx - lp.minx; 70 | const h = lp.maxy - lp.miny; 71 | const sectW = w / 3; 72 | const gridH = 2 * h / 3; 73 | 74 | const gridW = sectW / 2; 75 | const offsetx = sectW / 4; // 1/3 of 1/4 76 | const offsety = h / 6; 77 | 78 | const index = 2; //0,1,2 for all 3 79 | const startx = lp.minx + offsetx + index * (2 * offsetx + gridW); 80 | const starty = lp.miny + offsety; 81 | const z = 3 + lp.layerHeight; 82 | 83 | while (ant = createAnt(grid)) { 84 | if (ant) { 85 | ant.maxLife = 36; 86 | while (ant.alive) { 87 | move(ant, grid); 88 | } 89 | paths.push(ant.path); 90 | } 91 | } 92 | 93 | //lp.unretract(); 94 | //print all paths 95 | lp.printPaths({ paths: paths, x: startx, y: starty, z: z + lp.layerHeight * layers * iteration, w: gridW, h: gridH, passes: layers }); 96 | 97 | 98 | lp.up(100).go(); // move up at end 99 | 100 | -------------------------------------------------------------------------------- /liveprinter/avr_isp/__init__.py: -------------------------------------------------------------------------------- 1 | # chip functions -------------------------------------------------------------------------------- /liveprinter/avr_isp/chipDB.py: -------------------------------------------------------------------------------- 1 | """ 2 | Database of AVR chips for avr_isp programming. Contains signatures and flash sizes from the AVR datasheets. 3 | To support more chips add the relevant data to the avrChipDB list. 4 | This is a python 3 conversion of the code created by David Braam for the Cura project. 5 | """ 6 | 7 | avr_chip_db = { 8 | "ATMega1280": { 9 | "signature": [0x1E, 0x97, 0x03], 10 | "pageSize": 128, 11 | "pageCount": 512, 12 | }, 13 | "ATMega2560": { 14 | "signature": [0x1E, 0x98, 0x01], 15 | "pageSize": 128, 16 | "pageCount": 1024, 17 | }, 18 | } 19 | 20 | def getChipFromDB(sig): 21 | for chip in avr_chip_db.values(): 22 | if chip["signature"] == sig: 23 | return chip 24 | return False 25 | 26 | -------------------------------------------------------------------------------- /liveprinter/avr_isp/intelHex.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to read intel hex files into binary data blobs. 3 | IntelHex files are commonly used to distribute firmware 4 | See: http://en.wikipedia.org/wiki/Intel_HEX 5 | This is a python 3 conversion of the code created by David Braam for the Cura project. 6 | """ 7 | import io 8 | from UM.Logger import Logger 9 | 10 | def readHex(filename): 11 | """ 12 | Read an verify an intel hex file. Return the data as an list of bytes. 13 | """ 14 | data = [] 15 | extra_addr = 0 16 | f = io.open(filename, "r") 17 | for line in f: 18 | line = line.strip() 19 | if len(line) < 1: 20 | continue 21 | if line[0] != ":": 22 | raise Exception("Hex file has a line not starting with ':'") 23 | rec_len = int(line[1:3], 16) 24 | addr = int(line[3:7], 16) + extra_addr 25 | rec_type = int(line[7:9], 16) 26 | if len(line) != rec_len * 2 + 11: 27 | raise Exception("Error in hex file: " + line) 28 | check_sum = 0 29 | for i in range(0, rec_len + 5): 30 | check_sum += int(line[i*2+1:i*2+3], 16) 31 | check_sum &= 0xFF 32 | if check_sum != 0: 33 | raise Exception("Checksum error in hex file: " + line) 34 | 35 | if rec_type == 0:#Data record 36 | while len(data) < addr + rec_len: 37 | data.append(0) 38 | for i in range(0, rec_len): 39 | data[addr + i] = int(line[i*2+9:i*2+11], 16) 40 | elif rec_type == 1: #End Of File record 41 | pass 42 | elif rec_type == 2: #Extended Segment Address Record 43 | extra_addr = int(line[9:13], 16) * 16 44 | else: 45 | Logger.log("d", "%s, %s, %s, %s, %s", rec_type, rec_len, addr, check_sum, line) 46 | f.close() 47 | return data 48 | -------------------------------------------------------------------------------- /liveprinter/avr_isp/ispBase.py: -------------------------------------------------------------------------------- 1 | """ 2 | General interface for Isp based AVR programmers. 3 | The ISP AVR programmer can load firmware into AVR chips. Which are commonly used on 3D printers. 4 | 5 | Needs to be subclassed to support different programmers. 6 | Currently only the stk500v2 subclass exists. 7 | This is a python 3 conversion of the code created by David Braam for the Cura project. 8 | """ 9 | 10 | from . import chipDB 11 | from UM.Logger import Logger 12 | 13 | class IspBase(): 14 | """ 15 | Base class for ISP based AVR programmers. 16 | Functions in this class raise an IspError when something goes wrong. 17 | """ 18 | def programChip(self, flash_data): 19 | """ Program a chip with the given flash data. """ 20 | self.cur_ext_addr = -1 21 | self.chip = chipDB.getChipFromDB(self.getSignature()) 22 | if not self.chip: 23 | raise IspError("Chip with signature: " + str(self.getSignature()) + "not found") 24 | self.chipErase() 25 | 26 | Logger.log("d", "Flashing %i bytes", len(flash_data)) 27 | self.writeFlash(flash_data) 28 | Logger.log("d", "Verifying %i bytes", len(flash_data)) 29 | self.verifyFlash(flash_data) 30 | Logger.log("d", "Completed") 31 | 32 | def getSignature(self): 33 | """ 34 | Get the AVR signature from the chip. This is a 3 byte array which describes which chip we are connected to. 35 | This is important to verify that we are programming the correct type of chip and that we use proper flash block sizes. 36 | """ 37 | sig = [] 38 | sig.append(self.sendISP([0x30, 0x00, 0x00, 0x00])[3]) 39 | sig.append(self.sendISP([0x30, 0x00, 0x01, 0x00])[3]) 40 | sig.append(self.sendISP([0x30, 0x00, 0x02, 0x00])[3]) 41 | return sig 42 | 43 | def chipErase(self): 44 | """ 45 | Do a full chip erase, clears all data, and lockbits. 46 | """ 47 | self.sendISP([0xAC, 0x80, 0x00, 0x00]) 48 | 49 | def writeFlash(self, flash_data): 50 | """ 51 | Write the flash data, needs to be implemented in a subclass. 52 | """ 53 | raise IspError("Called undefined writeFlash") 54 | 55 | def verifyFlash(self, flash_data): 56 | """ 57 | Verify the flash data, needs to be implemented in a subclass. 58 | """ 59 | raise IspError("Called undefined verifyFlash") 60 | 61 | 62 | class IspError(Exception): 63 | def __init__(self, value): 64 | self.value = value 65 | 66 | def __str__(self): 67 | return repr(self.value) 68 | -------------------------------------------------------------------------------- /liveprinter/dummyserial/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Dummy Serial Python Module. 5 | 6 | """ 7 | Dummy Serial Python Module. 8 | ~~~~ 9 | 10 | :author: Greg Albrecht 11 | :copyright: Copyright 2016 Orion Labs, Inc. 12 | :license: Apache License, Version 2.0 13 | :source: 14 | 15 | """ 16 | 17 | from .classes import Serial # NOQA 18 | from .exceptions import DSIOError, DSTypeError # NOQA 19 | -------------------------------------------------------------------------------- /liveprinter/dummyserial/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Dummy Serial Constants.""" 5 | 6 | import logging 7 | 8 | __author__ = 'Greg Albrecht ' 9 | __license__ = 'Apache License, Version 2.0' 10 | __copyright__ = 'Copyright 2016 Orion Labs, Inc.' 11 | 12 | 13 | LOG_LEVEL = logging.WARN 14 | LOG_FORMAT = logging.Formatter( 15 | '%(asctime)s dummyserial %(levelname)s %(name)s.%(funcName)s:%(lineno)d' 16 | ' - %(message)s') 17 | 18 | # The default timeout value in seconds. 19 | DEFAULT_TIMEOUT = 2 20 | 21 | # The default Baud Rate. 22 | DEFAULT_BAUDRATE = 250000 23 | 24 | # Response when no matching message (key) is found in the look-up dictionary. 25 | # * Should not be an empty string, as that is interpreted as 26 | # "no data available on port". 27 | DEFAULT_RESPONSE = b'ok\n' 28 | 29 | NO_DATA_PRESENT = '' 30 | -------------------------------------------------------------------------------- /liveprinter/dummyserial/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Dummy Serial Exceptions.""" 5 | 6 | __author__ = 'Greg Albrecht ' 7 | __license__ = 'Apache License, Version 2.0' 8 | __copyright__ = 'Copyright 2016 Orion Labs, Inc.' 9 | 10 | 11 | class DSIOError(IOError): 12 | """Dummy Serial Wrapper for IOError Exception.""" 13 | pass 14 | 15 | 16 | class DSTypeError(TypeError): 17 | """Dummy Serial Wrapper for TypeError Exception.""" 18 | pass 19 | -------------------------------------------------------------------------------- /liveprinter/printerreponse.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class PrinterResponse(): 4 | def __init__(self, **fields): 5 | self._time = time.time() * 1000 #ms for javascript front end 6 | self._type = fields['type'] 7 | self._command = fields['command'] 8 | self._properties = {} 9 | 10 | for key,val in fields.items(): 11 | if key != "type" and key != "command": 12 | self._properties[key] = val 13 | 14 | def getTime(self): 15 | return self._time 16 | 17 | def getType(self): 18 | return self._type 19 | 20 | def toDict(self): 21 | return { 22 | 'type': self._type, 23 | 'time': self._time, 24 | 'command': self._command, 25 | 'properties': self._properties 26 | } 27 | 28 | def toJSONRPC(self): 29 | json = { 30 | 'jsonrpc': '2.0', 31 | 'id': 3, 32 | 'method': self._type, 33 | 'params': { 34 | 'time': self._time, 35 | 'command': self._command 36 | } 37 | } 38 | for key,val in self._properties.items(): 39 | json['params'][key] = val 40 | 41 | return json 42 | 43 | def __repr__(self): 44 | return self.toDict() 45 | 46 | def __str__(self): 47 | return str(self.toDict()) 48 | 49 | 50 | # TEST 51 | #pr = PrinterResponse(**{ 52 | # 'type': "temperature", 53 | # 'command': "M105", 54 | # 'bed': 200, 55 | # 'bed_target': 201, 56 | # 'hotend': 180, 57 | # 'hotend_target': 181 58 | # }) 59 | 60 | #Logger.log("d", "{}".format(pr)) 61 | -------------------------------------------------------------------------------- /liveprinter/static/examples/algorave.js: -------------------------------------------------------------------------------- 1 | // draw the algorave logo 2 | lp.start(195); 3 | 4 | lp.sync(); 5 | lp.extrude({ e: 14, speed: 10 }); 6 | lp.downto(lp.layerHeight); 7 | 8 | lp.moveto({ x: 60, y: lp.cy - 8 }); 9 | lp.speed(45); 10 | let gap = 4; 11 | let forwards = 120; 12 | let backwards = -120; 13 | 14 | // 2 layers 15 | for (let i of numrange(0, 1)) { 16 | lp.moveto({ x: 60, y: lp.cy - 8 }); 17 | lp.turnto(0); 18 | 19 | let d = 110; 20 | //lp.dist(d).go(1); 21 | //also use plain fillDirection for other results 22 | 23 | lp.fillDirectionH(2.5, d, gap * lp.layerHeight, false); 24 | lp.turn(backwards); 25 | 26 | for (let ii of numrange(0, 12)) { 27 | //also use plain fillDirection for other results 28 | lp.fillDirectionH(2.5, d, gap * lp.layerHeight, false); 29 | //lp.dist(d).go(1); 30 | lp.turn(backwards); 31 | 32 | d -= 8; 33 | } 34 | lp.up(lp.layerHeight); 35 | } 36 | lp.retract(12.5); 37 | lp.up(60); -------------------------------------------------------------------------------- /liveprinter/static/examples/circle.js: -------------------------------------------------------------------------------- 1 | // Drawing a circle with LivePrinter 2 | // By Evan Raskob, 2019 3 | // -------------------------------- 4 | 5 | // draw a circle composed of a number of segments at a certain radius 6 | 7 | // center head first 8 | lp.moveto({ x: lp.cx, y: lp.cy, z: lp.layerHeight }); 9 | { 10 | let radius = 30; 11 | let segments = 10; 12 | let angle = Math.PI * 2 / segments; 13 | 14 | let dist_to_extrude = radius * Math.sin(angle); 15 | 16 | for (let i = 0; i < segments; i++) { 17 | lp.dist(dist_to_extrude).speed(15).go(1).turn(angle); 18 | } 19 | } 20 | 21 | //--------------------------- 22 | // copy all code below up until we say so: 23 | 24 | // define a polygon function -- use more segments for a better circle 25 | function polygon(r, segs = 10) { 26 | // law of cosines 27 | let r2x2 = r * r * 2; 28 | let segAngle = Math.PI * 2 / segs; 29 | let cx = Math.sqrt(r2x2 - r2x2 * Math.cos(segAngle)); 30 | loginfo(r2x2 + "," + segAngle + "," + cx); 31 | 32 | //translate((float)radius, 0); 33 | lp.turn(Math.PI / 2, true); // use radians 34 | // we're in the middle of segment 35 | lp.turn(-segAngle / 2, true); // use radians 36 | lp.unretract(); 37 | for (let i = 0; i < segs; i++) { 38 | lp.turn(segAngle, true); // use radians 39 | // print without retraction 40 | lp.dist(cx).go(1, false); 41 | } 42 | lp.retract(); 43 | } 44 | // move to a spot 45 | lp.moveto({ x: lp.cx, y: lp.cy, z: lp.layerHeight }); 46 | polygon(20, 20); 47 | // move up to inspect the drawing! 48 | lp.up(40).speed(40).go(); 49 | 50 | // stop copying here! 51 | //--------------------------- 52 | 53 | // use the built-in function to create a cylinder: 54 | lp.moveto({ x: lp.cx, y: lp.cy, z: lp.layerHeight }); 55 | lp.retractSpeed = 30; 56 | lp.unretract(); 57 | for (let i of numrange(1, 14)) { 58 | lp.speed(30).polygon(20, 16); 59 | lp.up(lp.layerHeight).go(); 60 | } 61 | lp.retract(); 62 | lp.up(40).speed(40).go(); 63 | 64 | 65 | // another way 66 | let segs = 10; 67 | for (let i = 0; i < segs; i++) { 68 | let angle = Math.PI * 2 * i / (segs - 1); 69 | let x = Math.cos(angle) * r; 70 | let y = Math.sin(angle) * r; 71 | lp.extrudeto({ 'x': cx + x, 'y': cy + y }); 72 | } 73 | 74 | 75 | // make a cylinder by moving up each time 76 | lp.unretract(); 77 | lp.polygon(30, 20); 78 | lp.retract(); 79 | lp.move({ z: lp.layerHeight }); 80 | 81 | -------------------------------------------------------------------------------- /liveprinter/static/examples/circle.py: -------------------------------------------------------------------------------- 1 | 2 | # the second time around, use this to unretract! 3 | # lp.extrude({'e':12,'speed':30}) 4 | 5 | # draw a circle 6 | import math 7 | 8 | # number of segments in circle 9 | ticks = 5 10 | # radius - set to 1/10 the max x coordinate 11 | r = lp.maxx/10 12 | layers = 5 13 | lp.layerHeight = 0.3 # in mm 14 | 15 | # lp.cx gives you the center x 16 | # lp.cy gives you the center y 17 | 18 | lp.moveto({'x':lp.cx+r, 'y':lp.cy,'z':lp.layerHeight, 'speed':80}) 19 | for _ in range(layers): 20 | arange = (a*math.pi*2/ticks for a in range(0,ticks+1)) 21 | for a in arange: 22 | lp.extrudeto({'x':lp.cx+r*math.cos(a), 'y':lp.cy+r*math.sin(a), 23 | 'speed':10}) 24 | lp.move({'z':lp.layerHeight, 'speed':80}) 25 | # try to stop the goo, make sure to unretract at end 26 | lp.extrude({'e':-12,'speed':80}) 27 | -------------------------------------------------------------------------------- /liveprinter/static/examples/corbusier.js: -------------------------------------------------------------------------------- 1 | // In progress... 2 | 3 | /* Le Corbusier's Modulo in mm 4 | 5 | 102 6 | 126 7 | 165 8 | 204 9 | 267 10 | 330 11 | 432 12 | 534 13 | 699 14 | 863 15 | */ 16 | 1. 3 17 | 2. 4*3 + 3 = 15 18 | 3. 4 * (4 * 3 + 3) + 3 = 63 19 | 4. 20 | 21 | //102, 126, 165, 204, 267, 330, 432, 534, 699, 863 22 | //gs4 b4 e5 gs5 c6 e6 a6 c7 f7 a7 -------------------------------------------------------------------------------- /liveprinter/static/examples/cyber-ants.js: -------------------------------------------------------------------------------- 1 | // Langston's CyberAnthill by Evan Raskob , 2019 2 | // Cellular automata 'walkers' that fill up a rectangular grid space 3 | 4 | attachScript("the-cyber-anthill/ant.js"); // run this line first by itself 5 | attachScript("the-cyber-anthill/grid.js"); // run this line second by itself 6 | attachScript("the-cyber-anthill/antgrid-api.js"); // run this line 3rd by itself 7 | 8 | lp.start(190); // start up printer, home axes, set print head temperature and bed temp 9 | 10 | lp.lh(0.35); 11 | 12 | lp.speed(20); // set print speed to a bit slower 13 | 14 | let dims = 8; // dimensions of underlying grid - careful not to make too big! 15 | let grid = new Grid(dims, dims); // grid for ants to fill up 16 | let ant = null; // our current ant walker 17 | let paths = []; // array of paths walked 18 | let layers = 4; // layers per ant trail 19 | let iteration = 0; // how many time's we've run this before (start at 0) 20 | 21 | while (ant = createAnt(grid)) { 22 | if (ant) { 23 | ant.maxLife = 20; 24 | //ant.skip = 1; // move per loop 25 | while (ant.alive) { 26 | move(ant, grid); 27 | } 28 | paths.push(ant.path); 29 | } 30 | } 31 | //print all paths 32 | lp.printPaths({ paths: paths, x: lp.maxx / 4, y: lp.maxy / 4, z: lp.layerHeight * layers * iteration, w: lp.maxx / 8, passes: layers }); 33 | 34 | lp.up(100).go(); // move up at end 35 | -------------------------------------------------------------------------------- /liveprinter/static/examples/hilbert.js: -------------------------------------------------------------------------------- 1 | // L-systems test 2 | // Uses a modified verion of the Lindenmayer library for JavaScript that uses ES6 Generators 3 | // https://github.com/pixelpusher/lindenmayer 4 | 5 | 6 | await lp.start(195); 7 | 8 | attachScript("http://localhost:8888/static/lib/linden/linden.js"); 9 | 10 | await lp.moveto({ x: 40, y: 80, z: 80, speed: 80 }); 11 | 12 | await lp.retract(); 13 | 14 | await lp.ext({ e: 10, speed: 8 }) 15 | 16 | // Now initialize the L-System to generate the Hilbert curve 17 | global hilbert = new LSystem({ 18 | axiom: 'L', 19 | ignoredSymbols: "", 20 | productions: { 21 | 'L': "+RF-LFL-FR+", 22 | 'R': "-LF+RFR+FL-", 23 | }, 24 | finals: { 25 | 'F': async () => { await lp.dist(4).go(1, false); }, 26 | '+': async () => { lp.turn(-90); return true; }, 27 | '-': async () => { lp.turn(90); return true; } 28 | } 29 | }); 30 | 31 | // Run the 1 iterations of the L-System 32 | hilbert.iterate(1); 33 | 34 | console.log(window.hilbert.getFuncs()) 35 | console.log(window.hilbert.axiom) 36 | 37 | loginfo(hilbert.getString()) 38 | loginfo(totalsteps) 39 | 40 | 41 | // draw each layer of the Hilbert curve (as a block, or manually) 42 | { 43 | lp.lh = 0.25; // make it thick 44 | let layer = 1; // current index of layer we're printing 45 | await lp.moveto({ x: 120, y: 100, z: 0.2 + 0.2 * layer, speed: 80 }); // set this to where you want to print and how high 46 | 47 | lp.speed(10); // set speed conservatively 48 | await lp.unretract(); // unretract material for printing 49 | lp.turnto(0); // turn to initial angle (facing right) 50 | //await hilbert.run(); 51 | 52 | for await (let f of hilbert.getFuncs()) { 53 | const func = f[0]; 54 | const index = f[1]; 55 | const part = f[2]; 56 | await func({ index, part }); 57 | } 58 | 59 | await lp.retract(); // unretract material for printing 60 | await lp.up(90).go(); 61 | } 62 | 63 | -------------------------------------------------------------------------------- /liveprinter/static/examples/lsystems.js: -------------------------------------------------------------------------------- 1 | // L-systems test 2 | // Uses a modified verion of the Lindenmayer library for JavaScript that uses ES6 Generators 3 | // https://github.com/pixelpusher/lindenmayer 4 | 5 | 6 | lp.start(195); 7 | 8 | attachScript("http://localhost:8888/static/lib/linden/linden.js"); 9 | 10 | lp.moveto({ x: 20, y: lp.maxy - 20, z: lp.lh, speed: 30 }); 11 | 12 | lp.unretract(); 13 | 14 | 15 | // Now initialize the L-System to generate the Hilbert curve 16 | global hilbert = new LSystem({ 17 | axiom: 'L', 18 | ignoredSymbols: "", 19 | productions: { 20 | 'L': "+RF-LFL-FR+", 21 | 'R': "-LF+RFR+FL-", 22 | }, 23 | finals: { 24 | 'F': () => { loginfo("F") }, 25 | '+': () => { loginfo("+") }, 26 | '-': () => { loginfo("-") } 27 | } 28 | }); 29 | 30 | // Run the 5 iterations of the L-System 31 | 32 | hilbert.iterate(1); 33 | 34 | // print some info 35 | loginfo(hilbert.getString()); 36 | 37 | // run through drawing commands in order (index < total) 38 | { 39 | let i = hilbert.run(); 40 | console.log(i); 41 | //i.next() 42 | 43 | for (let iter of i) { 44 | for (let p in iter) { 45 | loginfo(p + " " + iter[p]); 46 | } 47 | if (iter.index === iter.total) loginfo("DONE!"); 48 | } 49 | } 50 | 51 | // step through manually 52 | // works with run or steps 53 | s.iterator = hilbert.steps()[Symbol.iterator](); // run once to initialise 54 | s.result = s.iterator.next(); // keep running this to draw next line 55 | 56 | 57 | // run all functions and print out parts and index -- this can be faster but you don't know how many you have 58 | // last part is false and index is -1 59 | for (let v of hilbert.steps()) { 60 | for (let p in v) { 61 | loginfo(p + " " + v[p]); 62 | } 63 | } 64 | 65 | // running through steps repeatedly 66 | { 67 | let iter = hilbert.steps(true); // step through all steps forever, for 30 steps 68 | 69 | let i = 0; // counter for safety! 70 | while (i++ < 30) { 71 | let v = iter.next().value; 72 | console.log(v); 73 | 74 | if (v.part) // part is false at loop 75 | for (let p in v) { 76 | loginfo(i + ": " + p + " " + v[p]); 77 | } 78 | } 79 | } 80 | 81 | // better to reverse the fractal after each loop 82 | { 83 | let iter = hilbert.steps(true); // step through all steps forever, for 30 steps 84 | 85 | let i = 0; // counter for safety! 86 | while (i++ < 30) { 87 | let v = iter.next().value; 88 | console.log(v); 89 | 90 | if (v.part) { // part is false at loop 91 | for (let p in v) { 92 | loginfo(i + ": " + p + " " + v[p]); 93 | } 94 | } else { 95 | loginfo(hilbert.axiom); 96 | let str = ""; 97 | for (let a of hilbert.axiom) { 98 | if (a === "+") str += "-"; 99 | else if (a === "-") str += "+"; 100 | else str += a; 101 | } 102 | hilbert.axiom = str; 103 | loginfo(hilbert.axiom); 104 | } 105 | } 106 | } 107 | 108 | 109 | s.iterator = function (iterable) { 110 | let iter = iterable[Symbol.iterator](); 111 | 112 | return () => { 113 | const result = iter.next(); 114 | let retVal = result.value; 115 | 116 | if (result.done || result.value === "done") { 117 | loginfo("DONE"); 118 | retVal = false; 119 | } 120 | return retVal; 121 | }; 122 | }; 123 | 124 | // make iterator for all drawing steps 125 | s.steptree = s.iterator(hilbert.steps()); 126 | 127 | loginfo(s.steptree().part); // will be undefined when all steps have been looped through 128 | 129 | 130 | // or built-in makeIterator: 131 | global stepFunc = makeIterator(hilbert.steps()); 132 | loginfo(stepFunc().part); 133 | 134 | 135 | // run whole fractal at once 136 | while (s.steptree().part); 137 | -------------------------------------------------------------------------------- /liveprinter/static/examples/lsystems2.js: -------------------------------------------------------------------------------- 1 | // musical events example 2 | 3 | lp.start(195); 4 | 5 | lp.temp(195) 6 | 7 | lp.bed(50) 8 | 9 | gcode("G28") 10 | 11 | attachScript("http://localhost:8888/static/lib/linden/linden.js"); 12 | 13 | lp.moveto({ x: 20, y: lp.maxy - 20, z: lp.lh, speed: 30 }); 14 | 15 | # lp.mov y: 20 16 | 17 | # lp.ext e: 300 speed: 30 18 | 19 | lp.angle = 0 20 | 21 | lp.retract() 22 | 23 | # lp.mov z: 40 24 | 25 | cancel() 26 | lp.sync() 27 | 28 | lp.up(80).go() 29 | 30 | # lp.mov2 z: 0.2 y: 180 x: 20 31 | 32 | let baseNote = 40; 33 | let iter = 0; 34 | const iters = 1; 35 | lp.unretract() 36 | // Now initialize the L-System to generate the tree 37 | var tree = new LSystem({ 38 | axiom: 'L', 39 | ignoredSymbols: "", 40 | productions: { 41 | 'L': "+RF-LFL-FR+", 42 | 'R': "-LF+RFR+FL-", 43 | }, 44 | finals: { 45 | 'F': () => { lp.dist(20 / iters).speed(window.scales.majPattern[iter++ % 8] + baseNote).go(1, false); }, 46 | '+': () => { lp.turn(90); }, 47 | '-': () => { lp.turn(-90); } 48 | } 49 | }); 50 | 51 | // Run the 5 iterations of the L-System 52 | tree.iterate(4); 53 | loginfo(tree.getString()); 54 | tree.final(); 55 | 56 | 57 | 58 | 59 | lp.start(0); // start 60 | lp.bed(0); 61 | lp.temp(0); 62 | 63 | 64 | lp.maxTimePerOperation = 40; 65 | 66 | let toff = 800; 67 | let ang = 20; 68 | // basic/quick way of adding repeated tasks ("events") 69 | let loop1e = { 70 | name: "loop1", 71 | notes: [62, 72, 84], 72 | delay: toff * 3, 73 | run: function (time) { 74 | this.notes.map( 75 | t => lp.turn(ang).m2s(t).t2d(toff).go(0, 0)); 76 | lp.up(lp.layerHeight).go(); 77 | }, 78 | repeat: true 79 | }; 80 | 81 | addTask(loop1e); 82 | 83 | removeTask("loop1"); // remove when needed, or via GUI 84 | 85 | // getting a task and modifying it 86 | let loop = getTask("loop1"); 87 | loop.notes = [60, 62, 63, 64]; 88 | 89 | 90 | 91 | lp.turnto(0).dist(lp.maxx / 3).speed(150).go() 92 | 93 | 94 | // more "standard" way of creating a task 95 | 96 | let loop = new Task; 97 | loop.name = "loop1"; 98 | loop.data.notes = [62, 72, 84]; 99 | loop.data.duration = 50; 100 | loop.delay = 1000; 101 | loop.run = function (time) { 102 | this.data.notes.map(t => lp.turn(90).m2s(t).t2d(this.data.duration).go(0)); 103 | lp.up(lp.layerHeight).go(); 104 | }; 105 | 106 | addTask(loop); 107 | 108 | 109 | // get previous task and create another task to modify it 110 | s.loop = getTask("loop1"); 111 | 112 | let loop = new Task; 113 | loop.name = "loop3"; 114 | loop.delay = 100; 115 | loop.run = function (time) { 116 | s.loop.data.duration = Math.random() * 100 + 100; 117 | }; 118 | addTask(loop); 119 | 120 | 121 | s.loop.data.duration = 150; 122 | 123 | 124 | let loop1e = { 125 | name: "loop2", 126 | delay: 3200, 127 | run: function (time) { 128 | [41, 42, 45, 56].map(n => lp.m2s(n).t2d(250).go().wait(125)); 129 | lp.turn(180); 130 | }, 131 | repeat: true 132 | }; 133 | 134 | addTask(loop1e); -------------------------------------------------------------------------------- /liveprinter/static/examples/manualhilbert.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /liveprinter/static/examples/maryhadalittlelamb.js: -------------------------------------------------------------------------------- 1 | // Mary had a little lamb, LivePrinter style: 2 | // by Evan Raskob 2019 3 | 4 | p.moveto({ x: lp.cx, lp.cy }); 5 | lp.turnto(0); 6 | let octave = 2; // shift up 2 octaves 7 | for (let note of [40, 38, 36, 38, 40, 40, 40, 0, 38, 38, 38, 0, 40, 43, 43, 0]) { // do something for these midi notes 8 | if (note == 0) lp.wait(500); // wait 1/2 second if no note (0) 9 | // lp.m2s => midi note to motor speed 10 | // lp.t2d => time to distance (seconds to millimeters) based on the current printing speed 11 | lp.m2s(note + octave * 12, "x").t2d(500).go().turn(90); // play the note for 1/2 second and turn 90 CCW 12 | } 13 | -------------------------------------------------------------------------------- /liveprinter/static/examples/maryhadalittlelamb.py: -------------------------------------------------------------------------------- 1 | # start it up 2 | lp.start() 3 | 4 | #move to the side 5 | lp.moveto({'z':0.2, 'x':lp.minx,'y':lp.miny, 'speed':100}) 6 | 7 | # mary had a little lamb in 2 axes, two octaves 8 | notes = [40,38,36,38,40,40,40,0,38,38,38,0,40,43,43,0] 9 | for i in range(1,3): 10 | for n in notes: 11 | lp.note(n+12*i,200,'x') 12 | lp.note(n+24+12*i,200,'y') 13 | -------------------------------------------------------------------------------- /liveprinter/static/examples/minigrammar.js: -------------------------------------------------------------------------------- 1 | // Minigrammar - code quickly! 2 | // You can use the experimental mini-grammar as a shorthand. 3 | 4 | // normally you type this whole function (with punctuation!): 5 | 6 | //absolute move to x=20mm, y=20mm at speed 80mm/s: 7 | await lp.moveto({ x: 20, y: 20, speed: 80 }); 8 | 9 | // Instead you can type: 10 | # moveto x: 20 y: 20 speed: 80 11 | 12 | // you can enclose statements in # # if you're worried about mixing with javascript: 13 | 14 | # moveto x: 20 y: 20 speed: 80 # 15 | 16 | // or 17 | #moveto x:20 y:20 speed:80# 18 | 19 | 20 | //... and this is automatically compiled into the whole function call above. 21 | // Single lnes need to start with '#'. 22 | 23 | // You can use the '|' character to chain together functions like so: 24 | 25 | # start 210 | move x: 23 y: 50 z: 10 | extrude x: 50 speed: 15 | go 1 26 | 27 | // compiles to: 28 | // await lp.start(210);await lp.move({x:23,y:50,z:10});await lp.extrude({x:50,speed:15});await lp.go(1); 29 | 30 | // you can also do multiline blocks by surrounding code with ##'s: 31 | 32 | ## 33 | start 210 34 | moveto x: 20 y: 30 speed: 40 35 | extrude e: 10 speed: 8 36 | 37 | m2s 64 | t2d 400 | go 1 38 | ## 39 | 40 | // the above compiles to: 41 | 42 | //await lp.start(210);await lp.moveto({x:20,y:30,speed:40});await lp.extrude({e:10,speed:8});lp.m2s(64);lp.t2d(400);await lp.go(1); 43 | 44 | // you can also interleave js and minigrammar: 45 | # mov2 x:lp.cx/2 y:lp.cy/2 z:lp.lh 46 | 47 | for (let i = 0; i < 10; i++) { 48 | if (i % 2) 49 | # mov x: 10 y: 10 50 | else 51 | # mov x:-10 y: 10 52 | } 53 | 54 | 55 | // you can also safely enclose minigrammar statements inside lines with # (code) # 56 | let bung = () => { # mov2 x: 20 y: 40 # } 57 | -------------------------------------------------------------------------------- /liveprinter/static/examples/musicalevents.js: -------------------------------------------------------------------------------- 1 | // musical events example 2 | 3 | lp.start(0); // start 4 | 5 | lp.maxTimePerOperation = 40; 6 | 7 | 8 | // basic/quick way of adding repeated tasks ("events") 9 | let loop1e = { 10 | name: "loop1", 11 | notes: [62, 72, 84], 12 | delay: toff * 3, 13 | run: function (time) { 14 | this.notes.map( 15 | t => lp.turn(ang).m2s(t).t2d(toff).go(1, 0)); 16 | lp.up(lp.layerHeight).go(); 17 | }, 18 | repeat: true 19 | }; 20 | 21 | addTask(loop1e); 22 | 23 | removeTask("loop1"); // remove when needed, or via GUI 24 | 25 | // getting a task and modifying it 26 | let loop = getTask("loop1"); 27 | loop.notes = [60, 62, 63, 64]; 28 | 29 | 30 | 31 | lp.turnto(0).dist(lp.maxx / 3).speed(150).go() 32 | 33 | 34 | // more "standard" way of creating a task 35 | 36 | let loop = new Task; 37 | loop.name = "loop1"; 38 | loop.data.notes = [62, 72, 84]; 39 | loop.data.duration = 50; 40 | loop.delay = 1000; 41 | loop.run = function (time) { 42 | this.data.notes.map( t => lp.turn(90).m2s(t).t2d(this.data.duration).go(0)); 43 | lp.up(lp.layerHeight).go(); 44 | }; 45 | 46 | addTask(loop); 47 | 48 | 49 | // get previous task and create another task to modify it 50 | s.loop = getTask("loop1"); 51 | 52 | let loop = new Task; 53 | loop.name = "loop3"; 54 | loop.delay = 100; 55 | loop.run = function (time) { 56 | s.loop.data.duration = Math.random() * 100 + 100; 57 | }; 58 | addTask(loop); 59 | 60 | 61 | s.loop.data.duration = 150; 62 | 63 | 64 | let loop1e = { 65 | name: "loop2", 66 | delay: 3200, 67 | run: function (time) { 68 | [41, 42, 45, 56].map(n => lp.m2s(n).t2d(250).go().wait(125)); 69 | lp.turn(180); 70 | }, 71 | repeat: true 72 | }; 73 | 74 | addTask(loop1e); -------------------------------------------------------------------------------- /liveprinter/static/examples/pythontest.py: -------------------------------------------------------------------------------- 1 | from browser import document as doc 2 | from browser import window as win 3 | from browser import alert 4 | from browser.html import * 5 | 6 | geo = win.navigator.geolocation 7 | 8 | def navi(pos): 9 | xyz = pos.coords 10 | print ("{} {}".format(xyz.latitude, xyz.longitude)) 11 | 12 | def nonavi(error): 13 | print(error) 14 | 15 | if geo: 16 | geo.getCurrentPosition(navi, nonavi) 17 | 18 | print(win.scope.printer.cx) 19 | -------------------------------------------------------------------------------- /liveprinter/static/examples/sending-gcode.js: -------------------------------------------------------------------------------- 1 | // By Evan Raskob, 2020 2 | // -------------------------------- 3 | // GCode reference: https://github.com/Ultimaker/Ultimaker2Marlin 4 | 5 | // home all axes - this is useful to make sure the print head position is accurate 6 | 7 | # gcode "G28" 8 | 9 | //get the current positions of all the motors (x,y,z,e) 10 | // will display in the Info panel 11 | await lp.gcode("M114"); 12 | 13 | //turn on hot end to 190C 14 | # gcode "M104 S190" 15 | 16 | //heat bed to 60C 17 | # gcode "M140 S40" 18 | 19 | //heat bed to 60C and wait until fully heated to proceed: will time out! 20 | // DANGER: this will likely stop the server so you have to reboot. 21 | # gcode "M190 S60" 22 | 23 | // get the current temperature (will show in the lower right side box) 24 | // this is what happens when we poll the temperature 25 | # gcode "M105" 26 | 27 | # gcode "M104 S0" // turn off the heater for the hot end 28 | # gcode "M106 S100" // turn fan on to full (100) 29 | 30 | // turn off fan 31 | # gcode "M107" 32 | 33 | // BEEPING! 34 | // beep at 440Hz for 2 seconds (2000ms) 35 | # gcode "M300 S440 P2000" 36 | 37 | // RETRACTION: 38 | // NOTE: LivePrinter by default uses hardware retraction but you can disable it and use software retraction explicity 39 | // or no retraction on a command if you'd like by adding a {retract: false} property in the extrude functions or 40 | // setting lp.retractLength to 0 41 | 42 | //M207 - set retract length S[positive mm] F[feedrate mm/sec] Z[additional zlift/hop] 43 | // setting this manually is a bad idea because of what goes on in 44 | // liveprinter internally 45 | # gcode "M207 S4.5 F1500 Z0.2" 46 | 47 | //M208 - set recover=unretract length S[positive mm surplus to the M207 S*] F[feedrate mm/sec] 48 | # gcode "M208 S4.5 F1000" 49 | -------------------------------------------------------------------------------- /liveprinter/static/examples/using-functions.js: -------------------------------------------------------------------------------- 1 | // Using functions with LivePrinter 2 | // By Evan Raskob, 2020 3 | // -------------------------------- 4 | 5 | // You can mix liveprinter code with javascript. 6 | // There are some utility functions to help you do things faster... 7 | 8 | await repeat (5, async () => { 9 | # gcode "M105" 10 | # wait 500 11 | loginfo("got temp!"); 12 | }) 13 | 14 | 15 | await repeat (8, async () => { 16 | # gcode "M105" 17 | # wait 500 18 | loginfo("got temp!"); 19 | }) 20 | -------------------------------------------------------------------------------- /liveprinter/static/imgs/cat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 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 | -------------------------------------------------------------------------------- /liveprinter/static/imgs/dreidel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 11 | 13 | 15 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /liveprinter/static/imgs/menorah.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /liveprinter/static/imgs/menorah2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /liveprinter/static/imgs/penguin-simp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /liveprinter/static/imgs/whitebolt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 34 | 35 | -------------------------------------------------------------------------------- /liveprinter/static/util/svgrenderer.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 |
22 |
...
23 |
24 |
25 |

26 | xoffset: 27 |

28 |

29 | y offset: 30 |

31 |

32 | z offset: 33 |

34 |

35 | x max: 36 |

37 |

38 | y max: 39 |

40 | 41 |
42 |
43 | 44 | 91 |
92 | 93 | 94 |
95 | 96 | -------------------------------------------------------------------------------- /liveprinter/templates/info.html: -------------------------------------------------------------------------------- 1 |
2 | {% for line in message["result"]["info"] %} 3 |

{{line}}

4 | {% end %} 5 |
6 | -------------------------------------------------------------------------------- /misc/LivePrinter4Max/LivePrinter4Max.maxproj: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "InstaGraphics", 3 | "version" : 1, 4 | "creationdate" : -816294134, 5 | "modificationdate" : 3655310475, 6 | "viewrect" : [ 25.0, 69.0, 300.0, 500.0 ], 7 | "autoorganize" : 1, 8 | "hideprojectwindow" : 0, 9 | "showdependencies" : 1, 10 | "autolocalize" : 1, 11 | "contents" : { 12 | "patchers" : { 13 | "LivePrinter.maxpat" : { 14 | "kind" : "patcher", 15 | "local" : 1 16 | } 17 | 18 | } 19 | , 20 | "code" : { 21 | "liveprinter.js" : { 22 | "kind" : "javascript", 23 | "local" : 1 24 | } 25 | 26 | } 27 | 28 | } 29 | , 30 | "layout" : { 31 | 32 | } 33 | , 34 | "searchpath" : { 35 | 36 | } 37 | , 38 | "detailsvisible" : 0, 39 | "amxdtype" : 0, 40 | "readonly" : 1, 41 | "devpathtype" : 0, 42 | "devpath" : ".", 43 | "sortmode" : 0 44 | } 45 | -------------------------------------------------------------------------------- /misc/LivePrinter4Max/code/liveprinter.js: -------------------------------------------------------------------------------- 1 | outlets = 2; 2 | 3 | function gcode(cmd) 4 | { 5 | var ajaxreq; 6 | 7 | //create a XMLHttpRequest object 8 | ajaxreq = new XMLHttpRequest(); 9 | ajaxreq.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 10 | 11 | var gcodereq = { "jsonrpc": "2.0", "id": 4, "method": "send-gcode","params": [cmd]}; 12 | 13 | 14 | // others: 15 | // { "jsonrpc": "2.0", "id": 6, "method": "get-serial-ports","params": []} 16 | // { "jsonrpc": "2.0", "id": 5, "method": "set-serial-port","params": [ "COM3", 250000]} 17 | // { "jsonrpc": "2.0", "id": 2, "method": "close-serial-port","params": []} 18 | 19 | //set the HTTP message to be sent (usually a special formatted URL) 20 | ajaxreq.open("POST","localhost:8888/jsonrpc"); 21 | ajaxreq.send(JSON.stringify(gcodereq)); 22 | //set the callback function 23 | ajaxreq.onreadystatechange = function () { 24 | if(ajaxreq.readyState === 4 && ajaxreq.status === 200) { 25 | outlet(0,ajaxreq.responseText); 26 | } 27 | }; 28 | //send the request 29 | ajaxreq.send(); 30 | } -------------------------------------------------------------------------------- /misc/LivePrinter4Max/patchers/LivePrinter.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 7, 6 | "minor" : 3, 7 | "revision" : 5, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "rect" : [ 42.0, 85.0, 877.0, 721.0 ], 13 | "bglocked" : 0, 14 | "openinpresentation" : 0, 15 | "default_fontsize" : 12.0, 16 | "default_fontface" : 0, 17 | "default_fontname" : "Arial", 18 | "gridonopen" : 1, 19 | "gridsize" : [ 15.0, 15.0 ], 20 | "gridsnaponopen" : 1, 21 | "objectsnaponopen" : 1, 22 | "statusbarvisible" : 0, 23 | "toolbarvisible" : 1, 24 | "lefttoolbarpinned" : 0, 25 | "toptoolbarpinned" : 0, 26 | "righttoolbarpinned" : 0, 27 | "bottomtoolbarpinned" : 0, 28 | "toolbars_unpinned_last_save" : 0, 29 | "tallnewobj" : 0, 30 | "boxanimatetime" : 200, 31 | "enablehscroll" : 1, 32 | "enablevscroll" : 1, 33 | "devicewidth" : 0.0, 34 | "description" : "", 35 | "digest" : "", 36 | "tags" : "", 37 | "style" : "", 38 | "subpatcher_template" : "", 39 | "boxes" : [ { 40 | "box" : { 41 | "id" : "obj-5", 42 | "maxclass" : "newobj", 43 | "numinlets" : 1, 44 | "numoutlets" : 0, 45 | "patching_rect" : [ 31.0, 186.0, 34.0, 22.0 ], 46 | "style" : "", 47 | "text" : "print" 48 | } 49 | 50 | } 51 | , { 52 | "box" : { 53 | "bubble" : 1, 54 | "bubblepoint" : 0.3, 55 | "fontname" : "Arial", 56 | "fontsize" : 12.0, 57 | "id" : "obj-38", 58 | "maxclass" : "comment", 59 | "numinlets" : 1, 60 | "numoutlets" : 0, 61 | "patching_rect" : [ 117.0, 7.0, 178.0, 24.0 ], 62 | "style" : "", 63 | "text" : "replace with any GCODE" 64 | } 65 | 66 | } 67 | , { 68 | "box" : { 69 | "fontname" : "Arial", 70 | "fontsize" : 12.0, 71 | "id" : "obj-8", 72 | "maxclass" : "message", 73 | "numinlets" : 2, 74 | "numoutlets" : 1, 75 | "outlettype" : [ "" ], 76 | "patching_rect" : [ 31.0, 7.0, 69.0, 22.0 ], 77 | "style" : "", 78 | "text" : "gcode G28" 79 | } 80 | 81 | } 82 | , { 83 | "box" : { 84 | "fontname" : "Arial", 85 | "fontsize" : 28.513699, 86 | "id" : "obj-1", 87 | "maxclass" : "newobj", 88 | "numinlets" : 1, 89 | "numoutlets" : 2, 90 | "outlettype" : [ "", "" ], 91 | "patching_rect" : [ 31.0, 103.0, 190.0, 41.0 ], 92 | "saved_object_attributes" : { 93 | "filename" : "liveprinter.js", 94 | "parameter_enable" : 0 95 | } 96 | , 97 | "style" : "", 98 | "text" : "js liveprinter.js" 99 | } 100 | 101 | } 102 | ], 103 | "lines" : [ { 104 | "patchline" : { 105 | "destination" : [ "obj-5", 0 ], 106 | "source" : [ "obj-1", 0 ] 107 | } 108 | 109 | } 110 | , { 111 | "patchline" : { 112 | "destination" : [ "obj-1", 0 ], 113 | "source" : [ "obj-8", 0 ] 114 | } 115 | 116 | } 117 | ], 118 | "dependency_cache" : [ { 119 | "name" : "liveprinter.js", 120 | "bootpath" : "~/Documents/code/InstaGraphics/code", 121 | "patcherrelativepath" : "../code", 122 | "type" : "TEXT", 123 | "implicit" : 1 124 | } 125 | ], 126 | "autosave" : 0 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LivePrinter", 3 | "description": "LivePrinter livecoding system for 3D printing and CNC manufacturing", 4 | "version": "1.1.0", 5 | "keywords": [ 6 | "3DPrinting, livecoding, Marlin, GCode" 7 | ], 8 | "bugs": { 9 | "email": "info@pixelist.info", 10 | "url": "https://github.com/pixelpusher/liveprinter/issues" 11 | }, 12 | "homepage": "https://github.com/pixelpusher/liveprinter", 13 | "repository": "https://github.com/pixelpusher/liveprinter", 14 | "license": "AGPL-3.0-or-later", 15 | "author": "Evan Raskob", 16 | "targets": { 17 | "live": { 18 | "source": "js/main.js", 19 | "distDir": "./liveprinter/static/lib/", 20 | "context": "browser", 21 | "outputFormat": "global" 22 | }, 23 | "test": { 24 | "source": "test/test.html", 25 | "distDir": "./test/build", 26 | "context": "browser", 27 | "outputFormat": "esmodule", 28 | "publicUrl": "./" 29 | } 30 | }, 31 | "dependencies": { 32 | "@swc/helpers": "^0.4.12", 33 | "bootstrap": "^4.4.1", 34 | "bottleneck": "^2.19.5", 35 | "brython": "^3.8.8", 36 | "codemirror": "^5.53.2", 37 | "grammardraw": "github:pixelpusher/grammardraw", 38 | "jquery": "^3.5.0", 39 | "jshint": "^2.13.4", 40 | "liveprinter-utils": "github:pixelpusher/lp-utils", 41 | "nearley": "^2.19.3", 42 | "popper.js": "^1.16.1", 43 | "regenerator-runtime": "^0.13.9", 44 | "svgjs": "^2.6.2", 45 | "tonal": "^2.2.2" 46 | }, 47 | "devDependencies": { 48 | "assert": "^2.0.0", 49 | "events": "^3.3.0", 50 | "jsdoc-to-markdown": "^7.1.1", 51 | "minami": "^1.2.3", 52 | "parcel": "^2.4.1", 53 | "process": "^0.11.10", 54 | "util": "^0.12.4" 55 | }, 56 | "browserslist": "> 0.5%, last 2 versions, not dead", 57 | "scripts": { 58 | "build": "parcel build --target live", 59 | "test": "parcel watch --target test", 60 | "watch": "parcel watch --target live", 61 | "server": "python3 liveprinter/LivePrinterServer.py", 62 | "docs": "jsdoc2md js/liveprinter.comms.js > doc/comms.md && jsdoc2md js/liveprinter.ui.js > doc/ui.md && jsdoc2md js/liveprinter.editor.js > doc/editor.md && jsdoc2md js/liveprinter.printer.js > doc/printer.md" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /profile_stats.py: -------------------------------------------------------------------------------- 1 | import pstats 2 | p = pstats.Stats('server_profile') 3 | # skip strip_dirs() if you want to see full path's 4 | p.strip_dirs().print_stats() -------------------------------------------------------------------------------- /reference/Retraction_heuristics.md: -------------------------------------------------------------------------------- 1 | Retraction variables and heuristics for LivePrinter 2 | =================================================== 3 | 4 | _Last modified 2021-May-22_ 5 | 6 | This document explains how LivePrinter handles retractions. This heuristic was developed as a result of user testing and self-reflection during the development of the software, performances and experiments. The code that implements these heuristics can be found in the _liveprinter.printer.js_ file at https://github.com/pixelpusher/liveprinter/blob/master/js/liveprinter.printer.js. 7 | 8 | Note 9 | ---------- 10 | 11 | by default, LivePrinter retracts after each move unless specified otherwise. This is to support step-by-step drawing, which is how beginners and first-time users are expected to use LivePrinter. This will considerably slow down complex drawing commands. More advanced users who wish to create more complex shapes using continuous drawing can simply turn it off and on again at will and the system will try to make sure that the filament is always unretracted before continuing to draw, no matter what. 12 | 13 | Variables 14 | ---------- 15 | 16 | * **autoRetract** (true by default) -- controls automatic retraction. Set true/false 17 | * **retractLength** (async, set --> updateFirmwareSettings) 18 | * **retractSpeed** (async, set --> updateFirmwareSettings) 19 | * **currentRetraction** -- length of retraction (0 if not retracted, else positive) 20 | * **extraRetract** -- a little extra to retract each time, maybe due to some material getting lost when the filament grinds down against the motor or flattens in the extruder 21 | 22 | General drawing heuristics 23 | --------------------------- 24 | 25 | 1. draw: options, when retract, etc. 26 | 27 | Descriptions of key functions 28 | ----------------------------- 29 | 30 | Here are some step-by-step descriptions of the workings of some key functions referenced in this document: 31 | 32 | * **sendFirmwareRetractSettings** -- sends GCode to set Marlin's firmware auto-retraction, with parameters _length_ and _speed_ 33 | * **unretract (length, speed)** 34 | 1. check if filament is currently retracted (currentRetraction < 0) 35 | * no: quit 36 | * yes: 37 | 1. check if speed is within printer bounds 38 | 1. no: quit 39 | 2. yes: 40 | 1. Calculate and store the new filament position _e_ as the old position plus the retraction _length_ specified in the function arguments, plus an additional (optional) amount to compensate for filament flattening and grinding: ``this.e += this.retractLength + this.extraUnretract;`` 41 | 2. check if we are using firmware retraction (as opposed to software) 42 | 1. no: asynchronously send the GCode to move the filament to the new position using a standard move-with-extrusion operation _G1_: ``await this.gcodeEvent("G1 " + "E" + this.e.toFixed(4) + " F" + this._retractSpeed.toFixed(4));`` 43 | 2. yes: 44 | 1. has speed changed, or is this retraction length different than retractLength? 45 | 1. no: do nothing 46 | 2. yes: this.sendFirmwareRetractSettings() 47 | 2. asynchronously send firmware unretract GCode: ``await this.gcodeEvent("G11");`` 48 | 3. reset internal current retraction variable to 0 (i.e. filament not retracted) 49 | * **retract (length, speed)** 50 | 1. check if filament is currently unretracted (currentRetraction > 0) 51 | * no: quit 52 | * yes: 53 | 1. check if speed is within printer bounds 54 | 1. no: quit 55 | 2. yes: 56 | 1. this.currentRetraction = this.retractLength; 57 | 2. this.e -= this.retractLength; 58 | 3. check if we are using firmware retraction (as opposed to software) 59 | 1. no: ``await this.gcodeEvent("G1 " + "E" + this.e.toFixed(4) + " F" + this._retractSpeed.toFixed(4));`` 60 | 2. yes: 61 | 1. has speed changed, or is this retraction length different than retractLength? 62 | 1. no: do nothing 63 | 2. yes: this.sendFirmwareRetractSettings() 64 | 2. send firmware unretract: ``await this.gcodeEvent("G10");`` 65 | -------------------------------------------------------------------------------- /reference/extrudeto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelpusher/liveprinter/72d298df9d3ab79d4b105f1006b9f29f85fe66c2/reference/extrudeto.png -------------------------------------------------------------------------------- /reference/highlevel.dot: -------------------------------------------------------------------------------- 1 | // see https://marlinfw.org/docs/gcode/G000-G001.html 2 | 3 | 4 | digraph movement { 5 | fontname="Arial" 6 | splines=true 7 | rankdir="TB" 8 | 9 | node [shape=rect, style="filled, rounded", fillcolor="aquamarine", fontsize="9", fontname="Arial", color=mediumaquamarine, 10 | fontcolor=darkslategrey] 11 | 12 | edge [color=mediumaquamarine, 13 | fontsize="9", 14 | fontname="Arial", 15 | fontcolor=darkslategray, 16 | compound=true] 17 | 18 | // Nodes------------------- 19 | subgraph cluster_lp { 20 | fillcolor=paleturquoise1 21 | fontcolor=paleturquoise4 22 | style="filled,rounded" 23 | color=paleturquoise3 24 | label= 25 | operation [label=tool operation>] 26 | interpreter [label=] 27 | } 28 | subgraph cluster_marlin { 29 | fillcolor=paleturquoise1 30 | color=paleturquoise3 31 | fontcolor=paleturquoise4 32 | style="filled,rounded" 33 | label= 34 | firmware [label="Marlin firmware"] 35 | controllers [label="controller chips"] 36 | } 37 | subgraph cluster_printer { 38 | fillcolor=paleturquoise1 39 | color=paleturquoise3 40 | fontcolor=paleturquoise4 41 | style="filled,rounded" 42 | label= 43 | actuators [label=physical motors,
heaters,
etc.>] 44 | } 45 | 46 | // Connections------------- 47 | 48 | operation -> interpreter [label=< interpreted
JavaScript>] 49 | interpreter -> firmware [label=< GCode over
serial>, fontcolor=darkslategray] 50 | firmware -> controllers [label=""] 51 | controllers -> actuators [label=< electronic
signals>] 52 | 53 | // Ranks------------------- 54 | } -------------------------------------------------------------------------------- /reference/highlevel.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | movement 11 | 12 | GCode Tool Operations 13 | 14 | 15 | operation 16 | 17 | user-initiated 18 | tool operation 19 | 20 | 21 | 22 | interpreter 23 | 24 | GCode generator 25 | 26 | 27 | 28 | operation->interpreter 29 | 30 | 31 | parameters 32 | 33 | 34 | 35 | firmware 36 | 37 | Marlin firmware 38 | 39 | 40 | 41 | interpreter->firmware 42 | 43 | 44 | GCode 45 | 46 | 47 | 48 | controllers 49 | 50 | controller chips 51 | 52 | 53 | 54 | firmware->controllers 55 | 56 | 57 | ttl, serial, etc. 58 | 59 | 60 | 61 | actuators 62 | 63 | actuators: 64 | physical motors, 65 | heaters, 66 | etc. 67 | 68 | 69 | 70 | controllers->actuators 71 | 72 | 73 | electronic   pulses 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /reference/img/LivePrinter-Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelpusher/liveprinter/72d298df9d3ab79d4b105f1006b9f29f85fe66c2/reference/img/LivePrinter-Architecture.png -------------------------------------------------------------------------------- /reference/img/LivePrinter_overview_diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixelpusher/liveprinter/72d298df9d3ab79d4b105f1006b9f29f85fe66c2/reference/img/LivePrinter_overview_diagram.jpg -------------------------------------------------------------------------------- /reference/print.dot: -------------------------------------------------------------------------------- 1 | digraph movement { 2 | 3 | splines="TRUE"; 4 | 5 | 6 | node [style="filled", fillcolor="lightcyan2", fontsize="10", fontname="Arial"] 7 | 8 | edge [style="filled", fillcolor="lightcyan2", fontsize="10", fontname="Arial", compound=true] 9 | 10 | /* Entities */ 11 | 12 | startgo [label=, shape="invhouse" ] 13 | 14 | checkwait [label="waiting\ntime set?" shape="diamond",style=filled, fillcolor="cyan2"] 15 | 16 | "printer\nwaits" [shape="octagon",style="filled",fillcolor="lightcoral"] 17 | 18 | calcmovedist [label="calculate\nrelative\nmove"] 19 | 20 | startmoveto [label=, shape="invhouse" ] 21 | 22 | 23 | startmove [label=, shape="invhouse" ] 24 | 25 | checkparamsmove [label=distance
specified?> shape="diamond",style=filled, fillcolor="cyan2"] 26 | 27 | startextrude [label=, shape="invhouse" ] 28 | 29 | checkparams [label=distance
specified?>, shape="diamond",style=filled, fillcolor="cyan2"] 30 | 31 | checkextruding [label=specified?>, shape="diamond",style=filled, fillcolor="cyan2"] 32 | 33 | "printer\nmovement" [label="call\nextrudeto( )", shape="parallelogram",style="filled",fillcolor="lemonchiffon"] 34 | 35 | "set\nextrusion\nto 0" [shape="rect",style=filled, fillcolor="darkolivegreen1"] 36 | 37 | /* Relationships */ 38 | 39 | startmove:s -> checkparamsmove:n 40 | checkparamsmove:s -> startgo:n [label="yes"] 41 | checkparamsmove:w -> calcmovedist:n [label="no"] 42 | calcmovedist -> "set\nextrusion\nto 0" 43 | 44 | "set\nextrusion\nto 0" -> "printer\nmovement" 45 | 46 | startmoveto:s -> "set\nextrusion\nto 0" 47 | 48 | ////// EXTRUDE () -------------------- 49 | 50 | startextrude:s -> checkparams 51 | 52 | checkparams:s -> startgo:n [label=] 53 | 54 | checkparams:w -> "calculate\nrelative\nextrusion":n [label=] 55 | 56 | "calculate\nrelative\nextrusion" -> "printer\nmovement" 57 | 58 | 59 | ////// GO () ------------------------- 60 | 61 | startgo:s -> checkwait:n 62 | 63 | checkwait:w -> "calculate\nrelative\nmove" [label="no"] 64 | 65 | "calculate\nrelative\nmove" -> checkextruding 66 | 67 | checkwait:s -> "printer\nwaits":n [label="yes"] 68 | 69 | checkextruding:s -> "printer\nmovement":n [label=(printing)>] 70 | 71 | checkextruding:w -> "set\nextrusion\nto 0":n [label=(traveling)>] 72 | 73 | 74 | /* Ranks */ 75 | subgraph start { rank=same; startgo; startextrude; startmove; startmoveto}; 76 | } 77 | -------------------------------------------------------------------------------- /reference/retraction.dot: -------------------------------------------------------------------------------- 1 | digraph retract { 2 | splines="TRUE"; 3 | 4 | node [style="filled", fillcolor="lightcyan2", fontsize="10", fontname="Arial"] 5 | 6 | edge [style="filled", fillcolor="lightcyan2", fontsize="10", fontname="Arial", compound=true] 7 | 8 | /* Entities */ 9 | start [label=, shape="invhouse" ] 10 | 11 | check [label= 12 | retracted?> shape="diamond",style=filled, fillcolor="cyan2"] 13 | 14 | speedcheck[label=too fast or
15 | too slow?> shape="diamond",style=filled, fillcolor="cyan2"] 16 | 17 | updatelength [label=length from filament> shape="ellipse"] 18 | 19 | firmwarecheck [label=retraction?> shape="diamond",style=filled, fillcolor="cyan2"] 20 | 21 | speedchangecheck[label=retraction speed or
length changed?> shape="diamond",style=filled, fillcolor="cyan2"] 22 | 23 | manualretraction[label=to move filament
back by retraction
length>, shape="parallelogram",style="filled",fillcolor="lemonchiffon"] 24 | 25 | sendfirmwareupdate[label=to update
firmware settings
length>, shape="parallelogram",style="filled",fillcolor="lemonchiffon"] 26 | 27 | sendfirmware [label=for firmware retract
(G10)> , shape="parallelogram",style="filled",fillcolor="lemonchiffon"] 28 | 29 | quit [shape="octagon",style=filled, fillcolor="lightcoral"] 30 | 31 | error [shape="doubleoctagon",style=filled, fillcolor="firebrick1"] 32 | 33 | 34 | /* Relationships */ 35 | start -> check [label= 36 | retraction state>] 37 | 38 | check:s -> quit [label=" yes"] 39 | 40 | check:e -> speedcheck:n [label=" no"] 41 | 42 | speedcheck:s -> error:n [label=" yes"] 43 | 44 | speedcheck:e -> updatelength:n 45 | 46 | updatelength -> firmwarecheck 47 | 48 | firmwarecheck:s -> speedchangecheck:n [label=" yes"] 49 | 50 | firmwarecheck:e -> manualretraction:n [label=" no"] 51 | 52 | manualretraction:s -> quit 53 | 54 | speedchangecheck:s -> sendfirmwareupdate:n [label=" yes"] 55 | 56 | sendfirmwareupdate -> sendfirmware 57 | 58 | sendfirmware:s -> quit 59 | 60 | speedchangecheck:e -> sendfirmware:n [label=" no"] 61 | 62 | /* Ranks */ 63 | { rank=same; sendfirmwareupdate; sendfirmware;manualretraction}; 64 | } 65 | -------------------------------------------------------------------------------- /reference/unretraction.dot: -------------------------------------------------------------------------------- 1 | digraph unretract { 2 | splines="TRUE"; 3 | 4 | node [style="filled", fillcolor="lightcyan2", fontsize="10", fontname="Arial"] 5 | 6 | edge [style="filled", fillcolor="lightcyan2", fontsize="10", fontname="Arial", compound=true] 7 | 8 | /* Entities */ 9 | start [label=, shape="invhouse" ] 10 | 11 | check [label=< is filament
12 | retracted? > shape="diamond",style=filled, fillcolor="cyan2"] 13 | 14 | speedcheck[label=too fast or
15 | too slow?> shape="diamond",style=filled, fillcolor="cyan2"] 16 | 17 | updatelength [label=length to filament> shape="ellipse"] 18 | 19 | firmwarecheck [label=retraction?> shape="diamond",style=filled, fillcolor="cyan2"] 20 | 21 | speedchangecheck[label=speed or
length changed> shape="diamond",style=filled, fillcolor="cyan2"] 22 | 23 | manualretraction[label=to move filament
forward by retraction
length> shape="parallelogram",style="filled",fillcolor="lemonchiffon"] 24 | 25 | sendfirmwareupdate[label=to update
firmware settings
length> shape="parallelogram",style="filled",fillcolor="lemonchiffon"] 26 | 27 | sendfirmware [label=for firmware unretract
(G11)> shape="parallelogram",style="filled",fillcolor="lemonchiffon"] 28 | 29 | quit [shape="octagon",style=filled, fillcolor="lightcoral"] 30 | 31 | error [shape="doubleoctagon",style=filled, fillcolor="firebrick1"] 32 | 33 | /* Relationships */ 34 | start -> check:n [label= 35 | retraction state>] 36 | 37 | check:e -> quit [label=" no"] 38 | 39 | check:s -> speedcheck:n [label=" yes"] 40 | 41 | speedcheck:s -> error:n [label=" yes"] 42 | 43 | speedcheck:e -> updatelength [label=" no"] 44 | 45 | updatelength -> firmwarecheck:n 46 | 47 | firmwarecheck:s -> speedchangecheck:n [label=" yes"] 48 | 49 | firmwarecheck:e -> manualretraction:n [label=" no"] 50 | 51 | manualretraction -> quit 52 | 53 | speedchangecheck:s -> sendfirmwareupdate:n [label="yes"] 54 | 55 | sendfirmwareupdate -> sendfirmware 56 | 57 | sendfirmware:s -> quit 58 | 59 | speedchangecheck:e -> sendfirmware:n [label=" no"] 60 | 61 | /* Ranks */ 62 | { rank=same; sendfirmwareupdate; sendfirmware;manualretraction}; 63 | } 64 | -------------------------------------------------------------------------------- /reference/use_cases.dot: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | # Use cases 4 | 5 | ## Moving (e.g. traveling) 6 | 7 | Moving the print head into position to start printing; moving out of the way after finishing a print; or moving in order to make a sound. 8 | 9 | 1. moving the print head from the current position to a _specific location_ (x,y,z) in the print cavity 10 | 2. moving the print head a specific amount from the current position in the x,y,z direction (e.g. 10mm to the right, 2mm up ) 11 | 3. moving the print head a specific amount from the current position and at a specific 2D angle, possibly also with an angle of elevation (i.e. either upwards or downwards) 12 | 13 | ## Printing (e.g. extruding) 14 | 15 | Extruding filament to build up plastic forms on the print bed (e.g. building up a form 2D layer-by-layer, or in a single extruded blog), or in the air (like WirePrint); reversing (retracting) filament to prevent leakage or perform maintenance like changing the filament; forwarding filament in the tube until it is inside the print head and ready for further printing operations 16 | 17 | 1. printing a line from the current position to a _specific location_ (x,y,z) in the print cavity 18 | 2. printing a line with the length of a specific amount from the current position in the x,y,z direction (e.g. 10mm to the right, 2mm up ) 19 | 3. printing a line with the length of a specific amount from the current position and at a specific 2D angle, possibly also with an angle of elevation (i.e. either upwards or downwards) 20 | 4. moving the filament backwards into the tube and then changing it, or cleaning the head, and then forwards again until it is prinedin the print head 21 | 22 | ## Waiting 23 | 24 | Pausing printing and traveling for a specific amount of time 25 | 26 | 1. waiting for the extruded plastic to cool after a print move 27 | 2. in a musical sense, waiting a few "beats" as part of a melody 28 | 29 | **/ 30 | 31 | 32 | /* 33 | 34 | test cases: 35 | 36 | 1. retract -> travel -> travel (should stay retracted) 37 | 2. unretract -> travel -> travel (should retract at start and stay retracted) 38 | 3. retract -> extrude manual (e value only) -> shouldn't retract at start or at end 39 | 4. retract -> extrude normal (should be retracted at end) 40 | 5. retract -> extrude normal -> travel (should be retracted at end) 41 | 6. retract -> unretract -> extrude normal (should be retracted at end) 42 | 7. retract -> extrude normal w/retraction disabled (should unretract before and be unretracted at end) 43 | 44 | */ 45 | 46 | 47 | // example case of printing a square on the print bed 48 | 49 | digraph movement { 50 | fontname="Arial" 51 | labelloc="t" 52 | label="Retraction handling explained\nfor drawing a 2D square" 53 | splines=true 54 | 55 | node [shape=rect, style="filled, rounded", fillcolor="lightblue", fontsize="10", fontname="Arial", color="#444455"] 56 | 57 | edge [color="#444455",fontsize="10", fontname="Arial", compound=true] 58 | 59 | /* Entities */ 60 | 61 | start [label ="begin with retraction\nstate uncertain; goal is to\ndraw a square with side\nlength 20mm starting\n at (x,y) of (20mm,30mm)", shape="note", fillcolor="#fefeea"] 62 | 63 | "prime filament" [label ="1. Manual extrude:\nno retraction;\nclear previous\nretraction;\notherwise would\nunretract before\nand after", shape="note", fillcolor="#fefeea", labeljust=l] 64 | 65 | extrude [label="prime filament:\nextrude 10mm\nfilament"] 66 | 67 | printline [label="print 20mm line in\nthe current direction,\nretraction disabled"] 68 | 69 | printlinenote [label="3. don't want retraction\nbetween successive\ndrawing operations\nto speed up printing\nand prevent\nfilament grinding", shape=note, fillcolor="#fefeea"] 70 | 71 | move [label="move to print bed\nstart location"] 72 | 73 | movenote [label="2. does not retract before\nor after travel moves", shape=note, fillcolor="#fefeea"] 74 | 75 | turnnote [label="4. no retraction\nfor turns, purely\nvirtual operation", shape=note, fillcolor="#fefeea"] 76 | 77 | retractnote [label="5. manually retract to prevent\nleakage, because it was\ndisabled before", shape=note, fillcolor="#fefeea"] 78 | 79 | endnote [label="6. no retraction for moves", shape=note, fillcolor="#fefeea"] 80 | 81 | turn [label="turn 90 degrees"] 82 | 83 | up [label="move up 100mm"] 84 | 85 | repeat [shape=diamond, label="repeated\n4 times?", fillcolor="#ffee88"] 86 | 87 | //moveto [label=, shape="invhouse" ] 88 | 89 | 90 | /* Relationships */ 91 | start -> unretract [arrowhead="none",style=dotted] 92 | unretract-> extrude 93 | extrude -> move 94 | move -> printline 95 | move -> movenote [arrowhead=none,style=dashed]; 96 | printline -> printlinenote [arrowhead=none,style=dashed]; 97 | printline:s -> turn:n 98 | turn -> turnnote [arrowhead=none,style=dashed] 99 | turn:s -> repeat:n 100 | repeat:e -> printline:n [label=" no"] 101 | repeat:s -> retract [label=" yes"] 102 | retract -> up 103 | retract -> retractnote:w [arrowhead=none,style=dashed]; 104 | up -> endnote [arrowhead=none]; 105 | 106 | extrude:e -> "prime filament":w [arrowhead=none,style=dashed]; 107 | 108 | /* Ranks */ 109 | subgraph { rank=same; extrude; "prime filament"}; 110 | subgraph { rank=same; printline; printlinenote}; 111 | subgraph {rank=same; move; movenote} 112 | subgraph {rank=same; up; endnote} 113 | subgraph {rank=same; turn; turnnote} 114 | subgraph {rank=same; retract; retractnote} 115 | } 116 | -------------------------------------------------------------------------------- /testing/.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 cache directory 2 | /.vs/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | #lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /testing/antgrid-2019-03-13-tests.js: -------------------------------------------------------------------------------- 1 | 2 | // cyberanthill v2 (16x16) -- 2019 March 14 3 | 4 | let dims = 16; // dimensions of underlying grid - careful not to make too big! 5 | let grid = new Grid(dims, dims); // grid for ants to fill up 6 | let ant = null; // our current ant walker 7 | let paths = []; // array of paths walked 8 | 9 | 10 | while (ant = createAnt(grid)) { 11 | if (ant) { 12 | //ant.skip = 1; // move per loop 13 | while (ant.alive) { 14 | move(ant, grid); 15 | } 16 | paths.push(ant.path); 17 | } 18 | } 19 | //print all paths 20 | lp.printPaths({ paths: paths, x: lp.maxx / 4, y: lp.maxy / 4, w: lp.maxx / 4, passes: 2 }); 21 | 22 | 23 | 24 | // chunkier: 25 | 26 | 27 | lp.start(190); // start up printer, home axes, set print head temperature and bed temp 28 | lp.speed(30); // set print speed to a bit slower 29 | 30 | lp.fan(50); // turn off fan for first layer 31 | 32 | lp.lh(0.35); 33 | 34 | 35 | let dims = 12; // dimensions of underlying grid - careful not to make too big! 36 | let grid = new Grid(dims, dims); // grid for ants to fill up 37 | let ant = null; // our current ant walker 38 | let paths = []; // array of paths walked 39 | let layers = 4; // layers per ant trail 40 | let iteration = 0; // how many time's we've run this before (start at 1) 41 | 42 | while (ant = createAnt(grid)) { 43 | if (ant) { 44 | ant.maxLife = 20; 45 | //ant.skip = 1; // move per loop 46 | while (ant.alive) { 47 | move(ant, grid); 48 | } 49 | paths.push(ant.path); 50 | } 51 | } 52 | //print all paths 53 | lp.printPaths({ paths: paths, x: lp.maxx / 4, y: lp.maxy / 4, z: lp.layerHeight * layers * iteration, w: lp.maxx / 5, passes: layers }); 54 | 55 | lp.up(100).go() 56 | -------------------------------------------------------------------------------- /testing/antgrid_12x12_layered.js: -------------------------------------------------------------------------------- 1 | attachScript("the-cyber-anthill/ant.js"); // run this line first by itself 2 | attachScript("the-cyber-anthill/grid.js"); // run this line second by itself 3 | attachScript("the-cyber-anthill/antgrid-api.js"); // run this line 3rd by itself 4 | 5 | lp.start(190); // start up printer, home axes, set print head temperature and bed temp 6 | 7 | const minZ = 3.08; 8 | 9 | lp.speed(20); // set print speed to a bit slower 10 | lp.lh(0.15); 11 | 12 | let dims = 12; // dimensions of underlying grid - careful not to make too big! 13 | let grid = new Grid(dims, dims); // grid for ants to fill up 14 | let ant = null; // our current ant walker 15 | let paths = []; // array of paths walked 16 | let layers = 2; // layers per ant trail 17 | let iteration = 0; // how many time's we've run this before (start at 0) 18 | 19 | while (ant = createAnt(grid)) { 20 | if (ant) { 21 | ant.maxLife = 12; 22 | while (ant.alive) { 23 | move(ant, grid); 24 | } 25 | paths.push(ant.path); 26 | } 27 | } 28 | 29 | //print all paths 30 | lp.printPaths({ paths: paths, x: lp.maxx / 6 + lp.maxx / 6, y: lp.maxy / 6, z: minZ + lp.layerHeight * layers * iteration, w: lp.maxx / 6, passes: layers }); 31 | 32 | while (paths.length > 0) { 33 | iteration++; 34 | 35 | // fan on layer 1 36 | if (iteration === 1) lp.fan(80); 37 | 38 | // go backwards in paths and degrade each 39 | paths = paths.filter(path => { path.splice(0, 1); return path.length > 0 }); 40 | 41 | //print all paths 42 | lp.printPaths({ paths: paths, x: lp.maxx / 6 + lp.maxx / 6, y: lp.maxy / 6, z: minZ + lp.layerHeight * layers * iteration, w: lp.maxx / 6, passes: layers }); 43 | } 44 | 45 | lp.up(100).go(); // move up at end 46 | -------------------------------------------------------------------------------- /testing/antgrid_12x24_layered.js: -------------------------------------------------------------------------------- 1 | attachScript("the-cyber-anthill/ant.js"); // run this line first by itself 2 | attachScript("the-cyber-anthill/grid.js"); // run this line second by itself 3 | attachScript("the-cyber-anthill/antgrid-api.js"); // run this line 3rd by itself 4 | 5 | lp.start(190); // start up printer, home axes, set print head temperature and bed temp 6 | 7 | lp.moveto({ x: lp.cx + 10, y: lp.miny + 32 }) 8 | lp.speed(50); 9 | 10 | 11 | const minZ = 3.14; 12 | lp.moveto({ z: minZ }) 13 | 14 | lp.up(100).go(); 15 | lp.extrude({ e: 20, speed: 8 }) 16 | lp.retract(12) 17 | 18 | lp.bed(0); 19 | 20 | lp.turnto(0).dist(4).go(); 21 | 22 | lp.speed(22); // set print speed to a bit slower 23 | lp.lh(0.1); 24 | 25 | let dims = 12; // dimensions of underlying grid - careful not to make too big! 26 | let grid = new Grid(dims, dims * 2); // grid for ants to fill up 27 | let ant = null; // our current ant walker 28 | let paths = []; // array of paths walked 29 | let layers = 2; // layers per ant trail 30 | let iteration = 0; // how many time's we've run this before (start at 0) 31 | const startx = lp.x; 32 | const starty = lp.y; 33 | const z = lp.z; 34 | const w = lp.maxx / 10; 35 | 36 | while (ant = createAnt(grid)) { 37 | if (ant) { 38 | ant.maxLife = 12; 39 | while (ant.alive) { 40 | move(ant, grid); 41 | } 42 | paths.push(ant.path); 43 | } 44 | } 45 | 46 | //print all paths 47 | lp.printPaths({ paths: paths, x: startx, y: starty, z: z + lp.layerHeight * layers * iteration, w: w, passes: layers }); 48 | 49 | while (paths.length > 0) { 50 | iteration++; 51 | 52 | // fan on layer 1 53 | if (iteration === 1) lp.fan(80); 54 | 55 | // go backwards in paths and degrade each 56 | paths = paths.filter(path => { path.splice(0, 1); return path.length > 0 }); 57 | 58 | 59 | //print all paths 60 | lp.printPaths({ paths: paths, x: startx, y: starty, z: z + lp.layerHeight * layers * iteration, w: w, passes: layers }); 61 | } 62 | 63 | lp.up(100).go(); // move up at end 64 | -------------------------------------------------------------------------------- /testing/antgrid_16x16_sm.js: -------------------------------------------------------------------------------- 1 | let dims = 16; // dimensions of underlying grid - careful not to make too big! 2 | let grid = new Grid(dims, dims); // grid for ants to fill up 3 | let ant = null; // our current ant walker 4 | let paths = []; // array of paths walked 5 | let layers = 3; // layers per ant trail 6 | let iteration = 0; // how many time's we've run this before (start at 0) 7 | 8 | while (ant = createAnt(grid)) { 9 | if (ant) { 10 | ant.maxLife = 12; 11 | //ant.skip = 1; // move per loop 12 | while (ant.alive) { 13 | move(ant, grid); 14 | } 15 | paths.push(ant.path); 16 | } 17 | } 18 | 19 | //print all paths 20 | lp.printPaths({ paths: paths, x: lp.maxx / 8, y: lp.maxy / 8, z: lp.layerHeight * layers * iteration, w: lp.maxx / 8, passes: layers }); 21 | 22 | while (paths.length > 0) { 23 | iteration++; 24 | 25 | // go backwards in paths and degrade each 26 | paths = paths.filter(path => path.splice(0, 1).length > 0); 27 | 28 | //print all paths 29 | lp.printPaths({ paths: paths, x: lp.maxx / 8, y: lp.maxy / 8, z: lp.layerHeight * layers * iteration, w: lp.maxx / 8, passes: layers }); 30 | } 31 | 32 | lp.up(100).go(); // move up at end 33 | -------------------------------------------------------------------------------- /testing/antgrid_inc.js: -------------------------------------------------------------------------------- 1 | let dims = 16; // dimensions of underlying grid - careful not to make too big! 2 | let grid = new Grid(dims, dims); // grid for ants to fill up 3 | const numAnts = 4; // ants at any point 4 | let ants = Array.from({ length: numAnts }, () => (createAnt(grid))); // our current ant walker 5 | let paths = []; // array of paths walked at each layer 6 | 7 | let layers = 1; // layers we're rendered 8 | 9 | ///// loop code - run this each time 10 | 11 | if (ant) { 12 | ant.maxLife = 12; 13 | //ant.skip = 1; // move per loop 14 | while (ant.alive) { 15 | move(ant, grid); 16 | } 17 | paths.push(ant.path); 18 | } 19 | } 20 | //print all paths 21 | lp.printPaths({ paths: paths, x: lp.maxx / 8, y: lp.maxy / 8, z: lp.layerHeight * layers, w: lp.maxx / 8, passes: layers }); 22 | } 23 | lp.up(100).go(); // move up at end 24 | -------------------------------------------------------------------------------- /testing/async_script_attach.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 28 | 29 | -------------------------------------------------------------------------------- /testing/gcodegrammar.js: -------------------------------------------------------------------------------- 1 | { 2 | function extractList(list, index) { 3 | return list.map(function (element) { return element[index]; }); 4 | } 5 | 6 | function buildList(head, tail, index) { 7 | return [head].concat(extractList(tail, index)); 8 | } 9 | } 10 | 11 | //PrimaryExpression "expression" 12 | // = ((GCommand+) __)+ 13 | 14 | PropertyNameAndValueList 15 | = head: GCommand tail: (__ GCommand)* { 16 | return buildList(head, tail, 1); 17 | } 18 | 19 | __ 20 | = (WhiteSpace / LineTerminatorSequence / Comment) * 21 | 22 | GCommand 23 | = commandType: GCommandPrefix command: (Number +) { 24 | var r = {}; 25 | switch (commandType) { 26 | case "G": { 27 | // hack 28 | switch (command) { 29 | case "90": r = { type: "movement", value: "absolute" }; 30 | break; 31 | case "91": r = { type: "movement", value: "relative" }; 32 | break; 33 | case "1": r = { type: "movement", value: "normal" }; 34 | break; 35 | default: r = { type: "movement", value: command }; 36 | } 37 | } 38 | break; 39 | case "X": r = { type: "x-movement", value: command + "" }; 40 | break; 41 | case "Y": r = { type: "y-movement", value: command + "" }; 42 | break; 43 | default: r = { type: "setting", value: command + "" }; 44 | break; 45 | } 46 | return r; 47 | } 48 | 49 | Number = DecimalLiteral / SignedInteger 50 | 51 | GCommandPrefix "G code character (G,T,M,X,Y,Z,S,E,F)" 52 | = $[GTFMYXZSE] 53 | 54 | SourceCharacter = . 55 | 56 | WhiteSpace "whitespace" 57 | = "\t" 58 | / "\v" 59 | / "\f" 60 | / " " 61 | / "\u00A0" 62 | / "\uFEFF" 63 | / Zs 64 | 65 | LineTerminator 66 | = [\n\r\u2028\u2029] 67 | 68 | LineTerminatorSequence "end of line" 69 | = "\n" 70 | / "\r\n" 71 | / "\r" 72 | / "\u2028" 73 | / "\u2029" 74 | 75 | Comment "comment" 76 | = MultiLineComment 77 | / SingleLineComment 78 | 79 | MultiLineComment 80 | = "/*"(!"*/" SourceCharacter) * "*/" 81 | 82 | MultiLineCommentNoLineTerminator 83 | = "/*"(!("*/" / LineTerminator) SourceCharacter) * "*/" 84 | 85 | SingleLineComment 86 | = [;](!LineTerminator SourceCharacter) * 87 | 88 | 89 | DecimalLiteral 90 | = DecimalIntegerLiteral "." DecimalDigit * ExponentPart ? { 91 | return { type: "Literal", value: parseFloat(text()) }; 92 | } 93 | / "." DecimalDigit + ExponentPart ? { 94 | return { type: "Literal", value: parseFloat(text()) }; 95 | } 96 | / DecimalIntegerLiteral ExponentPart ? { 97 | return { type: "Literal", value: parseFloat(text()) }; 98 | } 99 | 100 | DecimalIntegerLiteral 101 | = "0" 102 | / NonZeroDigit DecimalDigit * 103 | 104 | DecimalDigit 105 | =[0 - 9] 106 | 107 | NonZeroDigit 108 | = [1 - 9] 109 | 110 | ExponentPart 111 | = ExponentIndicator SignedInteger 112 | 113 | ExponentIndicator 114 | = "e"i 115 | 116 | SignedInteger 117 | = [+-] ? DecimalDigit + { 118 | return { type: "Literal", value: parseFloat(text()) }; 119 | } 120 | 121 | // Unicode ////////////////////// 122 | // Separator, Space 123 | Zs = [\u0020\u00A0\u1680\u2000 -\u200A\u202F\u205F\u3000] -------------------------------------------------------------------------------- /testing/grammar_test.js: -------------------------------------------------------------------------------- 1 | // Grammar parser for GCode 2 | 3 | PrimaryExpression 4 | = (GCommand __)+ 5 | 6 | __ 7 | = (WhiteSpace / LineTerminatorSequence / Comment)* 8 | 9 | GCommand 10 | = commandType:GCommandPrefix command:(Number+) { 11 | var r = {}; 12 | switch(commandType) { 13 | case "G": r = { type: "movement", value: command }; 14 | break; 15 | case "M": r = { type: "setting", value: command }; 16 | break; 17 | case "X": r = { type: "x-movement", value: command }; 18 | break; 19 | case "Y": r = { type: "y-movement", value: command }; 20 | break; 21 | } 22 | return r; 23 | } 24 | 25 | Number = DecimalLiteral / DecimalIntegerLiteral 26 | 27 | GCommandPrefix "G code character (G,T,M,X,Y,Z,S,E)" 28 | = $[GTMYXZSE] 29 | 30 | SourceCharacter = . 31 | 32 | WhiteSpace "whitespace" 33 | = "\t" 34 | / "\v" 35 | / "\f" 36 | / " " 37 | / "\u00A0" 38 | / "\uFEFF" 39 | / Zs 40 | 41 | LineTerminator 42 | = [\n\r\u2028\u2029] 43 | 44 | LineTerminatorSequence "end of line" 45 | = "\n" 46 | / "\r\n" 47 | / "\r" 48 | / "\u2028" 49 | / "\u2029" 50 | 51 | Comment "comment" 52 | = MultiLineComment 53 | / SingleLineComment 54 | 55 | MultiLineComment 56 | = "/*" (!"*/" SourceCharacter)* "*/" 57 | 58 | MultiLineCommentNoLineTerminator 59 | = "/*" (!("*/" / LineTerminator) SourceCharacter)* "*/" 60 | 61 | SingleLineComment 62 | = "//" (!LineTerminator SourceCharacter)* 63 | 64 | DecimalLiteral 65 | = DecimalIntegerLiteral "." DecimalDigit* ExponentPart? { 66 | return { type: "Literal", value: parseFloat(text()) }; 67 | } 68 | / "." DecimalDigit+ ExponentPart? { 69 | return { type: "Literal", value: parseFloat(text()) }; 70 | } 71 | / DecimalIntegerLiteral ExponentPart? { 72 | return { type: "Literal", value: parseFloat(text()) }; 73 | } 74 | 75 | DecimalIntegerLiteral 76 | = "0" 77 | / NonZeroDigit DecimalDigit* 78 | 79 | DecimalDigit 80 | = [0-9] 81 | 82 | NonZeroDigit 83 | = [1-9] 84 | 85 | ExponentPart 86 | = ExponentIndicator SignedInteger 87 | 88 | ExponentIndicator 89 | = "e"i 90 | 91 | SignedInteger 92 | = [+-]? DecimalDigit+ 93 | 94 | // Unicode ////////////////////// 95 | // Separator, Space 96 | Zs = [\u0020\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000] -------------------------------------------------------------------------------- /testing/hilbert_livecoding/hilbert-experiment-01.js: -------------------------------------------------------------------------------- 1 | // L-systems test for manual Hilbert drawing 2 | // Uses a modified verion of the Lindenmayer library for JavaScript that uses ES6 Generators 3 | // https://github.com/pixelpusher/lindenmayer 4 | 5 | // print onto acrylic held in place by clamps in the printer bed 6 | await lp.sync() 7 | lp.start(195); 8 | lp.bed(60); // turn off heat, will warp acrylic 9 | 10 | attachScript("http://localhost:8888/static/lib/linden/linden.js"); 11 | 12 | // startup stuff - move somewhere out of the way 13 | # mov2 x: lp.cx y: lp.cy z: 0.2 # 14 | # mov2 x: 222 y: lp.cy z: 40 # 15 | 16 | // prime filament 17 | for (let i = 0; i < 2; i++) 18 | # ext e: 10 speed: 8 19 | 20 | lp.retract(10); // retract to stop leaking 21 | 22 | global nextNote = () => 60 + Math.round(Math.random() * 3) * 2; 23 | global noteDuration = 300; 24 | 25 | lp.m2s(nextNote()); // note 60 26 | loginfo(lp.t2mm(noteDuration)); // 1.6674721983442735 27 | loginfo(lp.printSpeed); // 5.558240661147578 28 | 29 | 30 | // Now initialize the L-System to generate the Hilbert curve 31 | global hilbert = new LSystem({ 32 | axiom: 'L', 33 | ignoredSymbols: "", 34 | productions: { 35 | 'L': "+RF-LFL-FR+", 36 | 'R': "-LF+RFR+FL-", 37 | }, 38 | finals: { 39 | 'F': async () => { await lp.m2s(nextNote()).t2d(noteDuration).go(1, false); return 1; }, 40 | '+': async () => { lp.turn(-90); return 0; }, 41 | '-': async () => { lp.turn(90); return 0; } 42 | } 43 | }); 44 | 45 | // Run the iterations of the L-System - each one makes it more complex 46 | hilbert.iterate(1); 47 | 48 | loginfo(hilbert.getString()); // print some info 49 | 50 | // draw each layer of the Hilbert curve (as a block, or manually) 51 | { 52 | await lp.bed(60); 53 | 54 | lp.maxTimePerOperation = 45; 55 | 56 | # ext e: 20 speed: 4 57 | # up 80 | speed 80 | go 58 | # retract 10 59 | 60 | lp.lh = 0.15; 61 | 62 | let layer = 1; // current index of layer we're printing 63 | await lp.moveto({ x: 20, y: 160, z: layer * lp.lh, speed: 80 }); // set this to where you want to print and how high 64 | 65 | //lp.speed(10); // set speed conservatively 66 | await lp.unretract(10); 67 | await lp.wait(500); 68 | lp.turnto(0); // turn to initial angle (facing right) 69 | global runner = await hilbert.run(); // start running the hilbert fractal 70 | await lp.retract(10); // stop material dripping 71 | await lp.up(5).go(); // move head up for next layer, or done! 72 | } -------------------------------------------------------------------------------- /testing/hilbert_livecoding/hilbert-experiment-02.js: -------------------------------------------------------------------------------- 1 | // L-systems test for manual Hilbert drawing 2 | // Uses a modified verion of the Lindenmayer library for JavaScript that uses ES6 Generators 3 | // https://github.com/pixelpusher/lindenmayer 4 | 5 | // print onto acrylic held in place by clamps in the printer bed 6 | await lp.sync() 7 | lp.start(195); 8 | lp.bed(60); // turn off heat, will warp acrylic 9 | 10 | attachScript("http://localhost:8888/static/lib/linden/linden.js"); 11 | 12 | // startup stuff - move somewhere out of the way 13 | # mov2 x: lp.cx y: lp.cy z: 0.2 # 14 | # mov2 x: 222 y: lp.cy z: 40 # 15 | 16 | // prime filament 17 | for (let i = 0; i < 2; i++) 18 | # ext e: 10 speed: 8 19 | 20 | lp.retract(10); // retract to stop leaking 21 | 22 | global nextNote = () => 72 + Math.round(Math.random() * 3) * 2 + Math.ceil(Math.random()) * 12; 23 | 24 | console.log(nextNote()); 25 | 26 | lp.m2s(nextNote() / 2); 27 | loginfo(lp.t2mm(1000)); // from 1.9651348314821258 to 2.3369523235382172 28 | 29 | // print speeds in mm/s: 30 | // 1.9651348314821258 31 | // 2.0819878293952856 32 | // 2.2057892681495215 33 | // 2.3369523235382172 34 | 35 | 36 | 37 | // Now initialize the L-System to generate the Hilbert curve 38 | global hilbert = new LSystem({ 39 | axiom: 'L', 40 | ignoredSymbols: "", 41 | productions: { 42 | 'L': "+RF-LFL-FR+", 43 | 'R': "-LF+RFR+FL-", 44 | }, 45 | finals: { 46 | 'F': async () => { await lp.m2s(nextNote() / 2).t2d(1000).go(1, false); return 1; }, 47 | '+': async () => { lp.turn(-90); return 0; }, 48 | '-': async () => { lp.turn(90); return 0; } 49 | } 50 | }); 51 | 52 | // Run the iterations of the L-System - each one makes it more complex 53 | hilbert.iterate(1); 54 | 55 | loginfo(hilbert.getString()); // print some info 56 | 57 | // draw each layer of the Hilbert curve (as a block, or manually) 58 | { 59 | await lp.bed(60); 60 | 61 | # ext e: 10 speed: 10 62 | # up 100 | speed 80 | go 63 | # retract 10 64 | 65 | lp.lh = 0.15; 66 | 67 | let layer = 1; // current index of layer we're printing 68 | await lp.moveto({ x: 20, y: 120, z: 0.1 * layer, speed: 80 }); // set this to where you want to print and how high 69 | 70 | //lp.speed(10); // set speed conservatively 71 | await lp.unretract(10); // unretract material for printing 72 | # ext e: 5 speed: 10 # 73 | await lp.wait(500); 74 | lp.turnto(0); // turn to initial angle (facing right) 75 | global runner = await hilbert.run(); // start running the hilbert fractal 76 | 77 | /* These let you step through in chunks, instead of the whole thing 78 | for (let ctr of numrange(1,5)) { // do 5 steps, some steps are just turns 79 | let p = runner.next().value; // this steps through 80 | // loginfo(p.index + "/" + p.total) // log the count to the side window - can be slow 81 | */ 82 | await lp.retract(10); // stop material dripping 83 | await lp.up(5).go(); // move head up for next layer, or done! 84 | } -------------------------------------------------------------------------------- /testing/iterator-apr-14-1.js: -------------------------------------------------------------------------------- 1 | // LivePrinter test April 14 2023 2pm version 1 2 | 3 | # start 4 | 5 | # mov2 x:lp.maxx*0.85 y:lp.maxy*0.1 z:60 6 | 7 | # bed 65 | temp 215 8 | 9 | # sync 10 | 11 | # up 50 12 | 13 | # ext e:4 speed:3 14 | 15 | # retract 16 | 17 | const { concat, cycle, take, reverse, range, butLast, partitition } = await import("https://cdn.skypack.dev/@thi.ng/iterators"); 18 | 19 | const { zigzagColumns2d } = await import('https://cdn.skypack.dev/@thi.ng/grid-iterators'); 20 | const cos = Math.cos; 21 | const sin = Math.sin; 22 | 23 | const PI = Math.PI; 24 | const TAU = Math.PI*2; 25 | const PTS = 30; // grid points -- 45 is nice too 26 | const pad = 10; 27 | const W = Math.ceil((lp.maxx/2-2*pad) / (PTS+1)); 28 | const H = Math.ceil((lp.maxy/2-2*pad) / (PTS+1)); 29 | 30 | const offsetx = lp.maxx*0.05; 31 | const offsety = lp.maxy*0.01; 32 | 33 | loginfo(`w:${W} h:${H}`); 34 | 35 | // avoid repeating front and end points 36 | const forwardgrid = 37 | butLast(zigzagColumns2d({ cols: PTS })); 38 | 39 | // grid in reverse 40 | const reversegrid = butLast(reverse(zigzagColumns2d({ cols: PTS }))); 41 | 42 | // create infinite repetition of the combined fwd & reverse sequence 43 | const grid = cycle(concat(forwardgrid, reversegrid)); 44 | 45 | function mapping(i, [x,y]) { 46 | const zangle = TAU*lp.z/50; 47 | const zamt = lp.t2mm("1/16b")/2; 48 | return [ 49 | (x+sin(zangle/2)*0.33*cos(5*PTS*PI*i/10)) * W + zamt*cos(zangle) + pad + offsetx, 50 | (y+sin(zangle/2)*0.5*cos(6*PI*i/PTS))* H + zamt*sin(zangle) + pad + offsety 51 | ]; 52 | } 53 | 54 | const TPS = PTS*PTS; 55 | 56 | # mov2 x:lp.maxx*0.5 y:lp.maxy*0.1 z:2.8 | thick 0.25 | interval "1/16b" | unretract 57 | 58 | // 20 layers back and forth 59 | for (let ctr=0; ctr < TPS*20; ctr++) { 60 | if (ctr % TPS === 0 ) { 61 | # up 0.2 62 | } 63 | 64 | const coords = grid.next().value; 65 | 66 | let [x,y] = mapping(ctr, [coords[0], coords[1]]); 67 | 68 | loginfo(`x:${x} y:${y}`); 69 | 70 | let beats = "1/2b"; 71 | 72 | # bpm 135 | to x:x y:y t:beats | draw 73 | 74 | } 75 | 76 | # up 50 77 | 78 | # retract 79 | -------------------------------------------------------------------------------- /testing/iterator-closed-symmetry-M-creatlity-big.js: -------------------------------------------------------------------------------- 1 | // BIG W for creatlity -- note needs 112500 baud 2 | // april 25 4:50pm 3 | // leveling was tough -- needs to be close to bed, thickness needs 4 | // more maybe because of diff firmware calc from Ultimaker 2 5 | 6 | # gcode "G28" | temp 220 | bed 75 7 | 8 | # gcode "G29" // 4 point calibration 9 | 10 | 11 | # thick 0.3 | draw 140 12 | 13 | # mov2 x:lp.maxx*0.2 y:lp.maxy*0.2 z:0.4 14 | 15 | # ext e:12 speed:3 16 | 17 | # retract 18 | 19 | // April 17, 19:54 20 | // last one, symmetry but fixed level up bug (should be on 1st pt) 21 | // makes an enclosed M shape 22 | 23 | # bpm 60 // set bpm for piece 24 | 25 | const { concat, cycle, take, reverse, range, butLast, partition } = await import("https://cdn.skypack.dev/@thi.ng/iterators"); 26 | 27 | const { zigzagColumns2d } = await import('https://cdn.skypack.dev/@thi.ng/grid-iterators'); 28 | const cos = Math.cos; 29 | const sin = Math.sin; 30 | 31 | const PI = Math.PI; 32 | const TAU = Math.PI*2; 33 | 34 | const COLS = 4; // grid points -- 45 is nice too 35 | const ROWS = 4; 36 | // total points 37 | const TPS = (ROWS*COLS); // avoid last dupes 38 | 39 | const pad = 0; 40 | 41 | //const W = Math.ceil((lp.maxx/3-2*pad) / (ROWS+1)); 42 | //const H = Math.ceil((lp.maxy/3-2*pad) / (ROWS+1)); 43 | 44 | const W = Math.ceil((lp.maxx*0.7) / (ROWS+1)); 45 | const H = Math.ceil((lp.maxy*0.5) / (ROWS+1)); 46 | 47 | 48 | loginfo(`w:${W}/h:${H}`); 49 | 50 | const offsetx = lp.maxx*0.2; 51 | const offsety = lp.maxy*0.3; 52 | 53 | const minz = 0.1; // start z 54 | const minthick = 0.36; 55 | 56 | // avoid repeating front and end points 57 | const forwardgrid = 58 | butLast(zigzagColumns2d({ cols: COLS, rows: ROWS })); 59 | 60 | // grid in reverse 61 | const reversegrid = butLast(reverse(zigzagColumns2d({ cols: COLS, rows: ROWS }))); 62 | 63 | // create infinite repetition of the combined fwd & reverse sequence 64 | const grid = cycle(concat(forwardgrid, reversegrid)); 65 | 66 | const beat = "1b"; 67 | 68 | const beatHeight = lp.t2mm(beat); 69 | 70 | function mapping(i, [x,y]) { 71 | const zangle = PI*(lp.z - minz)/beatHeight; 72 | const ramp =sin(zangle); 73 | 74 | return [ 75 | (x+ramp*0.25*(0.6*sin(PI*i/TPS)+sin(237*PI*i/TPS))) * W + pad + offsetx, 76 | (y+ramp*0.33*(0.8*sin(PI*(i+25)/TPS)+cos(353*PI*i/TPS)))* H + pad + offsety 77 | ]; 78 | } 79 | 80 | # mov2 x:offsetx y:offsety z:minz | interval "1/16b" | unretract 81 | 82 | 83 | loginfo(`beatheight: ${beatHeight}`); 84 | 85 | while (lp.z < beatHeight+minz) 86 | { 87 | const pctDone = (lp.z-minz)/beatHeight; 88 | const newthick = minthick - 0.05*pctDone; 89 | const fansp = Math.min(100, Math.floor(150*pctDone)); 90 | 91 | for (let ctr=0; ctr<(TPS-1); ctr++) { 92 | 93 | if (window.bail) { 94 | # retract | speed 50 | up 50 95 | return; //safety stop 96 | } 97 | 98 | //loginfo(`${newthick}`); 99 | 100 | if (ctr === 1) { 101 | # up 0.2 | fan fansp | thick newthick 102 | } 103 | 104 | const coords = grid.next().value; 105 | 106 | let [x,y] = mapping(ctr, [coords[0], coords[1]]); 107 | 108 | //loginfo(`${ctr}::${[x,y]}::${beat}`); 109 | 110 | # to x:x y:y t:beat | draw 111 | }; 112 | 113 | } 114 | 115 | # retract | speed 50 | up 50 116 | 117 | //----------------------------------------------s 118 | 119 | 120 | 121 | # up 50 122 | 123 | # moveto x:lp.cx/2 y:lp.cy-95 | downto 0.37 124 | # autoretract 0 | fan 0 | unretract | turnto 0 | printspeed 15 | draw 40 | fan 100 | elev 35 | printspeed 20 | drawup 3 | retract 12 | wait 1500 | unretract | elev -35 | printspeed 18 | drawdown 3 | printspeed 20 | draw 30 | retract 6 | fan 0 125 | 126 | # unretract | drawdown 5 | printspeed 20 | draw 30 | retract 8 127 | 128 | 129 | await repeat(6, async (i) => { 130 | # autoretract 0 131 | # unretract 132 | await repeat(4, async (i) => { 133 | ## 134 | turn 90 135 | draw 40 136 | ## 137 | }); 138 | # up lp.lh 139 | }); 140 | # retract | up 100 141 | 142 | psp => printspeed: set/get printSpeed 143 | tsp => travelspeed: set/get move speed 144 | 145 | 146 | -------------------------------------------------------------------------------- /testing/iterator-closed-symmetry-M-creatlity.js: -------------------------------------------------------------------------------- 1 | // for creatlity -- note needs 112500 baud 2 | // april 25 3:38pm 3 | // leveling was tough -- needs to be close to bed, thickness needs 4 | // more maybe because of diff firmware calc from Ultimaker 2 5 | 6 | # gcode "G28" | temp 220 | bed 70 7 | 8 | # gcode "G29" // 4 point calibration 9 | 10 | 11 | # thick 0.3 | draw 140 12 | 13 | # mov2 x:lp.maxx*0.2 y:lp.maxy*0.2 z:0.4 14 | 15 | # ext e:12 speed:3 16 | 17 | # retract 18 | 19 | // April 17, 19:54 20 | // last one, symmetry but fixed level up bug (should be on 1st pt) 21 | // makes an enclosed M shape 22 | 23 | # bpm 60 // set bpm for piece 24 | 25 | const { concat, cycle, take, reverse, range, butLast, partitition } = await import("https://cdn.skypack.dev/@thi.ng/iterators"); 26 | 27 | const { zigzagColumns2d } = await import('https://cdn.skypack.dev/@thi.ng/grid-iterators'); 28 | const cos = Math.cos; 29 | const sin = Math.sin; 30 | 31 | const PI = Math.PI; 32 | const TAU = Math.PI*2; 33 | 34 | const COLS = 4; // grid points -- 45 is nice too 35 | const ROWS = 4; 36 | // total points 37 | const TPS = (ROWS*COLS); // avoid last dupes 38 | 39 | const pad = 0; 40 | 41 | //const W = Math.ceil((lp.maxx/3-2*pad) / (ROWS+1)); 42 | //const H = Math.ceil((lp.maxy/3-2*pad) / (ROWS+1)); 43 | 44 | const W = Math.ceil((lp.maxx*0.4) / (ROWS+1)); 45 | const H = Math.ceil((lp.maxy*0.3) / (ROWS+1)); 46 | 47 | 48 | loginfo(`w:${W}/h:${H}`); 49 | 50 | const offsetx = lp.maxx*0.2; 51 | const offsety = lp.maxy*0.3; 52 | 53 | const minz = 0.12; // start z 54 | const minthick = 0.3; 55 | 56 | // avoid repeating front and end points 57 | const forwardgrid = 58 | butLast(zigzagColumns2d({ cols: COLS, rows: ROWS })); 59 | 60 | // grid in reverse 61 | const reversegrid = butLast(reverse(zigzagColumns2d({ cols: COLS, rows: ROWS }))); 62 | 63 | // create infinite repetition of the combined fwd & reverse sequence 64 | const grid = cycle(concat(forwardgrid, reversegrid)); 65 | 66 | const beat = "1/2b"; 67 | 68 | const beatHeight = lp.t2mm(beat)/2; 69 | 70 | function mapping(i, [x,y]) { 71 | const zangle = PI*(lp.z - minz)/beatHeight; 72 | const ramp =sin(zangle); 73 | 74 | return [ 75 | (x+ramp*0.25*(0.6*sin(PI*i/TPS)+sin(237*PI*i/TPS))) * W + pad + offsetx, 76 | (y+ramp*0.33*(0.8*sin(PI*(i+25)/TPS)+cos(353*PI*i/TPS)))* H + pad + offsety 77 | ]; 78 | } 79 | 80 | # mov2 x:offsetx y:offsety z:minz | interval "1/16b" | unretract 81 | 82 | 83 | loginfo(`beatheight: ${beatHeight}`); 84 | 85 | while (lp.z < beatHeight+minz) 86 | { 87 | const pctDone = (lp.z-minz)/beatHeight; 88 | const newthick = minthick - 0.05*pctDone; 89 | const fansp = Math.min(100, Math.floor(150*pctDone)); 90 | 91 | for (let ctr=0; ctr<(TPS-1); ctr++) { 92 | 93 | if (window.bail) { 94 | # retract | speed 50 | up 50 95 | return; //safety stop 96 | } 97 | 98 | //loginfo(`${newthick}`); 99 | 100 | if (ctr === 1) { 101 | # up 0.15 | fan fansp | thick newthick 102 | } 103 | 104 | const coords = grid.next().value; 105 | 106 | let [x,y] = mapping(ctr, [coords[0], coords[1]]); 107 | 108 | //loginfo(`${ctr}::${[x,y]}::${beat}`); 109 | 110 | # to x:x y:y t:beat | draw 111 | }; 112 | 113 | } 114 | 115 | # retract | speed 50 | up 50 116 | 117 | //----------------------------------------------s 118 | -------------------------------------------------------------------------------- /testing/iterator-closed-symmetry-M.js: -------------------------------------------------------------------------------- 1 | // April 17, 19:54 2 | // last one, symmetry but fixed level up bug (should be on 1st pt) 3 | // makes an enclosed M shape 4 | 5 | # bpm 110 // set bpm for piece 6 | 7 | const { concat, cycle, take, reverse, range, butLast, partitition } = await import("https://cdn.skypack.dev/@thi.ng/iterators"); 8 | 9 | const { zigzagColumns2d } = await import('https://cdn.skypack.dev/@thi.ng/grid-iterators'); 10 | const cos = Math.cos; 11 | const sin = Math.sin; 12 | 13 | const PI = Math.PI; 14 | const TAU = Math.PI*2; 15 | 16 | const COLS = 4; // grid points -- 45 is nice too 17 | const ROWS = 4; 18 | // total points 19 | const TPS = (ROWS*COLS); // avoid last dupes 20 | 21 | const pad = 0; 22 | 23 | //const W = Math.ceil((lp.maxx/3-2*pad) / (ROWS+1)); 24 | //const H = Math.ceil((lp.maxy/3-2*pad) / (ROWS+1)); 25 | 26 | const W = Math.ceil((lp.maxx*0.4) / (ROWS+1)); 27 | const H = Math.ceil((lp.maxy*0.3) / (ROWS+1)); 28 | 29 | 30 | loginfo(`w:${W}/h:${H}`); 31 | 32 | const offsetx = lp.maxx*0.4; 33 | const offsety = lp.maxy*0.06; 34 | 35 | const minz = 0.3; // start z 36 | const minthick = 0.15; 37 | 38 | // avoid repeating front and end points 39 | const forwardgrid = 40 | butLast(zigzagColumns2d({ cols: COLS, rows: ROWS })); 41 | 42 | // grid in reverse 43 | const reversegrid = butLast(reverse(zigzagColumns2d({ cols: COLS, rows: ROWS }))); 44 | 45 | // create infinite repetition of the combined fwd & reverse sequence 46 | const grid = cycle(concat(forwardgrid, reversegrid)); 47 | 48 | const beat = "1/2b"; 49 | 50 | const beatHeight = lp.t2mm(beat)/2; 51 | 52 | function mapping(i, [x,y]) { 53 | const zangle = PI*(lp.z - minz)/beatHeight; 54 | const ramp =sin(zangle); 55 | 56 | return [ 57 | (x+ramp*0.25*(0.6*sin(PI*i/TPS)+sin(237*PI*i/TPS))) * W + pad + offsetx, 58 | (y+ramp*0.33*(0.8*sin(PI*(i+25)/TPS)+cos(353*PI*i/TPS)))* H + pad + offsety 59 | ]; 60 | } 61 | 62 | # mov2 x:offsetx y:offsety z:minz | interval "1/16b" | unretract 63 | 64 | 65 | loginfo(`beatheight: ${beatHeight}`); 66 | 67 | while (lp.z < beatHeight+minz) 68 | { 69 | const pctDone = (lp.z-minz)/beatHeight; 70 | const newthick = minthick - 0.05*pctDone; 71 | const fansp = Math.min(100, Math.floor(150*pctDone)); 72 | 73 | for (let ctr=0; ctr<(TPS-1); ctr++) { 74 | 75 | if (window.bail) { 76 | # retract | speed 50 | up 50 77 | return; //safety stop 78 | } 79 | 80 | //loginfo(`${newthick}`); 81 | 82 | if (ctr === 1) { 83 | # up newthick | fan fansp | thick newthick 84 | } 85 | 86 | const coords = grid.next().value; 87 | 88 | let [x,y] = mapping(ctr, [coords[0], coords[1]]); 89 | 90 | //loginfo(`${ctr}::${[x,y]}::${beat}`); 91 | 92 | # to x:x y:y t:beat | draw 93 | }; 94 | 95 | } 96 | 97 | # retract | speed 50 | up 50 98 | -------------------------------------------------------------------------------- /testing/iterator-closed-symmetry-W+may13.js: -------------------------------------------------------------------------------- 1 | // BIG W+ at home May 13th 2023 2 | 3 | # gcode "G28" | temp 225 | bed 75 4 | 5 | # temp 230 6 | 7 | minthick = 0.15 8 | 9 | # fan 70 10 | 11 | global bail = true 12 | 13 | # sync 14 | 15 | # thick 0.3 | draw 140 16 | 17 | # mov2 x:lp.maxx*0.2 y:lp.maxy*0.2 z:60.4 18 | 19 | # ext e:10 speed:1 20 | 21 | # retract 22 | 23 | // April 17, 19:54 24 | // last one, symmetry but fixed level up bug (should be on 1st pt) 25 | // makes an enclosed M shape 26 | 27 | # bpm 60 // set bpm for piece 28 | 29 | const { concat, cycle, take, reverse, range, butLast, partition } = await import("https://cdn.skypack.dev/@thi.ng/iterators"); 30 | 31 | const { zigzagColumns2d } = await import('https://cdn.skypack.dev/@thi.ng/grid-iterators'); 32 | const cos = Math.cos; 33 | const sin = Math.sin; 34 | 35 | const PI = Math.PI; 36 | const TAU = Math.PI*2; 37 | 38 | const COLS = 6; // grid points -- 45 is nice too 39 | const ROWS = 6; 40 | // total points 41 | const TPS = (ROWS*COLS); // avoid last dupes 42 | 43 | const pad = 0; 44 | 45 | //const W = Math.ceil((lp.maxx/3-2*pad) / (ROWS+1)); 46 | //const H = Math.ceil((lp.maxy/3-2*pad) / (ROWS+1)); 47 | 48 | const W = Math.ceil((lp.maxx*0.8) / (ROWS+1)); 49 | const H = Math.ceil((lp.maxy*0.6) / (ROWS+1)); 50 | 51 | 52 | loginfo(`w:${W}/h:${H}`); 53 | 54 | const offsetx = lp.maxx*0.1; 55 | const offsety = lp.maxy*0.2; 56 | 57 | const minz = 0.1; // start z 58 | global minthick = 0.18; 59 | 60 | // avoid repeating front and end points 61 | const forwardgrid = 62 | butLast(zigzagColumns2d({ cols: COLS, rows: ROWS })); 63 | 64 | // grid in reverse 65 | const reversegrid = butLast(reverse(zigzagColumns2d({ cols: COLS, rows: ROWS }))); 66 | 67 | // create infinite repetition of the combined fwd & reverse sequence 68 | const grid = cycle(concat(forwardgrid, reversegrid)); 69 | 70 | global beat = "1b"; 71 | 72 | const beatHeight = lp.t2mm(beat); 73 | 74 | function mapping(i, [x,y]) { 75 | const zangle = PI*(lp.z - minz)/beatHeight; 76 | const ramp =sin(zangle); 77 | 78 | return [ 79 | (x+ramp*0.45*(0.6*sin(PI*i/TPS)+sin(237*PI*i/TPS))) * W + pad + offsetx, 80 | (y+ramp*0.40*(0.8*sin(PI*(i+25)/TPS)+cos(353*PI*i/TPS)))* H + pad + offsety 81 | ]; 82 | } 83 | 84 | # mov2 x:offsetx y:offsety z:minz | interval "1/16b" | unretract 85 | 86 | 87 | loginfo(`beatheight: ${beatHeight}`); 88 | 89 | while (lp.z < beatHeight+minz) 90 | { 91 | const pctDone = (lp.z-minz)/beatHeight; 92 | const newthick = minthick - 0.05*pctDone; 93 | const fansp = Math.min(100, Math.floor(150*pctDone)); 94 | 95 | for (let ctr=0; ctr<(TPS-1); ctr++) { 96 | 97 | if (window.bail) { 98 | # retract | speed 50 | up 50 99 | return; //safety stop 100 | } 101 | 102 | //loginfo(`${newthick}`); 103 | 104 | if (ctr === 1) { 105 | # up minthick 106 | } 107 | 108 | const coords = grid.next().value; 109 | 110 | let [x,y] = mapping(ctr, [coords[0], coords[1]]); 111 | 112 | //loginfo(`${ctr}::${[x,y]}::${beat}`); 113 | 114 | # to x:x y:y t:beat | draw 115 | }; 116 | 117 | } 118 | 119 | # retract | speed 50 | up 50 120 | 121 | //----------------------------------------------s 122 | 123 | -------------------------------------------------------------------------------- /testing/iterator-closed-symmetry.js: -------------------------------------------------------------------------------- 1 | // LivePrinter test April 17 2023 18:53 2 | // closed sinusoidal with accidental symmetry 3 | 4 | # start | bed 60 | temp 220 5 | 6 | # bed 60 7 | 8 | # mov2 x:lp.maxx*0.4 y:lp.maxy*0.62 z:5 speed: 80 9 | 10 | # ext e:10 speed:3 11 | 12 | # sync 13 | 14 | # retract 15 | 16 | # fan 0 17 | 18 | # bpm 100 // set bpm for piece 19 | 20 | const { concat, cycle, take, reverse, range, butLast, partitition } = await import("https://cdn.skypack.dev/@thi.ng/iterators"); 21 | 22 | const { zigzagColumns2d } = await import('https://cdn.skypack.dev/@thi.ng/grid-iterators'); 23 | const cos = Math.cos; 24 | const sin = Math.sin; 25 | 26 | const PI = Math.PI; 27 | const TAU = Math.PI*2; 28 | 29 | const COLS = 4; // grid points -- 45 is nice too 30 | const ROWS = 8; 31 | // total points 32 | const TPS = (ROWS*COLS); // avoid last dupes 33 | 34 | const pad = 0; 35 | 36 | //const W = Math.ceil((lp.maxx/3-2*pad) / (ROWS+1)); 37 | //const H = Math.ceil((lp.maxy/3-2*pad) / (ROWS+1)); 38 | 39 | const W = Math.ceil((lp.maxx*0.4) / (ROWS+1)); 40 | const H = Math.ceil((lp.maxy*0.3) / (ROWS+1)); 41 | 42 | 43 | loginfo(`w:${W}/h:${H}`); 44 | 45 | const offsetx = lp.maxx*0.4; 46 | const offsety = lp.maxy*0.06; 47 | 48 | const minz = 0.3; // start z 49 | const minthick = 0.12; 50 | 51 | // avoid repeating front and end points 52 | const forwardgrid = 53 | butLast(zigzagColumns2d({ cols: COLS, rows: ROWS })); 54 | 55 | // grid in reverse 56 | const reversegrid = butLast(reverse(zigzagColumns2d({ cols: COLS, rows: ROWS }))); 57 | 58 | // create infinite repetition of the combined fwd & reverse sequence 59 | const grid = cycle(concat(forwardgrid, reversegrid)); 60 | 61 | const beat = "1/2b"; 62 | 63 | const beatHeight = lp.t2mm(beat)/2; 64 | 65 | function mapping(i, [x,y]) { 66 | const zangle = PI*(lp.z - minz)/beatHeight; 67 | const ramp =sin(zangle); 68 | 69 | return [ 70 | (x+ramp*0.25*(0.6*sin(PI*i/TPS)+sin(237*PI*i/TPS))) * W + pad + offsetx, 71 | (y+ramp*0.33*(0.8*sin(PI*(i+25)/TPS)+cos(353*PI*i/TPS)))* H + pad + offsety 72 | ]; 73 | } 74 | 75 | # mov2 x:offsetx y:offsety z:minz | interval "1/16b" | unretract 76 | 77 | 78 | loginfo(`beatheight: ${beatHeight}`); 79 | 80 | let f = true; 81 | 82 | let index=0; 83 | 84 | while (lp.z < beatHeight+minz) 85 | { 86 | const pctDone = (lp.z-minz)/beatHeight; 87 | const newthick = minthick - 0.05*pctDone; 88 | const fansp = Math.min(100, Math.floor(150*pctDone)); 89 | 90 | for (let ctr=0; ctr<(TPS-1); ctr++) { 91 | 92 | if (window.bail) { 93 | # retract | speed 50 | up 50 94 | return; //safety stop 95 | } 96 | 97 | //loginfo(`${newthick}`); 98 | 99 | if (!f && ctr === 1) { 100 | # up newthick | fan fansp | thick newthick 101 | 102 | f = true; // flip to go up every other time because of this pattern's symmetry 103 | } 104 | 105 | const coords = grid.next().value; 106 | 107 | let [x,y] = mapping(ctr, [coords[0], coords[1]]); 108 | 109 | //loginfo(`${ctr}::${[x,y]}::${beat}`); 110 | 111 | # to x:x y:y t:beat | draw 112 | }; 113 | f = false; 114 | 115 | } 116 | 117 | # retract | speed 50 | up 50 118 | 119 | 120 | 121 | delete window.bail; 122 | -------------------------------------------------------------------------------- /testing/iteratorgrid-ambient-long.js: -------------------------------------------------------------------------------- 1 | // this one was long and ambient -- apr. 17, 5:30pm 2 | // it was out of sync too... see later ones (up on 1st pt not 0th) 3 | 4 | 5 | # bpm 100 // set bpm for piece 6 | 7 | const { concat, cycle, take, reverse, range, butLast, partitition } = await import("https://cdn.skypack.dev/@thi.ng/iterators"); 8 | 9 | const { zigzagColumns2d } = await import('https://cdn.skypack.dev/@thi.ng/grid-iterators'); 10 | const cos = Math.cos; 11 | const sin = Math.sin; 12 | 13 | const PI = Math.PI; 14 | const TAU = Math.PI*2; 15 | 16 | const COLS = 8; // grid points -- 45 is nice too 17 | const ROWS = 32; 18 | // total points 19 | const TPS = (ROWS*COLS)-1; // avoid last dupes 20 | 21 | const pad = 0; 22 | 23 | //const W = Math.ceil((lp.maxx/3-2*pad) / (ROWS+1)); 24 | //const H = Math.ceil((lp.maxy/3-2*pad) / (ROWS+1)); 25 | 26 | const W = Math.ceil((lp.maxx*0.25) / (ROWS+1)); 27 | const H = Math.ceil((lp.maxy*0.75) / (ROWS+1)); 28 | 29 | 30 | loginfo(`w:${W}/h:${H}`); 31 | 32 | const offsetx = lp.maxx*0.25; 33 | const offsety = lp.maxy*0.15; 34 | 35 | const minz = 0.7; // start z 36 | const minthick = 0.15; 37 | 38 | // avoid repeating front and end points 39 | const forwardgrid = 40 | butLast(zigzagColumns2d({ cols: COLS, rows: ROWS })); 41 | 42 | // grid in reverse 43 | const reversegrid = butLast(reverse(zigzagColumns2d({ cols: COLS, rows: ROWS }))); 44 | 45 | // create infinite repetition of the combined fwd & reverse sequence 46 | const grid = cycle(concat(forwardgrid, reversegrid)); 47 | 48 | const beat = "1/4b"; 49 | 50 | const beatHeight = lp.t2mm(beat); 51 | 52 | function mapping(i, [x,y]) { 53 | const zangle = PI*(lp.z - minz)/beatHeight; 54 | const ramp =sin(zangle); 55 | 56 | return [ 57 | (x+ramp*0.4*(0.6*sin(PI*i/TPS)+sin(237*PI*i/TPS))) * W + pad + offsetx, 58 | (y+ramp*0.33*(0.8*sin(PI*(i+25)/TPS)+cos(353*PI*i/TPS)))* H + pad + offsety 59 | ]; 60 | } 61 | 62 | # mov2 x:offsetx y:offsety z:minz | interval "1/16b" | unretract 63 | 64 | 65 | loginfo(`beatheight: ${beatHeight}`); 66 | 67 | while (lp.z < beatHeight+minz) 68 | { 69 | const pctDone = (lp.z-minz)/beatHeight; 70 | 71 | for (let ctr=0; ctr<=TPS; ctr++) { 72 | 73 | if (window.bail) return; //safety stop 74 | 75 | const coords = grid.next().value; 76 | 77 | let [x,y] = mapping(ctr, [coords[0], coords[1]]); 78 | 79 | # to x:x y:y t:beat | draw 80 | 81 | } 82 | 83 | const newthick = minthick - 0.05*pctDone; 84 | const fansp = Math.min(100, Math.floor(150*pctDone)); 85 | 86 | # up newthick | fan fansp | thick newthick 87 | 88 | } 89 | 90 | # retract | speed 50 | up 50 91 | -------------------------------------------------------------------------------- /testing/iteratorgrid-smooth.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | # bpm 135 // set bpm for piece 4 | 5 | const { concat, cycle, take, reverse, range, butLast, partitition } = await import("https://cdn.skypack.dev/@thi.ng/iterators"); 6 | 7 | const { zigzagColumns2d } = await import('https://cdn.skypack.dev/@thi.ng/grid-iterators'); 8 | const cos = Math.cos; 9 | const sin = Math.sin; 10 | 11 | const PI = Math.PI; 12 | const TAU = Math.PI*2; 13 | 14 | const COLS = 4; // grid points -- 45 is nice too 15 | const ROWS = 16; 16 | // total points 17 | const TPS = (ROWS*COLS)-1; // avoid last dupes 18 | 19 | const pad = 0; 20 | 21 | const W = Math.ceil((lp.maxx/4-2*pad) / (ROWS+1)); 22 | const H = Math.ceil((lp.maxy/6-2*pad) / (ROWS+1)); 23 | 24 | loginfo(`w:${W}/h:${H}`); 25 | 26 | const offsetx = lp.maxx*0.44; 27 | const offsety = lp.maxy*0.42; 28 | 29 | const minz = 0.7; // start z 30 | const minthick = 0.15; 31 | 32 | // avoid repeating front and end points 33 | const forwardgrid = 34 | butLast(zigzagColumns2d({ cols: COLS, rows: ROWS })); 35 | 36 | // grid in reverse 37 | const reversegrid = butLast(reverse(zigzagColumns2d({ cols: COLS, rows: ROWS }))); 38 | 39 | // create infinite repetition of the combined fwd & reverse sequence 40 | const grid = cycle(concat(forwardgrid, reversegrid)); 41 | 42 | const beat = "1/4b"; 43 | 44 | const beatHeight = lp.t2mm(beat)/2; 45 | 46 | function mapping(i, [x,y]) { 47 | const zangle = PI/2*(lp.z - minz)/beatHeight; 48 | const ramp =sin(zangle); 49 | 50 | return [ 51 | (x+ramp*0.33*sin(33*PI*i/TPS)) * W + pad + offsetx, 52 | (y+ramp*0.25*cos(51*PI*i/TPS))* H + pad + offsety 53 | ]; 54 | } 55 | 56 | # mov2 x:offsetx y:offsety z:minz | interval "1/16b" | unretract 57 | 58 | 59 | loginfo(`beatheight: ${beatHeight}`); 60 | 61 | while (lp.z < beatHeight+minz) 62 | { 63 | const pctDone = (lp.z-minz)/beatHeight; 64 | 65 | for (let ctr=0; ctrv+(7+Math.round(Math.sin(PI/3*i)))); 25 | 26 | const prog = cycle( 27 | concat(butLast(notes), butLast(reverse(notes.slice()))) 28 | ); 29 | 30 | function mapping([x,y]) { 31 | return [(x+1+0.5*Math.sin(y*2.13*TAU/PTS)*Math.sin(y*6.74*TAU/TPS))*W+pad, (y+1+0.2*Math.cos(y*16.8*TAU/PTS))*H+pad]; 32 | } 33 | 34 | const TPS = PTS*PTS; 35 | 36 | // almost twice 37 | //let i = Math.floor(1.25*TPS); 38 | 39 | # mov2 x:lp.maxx*0.05 y:lp.maxy*0.1 z:0.25 | thick 0.25 | unretract 40 | 41 | for (let i=0; i majScale(n, 0)); 14 | global note = infiter(notes); // infinite iterator over array 15 | 16 | loginfo(note()); // if passed true, resets 17 | 18 | 19 | global startNote = 12; 20 | 21 | startNote = 12; 22 | startNote--; 23 | 24 | addTask("noteup", 8000, () => startNote += 2); 25 | 26 | (() => { 27 | const t = 250; 28 | addTask("majscale", t * 4, 29 | () => { 30 | repeat(4, (i) => lp.turn(45).m2s(majScale(startNote, 8 * i * Math.sin(i / 8))).t2d(t / 2).go()); 31 | lp.wait(t / 4); 32 | }); 33 | })(); 34 | 35 | removeTask("majscale"); 36 | 37 | 38 | lp.t2mm = (time, speed = lp.travelSpeed) => { 39 | return speed * time / 1000; // time in ms 40 | }; 41 | 42 | (() => { 43 | let bumpup = 0; 44 | addTask("zbump", 4000, 45 | () => { 46 | bumpup = (1 - bumpup); 47 | repeat(4, () => 48 | lp.m2s(majScale(0, 8)).up(lp.t2mm(50 * (bumpup * 2 - 1))).go() 49 | ); 50 | } 51 | ); 52 | })(); 53 | 54 | removeTask("zbump") -------------------------------------------------------------------------------- /testing/spirals.js: -------------------------------------------------------------------------------- 1 | 2 | //await repeat(notes.length, async (i) => { 3 | 4 | # mov2 x:lp.maxx*0.55 y:lp.maxy*0.65 | speed "C6" | unretract | turnto 0 5 | 6 | await repeat(steps, async (i) => { 7 | await drawSeg(); 8 | //let n = notes[i]; 9 | } ); 10 | 11 | # retract | tsp 80 | up 0.2 12 | //03/26/2023, 19:44:22 13 | 14 | const smallL = lp.parseAsTime("1/24b"); 15 | const bigL = lp.parseAsTime("1/2b"); 16 | const steps = 12; // 64 steps to 2PI or a full rotation 17 | const PI = Math.PI; 18 | const TAU = 2*PI; 19 | const sin = Math.sin; 20 | const cos = Math.cos; 21 | const abs = Math.abs; 22 | 23 | loginfo(bigL); 24 | 25 | let baseAngle = TAU/steps; 26 | 27 | let prevTheta = 0, 28 | blendAmt = 0.25; 29 | 30 | async function drawSeg() { 31 | let theta = PI/8*cos(lp.time/130+PI/15) + 32 | PI/6*cos(lp.time/120+PI/4.3) + 33 | PI/8*cos(lp.time/3.2) - 34 | PI/4*sin(lp.time*cos(lp.time*PI/3)/12.2); 35 | 36 | theta = (1-blendAmt)*theta + blendAmt*prevTheta; 37 | prevTheta = theta; 38 | 39 | theta = PI/4; 40 | 41 | if (theta > PI/8) { 42 | 43 | lp.turn(theta,true); 44 | 45 | await lp.drawtime(bigL); 46 | 47 | lp.turn(-theta,true); 48 | 49 | await lp.drawtime(smallL); 50 | 51 | lp.turn(theta+PI,true); 52 | await lp.drawtime(2*bigL); 53 | 54 | lp.turn(-theta - PI,true); 55 | await lp.drawtime(smallL); 56 | 57 | lp.turn(theta,true); 58 | await lp.drawtime(bigL); 59 | 60 | lp.turn(-theta,true); 61 | } 62 | else { 63 | await lp.drawtime(2*smallL); 64 | } 65 | lp.turn(baseAngle,true); 66 | //let m = 6; 67 | 68 | //lp.turn(baseAngle+(PI/64)*cos(m*PI*(timeCount/60))*sin(m*PI*(lp.time/60))); 69 | //if (dir > 2*PI) { 70 | // dir %= 2*PI; 71 | //baseAngle -= (PI/steps)/12; 72 | //} 73 | 74 | await lp.drawtime(8*smallL); 75 | } 76 | 77 | 78 | 79 | //await repeat(notes.length, async (i) => { 80 | 81 | # mov2 x:lp.maxx*0.55 y:lp.maxy*0.65 | speed "C6" | unretract | turnto 0 82 | 83 | await repeat(steps, async (i) => { 84 | await drawSeg(); 85 | //let n = notes[i]; 86 | } ); 87 | 88 | # retract | tsp 80 | up 0.2 89 | //03/26/2023, 19:44:45 90 | const smallL = lp.parseAsTime("1/24b"); 91 | const bigL = lp.parseAsTime("1/2b"); 92 | const steps = 12; // 64 steps to 2PI or a full rotation 93 | const PI = Math.PI; 94 | const TAU = 2*PI; 95 | const sin = Math.sin; 96 | const cos = Math.cos; 97 | const abs = Math.abs; 98 | 99 | loginfo(bigL); 100 | 101 | let baseAngle = TAU/steps; 102 | 103 | let prevTheta = 0, 104 | blendAmt = 0.25; 105 | 106 | async function drawSeg() { 107 | let theta = PI/8*cos(lp.time/130+PI/15) + 108 | PI/6*cos(lp.time/120+PI/4.3) + 109 | PI/8*cos(lp.time/3.2) - 110 | PI/4*sin(lp.time*cos(lp.time*PI/3)/12.2); 111 | 112 | theta = (1-blendAmt)*theta + blendAmt*prevTheta; 113 | prevTheta = theta; 114 | 115 | theta = PI/4; 116 | 117 | if (theta > PI/8) { 118 | 119 | lp.turn(theta,true); 120 | 121 | await lp.drawtime(bigL); 122 | 123 | lp.turn(-theta,true); 124 | 125 | await lp.drawtime(smallL); 126 | 127 | lp.turn(theta+PI,true); 128 | await lp.drawtime(2*bigL); 129 | 130 | lp.turn(-theta - PI,true); 131 | await lp.drawtime(smallL); 132 | 133 | lp.turn(theta,true); 134 | await lp.drawtime(bigL); 135 | 136 | lp.turn(-theta,true); 137 | } 138 | else { 139 | await lp.drawtime(2*smallL); 140 | } 141 | lp.turn(baseAngle,true); 142 | //let m = 6; 143 | 144 | //lp.turn(baseAngle+(PI/64)*cos(m*PI*(timeCount/60))*sin(m*PI*(lp.time/60))); 145 | //if (dir > 2*PI) { 146 | // dir %= 2*PI; 147 | //baseAngle -= (PI/steps)/12; 148 | //} 149 | 150 | await lp.drawtime(8*smallL); 151 | } 152 | 153 | 154 | -------------------------------------------------------------------------------- /testing/stresstest.js: -------------------------------------------------------------------------------- 1 | 2 | // API stress test! 3 | 4 | let layer = 0; // current index of layer we're printing 5 | await lp.moveto({ x: 60, y: 100, z: 0.15 + 0.2 * layer, speed: 80 }); 6 | lp.turnto(0); 7 | let i = 0; 8 | while (i < 22) { 9 | await lp.dist(50).speed(40).go(1, false); 10 | lp.turn(90); 11 | i++; 12 | if (i % 4 == 0) { 13 | layer++; // current index of layer we're printing 14 | await lp.moveto({ x: 60, y: 100, z: 0.15 + 0.2 * layer, speed: 80 }); 15 | } 16 | } 17 | 18 | 19 | await lp.speed(80).up(40).go() 20 | 21 | for (let i = 0; i < 10; i++) { 22 | let d = i % 2 ? 10 : -10; 23 | if (i % 2) lp.turnto(180); 24 | else lp.turnto(0); 25 | await lp.dist(40).speed(120).go(0); 26 | await s.sendGCode("M400"); 27 | 28 | updateGUI(); 29 | await lp.speed(80).up(d).go(); 30 | await s.sendGCode("M400"); 31 | updateGUI(); 32 | } 33 | -------------------------------------------------------------------------------- /testing/test_gcode.txt: -------------------------------------------------------------------------------- 1 | T0 2 | M82 ;absolute extrusion mode 3 | 4 | G92 E0 5 | M109 S205 6 | G0 F15000 X9 Y6 Z2 7 | G280 8 | G1 F1500 E-6.5 9 | ;LAYER_COUNT:255 10 | ;LAYER:0 11 | M107 12 | M204 S625 13 | M205 X6 Y6 14 | G0 F4285.7 X96.96 Y87.04 Z0.27 15 | M204 S500 16 | M205 X5 Y5 17 | ;TYPE:SKIRT 18 | G1 F1500 E0 19 | G1 F1200 X97.338 Y86.695 E0.00758 20 | G1 X97.478 Y86.519 E0.01091 21 | G1 X97.877 Y86.087 E0.01962 22 | G1 X98.407 Y85.573 E0.03056 23 | G1 X98.763 Y85.295 E0.03725 24 | G1 X98.848 Y85.204 E0.0391 25 | G1 X99.382 Y84.693 E0.05004 26 | G1 X99.967 Y84.243 E0.06098 27 | G1 X100.192 Y84.107 E0.06487 28 | G1 X100.591 Y83.731 E0.07299 29 | G1 X101.18 Y83.286 E0.08393 30 | G1 X101.814 Y82.906 E0.09488 31 | G1 X101.851 Y82.888 E0.09549 32 | G1 X102.42 Y82.463 E0.10601 33 | G1 X103.384 Y81.927 E0.12235 34 | G1 X103.825 Y81.727 E0.12952 35 | G1 X104.092 Y81.55 E0.13427 36 | G1 X104.729 Y81.176 E0.14521 37 | G1 X105.403 Y80.873 E0.15615 38 | G1 X105.665 Y80.795 E0.1602 39 | M204 S625 40 | M205 X6 Y6 41 | G0 F4285.7 X97.222 Y87.272 42 | M204 S500 43 | M205 X5 Y5 44 | G1 F1200 X97.576 Y86.953 E2.47501 --------------------------------------------------------------------------------