├── .gitignore ├── vsvg ├── simple │ ├── readme.md │ ├── src │ │ └── main.rs │ ├── Cargo.toml │ └── examples │ │ ├── pattern_1.rs │ │ ├── christmas_tree.rs │ │ ├── circles2.rs │ │ ├── snowfl.rs │ │ ├── mst.rs │ │ └── circles.rs └── dither_hatch │ ├── Cargo.toml │ └── src │ └── main.rs ├── README.md ├── other_testplots ├── readme.md ├── DMP-60.png ├── dmp_60.png ├── hp7585.png ├── hp7475a.png ├── roland_2.png ├── hp_draft_pro.png ├── roland-dxy-880.png ├── tektronix_4662.png ├── dxy-800_dxy-101.png ├── roland_dpx-2000.png ├── roland_dpx-3300.png ├── calcomp_artisan_102x.png └── atari_1020_color_printer.png ├── notes ├── pictures │ ├── supplies │ │ └── combined_b.jpg │ └── plotter-hardware │ │ ├── lumenpnp.jpg │ │ ├── pinion.jpg │ │ ├── iv_project.jpg │ │ └── idraw_z_axis.jpg ├── vintage_plotter_links.md ├── random_links.md ├── plotter_notes.csv ├── protocols.md ├── diy_plotters.md ├── prtocols_big.csv └── plotter_hardware_designs.md ├── test_plot ├── Readme.md └── shift_compact.svg └── turtletoy ├── testTurtle.js ├── flower.js ├── hex_grid.js ├── sprio1.js ├── truchet_snowflakes.js ├── glyph.js └── rain.js /.gitignore: -------------------------------------------------------------------------------- 1 | vsvg/*/target 2 | turtletoy/node_modules/ 3 | -------------------------------------------------------------------------------- /vsvg/simple/readme.md: -------------------------------------------------------------------------------- 1 | Running: `cargo run --example ` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plotter_sketches 2 | Various sketches for pen plotters 3 | -------------------------------------------------------------------------------- /vsvg/simple/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("See examples !"); 3 | } 4 | -------------------------------------------------------------------------------- /other_testplots/readme.md: -------------------------------------------------------------------------------- 1 | Collection of demo and test prints from various pen plotters. -------------------------------------------------------------------------------- /other_testplots/DMP-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/DMP-60.png -------------------------------------------------------------------------------- /other_testplots/dmp_60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/dmp_60.png -------------------------------------------------------------------------------- /other_testplots/hp7585.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/hp7585.png -------------------------------------------------------------------------------- /other_testplots/hp7475a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/hp7475a.png -------------------------------------------------------------------------------- /other_testplots/roland_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/roland_2.png -------------------------------------------------------------------------------- /other_testplots/hp_draft_pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/hp_draft_pro.png -------------------------------------------------------------------------------- /other_testplots/roland-dxy-880.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/roland-dxy-880.png -------------------------------------------------------------------------------- /other_testplots/tektronix_4662.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/tektronix_4662.png -------------------------------------------------------------------------------- /other_testplots/dxy-800_dxy-101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/dxy-800_dxy-101.png -------------------------------------------------------------------------------- /other_testplots/roland_dpx-2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/roland_dpx-2000.png -------------------------------------------------------------------------------- /other_testplots/roland_dpx-3300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/roland_dpx-3300.png -------------------------------------------------------------------------------- /notes/pictures/supplies/combined_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/notes/pictures/supplies/combined_b.jpg -------------------------------------------------------------------------------- /other_testplots/calcomp_artisan_102x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/calcomp_artisan_102x.png -------------------------------------------------------------------------------- /notes/pictures/plotter-hardware/lumenpnp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/notes/pictures/plotter-hardware/lumenpnp.jpg -------------------------------------------------------------------------------- /notes/pictures/plotter-hardware/pinion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/notes/pictures/plotter-hardware/pinion.jpg -------------------------------------------------------------------------------- /other_testplots/atari_1020_color_printer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/other_testplots/atari_1020_color_printer.png -------------------------------------------------------------------------------- /notes/pictures/plotter-hardware/iv_project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/notes/pictures/plotter-hardware/iv_project.jpg -------------------------------------------------------------------------------- /notes/pictures/plotter-hardware/idraw_z_axis.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karliss/plotter_sketches/HEAD/notes/pictures/plotter-hardware/idraw_z_axis.jpg -------------------------------------------------------------------------------- /vsvg/dither_hatch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dither_hatch" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | whiskers = { git = "https://github.com/abey79/vsvg.git" } 8 | vsvg = { git = "https://github.com/abey79/vsvg.git" } 9 | image = "0.25" 10 | nalgebra = "0.33" -------------------------------------------------------------------------------- /vsvg/simple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plotter_sketches_vsg_simple" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | whiskers = { git = "https://github.com/abey79/vsvg.git" } 10 | vsvg = { git = "https://github.com/abey79/vsvg.git" } 11 | image = "0.25" 12 | nalgebra = "0.33" -------------------------------------------------------------------------------- /notes/vintage_plotter_links.md: -------------------------------------------------------------------------------- 1 | 2 | * http://www.lesporteslogiques.net/wiki/materiel/plotter_roland_dxy-1200 3 | * XY4160 http://www.krupkaj.cz/sblog/article_detail.php?itmid=9000030 4 | * DXY-1150 https://martianwabbit.com/2019/10/25/notes-on-a-roland-dxy-1150.html 5 | * https://hackaday.io/project/12276-roland-dg-dxy-990 6 | * roland DXY-1200 https://www.elektroda.pl/rtvforum/topic1192751.html 7 | * https://www.flickr.com/photos/anachrocomputer/albums/72157633082493407/ (roland dpx-3300 teardown pictures) 8 | 9 | * https://plotterstation.osp.kitchen/catalogue.html 10 | * (a lot of info about most HP plotters) https://www.hpmuseum.net/exhibit.php?class=4&cat=24 11 | 12 | * HP replacement carousel https://www.thingiverse.com/thing:6961560 13 | * https://retrohax.net/atari-1020-plotter-plotting-pens-replacement/ 14 | 15 | * https://www.evident-shop.de/en/search?sSearch=stifte 16 | * https://github.com/ithinkido/PlotterROM/tree/main 17 | 18 | # Pen adapters (for vintage plotters) 19 | 20 | 21 | * https://www.thingiverse.com/karliss/collections/43091102/things 22 | * https://github.com/juliendorra/3D-printable-plotter-adapters-for-pens-and-refills (HP) 23 | * https://github.com/aaronbot3000/hp7550-things/tree/master/pen_holders (more HP) 24 | * https://softsolder.com/2015/04/21/hp-7475a-plotter-oem-pen-body-model/ (HP measurements) 25 | * https://github.com/aaronbot3000/hp7550-things/tree/master/pen_holders 26 | 27 | * https://github.com/mmuman/ALPS-plotter-pen-replacement/tree/main (Small capsule thingies) 28 | * https://retrohax.net/atari-1020-plotter-plotting-pens-replacement/ 29 | 30 | 31 | 32 | # misc videos 33 | 34 | * https://www.youtube.com/watch?v=YcNpABZoCMI&t=22s hp 7220c -------------------------------------------------------------------------------- /test_plot/Readme.md: -------------------------------------------------------------------------------- 1 | # Plotter test file 2 | 3 | For testing plotter and pens. 4 | 5 | When plotting **disable** any **optimizations** which reorder the paths! Certain parts of this file are meant to be plotted in specific order and direction. 6 | 7 | 8 | 9 | ## Checks 10 | 11 | 1) Pen width test, can be used to find what spacing produces solid infill 12 | 2) Spiral 13 | 3) Spiral, larger spacing and path going from outside to the center 14 | 4) Pen width check, same as 1. but vertical 15 | 5) Concentric circles. The middle one is using alternating directions. Pay attention whether circles are concentric, round (not oval) and evenly spaced. 16 | 6) Grid of lines 17 | 7) Text size, size of capital letters in mm 18 | 8) Grid made of rectangles with 1mm and 2mm spacing. Unlike 6 the rectangles cause lines to go in alternating directions. 19 | 9) Grid of rectangles, same as 8. but rotated 45°. 20 | 10) Grid made of squares drawn in randomized order. 21 | 11) Backlash test. 1mm gaps are intentional. To do the test - place a piece of paper (or other flat straight material) along each square edge and check if the line segment from corner piece is aligned with the square edge. 22 | 23 | 24 | 12) empty 25 | 26 | 13) The big square 170mm. Can be used for testing that scale and steps/mm configuration is correct. Can be used for testing axis squareness by comparing diagonal length. 27 | 14) Crosses in the corners of big square. Meant to detect any position shifts during the print, which could be caused by motors skipping steps, paper being insufficiently secured, (in case of roller plotters that move the paper) paper slipping. First set of crosses should be plotted at the start and second set at the end. 28 | 15) Stretched crosses. Possible results (described for horizontal one, but works similarly ): 29 | - all 4 lines cross in the center - good 30 | - diagonal line crossing is shifted to the right or left - movement direction dependent vertical position error 31 | 32 | ## Files 33 | 34 | * test_cal_plot_A4.svg - source file 35 | * \*\_exported.svg - finalized file with all text converted to strokes 36 | 37 | ## Changelog 38 | 39 | * v0.22 40 | - Change the starting point for backlash test 11 so that it doesn't start from corner 41 | - Add 1.25mm font size 42 | - Add feature numbers 43 | - Add feature 15 - stretched crosses 44 | - Add direction arrows 45 | * v0.21 first public version 46 | -------------------------------------------------------------------------------- /turtletoy/testTurtle.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | export class TestTurtle 4 | { 5 | constructor(outputname="output.svg") { 6 | this.x = 0; 7 | this.y = 0; 8 | this.angle = 0; 9 | this.pen = false; 10 | this.started = false; 11 | this.log = (outputname == "-"); 12 | this.outputname = outputname; 13 | this.buffer = ""; 14 | 15 | 16 | this.startSVG(); 17 | } 18 | print(x){ 19 | if (this.log) { 20 | console.log(x); 21 | } else { 22 | this.buffer += x; 23 | } 24 | } 25 | startSVG() { 26 | this.print(String.raw` 27 | 35 | 36 | 42 | `); 43 | if (!this.log && this.outputname) { 44 | let fd = fs.openSync(this.outputname, "w"); 45 | fs.writeFileSync(fd, this.buffer); 46 | this.buffer = ""; 47 | fs.closeSync(fd); 48 | } 49 | } 50 | jump(x, y=undefined) { 51 | if (y === undefined) { 52 | y = x[1]; 53 | x = x[0]; 54 | } 55 | this.x = x; 56 | this.y = y; 57 | this.started = false; 58 | } 59 | pendown(){ 60 | this.pen = true; 61 | } 62 | penup() { 63 | this.pen = false; 64 | } 65 | forward(x) { 66 | this.goto(this.x + Math.sin(this.angle)*x, this.y + Math.cos(this.angle)*x); 67 | } 68 | backward(x) { 69 | this.goto(this.x - Math.sin(this.angle)*x, this.y - Math.cos(this.angle)*x); 70 | } 71 | right(x) { 72 | this.angle -= x; 73 | } 74 | left(x) { 75 | this.angle += x; 76 | } 77 | setheading(x) { 78 | this.angle = x; 79 | } 80 | goto(x, y=undefined){ 81 | if (y === undefined) { 82 | y = x[1]; 83 | x = x[0]; 84 | } 85 | //this.print(` (${this.x};${this.y}) => (${x};${y})`); 86 | if (this.pen) { 87 | if (!this.started) { 88 | this.print(`M${this.x},${this.y}L`); 89 | this.started = true; 90 | } 91 | this.print(`${x},${y}\n`) 92 | } else { 93 | this.started = false; 94 | } 95 | this.x = x; 96 | this.y = y; 97 | } 98 | } -------------------------------------------------------------------------------- /notes/random_links.md: -------------------------------------------------------------------------------- 1 | 2 | # Other collections of resources 3 | 4 | * https://github.com/beardicus/awesome-plotters 5 | * https://drawingbots.net/ 6 | 7 | # Plotters: 8 | 9 | * https://www.youtube.com/watch?v=U6kt-N3fzH4 10 | 11 | # Drawings and artists: 12 | 13 | * https://github.com/LingDong- (procedural genration) 14 | * https://chocographix.net/ (vectorart) 15 | 16 | # Blogs 17 | 18 | * https://newsletter.revdancatt.com/ 19 | * https://lostpixels.io/writings 20 | * https://penplotter.art/ 21 | * https://blog.nexp.pt/ 22 | 23 | # Software 24 | 25 | 26 | ## general purpose SVG editors 27 | 28 | * [Inkscape](https://inkscape.org/) 29 | * [Graphite](https://graphite.rs/) (general purpose but also supports node based workflow and even mixing them) 30 | * [BoxySVG](https://boxy-svg.com/) 31 | 32 | ## generative/node based editors 33 | 34 | * [Kinogaki](https://app.kinogaki.com/) 35 | * [Patternodes](https://www.lostminds.com/patternodes3/) 36 | * [Blender Sverchok](https://github.com/nortikin/sverchok) Blender plugin which provides functionality similar to geometry nodes 37 | * [Blender geometry nodes+Freestyle SVG exporter](https://www.blender.org/) 38 | * [Makelangelo software](https://github.com/MarginallyClever/Makelangelo-software) 39 | * [Paragraphic](https://paragraphic.design/) 40 | 41 | 42 | ## creative coding libraries/frameworks/platforms 43 | 44 | * [ln](https://github.com/fogleman/ln/tree/master) (go, 3D) 45 | * https://turtletoy.net/ (online JS) 46 | * https://processing.org/ , https://processing.org/reference/libraries/svg/index.html 47 | * [VSVG/Whiskers](https://github.com/abey79/vsvg/blob/master/crates/whiskers/README.md) (Rust) 48 | * [vpype](https://github.com/abey79/vpype) (Python, 2d drawing API + svg utilities+svg to gcode/hpgl converter) 49 | * [Urpflanze](https://urpflanze.genbs.dev/) (JS) 50 | 51 | 52 | ## Other software for plotter drawing creation 53 | 54 | A bit more flexible than single purpose plotter sketches, more niche than general purpose drawing tools. 55 | 56 | * [DrawingbotV3](https://drawingbotv3.com/) Provides of various effects for converting bitmaps to plotter friendly drawings 57 | * https://sandify.org/ 58 | * https://lostminds.com/vectoraster8/ 59 | 60 | 61 | ## single purpose tools/plotter sketches 62 | 63 | * https://pro.mandalagaba.com/#l5FQqM (symmetry, tiling) 64 | * https://shefalinayak.com/tile-playground/index.html (tiling) 65 | * https://isohedral.ca/other/Beyond96/ (tiling) 66 | * https://eskimoblood.github.io/elm-wallpaper-editor/ (tiling) 67 | * https://msurguy.github.io/cnc-text-tool/ (text) 68 | * https://contours.axismaps.com (maps) 69 | * https://anvaka.github.io/city-roads/ (maps) 70 | * https://github.com/anvaka/peak-map (maps) 71 | * https://plotter.vision (3d) 72 | 73 | # Misc 74 | 75 | 76 | # Blog posts 77 | 78 | * https://hannahilea.com/blog/ly-drawbot-setup/ 79 | 80 | -------------------------------------------------------------------------------- /turtletoy/flower.js: -------------------------------------------------------------------------------- 1 | // Forked from "Spiro 1" by kabacis 2 | // https://turtletoy.net/turtle/2a892789a4 3 | 4 | // You can find the Turtle API reference here: https://turtletoy.net/syntax 5 | Canvas.setpenopacity(1); 6 | let stepMul=256;// min=2 max=512 step=1 7 | let offset=-0.7; // min=-1 max=1 step=0.1 8 | let length=57; // min=1 max=200 step=1 9 | let extraRot=-100; // min=-360 max=360 step=5 10 | let a2 = 68; // min=0 max=90 step=1 11 | let scaleX=0.4; // min=0.3 max=1.3 step=0.1 12 | let zfreq=1; // min=0.1 max=10 step=0.01 13 | let z_phase_offset=-0.5; // min=-1 max=1 step=0.05 14 | let h=191; // min=0.0 max=200 step=0.1 15 | let rs=5.65; // min=0.1 max=10 step=0.01 16 | let rVarF = 2.77; // min=0.01 max=10 step=0.01 17 | let rVar = 0.337; // min=0.0 max=4 step=0.001 18 | let lift=0; // min=0, max=1, step=1 (No, Yes) 19 | let verticalOffset = -20; // min=-50, max=50, step=1 20 | let stemL = 81; // min=0, max=100, step=1 21 | let stemB = 3; // min = 0, max=100, step=1 22 | 23 | // Global code will be evaluated once. 24 | const turtle = new Turtle(); 25 | turtle.penup(); 26 | 27 | 28 | function f(i, down) { 29 | let x = Math.cos(2 * Math.PI * i / stepMul) + offset; 30 | let y = Math.sin(2 * Math.PI * i / stepMul); 31 | 32 | 33 | let rotk = Math.PI * 2 * (Math.floor((i + (down ? 0 : -1)) / stepMul) * rs); 34 | let xt=x; 35 | let yt=y; 36 | x = Math.cos(rotk)*xt-Math.sin(rotk)*yt; 37 | y = Math.sin(rotk)*xt+Math.cos(rotk)*yt; 38 | 39 | let rvarA = Math.PI * 2 * (Math.floor((i + (down ? 0 : -1)) / stepMul) * rVarF); 40 | let r2 = 1 + Math.sin(rvarA) * rVar; 41 | 42 | x *= r2; 43 | y *= r2; 44 | 45 | 46 | let realRot = 2 * Math.PI * extraRot/360; 47 | xt=x; 48 | yt=y; 49 | x = Math.cos(realRot)*xt-Math.sin(realRot)*yt; 50 | y = Math.sin(realRot)*xt+Math.cos(realRot)*yt; 51 | 52 | 53 | let t = (1+Math.cos(zfreq * 2 * Math.PI * i / stepMul))*0.5; 54 | let w = 1;//t; 55 | let z = (1-Math.cos(Math.PI*(t*0.5 + z_phase_offset)))*0.5; 56 | 57 | x *= scaleX * w; 58 | y *= scaleX * w; 59 | z *= h * scaleX; 60 | 61 | x *= 50; 62 | y = 50 * y * Math.cos(Math.PI * a2 / 180) - z * Math.sin(Math.PI * a2 / 180); 63 | 64 | y += verticalOffset; 65 | 66 | turtle.goto(x, y); 67 | } 68 | 69 | turtle.goto(0, verticalOffset); 70 | turtle.pendown(); 71 | for (var j=0;j Self { 29 | Self { 30 | line_size: 1.0, 31 | columns: 5, 32 | rows: 5, 33 | spacing: 0.0, 34 | is_pointy_orientation: false, 35 | cell_size: 40.0, 36 | tree_h: 0.7, 37 | angle: 45.0, 38 | angle2: 0.0, 39 | } 40 | } 41 | } 42 | 43 | impl App for HexGridSketch { 44 | fn update(&mut self, sketch: &mut Sketch, _ctx: &mut Context) -> anyhow::Result<()> { 45 | sketch.stroke_width(self.line_size); 46 | 47 | let grid = if self.is_pointy_orientation { 48 | HexGrid::with_pointy_orientation() 49 | } else { 50 | HexGrid::with_flat_orientation() 51 | }; 52 | 53 | sketch.scale(Unit::Mm); 54 | 55 | grid.cell_size(self.cell_size) 56 | .columns(self.columns) 57 | .rows(self.rows) 58 | .spacing(self.spacing) 59 | .build(sketch, |sketch, cell| { 60 | let b = Point::new(0.0, 0.0); //cell.center; 61 | let top = b + Point::new(0, -self.cell_size * self.tree_h); 62 | let mut points = vec![b, top]; 63 | 64 | for i in 0..3 { 65 | let frac = i as f64 / 3 as f64; 66 | let start = Point::new(0, top.y() - (top.y() * 0.5) * frac); 67 | points.push(start); 68 | let w = top.y().abs() * lerp(3.0..=10.0, 0.1); 69 | let br_h = w / self.angle.to_radians().tan(); 70 | points.push(start + Point::new(-w, br_h)); 71 | points.push(start); 72 | points.push(start + Point::new(w, br_h)); 73 | points.push(start); 74 | } 75 | sketch.push_matrix(); 76 | 77 | 78 | sketch.translate(cell.center.x(), cell.center.y()); 79 | sketch.rotate_around( 80 | Angle::from_deg(self.angle2), 81 | 0.0, 82 | top.y() * 0.5, 83 | ); 84 | 85 | sketch.polyline(points, true).pop_matrix(); 86 | //sketch.add_path(cell); 87 | }); 88 | 89 | Ok(()) 90 | } 91 | } 92 | 93 | fn main() -> Result { 94 | HexGridSketch::runner() 95 | .with_page_size_options(PageSize::A5H) 96 | .with_layout_options(LayoutOptions::Center) 97 | .run() 98 | } 99 | -------------------------------------------------------------------------------- /turtletoy/sprio1.js: -------------------------------------------------------------------------------- 1 | // https://turtletoy.net/turtle/2a892789a4#stepMul=256,offset=0.7,length=52,mulX=0.9,mulY=1.0,zfreq=1.86,h=4.6,rs=1.41 2 | // https://turtletoy.net/turtle/2a892789a4#stepMul=256,offset=1.2,length=20,mulX=0.7,mulY=0.7,zfreq=0.39,h=0.1,rs=2.15 3 | // https://turtletoy.net/turtle/2a892789a4#stepMul=256,offset=1.2,length=20,mulX=0.7,mulY=0.7,zfreq=0.39,h=0.1,rs=2.45 4 | // https://turtletoy.net/turtle/2a892789a4#stepMul=256,offset=1.2,length=99,mulX=0.7,mulY=0.7,zfreq=0.39,h=0.1,rs=2.63 5 | // https://turtletoy.net/turtle/2a892789a4#stepMul=256,offset=1.2,length=52,mulX=0.7,mulY=0.7,zfreq=0.39,h=0.1,rs=2.7 6 | // https://turtletoy.net/turtle/2a892789a4#stepMul=256,offset=1.2,length=52,mulX=0.7,mulY=0.7,zfreq=0.39,h=0.1,rs=2.67 7 | // https://turtletoy.net/turtle/2a892789a4#stepMul=299,offset=2,length=130,mulX=0.6,mulY=0.5,zfreq=8.93,h=19.6,rs=4.9 8 | // https://turtletoy.net/turtle/2a892789a4#stepMul=700,offset=1.4,length=197,mulX=0.6,mulY=0.6,zfreq=4.82,h=2,rs=0.95,lift=0 9 | // https://turtletoy.net/turtle/2a892789a4#stepMul=700,offset=0,length=31,extraRot=-105,mulX=0.6,mulY=0.4,zfreq=10,h=11.6,rs=7.74,lift=0 10 | // https://turtletoy.net/turtle/2a892789a4#stepMul=220,offset=-0.6,length=83,extraRot=-155,mulX=0.7,mulY=0.6,zfreq=4,h=106,rs=1.17,lift=0 11 | 12 | // You can find the Turtle API reference here: https://turtletoy.net/syntax 13 | Canvas.setpenopacity(1); 14 | 15 | let stepMul=100;// min=2 max=512 step=1 16 | let offset=1; // min=-2 max=2 step=0.1 17 | let length=1; // min=1 max=200 step=1 18 | let extraRot=0; // min=-360 max=360 step=5 19 | let mulX=1; // min=0.1 max=3 step=0.1 20 | let mulY=1;// min=0.1 max=3 step=0.1 21 | let zfreq=0.51; // min=0.1 max=10 step=0.01 22 | let h=1; // min=0.1 max=200 step=0.1 23 | let rs=1; // min=0.1 max=10 step=0.01 24 | let lift=0; // min=0, max=1, step=1 (No, Yes) 25 | 26 | // Global code will be evaluated once. 27 | const turtle = new Turtle(); 28 | turtle.penup(); 29 | 30 | 31 | function f(i, down) { 32 | let x = Math.cos(2 * Math.PI * i / stepMul) + offset; 33 | let y = Math.sin(2 * Math.PI * i / stepMul); 34 | 35 | 36 | let rotk = Math.PI * 2 * (Math.floor((i + (down ? 0 : -1)) / stepMul) * rs); 37 | let xt=x; 38 | let yt=y; 39 | x = Math.cos(rotk)*xt-Math.sin(rotk)*yt; // buggy implementation of rotation but it makes interesting effect 40 | y = Math.sin(rotk)*xt-Math.cos(rotk)*yt; 41 | xt=x; 42 | yt=y; 43 | let realRot = 2 * Math.PI * extraRot/360; 44 | x = Math.cos(realRot)*xt-Math.sin(realRot)*yt; 45 | y = Math.sin(realRot)*xt+Math.cos(realRot)*yt; 46 | 47 | 48 | let t = (1+Math.cos(zfreq * 2 * Math.PI * i / stepMul))*0.5; 49 | let w = 1;//t; 50 | let z = (1-Math.cos(t * Math.PI*0.5))*0.5; 51 | 52 | x *= w * 50 * mulX; 53 | y *= w * 50 * mulY; 54 | y += (-z * h); 55 | 56 | turtle.goto(x, y); 57 | } 58 | // The walk function will be called until it returns false. 59 | function walk(i) { 60 | let i2 = i-1; 61 | 62 | let first = ((i2 % stepMul) == 0); 63 | let last = (((i2+1) % stepMul) == 0); 64 | 65 | f(i2, true); 66 | if (first) { turtle.pendown(); } 67 | if (last) { 68 | f(i2+1, false); 69 | if (lift) { 70 | turtle.penup(); 71 | } 72 | } 73 | 74 | 75 | return i < length * stepMul; 76 | } -------------------------------------------------------------------------------- /vsvg/simple/examples/christmas_tree.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates the use of the [`HexGrid`] helper. 2 | 3 | use egui::lerp; 4 | use vsvg::UNITS; 5 | use whiskers::prelude::*; 6 | 7 | #[sketch_app] 8 | struct HexGridSketch { 9 | #[param(min = 0.01, max = 100.0)] 10 | line_size: f64, 11 | is_pointy_orientation: bool, 12 | #[param(slider, min = 2, max = 100)] 13 | columns: usize, 14 | #[param(slider, min = 2, max = 100)] 15 | rows: usize, 16 | #[param(slider, min = 0.0, max = 200.0)] 17 | spacing: f64, 18 | #[param(min = 0.0, max = 100.0)] 19 | cell_size: f64, 20 | #[param(slider, min = 0.0, max = 1.0)] 21 | tree_h: f64, 22 | #[param(slider, min = 2, max = 20)] 23 | levels: i32, 24 | #[param(slider, min = 0.0, max = 1.0)] 25 | branch_h: f64, 26 | #[param(slider, min = 0.0, max = 90.0)] 27 | angle: f64, 28 | #[param(slider, min = 0.0, max = 1.0)] 29 | max_w: f64, 30 | #[param(slider, min = 0.0, max = 1.0)] 31 | min_w: f64, 32 | random: bool, 33 | 34 | #[param(slider, min = 0.0, max = 360.0)] 35 | angle2: f64, 36 | #[param(slider, min = 0.0, max = 1.0)] 37 | rot_center: f64 38 | } 39 | 40 | impl Default for HexGridSketch { 41 | fn default() -> Self { 42 | Self { 43 | line_size: 1.0, 44 | columns: 5, 45 | rows: 5, 46 | spacing: 0.0, 47 | is_pointy_orientation: false, 48 | cell_size: 40.0, 49 | tree_h: 0.7, 50 | levels: 2, 51 | angle: 45.0, 52 | branch_h: 0.6, 53 | max_w: 0.5, 54 | min_w: 0.05, 55 | random: false, 56 | angle2: 0.0, 57 | rot_center: 0.5, 58 | } 59 | } 60 | } 61 | 62 | impl App for HexGridSketch { 63 | fn update(&mut self, sketch: &mut Sketch, _ctx: &mut Context) -> anyhow::Result<()> { 64 | sketch.stroke_width(self.line_size); 65 | 66 | let grid = if self.is_pointy_orientation { 67 | HexGrid::with_pointy_orientation() 68 | } else { 69 | HexGrid::with_flat_orientation() 70 | }; 71 | 72 | sketch.scale(Unit::Mm); 73 | 74 | grid.cell_size(self.cell_size) 75 | .columns(self.columns) 76 | .rows(self.rows) 77 | .spacing(self.spacing) 78 | .build(sketch, |sketch, cell| { 79 | let b = Point::new(0.0, 0.0); //cell.center; 80 | let top = b + Point::new(0, -self.cell_size * self.tree_h); 81 | let mut points = vec![b, top]; 82 | for i in 0..self.levels { 83 | let frac = i as f64 / self.levels as f64; 84 | let start = Point::new(0, top.y() - (top.y() * self.branch_h) * frac); 85 | points.push(start); 86 | let w = top.y().abs() * lerp(self.min_w..=self.max_w, frac); 87 | let br_h = w / self.angle.to_radians().tan(); 88 | points.push(start + Point::new(-w, br_h)); 89 | points.push(start); 90 | points.push(start + Point::new(w, br_h)); 91 | points.push(start); 92 | } 93 | sketch.push_matrix(); 94 | 95 | 96 | sketch.translate(cell.center.x(), cell.center.y()); 97 | sketch.rotate_around( 98 | Angle::from_deg(self.angle2), 99 | 0.0, 100 | top.y() * self.rot_center, 101 | ); 102 | if self.random { 103 | sketch.rotate_around( 104 | Angle::from_deg(60f64 * _ctx.rng_range(0..6) as f64), 105 | 0.0, 106 | top.y() * self.rot_center, 107 | ); 108 | } 109 | 110 | sketch.polyline(points, true).pop_matrix(); 111 | //sketch.add_path(cell); 112 | }); 113 | 114 | Ok(()) 115 | } 116 | } 117 | 118 | fn main() -> Result { 119 | HexGridSketch::runner() 120 | .with_page_size_options(PageSize::A5H) 121 | .with_layout_options(LayoutOptions::Center) 122 | .run() 123 | } 124 | -------------------------------------------------------------------------------- /notes/plotter_notes.csv: -------------------------------------------------------------------------------- 1 | model,manufacturer,type,protocols,connection,speed,accel,max size,scale,axis,pens,screen,force adjustment,paper holding,paper autoload,notes 2 | HP7221,HP,flatbed,custom binary,RS232 serial,,,,,,*,,,,*,"S and T have roller feed, C and T 8 pens" 3 | HP7220,HP,flatbed,HPGL,,,,,,,,,,,, 4 | HP 7440A (ColorPro),HP,roller,,"RS232, HP-IB (one)",400mm/s,1.2g,A4,,,8,,,,, 5 | HP7475A,HP,roller,HPGL,,,,"A3, ANSI A",0.02488mm,XRYU(A4) / XDYR(A3),6,,,,,Paper size chosen with switches in back and front. Axis direction changes depending on if it's A4 or A3 6 | HP7550A,HP,roller,HPGL,HP-IB+2xRS232 serial with passthrough,800mm/s,6g,A3,,,8,,,,tray, 7 | HP7550B,HP,roller,,,,,,,,,,,,, 8 | HP7550 Plus,HP,roller,,,800mm/s,6g,A3,,,,,yes,,, 9 | HP7470A,HP,roller,HPGL,"HP-IB, RS-232 (25 pin), HP-IL (one of 3)","381mm/s , 508mm/s (down up)",2g,"A4, Letter",0.025mm,XDYR,2,,,,, 10 | HP7475A,HP,roller,HPGL,"HP-IB, RS-232 (one)","381mm/s , 508mm/s (down up)",2g,"A3, B",,,6,,,,, 11 | HP7580,HP,roller,HPGL,,,,,,,,,,,, 12 | HP7585A,HP,roller,HPGL,,,,A0,,,,,,,, 13 | HP7585B,HP,roller,HPGL,HP-IB+RS232,,,A4/A - A0/E 927x1190mm,0.025mm,,8,3digit,yes,,, 14 | HP7595C,HP,roller,,,,,,,,,,,,, 15 | DMP-40V,HI,roller,DMPL,,,,400-420mmx2000mm,"0.1mm, 0.001in, 0.005in",,,,,,,Has motorized blade rotation 16 | DMP-61,HI,roller,"DMPL,HPGL",,,,"A-D,A4-A1",0.001in 0.005in 0.1mm 0.025mm,related to small/large chart option,,,,,, 17 | DMP-62,HI,roller,"DMPL,HPGL",,,,"A-F,A4-A0,B1",0.001in 0.005in 0.1mm 0.025mm,varies,,,,,,Axis direction depends on small chart/large chart option. Origin and axis direction in HPGL mode differs from DMPL. HPGL origin in center. 18 | DMP-161,HI,roller,"DMPL,HPGL,HP-GL/2",serial,800mm/s,4G,<=24in (specific widths),0.001in 0.005in 0.1mm 0.025mm,varies,8,LCD,,,,"axis direction varies depending on size, but might be possible to configure fixed corner." 19 | DMP-162,HI,roller,"DMPL,HPGL,HP-GL/2",serial,"800,600 mm/s (pen, paper)",4G/2G(paper),36in,0.001in 0.005in 0.1mm 0.025mm,varies,8,LCD,,,, 20 | DMP-162R,HI,roller,"DMPL,HPGL,HP-GL/2",serial,"800,600mm/s",4G/2G(paper),36in +rolls,0.001in 0.005in 0.1mm 0.025mm,varies,8,LCD,,,paper roll, 21 | DXY-1350A,Roland,flatbed,"RD-GL II, RD-GL I,DXY-GL",parallel(centronics)+serial(db25),600mm/s,,,,,,,,electrostatic,,Electrostatic paper holder+replot Otherwise same as 1150A. 22 | DXY-1150A,Roland,flatbed,"RD-GL II, RD-GL I,DXY-GL",parallel(centronics)+serial(db25),600mm/s,,"A3,B,431.8x297mm","0.025mm,0.1mm","TODO, centered 0 in RD-GL II mode",8,,,magnetic,,"round buttons, holes for pen storage" 23 | DXY-1350,Roland,flatbed,,,,,,,,,7-segment LED,,electrostatic,,"round buttons, holes for pen storage, replot" 24 | DXY-1250,Roland,flatbed,,,,,,,,,7-segment LED,,electrostatic,,"round buttons, holes for pen storage" 25 | DXY-1150,Roland,flatbed,,,,,,,,,,,,,"round buttons, holes for pen storage" 26 | DXY-1300,Roland,flatbed,"RD-GL I, DXG-GL",parallel(centronics)+serial(db25),,,,"DXY-GL(0.025mm, 0.1mm), RD-GL I(0.025mm)",XRYU,,7-segment LED,,electrostatic,,"square buttons, replot" 27 | DXY-1200,Roland,flatbed,"RD-GL I, DXG-GL",parallel(centronics)+serial(db25),,,,"DXY-GL(0.025mm, 0.1mm), RD-GL I(0.025mm)",XRYU,,7-segment LED,,electrostatic,,square buttons 28 | DXY-1100,Roland,flatbed,"RD-GL I, DXG-GL",parallel(centronics)+serial(db25),,,,"DXY-GL(0.025mm, 0.1mm), RD-GL I(0.025mm)",XRYU,,,,magnetic,,square buttons 29 | DXY-980A,Roland,flatbed,,,,,,,,,7-segment LED,,electrostatic,, 30 | DXY-880A,Roland,flatbed,,,,,,,,,,,magnetic,, 31 | DXY-880,Roland,flatbed,"DXY-GL,RD_GL",parallel(centronics)+serial(db25),,,"A3 (with unreachable padding),380x270mm","0.1mm DXY, 0.025mm RD-GL",XRYU,,,,magnetic,, 32 | DXY-980,Roland,flatbed,"DXY-GL,RD_GL",parallel(centronics)+serial(db25),,,"A3 (with unreachable padding),380x270mm","1) 0.1mm DXY, 0.025mm RD-GL",XRYU,,7-segment LED,,electrostatic,, 33 | DXY-990,Roland,flatbed,RD-GL I,parallel(centronics)+serial(db25),300mm/s,,"A3, B (unreachable padding)",,,,7-segment LED,,electrostatic,,pen buttons 34 | DXY-885,Roland,flatbed,RD-GL I,parallel(centronics)+serial(db25),300mm/s,,"416mmx259mm (ANSI B), 403mmx276mm (A3)",,,,,,magnetic,, 35 | DXY-800,Roland,flatbed,DXY-GL,parallel(centronics)+serial(db25),180mm/s,,350x260mm,0.1mm,,8,,,,, 36 | DXY-101,Roland,flatbed,DXY-GL,parallel(centronics)+serial(db25),180mm/s,,370x260mm,0.1mm,,1,,,,, 37 | DPX-2000,Roland,flatbed,,,,,,,,,,,,, 38 | DPX-2200,Roland,flatbed,,parallel(centronics)+serial(db25),450mm/s,,C/A2,,,,,,,, 39 | DPX-3300,Roland,flatbed,,parallel(centronics)+serial(db25),450mm/s,,D/A1,,,,7-segment LED,yes,,, 40 | DPX-2500,Roland,flatbed,,,,,,,,,LCD,,,, 41 | DPX-3500,Roland,flatbed,,,,,,,,,LCD,,,, 42 | -------------------------------------------------------------------------------- /vsvg/simple/examples/circles2.rs: -------------------------------------------------------------------------------- 1 | 2 | use image::{GenericImageView, ImageBuffer, ImageReader, Luma, Pixel, Rgb}; 3 | use kurbo::Vec2; 4 | use nalgebra::{vector, Vector2, Vector3}; 5 | use serde; 6 | use std::result::Result; 7 | use vsvg::UNITS; 8 | use whiskers::prelude::*; 9 | 10 | 11 | #[sketch_app] 12 | struct CircleSketch { 13 | #[param(slider, min = 0.01, max = 5.0)] 14 | line_size: f64, 15 | grid_mul: f64, 16 | have_image:bool, 17 | scale_mul: f64, 18 | w: u32, 19 | h: u32, 20 | 21 | circles: usize, 22 | black_max: i32, 23 | rings: i32, 24 | #[param(slider, min = 0.1, max = 20.0)] 25 | spacing: f64, 26 | 27 | subdiv: i32, 28 | 29 | priority: bool, 30 | invert: bool, 31 | 32 | 33 | 34 | #[skip] 35 | #[serde(skip)] 36 | dstate: DynamicState, 37 | } 38 | 39 | #[derive(Default)] 40 | struct DynamicState { 41 | } 42 | 43 | impl Default for CircleSketch { 44 | fn default() -> Self { 45 | Self { 46 | line_size: 1.0, 47 | grid_mul: 1.0, 48 | have_image: false, 49 | scale_mul: 1.0, 50 | w: 0, 51 | h: 0, 52 | circles: 4, 53 | black_max: 5, 54 | rings: 20, 55 | spacing: 5.0, 56 | subdiv: 128, 57 | priority: false, 58 | invert: false, 59 | dstate: DynamicState::default(), 60 | } 61 | } 62 | } 63 | 64 | impl CircleSketch { 65 | 66 | fn draw(self: &mut CircleSketch, sketch: &mut Sketch, ctx: &mut Context) 67 | { 68 | 69 | let circle_space = self.line_size * self.spacing; 70 | 71 | 72 | let mut circles = Vec::new(); 73 | let w = vsvg::Unit::Mm.convert_from(&vsvg::Unit::Px, sketch.width()); 74 | let h = vsvg::Unit::Mm.convert_from(&vsvg::Unit::Px,sketch.height()); 75 | for circle in 0..self.circles { 76 | let c = ctx.rng_point(0.0..w, 0.0..h); 77 | let r = self.rings as f64 * circle_space; 78 | 79 | circles.push((c, r)); 80 | } 81 | for circle in 0..self.circles as usize { 82 | let (c, r) = circles[circle]; 83 | 84 | let need = *ctx.rng_choice(&[1, 1, 1, 2]); 85 | for ring in 0..self.rings { 86 | //sketch.circle(c.x(), c.y(), (ring+1) as f64 * circle_space); 87 | let r = (ring+1) as f64 * circle_space; 88 | let mut prev = c + Point::new(r, 0); 89 | for i in 1..=self.subdiv { 90 | let angle = f64::to_radians((360.0 * i as f64) / (self.subdiv as f64)); 91 | let p2: Point = c + Point::new(angle.cos(), angle.sin()) * r; 92 | 93 | /*let mut posp = (image.width() as f64 * (p2.x()/w), image.height()as f64 * (p2.y()/h)); 94 | posp.0 = if posp.0 >= 0.0 {posp.0} else {(image.width() + 3) as f64}; 95 | posp.1 = if posp.1 >= 0.0 {posp.1} else {(image.height() + 3) as f64}; 96 | */ 97 | 98 | //let v = pixel_magnitude2(*pixel); 99 | /*let v = pixel.to_luma().0[0]; 100 | 101 | let numf = if self.invert { 102 | v as f32 / 255.0 103 | } else { 104 | 1.0-(v as f32 / 255.0) 105 | }; 106 | let need = (numf * self.black_max as f32) as i32;*/ 107 | 108 | let num_have = if self.priority { 109 | circles[0..circle].iter().map(|x: &(Point, f64)| { 110 | if p2.distance(&x.0) < x.1 { 111 | 1 112 | } else { 113 | 0 114 | } 115 | }).sum() 116 | } else { 117 | circles.iter().enumerate().map(|(i, x)| { 118 | if i == circle { 119 | return 0; 120 | } 121 | if p2.distance(&x.0) < x.1 && p2.distance(&x.0) < p2.distance(&c){ 122 | 1 123 | } else { 124 | 0 125 | } 126 | }).sum() 127 | }; 128 | 129 | if need > num_have { 130 | sketch.line(prev.x(), prev.y(), p2.x(), p2.y()); 131 | } 132 | 133 | prev = p2; 134 | 135 | } 136 | } 137 | } 138 | 139 | /*grid.build(sketch, |sketch, cell| { 140 | let p1 = cell.position; 141 | let p2 = p1 + Point::new(cell.size[0]*0.1, 0); 142 | 143 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 144 | let mg = pixel_magnitude2(*c1).isqrt(); 145 | 146 | let W = mina.len(); 147 | let a: i32 = mina[cell.row % W][cell.column % W]; 148 | let d: i32 = dir[cell.row % W][cell.column % W]; 149 | 150 | 151 | 152 | if 443-mg > ((443) * (a+1)) / 17 { 153 | *flags.get_pixel_mut(cell.column as u32, cell.row as u32) = Luma([d as u8; 1]); 154 | //sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 155 | } 156 | });*/ 157 | 158 | } 159 | } 160 | 161 | fn pixel_magnitude2(p: Rgb) -> i32 { 162 | let c = p.channels(); 163 | return c[0] as i32 * c[0] as i32 + c[1] as i32 * c[1] as i32 + c[2] as i32 * c[2] as i32; 164 | } 165 | 166 | type IPos2 = Vector2; 167 | 168 | fn pixel_to_vec32f(p: Rgb) -> Vector3 { 169 | return Vector3::new(p.0[0] as f32, p.0[1] as f32, p.0[2] as f32); 170 | } 171 | 172 | 173 | 174 | pub fn grid_cell(grid: f64, pos: IPos2) -> Vec2 { 175 | return Vec2::new(pos.x as f64 * grid, pos.y as f64 * grid); 176 | } 177 | 178 | 179 | 180 | 181 | 182 | 183 | impl App for CircleSketch { 184 | fn update(&mut self, sketch: &mut Sketch, ctx: &mut Context) -> anyhow::Result<()> { 185 | 186 | sketch.scale(Unit::Mm); 187 | sketch.stroke_width( vsvg::Unit::Mm.convert_to(&vsvg::Unit::Px, self.line_size) ); 188 | 189 | self.draw(sketch, ctx); 190 | 191 | Ok(()) 192 | } 193 | } 194 | 195 | fn main() -> whiskers::prelude::Result { 196 | CircleSketch::runner() 197 | .with_page_size_options(PageSize::A5H) 198 | .with_layout_options(LayoutOptions::Center) 199 | .run() 200 | } 201 | -------------------------------------------------------------------------------- /turtletoy/truchet_snowflakes.js: -------------------------------------------------------------------------------- 1 | // You can find the Turtle API reference here: https://turtletoy.net/syntax 2 | // https://turtletoy.net/turtle/1dedb7c6d0 3 | Canvas.setpenopacity(1); 4 | 5 | let size = 5; // min=1 max=100 step=0.1 6 | 7 | let W=15; // min = 1 max = 100 step=1 8 | let H=W; 9 | let symetry_mode = 2; // min=1 max=2 step=1 (rot6, rot6+mirror) 10 | 11 | // Global code will be evaluated once. 12 | const turtle = new Turtle(); 13 | 14 | 15 | class V2 { 16 | constructor(x, y) { 17 | this.x = x; 18 | this.y = y; 19 | } 20 | add(b) { return new V2(this.x + b.x, this.y + b.y); } 21 | sub(b) { return new V2(this.x - b.x, this.y - b.y); } 22 | mul(b) { return new V2(this.x * b, this.y * b); } 23 | flipx() { return new V2(-this.x, this.y); } 24 | flipy() { return new V2(this.x, -this.y); } 25 | rotDeg(deg) { 26 | const a = deg*Math.PI/180; 27 | const s = Math.sin(a); 28 | const c = Math.cos(a); 29 | return new V2(this.x*c-this.y*s, this.x*s+this.y*c); 30 | } 31 | asPair() { 32 | return [this.x, this.y]; 33 | } 34 | outp() { 35 | return this.flipy().mul(size).asPair(); 36 | } 37 | } 38 | 39 | const DVX = new V2(Math.sqrt(3), 0); 40 | const DVY = new V2(Math.sqrt(3)/2, -3/2); 41 | 42 | function hexToPixel(hexpos) 43 | { 44 | return DVX.mul(hexpos.x).add(DVY.mul(hexpos.y)); 45 | } 46 | function hexRotate(hp, n=1) { 47 | for (let i=0; i 0) { 95 | while (this.tile.connections[d2] != g0) { 96 | d2 = (d2+1)%6; 97 | } 98 | } else { 99 | d2 = d; 100 | } 101 | // console.log(d2); 102 | let k = (d2 - d + 6) % 6; 103 | //console.log(loops[k]); 104 | loops[k].forEach((v) => { 105 | p.push(v.rotDeg(60 * i)); 106 | }); 107 | turtle.jump(pv2.add(p[0]).outp()); 108 | for (let i=1; i 0; 132 | } 133 | 134 | fullSymetry() { 135 | return this.symetry[0]+this.symetry[1]+this.symetry[2] == 3; 136 | } 137 | angledSymetry() { 138 | return this.symetry[3] > 0; 139 | } 140 | place(dir) { 141 | return new PlacedTile(this, dir); 142 | } 143 | 144 | 145 | } 146 | 147 | const tiles = [ 148 | new Tile([1,1,1,1,1,1], [1, 1, 1, 1]), 149 | new Tile([0,0,0,0,0,0], [1, 1, 1, 1]), 150 | new Tile([1,1,2,2,3,3], [0, 0, 0, 1]), // othersymb 151 | new Tile([1,2,2,1,3,3], [1, 0, 0, 0]) , 152 | new Tile([1,0,0,1,0,0], [1, 0, 0, 0]) , 153 | new Tile([1,1,0,1,0,1], [1, 0, 0, 0]) , 154 | new Tile([0,1,1,0,2,2], [1, 0, 0, 0]), 155 | new Tile([0,1,1,0,1,1], [1, 0, 0, 0]), 156 | new Tile([1,2,2,1,0,1], [0, 0, 0, 0]), 157 | new Tile([1,1,1,0,0,1], [0, 0, 0, 1]), 158 | new Tile([1,1,2,3,3,2], [0, 0, 0, 1]), 159 | ]; 160 | 161 | 162 | 163 | 164 | 165 | //turtle.penup(); 166 | //turtle.goto(0, 0); 167 | 168 | let data=[]; 169 | turtle.pendown(); 170 | for (let y=0; y { 180 | let good = true; 181 | if ((x == 0 && y == 0)) { 182 | good = t.fullSymetry(); 183 | } else if (x == y) { 184 | good = t.angledSymetry(); 185 | } else if (y == 0) { 186 | good = t.isSymetric(0); 187 | } 188 | if (good) { 189 | candidates.push(t); 190 | } 191 | }); 192 | if (x+y > W) { 193 | candidates = [tiles[1]]; 194 | } 195 | let rot = 0; 196 | let tile = candidates[randInt(candidates.length)]; 197 | if (y == 0 && x > 0) { 198 | rot = randInt(2)*3; 199 | } else if (x == y) { 200 | rot = 1+randInt(2)*3; 201 | }else { 202 | rot = randInt(6); 203 | } 204 | let pt = tile.place(rot); 205 | line.push(pt); 206 | } 207 | data.push(line); 208 | } 209 | for (let x=0; x 0 && x==0) { 214 | continue; 215 | } 216 | for (let r=0; r Self { 33 | Self { 34 | line_size: 1.0, 35 | columns: 5, 36 | rows: 5, 37 | spacing: 0.0, 38 | is_pointy_orientation: false, 39 | cell_size: 40.0, 40 | tree_h: 0.7, 41 | angle: 45.0, 42 | angle2: 0.0, 43 | } 44 | } 45 | } 46 | 47 | impl App for HexGridSketch { 48 | fn update(&mut self, sketch: &mut Sketch, _ctx: &mut Context) -> anyhow::Result<()> { 49 | sketch.stroke_width(self.line_size); 50 | 51 | let grid = if self.is_pointy_orientation { 52 | HexGrid::with_pointy_orientation() 53 | } else { 54 | HexGrid::with_flat_orientation() 55 | }; 56 | 57 | sketch.scale(Unit::Mm); 58 | 59 | grid.cell_size(self.cell_size) 60 | .columns(self.columns) 61 | .rows(self.rows) 62 | .spacing(self.spacing) 63 | .build(sketch, |sketch, cell| { 64 | let b = Point::new(0.0, 0.0); //cell.center; 65 | let top = b + Point::new(0, -self.cell_size * self.tree_h); 66 | 67 | sketch.push_matrix(); 68 | 69 | sketch.translate(cell.center.x(), cell.center.y()); 70 | sketch.rotate_around(Angle::from_deg(self.angle2), 0.0, 0.0); 71 | 72 | let size: f64 = self.tree_h * self.cell_size; 73 | let steps = _ctx.rng_range(2..5); 74 | let mut branches = vec![(0.0, 0); steps]; 75 | let shape = _ctx.rng_range(0..1); 76 | let branch_w = vec![(80.0, 0), (20.0, 1), (10.0, 2), (10.0, 3)]; 77 | let linec = { 78 | if _ctx.rng_weighted_bool(0.10) { 79 | _ctx.rng_range(2..4) 80 | } else { 81 | 1 82 | } 83 | }; 84 | 85 | for b in 0..steps { 86 | let pos = size * ((1 + b) as f64) / (steps as f64 + 1.0); 87 | let mut maxl = size; 88 | if shape == 0 { 89 | maxl = f64::min(size * 0.3, pos); 90 | } 91 | let l = _ctx.rng_range(0.3..1.3) * maxl; 92 | let t = *_ctx.rng_weighted_choice(&branch_w); 93 | branches[b] = (l, t); 94 | } 95 | 96 | if !self.is_pointy_orientation { 97 | sketch.rotate(Angle::from_deg(30.0)); 98 | } 99 | 100 | for _i in 0..3 { 101 | sketch.rotate(Angle::from_deg(60.0)); 102 | 103 | if linec == 1 { 104 | sketch.line(0, -size, 0, size); 105 | } else if linec == 2 { 106 | let w = 0.05 * size; 107 | sketch.line(-w, -size, -w, size); 108 | sketch.line(w, size, w, -size); 109 | } else { 110 | for i in 0..linec { 111 | let w = 0.1 * size; 112 | let p = i as f64 * w / ((linec - 1) as f64) - 0.5 * w; 113 | let down = if i % 2 == 0 {1f64} else {-1f64}; 114 | sketch.line(p, -size * down, p, size * down); 115 | 116 | 117 | } 118 | } 119 | 120 | } 121 | for _i in 0..6 { 122 | sketch.rotate(Angle::from_deg(60.0)); 123 | //sketch.line(0, 0, 0, size); 124 | for b in 0..steps { 125 | let pos = size * ((1 + b) as f64) / (steps as f64 + 1.0); 126 | let (l, t) = branches[b]; 127 | if t == 0 { 128 | sketch.line( 129 | -f64::sin(60_f64.to_radians()) * l, 130 | pos + f64::cos(60_f64.to_radians()) * l, 131 | 0, 132 | pos, 133 | ); 134 | sketch.line( 135 | 0, 136 | pos, 137 | f64::sin(60_f64.to_radians()) * l, 138 | pos + f64::cos(60_f64.to_radians()) * l, 139 | ); 140 | } else if t == 1 { 141 | sketch.line( 142 | -f64::sin(60_f64.to_radians()) * l, 143 | pos - f64::cos(60_f64.to_radians()) * l, 144 | 0, 145 | pos, 146 | ); 147 | sketch.line( 148 | 0, 149 | pos, 150 | f64::sin(60_f64.to_radians()) * l, 151 | pos - f64::cos(60_f64.to_radians()) * l, 152 | ); 153 | } else if t == 2 { 154 | sketch.circle(0, pos, f64::min(l, 0.15 * size)); 155 | } else if t == 3 { 156 | let l2 = f64::min(l, 0.15 * size); 157 | sketch 158 | .push_matrix() 159 | .translate(0, pos) 160 | .polyline( 161 | (0..6).map(|i| { 162 | let a = ((60 * i) as f64).to_radians(); 163 | Point::new(a.sin() * l2, a.cos() * l2) 164 | }), 165 | true, 166 | ) 167 | .pop_matrix(); 168 | } 169 | } 170 | } 171 | 172 | sketch.pop_matrix(); 173 | //sketch.add_path(cell); 174 | }); 175 | 176 | Ok(()) 177 | } 178 | } 179 | 180 | fn main() -> Result { 181 | HexGridSketch::runner() 182 | .with_page_size_options(PageSize::A5H) 183 | .with_layout_options(LayoutOptions::Center) 184 | .run() 185 | } 186 | -------------------------------------------------------------------------------- /vsvg/simple/examples/mst.rs: -------------------------------------------------------------------------------- 1 | 2 | use image::{GenericImageView, ImageBuffer, ImageReader, Pixel, Rgb}; 3 | use kurbo::Vec2; 4 | use nalgebra::{vector, Vector2, Vector3}; 5 | use serde; 6 | use std::result::Result; 7 | use vsvg::UNITS; 8 | use whiskers::prelude::*; 9 | 10 | #[sketch_app] 11 | struct MSTImageSketch { 12 | #[param(slider, min = 0.01, max = 5.0)] 13 | line_size: f64, 14 | path: String, 15 | have_image: bool, 16 | #[param(min = 0.0, max = 100.0)] 17 | zero_dist_cost: f32, 18 | min_dist: bool, 19 | pr_mult: f32, 20 | #[param(slider, min = 0.01, max = 5.0)] 21 | pr_power: f64, 22 | #[skip] 23 | #[serde(skip)] 24 | dstate: DynamicState, 25 | } 26 | 27 | #[derive(Default)] 28 | struct DynamicState { 29 | path_loaded: String, 30 | image: ImageBuffer, Vec>, 31 | } 32 | 33 | impl Default for MSTImageSketch { 34 | fn default() -> Self { 35 | Self { 36 | line_size: 1.0, 37 | path: String::default(), 38 | zero_dist_cost: 0f32, 39 | pr_mult: 1f32, 40 | pr_power: 1f64, 41 | have_image: false, 42 | min_dist: false, 43 | dstate: DynamicState::default(), 44 | } 45 | } 46 | } 47 | 48 | impl MSTImageSketch { 49 | fn load_image(&mut self) -> Result<(), ()> { 50 | let mut img = ImageReader::open(&self.path) 51 | .map_err(|e| ())? 52 | .decode() 53 | .map_err(|e| ())? 54 | .to_rgb8(); 55 | 56 | let img = image::imageops::resize( 57 | &img, 58 | img.width() / 4, 59 | img.height() / 4, 60 | image::imageops::FilterType::CatmullRom, 61 | ); 62 | 63 | self.dstate.image = img; 64 | self.dstate.path_loaded = self.path.clone(); 65 | return Ok(()); 66 | } 67 | } 68 | 69 | fn pixel_magnitude2(p: Rgb) -> i32 { 70 | let c = p.channels(); 71 | return c[0] as i32 * c[0] as i32 + c[1] as i32 * c[1] as i32 + c[2] as i32 * c[2] as i32; 72 | } 73 | 74 | type IPos2 = Vector2; 75 | 76 | fn pixel_to_vec32f(p: Rgb) -> Vector3 { 77 | return Vector3::new(p.0[0] as f32, p.0[1] as f32, p.0[2] as f32); 78 | } 79 | 80 | 81 | struct HeapPos { 82 | cost: f32, 83 | pos: IPos2, 84 | prev: IPos2, 85 | } 86 | 87 | 88 | impl PartialEq for HeapPos { 89 | fn eq(&self, other: &Self) -> bool { 90 | self.cost == other.cost 91 | } 92 | } 93 | impl PartialOrd for HeapPos { 94 | fn partial_cmp(&self, other: &Self) -> Option { 95 | return Some(self.cmp(other)); 96 | } 97 | } 98 | impl Ord for HeapPos { 99 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 100 | return other.cost.total_cmp(&self.cost); 101 | } 102 | } 103 | impl Eq for HeapPos { 104 | 105 | } 106 | 107 | pub fn grid_cell(grid: f64, pos: IPos2) -> Vec2 { 108 | return Vec2::new(pos.x as f64 * grid, pos.y as f64 * grid); 109 | } 110 | 111 | impl App for MSTImageSketch { 112 | fn update(&mut self, sketch: &mut Sketch, _ctx: &mut Context) -> anyhow::Result<()> { 113 | if self.path != self.dstate.path_loaded { 114 | match self.load_image() { 115 | Err(_) => { 116 | std::eprintln!("Failed to load image"); 117 | self.have_image = false; 118 | } 119 | Ok(_) => { 120 | self.have_image = true; 121 | } 122 | } 123 | self.dstate.path_loaded = self.path.clone(); 124 | } 125 | if self.dstate.image.is_empty() { 126 | eprintln!("No image"); 127 | return Ok(()); 128 | } 129 | sketch.scale(Unit::Mm); 130 | sketch.stroke_width(self.line_size); 131 | 132 | let image = &self.dstate.image; 133 | let pw = vsvg::Unit::Mm.convert(sketch.width()) / image.width() as f64; 134 | 135 | let grid = Grid::from_cell_size([pw, pw]) 136 | .columns(self.dstate.image.width() as usize) 137 | .rows(self.dstate.image.height() as usize); 138 | let mut nr = 0; 139 | /*grid.build(sketch, |sketch, cell| { 140 | let p1 = cell.position; 141 | let p2 = p1 + Point::new(cell.size[0], 0); 142 | if cell.column + 1 >= image.width() as usize { 143 | return; 144 | } 145 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 146 | let c2 = image.get_pixel((cell.column + 1) as u32, cell.row as u32); 147 | if pixel_magnitude2(*c1) < pixel_magnitude2(*c2) { 148 | sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 149 | } 150 | if nr < 8000000 { 151 | nr += 1; 152 | } 153 | });*/ 154 | let mut heap: std::collections::BinaryHeap = std::collections::BinaryHeap::new(); 155 | let mut processed: std::collections::HashSet = std::collections::HashSet::new(); 156 | let DV = [ 157 | IPos2::new(-1, 0), 158 | IPos2::new(1, 0), 159 | IPos2::new(0, 1), 160 | IPos2::new(0, -1), 161 | IPos2::new(1, -1), 162 | IPos2::new(-1, -1), 163 | IPos2::new(1, 1), 164 | IPos2::new(-1, 1), 165 | ]; 166 | let mut p0 = IPos2::new(0, 0); 167 | let mut best = 100_f32; 168 | for i in 0..image.height() { 169 | for j in 0..image.width() { 170 | let p = *image.get_pixel(j, i); 171 | let sum = pixel_to_vec32f(p).magnitude(); 172 | if sum < best { 173 | p0 = IPos2::new(j as i32, i as i32); 174 | best = sum; 175 | } 176 | } 177 | } 178 | 179 | heap.push(HeapPos { 180 | cost: 0.0, 181 | pos: p0, 182 | prev: p0, 183 | }); 184 | while let Some(front) = heap.pop() { 185 | if processed.contains(&front.pos) { 186 | continue; 187 | } 188 | processed.insert(front.pos); 189 | 190 | let current_pix = 191 | pixel_to_vec32f(*image.get_pixel(front.pos.x as u32, front.pos.y as u32)); 192 | 193 | let pr = 1f64 - current_pix.magnitude() as f64/((3*255*255) as f64).sqrt(); 194 | if !_ctx.rng_weighted_bool(( self.pr_mult as f64 * pr.powf(self.pr_power)).clamp(0.0, 1.0)) { 195 | continue; 196 | } 197 | 198 | let p1 = grid_cell(pw, front.pos); 199 | let p2 = grid_cell(pw, front.prev); 200 | 201 | 202 | sketch.line(p1.x, p1.y, p2.x, p2.y); 203 | for dir in DV { 204 | let target = front.pos + dir; 205 | 206 | if target.x < 0 207 | || target.y < 0 208 | || !image.in_bounds(target.x as u32, target.y as u32) 209 | { 210 | continue; 211 | } 212 | let target_pixel = 213 | pixel_to_vec32f(*image.get_pixel(target.x as u32, target.y as u32)); 214 | let mut dist = ((target_pixel - current_pix).magnitude() + self.zero_dist_cost) * (dir.dot(&dir) as f32).sqrt(); 215 | if self.min_dist { 216 | dist += front.cost; 217 | } 218 | heap.push(HeapPos { 219 | cost: dist /*+ front.cost*/, 220 | pos: target, 221 | prev: front.pos, 222 | }) 223 | } 224 | } 225 | 226 | Ok(()) 227 | } 228 | } 229 | 230 | fn main() -> whiskers::prelude::Result { 231 | MSTImageSketch::runner() 232 | .with_page_size_options(PageSize::A5H) 233 | .with_layout_options(LayoutOptions::Center) 234 | .run() 235 | } 236 | -------------------------------------------------------------------------------- /vsvg/simple/examples/circles.rs: -------------------------------------------------------------------------------- 1 | 2 | use image::{GenericImageView, ImageBuffer, ImageReader, Luma, Pixel, Rgb}; 3 | use kurbo::Vec2; 4 | use nalgebra::{vector, Vector2, Vector3}; 5 | use serde; 6 | use std::result::Result; 7 | use vsvg::UNITS; 8 | use whiskers::prelude::*; 9 | 10 | 11 | #[sketch_app] 12 | struct CircleSketch { 13 | #[param(slider, min = 0.01, max = 5.0)] 14 | line_size: f64, 15 | grid_mul: f64, 16 | path: String, 17 | have_image:bool, 18 | scale_mul: f64, 19 | w: u32, 20 | h: u32, 21 | 22 | circles: usize, 23 | black_max: i32, 24 | rings: i32, 25 | #[param(slider, min = 0.1, max = 20.0)] 26 | spacing: f64, 27 | 28 | subdiv: i32, 29 | 30 | priority: bool, 31 | invert: bool, 32 | 33 | 34 | 35 | #[skip] 36 | #[serde(skip)] 37 | dstate: DynamicState, 38 | } 39 | 40 | #[derive(Default)] 41 | struct DynamicState { 42 | path_loaded: String, 43 | image: ImageBuffer, Vec>, 44 | } 45 | 46 | impl Default for CircleSketch { 47 | fn default() -> Self { 48 | Self { 49 | line_size: 1.0, 50 | grid_mul: 1.0, 51 | path: String::default(), 52 | have_image: false, 53 | scale_mul: 1.0, 54 | w: 0, 55 | h: 0, 56 | circles: 4, 57 | black_max: 5, 58 | rings: 20, 59 | spacing: 5.0, 60 | subdiv: 128, 61 | priority: false, 62 | invert: false, 63 | dstate: DynamicState::default(), 64 | } 65 | } 66 | } 67 | 68 | impl CircleSketch { 69 | fn load_image(&mut self) -> Result<(), ()> { 70 | let mut img = ImageReader::open(&self.path) 71 | .map_err(|e| ())? 72 | .decode() 73 | .map_err(|e| ())? 74 | .to_rgb8(); 75 | 76 | let w2 = (img.width()as f64 * self.scale_mul) as u32; 77 | let h2 = (img.height()as f64 * self.scale_mul) as u32; 78 | self.w = w2; 79 | self.h = h2; 80 | 81 | let img = image::imageops::resize( 82 | &img, 83 | w2, 84 | h2, 85 | image::imageops::FilterType::CatmullRom, 86 | ); 87 | 88 | self.dstate.image = img; 89 | self.dstate.path_loaded = self.path.clone(); 90 | return Ok(()); 91 | } 92 | 93 | fn draw(self: &mut CircleSketch, sketch: &mut Sketch, ctx: &mut Context) 94 | { 95 | let image = &self.dstate.image; 96 | let scale1 = f64::min(vsvg::Unit::Mm.convert_from(&vsvg::Unit::Px, sketch.width()) / image.width() as f64, 97 | vsvg::Unit::Mm.convert_from(&vsvg::Unit::Px, sketch.height()) / image.height() as f64); 98 | let circle_space = self.line_size * self.spacing; 99 | 100 | 101 | let mut flags: ImageBuffer, Vec> = ImageBuffer::from_fn(image.width(), image.height(), |x, y| { 102 | image::Luma([0u8]) 103 | }); 104 | 105 | let mut circles = Vec::new(); 106 | let w = (image.width() as f64) * scale1; 107 | let h = (image.height() as f64) * scale1; 108 | for circle in 0..self.circles { 109 | let c = ctx.rng_point(0.0..w, 0.0..h); 110 | let r = self.rings as f64 * circle_space; 111 | 112 | circles.push((c, r)); 113 | } 114 | for circle in 0..self.circles as usize { 115 | let (c, r) = circles[circle]; 116 | for ring in 0..self.rings { 117 | //sketch.circle(c.x(), c.y(), (ring+1) as f64 * circle_space); 118 | let r = (ring+1) as f64 * circle_space; 119 | let mut prev = c + Point::new(r, 0); 120 | for i in 1..=self.subdiv { 121 | let angle = f64::to_radians((360.0 * i as f64) / (self.subdiv as f64)); 122 | let p2: Point = c + Point::new(angle.cos(), angle.sin()) * r; 123 | 124 | let mut posp = (image.width() as f64 * (p2.x()/w), image.height()as f64 * (p2.y()/h)); 125 | posp.0 = if posp.0 >= 0.0 {posp.0} else {(image.width() + 3) as f64}; 126 | posp.1 = if posp.1 >= 0.0 {posp.1} else {(image.height() + 3) as f64}; 127 | 128 | let pixel = image.get_pixel_checked(posp.0 as u32, posp.1 as u32).unwrap_or(&image::Rgb([255u8, 255u8, 255u8])); 129 | //let v = pixel_magnitude2(*pixel); 130 | let v = pixel.to_luma().0[0]; 131 | 132 | let numf = if self.invert { 133 | v as f32 / 255.0 134 | } else { 135 | 1.0-(v as f32 / 255.0) 136 | }; 137 | let need = (numf * self.black_max as f32) as i32; 138 | 139 | let num_have = if self.priority { 140 | circles[0..circle].iter().map(|x: &(Point, f64)| { 141 | if p2.distance(&x.0) < x.1 { 142 | 1 143 | } else { 144 | 0 145 | } 146 | }).sum() 147 | } else { 148 | circles.iter().enumerate().map(|(i, x)| { 149 | if i == circle { 150 | return 0; 151 | } 152 | if p2.distance(&x.0) < x.1 && p2.distance(&x.0) < p2.distance(&c){ 153 | 1 154 | } else { 155 | 0 156 | } 157 | }).sum() 158 | }; 159 | 160 | if need > num_have { 161 | sketch.line(prev.x(), prev.y(), p2.x(), p2.y()); 162 | } 163 | 164 | prev = p2; 165 | 166 | } 167 | } 168 | } 169 | 170 | /*grid.build(sketch, |sketch, cell| { 171 | let p1 = cell.position; 172 | let p2 = p1 + Point::new(cell.size[0]*0.1, 0); 173 | 174 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 175 | let mg = pixel_magnitude2(*c1).isqrt(); 176 | 177 | let W = mina.len(); 178 | let a: i32 = mina[cell.row % W][cell.column % W]; 179 | let d: i32 = dir[cell.row % W][cell.column % W]; 180 | 181 | 182 | 183 | if 443-mg > ((443) * (a+1)) / 17 { 184 | *flags.get_pixel_mut(cell.column as u32, cell.row as u32) = Luma([d as u8; 1]); 185 | //sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 186 | } 187 | });*/ 188 | 189 | } 190 | } 191 | 192 | fn pixel_magnitude2(p: Rgb) -> i32 { 193 | let c = p.channels(); 194 | return c[0] as i32 * c[0] as i32 + c[1] as i32 * c[1] as i32 + c[2] as i32 * c[2] as i32; 195 | } 196 | 197 | type IPos2 = Vector2; 198 | 199 | fn pixel_to_vec32f(p: Rgb) -> Vector3 { 200 | return Vector3::new(p.0[0] as f32, p.0[1] as f32, p.0[2] as f32); 201 | } 202 | 203 | 204 | 205 | pub fn grid_cell(grid: f64, pos: IPos2) -> Vec2 { 206 | return Vec2::new(pos.x as f64 * grid, pos.y as f64 * grid); 207 | } 208 | 209 | 210 | 211 | 212 | 213 | 214 | impl App for CircleSketch { 215 | fn update(&mut self, sketch: &mut Sketch, ctx: &mut Context) -> anyhow::Result<()> { 216 | if self.path != self.dstate.path_loaded { 217 | match self.load_image() { 218 | Err(_) => { 219 | std::eprintln!("Failed to load image"); 220 | self.have_image = false; 221 | } 222 | Ok(_) => { 223 | self.have_image = true; 224 | } 225 | } 226 | self.dstate.path_loaded = self.path.clone(); 227 | } 228 | if self.dstate.image.is_empty() { 229 | eprintln!("No image"); 230 | return Ok(()); 231 | } 232 | sketch.scale(Unit::Mm); 233 | sketch.stroke_width( vsvg::Unit::Mm.convert_to(&vsvg::Unit::Px, self.line_size) ); 234 | 235 | self.draw(sketch, ctx); 236 | 237 | Ok(()) 238 | } 239 | } 240 | 241 | fn main() -> whiskers::prelude::Result { 242 | CircleSketch::runner() 243 | .with_page_size_options(PageSize::A5H) 244 | .with_layout_options(LayoutOptions::Center) 245 | .run() 246 | } 247 | -------------------------------------------------------------------------------- /notes/protocols.md: -------------------------------------------------------------------------------- 1 | 2 | | | HP-GL | GP-GL | DMP/PL | DXG-GL, CAMM-GL mode 1 | RD-GL, CAMM-GL mode 2 | GCode | 3 | | :------------------- | :----------- | -------------- | ----------------------------------------------- | --------------------------- | --------------------- | ------------------------ | 4 | | Main or initial user | HP | Graphtec | Houston instrument | Roland | Roland | | 5 | | units | 0.025mm, 0.02488mm* | 0.1mm/0.025mm | EC1 0.001in, EC5 0.005in, ECM 0.1mm, ECN 0.025mm | 0.025mm, some mills 0.01mm, 0.1mm | 0.025mm | G20 inch, G21 metric(mm) | 6 | | pen lift/down | PU/PD | M/D | M/D | M/D | PU/PD | machine specific | 7 | | absolute/relative | PA/PR | M/O (down D/E) | A/R | M/R (down D/I) | PA/PR | G90/G91 | 8 | 9 | 10 | # HP-GL (and HP-GL/2) 11 | 12 | Some materials mention HP-GL/2 and HP RTL, what are those? 13 | 14 | HP RTL is protocol for raster printers, not relevant for pen plotters 15 | The HP-GL/2 looks very similar to HP-GL, but it seemed to be described in context of device that support raster printing and other printing protocols. It's unclear if 16 | 17 | Exact scaling factor HP-GL devices is unclear. Many manuals say simply 0.025mm, some device manuals say "0.02488mm or 0.00098in", but there are also some that say "0.00098in 0.025mm". Does the real value vary between device models, are some of those simply inacurate rounding? It almost sounds like 0.02488mm is bad double conversation roundtrip. And that's just the HP docs, who knows what the other manufacturers implementing HP-GL do. 18 | 19 | * 0.025mm = 40steps/mm = 1016steps/in (exact) ~= 0.00098425197in | 100% 20 | * 0.00098in=0.024892mm ~= 1020.408163 step/in ~= 40.173549 step/mm | 99.6% 21 | * 1021step/in ~= 0.0009794319in ~= 0.024877571mm | 99.5% 22 | 23 | Rounding error between 0.02488 and 0.025 might seem insignificant, but that's 1.5mm for A4 page or 3mm for A3 which can be easily measured and seen with naked eye. 24 | 25 | 26 | ## Available references 27 | * HP DraftPro Plotter Programmer's Reference 28 | * CHAPTER 9 HP-GL Graphics Language - not the offcial docs from HP 29 | * The HP-GL/2 and HP RTL Reference Guide A Handbook for Program Developers 30 | 31 | ## Secondary references (for confirming dialects supported by other manufacturer devices) 32 | * DMP-60 SERIES PLOTTERS OPERATION MANUAL 33 | * Houston Instrument DMP-160 Series Plotters Operation 34 | * Various Roland manuals where one of the supported modes is renamed version of HP-GL 35 | 36 | 37 | # GP-GL 38 | * Graphtec MP4000 Series command set reference manual 39 | * https://github.com/fablabnbg/inkscape-silhouette/blob/main/Commands.md 40 | * https://www.ohthehugemanatee.net/2011/07/gpgl-reference-courtesy-of-graphtec/ 41 | 42 | # Roland CAMM-GL (I/II/III), DXG-GL, RD-GL (I, II, III) 43 | 44 | The versioning is very weird. According to CAMM-GL II programmer's manual: 45 | > Consideration has been made for to ensure compatibility with DXY-GL for plotters from 46 | Roland DG Corp. and with CAMM-GL III for the CAMM-1 Series of cutting machines 47 | (mode1), as well as with CAMM-GL I for the CAMM-3 Series of compact modeling 48 | machines (mode1). 49 | 50 | Many Roland plotters support two command modes, one slightly similar to GP-Gl and other quite similar to HP-GL. 51 | 52 | 53 | | name | device series | similar roland protocol | similar protocol | device type | notes | 54 | | :----------------- | :------------ | :---------------------- | :--------------- | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 55 | | DXG-GL | DXY | | GP-GL* | pen plotter | *many of the more advanced commands differ from GP-GL | 56 | | RD-GL I, II, III | DXY,DPX | | HP-GL | pen plotter | Roman number roughly means protocol version, with higher version meaning more commands. But III isn't a strict superset, some commands are removed or argument meaning changed. | 57 | | CAMM-GL III mode 1 | CAMM-1 | DXG-GL | GP-GL* | cutting plotter | | 58 | | CAMM-GL II mode 1 | CAMM-2 | DXG-GL | GP-GL* | engraver | Additional commands for spindle motor control and engraving depth | 59 | | CAMM-GL I mode 1 | CAMM-3 | DXG-GL | GP-GL* | mill | Adds additional commands for 3axis movement and spindle control. Some mills are using 0.01 instead of 0.025 step size. Recommended to use CAM software meant for CNC mills instead of plotters/engravers. | 60 | | CAMM-GL III mode 2 | CAMM-1 | RD-GL | HP-GL | cutting plotter | The Roman number based numbering scheme for RD-GL and CAMM-GL has completely different meaning, | 61 | | CAMM-GL II mode 2 | CAMM-2 | RD-GL | HP-GL | engraver | | 62 | | CAMM-GL I mode 2 | CAMM-3 | RD-GL | HP-GL | mill | Recommended to use CAM software meant for CNC mills instead of plotters/engravers. | 63 | 64 | DXG-GL, CAMM-GL mode 1 uses only 1 character commands while the GP-GL has some 2 character commands. 65 | 66 | CAMM-GL devices have some 3 character commands prefixed by !, which can be used in both mode 1 and mode 2. These are usually Roland specific config commands which differ from GP-GL and HP-GL. 67 | 68 | 69 | ## Available references 70 | * CAMM-GL II Programmer's Manual (CAMM-GL II mode 1, mode 2) 71 | 72 | * DXY-880 operation manual (DXG-GL, RD-GL) 73 | * DXY-1350A manual (DXG-GL) 74 | 75 | 76 | # DM/PL 77 | 78 | ## Available references 79 | * DMP/PL COMMAND LANGUAGE 80 | * Houston Instrument DMP-160 Series Plotters Operation Manual 81 | * HIPLOT® DMP-51/52 OPERATION MANUAL 82 | * DMP-60 SERIES PLOTTERS OPERATION MANUAL 83 | 84 | # GCode 85 | Commonly used CNC mills, lathes, routers more recently also used by most FDM 3d printers. Popular choice for DIY machines with wide variety of available controller boards and firmwares. 86 | 87 | While basic commands are similar almost major manufacturer or firmware uses their own dialect. The biggest differences are usually related to: 88 | * initialization 89 | * homing procedure 90 | * device configuration changes 91 | * machine type and tool specific actions (needs of CNC mill are different from a lathe or 3d printer). For example 3d printer will have commands for controlling hotend temperature and synchronizing filament flow with movements, but CNC mills are more concerned with how fast the tool spins, tool size compensation and various routines for special operations like drilling or tapping. 92 | * macros and builtin scripting functionality 93 | 94 | ## Available references 95 | When possible check the documentation of relevant controller manufacturer or firmware. 96 | 97 | * https://www.linuxcnc.org/documents/ the GCode dialect is based on RS274/NGC . LinuxCNC rarely used for pen plotters 98 | * RS274/NGC - not to be confused with various RS274 standards, describes the NIST reference GCode interpreters (with actual source code not an abstract specification) 99 | * RS274 - common name for various standards made by NIST and other standards organizations describing the GCode. As far as I am aware the text can be only obtained by paying like many other standards. Limited relevance for hobbyists since most controller firmwares follow it very loosely. 100 | * https://reprap.org/wiki/G-code - covers differences between various 3d printer firmwares 101 | * https://github.com/gnea/grbl/wiki and https://github.com/gnea/grbl/wiki While the original GRBL itself isn't actively maintained and the supported hardware is outdated, there are many forks derived from GRBL supporting newer hardware which are using similar gcode dialect. There are also plenty of older (and not so old) DIY builds still using it. 102 | - [FluidNC](http://wiki.fluidnc.com/) - popular for DIY plotters, currently supports only controller boards using one specific ESP32 chip, but range of supported hardware configurations isn't bad 103 | - [grblHAL](https://github.com/grblHAL) 104 | - [RabbitGRBL](https://github.com/SourceRabbit/RabbitGRBL) 105 | - [uCNC](https://github.com/Paciente8159/uCNC) 106 | * [MarlinFW](https://marlinfw.org/meta/gcode/) - Popular open source controller firmware. Supports wide range of controller boards. Primary targeted at 3d printers, but also has some CNC and laser specific features. 107 | 108 | ## Other DIY friendly GCode interpreters 109 | * [Klipper](https://www.klipper3d.org/G-Codes.html) - high performance gcode interpreter. Primarily targeted at 3d printers, non 3d printer functionality limited. Good support for wide range 110 | 111 | # EBB board protocol 112 | 113 | Used by AxiDraw and iDraw machines. 114 | 115 | * http://evil-mad.github.io/EggBot/ebb.html 116 | 117 | ## Available references 118 | * [EBBB command set Firmware V3.0+](http://evil-mad.github.io/EggBot/ebb.html) -------------------------------------------------------------------------------- /notes/diy_plotters.md: -------------------------------------------------------------------------------- 1 | TODO: 2 | 3 | ## Commonly used parts 4 | 5 | * Nema17 stepper motors (could use other sizes as well, but these are most common and easily available and usually don't need bigger ones in a pen plotter) 6 | * 28BYJ-48 stepper motors (not recommended unless you are going for <$80 build budget, out of the box these will not work with many controller CNC/3d printer firmwares/stepper drivers) 7 | * controller board -> recommended to search for "3d printer motherboard". You can get a decent modern board for $25-$50. Some manufacturers: 8 | - BIGTREETECH / BIQU 9 | - fysetc 10 | - MKS 11 | * (not recommended) boards mentioning Arduino, RAMPS or arduino CNC shield . 12 | * stepper motor driver boards 13 | - some controller boards have builtin 14 | - other boards have sockets for step stick format driver boards 15 | - driver based on TMC chip are recommended (at the time of writing these notes) 16 | - larger format standalone stepper drivers are unlikely to be necessary for motors that you would use in a pen plotter 17 | * Linear rails 18 | * Linear bearings common size LM8UU 19 | * Aluminum extrusion (for frame) commonly used 2020 series. There are variations 2x1, 3x1 and other variations which are 40x20 and 60x20 mm accordingly. Usually used together with matching angle brackets and t-nuts. Notes. Note that there are various other similar looking aluminum extrusion systems which have incompatible dimensions. Also 80/20 extrusions often instead referring to 4x1 2020 series profile (which have dimensions of 80x20mm), instead means one of the aluminum extrusions made by company called "80/20" which have multiple extrusion systems including some that are based on inches instead of metric units. 20 | * V-wheels for matching aluminum extrusions. You can also buy ready mount plates with 3-4 wheels, with builtin mechanism for adjusting tension. 21 | * Belts and pulleys for them. Most common type used in 3d printers -> GT2 6mm. 22 | - belt 23 | - drive pulley for mounting on stepper motor shaft, with grubscrew for locking it in place on D shaped shaft 24 | - toothed and smooth idler pulleys depending on belt routing 25 | 26 | 27 | ### Linear slide mechanism 28 | 29 | Most pen plotters will have multiple linear axis requiring some kind of slide mechanism. Common choices are 2020 V slot profiles with POM wheels, round rods with linear bearings or plastic bushings, linear rails. 30 | 31 | 32 | My personal opinion is that linear rails are slightly overhyped for pen plotters. They are good parts when used correctly, but they aren't silver bullet that will automatically make the plotter 10x more accurate. 33 | I get the feeling that one of reasons linear rails are perceived as "being the best" is because out of the box they directly provide all the required functionality and even when used incorrectly will likely behave good enough for DIY level machine. Other systems have a lot more room for behaving awful when used incorrectly. 34 | 35 | Seemingly out of the box they: 36 | * are straight and self supported (not quite true 1*) 37 | * fully constrain rotation and position along all axis except one linear motion (*3) 38 | * has no slop (*2) 39 | 40 | In reality 41 | 1*) Linear rails are not designed to be a structural part. They will be only as straight and rigid as frame you mount them to. Any stick shaped object longer than 0.5m will bend. Less critical for pen plotters, but for any other CNC machine if you want to benefit from high quality linear rails you need to think about what you are attaching the linear rail to. 42 | 2*) Cheap linear rails can have slop especially if you are buying the rails and carriages separately. Less of problem if you are buying a set of rail+carriage in which case the seller might have already picked pairs which have the best fit. Occasionally hobbyists will buy a rail+a bunch of carriages just to pick one with best fit. 43 | 3*) Typical linear rails are quite narrow. This means that while they constrain rotation on all 3 axis, the maximum torque is limited and the leverage from anything attached to it can easily be 10-20x than the size of carriage. So in any application where some forces are involved will likely have 2 parallel rails and or carriages. Increasing spacing with 2 rails is often a simpler solution than increasing size of rail. 44 | 45 | In comparison 46 | 47 | Linear rods+linear bearings(or plastic bushings) have few properties which require more careful design ahead of time. 48 | They don't constrain rotation which sometimes can be a feature, but in most cases you will need two parallel rods to constrain the rotation. 49 | On the other hand, like previously mentioned linear rails are often in pairs anyway so this isn't as big of deal as it might seem. But even if you have 2 parallel rods, unless you have additional structural they can easily twist. 50 | 51 | Linear bearing are round which makes attaching them to rest of mechanism a bit trickier. To make things worse they are often designed with assumption that they will be press fit in specifically size hole which will provide certain amount of compression and ensure 0 slop. Poorly designed systems can give an incorrect impression that linear bearings are inherently sloppy. You can buy linear bearing blocks which solve both the mounting and compression problems, but those are usually somewhat expensive. More common in industrial application, less in DIY builds. 52 | 53 | Linear rods can only be supported by the ends. You can't support them in the middle like linear rails or v wheels. This limits their use for bigger machines. 54 | 55 | 56 | V slot profiles with wheels might seem like inferior solution (partially due to prejudice against plastic), but if used correctly they can be more than adequate for the forces and precision requirements of pen plotters. 57 | 58 | First thing with v slot profiles and wheels is to get actual v slot aluminium profiles designed to be used with corresponding wheels. There are other 2020 aluminum profiles with very similar shape (except small difference for the grove in the middle), which are not designed to be used with wheels. Using wrong ones might work temporary immediately after assembling but wrong shape profiles will very quickly vear out groves in the wheels. Don't trust amazon/ebay listing descriptions, check the pictures as well. Even when description says V-slot pictures occasionally show regular non v slot 2020 profiles, best to avoid listing where picture doesn't match description. 59 | 60 | Second important thing is that the wheels need a to be properly tensioned. Typically this is achieved by having an eccentric nut which allows shifting the wheel by a millimeter in either direction and thus tighten it against the aluminum profile. Once tightened there should be no slop. But don't tighten them excessively as that will cause excessive wear on the wheels and also increase friction. 61 | 62 | You can buy the of the shelf mounting plates designed to be used with 2020 v slot profiles and wheels both as a kit with wheels included and just plates. They have the proper spacing for hole size to be used this way. You can buy just the wheels and eccentric nuts separately and design your own mounting plates. But unless you have the resources to make them out of metal, consider buying existing mounting plates. Attempting to 3d print them can easily lead to wheels either being loose immediately or over time as the plastic parts deform. The way tightening mechanism works means you can't easily support the bolts with wheels from both sides, and supporting them from single side with a plate is more suited for stronger materials. 63 | 64 | One advantage of v slots design is that the aluminum profiles can be also used as structural elements resulting in simpler more compact design. 65 | 66 | One more advantage of v slots is the wheel spacing. Even in the smallest case with 1x1 2020 you get 4 wheels spaced 40mm in both directions. You can also get bigger plates designed for 2x1 or 2x2 profiles further increasing wheel spacing. Larger wheel spacing helps resisting any torque without need for 2 parallel rails or multiple carriages on single rail like you would need do with linear rails or rods. 67 | 68 | ## Source of inspiration/existing models 69 | 70 | If you are looking for inspiration or an existing design to build checkout websites with models for 3d printing, and posting diy projects. Recommended keywords for search "plotter", "pen plotter", "drawingbot". The later two will have more projects which are using mix of different techniques not just 3d printing. 71 | 72 | 3d printing 73 | * [Printables](https://www.printables.com/search/models?q=plotter) 74 | * [various pen plotters](https://www.printables.com/@Kabacis_332837/collections/1870431) 75 | * [drawingmachines](https://www.printables.com/search/models?q=tag%3Adrawingmachine) 76 | * [penplotter](https://www.printables.com/search/models?q=tag%3Apenplotter) 77 | * [plotter](https://www.printables.com/search/models?q=tag%3Aplotter) 78 | * [Thingiverse](https://www.thingiverse.com/search?q=plotter) 79 | * [various pen plotters](https://www.thingiverse.com/karliss/collections/43091126/things) 80 | * [subassemblies](https://www.thingiverse.com/karliss/collections/43091129/things) 81 | * [Makerworld](https://makerworld.com/en/search/models?keyword=plotter) 82 | * [Thagns](https://thangs.com/search/pen%20plotter?scope=all) 83 | 84 | All kind of DIY projects: 85 | * [hackaday.io](https://hackaday.io/search?term=pen+plotter) 86 | * [instructables](https://www.instructables.com/search/?q=plotter&projects=all) 87 | 88 | * https://www.instructables.com/CNC-Plotter-1/ (lingib) 89 | * https://howtomechatronics.com/projects/diy-pen-plotter-with-automatic-tool-changer-cnc-drawing-machine/ 90 | 91 | 92 | ## Lifter designs 93 | 94 | 95 | * https://www.printables.com/model/868919-generative-pen-plotter-art-cnc-arduino-vinyl-cutte#preview.80Yeb (string stepper) 96 | * https://discord.com/channels/929089222118359100/1133548294925209814/1188035146457284668 (idraw clone) 97 | * https://discord.com/channels/499297341472505858/499298106035273770/1323441263625638039 (belt string) 98 | * https://discord.com/channels/929089222118359100/1191870618187087992/1331014811848413265 (belt loop, 2 sliders on one rail) 99 | 100 | 101 | # Various builds 102 | * https://github.com/DanniDesign/Ploxy/tree/main 103 | * https://github.com/jamescarruthers/PlotteRXY 104 | * https://www.thingiverse.com/thing:6841321 105 | * https://www.niklasroy.com/robotfactory/ 106 | * https://getmural.me/ https://github.com/nikivanov/mural 107 | 108 | # Pen changer builds 109 | 110 | * https://www.youtube.com/watch?v=GGtdwYdZWi8 111 | * https://howtomechatronics.com/projects/diy-pen-plotter-with-automatic-tool-changer-cnc-drawing-machine/ 112 | 113 | * https://www.youtube.com/watch?v=qAdHhoz2k2I 114 | 115 | * https://www.youtube.com/watch?v=JuZ5lk2J5p4 116 | * https://www.youtube.com/watch?v=CpA98QtHj4s 117 | 118 | * https://www.doublejumpelectric.com/projects/toolchanging_pen_plotter/2019-03-17-toolchanging_pen_plotter/ 119 | 120 | * https://patents.google.com/patent/US4417258A/en -------------------------------------------------------------------------------- /notes/prtocols_big.csv: -------------------------------------------------------------------------------- 1 | HP-GL GP-GL DMP/DL DXY-GL (I) CAMM-GL II mode1 CAM-GL I mode 1 RD-GL I RD-GL II RD-GL III CAMM-GL II mode2 2 | Main manufacturer HP Graphtec Houston instrument Roland Roland Roland 3 | Known plotters MP4000 DXY-1350A, DXY-1150A,DXY-880 PNC-3100 DXY-1350A, DXY-1150A DXY-1350A, DXY-1150A, DPX-3700A, DPX-2700A DPX-3700A,DPX-2700A 4 | Reference material Chapter 9 HP-Gl CAMM-GL II programmer’s manual CAMM-GL II programmer’s manual 5 | Based on GP-GL GP-GL GP-GL HP-GL HP-GL HP-GL RDL-GL I, CAMM-GL III mode 2, CAMM-GL I 6 | Some compatibility DXY-GL, CAMM-GL III mode 1, CAMM-GL mode 1 DXY-GL, CAMM-GL III mode 1, CAMM-GL mode 1 7 | Notes Has some engraving sturctions CNC mill, 3axis and spindle Accepts fractions Most (maybe) parameters accept only integers, has a few more commands than RD-GL I, symetric coordinates configurable 0, 0 always in bottom left unlike 8 | Command separator/terminator ; ETX \r\n ; ; ; 9 | ---------------------------- Basic movement -------------------------------------------------------------------------------------- 10 | Move/Pen up PU x1,y1,...; M x1, y1, ... M x1, y1, ... M x1, y1, ... PU x1,y1,...; PU x1,y1,...; PU x1,y1,...; PU x1,y1,...; 11 | Relative move PU/PR O dx,dy R dx1, dy1 R dx1, dy1 PU/PR PU/PR PU/PR PU/PR 12 | Draw absolute PA/PD D x1, y1, ... D x1, y1, ... D x1, y1, ... PA/PD PA/PD PA/PD PA/PD 13 | Relative draw PR/PD x1,y1,...; E dx1,dy1, ... I dx1,dy1, ... I dx1,dy1, ... PR/PD x1,y1,...; PR/PD x1,y1,...; PR/PD x1,y1,...; PR/PD x1,y1,...; 14 | Move polar MP r,o,[t] 15 | Draw polar DP r1,o1,r2,o2,rn,on 16 | Relative draw polar EP 17 | Relative move polar OP 18 | Radius plot RP 19 | ??? PE 20 | Advance full page (ignored)AF; (ignored)AF; 21 | Page feed PG[ n]; PG[ n]; PG[ n]; PG[ n]; 22 | ---------------------------- Circle stuff -------------------------------------------------------------------------------------- 23 | Circle center A x,y A x,y 24 | Circle AA x,y,oc,od; W x0,y0,r1,r2,o1,o2[,d][,t] C x,y,r,o1,o2,(od) C x,y,r,o1,o2,(od) AA x,y,oc,od; AA x,y,oc,od AA x,y,oc,od; AA x,y,oc,od; 25 | Relative circle AR dx,dy,oc,od E r,o1,o2,od E r,o1,o2,od AR dx,dy,oc,od AR dx,dy,oc,od AR dx,dy,oc,od AR dx,dy,oc,od 26 | A+Circle G r,o1,o2 G r,o1,o2 27 | Circle here CI r,od ]r1,r2,o1,o2[,d][,t] CI r, od CI r,od CI r,od CI r,od 28 | ??? absolute circle stuff AT ??? 29 | ??? relative circle stuff RT 30 | Sector % A+ K n,l1,l2 A+ K n,l1,l2 31 | 3 point circle WP x1,y1,x2,y2,x3,y3 32 | Ellipse )a,x0,y0,r1,r2,o1,o2,o3 33 | ---------------------------- ???????????? -------------------------------------------------------------------------------------- 34 | Curve Y a,x0,y0,... Y m,x1,y1,x2,y2... Y m,x1,y1,x2,y2... 35 | Relative curve _ a,dx0,dy0,... _ m,dx1,dy1,... 36 | Bezier curve BZ a,x1,y1,x2,y3,x4,y4 37 | Axis (draw axis with tickmarks) X X p,q,r X p,q,r 38 | Tick length TL lp, ln; TL lp, ln; TL lp, ln; 39 | X tick XT; XT; XT; XT; 40 | Y tick YT; YT; YT; YT; 41 | ---------------------------- Text -------------------------------------------------------------------------------------- 42 | Print text LB c1,c2,cn [terminator]; P P c1c2cn... P c1c2cn... LB c1,c2,cn [terminator] LB c1,c2,cn [terminator] LB c1,c2,cn [terminator] LB c1,c2,cn [terminator] 43 | Kana,Greek K 44 | Engraves special symbols N N n N n 45 | Alpha rotate R Q [0-3] Q [0-3] 46 | Alpha scale S S [0-127] S [0-127] 47 | ??? AD 48 | Altrnate character set CA[ n]; CA[ n]; CA[ n]; CA[ n]; 49 | Standard character set CS[ n]; CS[ n]; CS[ n]; CS[ n]; 50 | Activate standard character set SS; SS; SS; SS; SS; 51 | Activate alternate set SA; SA; SA; SA; SA; 52 | Move by character size CP nx,ny; CP nx,ny; CP nx,ny; CP nx,ny; CP nx,ny; 53 | Character Cord Angle (curved character quality) CC oc CC oc 54 | Font $n[,m] 55 | (unsuported)CF 56 | Absolute direction(text) DI[a,b]; DI[a,b]; DI[a,b]; DI[a,b]; DI[a,b]; 57 | Relative direction(text) DR[a,b]; DR[a,b]; DR[a,b]; DR[a,b]; DR[a,b]; 58 | Define label terminator DT t; DT t; DT t; DT t; DT t; 59 | DV?? (ignored)DV [0 or 1] DV path[0,1,2,3] line[0, 1] 60 | Extra text spacing Q ES w,h; ES w,h; ES w,h; 61 | ??? LO[ n]; LO[ n]; 62 | ???? SD?? 63 | absolute characer size SI w,h; (cm) SI w,h; SI w,h; (cm) how are fractions handled? SI w,h; SI w,h; (0.4 cm) 64 | Character slant SL[ tanO]; SL[ tanO]; SL[ tanO]; SL[ tanO]; SL[ tanO]; 65 | Alpha italic I 66 | Label position LP 67 | Alpha reset A 68 | Replot character RC c,x1,y1,[P],... 69 | Symbol mode -> draws character in everyy position spciefied by PA,PR or PD until exiting symbol mode SM s; SP SM s; SM s; SM s; SM s; 70 | Relative character size SR w,h; SR w,h; SR w,h; SR w,h; 71 | ???? TD i; 72 | User defined character UC x1,y1, ...;(lift logic based on value range) (P UC c,x1,y1, ...; UC c,x1,y1, ...; 73 | Select point mark SP 74 | User’s pattern ( 75 | ---------------------------- Area ,fill and polygons -------------------------------------------------------------------------------------- 76 | Pen change SP n; Jn[,m] J n SP n; SP n; SP n; 77 | Line type L p L p L p LT n,l 78 | Line scale B l B l B l 79 | Hatch rectangle % (also for non rectangle) T n,x,y,d,t T n,x,y,d,t 80 | Polygon mode PM [0,1,2] PM [0, 1, 2] 81 | Shade rectangle absolute RA x,y; RA x,y; RA x,y; RA x,y; RA x,y; 82 | Shade rectangle relative RR dx,dy; RR dx,dy; RR dx,dy; RR dx,dy; RR dx,dy; 83 | Shade wedge WG r,o1,oc,od WG r,o1,oc,od WG r,o1,oc,od WG r,o1,oc,od WG r,o1,oc,od 84 | Fill type FT n[,d[,o]] FT n[,d[,o]] FT n[,d[,o]] FT n[,d[,o]] FT n[,d[,o]] 85 | ??? line,fill,limit something AC ??? 86 | Line fill something LA 87 | Line type LT n[,l] LT n[,l] LT ??? LT n[,l] 88 | Pen width PW(1/300 inch) PW 89 | ??? (ignoded)RW index,width,height, pen number 90 | ??? UL 91 | ??? WU 92 | Edge rectangle absolute EA x,y; EA x,y; EA x,y; EA x,y; EA x,y; 93 | Edge rectangle relative ER dx,dy ER dx,dy ER dx,dy ER dx,dy ER dx,dy 94 | Edge wedge EW r,o1,oc,od EW r,o1,oc,od EW r,o1,oc,od EW r,o1,oc,od EW r,o1,oc,od 95 | Hatching spacing partition ratio UF d1,(d2, ...d20); 96 | Pen thickness PT[ d]; 0.1 --- 5.0(mm) PT[ d]; PT[ d]; 0.1 --- 5.0(mm) PT[ d]; (multiple of 0.4 mm) 97 | ---------------------------- Techincal -------------------------------------------------------------------------------------- 98 | Initialize IN; IN; IN[ n]; IN[ n]; IN; 99 | Default settings DF; DF; DF; DF; 100 | Clear (init and reset settings) : 101 | Interface clear ; 102 | Home(go, or find) H H H 103 | ???? BP i i?? 104 | CT?? CT[ n]; CT[ 0, 1]; 105 | DL??? DL n,pc,x1,y1, 106 | EC?? (ignored)EC n; (ignored)EC n; 107 | ??? (ignored)FR; (ignored)FR; 108 | ??? (ignored)MC 109 | ??? (ignored)MR 110 | ??? material type? MT [0,1,2,3,4,5] 111 | Not ready??, wait for user NR; NR; NR [timeout]; 112 | Activate pause unti l butto nis pressed !NR 113 | PS l,(w); PS l,w 114 | Quality level ?? QL [0..100] 115 | ??? ST [-1, 0, 1, 2] 116 | Velocity ! (multiple input modes) VS s; VS s,(n); cm/s VS [1-72] (n[1-8]) VS s; 117 | Acceleration, force * 118 | Cutter offset FC 119 | Blade rotational control FD 120 | Show message on display WD 121 | Set buffer sizes GM pl,dl,(r1,r2,r3) 122 | Buzzer/alert indicator T n 123 | Chart feed F l[t] 124 | Set terminator character = 125 | ---------------------------- Scaling and dimensions-------------------------------------------------------------------------------------- 126 | Paper size PS s; 127 | Roatete coordiante system RO [0,90]; / RO [0,90]; RO [0,90]; RO [0,90, 180, 270]; 128 | Scaling SC xmin,xmax,ymin,ymax SC xmin,xmax,ymin,ymax SC xmin,xmax,ymin,ymax(,type,left,bottom); SC Xmin, X,Xfactro,Ymin,Yfactor,type; SC Xmin,Xmax,Ymin,ymax,Xfactor,yFactro,tpye, left, bottom SC xmin,xmax,ymin,ymax 129 | Input mask IM[ e]; IM[ e]; IM[ e]; 130 | Input p1 p2 IP p1x,p1y(p2x,p2y) IP p1x,p1y(p2x,p2y) IP p1x,p1y(p2x,p2y) IP p1x,p1y(p2x,p2y) IP p1x,p1y(p2x,p2y) 131 | Input p1 p2 relative IR p1x,p1y,p2x,p2y % 132 | Write lower left (set?) \x,y 133 | Write upper right Zx,y 134 | Input window IW Llx,Lly,Urx,Ury IW Llx,Lly,Urx,Ury IW Llx,Lly,Urx,Ury IW Llx,Lly,Urx,Ury IW Llx,Lly,Urx,Ury 135 | Clipping (blocked area) > 136 | ???? RP [1-99] 137 | Offset ↑x,y(up arrow in what encoding?) 138 | Offset polar ^P 139 | Factor &p,q,r 140 | Set origin SO n 141 | ---------------------------- State query -------------------------------------------------------------------------------------- 142 | Output actual position OA; OA; OA; 143 | Output commanded position OC; OC; (unlike most other RD-GL II commands outputs fraction number) OC; 144 | GIN G 145 | call GIN C 146 | Output digitize (ignored)OD; (ignored)OD; OD; 147 | Output error OE; OE; OE; OE 148 | Output factor OF; (40 or 10 depending on 0.025mm or 0.1mm mode) OF; (->40 ) OF 149 | Output hard clip limits OH; OH; OH; OH; 150 | Output identification OI; (->1350, 1150) OI; (->1350, 1150) OI; OI; 151 | (ignored)OK; 152 | OL; 153 | Output option parameter OO; (->0,1,00,1,0,00) OO; (->0,1,00,1,0,00) OO; (->0,1,00,1,0,00) 154 | Output p1,p2 OP; OP; OP; OP; 155 | Read lower left [ 156 | Read upper right U 157 | Output offset ? 158 | OT; 159 | Output status V OS; OS; OS; OS; 160 | Output status 2 @ 161 | Output status 3 # 162 | 163 | Output window OW; OW; OW; 164 | Error mask “ 165 | Buffer size BS s1,s2,s3,s4 166 | ---------------------------- Pallette -------------------------------------------------------------------------------------- 167 | ??? (unsupported)CR 168 | ??? NP [2,4,8,16, 32] 169 | ??? (unsupported)PC 170 | ??? (unsupported)SV 171 | ??? (unsupported)TR 172 | ---------------------------- ???? -------------------------------------------------------------------------------------- 173 | Call RD-GL I command ^cmd [;] ^cmd [;] 174 | AH?? (ignored)AH; 175 | Automatic pen features AP n; 176 | Text??? BL c1c2cn; 177 | CM?? CM n1, n2; 178 | Digitize clear (ignored)DC; DC; 179 | Digitize point (ignored)DP; 180 | DP??? (ignored)DP; 181 | DS?? DS s,n; 182 | EP?? EP; EP; 183 | ??? FP; FP; 184 | ??? (ignored)FS f,n; 185 | ??? something with pen groups (ignored)GP g,h,ij 186 | ??? IV s,t 187 | (ignored)KY k,f; 188 | ??? something with buffer PB; 189 | (ignored)SG[ g]; 190 | motor control !MC 191 | Cutting depth, tool up position !PZ z1, c2 192 | Drill down velocity !VZ 193 | (unsupported)ESC %#A 194 | (unsupported)ESC E 195 | (unsupported)FI 196 | (unsupported)FN 197 | (unsupported)SB 198 | Control codes TODO ESC ... ESC ... 199 | -------------------------------------------------------------------------------- /turtletoy/glyph.js: -------------------------------------------------------------------------------- 1 | //https://turtletoy.net/turtle/85de2a6339 2 | // You can find the Turtle API reference here: https://turtletoy.net/syntax 3 | Canvas.setpenopacity(1); 4 | 5 | let scale = 2.19;// min=0.1 max=20 step=0.01 6 | let columns = 8;// min = 1 max=100 step=1 7 | let rows = 9;// min = 1 max=100 step=1 8 | let spacing = 18; // min = 1 max=200 step=1 9 | let lines = 1; // min=0, max=1, step=1, (Main, All) 10 | let distPow = 1.9; // min = 0.5 max=5 step=0.01 11 | let bend = 3.42;// min=0 max=10 step=0.01 12 | let bendLimit = 3.93;// min=0 max=10 step=0.01 13 | let lineDist = 2.5; // min=0 max=10 step=0.01 14 | 15 | // Global code will be evaluated once. 16 | const turtle = new Turtle(); 17 | const positions = [[0, 0], [1, 0], [2, 0], 18 | [0, 1], [1, 1], [2, 1], 19 | [0, 2], [1, 2], [2, 2], 20 | [1, 3] 21 | ]; 22 | 23 | // 0 1 2 24 | // 3 4 5 25 | // 6 7 8 26 | // 9 27 | const groups = [ 28 | [3, 0, 4, 6], 29 | [4, 1, 5, 7, 3], 30 | [5, 2, 4, 8], 31 | [7, 4, 8, 9, 6] 32 | ]; 33 | 34 | const NB = [ 35 | [[1, 3], [4]], 36 | [[2,4,0],[5,3]], 37 | [[5,1],[4]], 38 | [[0,4,6],[1, 7]], 39 | [[1,5,7,3],[2,8,0,6]], 40 | [[2,4,8],[1,7]], 41 | [[3,7],[4,9]], 42 | [[4,8,9,6],[3,5]], 43 | [[5,7],[4,9]], 44 | [[7],[6,8]], 45 | ]; 46 | 47 | function i_to_dpos(i, p0) 48 | { 49 | const p = positions[i]; 50 | return [(p[0]-1) * scale + p0[0], (p[1]-1) * scale + p0[1]]; 51 | } 52 | 53 | function edge_id(a, b) { 54 | return a < b ? 10 * a + b : 10 * b + a; 55 | } 56 | 57 | function draw_shape(shape) 58 | { 59 | const p = turtle.pos(); 60 | for (var i=0; i<10; i++) { 61 | if (shape & (1< 0 && ((shape & (1< 0) { 157 | s2 |= (1< 0) { 223 | let s1 = q.shift(); 224 | let neighbours = next_states(s1); 225 | states[s1] = neighbours; 226 | for (const state of neighbours){ 227 | if (!(state in states2)) { 228 | states2[state]=[]; 229 | } 230 | states2[state].push(s1); 231 | if (visited[state]) { 232 | continue; 233 | } 234 | visited[state] = true; 235 | q.push(state); 236 | } 237 | order.push(s1); 238 | } 239 | let fillers = -1; 240 | while (order.length < rows*columns) { 241 | order.push(fillers); 242 | states[fillers] = []; 243 | states2[fillers] = []; 244 | fillers -= 1; 245 | } 246 | /*for (let i = order.length - 1; i >= 0; i--) { 247 | let j = Math.floor(Math.random() * (i + 1)); 248 | if (j >= i) { 249 | j = 0; 250 | } 251 | let temp = order[i]; 252 | order[i] = order[j]; 253 | order[j] = temp; 254 | }*/ 255 | let dst =0; 256 | for (let i=0; i>>>>>>>> ${x}`); 263 | for (let i=0; i= d1) { 273 | let temp = order[i]; 274 | order[i] = order[j]; 275 | order[j] = temp; 276 | } else { 277 | // console.log(`tmpswpa ${d1}->${d2} ${i} ${j} ${order[i]} ${order[j]}`); 278 | } 279 | } 280 | } 281 | /*let dst2 =0; 282 | for (let i=0; i= order.length) { 292 | continue; 293 | } 294 | let p = [c * spacing - (columns-1)*spacing*0.5, 295 | r * spacing - (rows-1)*spacing*0.5]; 296 | turtle.jump(p); 297 | if (order [index] > 0){ 298 | draw_shape(order[index]); 299 | 300 | //console.log(`N: ${states[order[index]]}`); 301 | for (let j=0; j ${order[j]}`) 304 | 305 | if (index == j) { 306 | continue; 307 | } 308 | let c2 = j % columns; 309 | let r2 = Math.floor(j/ columns); 310 | //console.log(`${order[j]} ${c2} ${r2}`); 311 | 312 | let p1 = p; 313 | let p2 = [c2 * spacing - (columns-1)*spacing*0.5, 314 | r2 * spacing - (rows-1)*spacing*0.5]; 315 | 316 | let dx = p2[0] -p1[0]; 317 | let dy = p2[1] -p1[1]; 318 | if (Math.abs(r-r2) ==1 && Math.abs(c-c2) == 1) { 319 | if (p1[1] < p2[1]) { 320 | p1[1] += 2 * scale; 321 | } else { 322 | p2[1] += 2 * scale; 323 | } 324 | } 325 | 326 | turtle.jump(p); 327 | for (let t=0; t<1; t += (1.0/128.0)) { 328 | 329 | let px =slerp(p1[0], p2[0], t); 330 | let py =slerp(p1[1], p2[1], t); 331 | let shift = bend*scale * (1 - 4 * ((t-0.5)*(t-0.5))) * Math.min(bendLimit, (((Math.abs(dx)+Math.abs(dy)) / spacing)-1)); 332 | if (Math.abs(dx) < Math.abs(dy)) { 333 | px += shift; 334 | } else { 335 | py -= shift; 336 | } 337 | if (xydist(p, [px, py]) < scale*lineDist || 338 | xydist(p2, [px, py]) < scale*lineDist ) { 339 | turtle.penup(); 340 | } else { 341 | turtle.pendown(); 342 | } 343 | turtle.goto(px, py); 344 | } 345 | turtle.goto(p2); 346 | } 347 | } 348 | 349 | } 350 | index++; 351 | } 352 | } 353 | return false; 354 | } 355 | 356 | /*function walk(i) { 357 | if (q.length <= 0) { 358 | return false; 359 | } 360 | if (rc[1] >= columns) { 361 | rc[1] = 0; 362 | rc[0] += 1; 363 | } 364 | if (rc[0] >= rows) { 365 | return false; 366 | } 367 | let s1 = q.shift(); 368 | turtle.jump([rc[1] * spacing - (columns-1)*spacing*0.5, 369 | rc[0] * spacing - (rows-1)*spacing*0.5]); 370 | draw_shape(s1); 371 | let neighbours = next_states(s1); 372 | for (const state of neighbours){ 373 | if (state in visited) { 374 | continue; 375 | } 376 | visited[state] = true; 377 | q.push(state); 378 | } 379 | rc[1] += 1; 380 | //turtle.jump(0, 0); 381 | //draw_shape(0x77); 382 | return true; 383 | }*/ -------------------------------------------------------------------------------- /test_plot/shift_compact.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 38 | 45 | 62 | 67 | 72 | 77 | 82 | 87 | 92 | 108 | 109 | 111 | 115 | 120 | 123 | 129 | 135 | 141 | 147 | 154 | 158 | 162 | 163 | 166 | 170 | 174 | 178 | 182 | 186 | 187 | 191 | 195 | 199 | 203 | 207 | 208 | 213 | 217 | 221 | 225 | 231 | 232 | 237 | 241 | 245 | 249 | 255 | 256 | 261 | 265 | 269 | 273 | 279 | 280 | 285 | 289 | 293 | 297 | 303 | 304 | 305 | 309 | 313 | 314 | -------------------------------------------------------------------------------- /turtletoy/rain.js: -------------------------------------------------------------------------------- 1 | //https://turtletoy.net/turtle/e765a9b502 2 | Canvas.setpenopacity(1); 3 | 4 | // Global code will be evaluated once. 5 | const turtle = new Turtle(); 6 | 7 | 8 | let a1 = 0; // min = -720, max=720, step=1 9 | let a2 = 46; // min = -90, max=90, step=1 10 | let cd = 71.9; // min = 0, max=360, step=0.1 11 | let perspective = 1; // min=0, max=1, step=1, (off, on) 12 | let viewSize = 200; // min=10, max=400, step=1 13 | let fov = 90; // min=10, max=160, step=0.1 14 | 15 | let radius = 109; // min = 10, max=200, step=1 16 | let amount = 9800; // min=0, max=50000, step=1 17 | let rainLen = 2.2; // min=0, max=100, step=0.1 18 | let circleSize = 3.8; // min=0, max=30, step=0.1 19 | 20 | let boxSize = 27.6; // min=1, max=50, step=0.1 21 | 22 | function circle(p, r) { 23 | let loop = []; 24 | const STEPS =32; 25 | for (let i=0; i 0) { 38 | scene.fov = [fov, fov]; 39 | scene.w = scene.h = viewSize; 40 | scene.setPerspective(new V3(0, 0, 0), new V3(0, 0, 0)); 41 | } else { 42 | scene.setOrthographic(1, new V3(0,0,0), new V3(0, 0, 0)); 43 | } 44 | scene.camera_pos = Scene.worldCameraOrbit(new V3(0, 0, 0), cd, a1, a2); 45 | // 46 | 47 | let cube = new Ob([ 48 | //[0, 0, 0, 20, 0, 0], 49 | [0, 0, 0, 10, 0, 0], 50 | [11, 0, 0, 20, 0, 0], 51 | //[0, 0, 0, 0, 20, 0], 52 | [0, 0, 0, 0, 10, 0], 53 | [0, 11, 0, 0, 20, 0], 54 | [0, 0, 0, 0, 0, 20], 55 | ]); 56 | 57 | //scene.addOb(cube); 58 | /*scene.addLine(new V3(50, 5, 0), new V3(50, 0, 0)); 59 | scene.addLine(new V3(0, 0, 0), new V3(0, 50, 0)); 60 | scene.addLine(new V3(0, 0, 0), new V3(0, 0, 25));*/ 61 | let sdFunc = SDF.move(SDF.sphere(V(0, 0, 0), boxSize), V(0, 0, boxSize) );// SDF.box(V(20, 10, 10)); 62 | 63 | for (let i =0; i radius) { 66 | continue; 67 | } 68 | let posXY = p.changez(0); 69 | let p2 = p.add(new V3(0, 0, Util.rndRange(rainLen, rainLen * 1.05))); 70 | const [hit, travel] = SDF.runRay(sdFunc, posXY.changez(radius), V(0, 0, -1), radius); 71 | let h = hit.z; 72 | 73 | 74 | 75 | if (p.z < h && h > 0.01) { 76 | if (p.z < h && h-p.z < circleSize ) { 77 | let r= h-p.z; 78 | if (h > 0.01) { 79 | r = 0.5; 80 | } 81 | scene.addOb(circle(p.changez(h), r)); 82 | p.z = Math.max(h, p.z); 83 | p2.z = Math.max(h, p2.z); 84 | scene.addLine(p2, p); 85 | } else { 86 | let d = Math.random() * Math.PI * 2; 87 | let dv = V(Math.cos(d), Math.sin(d)); 88 | let l=0, r = radius; 89 | for (let i=0; i<20; i++) { 90 | let m = (l+r)/2; 91 | let pSide = posXY.changez(radius).add(dv.mul(m)); 92 | let [hit2, travel2] = SDF.runRay(sdFunc, pSide, V(0, 0, -1), radius); 93 | if (hit2.z <= 0.01) { 94 | r = m; 95 | } else { 96 | l = m; 97 | } 98 | } 99 | let pnew = posXY.add(dv.mul(l)); 100 | p = pnew.changez(Math.max(0, p.z)); 101 | p2 = pnew.changez(Math.max(0, p2.z)); 102 | scene.addLine(p2, p); 103 | } 104 | } else { 105 | if (p.z < h && h-p.z < circleSize ) { 106 | let r= h-p.z; 107 | if (h > 0.01) { 108 | r = 2; 109 | } 110 | scene.addOb(circle(p.changez(h), r)); 111 | } 112 | p.z = Math.max(h, p.z); 113 | p2.z = Math.max(h, p2.z); 114 | scene.addLine(p2, p); 115 | } 116 | 117 | 118 | 119 | 120 | 121 | } 122 | /*let o1 = new Ob(); 123 | scene.addLine(new V3(0, 0, 0), new V3(50, 0, 0)); 124 | scene.addLine(new V3(50, 5, 0), new V3(50, 0, 0)); 125 | scene.addLine(new V3(0, 0, 0), new V3(0, 50, 0)); 126 | scene.addLine(new V3(0, 0, 0), new V3(0, 0, 25)); 127 | 128 | 129 | //o1.transform(M4.translate(new V3(-10, -10, -10))); 130 | scene.addOb(o1);*/ 131 | scene.draw(); 132 | return false; 133 | } 134 | 135 | class V3 { 136 | constructor(x=0, y=0, z=0) { 137 | this.x = x; 138 | this.y = y; 139 | this.z = z; 140 | } 141 | static V0 = new V3(0, 0, 0); 142 | toString() { return `V3(${this.x}, ${this.y}, ${this.z})`; } 143 | add(b) { return new V3(this.x + b.x, this.y + b.y, this.z + b.z); } 144 | sub(b) { return new V3(this.x - b.x, this.y - b.y, this.z - b.z); } 145 | mul(b) { return new V3(this.x * b, this.y * b, this.z * b); } 146 | scale(b) { return new V3(this.x * b.x, this.y * b.y, this.z * b.z); } 147 | flipx() { return new V3(-this.x, this.y, this.z); } 148 | flipy() { return new V3(this.x, -this.y, this.z); } 149 | copy() { return new V3(this.x,this.y,this.z); } 150 | changex(v) { let res = this.copy(); res.x = v; return res;} 151 | changey(v) { let res = this.copy(); res.y = v; return res;} 152 | changez(v) { let res = this.copy(); res.z = v; return res; } 153 | len2() { return this.x*this.x + this.y*this.y + this.z*this.z; } 154 | static lerp(a, b, x) { return a.mul(1-x).add(b.mul(x)); } 155 | magnitude() { return Math.sqrt(this.len2()); } 156 | normalized() { return this.mul(1/this.magnitude()); } 157 | xyzs() { return this.x + this.y + this.z } 158 | xyzMax() { return Math.max(this.x, this.y, this.z); } 159 | xyzMin() { return Math.min(this.x, this.y, this.z); } 160 | 161 | abs() { return new V3(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z)); } 162 | max(b) { return new V3(Math.max(this.x, b.x), Math.max(this.y, b.y), Math.max(this.z, b.z)); } 163 | min(b) { return new V3(Math.min(this.x, b.x), Math.min(this.y, b.y), Math.min(this.z, b.z)); } 164 | maxK(k) { return new V3(Math.max(this.x, k), Math.max(this.y, k), Math.max(this.z, k)); } 165 | minK(k) { return new V3(Math.min(this.x, k), Math.min(this.y, k), Math.min(this.z, k)); } 166 | dot(b) { 167 | return this.scale(b).xyzs(); 168 | } 169 | rotDeg(deg) { 170 | const a = deg*Math.PI/180; 171 | const s = Math.sin(a); 172 | const c = Math.cos(a); 173 | return new V2(this.x*c-this.y*s, this.x*s+this.y*c); 174 | } 175 | } 176 | class M4 { 177 | constructor() { 178 | this.d = [[1,0,0,0], 179 | [0,1,0,0], 180 | [0,0,1,0], 181 | [0,0,0,1]]; 182 | } 183 | mul(b) { 184 | let res = new M4(); 185 | for (let i=0; i<4; i++) { 186 | for (let j=0; j<4; j++) { 187 | let s = 0; 188 | for (let k=0; k<4; k++) { 189 | s += this.d[i][k] * b.d[k][j] 190 | } 191 | res.d[i][j] = s; 192 | } 193 | } 194 | return res; 195 | } 196 | transpose() { 197 | let res = new M4(); 198 | for (let i=0; i<4; i++) { 199 | for (let j=0; j<4; j++) { 200 | res.d[i][j] = this.d[j][i]; 201 | } 202 | } 203 | return res; 204 | } 205 | mulv(v) { 206 | let vv = [v.x, v.y, v.z, 1]; 207 | let res = [0, 0, 0, 0]; 208 | for (let i=0; i<4; i++) { 209 | for (let k=0; k<4; k++) { 210 | res[i] += this.d[i][k] * vv[k]; 211 | } 212 | } 213 | return new V3(res[0], res[1], res[2]); 214 | } 215 | static translate(v) { 216 | let r = new M4(); 217 | r.d[0][3] = v.x; 218 | r.d[1][3] = v.y; 219 | r.d[2][3] = v.z; 220 | return r; 221 | } 222 | static euler(a,b,c) { 223 | let m1 = new M4(); 224 | let m2 = new M4(); 225 | let m3 = new M4(); 226 | m1.d = [[1, 0, 0, 0], 227 | [0, Math.cos(a), -Math.sin(a), 0], 228 | [0, Math.sin(a), Math.cos(a), 0], 229 | [0, 0, 0, 1]]; 230 | m2.d = [[Math.cos(b), 0, Math.sin(b), 0], 231 | [0, 1, 0, 0], 232 | [-Math.sin(b), 0, Math.cos(b), 0], 233 | [0, 0, 0, 1]]; 234 | m3.d = [[Math.cos(c), -Math.sin(c), 0, 0], 235 | [Math.sin(c), Math.cos(c), 0, 0], 236 | [0, 0, 1, 0], 237 | [0, 0, 0, 1]]; 238 | return m1.mul(m2).mul(m3); 239 | } 240 | static eulerv(v) { 241 | return euler(v.x, v.y, v.z); 242 | } 243 | } 244 | class Util { 245 | static rndRange(min, max) { 246 | return Math.random() * (max-min)+min; 247 | } 248 | static inverseLerp(a, b, x) { 249 | let d = b-a; 250 | if (d != 0) { 251 | return (x-a)/d; 252 | } else { 253 | return 0; 254 | } 255 | } 256 | static lerp(a, b, x) { 257 | return (1-x)*a + x*b; 258 | } 259 | static planeDistance(p0, pnorm, x) { 260 | pnorm = pnorm.normalized(); 261 | let k = p0.dot(pnorm); 262 | return x.dot(pnorm) - k; 263 | } 264 | static rectContains(r, p) { 265 | return p.x >= r[0] && p.x <= r[1] && p.y >= r[2] && p.y <= r[3]; 266 | } 267 | static planeClip(p0, pnorm, a, b) { 268 | let d1 = Util.planeDistance(p0, pnorm, a); 269 | let d2 = Util.planeDistance(p0, pnorm, b); 270 | if (d1 >= 0 && d2 >= 0) { 271 | return [a, b]; 272 | } 273 | if (d1 < 0 && d2 < 0) { 274 | return null; 275 | } 276 | let m = Util.inverseLerp(d1, d2, 0); 277 | let p = V3.lerp(a, b, m); 278 | if (d1 < 0) { 279 | return [p, b]; 280 | } else { 281 | return [a, p]; 282 | } 283 | } 284 | 285 | static clipRect(a,b,r) { 286 | let ca = rectContains(r, a); 287 | let cb = rectContains(r, b); 288 | if (ca && cb) { 289 | return [a, b]; 290 | } 291 | } 292 | } 293 | class Scene { 294 | constructor() { 295 | this.camera_pos = Scene.worldCameraM(new V3(0, 0, 0), new V3(1, 0, 0)); 296 | this.camera = Scene.orthographic1(); 297 | this.lines=[]; 298 | this.ortho = true; 299 | this.w=100; 300 | this.h=100; 301 | this.fov = [90, 90]; 302 | } 303 | addLine(a,b) { 304 | this.lines.push([a, b]); 305 | } 306 | addOb(x) { 307 | this.lines.push(...x.lines); 308 | } 309 | mapPoint(x) { 310 | let p = this.camera.mulv(x); 311 | if (p.z != 0) { 312 | p = p.mul(1/Math.abs(p.z)); 313 | } else { 314 | p.x = 0; p.y = 0; 315 | } 316 | return [p.x, p.y]; 317 | } 318 | draw() { 319 | this.lines.forEach((l) => { 320 | let debug=false; 321 | if (l[0].z > 0 && l[1].z > 0) { 322 | debug=true; 323 | } 324 | let p1 = this.camera_pos.mulv(l[0]); 325 | let p2 = this.camera_pos.mulv(l[1]); 326 | 327 | 328 | /*if (p1.z > 0 && p2.z > 0) { 329 | return; 330 | } else if (p1.z > 0 || p2.z > 0) { 331 | let x = Util.inverseLerp(p1.z, p2.z, 0.011); 332 | if (p1.z > 0) { 333 | p1 = V3.lerp(p1, p2, x); 334 | } else { 335 | p2 = V3.lerp(p1, p2, x); 336 | } 337 | }*/ 338 | if (!this.ortho) { 339 | let p0 = new V3(0, 0, 0); 340 | let norm = new V3(); 341 | let line = [p1, p2]; 342 | let a1 = Math.PI * 0.5*this.fov[0]/180; 343 | let a2 = Math.PI * 0.5*this.fov[1]/180; 344 | if (line) { 345 | norm = new V3(Math.cos(a1), 0, -Math.sin(a1)); 346 | line = Util.planeClip(p0, norm, line[0], line[1]); 347 | } 348 | if (line) { 349 | norm = new V3(-Math.cos(a1), 0, -Math.sin(a1)); 350 | line = Util.planeClip(p0, norm, line[0], line[1]); 351 | } 352 | if (line) { 353 | norm = new V3(0, -Math.cos(a2), -Math.sin(a2)); 354 | line = Util.planeClip(p0, norm, line[0], line[1]); 355 | } 356 | if (line) { 357 | norm = new V3(0, Math.cos(a2), -Math.sin(a2)); 358 | line = Util.planeClip(p0, norm, line[0], line[1]); 359 | } 360 | if (line) { 361 | p1 = line[0]; 362 | p2 = line[1]; 363 | } else { 364 | return; 365 | } 366 | } 367 | p1 = this.mapPoint(p1); 368 | p2 = this.mapPoint(p2); 369 | turtle.jump(p1) 370 | turtle.pendown(); 371 | turtle.goto(p2); 372 | }); 373 | } 374 | static worldCameraM(from, to) { 375 | let d = to.sub(from); 376 | let m1 = M4.translate(from.mul(-1)); 377 | let a1 = Math.atan2(d.y, d.x); 378 | let xy = d.changez(0); 379 | 380 | let a2 = Math.atan2(d.z, xy.magnitude()); 381 | //let m2 = (new M4()).mul(M4.euler(0, 0, +a1)).mul(M4.euler(-Math.PI/2 - a2, 0, 0)); 382 | let m2 = (new M4()).mul(M4.euler(-a2, -a1, 0)).mul(M4.euler(Math.PI/2, Math.PI, -Math.PI/2)); 383 | return m2.mul(m1); 384 | } 385 | static worldCameraOrbit(to, distance, z0, x0) { 386 | let x = x0* Math.PI / 180; 387 | let z = z0* Math.PI / 180; 388 | let p = M4.euler(0, 0, z).mul(M4.euler(0, x, 0)).mulv(new V3(-1, 0, 0)); 389 | let p2 = p.mul(distance).add(to); 390 | if (x0 > -90 && x0 < 90) { 391 | return Scene.worldCameraM(p2, p2.sub(p)); 392 | } else { 393 | let m1 = M4.translate(p2.mul(-1)); 394 | if (x0 > 0) { 395 | return M4.euler(0, 0, 1*Math.PI/2-z).mul(m1); 396 | } else { 397 | return M4.euler(0, Math.PI, -1*Math.PI/2-z).mul(m1); 398 | } 399 | 400 | } 401 | } 402 | 403 | static orthographic1(scale=1) { 404 | let res = new M4(); 405 | res.d = [ 406 | [scale,0,0,0], 407 | [0,-scale,0,0], 408 | [0, 0, 0,1], 409 | [0,0,0,1], 410 | ]; 411 | //res = res.mul(Scene.worldCameraM(from, to)); 412 | res = res; 413 | return res; 414 | } 415 | setOrthographic(scale, from, to) { 416 | this.camera = Scene.orthographic1(scale); 417 | this.camera_pos = Scene.worldCameraM(from, to); 418 | this.ortho = true; 419 | } 420 | static perspective(scale) { 421 | let res = new M4(); 422 | res.d = [ 423 | [scale,0,0,0], 424 | [0, -scale,0,0], 425 | [0, 0, -1,0], 426 | [0,0,0,1], 427 | ]; 428 | return res; 429 | } 430 | setPerspective(from, to) { 431 | 432 | this.camera = Scene.perspective(this.w*0.5 * Math.tan((90-this.fov[0]*0.5)/180*Math.PI)); 433 | this.camera_pos = Scene.worldCameraM(from, to); 434 | this.ortho = false; 435 | } 436 | } 437 | class Ob { 438 | constructor(linesa=[]){ 439 | this.lines=[]; 440 | if (linesa) { 441 | this.addLines(linesa); 442 | } 443 | } 444 | static fromChain(points) { 445 | let res = new Ob(); 446 | for (let i=1; i 1) { 454 | res.addLine(points[points.length-1], points[0]); 455 | } 456 | return res; 457 | } 458 | addLine(a,b) { 459 | this.lines.push([a, b]); 460 | } 461 | addLines(ar) { 462 | for (const x of ar) { 463 | this.addLine(new V3(x[0], x[1], x[2]), new V3(x[3], x[4], x[5])); 464 | } 465 | } 466 | transform(m) { 467 | this.lines.forEach((v, i) => { 468 | this.lines[i] = [m.mulv(v[0]), m.mulv(v[1])]; 469 | }); 470 | } 471 | } 472 | class SDF { 473 | static bind1(f, ...args) { 474 | return function(...x) { 475 | f.bind(null, ...x); 476 | } 477 | } 478 | // combinations 479 | static unionD(a, b, x) { return Math.min(a(x), b(x)); } 480 | static union(a, b) { return SDF.unionD.bind(null, a, b); } 481 | static diffD(a, b, x) { return Math.max(-a(x), b(x)); } 482 | static diff(a, b) { return SDF.diffD.bind(a, b); } 483 | static intersectionD(a, b, x) { return Math.max(a(x), b(x)); } 484 | static intersection(a, b) { return SDF.intersectionD.bind(a, b); } 485 | static xorD(a, b, x) { 486 | let d1=a(x), d2=b(x); 487 | return Math.max(min(d1, d2), -max(d1, d2)); 488 | } 489 | static xor(a, b) { return SDF.xorD.bind(a, b); } 490 | static moveD(f, p, x) { 491 | return f(x.sub(p)); 492 | } 493 | static move(f, p) { 494 | return SDF.moveD.bind(null, f, p); 495 | } 496 | static rotateD(f, euler, x) { 497 | return f(M4.euler(euler).mulV(x)); 498 | } 499 | static rotate(f, euler) { return SDF.rotateD.bind(f, euler); } 500 | 501 | // primitives 502 | static sphereD(p, r, x) { 503 | let dv = x.sub(p); 504 | return dv.magnitude() - r; 505 | } 506 | static sphere(p, r) { 507 | //SDF.bind1(SDF.sphereD); 508 | return SDF.sphereD.bind(null, p, r); 509 | } 510 | static boxD(s, x) { 511 | let q = x.abs().sub(s); 512 | return q.maxK(0).magnitude() + Math.min(q.xyzMax()); 513 | } 514 | static box = SDF.bind1(SDF.boxD); 515 | // 516 | static runRay(f, p0, dir, limit) { 517 | let travel = 0; 518 | let p = p0; 519 | while (travel < limit) { 520 | let distance = f(p); 521 | if (distance < 0.001) { 522 | break; 523 | } 524 | distance = Math.min(limit-travel, distance); 525 | p = p.add(dir.mul(distance)); 526 | travel += distance; 527 | } 528 | return [p, travel]; 529 | } 530 | } 531 | function initlib() { 532 | this.V3 = V3; 533 | this.V = (x,y,z)=>new V3(x, y, z); 534 | this.M4 = M4; 535 | this.Ob = Ob; 536 | this.Scene = Scene; 537 | this.SDF = SDF; 538 | } 539 | 540 | -------------------------------------------------------------------------------- /vsvg/dither_hatch/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | use image::{GenericImageView, ImageBuffer, ImageReader, Luma, Pixel, Rgb}; 3 | use kurbo::Vec2; 4 | use nalgebra::{vector, Vector2, Vector3}; 5 | use serde; 6 | use std::result::Result; 7 | use vsvg::UNITS; 8 | use whiskers::prelude::*; 9 | 10 | 11 | #[sketch_widget] 12 | #[derive(Default)] 13 | enum DitherMode { 14 | #[default] 15 | Ordered8Dot, 16 | HV1, 17 | BayerXJoin, 18 | XJoinOpt1, 19 | XJoinOpt2, 20 | XJoinOpt3, 21 | Random, 22 | } 23 | 24 | 25 | 26 | #[sketch_app] 27 | struct DitherSketch { 28 | #[param(slider, min = 0.01, max = 5.0)] 29 | line_size: f64, 30 | #[param(slider, min = 0.1, max = 2.0)] 31 | grid_mul: f64, 32 | path: String, 33 | have_image:bool, 34 | scale_mul: f64, 35 | w: u32, 36 | h: u32, 37 | mode: DitherMode, 38 | pattern: Vec, 39 | 40 | 41 | #[skip] 42 | #[serde(skip)] 43 | dstate: DynamicState, 44 | } 45 | 46 | #[derive(Default)] 47 | struct DynamicState { 48 | path_loaded: String, 49 | image: ImageBuffer, Vec>, 50 | } 51 | 52 | impl Default for DitherSketch { 53 | fn default() -> Self { 54 | Self { 55 | line_size: 1.0, 56 | grid_mul: 1.0, 57 | path: String::default(), 58 | have_image: false, 59 | scale_mul: 1.0, 60 | w: 0, 61 | h: 0, 62 | mode: DitherMode::default(), 63 | pattern: Vec::default(), 64 | dstate: DynamicState::default(), 65 | } 66 | } 67 | } 68 | 69 | impl DitherSketch { 70 | fn load_image(&mut self) -> Result<(), ()> { 71 | let mut img = ImageReader::open(&self.path) 72 | .map_err(|e| ())? 73 | .decode() 74 | .map_err(|e| ())? 75 | .to_rgb8(); 76 | 77 | let w2 = (img.width()as f64 * self.scale_mul) as u32; 78 | let h2 = (img.height()as f64 * self.scale_mul) as u32; 79 | self.w = w2; 80 | self.h = h2; 81 | 82 | let img = image::imageops::resize( 83 | &img, 84 | w2, 85 | h2, 86 | image::imageops::FilterType::CatmullRom, 87 | ); 88 | 89 | self.dstate.image = img; 90 | self.dstate.path_loaded = self.path.clone(); 91 | return Ok(()); 92 | } 93 | } 94 | 95 | fn pixel_magnitude2(p: Rgb) -> i32 { 96 | let c = p.channels(); 97 | return c[0] as i32 * c[0] as i32 + c[1] as i32 * c[1] as i32 + c[2] as i32 * c[2] as i32; 98 | } 99 | 100 | type IPos2 = Vector2; 101 | 102 | fn pixel_to_vec32f(p: Rgb) -> Vector3 { 103 | return Vector3::new(p.0[0] as f32, p.0[1] as f32, p.0[2] as f32); 104 | } 105 | 106 | 107 | struct HeapPos { 108 | cost: f32, 109 | pos: IPos2, 110 | prev: IPos2, 111 | } 112 | 113 | 114 | impl PartialEq for HeapPos { 115 | fn eq(&self, other: &Self) -> bool { 116 | self.cost == other.cost 117 | } 118 | } 119 | impl PartialOrd for HeapPos { 120 | fn partial_cmp(&self, other: &Self) -> Option { 121 | return Some(self.cmp(other)); 122 | } 123 | } 124 | impl Ord for HeapPos { 125 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 126 | return other.cost.total_cmp(&self.cost); 127 | } 128 | } 129 | impl Eq for HeapPos { 130 | 131 | } 132 | 133 | pub fn grid_cell(grid: f64, pos: IPos2) -> Vec2 { 134 | return Vec2::new(pos.x as f64 * grid, pos.y as f64 * grid); 135 | } 136 | 137 | fn dither_a(state: &mut DitherSketch, sketch: &mut Sketch) 138 | { 139 | let image = &state.dstate.image; 140 | let gs = state.line_size * state.grid_mul; 141 | 142 | let grid = Grid::from_cell_size([gs, gs]) 143 | .columns(state.w as usize) 144 | .rows(state.h as usize); 145 | 146 | 147 | 148 | 149 | let mina = [ 150 | [0, 8, 2,10], 151 | [12, 4,14, 6], 152 | [ 3,11, 1, 9], 153 | 154 | [15, 7, 13, 5] 155 | ]; 156 | 157 | 158 | 159 | /*grid.build(sketch, |sketch, cell| { 160 | let p1 = cell.position; 161 | let p2 = p1 + Point::new(cell.size[0], 0); 162 | 163 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 164 | if pixel_magnitude2(*c1).isqrt() > (443/2) { 165 | sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 166 | } 167 | });*/ 168 | 169 | grid.build(sketch, |sketch, cell| { 170 | let p1 = cell.position; 171 | let p2 = p1 + Point::new(cell.size[0]*0.1, 0); 172 | 173 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 174 | let mg = pixel_magnitude2(*c1).isqrt(); 175 | 176 | let a = mina[cell.row % mina.len()][cell.column % mina[0].len()]; 177 | 178 | 179 | //if mg < ((443) * (a+1)) / 17 180 | if 443-mg > ((443) * (a+1)) / 17 181 | { 182 | sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 183 | } 184 | }); 185 | } 186 | 187 | 188 | fn dither_b(state: &mut DitherSketch, sketch: &mut Sketch) 189 | { 190 | let image = &state.dstate.image; 191 | let gs = state.line_size * state.grid_mul; 192 | 193 | let grid = Grid::from_cell_size([gs, gs]) 194 | .columns(state.w as usize) 195 | .rows(state.h as usize); 196 | 197 | 198 | let dir = [ 199 | [ 2, 2, 2, 1, 2, 2, 2, 1], 200 | [ 2, 1, 2, 1, 2, 1, 2, 1], 201 | [ 2, 2, 2, 1, 2, 2, 2, 1], 202 | [ 2, 1, 2, 1, 2, 1, 2, 1], 203 | 204 | [ 2, 2, 2, 1, 2, 2, 2, 1], 205 | [ 2, 1, 2, 1, 2, 1, 2, 1], 206 | [ 2, 2, 2, 1, 2, 2, 2, 1], 207 | [ 2, 1, 2, 1, 2, 1, 2, 1], 208 | ]; 209 | 210 | /*let mina = [ 211 | [ 6, 5, 4, 0, 4, 5, 6, 0], 212 | [13,10,12, 1, 12,11,13, 1], 213 | [ 7, 8, 9, 2, 9, 8, 7, 2], 214 | [14,11,15, 3, 15,10,14, 3], 215 | 216 | [ 4, 5, 6, 0, 6, 5, 4, 0], 217 | [12,11,13, 1, 13,10,12, 1], 218 | [ 9, 8, 7, 2, 7, 8, 9, 2], 219 | [15,10,14, 3, 14,11,15, 3], 220 | ];*/ 221 | 222 | let mina = [ 223 | [ 6, 5, 4, 2, 4, 5, 6, 0], 224 | [13,10,12, 1, 12,11,13, 1], 225 | [ 7, 8, 9, 0, 9, 8, 7, 2], 226 | [14,11,15, 3, 15,10,14, 3], 227 | 228 | [ 4, 5, 6, 2, 6, 5, 4, 0], 229 | [12,11,13, 1, 13,10,12, 1], 230 | [ 9, 8, 7, 0, 7, 8, 9, 2], 231 | [15,10,14, 3, 14,11,15, 3], 232 | ]; 233 | 234 | 235 | let flip = [ 236 | [99,99,99,99, 99,99,99,99], 237 | [99,99,99,99, 99,99,99,99], 238 | [99,99,99,99, 99,99,99,99], 239 | [99,99,99,99, 99,99,99,99], 240 | 241 | [99,99,99,99, 99,99,99,99], 242 | [99,99,99,99, 99,99,99,99], 243 | [99,99,99,99, 99,99,99,99], 244 | [99,99,99,99, 99,99,99,99], 245 | ]; 246 | 247 | 248 | 249 | /*grid.build(sketch, |sketch, cell| { 250 | let p1 = cell.position; 251 | let p2 = p1 + Point::new(cell.size[0], 0); 252 | 253 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 254 | if pixel_magnitude2(*c1).isqrt() > (443/2) { 255 | sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 256 | } 257 | });*/ 258 | 259 | let mut flags = ImageBuffer::from_fn(image.width(), image.height(), |x, y| { 260 | image::Luma([0u8]) 261 | }); 262 | 263 | grid.build(sketch, |sketch, cell| { 264 | let p1 = cell.position; 265 | let p2 = p1 + Point::new(cell.size[0]*0.1, 0); 266 | 267 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 268 | let mg = pixel_magnitude2(*c1).isqrt(); 269 | 270 | let W = mina.len(); 271 | let a: i32 = mina[cell.row % W][cell.column % W]; 272 | let d: i32 = dir[cell.row % W][cell.column % W]; 273 | 274 | 275 | 276 | if 443-mg > ((443) * (a+1)) / 17 { 277 | *flags.get_pixel_mut(cell.column as u32, cell.row as u32) = Luma([d as u8; 1]); 278 | //sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 279 | } 280 | }); 281 | for y in 0..(state.h as u32) { 282 | let mut x = 0u32; 283 | while x < state.w { 284 | if flags.get_pixel(x, y )[0] != 2 { 285 | x += 1; 286 | continue; 287 | } 288 | let first = x; 289 | let mut last_need = x; 290 | while x < (state.w as u32) && flags.get_pixel(x, y)[0] > 0 { 291 | if flags.get_pixel(x, y)[0] == 2 { 292 | last_need = x; 293 | } 294 | x += 1; 295 | } 296 | let v1 = first as f64; 297 | let mut v2 = last_need as f64; 298 | if first == last_need { 299 | v2 += 0.01; 300 | } 301 | sketch.line(v1 * gs, y as f64 * gs, v2 * gs, y as f64 * gs); 302 | } 303 | } 304 | for x in 0..(state.w as u32) { 305 | let mut y = 0u32; 306 | while y < state.h { 307 | if flags.get_pixel(x, y )[0] != 1 { 308 | y += 1; 309 | continue; 310 | } 311 | let first = y; 312 | let mut last_need = first; 313 | while y < (state.h as u32) && flags.get_pixel(x, y)[0] > 0 { 314 | if flags.get_pixel(x, y)[0] == 1 { 315 | last_need = y; 316 | } 317 | y += 1; 318 | } 319 | let v1 = first as f64; 320 | let mut v2 = last_need as f64; 321 | if first == last_need { 322 | v2 += 0.01; 323 | } 324 | sketch.line(x as f64 * gs, v1 * gs, x as f64 * gs, v2 * gs); 325 | } 326 | } 327 | } 328 | 329 | 330 | fn join_xpattern(state: &mut DitherSketch, sketch: &mut Sketch, flags: &mut ImageBuffer, Vec>) 331 | { 332 | let gs = state.line_size * state.grid_mul; 333 | for y in 0..(state.h) { 334 | for x in 0..state.w { 335 | if flags.get_pixel(x, y)[0] == 0 { 336 | continue; 337 | } 338 | let mut f2:u8 = 1; 339 | if let Some(v) = flags.get_pixel_checked(x.wrapping_sub(1), y.wrapping_sub(1)) { 340 | if v[0] > 0 { 341 | f2 |= 2; 342 | } 343 | } 344 | if let Some(v) = flags.get_pixel_checked(x+1, y+1) { 345 | if v[0] > 0 { 346 | f2 |= 2; 347 | } 348 | } 349 | if let Some(v) = flags.get_pixel_checked(x.wrapping_sub(1), y+1) { 350 | if v[0] > 0 { 351 | f2 |= 4; 352 | } 353 | } 354 | if let Some(v) = flags.get_pixel_checked(x+1, y.wrapping_sub(1)) { 355 | if v[0] > 0 { 356 | f2 |= 4; 357 | } 358 | } 359 | *flags.get_pixel_mut(x, y) = Luma([f2; 1]); 360 | } 361 | } 362 | for x0 in (-(state.h as i32))+1..(state.w as i32) { 363 | let (y0, x0) = if x0 < 0 { 364 | ((-x0) as u32, 0) 365 | } else { 366 | (0, (x0 as u32)) 367 | }; 368 | let mut x = x0; 369 | let mut y=y0; 370 | while x < state.w && y < state.h { 371 | let f0 = flags.get_pixel(x, y)[0]; 372 | if f0 == 0 { 373 | x += 1; 374 | y += 1; 375 | } else { 376 | let p0 = (x, y); 377 | while x < state.w && y < state.h && flags.get_pixel(x, y)[0] > 0 { 378 | x += 1; 379 | y += 1; 380 | } 381 | if (x - p0.0) > 1 || (f0 & 4 == 0) { 382 | sketch.line(gs * p0.0 as f64, gs * p0.1 as f64, gs * (x as f64 - 0.99), gs * (y as f64 - 0.99)); 383 | } 384 | } 385 | } 386 | } 387 | for x0 in 0..state.w + state.h - 1 { 388 | let (y0, x0) = if x0 >= state.w { 389 | (x0 - state.w + 1, state.w - 1) 390 | } else { 391 | (0, (x0 as u32)) 392 | }; 393 | let x0 = x0 as u32; 394 | let mut x = x0 as i32; 395 | let mut y= y0; 396 | while x >= 0 && y < state.h { 397 | let f0 = flags.get_pixel(x as u32, y)[0]; 398 | if f0 == 0 { 399 | x -= 1; 400 | y += 1; 401 | } else { 402 | let p0 = (x, y); 403 | while x >= 0 && y < state.h && flags.get_pixel(x as u32, y)[0] > 0 { 404 | x -= 1; 405 | y += 1; 406 | } 407 | if y - p0.1 > 1 { 408 | sketch.line(gs * p0.0 as f64, gs * p0.1 as f64, gs * (x as f64 + 0.99), gs * (y as f64 - 0.99)); 409 | } 410 | } 411 | } 412 | } 413 | } 414 | 415 | 416 | fn dither_ax(state: &mut DitherSketch, sketch: &mut Sketch) 417 | { 418 | let image = &state.dstate.image; 419 | let gs = state.line_size * state.grid_mul; 420 | 421 | let grid = Grid::from_cell_size([gs, gs]) 422 | .columns(state.w as usize) 423 | .rows(state.h as usize); 424 | 425 | 426 | let mina = [ 427 | [0, 8, 2,10], 428 | [12, 4,14, 6], 429 | [ 3,11, 1, 9], 430 | [15, 7, 13, 5] 431 | ]; 432 | 433 | 434 | let mut flags = ImageBuffer::from_fn(image.width(), image.height(), |_x, _y| { 435 | image::Luma([0u8]) 436 | }); 437 | 438 | 439 | 440 | grid.build(sketch, |sketch, cell| { 441 | let p1 = cell.position; 442 | let p2 = p1 + Point::new(cell.size[0]*0.1, 0); 443 | 444 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 445 | let mg = pixel_magnitude2(*c1).isqrt(); 446 | 447 | let W = mina.len(); 448 | let a: i32 = mina[cell.row % W][cell.column % W]; 449 | 450 | 451 | if 443-mg > ((443) * (a+1)) / 17 { 452 | *flags.get_pixel_mut(cell.column as u32, cell.row as u32) = Luma([1 as u8; 1]); 453 | //sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 454 | } 455 | }); 456 | join_xpattern(state, sketch, &mut flags); 457 | } 458 | 459 | fn dither_randx(state: &mut DitherSketch, sketch: &mut Sketch, ctx: &mut Context) 460 | { 461 | let image = &state.dstate.image; 462 | let gs = state.line_size * state.grid_mul; 463 | 464 | let grid = Grid::from_cell_size([gs, gs]) 465 | .columns(state.w as usize) 466 | .rows(state.h as usize); 467 | 468 | 469 | let mina = [ 470 | [0, 8, 2,10], 471 | [12, 4,14, 6], 472 | [ 3,11, 1, 9], 473 | [15, 7, 13, 5] 474 | ]; 475 | 476 | 477 | let mut flags = ImageBuffer::from_fn(image.width(), image.height(), |_x, _y| { 478 | image::Luma([0u8]) 479 | }); 480 | 481 | 482 | 483 | grid.build(sketch, |sketch, cell| { 484 | let p1 = cell.position; 485 | let p2 = p1 + Point::new(cell.size[0]*0.1, 0); 486 | 487 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 488 | let mg = pixel_magnitude2(*c1).isqrt(); 489 | 490 | let W = mina.len(); 491 | let a: i32 = mina[cell.row % W][cell.column % W]; 492 | 493 | 494 | if 443-mg > ctx.rng_range(0..443) { 495 | *flags.get_pixel_mut(cell.column as u32, cell.row as u32) = Luma([1 as u8; 1]); 496 | //sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 497 | } 498 | }); 499 | join_xpattern(state, sketch, &mut flags); 500 | } 501 | 502 | 503 | fn dither_xpat(state: &mut DitherSketch, sketch: &mut Sketch, mina: &[[i32; 4]; 4]) 504 | { 505 | let image = &state.dstate.image; 506 | let gs = state.line_size * state.grid_mul; 507 | 508 | let grid = Grid::from_cell_size([gs, gs]) 509 | .columns(state.w as usize) 510 | .rows(state.h as usize); 511 | 512 | 513 | let mut flags = ImageBuffer::from_fn(image.width(), image.height(), |_x, _y| { 514 | image::Luma([0u8]) 515 | }); 516 | 517 | 518 | grid.build(sketch, |sketch, cell| { 519 | let p1 = cell.position; 520 | let p2 = p1 + Point::new(cell.size[0]*0.1, 0); 521 | 522 | let c1 = image.get_pixel(cell.column as u32, cell.row as u32); 523 | let mg = pixel_magnitude2(*c1).isqrt(); 524 | 525 | let W = mina.len(); 526 | let a: i32 = mina[cell.row % W][cell.column % W]; 527 | 528 | 529 | if 443-mg > ((443) * (a+1)) / 17 { 530 | *flags.get_pixel_mut(cell.column as u32, cell.row as u32) = Luma([1 as u8; 1]); 531 | //sketch.line(p1.x(), p1.y(), p2.x(), p2.y()); 532 | } 533 | }); 534 | join_xpattern(state, sketch, &mut flags); 535 | } 536 | 537 | 538 | fn dither_xopt1(state: &mut DitherSketch, sketch: &mut Sketch) 539 | { 540 | let mina = [ 541 | [12, 7, 8, 0], 542 | [ 5,13, 1,10], 543 | [11, 2,14, 6], 544 | [ 3, 9, 4,14] 545 | ]; 546 | return dither_xpat(state, sketch, &mina); 547 | } 548 | 549 | fn dither_xopt2(state: &mut DitherSketch, sketch: &mut Sketch) 550 | { 551 | let mina = [ 552 | [15, 5,11, 0], 553 | [ 6,12, 1,10], 554 | [13, 2, 9, 4], 555 | [ 7 ,8, 3,14] 556 | ]; 557 | return dither_xpat(state, sketch, &mina); 558 | } 559 | 560 | 561 | impl App for DitherSketch { 562 | fn update(&mut self, sketch: &mut Sketch, _ctx: &mut Context) -> anyhow::Result<()> { 563 | if self.path != self.dstate.path_loaded { 564 | match self.load_image() { 565 | Err(_) => { 566 | std::eprintln!("Failed to load image"); 567 | self.have_image = false; 568 | } 569 | Ok(_) => { 570 | self.have_image = true; 571 | } 572 | } 573 | self.dstate.path_loaded = self.path.clone(); 574 | } 575 | if self.dstate.image.is_empty() { 576 | eprintln!("No image"); 577 | return Ok(()); 578 | } 579 | sketch.scale(Unit::Mm); 580 | 581 | sketch.stroke_width( vsvg::Unit::Mm.convert_to(&vsvg::Unit::Px, self.line_size) ); 582 | 583 | 584 | match self.mode { 585 | DitherMode::Ordered8Dot => { 586 | dither_a(self, sketch); 587 | } 588 | DitherMode::HV1 => { 589 | dither_b(self, sketch); 590 | } 591 | DitherMode::BayerXJoin => { 592 | dither_ax(self, sketch); 593 | } 594 | DitherMode::XJoinOpt1 => { 595 | dither_xopt1(self, sketch); 596 | } 597 | DitherMode::XJoinOpt2 => { 598 | dither_xopt2(self, sketch); 599 | } 600 | DitherMode::XJoinOpt3 => { 601 | let mut pat = [[0; 4]; 4]; 602 | for (i, v) in self.pattern.iter().enumerate() { 603 | if i < 16 { 604 | pat[i/4][i % 4] = *v; 605 | } 606 | } 607 | dither_xpat(self, sketch, &pat); 608 | } 609 | DitherMode::Random => { 610 | dither_randx(self, sketch, _ctx); 611 | } 612 | } 613 | Ok(()) 614 | } 615 | } 616 | 617 | fn main() -> whiskers::prelude::Result { 618 | DitherSketch::runner() 619 | .with_page_size_options(PageSize::A5H) 620 | .with_layout_options(LayoutOptions::Center) 621 | .run() 622 | } 623 | -------------------------------------------------------------------------------- /notes/plotter_hardware_designs.md: -------------------------------------------------------------------------------- 1 | # Plotter product categories 2 | 3 | | Name | Price | Ease of use | Versatility | Performance | 4 | | :------------------ | :----- | ----------- | ----------- | --- | 5 | | Vintage pen plotter | $$ - $$$ | + | 0 | ++++ | 6 | | DIY | $ - $$$ | + | ++++ | +/+++ | 7 | | Modern pen plotters | $$$ | ++ | ++ | ++ | 8 | | Cheap kits | $ - $$ | + | ++ | ++ | 9 | | Toy plotters | $ | +++ | - | - | 10 | | Hobby vinyl cutters/plotters | $$ | +++ | ++ | ++ | 11 | | Vinyl cutters | $$ - $$$$ | +++ | + | ++ | 12 | | Pen attachment for your existing CNC device or 3d printer | $ | + | ++ | ++ | 13 | 14 | 15 | ## Vintage pen plotters 16 | There was a period of time when pen plotters served a similar role as office printers and for a slightly longer period for plotting large format engineering and architectural drawings. 17 | Somewhat wide commercial use meant that the machines were very well engineered and optimized for one specific task which resulted great performance even by modern standards. Lightweight construction allows fast movement and acceleration. But more impressively solenoid based pen lifting mechanism in some machines is capable of lifting the pen 20-30 times a second. 18 | 19 | 20 | One thing that might slightly held them back is limited computing power and data transfer rate. At their time this was partially compensated by having builtin commands for things like dotted lines, polygon fill and circles. Currently, unless you are making your own software for your specific drawing and plotter, you are more likely to generate arbitrary vector drawing and then convert it to the language plotter understand by splitting all lines into into short segments. 21 | 22 | Two major subcategories are flatbed plotters and roller based ones. For the flatbed ones paper stands still and the pen moves along x and y axes. This means that plotter needs to be bigger than the paper sheet you are drawing on and it takes a lot of space on the table. Some of them where designed to allow standing at angle, not quite vertically. 23 | Roller based ones use rollers to move paper back and forth along Y axis, with X axis being stationary, similar to how modern printers work. This approach was used both for desktop sized plotters, also large ones supporting up to A1 or A2 size paper. 24 | 25 | Depending on how well it's preserved you can find deals for $200-$600, broken ones sold as parts only in $50-$150 range. Since it's not that easy to run them with modern computer not all sellers can easily test if the plotter is in working state, so you can occasionally be lucky and find a working one sold as "parts only". 26 | 27 | As with any vintage device availability is of spare parts and consumables is limited. It will likely need some tinkering to keep it running. Some plastic parts might be broken, rubber deteriorated. 28 | If you are lucky someone has already designed and shared models for 3d printable replacements. 29 | These types of pen plotters are usually meant to be used with specially shaped stubby pens. You can find some old stock in the various marketplaces, but if you are going to do a lot of plots that can become costly. So you will also likely make an adapter for pens. 30 | One more problem with using modern pens is that the original pens were very short and rest of machine designed with that in mind. This is mostly a problem for roller based plotter with pen changers or a plastic cover over the moving parts. Flatbed plotters are more likely to be compatible with longer pens without any modifications except the mounting adapter. 31 | 32 | One bright side in terms of repairability is that at the time these plotters where made, manufacturers providing a service manual with full schematics and detailed troubleshooting steps was still common. 33 | 34 | The optimized design comes at the cost of versatility. Loading an arbitrary pen or paper can be problematic. Again flatbed plotters are slightly better as you can more or less place any paper or flat material smaller than maximum size, but the roller based ones will likely expect very specific paper sizes and fail to feed anything which isn't normal thickness office paper. Rollers can also be problematic if they go over any wet ink smudging it. You would at least hope that roller based plotters have infinite Y axis, that isn't always the case. Plenty of roller based plotters only support specific standard paper sheet sizes, and even if paper rolls are supported accuracy over for plots bigger than 1-2 meters isn't guaranteed. 35 | Well optimized design means limited Z axis movement range and pen pressure, so using anything which isn't a normal pen will likely not work. 36 | 37 | Other major drawback is related to ease of use of use, connection and software setup. It's not exactly a plug and play process. You will likely need a some kind special adapter for connecting to modern PC. And not every adapter will properly function with every device. After electrical connection, next challenge is connection parameters. Serial port has half dozen parameters which need to be configured exactly the same on the plotter and the software running on your computer. Consult the documentation of your plotter to find out correct values, don't attempt to guess randomly there are way too many different combinations. Often these can be changed on the plotter using dip switches on the back of plotter. 38 | Third step is the actual printing. Instead of trying to get drivers and configured like regular printer (with exception of drivers for adapter if needed) treat it like generic serial port like device. There is a bunch of software meant for either directly interfacing with such plotters, or converting svg to the language plotter understands and then you send the file using some other software. You can also write your own script which directly generates the drawing in language understood by your plotter. The languages used by plotters were very simple but somewhat high level at the same time compared to modern formats so it's not too difficult if you have some programming knowledge. Usually it's either HPGL or GPGL or something based on those two, but there can also be a bunch subtle differences between each machine. So check the manual especially if you are planning to use anything more advanced the move to position, draw straight line. 39 | 40 | While the speed for straight lines and pen lifting/lowering is quite good, CPUs in these plotters are somewhat slow. Drawing circles or other curves can be much slower compared to straight lines. Stepper driver technology also has advanced over the years. Modern stepper drivers like those made by Trinamics can be much quiter compared to vintage pen plotters. 41 | 42 | 43 | ## Modern pen plotters 44 | For most practical use cases pen plotters have been replaced by other printing technologies. Modern "Plotters" that you might find in architecture firms are just large size inkjet printers. This means that new commercial plotters is a very small niche, with few manufacturers. 45 | 46 | The small market means there is not enough money to make serious engineering effort optimizing the build for performance or price. Currently the 2 main sellers are Bantam tools (previously Evil mad scientist with Axidraw machines) and other is UUNA TEK with iDraw machines. Costing somewhere between $500-$1500 for A4-A2 sizes and up to $5000 for largest machines. 47 | 48 | If you compare the pen plotters to similar price medium/high end hobby 3d printer or low end CNC machines. Pen plotter by the requirements is already much simpler machine. But even with that advantage the modern pen plotters are much closer to DIY machines than they are to machines of other types or plotters from 40 years ago. This isn't meant to be an insult for the creators of those machines, more of an observation about reality of market and what happens when economies of scale and competition do not apply. 49 | 50 | For some people slightly smoother overall experience might be worth it. 51 | 52 | ## Toy plotters 53 | 54 | Different type of new commercially available pen plotters is sold as kids toy. Consisting of 3 hobby servos and using 5-bar linkage to move the pen. It usually costs ~$50-$100. The drawing area is very limited, maybe 15x15cm, and the accuracy isn't great either. Sometimes there are cards or something similar for drawing one of the builtin image (it's meant to be a kids toy they aren't supposed to need a computer for using it), there might be a mobile app which allows making custom drawings. Some examples: Line-us, WeDraw (canceled kicstarter), "Quincy The Robot Artist". Some of them have speaker for giving children instructions how to draw the same thing. Some are sold as barebone kit more as a STEM toy. 55 | 56 | 57 | ## Cheap kits or preassembled machines from Ebay/Aliexpress 58 | If you don't want to design a DIY machine or the previously mentioned prices are way above your budget you can find a bunch of relatively cheap machines from ebay/Aliexpress or other similar marketplaces. 59 | Costing ~$100-$150 for small sizes up to $400 for bigger ones. That's closer what you would expect considering the prices of 3d printers using the same parts. 60 | 61 | If you want to save money and are ready for little bit of DIY instead of buying bigger ones it will be cheaper to get the smallest one and buy longer aluminum extrusions, belt and wire separately. Extra parts should cost <$50, nowhere near the hundreds of dollars that sellers are asking for size difference. This is a bit trickier if the machine has linear rails instead of V-wheels. Similar pricing logic applies to the different sizes of machines in "Modern pen plotters" category but you can't as easily extend them, because they might be using less common type of aluminum extrusions which will be much harder to source on your own. One more thing to keep in mind is that you can't extend it infinitely this way, at certain size it will become unreliable and require different hardware design or at least beefier components motors, rails, motor driver. 62 | 63 | The ones costing $100-$150 aren't a bad deal, if you tried to source exactly the same parts yourself it's not hard to end up with slightly higher cost especially considering shipping cost when ordering from multiple vendors. 64 | 65 | Cheap price comes with it's downsides (which might not be a problem depending on your skill set). 66 | 67 | There is a bit of lottery factor with these sellers. Not so much that you won't receive anything at all, but you might not receive exactly the same thing as in pictures. 68 | 69 | Related problem with cheap kits consisting almost purely from off the shelf components is that sellers might be substituting parts without ever testing whether all the parts fit together. Stuff like swapping a buttonhead screw (flatter and wider) with one that has cylindrical socket head (taller and narrower), replacing countersunk with non countersunk, changing the belts to slightly wider or narrower ones, replacing motors with ones that have wires attached on different sides, replacing 16teeth pulleys with 20teeth, different type of mounting brackets and so on. 70 | Small changes like that might cause parts to interfere during assembly. Or seller might forget to substitute all the related parts. In a well designed machine unless it something very compact, changes like that are usually fine. You wouldn't normally design everything with tightest fit possible places as that not only makes part substitution but also accounts for variance within same part and also makes assembly process easier. Good sellers would test such things, but you can't expect the same from sellers who can't even be bothered to make their own pictures or write description for store listing. 71 | 72 | Don't expect high quality, accurate instructions and be ready to figure out things yourself. 73 | 74 | One more potential upgrade for these is replacing the controller board. Many of these plotters use same controller board as axidraw to allow using with axidraw software. It's a <10years old board with underpowered microcontroller and custom protocol supported supported almost exclusively by axidraw sofware. See [Diy plotters](./diy-plotters.md) for alternative choices. You can now get much more powerful controller boards, with better feature set and supported by wider set of software for 1/3 the price of EBB board. 75 | 76 | Related category of machines is cheap non enclosed laser engravers, sometimes also sold as laser engravers/plotters. The movement part of these are very similar to previous, but maybe slightly higher quality with more custom parts. Makes sense since the laser engraves have significantly bigger market for making customized gifts and merchandise and wooden trinkets, and it's not something which can be easily substituted by other technique. 77 | My recommendation is stay away from these or at least immediately throw the laser module part into a bin. They are recipe for permanent eye damage to you and everyone around you. And you need proper ventilation. Laser cutters are not something to be used in your living room with children or pets running around. 78 | From the perspective of modifying one for pen plotting, a lot these have X axis high above desk surface. This is both due to height of laser module and also to support engraving various physical objects not just sheet material. It leads to significant stickout for the z axis and tool, which is fine for a laser engraver that has no side loads, but not optimal for pen plotting. You want the pen to be held as low as possible to minimize deflection. 79 | 80 | * DBU21 81 | * ly drawbot 82 | 83 | ## DIY builds 84 | For more information read [DIY plotters](./diy_plotters.md) 85 | 86 | DIY builds is the category which can vary the most. You can make something very cheap, or you can make something with decent performance which compares to modern pen plotters at half the price. 87 | 88 | The existing ecosystems of DIY CNC machines and more lately 3d printers means that there is wide availability of mechanical parts, electronic parts, controller boards and software. 89 | 90 | Some of the cheapest builds will likely be wallbot/polargraph style or hobby servo based scara type. 91 | 92 | 93 | 94 | ## Tabletop hobby plotters/vinyl cutters 95 | Compared to alternatives small format vinyl cutters meant for crafts and arts can be cheap option. These type of machines are meant not just for vinyl-cutting but also various crafts and arts projects like various paper crafts, making fancy greeting cards, scrapbooking, lightweight cloth cutting for quilting. 96 | 97 | Price is in $200-$400 range. 98 | 99 | Most common brands are Silhouette with their Cameo series, Brother with ScanNCut and Cricut. 100 | 101 | Software is one of the strongest and weakest parts of these machines. One hand it they provide high quality easy to use software, which comes with presets for the various tools and materials. On the other hand all 3 manufacturers try to steer you towards their subscription service. With some of functionality arbitrary locked away behind higher subscription tiers. In some of the cases even basic functionality like "upload your own design" is considered premium feature. Before buying do a proper reasearch and consider if you are fine with the business practices of specific manufactuer. 102 | 103 | In terms of versatility while they can partially limit your paper and pen choices that is somewhat compensated by being able to do various non drawing projects. They are also designed to be used with cutting mats and potentially thick materials, so while they are roller based, paper limitations aren't as bad as other roller based machines. 104 | 105 | ## Vinyl cutters 106 | Commercial larger format vinyl cutter from well known brands like Roland or Graphtec can be quite pricey up to few thousands. But there are also some options from less known Chinese brands like Vevor which have models in $300-$1000 range. 107 | 108 | Almost all the vinyl cutters are using roller system. This and all it's associated problems is one of the biggest downside using one as a pen plotter. 109 | 110 | 111 | ## Pen attachment to for your existing CNC device 112 | If you already have some kind of computer controlled device like a CNC router or a 3d printer, you can make a pen attachment for it. 113 | And since you have it means you already have the tool for creating the attachment. 114 | 115 | Don't forget that the pen mount needs to be spring loaded (rubber bands can be used as well), so that you can get reliable pen pressure without worrying about surface not being perfectly flat. See the [Z axis](#z-axis) section for more details. If you ignore this something will bend anyways, either the pen or frame of your device. The bed of some 3d printers is mounted on springs, don't rely on those for this purpose. They are very stiff and preloaded, compressing them even by fraction of millimeter will take way more force than you should use with a pen. 116 | 117 | The downside to this approach is that Z axis in most 3d printers isn't very fast, since during normal usage there is barely any Z movement. It's not problem for small simple drawings, but for larger ones with more than 10k-100k pen lifts this can be a major bottleneck. If you finetune everything so that you only need to lift the pen <1mm, it might not be so bad, but that will be hard to combine with reliable pen pressure. 118 | 119 | You can find existing models designed specifically for many of the most popular 3d printers https://www.printables.com/@Kabacis_332837/collections/1870430 . 120 | 121 | # Pen plotter types 122 | 123 | 124 | ## Standard Cartesian build 125 | The most straight forward thing to have is having X,Y,Z axis perpendicular to each other, and each controlled by their own stepper motors. The classic approach is having all 3 axis stacked serially in the order Y, X, Z. 126 | 127 | There are other common axis setups used by different kind of CNC machines, but they make less sense for pen plotters. 128 | 129 | Since the X axis mounted on top of Y there needs to be way to move both ends of X axis. Pushing just one of them would likely result in skewing or jamming. One solution is just having 2 motors for Y axis, that's what many CNC routers for cutting sheet material do. Otherwise you need to somehow synchronize the 2 sides of Y axis. Many of the cheap non enclosed laser cutters, use a rotating metal rod for connecting the belt on two sides. 130 | 131 | 132 | Downsides: 133 | * two sided Y axis increase total part count and the mechanical complexity 134 | * X axis motor rides along the Y axis thus increasing moving weight and limiting acceleration 135 | 136 | ## H-bot 137 | (See [T-bot](#t-bot) as simpler version of this if you want to understand how this work) 138 | 139 | H-bot has it's name from the shape of it's belt path. The frame also has H shape, but not every ploter with H shape is H-bot. Instead of having separate motors controlling each axis, there two motors connected to single loop of belt. Both motors are responsible for X and Y movement. Depending on whether they are spinning in same or opposite directions you will get the movement along either X or Y axis. Spinning just one them will result in diagonal movement. While this might sound complicated all decent controller firmwares should have a builtin mode for this type of kinematics, so you don't need to worry too much about it. 140 | 141 | Downsides: 142 | * belt path introduces twisting force for the x axis 143 | * single very long belt loop 144 | 145 | 146 | Benefits: 147 | * X and Y motors are mounted in fixed position, they don't move along axis, thus decreasing movable weight 148 | 149 | 150 | ## Core-xy 151 | One of recent trends among 3d printer builds is having core-xy kinematics. It shares many of the properties with H-bot, but solves some of it's downsides at the cost of slight increase in complexity. 152 | 153 | Note that there is large amount of seriously flawed core-xy designs out there. Before designing your own, or making someone's else design I would recommend reading some articles so that you are aware of potential pitfalls and how to avoid them. 154 | 155 | 156 | Benefits: 157 | * fixed motor mounts -> less moving weight thus better acceleration and speed 158 | * slightly shorter belt paths compared to H-bot 159 | 160 | Downsides: 161 | * more complex belt path 162 | 163 | Misc posts: 164 | 165 | * https://drmrehorst.blogspot.com/2018/08/corexy-mechanism-layout-and-belt.html 166 | * http://www.corexy.com/theory.html 167 | 168 | 169 | ## T-bot 170 | T-bot consists of two straight pieces connected in a cross or T shape. Similar to H-bot the T-bot has single belt connected to two motors and moving in X or Y direction requires turning both motors at the same time. 171 | 172 | Due to simple and cheap design this is common design used both by commercial machines like Axidraw, cheap DIY kits, and also various DIY builds. 173 | 174 | 175 | Benefits: 176 | * small part count -> cheap 177 | * fixed motor mounts -> less moving weight 178 | * While not being used, doesn't take too much space 179 | 180 | Downsides: 181 | * Takes alot ot space while being used. Working area is less than half of space taken by machine while active. Can't be placed next to a wall. 182 | * Cantilever design causes a lot of force in the central connection, can result in unsupported end drooping and variance in vertical position depending on X position. 183 | 184 | 185 | * https://www.youtube.com/watch?v=Ww2grGsl3Dk (video explaining how T-bot works) 186 | 187 | 188 | ## Roller based plotters 189 | Roller based plotters move the paper along Y axis using rollers. This has an advantage of being more compact and avoid the need for Y axis to move whole X axis assembly thus resulting in overall lighter and simpler design. 190 | This is an approach used by many of vintage plotters and modern vinyl cutters. This design also allows handling rolls of paper, which can be more convenient and cheaper to handle compared to individual paper sheets. 191 | 192 | But not all roller based plotters support paper in in rolls, some plotter models have variants with and without paper roll support. 193 | 194 | Not all roller based plotters fully utilize the space saving benefits of roller system. HP 7470A has a shelf for the paper end in front and back of roller system, making the plotter depth almost as big as the paper length it's using. 195 | 196 | Also be aware that many roller based devices support only very specifc paper widths due to limited positions of top and bottom rollers. 197 | 198 | Roller based design is less common for DIY machines, as it's tricky to get it working right and reliably especially for bigger paper sizes. Something like A4 is not so bad. Even commercial machines occasionaly have notes like about position not being reliable for plots longer than 1-2m assuming it's even supported. I don't recommend trying to make a roller based machine, unless you are up for a serious challange and are more interested in mechanical tinkering than getting a functioning pen plotter at the end. 199 | 200 | Many of the DIY roller plotters are either inspired or directly based on design done by "IV projects". 201 | 202 | * https://www.youtube.com/watch?v=wX90X4rVUr8 203 | * https://www.youtube.com/watch?v=DeLeu5LkZCo 204 | * https://github.com/IVProjects/Engineering_Projects/tree/main/ProjectFiles/Pen%20PlotterV2 205 | * https://github.com/IVProjects/Engineering_Projects/tree/main/ProjectFiles/High-Speed%20Pen%20Plotter 206 | * https://www.thingiverse.com/thing:3789969 207 | * https://www.youtube.com/watch?v=AuwU73lvwYM 208 | 209 | 210 | * https://github.com/BenjaminPoilve/Liplo 211 | 212 | ## Wallbot/polargraph/ ... 213 | 214 | This style of machine consists, of main pen hanging on string/chain between two motors. 215 | 216 | This type of plotter can be very cheap, for the simplest version you need: 217 | * 2 stepper motors 218 | * controller board 219 | * 1 hobby servo 220 | * wires somewhat long 221 | * string 222 | * power supply 223 | * means to mount it all on a wall or maybe a whiteboard 224 | Everything else (which isn't a lot) can be 3d printer, made from junk or any other material you are comfortable working with. 225 | 226 | It is also one of the simplest ways for very large plots. 227 | 228 | The precision and speed will likely not be very good. 229 | 230 | * [Polargrpah](https://www.instructables.com/Polargraph-Drawing-Machine/) 231 | * [Hetor](https://juerglehni.com/works/hektor) 232 | * [Makelangelo](http://www.makelangelo.com/) 233 | * [Mural](https://getmural.me/) 234 | 235 | ## SCARA/5-bar linkage 236 | Technically SCARA and 5-bar linkage mean slightly different things, but many of the plotters discussed are both, so it helps searching if you try both names. A serial SCARA robot wouldn't be using a 5-bar linkage. 237 | 238 | In theory SCARA robots can be very fast, and have relatively large working area which is bigger than their footprint. The main industrial application is picking up and moving around small parts, similar to delta robots. 239 | 240 | The potential for speed comes from lightweight arms and having all actuators being placed in main pillar and not having to move around. 241 | 242 | In practice most pen plotters using this type of mechanism are very slow, with small working area and quite imprecise. 243 | 244 | Most common categories are either toy robots described in [Toy plotters](#toy-plotters) section, or DIY builds also using 2-3 hobby servos. This is one of the cheapest/simplest pen plotters you can make. In the most minimal case you need 245 | * 2-3 hobby-servos 246 | * any microcontroller board 247 | * power source 248 | 249 | The arms can be built from any junk, it's not going to be precise or very fast anyway. These types of cheap plotters DIY plotters are more of robotics learning projects with the build process and programming them being more important than using final result as practical pen plotter. This means that use of a well known CNC controller firmware is less useful allowing to use almost any microcontroller instead of purpose built 3d printer or CNC controller board. Another simplification comes from using only hobby servos which thus removing need for stepper motor drivers. 250 | 251 | It is possible to make less floppy and fast SCARA plotter, and you can find some DIY projects doing that. Although those are mostly non plotter robot arms used as medium/high complexity robotics learning exercise. A high performance SCARA arm will likely need somewhat beefy mechanical design and powerful motors. 252 | 253 | The lack of precision comes in typical DIY builds comes from multiple factors: 254 | * Extended arms have a lot of leverage on the joints which makes everything flex a lot more than comparable Cartesian design 255 | * A hobby servo will likely have no more than 10-14bits for full motion range of ~90-180°. A stepper motor without microstepping has 200steps (<8bits) for 360° and microsteps might add up to 8 more bits. Any easily accessible angle sensor will also likely have no more than 10-14bits of resolution. By very rough estimates with 5 cm arms that gives ~0.1mm resolution which doesn't sound so bad. But as the area increases angle accuracy doesn't improve. All the movement range comes from angle accuracy of less than single motor turn. In most other robot designs single motor turn is responsible for a small range of motion, and for longer moves you perform multiple turns of motor. For comparison typical Cartesian design with stepper motors will likely have resolution of 0.16-0.2mm without microstepping, once you add microstepping you get resolution of 0.0008-0.0125mm and it isn't affect by working area size. 256 | * One more source of bad precision and jerky movement comes from the math required to transform the x y coordinates to motor angles. It requires moderate moderate amount of trigonometric calculations. Even worse you can't just calculate angles for endpoints of movement and linearly interpolate between the two, you need to do full calculations for all the intermediate points. Doing this thousands of times per second on a microcontroller which might not even a have a floating point unit can be a problem. 257 | 258 | 259 | Some examples 260 | * https://www.brachiograph.art/ 261 | * https://www.youtube.com/watch?v=a46DMy_3xc4 262 | * [Mechpen](https://tinkerlog.com/2019/08/27/mechpen/) (large one) 263 | * https://www.thingiverse.com/thing:3096135 264 | 265 | ## Uncommon designs 266 | 267 | * https://github.com/bdring/Polar-Coaster 268 | 269 | ### Turtle 270 | 271 | There is a long history of various educational graphic libraries and robots based on the idea of "turtle" driving around controlled by comands like forward, left, right, pen up, pen down. 272 | 273 | * https://www.niklasroy.com/robotfactory/ 274 | * https://www.instructables.com/OSTR/ 275 | 276 | ### Rolling gantry plotter 277 | 278 | * https://builds.openbuilds.com/builds/a-rolling-plotter.9207/ 279 | 280 | # Z axis 281 | Requirements of pen plotters compared to other CNC machines have some unique challenges, but it also opens doors for solutions which wouldn't be suitable in other devices. 282 | 283 | Pen plotters don't really need arbitrary z axis movement. It's only necessary to lower the pen so that it touches paper and lifting it above paper. 284 | 285 | 286 | 287 | ## RC servo 288 | 289 | One of the cheapest and simplest option is using a RC servo. 290 | 291 | Good parts: 292 | * doesn't need a separate motor driver 293 | * doesn't need homing 294 | * (smaller ones) can be very light 295 | 296 | Downsides: 297 | * Limited motion range 298 | * Cheaper ones will wear out after a while 299 | * Noisy and not the fastest 300 | 301 | ## Solenoid 302 | 303 | Good parts: 304 | * very fast (up to 10 dots per second or better) 305 | 306 | Downsides: 307 | * noisy 308 | * very limited motion range (1-3mm) 309 | 310 | Note that while the theoretical movement range of many solenoids might be 10-20mm, the force drastically falls outside the sweet spot. A solenoid might have >500g of force at one end of movement, but after just 1mm of travel it will likely be halved, with next mm halving it again. On the other end of 10mm travel you might get less than 5g of force barely enough to overcome the return spring. 311 | 312 | So if you design a z axis expecting 20mm of travel from solenoid, it will likely be way too strong and weak at the same time. 313 | 314 | ## Stepper belt ring 315 | An interesting approach using a stepper motor for Z axis is used by [LumenPnP](https://www.opulo.io/products/lumenpnp) machine. A single vertically mounted belt loop with 2 toolheads attached, one on each side. This gives movement of 2 tools for the cost 1 stepper motor. 316 | 317 | Software and firmware setup could require more customization than usual since both extremes of the axis cause one of the two tools to be activated. 318 | 319 | ![LumenPnP Z axis](./pictures/plotter-hardware/lumenpnp.jpg) 320 | 321 | ## String/Stepper belt pulling up 322 | A string or a belt can only apply force in tension, it can't push things. Usually that's a limitation, but in this case it's a feature. Belt can be used to pull the pen up, while leaving downward motion to gravity or a spring. 323 | 324 | Unlike the stepper belt ring approach, this doesn't need a belt tensioning mechanism. 325 | 326 | A slightly more complicated version of this can be seen in UUNA TEK iDraw machines. 327 | 328 | ![Idraw Z axis](./pictures/plotter-hardware/idraw_z_axis.jpg) 329 | 330 | ## Stepper with rack/pinion on single rail 331 | Somewhat unique plotter Z axis design can be seen in UUNA TEK 3 devices. Vertical motion is achieved using rack and pinion. It uses a clever solution for dealing with double slide problem by having two carriages on the same linear rail. 332 | Rack and pinion mechanism lifts the silver color metal plate. But the pen holder (red) is using separate carriage isn't directly connected to the plate. Lifting the plate also lifts the pen holder, but due to the shape of hole lowering the plate doesn't push the pen holder down. 333 | 334 | ![UUNA TEK 3 rack_pinion](./pictures/plotter-hardware/pinion.jpg) 335 | 336 | * https://www.printables.com/model/868919-generative-pen-plotter-art-cnc-arduino-vinyl-cutte 337 | 338 | ## (not recommended) Lead screw 339 | Many DIY CNC machines use some kind of lead screw mechanism for lifting and lowering Z axis. 340 | 341 | That makes sense for CNC routers where you might be lifting a spindle which weights a few kg, or 3d printers lifting whole X axis assembly or the printbead. It makes less sense for pen plotters. 342 | 343 | Leadscrew does 2 things it converts rotational movement into linear motion and it provides a force/distance trade off. It also provides a simple mapping from the motor angle to vertical distance, something like 1 turn or 1 stepper step being equal to x mm of vertical travel. 344 | In some cases you can't even backdrive the leadscrew based Z axis thus which prevents it from falling down when motors are turned off. In pen plotters you want the opposite -> force requirements are minimal and you want to move it as fast as possible. It is also not necessary to have control over exact Z position. 345 | 346 | Any leadscrew based design will also likely need secondary sliding mechanism for ensuring consistent pen pressure without crushing the pen. 347 | 348 | ## Tilting 349 | Instead of having some kind of vertical linear rails, which requires some less common components. It is sufficient to have simple tilting mechanism requiring at most regular ball bearings, but a smooth metal pin against plastic should also work. 350 | 351 | Downside to this approach is limited movement range and pen tilting as gets lifted. If everything is well designed it's not a problem. 352 | 353 | ## Side mounted actuator 354 | Instead of having Z axis actuator move along X axis, it can be mounted at the end of X axis. In this setup it lifts or twists a long bar which in turn lifts or lowers the pen. 355 | 356 | This is the approach used by many medium and small size vintage pen plotters, for example HP 7475A. 357 | The actuator which you mount on the side can be anything: solenoid, stepper motor, hobby servo. Most of the vintage plotters used solenoids for their fast movement, but many newer DIY designs choose steppers or hobby servos as there are more examples of using them in DIY CNC machines. 358 | 359 | Getting this right and functioning takes more upfront engineering effort taking into account positioning, movement range and forces of each component. 360 | 361 | Not strictly required but this approach is occasionally combined with one more trick -> reusing the X axis linear bearing for tilting the pen holder. The trick is best best illustrated by this DIY build from IV Project https://www.youtube.com/watch?v=wX90X4rVUr8 . 362 | 363 | ![IV project roller machine](./pictures/plotter-hardware/iv_project.jpg) 364 | 365 | Benefits: 366 | * very lightweight tool head 367 | * allows using powerful actuator with no weight penalty 368 | * toolhead can be fully passive (no electronics or wires attached) 369 | 370 | Downsides: 371 | * small Z axis movement range 372 | 373 | 374 | # Pen changer mechanisms 375 | 376 | ## Rotary pen changer 377 | 378 | Used by many of roller based vintage plotters. Requires only single motor for spinning the carousel of pens and clever latching mechanism allowing to take and return the pen by bumping the pen holder against carousel. Usually holds ~6-8 pens. 379 | 380 | Examples: HP 7475a, HP7440a 381 | 382 | ## Linear pen changers 383 | 384 | Mostly used by flatbed vintage plotters, but there is at least one case of linear pen changer being used in roller plotter. 385 | 386 | Consists of pens being placed at the edge of drawing area and a latching mechanism similar to rotary pen changer. This allows pen changer to be fully passive without any additional actuator beside the ones used to do x/y motion. 387 | 388 | Sometimes also used by DIY machines, tricky part is having a mechanism which allows reliably grabbing and releasing the pen but holds it rigidly without wobble while drawing. 389 | 390 | Typical example - Roland DXY series. 391 | 392 | * DMP-161 - unusual case of roller plotter with linear pen changer https://www.youtube.com/watch?v=JGgGQGiP4As 393 | 394 | ## Multiple pens in tool head 395 | 396 | There are a few similar receipt printer size pen plotters using small metal capsule shaped pens. They have a rotating pen changer mechanism holding 4 pens built into the toolhead. That way there is no need to grab or release a pen during change. It only needs to rotate which was also done by bumping the mechanism against end of x axis. 397 | * https://homecomputerguy.de/en/2021/12/26/commodore-vc-1520-plotter/ --------------------------------------------------------------------------------