diff --git a/404.html b/404.html index e309637..8da1be9 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | gcode2dplotterart - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/74876495.97a6c805.js b/assets/js/74876495.97a6c805.js new file mode 100644 index 0000000..3093df7 --- /dev/null +++ b/assets/js/74876495.97a6c805.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[505],{5680:(e,t,a)=>{a.d(t,{xA:()=>s,yg:()=>h});var n=a(6540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function o(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function l(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var p=n.createContext({}),d=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):l(l({},t),e)),a},s=function(e){var t=d(e.components);return n.createElement(p.Provider,{value:t},e.children)},g="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,o=e.originalType,p=e.parentName,s=i(e,["components","mdxType","originalType","parentName"]),g=d(a),c=r,h=g["".concat(p,".").concat(c)]||g[c]||u[c]||o;return a?n.createElement(h,l(l({ref:t},s),{},{components:a})):n.createElement(h,l({ref:t},s))}));function h(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=a.length,l=new Array(o);l[0]=c;var i={};for(var p in t)hasOwnProperty.call(t,p)&&(i[p]=t[p]);i.originalType=e,i[g]="string"==typeof e?e:r,l[1]=i;for(var d=2;d{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>u,frontMatter:()=>o,metadata:()=>i,toc:()=>d});var n=a(9668),r=(a(6540),a(5680));const o={sidebar_position:2},l="Quick start",i={unversionedId:"quickstart",id:"quickstart",title:"Quick start",description:"Video tutorials are available. Run pip install gcode2dplotterart then head over to YouTube to watch the 2D Plotter or 3D Printer tutorials.",source:"@site/docs/quickstart.mdx",sourceDirName:".",slug:"/quickstart",permalink:"/gcode2dplotterart/docs/quickstart",draft:!1,editUrl:"https://github.com/TravisBumgarner/gcode2dplotterart/tree/main/gcode2dplotterart-website/docs/quickstart.mdx",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"tutorialSidebar",next:{title:"Documentation",permalink:"/gcode2dplotterart/docs/category/documentation"}},p={},d=[{value:"0. Reference the Terminology",id:"0-reference-the-terminology",level:2},{value:"1. Install dependencies",id:"1-install-dependencies",level:2},{value:"2. Setup Hardware",id:"2-setup-hardware",level:2},{value:"2D plotter",id:"2d-plotter",level:3},{value:"3D printer",id:"3d-printer",level:3},{value:"3. Learn about UGS",id:"3-learn-about-ugs",level:2},{value:"4. Get Plotting Device Dimensions and Feed Rate",id:"4-get-plotting-device-dimensions-and-feed-rate",level:2},{value:"2D plotter",id:"2d-plotter-1",level:3},{value:"3D printer",id:"3d-printer-1",level:3},{value:"5. Add a layer",id:"5-add-a-layer",level:2},{value:"6. Add lines, shapes, and paths to the layers",id:"6-add-lines-shapes-and-paths-to-the-layers",level:2},{value:"7. Generate a preview",id:"7-generate-a-preview",level:2},{value:"8. Save layers to file",id:"8-save-layers-to-file",level:2},{value:"7. Plot",id:"7-plot",level:2},{value:"8. Read the documentation",id:"8-read-the-documentation",level:2},{value:"3D Printer",id:"3d-printer-2",level:3},{value:"2D Plotter",id:"2d-plotter-2",level:3},{value:"9. Next steps",id:"9-next-steps",level:2}],s={toc:d},g="wrapper";function u(e){let{components:t,...o}=e;return(0,r.yg)(g,(0,n.A)({},s,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"quick-start"},"Quick start"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Video tutorials are available. Run ",(0,r.yg)("inlineCode",{parentName:"strong"},"pip install gcode2dplotterart")," then head over to YouTube to watch the ",(0,r.yg)("a",{parentName:"strong",href:"https://www.youtube.com/watch?v=-7CjcRVRleQ"},"2D Plotter")," or ",(0,r.yg)("a",{parentName:"strong",href:"https://www.youtube.com/watch?v=qWwGgFmGk50"},"3D Printer")," tutorials. ")),(0,r.yg)("p",null,"This guide covers setup for both 2D plotter and 3D printers. Instructions at certain steps will differ based on if a 2D plotter or 3D printer is being used. Additionally, specific devices will require some extra setup steps and will be noted with an additional section of ",(0,r.yg)("strong",{parentName:"p"},"2D plotter")," or ",(0,r.yg)("strong",{parentName:"p"},"3D printer"),"."),(0,r.yg)("h2",{id:"0-reference-the-terminology"},"0. Reference the Terminology"),(0,r.yg)("p",null,"It is useful to keep the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology"},"terminology help doc")," open while reading through the quick start. "),(0,r.yg)("h2",{id:"1-install-dependencies"},"1. Install dependencies"),(0,r.yg)("p",null,"Install the Python package with ",(0,r.yg)("inlineCode",{parentName:"p"},"pip install gcode2dplotterart")," and the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/ugs#installation"},"Universal G-Code Sender")," software."),(0,r.yg)("h2",{id:"2-setup-hardware"},"2. Setup Hardware"),(0,r.yg)("h3",{id:"2d-plotter"},"2D plotter"),(0,r.yg)("p",null,"No special setup required. "),(0,r.yg)("h3",{id:"3d-printer"},"3D printer"),(0,r.yg)("p",null,"Follow the guide to ",(0,r.yg)("a",{parentName:"p",href:"./documentation/convert-3d-to-2d"},"Convert a 3D printer to a 2D plotter"),"."),(0,r.yg)("h2",{id:"3-learn-about-ugs"},"3. Learn about UGS"),(0,r.yg)("p",null,"If the Universal G-Code Sender application has never been used before, it is recommended to read ",(0,r.yg)("a",{parentName:"p",href:"./documentation/ugs"},"this article"),"."),(0,r.yg)("h2",{id:"4-get-plotting-device-dimensions-and-feed-rate"},"4. Get Plotting Device Dimensions and Feed Rate"),(0,r.yg)("p",null,"The ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#plotting-device"},"plotting device"),"'s dimensions act as a constraint to make sure anything that is plotted in code will physically fit within the bounds of the plotting device. ",(0,r.yg)("a",{parentName:"p",href:"./documentation/ugs#get-the-plotting-devicess-dimensions"},"Get the plotting device's dimensions"),". "),(0,r.yg)("p",null,"The feed rate is a measure of how quickly the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#plotter-head"},"plotter head")," can move. It's good to tweak this so that the plotting device moves not too fast that it'll create imperfections and not too slow that plotting takes forever. ",(0,r.yg)("a",{parentName:"p",href:"./documentation/ugs#get-the-plotting-devicess-feed-rate"},"Get the plotting device's feed rate"),"."),(0,r.yg)("p",null,"Fill in the plotting device's dimensions and feed rate below. "),(0,r.yg)("h3",{id:"2d-plotter-1"},"2D plotter"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},"from gcode2dplotterart import Plotter2D\n\nplotter=Plotter2D(\n title=\"Plotter2D Quickstart\",\n \n # The following 4 values are from the `Get the plotting device's dimensions` article above. \n x_min=0, # This will be the value `X-` or 0\n x_max=200, # This will be the value `X+`\n y_min=0, # This will be the value `Y-` or 0\n y_max=200, # This will be the value `Y+` or 0\n \n # This value is from the `Get the plotting device's feed rate` article above.\n feed_rate=0,\n\n output_directory=\"./output\", \n handle_out_of_bounds='Warning' # If a plotted point is outside of the bounds, give a warning, don't plot the point, and keep going.\n)\n")),(0,r.yg)("h3",{id:"3d-printer-1"},"3D printer"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},"from gcode2dplotterart import Plotter3D\n\nplotter=Plotter3D(\n title=\"Plotter3D Quickstart\",\n \n # The following 6 values are from the `Get the plotting device's dimensions` article above. \n x_min=0, # This will be the value `X-` or 0\n x_max=200, # This will be the value `X+`\n y_min=0, # This will be the value `Y-` or 0\n y_max=200, # This will be the value `Y+` or 0\n z_plotting_height=0, # This will be the value of `Z` that connects the plotter head to the plotting surface.\n z_navigation_height=3, # This will be the value of `Z` that separate the plotter head from the plotting surface.\n\n # This value is from the `Get the plotting device's feed rate` article above.\n feed_rate=0,\n\n output_directory=\"./output\", \n handle_out_of_bounds='Warning' # If a plotted point is outside of the bounds, give a warning, don't plot the point, and keep going.\n)\n")),(0,r.yg)("h2",{id:"5-add-a-layer"},"5. Add a layer"),(0,r.yg)("p",null,"A layer is a group of ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#instruction"},"instructions")," that will be executed sequentially. It usually makes sense to create layers based on the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#plotting-instrument"},"plotting instruments")," being used. "),(0,r.yg)("p",null,"Several layers can be added to plot with different colors. The ",(0,r.yg)("inlineCode",{parentName:"p"},"color")," value is used to generate a preview before plotting. A hex color (such as ",(0,r.yg)("inlineCode",{parentName:"p"},"#00FF00"),") or human readable color name (see ",(0,r.yg)("a",{parentName:"p",href:"https://matplotlib.org/stable/gallery/color/named_colors.html#css-colors"},"MatplotLib")," for list of supported color names) can be used. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},'black_pen_layer = "black_pen_layer"\nblue_marker_layer = "blue_marker_layer"\ngreen_marker_layer = "green_marker_layer"\n\nplotter.add_layer(black_pen_layer, color="black", line_width=1.0)\nplotter.add_layer(blue_marker_layer, color="blue", line_width=4.0)\nplotter.add_layer(green_marker_layer, color="#027F00", line_width=4.0)\n')),(0,r.yg)("h2",{id:"6-add-lines-shapes-and-paths-to-the-layers"},"6. Add lines, shapes, and paths to the layers"),(0,r.yg)("p",null,"Once a layer is created, start appending instructions to that layer. Note that the points should fit inside the plotting device's bounds, or else warnings will be seen when executing the script."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},'plotter.layers[black_pen_layer].add_point(x=30, y=40)\nplotter.layers[blue_marker_layer].add_circle(x_center=10, y_center=30, radius=10)\nplotter.layers[blue_marker_layer].add_rectangle(x_start=50, y_start=50, x_end=75, y_end=75)\nplotter.layers[green_marker_layer].add_path([(10, 10), (20, 25), (30, 15), (1, 100)])\nplotter.layers[green_marker_layer].add_line(x_start=70, y_start=80, x_end=70, y_end=15)\nplotter.layers[green_marker_layer].add_text("hello world", x_start=10, y_start=10, font_size=10)\n\n')),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"},(0,r.yg)("inlineCode",{parentName:"p"},"add_point"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"add_circle"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"add_rectangle"),", and other similar methods are all wrappers around ",(0,r.yg)("inlineCode",{parentName:"p"},"add_path"),". The ",(0,r.yg)("inlineCode",{parentName:"p"},"add_path")," method is the most flexible and can be used to create any path.")),(0,r.yg)("h2",{id:"7-generate-a-preview"},"7. Generate a preview"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},"plotter.preview()\n")),(0,r.yg)("p",null,"This will open up a preview of what will be plotted. This can be useful to spot check the G-Code instructions before plotting begins."),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"Preview screenshot",src:a(8732).A,width:"640",height:"480"})),(0,r.yg)("h2",{id:"8-save-layers-to-file"},"8. Save layers to file"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},"plotter.save()\n")),(0,r.yg)("p",null,"Inside the folder specified by the plotter's ",(0,r.yg)("inlineCode",{parentName:"p"},"output_directory")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"title")," there will be four files ",(0,r.yg)("inlineCode",{parentName:"p"},"preview.gcode"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"blue_marker_layer.gcode"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"black_pen_layer.gcode"),", and ",(0,r.yg)("inlineCode",{parentName:"p"},"green_marker_layer.gcode"),". Each of the files can be opened and the code browsed. The ",(0,r.yg)("a",{parentName:"p",href:"./documentation/gcode"},"G-Code Overview")," includes explanations of all of the instructions used in this library."),(0,r.yg)("h2",{id:"7-plot"},"7. Plot"),(0,r.yg)("admonition",{type:"danger"},(0,r.yg)("p",{parentName:"admonition"},"Be sure to ",(0,r.yg)("a",{parentName:"p",href:"documentation/ugs#reset-zero"},"Reset Zero")," every time the plotting device is powered on.")),(0,r.yg)("p",null,"In UGS, open up the ",(0,r.yg)("inlineCode",{parentName:"p"},"preview.gcode")," file. This won't plot anything but will give a preview of how large the plotting area will be. It's useful to run this command a few times to ensure that the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#plotting-surface"},"plotting surface")," is where it's expected to be and things are aligned horizontally and vertically. Open the next gcode file for the first layer to be plotted. Attach the drawing instrument and begin plotting. Repeat the process for each layer."),(0,r.yg)("h2",{id:"8-read-the-documentation"},"8. Read the documentation"),(0,r.yg)("h3",{id:"3d-printer-2"},"3D Printer"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"./api/Plotter3D"},"Plotter3D")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"./api/Layer3D"},"Layer3D"))),(0,r.yg)("h3",{id:"2d-plotter-2"},"2D Plotter"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"./api/Plotter2D"},"Plotter2D")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"./api/Layer2D"},"Layer2D"))),(0,r.yg)("h2",{id:"9-next-steps"},"9. Next steps"),(0,r.yg)("p",null,"Check out the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/plotting_tips"},"plotting tips")," and ",(0,r.yg)("a",{parentName:"p",href:"./documentation/code_tips"},"coding tips"),". Find some inspiration in the ",(0,r.yg)("a",{parentName:"p",href:"./category/gallery"},"plotting gallery"),"."),(0,r.yg)("p",null,"Create something cool? ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/TravisBumgarner/gcode2dplotterart/discussions/19"},"Share it here!")))}u.isMDXComponent=!0},8732:(e,t,a)=>{a.d(t,{A:()=>n});const n=a.p+"assets/images/preview-482331decc5549e0b7126876ef16fca1.png"}}]); \ No newline at end of file diff --git a/assets/js/74876495.b5e5a8a5.js b/assets/js/74876495.b5e5a8a5.js deleted file mode 100644 index e7e6ebc..0000000 --- a/assets/js/74876495.b5e5a8a5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[505],{5680:(e,t,a)=>{a.d(t,{xA:()=>s,yg:()=>h});var n=a(6540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function o(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function l(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var p=n.createContext({}),d=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):l(l({},t),e)),a},s=function(e){var t=d(e.components);return n.createElement(p.Provider,{value:t},e.children)},g="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,o=e.originalType,p=e.parentName,s=i(e,["components","mdxType","originalType","parentName"]),g=d(a),c=r,h=g["".concat(p,".").concat(c)]||g[c]||u[c]||o;return a?n.createElement(h,l(l({ref:t},s),{},{components:a})):n.createElement(h,l({ref:t},s))}));function h(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=a.length,l=new Array(o);l[0]=c;var i={};for(var p in t)hasOwnProperty.call(t,p)&&(i[p]=t[p]);i.originalType=e,i[g]="string"==typeof e?e:r,l[1]=i;for(var d=2;d{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>u,frontMatter:()=>o,metadata:()=>i,toc:()=>d});var n=a(9668),r=(a(6540),a(5680));const o={sidebar_position:2},l="Quick start",i={unversionedId:"quickstart",id:"quickstart",title:"Quick start",description:"Video tutorials are available. Run pip install gcode2dplotterart then head over to YouTube to watch the 2D Plotter or 3D Printer tutorials.",source:"@site/docs/quickstart.mdx",sourceDirName:".",slug:"/quickstart",permalink:"/gcode2dplotterart/docs/quickstart",draft:!1,editUrl:"https://github.com/TravisBumgarner/gcode2dplotterart/tree/main/gcode2dplotterart-website/docs/quickstart.mdx",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"tutorialSidebar",next:{title:"Documentation",permalink:"/gcode2dplotterart/docs/category/documentation"}},p={},d=[{value:"0. Reference the Terminology",id:"0-reference-the-terminology",level:2},{value:"1. Install dependencies",id:"1-install-dependencies",level:2},{value:"2. Setup Hardware",id:"2-setup-hardware",level:2},{value:"2D plotter",id:"2d-plotter",level:3},{value:"3D printer",id:"3d-printer",level:3},{value:"3. Learn about UGS",id:"3-learn-about-ugs",level:2},{value:"4. Get Plotting Device Dimensions and Feed Rate",id:"4-get-plotting-device-dimensions-and-feed-rate",level:2},{value:"2D plotter",id:"2d-plotter-1",level:3},{value:"3D printer",id:"3d-printer-1",level:3},{value:"5. Add a layer",id:"5-add-a-layer",level:2},{value:"6. Add lines, shapes, and paths to the layers",id:"6-add-lines-shapes-and-paths-to-the-layers",level:2},{value:"7. Generate a preview",id:"7-generate-a-preview",level:2},{value:"8. Save layers to file",id:"8-save-layers-to-file",level:2},{value:"7. Plot",id:"7-plot",level:2},{value:"8. Read the documentation",id:"8-read-the-documentation",level:2},{value:"3D Printer",id:"3d-printer-2",level:3},{value:"2D Plotter",id:"2d-plotter-2",level:3},{value:"9. Next steps",id:"9-next-steps",level:2}],s={toc:d},g="wrapper";function u(e){let{components:t,...o}=e;return(0,r.yg)(g,(0,n.A)({},s,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"quick-start"},"Quick start"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Video tutorials are available. Run ",(0,r.yg)("inlineCode",{parentName:"strong"},"pip install gcode2dplotterart")," then head over to YouTube to watch the ",(0,r.yg)("a",{parentName:"strong",href:"https://www.youtube.com/watch?v=-7CjcRVRleQ&ab_channel=TravistheMaker"},"2D Plotter")," or ",(0,r.yg)("a",{parentName:"strong",href:"https://www.youtube.com/watch?v=-7CjcRVRleQ&feature=youtu.be"},"3D Printer")," tutorials. ")),(0,r.yg)("p",null,"This guide covers setup for both 2D plotter and 3D printers. Instructions at certain steps will differ based on if a 2D plotter or 3D printer is being used. Additionally, specific devices will require some extra setup steps and will be noted with an additional section of ",(0,r.yg)("strong",{parentName:"p"},"2D plotter")," or ",(0,r.yg)("strong",{parentName:"p"},"3D printer"),"."),(0,r.yg)("h2",{id:"0-reference-the-terminology"},"0. Reference the Terminology"),(0,r.yg)("p",null,"It is useful to keep the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology"},"terminology help doc")," open while reading through the quick start. "),(0,r.yg)("h2",{id:"1-install-dependencies"},"1. Install dependencies"),(0,r.yg)("p",null,"Install the Python package with ",(0,r.yg)("inlineCode",{parentName:"p"},"pip install gcode2dplotterart")," and the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/ugs#installation"},"Universal G-Code Sender")," software."),(0,r.yg)("h2",{id:"2-setup-hardware"},"2. Setup Hardware"),(0,r.yg)("h3",{id:"2d-plotter"},"2D plotter"),(0,r.yg)("p",null,"No special setup required. "),(0,r.yg)("h3",{id:"3d-printer"},"3D printer"),(0,r.yg)("p",null,"Follow the guide to ",(0,r.yg)("a",{parentName:"p",href:"./documentation/convert-3d-to-2d"},"Convert a 3D printer to a 2D plotter"),"."),(0,r.yg)("h2",{id:"3-learn-about-ugs"},"3. Learn about UGS"),(0,r.yg)("p",null,"If the Universal G-Code Sender application has never been used before, it is recommended to read ",(0,r.yg)("a",{parentName:"p",href:"./documentation/ugs"},"this article"),"."),(0,r.yg)("h2",{id:"4-get-plotting-device-dimensions-and-feed-rate"},"4. Get Plotting Device Dimensions and Feed Rate"),(0,r.yg)("p",null,"The ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#plotting-device"},"plotting device"),"'s dimensions act as a constraint to make sure anything that is plotted in code will physically fit within the bounds of the plotting device. ",(0,r.yg)("a",{parentName:"p",href:"./documentation/ugs#get-the-plotting-devicess-dimensions"},"Get the plotting device's dimensions"),". "),(0,r.yg)("p",null,"The feed rate is a measure of how quickly the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#plotter-head"},"plotter head")," can move. It's good to tweak this so that the plotting device moves not too fast that it'll create imperfections and not too slow that plotting takes forever. ",(0,r.yg)("a",{parentName:"p",href:"./documentation/ugs#get-the-plotting-devicess-feed-rate"},"Get the plotting device's feed rate"),"."),(0,r.yg)("p",null,"Fill in the plotting device's dimensions and feed rate below. "),(0,r.yg)("h3",{id:"2d-plotter-1"},"2D plotter"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},"from gcode2dplotterart import Plotter2D\n\nplotter=Plotter2D(\n title=\"Plotter2D Quickstart\",\n \n # The following 4 values are from the `Get the plotting device's dimensions` article above. \n x_min=0, # This will be the value `X-` or 0\n x_max=200, # This will be the value `X+`\n y_min=0, # This will be the value `Y-` or 0\n y_max=200, # This will be the value `Y+` or 0\n \n # This value is from the `Get the plotting device's feed rate` article above.\n feed_rate=0,\n\n output_directory=\"./output\", \n handle_out_of_bounds='Warning' # If a plotted point is outside of the bounds, give a warning, don't plot the point, and keep going.\n)\n")),(0,r.yg)("h3",{id:"3d-printer-1"},"3D printer"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},"from gcode2dplotterart import Plotter3D\n\nplotter=Plotter3D(\n title=\"Plotter3D Quickstart\",\n \n # The following 6 values are from the `Get the plotting device's dimensions` article above. \n x_min=0, # This will be the value `X-` or 0\n x_max=200, # This will be the value `X+`\n y_min=0, # This will be the value `Y-` or 0\n y_max=200, # This will be the value `Y+` or 0\n z_plotting_height=0, # This will be the value of `Z` that connects the plotter head to the plotting surface.\n z_navigation_height=3, # This will be the value of `Z` that separate the plotter head from the plotting surface.\n\n # This value is from the `Get the plotting device's feed rate` article above.\n feed_rate=0,\n\n output_directory=\"./output\", \n handle_out_of_bounds='Warning' # If a plotted point is outside of the bounds, give a warning, don't plot the point, and keep going.\n)\n")),(0,r.yg)("h2",{id:"5-add-a-layer"},"5. Add a layer"),(0,r.yg)("p",null,"A layer is a group of ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#instruction"},"instructions")," that will be executed sequentially. It usually makes sense to create layers based on the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#plotting-instrument"},"plotting instruments")," being used. "),(0,r.yg)("p",null,"Several layers can be added to plot with different colors. The ",(0,r.yg)("inlineCode",{parentName:"p"},"color")," value is used to generate a preview before plotting. A hex color (such as ",(0,r.yg)("inlineCode",{parentName:"p"},"#00FF00"),") or human readable color name (see ",(0,r.yg)("a",{parentName:"p",href:"https://matplotlib.org/stable/gallery/color/named_colors.html#css-colors"},"MatplotLib")," for list of supported color names) can be used. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},'black_pen_layer = "black_pen_layer"\nblue_marker_layer = "blue_marker_layer"\ngreen_marker_layer = "green_marker_layer"\n\nplotter.add_layer(black_pen_layer, color="black", line_width=1.0)\nplotter.add_layer(blue_marker_layer, color="blue", line_width=4.0)\nplotter.add_layer(green_marker_layer, color="#027F00", line_width=4.0)\n')),(0,r.yg)("h2",{id:"6-add-lines-shapes-and-paths-to-the-layers"},"6. Add lines, shapes, and paths to the layers"),(0,r.yg)("p",null,"Once a layer is created, start appending instructions to that layer. Note that the points should fit inside the plotting device's bounds, or else warnings will be seen when executing the script."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},'plotter.layers[black_pen_layer].add_point(x=30, y=40)\nplotter.layers[blue_marker_layer].add_circle(x_center=10, y_center=30, radius=10)\nplotter.layers[blue_marker_layer].add_rectangle(x_start=50, y_start=50, x_end=75, y_end=75)\nplotter.layers[green_marker_layer].add_path([(10, 10), (20, 25), (30, 15), (1, 100)])\nplotter.layers[green_marker_layer].add_line(x_start=70, y_start=80, x_end=70, y_end=15)\nplotter.layers[green_marker_layer].add_text("hello world", x_start=10, y_start=10, font_size=10)\n\n')),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"},(0,r.yg)("inlineCode",{parentName:"p"},"add_point"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"add_circle"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"add_rectangle"),", and other similar methods are all wrappers around ",(0,r.yg)("inlineCode",{parentName:"p"},"add_path"),". The ",(0,r.yg)("inlineCode",{parentName:"p"},"add_path")," method is the most flexible and can be used to create any path.")),(0,r.yg)("h2",{id:"7-generate-a-preview"},"7. Generate a preview"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},"plotter.preview()\n")),(0,r.yg)("p",null,"This will open up a preview of what will be plotted. This can be useful to spot check the G-Code instructions before plotting begins."),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"Preview screenshot",src:a(8732).A,width:"640",height:"480"})),(0,r.yg)("h2",{id:"8-save-layers-to-file"},"8. Save layers to file"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-python"},"plotter.save()\n")),(0,r.yg)("p",null,"Inside the folder specified by the plotter's ",(0,r.yg)("inlineCode",{parentName:"p"},"output_directory")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"title")," there will be four files ",(0,r.yg)("inlineCode",{parentName:"p"},"preview.gcode"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"blue_marker_layer.gcode"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"black_pen_layer.gcode"),", and ",(0,r.yg)("inlineCode",{parentName:"p"},"green_marker_layer.gcode"),". Each of the files can be opened and the code browsed. The ",(0,r.yg)("a",{parentName:"p",href:"./documentation/gcode"},"G-Code Overview")," includes explanations of all of the instructions used in this library."),(0,r.yg)("h2",{id:"7-plot"},"7. Plot"),(0,r.yg)("admonition",{type:"danger"},(0,r.yg)("p",{parentName:"admonition"},"Be sure to ",(0,r.yg)("a",{parentName:"p",href:"documentation/ugs#reset-zero"},"Reset Zero")," every time the plotting device is powered on.")),(0,r.yg)("p",null,"In UGS, open up the ",(0,r.yg)("inlineCode",{parentName:"p"},"preview.gcode")," file. This won't plot anything but will give a preview of how large the plotting area will be. It's useful to run this command a few times to ensure that the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/terminology#plotting-surface"},"plotting surface")," is where it's expected to be and things are aligned horizontally and vertically. Open the next gcode file for the first layer to be plotted. Attach the drawing instrument and begin plotting. Repeat the process for each layer."),(0,r.yg)("h2",{id:"8-read-the-documentation"},"8. Read the documentation"),(0,r.yg)("h3",{id:"3d-printer-2"},"3D Printer"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"./api/Plotter3D"},"Plotter3D")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"./api/Layer3D"},"Layer3D"))),(0,r.yg)("h3",{id:"2d-plotter-2"},"2D Plotter"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"./api/Plotter2D"},"Plotter2D")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"./api/Layer2D"},"Layer2D"))),(0,r.yg)("h2",{id:"9-next-steps"},"9. Next steps"),(0,r.yg)("p",null,"Check out the ",(0,r.yg)("a",{parentName:"p",href:"./documentation/plotting_tips"},"plotting tips")," and ",(0,r.yg)("a",{parentName:"p",href:"./documentation/code_tips"},"coding tips"),". Find some inspiration in the ",(0,r.yg)("a",{parentName:"p",href:"./category/gallery"},"plotting gallery"),"."),(0,r.yg)("p",null,"Create something cool? ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/TravisBumgarner/gcode2dplotterart/discussions/19"},"Share it here!")))}u.isMDXComponent=!0},8732:(e,t,a)=>{a.d(t,{A:()=>n});const n=a.p+"assets/images/preview-482331decc5549e0b7126876ef16fca1.png"}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.0f92c02c.js b/assets/js/runtime~main.bbf0fbd4.js similarity index 98% rename from assets/js/runtime~main.0f92c02c.js rename to assets/js/runtime~main.bbf0fbd4.js index bd281a2..ea6709e 100644 --- a/assets/js/runtime~main.0f92c02c.js +++ b/assets/js/runtime~main.bbf0fbd4.js @@ -1 +1 @@ -(()=>{"use strict";var e,t,r,a,o,f={},b={};function c(e){var t=b[e];if(void 0!==t)return t.exports;var r=b[e]={exports:{}};return f[e].call(r.exports,r,r.exports,c),r.exports}c.m=f,e=[],c.O=(t,r,a,o)=>{if(!r){var f=1/0;for(i=0;i=o)&&Object.keys(c.O).every((e=>c.O[e](r[d])))?r.splice(d--,1):(b=!1,o0&&e[i-1][2]>o;i--)e[i]=e[i-1];e[i]=[r,a,o]},c.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return c.d(t,{a:t}),t},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,c.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var f={};t=t||[null,r({}),r([]),r(r)];for(var b=2&a&&e;"object"==typeof b&&!~t.indexOf(b);b=r(b))Object.getOwnPropertyNames(b).forEach((t=>f[t]=()=>e[t]));return f.default=()=>e,c.d(o,f),o},c.d=(e,t)=>{for(var r in t)c.o(t,r)&&!c.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},c.f={},c.e=e=>Promise.all(Object.keys(c.f).reduce(((t,r)=>(c.f[r](e,t),t)),[])),c.u=e=>"assets/js/"+({31:"2349377c",57:"30cdd815",61:"1f391b9e",66:"6998ad6d",78:"7f5d8852",120:"9e6942a3",133:"18e4be96",134:"393be207",161:"0068c63b",195:"988250f0",277:"b41a8b5c",286:"302578d6",335:"e41b20a3",401:"17896441",505:"74876495",510:"87e35500",571:"503685f3",581:"935f2afb",583:"1df93b7f",604:"2ce9266b",618:"b2d3bc69",621:"d1d134fa",677:"b15882fb",683:"7e9c41d9",688:"928e25d8",701:"7f887325",714:"1be78505",753:"cce22db2",816:"94f8389d",865:"fce6a2e4",874:"c3c163db",880:"9a09aacc",962:"77a48e5b",969:"14eb3368",994:"4eea0ab5"}[e]||e)+"."+{31:"cee44c88",57:"cc569b20",61:"ffc7ccbe",66:"5992d013",78:"2478120d",120:"8a7e810a",133:"6583da1d",134:"2914bbed",161:"eca4d6bb",195:"2aa927d9",262:"915b003d",277:"336bdc0e",286:"90d708ab",335:"1b990901",401:"40227fb7",505:"b5e5a8a5",510:"9ed50ad7",571:"f41c08aa",581:"7afe16f2",583:"5f46569f",604:"6caa6933",618:"8a6385ca",621:"e5cf1ef2",677:"6d73ba07",683:"71c8e5bd",688:"1a3f6669",701:"3d4603ce",714:"42dc4b9e",733:"43768e20",753:"204ac77d",774:"41434599",816:"35985500",865:"289b7a3e",874:"16a4d967",880:"d8387283",962:"01b06eb5",969:"fff8d62e",994:"fa6cdb8b"}[e]+".js",c.miniCssF=e=>{},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),a={},o="website:",c.l=(e,t,r,f)=>{if(a[e])a[e].push(t);else{var b,d;if(void 0!==r)for(var n=document.getElementsByTagName("script"),i=0;i{b.onerror=b.onload=null,clearTimeout(s);var o=a[e];if(delete a[e],b.parentNode&&b.parentNode.removeChild(b),o&&o.forEach((e=>e(r))),t)return t(r)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:b}),12e4);b.onerror=l.bind(null,b.onerror),b.onload=l.bind(null,b.onload),d&&document.head.appendChild(b)}},c.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.p="/gcode2dplotterart/",c.gca=function(e){return e={17896441:"401",74876495:"505","2349377c":"31","30cdd815":"57","1f391b9e":"61","6998ad6d":"66","7f5d8852":"78","9e6942a3":"120","18e4be96":"133","393be207":"134","0068c63b":"161","988250f0":"195",b41a8b5c:"277","302578d6":"286",e41b20a3:"335","87e35500":"510","503685f3":"571","935f2afb":"581","1df93b7f":"583","2ce9266b":"604",b2d3bc69:"618",d1d134fa:"621",b15882fb:"677","7e9c41d9":"683","928e25d8":"688","7f887325":"701","1be78505":"714",cce22db2:"753","94f8389d":"816",fce6a2e4:"865",c3c163db:"874","9a09aacc":"880","77a48e5b":"962","14eb3368":"969","4eea0ab5":"994"}[e]||e,c.p+c.u(e)},(()=>{var e={354:0,869:0};c.f.j=(t,r)=>{var a=c.o(e,t)?e[t]:void 0;if(0!==a)if(a)r.push(a[2]);else if(/^(354|869)$/.test(t))e[t]=0;else{var o=new Promise(((r,o)=>a=e[t]=[r,o]));r.push(a[2]=o);var f=c.p+c.u(t),b=new Error;c.l(f,(r=>{if(c.o(e,t)&&(0!==(a=e[t])&&(e[t]=void 0),a)){var o=r&&("load"===r.type?"missing":r.type),f=r&&r.target&&r.target.src;b.message="Loading chunk "+t+" failed.\n("+o+": "+f+")",b.name="ChunkLoadError",b.type=o,b.request=f,a[1](b)}}),"chunk-"+t,t)}},c.O.j=t=>0===e[t];var t=(t,r)=>{var a,o,f=r[0],b=r[1],d=r[2],n=0;if(f.some((t=>0!==e[t]))){for(a in b)c.o(b,a)&&(c.m[a]=b[a]);if(d)var i=d(c)}for(t&&t(r);n{"use strict";var e,t,r,a,o,f={},b={};function c(e){var t=b[e];if(void 0!==t)return t.exports;var r=b[e]={exports:{}};return f[e].call(r.exports,r,r.exports,c),r.exports}c.m=f,e=[],c.O=(t,r,a,o)=>{if(!r){var f=1/0;for(i=0;i=o)&&Object.keys(c.O).every((e=>c.O[e](r[d])))?r.splice(d--,1):(b=!1,o0&&e[i-1][2]>o;i--)e[i]=e[i-1];e[i]=[r,a,o]},c.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return c.d(t,{a:t}),t},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,c.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var f={};t=t||[null,r({}),r([]),r(r)];for(var b=2&a&&e;"object"==typeof b&&!~t.indexOf(b);b=r(b))Object.getOwnPropertyNames(b).forEach((t=>f[t]=()=>e[t]));return f.default=()=>e,c.d(o,f),o},c.d=(e,t)=>{for(var r in t)c.o(t,r)&&!c.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},c.f={},c.e=e=>Promise.all(Object.keys(c.f).reduce(((t,r)=>(c.f[r](e,t),t)),[])),c.u=e=>"assets/js/"+({31:"2349377c",57:"30cdd815",61:"1f391b9e",66:"6998ad6d",78:"7f5d8852",120:"9e6942a3",133:"18e4be96",134:"393be207",161:"0068c63b",195:"988250f0",277:"b41a8b5c",286:"302578d6",335:"e41b20a3",401:"17896441",505:"74876495",510:"87e35500",571:"503685f3",581:"935f2afb",583:"1df93b7f",604:"2ce9266b",618:"b2d3bc69",621:"d1d134fa",677:"b15882fb",683:"7e9c41d9",688:"928e25d8",701:"7f887325",714:"1be78505",753:"cce22db2",816:"94f8389d",865:"fce6a2e4",874:"c3c163db",880:"9a09aacc",962:"77a48e5b",969:"14eb3368",994:"4eea0ab5"}[e]||e)+"."+{31:"cee44c88",57:"cc569b20",61:"ffc7ccbe",66:"5992d013",78:"2478120d",120:"8a7e810a",133:"6583da1d",134:"2914bbed",161:"eca4d6bb",195:"2aa927d9",262:"915b003d",277:"336bdc0e",286:"90d708ab",335:"1b990901",401:"40227fb7",505:"97a6c805",510:"9ed50ad7",571:"f41c08aa",581:"7afe16f2",583:"5f46569f",604:"6caa6933",618:"8a6385ca",621:"e5cf1ef2",677:"6d73ba07",683:"71c8e5bd",688:"1a3f6669",701:"3d4603ce",714:"42dc4b9e",733:"43768e20",753:"204ac77d",774:"41434599",816:"35985500",865:"289b7a3e",874:"16a4d967",880:"d8387283",962:"01b06eb5",969:"fff8d62e",994:"fa6cdb8b"}[e]+".js",c.miniCssF=e=>{},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),a={},o="website:",c.l=(e,t,r,f)=>{if(a[e])a[e].push(t);else{var b,d;if(void 0!==r)for(var n=document.getElementsByTagName("script"),i=0;i{b.onerror=b.onload=null,clearTimeout(s);var o=a[e];if(delete a[e],b.parentNode&&b.parentNode.removeChild(b),o&&o.forEach((e=>e(r))),t)return t(r)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:b}),12e4);b.onerror=l.bind(null,b.onerror),b.onload=l.bind(null,b.onload),d&&document.head.appendChild(b)}},c.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.p="/gcode2dplotterart/",c.gca=function(e){return e={17896441:"401",74876495:"505","2349377c":"31","30cdd815":"57","1f391b9e":"61","6998ad6d":"66","7f5d8852":"78","9e6942a3":"120","18e4be96":"133","393be207":"134","0068c63b":"161","988250f0":"195",b41a8b5c:"277","302578d6":"286",e41b20a3:"335","87e35500":"510","503685f3":"571","935f2afb":"581","1df93b7f":"583","2ce9266b":"604",b2d3bc69:"618",d1d134fa:"621",b15882fb:"677","7e9c41d9":"683","928e25d8":"688","7f887325":"701","1be78505":"714",cce22db2:"753","94f8389d":"816",fce6a2e4:"865",c3c163db:"874","9a09aacc":"880","77a48e5b":"962","14eb3368":"969","4eea0ab5":"994"}[e]||e,c.p+c.u(e)},(()=>{var e={354:0,869:0};c.f.j=(t,r)=>{var a=c.o(e,t)?e[t]:void 0;if(0!==a)if(a)r.push(a[2]);else if(/^(354|869)$/.test(t))e[t]=0;else{var o=new Promise(((r,o)=>a=e[t]=[r,o]));r.push(a[2]=o);var f=c.p+c.u(t),b=new Error;c.l(f,(r=>{if(c.o(e,t)&&(0!==(a=e[t])&&(e[t]=void 0),a)){var o=r&&("load"===r.type?"missing":r.type),f=r&&r.target&&r.target.src;b.message="Loading chunk "+t+" failed.\n("+o+": "+f+")",b.name="ChunkLoadError",b.type=o,b.request=f,a[1](b)}}),"chunk-"+t,t)}},c.O.j=t=>0===e[t];var t=(t,r)=>{var a,o,f=r[0],b=r[1],d=r[2],n=0;if(f.some((t=>0!==e[t]))){for(a in b)c.o(b,a)&&(c.m[a]=b[a]);if(d)var i=d(c)}for(t&&t(r);n Layer2D | gcode2dplotterart - + @@ -47,7 +47,7 @@ to plotting surface. Should be used when starting a path.

Args:

  • instruction_phase (setup | plotting | teardown, optional) : The instruction phase of plotting to send the instruction to. Defaults to plotting.

Returns:

  • Layer : The Layer object. Allows for chaining of add methods.
- + \ No newline at end of file diff --git a/docs/api/Layer3D.html b/docs/api/Layer3D.html index 0d0025f..41ff994 100644 --- a/docs/api/Layer3D.html +++ b/docs/api/Layer3D.html @@ -4,7 +4,7 @@ Layer3D | gcode2dplotterart - + @@ -51,7 +51,7 @@ to plotting surface. Should be used when starting a path.

Args:

  • instruction_phase (setup | plotting | teardown, optional) : The instruction phase of plotting to send the instruction to. Defaults to plotting.

Returns:

  • Layer : The Layer object. Allows for chaining of add methods.
- + \ No newline at end of file diff --git a/docs/api/Plotter2D.html b/docs/api/Plotter2D.html index 6b1485c..06ed484 100644 --- a/docs/api/Plotter2D.html +++ b/docs/api/Plotter2D.html @@ -4,7 +4,7 @@ Plotter2D | gcode2dplotterart - + @@ -24,7 +24,7 @@ size of the art to be plotted. Defaults to True.

save

save(
self, clear_output_before_save: bool = True, include_layer_number: bool = True )
-> None

Save all the layers to the output directory defined by the output_directory Plotter param. Each layer will be saved as an individual file with the filename defined by {layer_number}_{layer_title}.gcode.

Args:

  • clear_output_before_save (bool, optional): Whether to remove all files from the artwork output directory (defined as [output_directory]/[title]) before saving. Defaults to True.
  • include_layer_number (bool, optional): Whether to prepend filename with layer_number. Defaults to True.
- + \ No newline at end of file diff --git a/docs/api/Plotter3D.html b/docs/api/Plotter3D.html index e15b9e7..8adc250 100644 --- a/docs/api/Plotter3D.html +++ b/docs/api/Plotter3D.html @@ -4,7 +4,7 @@ Plotter3D | gcode2dplotterart - + @@ -19,7 +19,7 @@ size of the art to be plotted. Defaults to True.

save

save(
self, clear_output_before_save: bool = True, include_layer_number: bool = True )
-> None

Save all the layers to the output directory defined by the output_directory Plotter param. Each layer will be saved as an individual file with the filename defined by {layer_number}_{layer_title}.gcode.

Args:

  • clear_output_before_save (bool, optional): Whether to remove all files from the artwork output directory (defined as [output_directory]/[title]) before saving. Defaults to True.
  • include_layer_number (bool, optional): Whether to prepend filename with layer_number. Defaults to True.
- + \ No newline at end of file diff --git a/docs/category/api.html b/docs/category/api.html index 9fc795a..ed37c57 100644 --- a/docs/category/api.html +++ b/docs/category/api.html @@ -4,13 +4,13 @@ API | gcode2dplotterart - + - + \ No newline at end of file diff --git a/docs/category/documentation.html b/docs/category/documentation.html index 3489267..f116dfb 100644 --- a/docs/category/documentation.html +++ b/docs/category/documentation.html @@ -4,13 +4,13 @@ Documentation | gcode2dplotterart - + - + \ No newline at end of file diff --git a/docs/category/gallery.html b/docs/category/gallery.html index a7e86ba..f3a2d4a 100644 --- a/docs/category/gallery.html +++ b/docs/category/gallery.html @@ -4,13 +4,13 @@ Gallery | gcode2dplotterart - +

Gallery

Image gallery with sample code

- + \ No newline at end of file diff --git a/docs/documentation/code_tips.html b/docs/documentation/code_tips.html index d43bb30..c19fd99 100644 --- a/docs/documentation/code_tips.html +++ b/docs/documentation/code_tips.html @@ -4,13 +4,13 @@ Coding Tips | gcode2dplotterart - +

Coding Tips

Chaining Instructions

All instructions can be chained.

plotter.layers[LAYER_1]\
.add_rectangle(x_start=25, y_start=25, x_end=50, y_end=50)\
.add_circle(x_center=10, y_center=10, radius=10)

add_path

All of the plotting methods - add_rectangle, add_line, add_circle, add_point, etc. all use add_path under the hood. add_path takes an array of (x,y) values.

- + \ No newline at end of file diff --git a/docs/documentation/convert-3d-to-2d.html b/docs/documentation/convert-3d-to-2d.html index 55e8152..2f66efa 100644 --- a/docs/documentation/convert-3d-to-2d.html +++ b/docs/documentation/convert-3d-to-2d.html @@ -4,13 +4,13 @@ Convert a 3D printer to a 2D plotter | gcode2dplotterart - +

Convert a 3D printer to a 2D plotter

Lots of folks have converted their 3D printers to 2D plotters. A quick search on Google, with the name of the plotting device and "2d plotter" will yield free models that can be printed on the 3d printer.

If you find an adapter or make your own, please share it here.

Available Adapters

Note - apart from the Creality Ender 5, these have not been tested.

Making your own adapter

Note - I recorded a 3 part series on Twitch where I designed and printed my own adapter. You can watch the videos on YouTube here: Part 1, Part 2, Part 3.

This is what we're going to aim to make.


Here are the instructions on how I made my own adapter.

Measurements photo

First I looked at my 3D printer and tried to find a place where I could mount a 3D printed part. My Ender 5 had two screws that were well placed that I thought I could attach my part to. My design would involve unscrewing those two screws, aligning the holes on the 3D printed part with those screw holes, and then screw everything together.

Experiments photo

It took me a few different tries and adjusting my measurements before I had a part that fit well. The first experiment made sure the screw holes were lined up correctly. The second experiment had a better placement for the screw holes. The third experiment made sure that everything fit nicely together. The fourth experiment tested the ability to grip a plotting device.

Completed photo

Eventually I figured everything out and was ready for plotting.

Example plot photo

- + \ No newline at end of file diff --git a/docs/documentation/faq.html b/docs/documentation/faq.html index 65c7860..63d4df0 100644 --- a/docs/documentation/faq.html +++ b/docs/documentation/faq.html @@ -4,13 +4,13 @@ FAQ & Troubleshooting | gcode2dplotterart - +

FAQ & Troubleshooting

What does this word mean?

Check out the terminology page.

Where do I report a bug?

Open an issue.

Where can I get help?

Start a discussion.

How do I make a feature request?

Create a suggestion.

Why is the plotting device running into the wall?

When the plotting device is turned on, it has no memory of where the plotter head previously was. Furthermore, the plotting device has no idea of its bounds.

Therefore, it's important, when first turning on the device, to let it know where it is.

All 3D printers and some 2D plotters ship with limit switches which allow you to automatically home the device. Send the G28 Command to the plotting device to automatically home it. If your plotting device doesn't have limit switches, continue below.

The first step is to define the lower and upper bounds of the plotting area. This involves manually moving the plotter head to the bottom left, to the most -X and -Y position, and if applicable -Z position, then hitting the reset zero button in UGS.

With the lower bound defined, it is also important to know the upper bound. Move the plotting head to the top right corner. The controller state values for X0 will display the width, and Y0 will display the height.

Do not plot outside of these bounds. If the plotting device's width is 200 and an instruction is sent to it for moving the X position to 250 the plotting device will happily grind the motors in an attempt to move it there. If at any time a point is plotted outside of the bounds of the plotting device, it's important to repeat these steps.

The Plotter class has built in support for preventing this from happening.

UGS says it's connected, but the plotting device is doing nothing.

Click the Connect or Disconnect button twice to reestablish a connection with the plotting device.

- + \ No newline at end of file diff --git a/docs/documentation/gcode.html b/docs/documentation/gcode.html index 322be7b..7894f64 100644 --- a/docs/documentation/gcode.html +++ b/docs/documentation/gcode.html @@ -4,13 +4,13 @@ G-Code Overview | gcode2dplotterart - +

G-Code Overview

What is G-Code

G-Code is the programming language of 2D plotters and 3D printers. It is a lower level programming language and therefore is typically difficult to understand. Below is a snippet of code to plot a rectangle. This library aims to abstract away these instructions so that more user friendly code, such as Python, can be used instead to generate G-Code. For more information on G

G21
F10000
M3 S0
G1 X0.000 Y-50.000 F10000
M3 S1000
G04 P0.25
G1 X0.000 Y0.000 F10000
G1 X50.000 Y0.000 F10000
G1 X50.000 Y-50.000 F10000
M3 S0
G04 P0.25
M2

Library Instructions

Below are a collection of all the instructions used in this library. For a more thorough explanation, RepRap and Marlin are great (and dense) resources.

The square braces indicate a variable that can be passed to the specific G-Code instructions. For example the instruction G1 X[x] Y[y] takes in variables x and y, which could result in a G-Code instruction looking like G1 X10.0 Y5.0.

Comment

Add a comment with a line starting with ;. This instruction is not read by the plotting device.

;The following instruction lifts the pen up.
M3 S0

F

Set the movement speed f of the plotting device with F[f]. Can also be attached to individual G1 commands.

F10000

G1

Move the plotting device to the specified x and y and z coordinates at a speed of f with G1 X[x] Y[y] Z[z] F[f]. Not all of the coordinates are required.

G1 X0.000 Y0.000 F10000
G1 X50.000 F10000
G1 Z10 F10000
G1 X50.000 Y-50.000 F10000

G4

Pause for a period of time p with G4 P[p]. This command is useful in combination with a move command. A pause after moving will give the plotting device a chance to stop any vibrating that might impact the plotting quality.

; Lower plotter head
M3 S1000
; Pause for 0.25 seconds
G4 P0.25
;Point: 59.84807753012208, -48.2635182233307
G1 X59.848 Y-48.264 F10000
G20

G21

Sets the units of measurements to mm.

G21

G28

Home the plotting device. Will return the plotter to X = 0, Y = 0 and if supported, Z = 0. Not all plotting devices support this command.

G28

M2

Marks the end of the program execution.

M2

M3

For 2D plotters, used to raise and lower the plotter head. M3 S0 will raise the print and M3 S1000 will lower the plotter head.

; Raise plotter head
M3 S0
; Move to first coordinate
G1 X0.000 Y0.000 F10000
; Lower the plotter head
M3 S1000
; Draw two lines
G1 X50.000 Y0.000 F10000
G1 X50.000 Y-50.000 F10000
; Raise the plotter head on a 2D plotter, A `G1` command with `Z` height would be used here for a 3D printer.
M3 S0
- + \ No newline at end of file diff --git a/docs/documentation/plotting_tips.html b/docs/documentation/plotting_tips.html index a689a8a..53f9081 100644 --- a/docs/documentation/plotting_tips.html +++ b/docs/documentation/plotting_tips.html @@ -4,13 +4,13 @@ Plotting Tips | gcode2dplotterart - +

Plotting Tips

Use a single sheet of flat paper. A notebook might be warped or bent. Tape the sheet of paper to a flat surface.

Success when plotting with multiple layers

When attaching a plotting instrument to the plotter head, note its height above the plotting surface. When changing layers and using a new plotting instrument, be sure that the height above the plotting surface is the same as the previous plotting instrument.

Be mindful of the order in which layers are plotted

This is mostly an artistic decision. Think about how colors layer on top of each other. What is the impact of a lighter color on a darker one, and vice versa?

There is also another consideration to keep in mind. If a darker color is plotted first, then a lighter color on top, the lighter color plotting instrument might absorb some of the dark color and become damaged.

- + \ No newline at end of file diff --git a/docs/documentation/terminology.html b/docs/documentation/terminology.html index c9630c3..3913e53 100644 --- a/docs/documentation/terminology.html +++ b/docs/documentation/terminology.html @@ -4,13 +4,13 @@ Terminology | gcode2dplotterart - +

Terminology

Don't see a definition below? Ask here.

Instruction

A single command sent from UGS to the plotting device. Check out the G-Code Overview for a full list of supported instructions.

Instruction Phase

Plotting is done in three phases. The first is setup which adds instructions to prepare the plotting device. The second step is plotting in which the plotting device is lowered and the plotting instrument is applied to the plotting surface. The final step is teardown which adds instructions to return the plotting device to its original state.

Layer

A layer is a group of instructions that will be executed sequentially, all with the same plotting instrument. A layer is represented by the class Layer2D or Layer3D depending on the plotting device.

Path

A path is a series of (x,y) points in an array. Under the hood, every method, such as add_line, or add_circle, are wrappers around add_path.

Plotting instrument

Refers to the tool used for creating artwork, such as a pen, marker, paintbrush, etc.

Plotting surface

Refers to the surface on which the plotting instrument is applied, such as paper, wood, etc.

Plotter head

The point where the plotting instrument is attached to the plotting device. When the plotter head is raised, it is not in contact with the plotting surface. When it is lowered, it is ready to plot.

Plotting device

Refers to the device used for plotting G-Code instructions, which can be either a 2D plotter or a 3D printer.

Feed Rate

The movement speed of the plotting device. The feed rate is measured in millimeters per minute (mm/min).

- + \ No newline at end of file diff --git a/docs/documentation/ugs.html b/docs/documentation/ugs.html index a1ea949..7cde686 100644 --- a/docs/documentation/ugs.html +++ b/docs/documentation/ugs.html @@ -4,13 +4,13 @@ Universal G-Code Sender (UGS) | gcode2dplotterart - +

Universal G-Code Sender (UGS)

danger

Do not try anything in this guide until reading it through completely. For more information, check out the FAQ question Why is the plotting device running into the wall?

What is UGS?

UGS is the go to software for interacting with plotting devices. It offers a bunch of helpful tools to get started and can be used to send G-Code instructions and G-Code files to plotting devices.

Setup

Installation

Navigate to the UGS download page to get the software.

Connect the plotting device

Connect screenshot

  1. Open UGS and connect the plotting device to the computer, most likely via USB.
  2. Click the refresh button next to Port.
  3. From the Port drop down menu select the plotting device. Once the plotting device is successfully connected, the Connect or Disconnect button will turn orange.

Finding the plotting device

Device naming may not be intuitive. An easy way to figure out which device is the plotting device is to select a device from the drop down menu, and click the Connect or Disconnect button. Repeat until a successful connection is made. Note that the plotting device might have a different name depending on the USB port it is connected to. For help finding the plotting device, check out the GitHub discussion.

Get the plotting devices's dimensions

danger

Do not try anything in this guide until reading it through completely. For more information, check out the FAQ question Why is the plotting device running into the wall?

  1. Open the jog controller.
  2. Select millimeters for units.
  3. Set the Step size XY to 10, Step size Z to 1, Feed Rate to 1,000. (For information on setting feed rate, check the [Get the plotting devices's feed rate](#get-the-plotting -devicess-feed-rate) section)
  4. Move the plotter head to the most negative X direction (X-) and most negative Y direction (Y-). If the plotter supports limit switches, send the G28 Command to move the plotter head to the limit switch.
  5. For 3D printers only, attach the plotting instrument to the plotter head. (For help with 3D printer setup, check out Convert a 3D printer to 2D plotter guide) Move the plotter head in the Z Direction (Z- or Z+ depending on the 3D printer) until the point of the plotting instrument is a comfortable height (3mm is good) above the plotting surface. You can find a ruler for measuring Z-Height here. Write this number down, it'll be the z_navigation_height variable.
  6. Switch to the Toolbox and Click Reset Zero. This defines the X = 0, Y = 0, and for 3D printers only, the Z = 0 starting point for the plotting device as the plotter head's current location.
  7. Switch back to the Jog Controller. Move the plotter head to the most positive X direction (X+) and most positive Y direction (Y+) it can go. Write these numbers down.
  8. For 3D printers only, move the plotting head so that the plotting instrument's point is comfortably touching the plotting surface. This will most likely be 0. Write this number down, it'll be the z_plotting_height variable.

Reset Zero

danger

Do not try anything in this guide until reading it through completely. For more information, check out the FAQ question Why is the plotting device running into the wall?

This step is not applicable if the plotting device has limit switches and get be reset with a G28 Command.

This step should be performed every time the plotting device is turned on. Additionally, it should be performed any time the plotter head is accidentally given coordinates that are outside of the bounds of the plotting device.

Repeat steps 1 through 6 from the getting the plotting device's dimensions section.

Get the plotting devices's feed rate

danger

Do not try anything in this guide until reading it through completely. For more information, check out the FAQ question Why is the plotting device running into the wall?

  1. Open the jog controller.

  2. Select millimeters for units.

  3. Set the Step size XY to 10, Step size Z to 1, Feed Rate to 1,000. (For information on setting feed rate, check the [Get the plotting devices's feed rate](#get-the-plotting -devicess-feed-rate) section)

  4. Click on the arrows to move the plotter head around the plotting surface.

  5. Raise and lower the Feed Rate until it moves at a speed that results in clean and fast plotting . This may take some experimentation to pick a good number. Write this number down.

Creating Macros

Macros are a way to turn repeated interactions in UGS into clickable buttons. To create a macro, in UGS, click on the Machine menu item and then click Edit macros....

Below are some commonly used macros. Check out the G-Code Overview for more inspiration. To test that macros work, isntructions can be sent by typing them into the console.

2D Plotter Macros

The plotter head can be raised by sending the G-Code command M3 S0 and lowered by sending the code M3 1000.

3D Printer Macros

Follow the Get the plotting devices's dimensions section to get the Z heights for the 3d printer. The instruction for moving the Z Axis looks like G1 Z123. Replace 123 with the recorded values to make macros for raising and lowering the plotting head.

UGS Features

Below are a collection of features referenced in other parts of the documentation including the setup in the next section.

Controller State

Controller screenshot

The controller state contains various details about the plotting device. Of interest are the X position (0.000) and the Y position (10.000). These coordinates will be used with the jog controller to determine the plotting device's coordinates. For 3D printers, the Z position will define the plotter head's lowered and raised positions.

Jog Controller

Jog controller screenshot

The jog controller controls the plotting device in real time. Instructions can be sent to move the plotter head in the X and Y directions. For 3D printers it'll also control the Z direction.

Toolbox

Toolbox screenshot

The toolbox contains several useful features listed below.

  • Reset Zero - This will set the plotter head's current position to X = 0, Y = 0, and optionally Z = 0. All future movements will be made relative to this position.
  • Return to Zero - This will return the plotter head to the position defined by Reset Zero. Do not click this before reading the reset zero section.

Macros

Macros screenshot

Macros are custom G-Code commands. Two examples here include Raise plotter head and Lower plotter head.

caution

These instructions are specific to 2D plotters. Check out the G-Code Docs for other instructions that can be made.

Create macro screenshot

Console

Consoles screenshot

The console is used for sending commands to the plotting device.

- + \ No newline at end of file diff --git a/docs/gallery/2023-06-20_sine_waves.html b/docs/gallery/2023-06-20_sine_waves.html index e700664..5954e71 100644 --- a/docs/gallery/2023-06-20_sine_waves.html +++ b/docs/gallery/2023-06-20_sine_waves.html @@ -4,13 +4,13 @@ 2023-06-20 Sine Waves | gcode2dplotterart - +

2023-06-20 Sine Waves

Description

Series of sine waves plotted with increasing amplitude

Images

example of plotted code

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

import math
from gcode2dplotterart import Plotter2D

magic_mark_eraser = "magic_marker_eraser"
color1 = "color1"
color2 = "color2"

plotter = Plotter2D(
title="Sine Waves",
x_min=0,
x_max=220,
y_min=0,
y_max=150,
feed_rate=10000,
output_directory="./output",
handle_out_of_bounds="Warning",
)

plotter.add_layer(magic_mark_eraser, color="red")
plotter.add_layer(color1, color="green")
plotter.add_layer(color2, color="blue")


def plot_sine_wave(y_offset, amplitude, wavelength):
path = []
scale_up = 10
scale_down = 1 / scale_up

for step in range(plotter.x_min * scale_up, plotter.x_max * scale_up):
x = step * scale_down
y = amplitude * math.sin((2 * math.pi * x) / wavelength)
y += y_offset
path.append((x, y))

return path


OFFSETS = [i for i in range(10, 110, 5)]

for index, i in enumerate(OFFSETS):
path = plot_sine_wave(y_offset=i, amplitude=index / 2, wavelength=40)
plotter.layers[color1].add_path(path)

for index, i in enumerate(reversed(OFFSETS)):
path = plot_sine_wave(y_offset=i, amplitude=index / 2, wavelength=40)
plotter.layers[color2].add_path(path)

for index, i in enumerate(OFFSETS):
path = plot_sine_wave(y_offset=i, amplitude=5, wavelength=80)
plotter.layers[magic_mark_eraser].add_path(path)


plotter.preview()
plotter.save()
- + \ No newline at end of file diff --git a/docs/gallery/2023-07-15_bunch_of_lines.html b/docs/gallery/2023-07-15_bunch_of_lines.html index 1d3b029..f6dd6e5 100644 --- a/docs/gallery/2023-07-15_bunch_of_lines.html +++ b/docs/gallery/2023-07-15_bunch_of_lines.html @@ -4,14 +4,14 @@ 2023-07-15 Bunch of Lines | gcode2dplotterart - +

2023-07-15 Bunch of Lines

Description

Take in a color. Plot a bunch of lines in a grid with a fixed length and a variable slope.

Images

example of plotted code example of plotted code

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from gcode2dplotterart import Plotter2D
from math import sin
from random import randint

plotter = Plotter2D(
title="Bunch of Lines",
x_min=0,
x_max=240,
y_min=0,
y_max=170,
feed_rate=10000,
output_directory="./output",
handle_out_of_bounds="Warning",
)

COLORS = ["purple", "blue", "yellow"]
HYPOTENUSE = 10

for color in COLORS:
plotter.add_layer(title=color, color=color)

for x0 in range(plotter.x_min, plotter.x_max, 10):
for y0 in range(plotter.y_min, plotter.y_max, 10):
slope = sin((x0**2 + y0**2) / (plotter.x_max**2 + plotter.y_max**2))

delta_x = HYPOTENUSE * (1 / (1 + slope**2)) ** 0.5
delta_y = delta_x * slope

x1 = x0 - delta_x
y1 = y0 - delta_y

x2 = x0 + delta_x
y2 = y0 + delta_y

rand = randint(0, len(COLORS))
if rand == len(COLORS):
# Every so often don't plot a line. For Art.
continue

plotter.layers[COLORS[rand]].add_line(x1, y1, x2, y2)


plotter.preview()
plotter.save()

- + \ No newline at end of file diff --git a/docs/gallery/2023-10-05_roaming_rectangles.html b/docs/gallery/2023-10-05_roaming_rectangles.html index fd0dab5..bf8bb26 100644 --- a/docs/gallery/2023-10-05_roaming_rectangles.html +++ b/docs/gallery/2023-10-05_roaming_rectangles.html @@ -4,14 +4,14 @@ 2023-10-05 Roaming Rectangles | gcode2dplotterart - +

2023-10-05 Roaming Rectangles

Description

Series of rectangles where one is connected to the next one, of varying colors and sizes.

Images

example of plotted code example of plotted code

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from random import randint, choice
from gcode2dplotterart import Plotter2D

BLACK_LAYER = 'thin_black'
COLOR_LAYERS=['thick_red', 'thick_yellow', 'thick_blue']

plotter = Plotter2D(
title="Roaming Rectangles",
x_min = 0,
x_max = 220,
y_min = -150, # Note - My plotting goes from -150 to 0.
y_max = 0,
feed_rate=10000,
output_directory="./output",
handle_out_of_bounds='Warning' # Some points will be out of bounds for this, that's ok.
)

for layer in COLOR_LAYERS:
plotter.add_layer(layer)
plotter.add_layer(BLACK_LAYER)

# Min movement is 10% of the total width/height
MIN_MOVEMENT_X = round((plotter.x_max - plotter.x_min) * 0.10)
MIN_MOVEMENT_Y = round((plotter.y_max - plotter.y_min) * 0.10)

# Max movement is 20% of the total width/height
MAX_MOVEMENT_X = round((plotter.x_max - plotter.x_min) * 0.20)
MAX_MOVEMENT_Y = round((plotter.y_max - plotter.y_min) * 0.20)

def calculate_next_move(start_point, end_point):
# Don't start the next rectangle where the current rectangle started, don't include (start_point[0], start_point[1])
next_point_start_options = [
(start_point[0], end_point[1]),
(end_point[0], start_point[1]),
(end_point[0], end_point[1]),
]

next_point_start = choice(next_point_start_options)

# Future improvement - Could optimize here so that choice([-1,1]) factors in the previous rectangle and doesn't plot on top.
movement_x = randint(MIN_MOVEMENT_X, MAX_MOVEMENT_X) * choice([-1, 1])
movement_y = randint(MIN_MOVEMENT_Y, MAX_MOVEMENT_Y) * choice([-1, 1])

next_point_end = (next_point_start[0] + movement_x, next_point_start[1] + movement_y)

return [next_point_start, next_point_end]

# Starting rectangle is a rectangle plotted around the center point.
CENTER_POINT = [(plotter.x_max + plotter.x_min) / 2, (plotter.y_max + plotter.y_min) / 2]
start_point = [CENTER_POINT[0] - plotter.width * 0.05, CENTER_POINT[1] - plotter.height * 0.05]
end_point = [CENTER_POINT[0] + (plotter.x_max - plotter.x_min) * 0.05, CENTER_POINT[1] + (plotter.y_max - plotter.y_min) * 0.05 ]

current_layer_index = 0
plotter.layers[COLOR_LAYERS[current_layer_index]].add_rectangle(start_point[0], start_point[1], end_point[0], end_point[1])
plotter.layers[BLACK_LAYER].add_rectangle(start_point[0], start_point[1], end_point[0], end_point[1])

TOTAL_RECTANGLES = 100
current_rectangle_count = 0

while True:
# With 3 colors being plotted, the next rectangle plotted should not be the same color as the previous.
current_layer_index_choices = [index for [index, value] in enumerate(COLOR_LAYERS) if index != current_layer_index]
current_layer_index = choice(current_layer_index_choices)

while True:
# Lazy solution to prevent plotter from going out of bounds
[next_start_point, next_end_point] = calculate_next_move(start_point, end_point)
if plotter.is_point_in_bounds(next_start_point[0], next_start_point[1]) and plotter.is_point_in_bounds(next_end_point[0], next_end_point[1]):
break

plotter.layers[COLOR_LAYERS[current_layer_index]].add_rectangle(next_start_point[0], next_start_point[1], next_end_point[0], next_end_point[1])
plotter.layers[BLACK_LAYER].add_rectangle(next_start_point[0], next_start_point[1], next_end_point[0], next_end_point[1])
start_point = next_start_point
end_point = next_end_point

current_rectangle_count += 1
if current_rectangle_count == TOTAL_RECTANGLES:
break

plotter.preview()
plotter.save()
- + \ No newline at end of file diff --git a/docs/gallery/2023-11-15_image_lines.html b/docs/gallery/2023-11-15_image_lines.html index 1d5137a..74c59f3 100644 --- a/docs/gallery/2023-11-15_image_lines.html +++ b/docs/gallery/2023-11-15_image_lines.html @@ -4,14 +4,14 @@ 2023-11-15 Image Lines | gcode2dplotterart - +

2023-11-15 Image Lines

Description

Convert an image into a series of parallel lines where each line is one of N colors.

Images

example of plotted code example of plotted code

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from gcode2dplotterart import Plotter2D
import cv2
import numpy as np
from imutils import resize
from math import floor
from typing import List

"""
Preface - numpy and cv2 are still a bit alien to me. The code here could be done better.

1. Take in an image.
2. Grayscale all of the pixels so that each pixel is represented by a number from 0 to 255.
3. Bucket the pixels such that
- 0 -> A becomes 0
- A -> B becomes 1
- B -> C becomes 2
- C -> 255 becomes 3
4. Start with the first row of pixels.
5. Add the first point to a new path and move to the next pixel.
6. If the current pixel is the same as the previous pixel, append the point to the path and repeat, otherwise,
start a new path.
7. Continue until all points of all colors are plotted.

Note
- Make sure that the combination of X_SCALE, Y_SCALE, and the resized image aren't too big for the plotter area.

"""

# These numbers can be changed in combination with the image size. Adds a bit of spacing since I use thicker
# pens and they'd overlap.
X_PIXELS_PER_PLOTTER_UNIT = 1 / 3
Y_PIXELS_PER_PLOTTER_UNIT = 1 / 3


def evenly_distribute_pixels_per_color(
img: cv2.typing.MatLike, n: int
) -> List[List[int]]:
"""
Ensures that each color has the same number of pixels.

Arg:
`img` : cv2.typing.MatLike
The image to process
`n` : Number of colors to distribute pixels into

Returns
` img` : List[List[int]]
Image mapped to n colors
"""

total_pixels = img.size
pixel_bins = []
histogram, bins = np.histogram(img.ravel(), 256, (0, 256))
count = 0
for pixel_value, pixel_count in enumerate(histogram):
if count >= total_pixels / (n):
count = 0
pixel_bins.append(pixel_value)
count += pixel_count

return np.subtract(np.digitize(img, pixel_bins), 0)


def resize_image_for_plotter(filename: str) -> List[List[int]]:
img = cv2.imread(filename)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# The following math will ensure that the image is scaled to the plotter size and the remaining math
# throughout the program will work.
plotter_ratio = "landscape" if plotter.width > plotter.height else "portrait"
# It appears shape is (columns, rows)
img_ratio = "landscape" if img.shape[1] > img.shape[0] else "portrait"
if (
plotter_ratio == "landscape"
and img_ratio == "landscape"
or plotter_ratio == "portrait"
and img_ratio == "landscape"
):
img = resize(img, width=floor(plotter.width * X_PIXELS_PER_PLOTTER_UNIT))
elif (
plotter_ratio == "portrait"
and img_ratio == "portrait"
or plotter_ratio == "landscape"
and img_ratio == "portrait"
):
print("resizing height")
img = resize(img, height=floor(plotter.height * Y_PIXELS_PER_PLOTTER_UNIT))

print("resized to ", img.shape)
return img


plotter = Plotter2D(
title="Horizontal Line Art",
x_min=0,
x_max=200,
y_min=-140, # Note - My plotting goes from -150 to 0.
y_max=0,
feed_rate=10000,
output_directory="./output",
handle_out_of_bounds="Warning", # It appears that some points end up outside of bounds so scale down.
)

COLOR_LAYERS = [
"purple",
"blue",
"yellow",
"orange",
"red",
]
for layer in COLOR_LAYERS:
plotter.add_layer(layer, color=layer)

input_filename = "landscape.jpg"

# Works with color PNGs exported from Lightroom and Photoshop. Could learn some more about reading images
resized_image = resize_image_for_plotter(input_filename)
color_reduced_image = evenly_distribute_pixels_per_color(
resized_image, n=len(COLOR_LAYERS)
)


for y_index, row in enumerate(color_reduced_image):
y_plotter_scale = (
y_index / Y_PIXELS_PER_PLOTTER_UNIT * -1
) # My plotter goes y=-150 to y=0, therefore numbers are negative. Probably a better solution.
line_start = [0, y_plotter_scale]
line_end = None
current_color_value = color_reduced_image[0][y_index]

for x_index, color_value in enumerate(row):
x_plotter_scale = x_index / X_PIXELS_PER_PLOTTER_UNIT
if color_value == current_color_value:
continue

line_end = [x_plotter_scale, y_plotter_scale]
plotter.layers[COLOR_LAYERS[current_color_value]].add_line(
line_start[0], line_start[1], line_end[0], line_end[1]
)

line_start = line_end

current_color_value = color_value
line_end = [x_plotter_scale, y_plotter_scale]
plotter.layers[COLOR_LAYERS[current_color_value]].add_line(
line_start[0], line_start[1], line_end[0], line_end[1]
)

plotter.preview()
plotter.save()

- + \ No newline at end of file diff --git a/docs/gallery/2023-11-19_wandering_lines.html b/docs/gallery/2023-11-19_wandering_lines.html index d6bcd1d..f768e8a 100644 --- a/docs/gallery/2023-11-19_wandering_lines.html +++ b/docs/gallery/2023-11-19_wandering_lines.html @@ -4,14 +4,14 @@ 2023-11-19 Wandering Lines | gcode2dplotterart - +

2023-11-19 Wandering Lines

Description

Convert an image into a series of wandering lines where each wandering line is one of N colors.

Images

example of plotted code example of plotted code

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from gcode2dplotterart import Plotter2D
import cv2
import numpy as np
from imutils import resize
from math import floor
from typing import List, Tuple
from random import shuffle
from collections import namedtuple
from sklearn.cluster import KMeans


def hex_to_rgb(hex: str):
hex = hex.lstrip("#")
return tuple(int(hex[i : i + 2], 16) for i in (0, 2, 4))


Point = namedtuple("Point", ["row", "col"])

"""
Preface - numpy and cv2 are still a bit alien to me. The code here could be done better.
1. Take in an image.
2. Grayscale all of the pixels so that each pixel is represented by a number from 0 to 255.
3. Bucket the pixels such that
- 0 -> A becomes 0
- A -> B becomes 1
- B -> C becomes 2
- C -> 255 becomes 3
4. Start with a random pixel in the image that hasn't been plotted.
5. Add this point to the path
6. Look to the neighboring pixels
- If a neighboring pixel, order randomly chosen, is of the same color, add it to the path.
- If the neighboring pixel hasn't been added to a path before, repeat step 6.
- If the neighboring pixel has been added to a path before, repeat step 4.
- If there are no neighboring pixels of the same color, repeat step 4.
7. Continue until all points of all colors are plotted.
Note
- Make sure that the combination of X_SCALE, Y_SCALE, and the resized image aren't too big for the plotter area.
"""

# These numbers can be changed in combination with the image size. Adds a bit of spacing since I use thicker
# pens and they'd overlap.
X_PIXELS_PER_PLOTTER_UNIT = 1 / 4
Y_PIXELS_PER_PLOTTER_UNIT = 1 / 4


# This function does not seem to bucket into each color.
def evenly_distribute_pixels_per_nth_percent_of_grayscale_range(
img: cv2.typing.MatLike, n: int
) -> List[List[int]]:
"""
Take the range of grayscale (0 -> 255) and bucket it such that n% of the range is a bucket.
Arg:
`img` : cv2.typing.MatLike
The image to process
`n` : Number of colors to distribute pixels into
Returns
` img` : List[List[int]]
Image mapped to n colors
"""
bucket_segments = np.linspace(
0, 256, n + 1
) # Use linspace to create evenly spaced buckets
grayscale_buckets = np.digitize(img, bucket_segments[:-1]) - 1

print(grayscale_buckets)

max_val = np.amax(grayscale_buckets)
print(max_val)

min_val = np.amin(grayscale_buckets)
print(min_val)

return grayscale_buckets


# Something is off here. Might not be the kmeans algo, but something is.
def kmeans_color_reduction(img: cv2.typing.MatLike, n: int) -> List[List[int]]:
colors = [hex_to_rgb(color_layer["color"]) for color_layer in COLOR_LAYERS]
h, w = img.shape
image_reshaped = img.reshape((h * w, 1))

# Use KMeans to find the closest colors
kmeans = KMeans(n_clusters=len(colors), random_state=0, n_init=10)
kmeans.fit(image_reshaped)
labels = kmeans.predict(image_reshaped)

# Replace each pixel with its closest color index
quantized_image_array = labels.reshape((h, w))

return quantized_image_array


def evenly_distribute_pixels_per_color(
img: cv2.typing.MatLike, n: int
) -> List[List[int]]:
"""
Ensures that each color has the same number of pixels.
Arg:
`img` : cv2.typing.MatLike
The image to process
`n` : Number of colors to distribute pixels into
Returns
` img` : List[List[int]]
Image mapped to n colors
"""

total_pixels = img.size
pixel_bins = []
histogram, bins = np.histogram(img.ravel(), 256, (0, 256))
count = 0
for pixel_value, pixel_count in enumerate(histogram):
if count >= total_pixels / (n):
count = 0
pixel_bins.append(pixel_value)
count += pixel_count

return np.subtract(np.digitize(img, pixel_bins), 0)


def resize_image_for_plotter(filename: str) -> List[List[int]]:
img = cv2.imread(filename)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# The following math will ensure that the image is scaled to the plotter size and the remaining math
# throughout the program will work.
plotter_ratio = "landscape" if plotter.width > plotter.height else "portrait"
# It appears shape is (columns, rows)
img_ratio = "landscape" if img.shape[1] > img.shape[0] else "portrait"
if (
plotter_ratio == "landscape"
and img_ratio == "landscape"
or plotter_ratio == "portrait"
and img_ratio == "landscape"
):
img = resize(img, width=floor(plotter.width * X_PIXELS_PER_PLOTTER_UNIT))
elif (
plotter_ratio == "portrait"
and img_ratio == "portrait"
or plotter_ratio == "landscape"
and img_ratio == "portrait"
):
print("resizing height")
img = resize(img, height=floor(plotter.height * Y_PIXELS_PER_PLOTTER_UNIT))

print("resized to ", img.shape)
return img


plotter = Plotter2D(
title="Horizontal Line Art",
x_min=0,
x_max=240,
y_min=0,
y_max=170,
feed_rate=10000,
output_directory="./output",
handle_out_of_bounds="Warning", # It appears that some points end up outside of bounds so scale down.
)

# Note to self - I'm currently using the K-Means Algorithm to handle the colors.
# The `color` key below should match the original image's colors to result in successful clustering.
# The title is the type of marker used to plot that.
COLOR_LAYERS = [
{"color": "#ff0000", "title": "Red"},
{"color": "#ff8000", "title": "Orange"},
{"color": "#ffff00", "title": "Yellow"},
{"color": "#00ff00", "title": "Green"},
{"color": "#0000ff", "title": "Blue"},
{"color": "#8000ff", "title": "Purple"},
]
for index, layer in enumerate(COLOR_LAYERS):
plotter.add_layer(layer["title"], color=layer["color"], line_width=3)

input_filename = "test.jpg"

# Works with color PNGs exported from Lightroom and Photoshop. Could learn some more about reading images
resized_image = resize_image_for_plotter(input_filename)
color_reduced_image = kmeans_color_reduction(resized_image, n=len(COLOR_LAYERS))


def get_neighboring_points(row_index: int, col_index: int) -> List[Tuple[int, int]]:
"""
Gets the neighboring points of a given point. The neighboring points are shuffled so that the order
in which they are checked is random. Will not return points outside of teh plotter bounds.
Args:
`row_index` : int
The row index of the point
`col_index` : int
The column index of the point
Returns:
`neighboring_points` : List[Tuple[int, int]]
The neighboring points, in the format of `(row_index, col_index)`.
"""

total_rows, total_cols = color_reduced_image.shape

neighboring_points = []
for row in range(-1, 2):
for col in range(-1, 2):
if row == 0 and col == 0:
continue

if (
row_index + row < 0
or row_index + row + 1 >= total_rows
or col_index + col < 0
or col_index + col + 1 >= total_cols
):
continue

neighboring_points.append(Point(row_index + row, col_index + col))
shuffle(neighboring_points)
return neighboring_points


def add_path_to_plotter(path: List[Point], color: str):
"""
The plotter expects the points to be in the format of (x, y) and the image is in the format of (row, col)
Additionally the image was scaled down to get less lines, now we need to scale it back up to take the total
space of the plotter.
"""
scaled_path = [
(col / Y_PIXELS_PER_PLOTTER_UNIT, row / X_PIXELS_PER_PLOTTER_UNIT)
for row, col in path
]
plotter.layers[COLOR_LAYERS[current_color_index]["title"]].add_path(scaled_path)


remaining_points_to_process = set()
for row_index, row in enumerate(color_reduced_image):
for col_index, value in enumerate(row):
remaining_points_to_process.add(Point(row_index, col_index))

current_point = remaining_points_to_process.pop()
current_path = [current_point]
current_color_index = color_reduced_image[current_point.row][current_point.col]

while len(remaining_points_to_process) > 0:
potential_next_points = get_neighboring_points(
row_index=current_point.row, col_index=current_point.col
)

for potential_next_point in potential_next_points:
current_point = None

if (
color_reduced_image[potential_next_point.row][potential_next_point.col]
== current_color_index
):
current_point = potential_next_point

if current_point in remaining_points_to_process:
# Prefer a point that hasn't been added to a path. This should make paths, on average, longer.
break

if current_point is None:
add_path_to_plotter(current_path, current_color_index)
current_point = remaining_points_to_process.pop()
current_path = [current_point]
current_color_index = color_reduced_image[current_point.row][current_point.col]
continue

if current_point and current_point not in remaining_points_to_process:
current_path.append(current_point)
add_path_to_plotter(current_path, current_color_index)
current_point = remaining_points_to_process.pop()
current_path = [current_point]
current_color_index = color_reduced_image[current_point.row][current_point.col]
continue

if current_point and current_point in remaining_points_to_process:
current_path.append(current_point)
remaining_points_to_process.remove(current_point)
continue

plotter.preview()
plotter.save()
- + \ No newline at end of file diff --git a/docs/gallery/2023-11-24_concentric_circles.html b/docs/gallery/2023-11-24_concentric_circles.html index 184e428..df396b8 100644 --- a/docs/gallery/2023-11-24_concentric_circles.html +++ b/docs/gallery/2023-11-24_concentric_circles.html @@ -4,13 +4,13 @@ 2023-11-24 Concentric Circles | gcode2dplotterart - +

2023-11-24 Concentric Circles

Description

Plot circles in a 2D grid where each row contains a donut of a single color and each column contains a circle enclosed in the donut of a single color. The result is a grid containing the unique combination of every color pair.

Images

example of plotted code

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from gcode2dplotterart import Plotter2D

LINE_WIDTH = 1.0

COLORS = [
{"title": "1_red1", "color": "#FF4141"},
{"title": "2_orange1", "color": "#FF7700"},
{"title": "3_yellow1", "color": "#FFDB11"},
{"title": "4_green1", "color": "#A9FF00"},
{"title": "5_green2", "color": "#00E350"},
{"title": "6_blue1", "color": "#A2FFF8"},
{"title": "7_blue2", "color": "#0024FF"},
{"title": "8_blue3", "color": "#5D9DB4"},
{"title": "9_pink1", "color": "#FF91D2"},
{"title": "a_pink2", "color": "#FF0096"},
{"title": "b_purple1", "color": "#EABEFF"},
{"title": "c_purple2", "color": "#AD00FF"},
{"title": "d_grey1", "color": "#E9E9E9"},
{"title": "e_grey2", "color": "#B1B1B1"},
{"title": "f_grey3", "color": "#1E1E1E"},
]

# Create a plotter object
plotter = Plotter2D(
title="Circles",
x_min=0,
x_max=290,
y_min=0,
y_max=210,
feed_rate=10000,
)

for color in COLORS:
for ground in ["foreground", "background"]:
plotter.add_layer(
title=f"{ground}_{color['title']}",
color=color["color"],
line_width=LINE_WIDTH,
)

PLOTTER_CONSTRAINT = min(plotter.width, plotter.height)

bounding_box = PLOTTER_CONSTRAINT / len(COLORS)
radius = (bounding_box / 2) - 2 # Add a little padding

INNER_CIRCLE_RADIUS = radius * 0.5

print

for foreground_index in range(len(COLORS)):
for background_index in range(len(COLORS)):
# Experimenting below with adding fill.

# offset centers so they're not on edge of plotter
foreground_offset = foreground_index + 0.5
background_offset = background_index + 0.5

remaining_radius = radius
print(remaining_radius)
while remaining_radius > INNER_CIRCLE_RADIUS:
plotter.layers[
"background_" + COLORS[background_index]["title"]
].add_circle(
x_center=bounding_box * foreground_offset,
y_center=bounding_box * background_offset,
radius=remaining_radius,
)
remaining_radius -= LINE_WIDTH

while remaining_radius > 0:
layer = (
"foreground_" + COLORS[foreground_index]["title"]
if foreground_index != background_index
else "background_" + COLORS[background_index]["title"]
)
plotter.layers[layer].add_circle(
x_center=bounding_box * foreground_offset,
y_center=bounding_box * background_offset,
radius=remaining_radius,
)
remaining_radius -= LINE_WIDTH
print(remaining_radius)

plotter.preview()

plotter.save(include_layer_number=False)
- + \ No newline at end of file diff --git a/docs/gallery/2023-11-24_josef_albers_homage.html b/docs/gallery/2023-11-24_josef_albers_homage.html index a27a426..ca40bef 100644 --- a/docs/gallery/2023-11-24_josef_albers_homage.html +++ b/docs/gallery/2023-11-24_josef_albers_homage.html @@ -4,14 +4,14 @@ 2023-11-24 Josef Albers Homage | gcode2dplotterart - +

2023-11-24 Josef Albers Homage

Description

An homage to Josef Albers

Images

example of plotted code example of plotted code

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from gcode2dplotterart import Plotter2D
from random import randrange, shuffle
import math
from typing import Dict, List, Union
import time

LINE_WIDTH = 2.5

COLORS = [
{"title": "blue1", "color": "#A2FFF8"},
{"title": "pink2", "color": "#FF0096"},
{"title": "purple2", "color": "#AD00FF"},
{"title": "grey1", "color": "#E9E9E9"},
]

plotter = Plotter2D(
title="Josef Albers Homage",
x_min=0,
x_max=200,
y_min=0,
y_max=200,
feed_rate=10000,
)

shuffle(COLORS)
color_choices = COLORS[0:4]

for color in color_choices:
plotter.add_layer(
title=color["title"],
color=color["color"],
line_width=LINE_WIDTH,
)

SIDE_PADDING = int(plotter.width * 0.2)

x_center = (plotter.x_max - plotter.x_min) / 2
y_center = randrange(
int(plotter.y_min) + SIDE_PADDING, int(plotter.y_max) - SIDE_PADDING
)

vertical_angle = math.degrees(math.atan(int(plotter.width / 2) / (y_center)))

square_side_length_percentages = [0.4, 0.7, 0.9, 1]

square_side_lengths = [
int(plotter.width * percentage) for percentage in square_side_length_percentages
]
sorted(square_side_lengths)

current_side_length = LINE_WIDTH
for index, color in enumerate(color_choices):
threshold_side_length = square_side_lengths[index]

while current_side_length < threshold_side_length:
x_left_of_center = current_side_length / 2
y_below_center = x_left_of_center / math.tan(math.radians(vertical_angle))

x_start = x_center - x_left_of_center
y_start = y_center - y_below_center

x_end = x_start + current_side_length
y_end = y_start + current_side_length

plotter.layers[color["title"]].add_rectangle(
x_start=x_start,
y_start=y_start,
x_end=x_end,
y_end=y_end,
)

current_side_length += LINE_WIDTH

plotter.preview()
plotter.save()
- + \ No newline at end of file diff --git a/docs/gallery/2023-11-25_Bubbles.html b/docs/gallery/2023-11-25_Bubbles.html index 1a6bf74..ac03c71 100644 --- a/docs/gallery/2023-11-25_Bubbles.html +++ b/docs/gallery/2023-11-25_Bubbles.html @@ -4,14 +4,14 @@ 2023-11-25 Bubbles | gcode2dplotterart - +

2023-11-25 Bubbles

Description

Generate a circle with a randomly chosen center point, color, and radius. Continuously decrease the radius at a random rate, with a random color, and maintain the same center point to plot successive circles until the radius becomes zero. After reaching a point, start the process again.

Images

example of plotted code example of plotted code

Timelapses

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from gcode2dplotterart import Plotter2D
from random import randrange, choice, randint

LINE_WIDTH = 1.0

COLORS = [
{"title": "purple1", "color": "#EABEFF"},
{"title": "blue1", "color": "#A2FFF8"},
{"title": "orange1", "color": "#FF7700"},
{"title": "purple2", "color": "#AD00FF"},
{"title": "grey1", "color": "#E9E9E9"},
]

# Create a plotter object
plotter = Plotter2D(
title="Bubbles",
x_min=0,
x_max=260,
y_min=0,
y_max=200,
feed_rate=10000,
)

for color in COLORS:
plotter.add_layer(
title=color["title"],
color=color["color"],
line_width=LINE_WIDTH,
)


counter = 0
STARTING_CIRCLES = 500

while counter < STARTING_CIRCLES:
x_center = randrange(int(plotter.x_min), int(plotter.x_max))
y_center = randrange(int(plotter.y_min), int(plotter.y_max))

min_distance_to_edge = min(
abs(x_center - plotter.x_min),
abs(plotter.x_max - x_center),
abs(y_center - plotter.y_min),
abs(plotter.y_max - y_center),
)

remaining_radius = min(randint(0, 20), min_distance_to_edge)

while remaining_radius > 0:
color = choice(COLORS)
layer = color["title"]
plotter.layers[layer].add_circle(
x_center,
y_center,
radius=remaining_radius,
)
remaining_radius -= randint(int(LINE_WIDTH), int(remaining_radius))
counter += 1


plotter.preview()

plotter.save()

- + \ No newline at end of file diff --git a/docs/gallery/2023-11-28_josef_albers_recursive_homage.html b/docs/gallery/2023-11-28_josef_albers_recursive_homage.html index 464124a..dee043a 100644 --- a/docs/gallery/2023-11-28_josef_albers_recursive_homage.html +++ b/docs/gallery/2023-11-28_josef_albers_recursive_homage.html @@ -4,13 +4,13 @@ 2023-11-28 Josef Albers Recursive Homage | gcode2dplotterart - +

2023-11-28 Josef Albers Recursive Homage

Description

An homage to Josef Albers, recursively.

Images

example of plotted code

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from gcode2dplotterart import Plotter2D
from random import randrange, shuffle
import math


LINE_WIDTH = 2.5

COLORS = [
{"title": "color1", "color": "darkslategrey"},
{"title": "color2", "color": "silver"},
{"title": "color3", "color": "cornsilk"},
{"title": "color4", "color": "tan"},
]

plotter = Plotter2D(
title="Josef Albers Homage",
x_min=0,
x_max=250,
y_min=0,
y_max=180,
feed_rate=10000,
)

DONT_PLOT_LAYER = {
"title": "DONT PLOT",
"color": "#FFFFFF",
"line_width": LINE_WIDTH,
}
plotter.add_layer(**DONT_PLOT_LAYER)

for color in COLORS:
plotter.add_layer(
title=color["title"],
color=color["color"],
line_width=LINE_WIDTH,
)

# Should be less than 100 so that the last square can be one later on.
SIDE_LENGTH_PERCENTAGE_CHOICES = [i / 100 for i in range(10, 99, 20)]


def josef_albers(x_min: float, y_min: float, side_length: float):
colors = COLORS.copy()

# Append some number of white layers.
for i in range(randrange(0, 3)):
colors.append(DONT_PLOT_LAYER)

shuffle(colors)

side_padding = LINE_WIDTH * 5
x_center = x_min + side_length / 2
y_center = randrange(
int(y_min + side_padding), int(y_min + side_length - side_padding)
)

vertical_angle = math.degrees(math.atan(int(side_length / 2) / (y_center - y_min)))

shuffle(SIDE_LENGTH_PERCENTAGE_CHOICES)
square_side_length_percentages = SIDE_LENGTH_PERCENTAGE_CHOICES[: len(colors) - 1]
square_side_length_percentages.append(1)

square_side_lengths = sorted(
[int(side_length * percentage) for percentage in square_side_length_percentages]
)

current_side_length = LINE_WIDTH
for index, color in enumerate(colors):
threshold_side_length = square_side_lengths[index]

while current_side_length < threshold_side_length:
x_left_of_center = current_side_length / 2
y_below_center = x_left_of_center / math.tan(math.radians(vertical_angle))
x_start = x_center - x_left_of_center
y_start = y_center - y_below_center

x_end = x_start + current_side_length
y_end = y_start + current_side_length

plotter.layers[color["title"]].add_rectangle(
x_start=x_start,
y_start=y_start,
x_end=x_end,
y_end=y_end,
)

current_side_length += LINE_WIDTH


SIDE_LENGTH = 50

for x in range(0, plotter.width - SIDE_LENGTH, SIDE_LENGTH + 5):
for y in range(0, plotter.height - SIDE_LENGTH, SIDE_LENGTH + 5):
josef_albers(x, y, SIDE_LENGTH)

plotter.preview()
plotter.save()
- + \ No newline at end of file diff --git a/docs/gallery/2023-12-09_bayer_patterns_cmyk.html b/docs/gallery/2023-12-09_bayer_patterns_cmyk.html index 911cabd..d38810c 100644 --- a/docs/gallery/2023-12-09_bayer_patterns_cmyk.html +++ b/docs/gallery/2023-12-09_bayer_patterns_cmyk.html @@ -4,13 +4,13 @@ 2023-12-09 Bayer Pattern CMYK | gcode2dplotterart - +

2023-12-09 Bayer Pattern CMYK

Description

An exploration into making bayer filters but with CMYK instead of RGB.

Images

example of plotted code

Inputs

preview screenshot

Plotter Preview

preview screenshot

Code

danger

This code may or may not run and is intended more as a reference. Additionally, it was most likely not written with the latest version of the library. To ensure compatibility, check the date of this post against the version history and install the corresponding version.

from gcode2dplotterart import Plotter2D
import cv2
from typing import List, Tuple
from random import shuffle
import time
import math
import imutils
import numpy as np
from scipy.cluster.vq import kmeans, vq

plotter = Plotter2D(
title="CMYK Bayer Patterns",
x_max=160,
x_min=0,
y_max=160,
y_min=0,
feed_rate=10000,
include_comments=False,
)

LINE_WIDTH = 2.5 # mm

CYAN_LAYER = "cyan"
MAGENTA_LAYER = "magenta"
YELLOW_LAYER = "yellow"
BLACK_LAYER = "black"
WHITE_LAYER = "white"


def bgr_to_cmyk(bgr_color):
b, g, r = [x / 255.0 for x in bgr_color]

c = 1 - r
m = 1 - g
y = 1 - b

k = min(c, m, y)

c = (c - k) / (1 - k) if 1 - k != 0 else 0
m = (m - k) / (1 - k) if 1 - k != 0 else 0
y = (y - k) / (1 - k) if 1 - k != 0 else 0

c, m, y, k = [round(x * 100) for x in (c, m, y, k)]

return c, m, y, k


LAYERS = [CYAN_LAYER, MAGENTA_LAYER, YELLOW_LAYER, BLACK_LAYER, WHITE_LAYER]

for layer in LAYERS:
plotter.add_layer(title=layer, color=layer, line_width=LINE_WIDTH)


def kmeans_algorithm(pixels, k=1):
# Convert the pixel array to a NumPy array
pixel_array_np = np.array(pixels)

# Flatten the array to 1D for kmeans
flattened_array = pixel_array_np.reshape(-1, 4)

centroids, _ = kmeans(flattened_array.astype(float), k)

# Assign each pixel to the nearest centroid
labels, _ = vq(flattened_array, centroids)

# Calculate the average CMYK for each cluster
average_cmyk_colors = [
tuple(np.mean(flattened_array[labels == i], axis=0).astype(int))
for i in range(k)
]

return average_cmyk_colors[0]


def sample_img_and_get_cmyk_ratio(sample_square, pixels_per_sample_side):
if sample_square.shape != (pixels_per_sample_side, pixels_per_sample_side, 4):
raise ValueError(
f"Input sample must be a {pixels_per_sample_side}x{pixels_per_sample_side} square with 4 color channels."
)

cmyk_value = kmeans_algorithm(sample_square, k=3)

# This ratio calculation might be incorrect.
cmyk_ratios = [value / sum(cmyk_value) * 100 for value in cmyk_value]
return {
CYAN_LAYER: cmyk_ratios[0],
MAGENTA_LAYER: cmyk_ratios[1],
YELLOW_LAYER: cmyk_ratios[2],
BLACK_LAYER: cmyk_ratios[3],
}


def read_and_prep_image(
filename: str,
pixels_per_sample_side: int,
resize_percent: float = 1.0,
) -> List[List[Tuple[int, int, int]]]:
"""
Resize the image such that the side lengths are divisible by the number of pixels that will be used to represent each dot.(pixels_per_sample)

Params:
filename: The name of the image file to read in.
pixels_per_sample_side: the number of pixels, along a side to sample. Used to round the image size such that samples per side becomes an int
resize_percent: Whether or not to resize the image. If true, the image will be resized to the specified percentage of the original image size.
"""

img = cv2.imread(filename) # Reads in image as BGR
print("original image shape", img.shape)
if pixels_per_sample_side > img.shape[0] or pixels_per_sample_side > img.shape[1]:
raise ValueError("pixels_per_sample_side must be less than the image size")

img = imutils.resize(img, width=int(img.shape[1] * resize_percent))

rounded_width = int(
math.floor(img.shape[0] / pixels_per_sample_side) * pixels_per_sample_side
)
rounded_height = int(
math.floor(img.shape[1] / pixels_per_sample_side) * pixels_per_sample_side
)

# Resize expects width, height unlike in just about every other place.
img = cv2.resize(img, (rounded_width, rounded_height))
print("rounded image shape", img.shape)

img = np.apply_along_axis(bgr_to_cmyk, 2, img)
print("converted bgr to cmyk")
return img


def image_to_cmyk_color_ratios(
img: List[List[Tuple[int, int, int]]], pixels_per_sample_side: int
):
"""
Take in an image, and return a 2D list of colors to plot as output.

Params:
img: The image to process
pixels_per_sample_side: the number of pixels, along a side to sample. The number of pixels would then be pixels_per_sample_side**2.
"""

output = []

for starting_col in range(0, len(img[0]), pixels_per_sample_side):
output_row = []
for starting_row in range(0, len(img), pixels_per_sample_side):
img_section = img[
starting_row : starting_row + pixels_per_sample_side,
starting_col : starting_col + pixels_per_sample_side,
]
cmyk_ratio = sample_img_and_get_cmyk_ratio(
img_section, pixels_per_sample_side
)
output_row.append(cmyk_ratio)
# Plotting is done from the bottom left corner, so we need to reverse the order of the rows.
output.append(output_row)

return output


def plot_points_per_cmyk_ratio(
cmyk_ratio,
x_start_mm,
y_start_mm,
mm_per_sample_side,
points_per_sample_side,
):
points = []
for color, percentage in cmyk_ratio.items():
num_points = int(percentage / 100 * points_per_sample_side**2)
for i in range(num_points):
points.append(color)

# Need to handle the situation where the number of points is less than the number of points per sample side.
# One such way this occurs is if the points_per_sample_side is odd.
filtered_dict = {k: v for k, v in cmyk_ratio.items() if v != 0}
# Sort the dictionary by values in descending order
cmyk_ratio_keys = list(
dict(
sorted(filtered_dict.items(), key=lambda item: item[1], reverse=True)
).keys()
)

current_index = 0
while len(points) < points_per_sample_side**2:
# White is the only color where all the percentages are 0.
if len(cmyk_ratio_keys) == 0:
points.append("white")
continue

points.append(cmyk_ratio_keys[current_index % len(cmyk_ratio_keys)])
current_index += 1

shuffle(points)

x_spacing = mm_per_sample_side / points_per_sample_side
y_spacing = mm_per_sample_side / points_per_sample_side

for i in range(points_per_sample_side):
for j in range(points_per_sample_side):
x = x_start_mm + i * x_spacing
y = y_start_mm + j * y_spacing
plot_color = points.pop()

plotter.layers[plot_color].add_point(
x=x,
y=y,
)


def main():
filename = "./desktop.png"

# The number of pixels to sample along a side of the image. If an image is 100x100, and pixels_per_sample_side is 10, then there will be 10x10 samples.
pixels_per_sample_side = 15

# The number of points to plot per sample. If points_per_sample_side is 2, then there will be 4 points per sample.
# The higher the number of points per sample, the more accurate the color will be, but the longer it will take to plot.
points_per_sample_side = 2

# Resize image to render faster, useful for testing
resize_percent = 1

# ========================================================================================================
# Don't modify anything below these lines
# ========================================================================================================

start_time = time.time()

# It is useful to set the resize_percent to a lower number while iterating
img = read_and_prep_image(
filename, pixels_per_sample_side, resize_percent=resize_percent
)
[rows, columns, color_channels] = img.shape
print(img[0][0])
width_samples = rows / pixels_per_sample_side
height_samples = columns / pixels_per_sample_side

print("width samples", width_samples)
print("height samples", height_samples)

mm_per_sample_width = plotter.width / width_samples
mm_per_sample_height = plotter.height / height_samples

print("mm per sample width", mm_per_sample_width)
print("mm per sample height", mm_per_sample_height)

# To maintain an aspect ratio and have all points fit on the canvas, we need to use the smaller of the two mm_per_sample values.
mm_per_sample_side = min(mm_per_sample_width, mm_per_sample_height)
print("mm", mm_per_sample_side)
cmyk_color_ratios = image_to_cmyk_color_ratios(
img, pixels_per_sample_side=pixels_per_sample_side
)

for row_index, row in enumerate(cmyk_color_ratios):
for col_index, cmyk_ratio in enumerate(row):
x_start_mm = col_index * mm_per_sample_side
y_start_mm = row_index * mm_per_sample_side
plot_points_per_cmyk_ratio(
cmyk_ratio=cmyk_ratio,
x_start_mm=x_start_mm,
y_start_mm=y_start_mm,
mm_per_sample_side=mm_per_sample_side,
points_per_sample_side=points_per_sample_side,
)

end_time = time.time()
print(f"Total time: {end_time - start_time}")
plotter.preview()
plotter.save()


if __name__ == "__main__":
main()

- + \ No newline at end of file diff --git a/docs/quickstart.html b/docs/quickstart.html index bd36592..88445a9 100644 --- a/docs/quickstart.html +++ b/docs/quickstart.html @@ -4,13 +4,13 @@ Quick start | gcode2dplotterart - +
-

Quick start

Video tutorials are available. Run pip install gcode2dplotterart then head over to YouTube to watch the 2D Plotter or 3D Printer tutorials.

This guide covers setup for both 2D plotter and 3D printers. Instructions at certain steps will differ based on if a 2D plotter or 3D printer is being used. Additionally, specific devices will require some extra setup steps and will be noted with an additional section of 2D plotter or 3D printer.

0. Reference the Terminology

It is useful to keep the terminology help doc open while reading through the quick start.

1. Install dependencies

Install the Python package with pip install gcode2dplotterart and the Universal G-Code Sender software.

2. Setup Hardware

2D plotter

No special setup required.

3D printer

Follow the guide to Convert a 3D printer to a 2D plotter.

3. Learn about UGS

If the Universal G-Code Sender application has never been used before, it is recommended to read this article.

4. Get Plotting Device Dimensions and Feed Rate

The plotting device's dimensions act as a constraint to make sure anything that is plotted in code will physically fit within the bounds of the plotting device. Get the plotting device's dimensions.

The feed rate is a measure of how quickly the plotter head can move. It's good to tweak this so that the plotting device moves not too fast that it'll create imperfections and not too slow that plotting takes forever. Get the plotting device's feed rate.

Fill in the plotting device's dimensions and feed rate below.

2D plotter

from gcode2dplotterart import Plotter2D

plotter=Plotter2D(
title="Plotter2D Quickstart",

# The following 4 values are from the `Get the plotting device's dimensions` article above.
x_min=0, # This will be the value `X-` or 0
x_max=200, # This will be the value `X+`
y_min=0, # This will be the value `Y-` or 0
y_max=200, # This will be the value `Y+` or 0

# This value is from the `Get the plotting device's feed rate` article above.
feed_rate=0,

output_directory="./output",
handle_out_of_bounds='Warning' # If a plotted point is outside of the bounds, give a warning, don't plot the point, and keep going.
)

3D printer

from gcode2dplotterart import Plotter3D

plotter=Plotter3D(
title="Plotter3D Quickstart",

# The following 6 values are from the `Get the plotting device's dimensions` article above.
x_min=0, # This will be the value `X-` or 0
x_max=200, # This will be the value `X+`
y_min=0, # This will be the value `Y-` or 0
y_max=200, # This will be the value `Y+` or 0
z_plotting_height=0, # This will be the value of `Z` that connects the plotter head to the plotting surface.
z_navigation_height=3, # This will be the value of `Z` that separate the plotter head from the plotting surface.

# This value is from the `Get the plotting device's feed rate` article above.
feed_rate=0,

output_directory="./output",
handle_out_of_bounds='Warning' # If a plotted point is outside of the bounds, give a warning, don't plot the point, and keep going.
)

5. Add a layer

A layer is a group of instructions that will be executed sequentially. It usually makes sense to create layers based on the plotting instruments being used.

Several layers can be added to plot with different colors. The color value is used to generate a preview before plotting. A hex color (such as #00FF00) or human readable color name (see MatplotLib for list of supported color names) can be used.

black_pen_layer = "black_pen_layer"
blue_marker_layer = "blue_marker_layer"
green_marker_layer = "green_marker_layer"

plotter.add_layer(black_pen_layer, color="black", line_width=1.0)
plotter.add_layer(blue_marker_layer, color="blue", line_width=4.0)
plotter.add_layer(green_marker_layer, color="#027F00", line_width=4.0)

6. Add lines, shapes, and paths to the layers

Once a layer is created, start appending instructions to that layer. Note that the points should fit inside the plotting device's bounds, or else warnings will be seen when executing the script.

plotter.layers[black_pen_layer].add_point(x=30, y=40)
plotter.layers[blue_marker_layer].add_circle(x_center=10, y_center=30, radius=10)
plotter.layers[blue_marker_layer].add_rectangle(x_start=50, y_start=50, x_end=75, y_end=75)
plotter.layers[green_marker_layer].add_path([(10, 10), (20, 25), (30, 15), (1, 100)])
plotter.layers[green_marker_layer].add_line(x_start=70, y_start=80, x_end=70, y_end=15)
plotter.layers[green_marker_layer].add_text("hello world", x_start=10, y_start=10, font_size=10)

info

add_point, add_circle, add_rectangle, and other similar methods are all wrappers around add_path. The add_path method is the most flexible and can be used to create any path.

7. Generate a preview

plotter.preview()

This will open up a preview of what will be plotted. This can be useful to spot check the G-Code instructions before plotting begins.

Preview screenshot

8. Save layers to file

plotter.save()

Inside the folder specified by the plotter's output_directory and title there will be four files preview.gcode, blue_marker_layer.gcode, black_pen_layer.gcode, and green_marker_layer.gcode. Each of the files can be opened and the code browsed. The G-Code Overview includes explanations of all of the instructions used in this library.

7. Plot

danger

Be sure to Reset Zero every time the plotting device is powered on.

In UGS, open up the preview.gcode file. This won't plot anything but will give a preview of how large the plotting area will be. It's useful to run this command a few times to ensure that the plotting surface is where it's expected to be and things are aligned horizontally and vertically. Open the next gcode file for the first layer to be plotted. Attach the drawing instrument and begin plotting. Repeat the process for each layer.

8. Read the documentation

3D Printer

2D Plotter

9. Next steps

Check out the plotting tips and coding tips. Find some inspiration in the plotting gallery.

Create something cool? Share it here!

- +

Quick start

Video tutorials are available. Run pip install gcode2dplotterart then head over to YouTube to watch the 2D Plotter or 3D Printer tutorials.

This guide covers setup for both 2D plotter and 3D printers. Instructions at certain steps will differ based on if a 2D plotter or 3D printer is being used. Additionally, specific devices will require some extra setup steps and will be noted with an additional section of 2D plotter or 3D printer.

0. Reference the Terminology

It is useful to keep the terminology help doc open while reading through the quick start.

1. Install dependencies

Install the Python package with pip install gcode2dplotterart and the Universal G-Code Sender software.

2. Setup Hardware

2D plotter

No special setup required.

3D printer

Follow the guide to Convert a 3D printer to a 2D plotter.

3. Learn about UGS

If the Universal G-Code Sender application has never been used before, it is recommended to read this article.

4. Get Plotting Device Dimensions and Feed Rate

The plotting device's dimensions act as a constraint to make sure anything that is plotted in code will physically fit within the bounds of the plotting device. Get the plotting device's dimensions.

The feed rate is a measure of how quickly the plotter head can move. It's good to tweak this so that the plotting device moves not too fast that it'll create imperfections and not too slow that plotting takes forever. Get the plotting device's feed rate.

Fill in the plotting device's dimensions and feed rate below.

2D plotter

from gcode2dplotterart import Plotter2D

plotter=Plotter2D(
title="Plotter2D Quickstart",

# The following 4 values are from the `Get the plotting device's dimensions` article above.
x_min=0, # This will be the value `X-` or 0
x_max=200, # This will be the value `X+`
y_min=0, # This will be the value `Y-` or 0
y_max=200, # This will be the value `Y+` or 0

# This value is from the `Get the plotting device's feed rate` article above.
feed_rate=0,

output_directory="./output",
handle_out_of_bounds='Warning' # If a plotted point is outside of the bounds, give a warning, don't plot the point, and keep going.
)

3D printer

from gcode2dplotterart import Plotter3D

plotter=Plotter3D(
title="Plotter3D Quickstart",

# The following 6 values are from the `Get the plotting device's dimensions` article above.
x_min=0, # This will be the value `X-` or 0
x_max=200, # This will be the value `X+`
y_min=0, # This will be the value `Y-` or 0
y_max=200, # This will be the value `Y+` or 0
z_plotting_height=0, # This will be the value of `Z` that connects the plotter head to the plotting surface.
z_navigation_height=3, # This will be the value of `Z` that separate the plotter head from the plotting surface.

# This value is from the `Get the plotting device's feed rate` article above.
feed_rate=0,

output_directory="./output",
handle_out_of_bounds='Warning' # If a plotted point is outside of the bounds, give a warning, don't plot the point, and keep going.
)

5. Add a layer

A layer is a group of instructions that will be executed sequentially. It usually makes sense to create layers based on the plotting instruments being used.

Several layers can be added to plot with different colors. The color value is used to generate a preview before plotting. A hex color (such as #00FF00) or human readable color name (see MatplotLib for list of supported color names) can be used.

black_pen_layer = "black_pen_layer"
blue_marker_layer = "blue_marker_layer"
green_marker_layer = "green_marker_layer"

plotter.add_layer(black_pen_layer, color="black", line_width=1.0)
plotter.add_layer(blue_marker_layer, color="blue", line_width=4.0)
plotter.add_layer(green_marker_layer, color="#027F00", line_width=4.0)

6. Add lines, shapes, and paths to the layers

Once a layer is created, start appending instructions to that layer. Note that the points should fit inside the plotting device's bounds, or else warnings will be seen when executing the script.

plotter.layers[black_pen_layer].add_point(x=30, y=40)
plotter.layers[blue_marker_layer].add_circle(x_center=10, y_center=30, radius=10)
plotter.layers[blue_marker_layer].add_rectangle(x_start=50, y_start=50, x_end=75, y_end=75)
plotter.layers[green_marker_layer].add_path([(10, 10), (20, 25), (30, 15), (1, 100)])
plotter.layers[green_marker_layer].add_line(x_start=70, y_start=80, x_end=70, y_end=15)
plotter.layers[green_marker_layer].add_text("hello world", x_start=10, y_start=10, font_size=10)

info

add_point, add_circle, add_rectangle, and other similar methods are all wrappers around add_path. The add_path method is the most flexible and can be used to create any path.

7. Generate a preview

plotter.preview()

This will open up a preview of what will be plotted. This can be useful to spot check the G-Code instructions before plotting begins.

Preview screenshot

8. Save layers to file

plotter.save()

Inside the folder specified by the plotter's output_directory and title there will be four files preview.gcode, blue_marker_layer.gcode, black_pen_layer.gcode, and green_marker_layer.gcode. Each of the files can be opened and the code browsed. The G-Code Overview includes explanations of all of the instructions used in this library.

7. Plot

danger

Be sure to Reset Zero every time the plotting device is powered on.

In UGS, open up the preview.gcode file. This won't plot anything but will give a preview of how large the plotting area will be. It's useful to run this command a few times to ensure that the plotting surface is where it's expected to be and things are aligned horizontally and vertically. Open the next gcode file for the first layer to be plotted. Attach the drawing instrument and begin plotting. Repeat the process for each layer.

8. Read the documentation

3D Printer

2D Plotter

9. Next steps

Check out the plotting tips and coding tips. Find some inspiration in the plotting gallery.

Create something cool? Share it here!

+ \ No newline at end of file diff --git a/docs/releases.html b/docs/releases.html index e343d3f..d796ed0 100644 --- a/docs/releases.html +++ b/docs/releases.html @@ -4,13 +4,13 @@ Release Notes | gcode2dplotterart - +

Release Notes

All notable changes to this project will be documented in this file. This project adheres to Semantic Versioning.

2.0.3

Added

  • Added proper support for 3D printers.
  • Added experimental functionality for a CLI tool and photo preprocessing.
from gcode2dplotterart import experimental_photo_utils, experimental_cli

experimental_photo_utils.buck_image_even_histogram_distribution
experimental_photo_utils.bucket_image_even_pixel_count
experimental_photo_utils.grayscale_image
experimental_photo_utils.load_image
experimental_photo_utils.resize_image
experimental_cli

2023-12-10

Added

  • GCO-93 Added option to not raise plotting device after plotting a path with raise_plotter_head_after_path param.
  • Added test for add_text

Changed

  • GCO-106 Refactored code to have smaller files.
  • Refactored test boilerplate to be reusable.

Fixed

  • GCO-105 Improved performance around plotting individual points.
  • GCO-104 Fixed bug where preview method would not show points.

1.1.1

2023-12-09

Fixed

  • Plotter.preview() method failed to plot single points. This has been fixed.

1.1.0

2023-12-02

Added

  • include_comments option to Plotter. When set to True, the plotter will include comments in the output.

Changed

  • When a point in a path is outside of the plotting device's bounds, the plotter will now print a warning and skip the entire path.

1.0.0

2023-12-02

Added

  • Initial versioning release
- + \ No newline at end of file diff --git a/index.html b/index.html index 0f30527..9db2784 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,13 @@ gcode2dplotterart | gcode2dplotterart - +

gcode2dplotterart

Easily generate plotter art with your 2D plotter or 3D printer. This library abstracts away G-Code so you can focus on making art.

Gallery

Find inspiration in the gallery of plotted art created by other users.
Browse Now

Documentation

Learn everything you need to know to take full advantage of the library.
Learn Now

- + \ No newline at end of file diff --git a/markdown-page.html b/markdown-page.html index e75b85f..0e85825 100644 --- a/markdown-page.html +++ b/markdown-page.html @@ -4,13 +4,13 @@ Markdown page example | gcode2dplotterart - +

Markdown page example

You don't need React to write simple standalone pages.

- + \ No newline at end of file