├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx ├── LaserCAM.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── README ├── SVG.cs ├── bin └── Debug │ ├── lasercam.exe │ ├── lasercam.pdb │ └── lasercam.vshost.exe ├── lasercam.csproj ├── lasercam.sln └── svg-sample.svg /Form1.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace LaserCAM 2 | { 3 | partial class Form1 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.OpenButton = new System.Windows.Forms.Button(); 32 | this.filePreview = new System.Windows.Forms.PictureBox(); 33 | this.dpiControl = new System.Windows.Forms.NumericUpDown(); 34 | this.label1 = new System.Windows.Forms.Label(); 35 | this.button1 = new System.Windows.Forms.Button(); 36 | this.groupBox1 = new System.Windows.Forms.GroupBox(); 37 | this.contourLabel = new System.Windows.Forms.Label(); 38 | this.pageStatsLabel = new System.Windows.Forms.Label(); 39 | this.groupBox2 = new System.Windows.Forms.GroupBox(); 40 | this.rasterCheckbox = new System.Windows.Forms.CheckBox(); 41 | this.materialComboBox = new System.Windows.Forms.ComboBox(); 42 | this.label4 = new System.Windows.Forms.Label(); 43 | this.rasterFeedControl = new System.Windows.Forms.NumericUpDown(); 44 | this.vectorFeedControl = new System.Windows.Forms.NumericUpDown(); 45 | this.label5 = new System.Windows.Forms.Label(); 46 | this.vectorCheckbox = new System.Windows.Forms.CheckBox(); 47 | this.groupBox3 = new System.Windows.Forms.GroupBox(); 48 | this.progressBar = new System.Windows.Forms.ProgressBar(); 49 | this.cvModeCheckbox = new System.Windows.Forms.CheckBox(); 50 | ((System.ComponentModel.ISupportInitialize)(this.filePreview)).BeginInit(); 51 | ((System.ComponentModel.ISupportInitialize)(this.dpiControl)).BeginInit(); 52 | this.groupBox1.SuspendLayout(); 53 | this.groupBox2.SuspendLayout(); 54 | ((System.ComponentModel.ISupportInitialize)(this.rasterFeedControl)).BeginInit(); 55 | ((System.ComponentModel.ISupportInitialize)(this.vectorFeedControl)).BeginInit(); 56 | this.groupBox3.SuspendLayout(); 57 | this.SuspendLayout(); 58 | // 59 | // OpenButton 60 | // 61 | this.OpenButton.Location = new System.Drawing.Point(6, 19); 62 | this.OpenButton.Name = "OpenButton"; 63 | this.OpenButton.Size = new System.Drawing.Size(75, 23); 64 | this.OpenButton.TabIndex = 0; 65 | this.OpenButton.Text = "Open..."; 66 | this.OpenButton.UseVisualStyleBackColor = true; 67 | this.OpenButton.Click += new System.EventHandler(this.OpenButton_Click); 68 | // 69 | // filePreview 70 | // 71 | this.filePreview.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 72 | | System.Windows.Forms.AnchorStyles.Left) 73 | | System.Windows.Forms.AnchorStyles.Right))); 74 | this.filePreview.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 75 | this.filePreview.Location = new System.Drawing.Point(170, 12); 76 | this.filePreview.Name = "filePreview"; 77 | this.filePreview.Size = new System.Drawing.Size(547, 479); 78 | this.filePreview.TabIndex = 1; 79 | this.filePreview.TabStop = false; 80 | // 81 | // dpiControl 82 | // 83 | this.dpiControl.Location = new System.Drawing.Point(35, 47); 84 | this.dpiControl.Maximum = new decimal(new int[] { 85 | 1200, 86 | 0, 87 | 0, 88 | 0}); 89 | this.dpiControl.Name = "dpiControl"; 90 | this.dpiControl.Size = new System.Drawing.Size(51, 20); 91 | this.dpiControl.TabIndex = 2; 92 | this.dpiControl.Value = new decimal(new int[] { 93 | 300, 94 | 0, 95 | 0, 96 | 0}); 97 | this.dpiControl.ValueChanged += new System.EventHandler(this.dpiControl_ValueChanged); 98 | // 99 | // label1 100 | // 101 | this.label1.AutoSize = true; 102 | this.label1.Location = new System.Drawing.Point(3, 49); 103 | this.label1.Name = "label1"; 104 | this.label1.Size = new System.Drawing.Size(28, 13); 105 | this.label1.TabIndex = 3; 106 | this.label1.Text = "DPI:"; 107 | // 108 | // button1 109 | // 110 | this.button1.Location = new System.Drawing.Point(7, 19); 111 | this.button1.Name = "button1"; 112 | this.button1.Size = new System.Drawing.Size(75, 23); 113 | this.button1.TabIndex = 4; 114 | this.button1.Text = "Convert"; 115 | this.button1.UseVisualStyleBackColor = true; 116 | this.button1.Click += new System.EventHandler(this.button1_Click); 117 | // 118 | // groupBox1 119 | // 120 | this.groupBox1.Controls.Add(this.pageStatsLabel); 121 | this.groupBox1.Controls.Add(this.contourLabel); 122 | this.groupBox1.Controls.Add(this.OpenButton); 123 | this.groupBox1.Location = new System.Drawing.Point(12, 12); 124 | this.groupBox1.Name = "groupBox1"; 125 | this.groupBox1.Size = new System.Drawing.Size(152, 100); 126 | this.groupBox1.TabIndex = 5; 127 | this.groupBox1.TabStop = false; 128 | this.groupBox1.Text = "Source SVG File"; 129 | // 130 | // contourLabel 131 | // 132 | this.contourLabel.AutoSize = true; 133 | this.contourLabel.Location = new System.Drawing.Point(7, 49); 134 | this.contourLabel.Name = "contourLabel"; 135 | this.contourLabel.Size = new System.Drawing.Size(52, 13); 136 | this.contourLabel.TabIndex = 1; 137 | this.contourLabel.Text = "Contours:"; 138 | // 139 | // pageStatsLabel 140 | // 141 | this.pageStatsLabel.AutoSize = true; 142 | this.pageStatsLabel.Location = new System.Drawing.Point(7, 72); 143 | this.pageStatsLabel.Name = "pageStatsLabel"; 144 | this.pageStatsLabel.Size = new System.Drawing.Size(52, 13); 145 | this.pageStatsLabel.TabIndex = 2; 146 | this.pageStatsLabel.Text = "Contours:"; 147 | // 148 | // groupBox2 149 | // 150 | this.groupBox2.Controls.Add(this.cvModeCheckbox); 151 | this.groupBox2.Controls.Add(this.vectorFeedControl); 152 | this.groupBox2.Controls.Add(this.label5); 153 | this.groupBox2.Controls.Add(this.vectorCheckbox); 154 | this.groupBox2.Controls.Add(this.rasterFeedControl); 155 | this.groupBox2.Controls.Add(this.label4); 156 | this.groupBox2.Controls.Add(this.materialComboBox); 157 | this.groupBox2.Controls.Add(this.rasterCheckbox); 158 | this.groupBox2.Controls.Add(this.dpiControl); 159 | this.groupBox2.Controls.Add(this.label1); 160 | this.groupBox2.Location = new System.Drawing.Point(12, 119); 161 | this.groupBox2.Name = "groupBox2"; 162 | this.groupBox2.Size = new System.Drawing.Size(152, 236); 163 | this.groupBox2.TabIndex = 6; 164 | this.groupBox2.TabStop = false; 165 | this.groupBox2.Text = "CAM Settings"; 166 | // 167 | // rasterCheckbox 168 | // 169 | this.rasterCheckbox.AutoSize = true; 170 | this.rasterCheckbox.Location = new System.Drawing.Point(6, 89); 171 | this.rasterCheckbox.Name = "rasterCheckbox"; 172 | this.rasterCheckbox.Size = new System.Drawing.Size(57, 17); 173 | this.rasterCheckbox.TabIndex = 0; 174 | this.rasterCheckbox.Text = "Raster"; 175 | this.rasterCheckbox.UseVisualStyleBackColor = true; 176 | // 177 | // materialComboBox 178 | // 179 | this.materialComboBox.FormattingEnabled = true; 180 | this.materialComboBox.Location = new System.Drawing.Point(7, 20); 181 | this.materialComboBox.Name = "materialComboBox"; 182 | this.materialComboBox.Size = new System.Drawing.Size(121, 21); 183 | this.materialComboBox.TabIndex = 1; 184 | this.materialComboBox.SelectedIndexChanged += new System.EventHandler(this.materialComboBox_SelectedIndexChanged); 185 | // 186 | // label4 187 | // 188 | this.label4.AutoSize = true; 189 | this.label4.Location = new System.Drawing.Point(6, 111); 190 | this.label4.Name = "label4"; 191 | this.label4.Size = new System.Drawing.Size(68, 13); 192 | this.label4.TabIndex = 4; 193 | this.label4.Text = "Raster Feed:"; 194 | // 195 | // rasterFeedControl 196 | // 197 | this.rasterFeedControl.Location = new System.Drawing.Point(77, 109); 198 | this.rasterFeedControl.Maximum = new decimal(new int[] { 199 | 2000, 200 | 0, 201 | 0, 202 | 0}); 203 | this.rasterFeedControl.Name = "rasterFeedControl"; 204 | this.rasterFeedControl.Size = new System.Drawing.Size(54, 20); 205 | this.rasterFeedControl.TabIndex = 5; 206 | // 207 | // vectorFeedControl 208 | // 209 | this.vectorFeedControl.Location = new System.Drawing.Point(77, 168); 210 | this.vectorFeedControl.Maximum = new decimal(new int[] { 211 | 2000, 212 | 0, 213 | 0, 214 | 0}); 215 | this.vectorFeedControl.Name = "vectorFeedControl"; 216 | this.vectorFeedControl.Size = new System.Drawing.Size(54, 20); 217 | this.vectorFeedControl.TabIndex = 8; 218 | // 219 | // label5 220 | // 221 | this.label5.AutoSize = true; 222 | this.label5.Location = new System.Drawing.Point(6, 170); 223 | this.label5.Name = "label5"; 224 | this.label5.Size = new System.Drawing.Size(68, 13); 225 | this.label5.TabIndex = 7; 226 | this.label5.Text = "Vector Feed:"; 227 | // 228 | // vectorCheckbox 229 | // 230 | this.vectorCheckbox.AutoSize = true; 231 | this.vectorCheckbox.Location = new System.Drawing.Point(6, 148); 232 | this.vectorCheckbox.Name = "vectorCheckbox"; 233 | this.vectorCheckbox.Size = new System.Drawing.Size(57, 17); 234 | this.vectorCheckbox.TabIndex = 6; 235 | this.vectorCheckbox.Text = "Vector"; 236 | this.vectorCheckbox.UseVisualStyleBackColor = true; 237 | // 238 | // groupBox3 239 | // 240 | this.groupBox3.Controls.Add(this.progressBar); 241 | this.groupBox3.Controls.Add(this.button1); 242 | this.groupBox3.Location = new System.Drawing.Point(12, 361); 243 | this.groupBox3.Name = "groupBox3"; 244 | this.groupBox3.Size = new System.Drawing.Size(152, 83); 245 | this.groupBox3.TabIndex = 7; 246 | this.groupBox3.TabStop = false; 247 | this.groupBox3.Text = "GCode Output"; 248 | // 249 | // progressBar 250 | // 251 | this.progressBar.Location = new System.Drawing.Point(7, 49); 252 | this.progressBar.Name = "progressBar"; 253 | this.progressBar.Size = new System.Drawing.Size(139, 23); 254 | this.progressBar.TabIndex = 5; 255 | // 256 | // cvModeCheckbox 257 | // 258 | this.cvModeCheckbox.AutoSize = true; 259 | this.cvModeCheckbox.Location = new System.Drawing.Point(10, 194); 260 | this.cvModeCheckbox.Name = "cvModeCheckbox"; 261 | this.cvModeCheckbox.Size = new System.Drawing.Size(137, 17); 262 | this.cvModeCheckbox.TabIndex = 9; 263 | this.cvModeCheckbox.Text = "Constant Velocity (G64)"; 264 | this.cvModeCheckbox.UseVisualStyleBackColor = true; 265 | // 266 | // Form1 267 | // 268 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 269 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 270 | this.ClientSize = new System.Drawing.Size(729, 503); 271 | this.Controls.Add(this.groupBox3); 272 | this.Controls.Add(this.groupBox2); 273 | this.Controls.Add(this.groupBox1); 274 | this.Controls.Add(this.filePreview); 275 | this.Name = "Form1"; 276 | this.Text = "Form1"; 277 | ((System.ComponentModel.ISupportInitialize)(this.filePreview)).EndInit(); 278 | ((System.ComponentModel.ISupportInitialize)(this.dpiControl)).EndInit(); 279 | this.groupBox1.ResumeLayout(false); 280 | this.groupBox1.PerformLayout(); 281 | this.groupBox2.ResumeLayout(false); 282 | this.groupBox2.PerformLayout(); 283 | ((System.ComponentModel.ISupportInitialize)(this.rasterFeedControl)).EndInit(); 284 | ((System.ComponentModel.ISupportInitialize)(this.vectorFeedControl)).EndInit(); 285 | this.groupBox3.ResumeLayout(false); 286 | this.ResumeLayout(false); 287 | 288 | } 289 | 290 | #endregion 291 | 292 | private System.Windows.Forms.Button OpenButton; 293 | private System.Windows.Forms.PictureBox filePreview; 294 | private System.Windows.Forms.NumericUpDown dpiControl; 295 | private System.Windows.Forms.Label label1; 296 | private System.Windows.Forms.Button button1; 297 | private System.Windows.Forms.GroupBox groupBox1; 298 | private System.Windows.Forms.Label pageStatsLabel; 299 | private System.Windows.Forms.Label contourLabel; 300 | private System.Windows.Forms.GroupBox groupBox2; 301 | private System.Windows.Forms.ComboBox materialComboBox; 302 | private System.Windows.Forms.CheckBox rasterCheckbox; 303 | private System.Windows.Forms.NumericUpDown vectorFeedControl; 304 | private System.Windows.Forms.Label label5; 305 | private System.Windows.Forms.CheckBox vectorCheckbox; 306 | private System.Windows.Forms.NumericUpDown rasterFeedControl; 307 | private System.Windows.Forms.Label label4; 308 | private System.Windows.Forms.GroupBox groupBox3; 309 | private System.Windows.Forms.ProgressBar progressBar; 310 | private System.Windows.Forms.CheckBox cvModeCheckbox; 311 | } 312 | } 313 | 314 | -------------------------------------------------------------------------------- /Form1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Chris Yerga 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | using System; 22 | using System.Collections.Generic; 23 | using System.ComponentModel; 24 | using System.Data; 25 | using System.Drawing; 26 | using System.Linq; 27 | using System.Text; 28 | using System.Windows.Forms; 29 | using Microsoft.Win32; 30 | using FrickenLaser.SVG; 31 | using System.IO; 32 | 33 | namespace LaserCAM 34 | { 35 | public partial class Form1 : Form 36 | { 37 | string filePath = null; 38 | SVGDocument document = null; 39 | 40 | public Form1() 41 | { 42 | InitializeComponent(); 43 | 44 | contourLabel.Text = ""; 45 | pageStatsLabel.Text = ""; 46 | 47 | // Default to CV mode always 48 | cvModeCheckbox.Checked = true; 49 | 50 | // Dropdown for material selection. Edit feeds/speeds 51 | // in materialComboBox_SelectedIndexChanged() 52 | materialComboBox.Items.Add("Acrylic (1.5mm)"); 53 | materialComboBox.Items.Add("Acrylic (3mm)"); 54 | materialComboBox.Items.Add("Acrylic (6mm)"); 55 | materialComboBox.Items.Add("Paper (100#)"); 56 | materialComboBox.Items.Add("Wood (1/8\" Birch)"); 57 | materialComboBox.Items.Add("Wood (1/4\" Birch)"); 58 | materialComboBox.Items.Add("Aluminum (Laptop, iPad, etc.)"); 59 | materialComboBox.SelectedIndex = 1; 60 | 61 | progressBar.Visible = false; 62 | 63 | 64 | button1.Enabled = false; 65 | } 66 | 67 | private string GetRegValue(string key, string defaultValue) 68 | { 69 | RegistryKey rootKey = Registry.CurrentUser.CreateSubKey(@"Software\atomsandelectrons\LaserCAM"); 70 | return (string)rootKey.GetValue(key, defaultValue); 71 | } 72 | 73 | private void SetRegValue(string key, string value) 74 | { 75 | RegistryKey rootKey = Registry.CurrentUser.CreateSubKey(@"Software\atomsandelectrons\LaserCAM"); 76 | rootKey.SetValue(key, value); 77 | } 78 | 79 | 80 | private void OpenButton_Click(object sender, EventArgs e) 81 | { 82 | OpenFileDialog dialog = new OpenFileDialog(); 83 | 84 | dialog.Filter = "SVG Drawing|*.svg"; 85 | dialog.InitialDirectory = GetRegValue("LastDirectory", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)); 86 | if (dialog.ShowDialog() == DialogResult.OK) 87 | { 88 | SetRegValue("LastDirectory", System.IO.Path.GetDirectoryName(dialog.FileName)); 89 | filePath = dialog.FileName; 90 | LoadSVG(dialog.FileName); 91 | 92 | UpdateStats(); 93 | 94 | button1.Enabled = true; 95 | } 96 | } 97 | 98 | void UpdateStats() 99 | { 100 | int contourCount = 0; 101 | int pointCount = 0; 102 | float xmin = 99999.0f; 103 | float xmax = -99999.0f; 104 | float ymin = 99999.0f; 105 | float ymax = -99999.0f; 106 | 107 | var contours = document.GetVectorContours((double)dpiControl.Value); 108 | foreach (var contour in contours) 109 | { 110 | ++contourCount; 111 | foreach (var point in contour.Points) 112 | { 113 | ++pointCount; 114 | } 115 | } 116 | 117 | foreach (var point in document.GetPoints()) 118 | { 119 | if (point.X < xmin) 120 | { 121 | xmin = point.X; 122 | } 123 | if (point.X > xmax) 124 | { 125 | xmax = point.X; 126 | } 127 | if (point.Y < ymin) 128 | { 129 | ymin = point.Y; 130 | } 131 | if (point.Y > ymax) 132 | { 133 | ymax = point.Y; 134 | } 135 | } 136 | 137 | contourLabel.Text = string.Format("{0} contours and {1} points", contourCount, pointCount); 138 | pageStatsLabel.Text = string.Format("{0:0.00}in x {1:0.00}in", xmax - xmin, ymax - ymin); 139 | } 140 | 141 | void UpdatePreview() 142 | { 143 | Bitmap preview = new Bitmap(filePreview.Width, filePreview.Height); 144 | Graphics gc = Graphics.FromImage(preview); 145 | 146 | gc.ScaleTransform(75, 75); 147 | document.Render(gc, false); 148 | filePreview.Image = preview; 149 | 150 | filePreview.Invalidate(); 151 | } 152 | 153 | private void LoadSVG(string path) 154 | { 155 | document = SVGDocument.LoadFromFile(path); 156 | UpdatePreview(); 157 | } 158 | 159 | private void Progress(double progress) 160 | { 161 | System.Console.WriteLine("Progress: {0}", (int)(100.0 * progress)); 162 | progressBar.Minimum = 0; 163 | progressBar.Maximum = 100; 164 | progressBar.Value = (int)(100.0 * progress); 165 | } 166 | 167 | private void button1_Click(object sender, EventArgs e) 168 | { 169 | progressBar.Visible = true; 170 | string gcodePath = Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath) + "-gcode.txt"); 171 | document.EmitGCode(gcodePath, 172 | true, (int)dpiControl.Value, (int)rasterFeedControl.Value, 173 | true, (int)dpiControl.Value, (int)vectorFeedControl.Value, cvModeCheckbox.Checked, 174 | Progress); 175 | progressBar.Visible = false; 176 | } 177 | 178 | private void dpiControl_ValueChanged(object sender, EventArgs e) 179 | { 180 | UpdateStats(); 181 | } 182 | 183 | private void materialComboBox_SelectedIndexChanged(object sender, EventArgs e) 184 | { 185 | rasterCheckbox.Checked = true; 186 | vectorCheckbox.Checked = true; 187 | 188 | switch (materialComboBox.SelectedIndex) 189 | { 190 | case 0: // 1.5mm acrylic 191 | rasterFeedControl.Value = 300; 192 | vectorFeedControl.Value = 30; 193 | break; 194 | 195 | case 1: // 3mm acrylic 196 | rasterFeedControl.Value = 300; 197 | vectorFeedControl.Value = 15; 198 | break; 199 | 200 | case 2: // 6mm acrylic 201 | rasterFeedControl.Value = 300; 202 | vectorFeedControl.Value = 6; 203 | break; 204 | 205 | case 3: // 100# paper 206 | rasterFeedControl.Value = 1000; 207 | vectorFeedControl.Value = 150; 208 | break; 209 | 210 | case 4: // 0.125" wood 211 | rasterFeedControl.Value = 200; 212 | vectorFeedControl.Value = 20; 213 | break; 214 | 215 | case 5: // 0.25" wood 216 | rasterFeedControl.Value = 1000; 217 | vectorFeedControl.Value = 10; 218 | break; 219 | 220 | case 6: // Anodized aluminum 221 | rasterFeedControl.Value = 1000; 222 | vectorFeedControl.Value = 10; 223 | break; 224 | 225 | } 226 | 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Form1.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /LaserCAM.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Chris Yerga 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | using System; 22 | using System.Collections.Generic; 23 | using System.Linq; 24 | using System.Windows.Forms; 25 | 26 | namespace LaserCAM 27 | { 28 | static class Program 29 | { 30 | /// 31 | /// The main entry point for the application. 32 | /// 33 | [STAThread] 34 | static void Main() 35 | { 36 | Application.EnableVisualStyles(); 37 | Application.SetCompatibleTextRenderingDefault(false); 38 | Application.Run(new Form1()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("lasercam")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("lasercam")] 13 | [assembly: AssemblyCopyright("Copyright © 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("cb60d4e8-0f0e-462b-972a-e53638c8e2db")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:2.0.50727.4206 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace lasercam.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("lasercam.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:2.0.50727.4206 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace lasercam.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | LaserCAM 2 | yergacheffe@atomsandelectrons.com 3 | 4 | This is a .Net Windows App that will read an SVG file containing both 5 | vector and bitmap shapes and convert it into GCode suitable for driving 6 | a laser cutter. This code was developed to drive an inexpensive Chinese 7 | laser from Mach3 and works quite well for vector shapes. The raster 8 | suffers from being too slow and doesn't control the laser on/off fast 9 | enough so there's uneven power applied. 10 | 11 | But compared to the horrific software that came with the machine, this 12 | produces smooth cuts that are an order of magnitude more precise. 13 | 14 | -------------------------------------------------------------------------------- /SVG.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Chris Yerga 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | using System; 22 | using System.Collections.Generic; 23 | using System.Drawing.Imaging; 24 | using System.Drawing.Drawing2D; 25 | using System.Drawing; 26 | using System.IO; 27 | using System.Linq; 28 | using System.Text; 29 | using System.Xml; 30 | 31 | namespace FrickenLaser.SVG 32 | { 33 | /// 34 | /// Interface for a drawable object found in an SVG file. This 35 | /// is not a general-purpose SVG library -- it only does the 36 | /// bare minimum necessary to drive a CNC machine so this is 37 | /// mostly just to handle vectors and paths. 38 | /// 39 | public interface ISVGElement 40 | { 41 | // Retrieve the list of contours for this shape 42 | List> GetContours(); 43 | 44 | // System.Drawing Path 45 | GraphicsPath GetPath(); 46 | 47 | // Fill and outline 48 | double OutlineWidth { get; } 49 | Color OutlineColor { get; } 50 | Color FillColor { get; } 51 | } 52 | 53 | /// 54 | /// A contour is a set of points describing a closed polygon. 55 | /// 56 | public class VectorContour 57 | { 58 | public double Brightness { get; set; } 59 | public Color Color { get; set; } 60 | public IEnumerable Points { get; set; } 61 | } 62 | 63 | /// 64 | /// Base class for shapes found in SVG file. This handles common tasks 65 | /// such as parsing styles, applying transforms, etc. 66 | /// 67 | public class SVGShapeBase 68 | { 69 | /// 70 | /// Constructor for SVGShapeBase class. Called from derived class 71 | /// constructor. 72 | /// 73 | /// XmlTextReader positioned at the XML element 74 | /// for the shape being constructed. This class uses it to look 75 | /// for style/transform attributes to apply to the shape 76 | /// Dictionary of named styles 77 | /// defined earlier in the SVG document, to be used should an 78 | /// XML style attribute with a name be encountered. 79 | public SVGShapeBase(XmlTextReader reader, Dictionary styleDictionary) 80 | { 81 | string styleText = reader.GetAttribute("class"); 82 | 83 | if (styleText != null) 84 | { 85 | string[] styleNames = styleText.Split(new char[] { ' ', '\t' }); 86 | 87 | foreach (string styleName in styleNames) 88 | { 89 | SVGStyle style = styleDictionary[styleName]; 90 | 91 | if (style.FillColorPresent) 92 | { 93 | FillColor = style.FillColor; 94 | } 95 | if (style.OutlineColorPresent) 96 | { 97 | OutlineColor = style.OutlineColor; 98 | } 99 | if (style.OutlineWidthPresent) 100 | { 101 | OutlineWidth = style.OutlineWidth; 102 | } 103 | } 104 | } 105 | 106 | string xfs = reader.GetAttribute("transform"); 107 | if (xfs != null) 108 | { 109 | if (xfs.StartsWith("matrix")) 110 | { 111 | xfs = xfs.Substring(6); 112 | } 113 | xfs = xfs.Trim(new char[] { '(', ')' }); 114 | string[] elements = xfs.Split(new char[] { ' ', '\t' }); 115 | 116 | matrix = new Matrix( 117 | float.Parse(elements[0]), 118 | float.Parse(elements[1]), 119 | float.Parse(elements[2]), 120 | float.Parse(elements[3]), 121 | float.Parse(elements[4]), 122 | float.Parse(elements[5])); 123 | } 124 | } 125 | 126 | /// 127 | /// Transform the geometry of this shape as appropriate 128 | /// 129 | /// 130 | /// 131 | public List Transform(List points) 132 | { 133 | PointF[] pts = points.ToArray(); 134 | 135 | matrix.TransformPoints(pts); 136 | return new List(pts); 137 | } 138 | 139 | internal Matrix matrix = new Matrix(); 140 | internal GraphicsPath _path; 141 | public GraphicsPath GetPath() { return _path; } 142 | public double OutlineWidth { get; set; } 143 | public Color OutlineColor { get; set; } 144 | public Color FillColor { get; set; } 145 | } 146 | 147 | /// 148 | /// SVG Rectangle 149 | /// 150 | public class SVGRect : SVGShapeBase, ISVGElement 151 | { 152 | private List points = new List(); 153 | 154 | public SVGRect(XmlTextReader reader, Dictionary styleDictionary) 155 | : base(reader, styleDictionary) 156 | { 157 | float x = 0; 158 | try 159 | { 160 | x = float.Parse(reader.GetAttribute("x")); 161 | } 162 | catch (ArgumentNullException) { } 163 | 164 | float y = 0; 165 | try 166 | { 167 | y = float.Parse(reader.GetAttribute("y")); 168 | } 169 | catch (ArgumentNullException) { } 170 | float w = float.Parse(reader.GetAttribute("width")); 171 | float h = float.Parse(reader.GetAttribute("height")); 172 | 173 | points.Add(new PointF(x, y)); 174 | points.Add(new PointF(x + w, y)); 175 | points.Add(new PointF(x + w, y + h)); 176 | points.Add(new PointF(x, y + h)); 177 | points.Add(new PointF(x, y)); 178 | 179 | points = Transform(points); 180 | 181 | _path = new GraphicsPath(); 182 | _path.AddPolygon(points.ToArray()); 183 | } 184 | 185 | public List> GetContours() 186 | { 187 | var result = new List>(); 188 | result.Add(points); 189 | 190 | return result; 191 | } 192 | } 193 | 194 | /// 195 | /// SVG Image. This is handled differently from the rest of the shapes 196 | /// as it cannot be represented as vector contours. It loads the bitmap 197 | /// and elsewhere encapsulation is broken willy-nilly and the client 198 | /// code reaches in and grabs said bits. If you don't like it, go on 199 | /// the internet and complain. 200 | /// 201 | public class SVGImage : SVGShapeBase, ISVGElement 202 | { 203 | float x = 0; 204 | float y = 0; 205 | float width, height; 206 | public Image bits; 207 | public RectangleF DestBounds { get; set; } 208 | 209 | public SVGImage(XmlTextReader reader, Dictionary styleDictionary, string baseDocPath) 210 | : base(reader, styleDictionary) 211 | { 212 | try 213 | { 214 | x = float.Parse(reader.GetAttribute("x")); 215 | } 216 | catch { } 217 | 218 | try 219 | { 220 | y = float.Parse(reader.GetAttribute("y")); 221 | } 222 | catch { } 223 | width = float.Parse(reader.GetAttribute("width")); 224 | height = float.Parse(reader.GetAttribute("height")); 225 | string path = reader.GetAttribute("xlink:href"); 226 | 227 | string dir = Path.GetDirectoryName(baseDocPath); 228 | string bitspath = Path.Combine(dir, path); 229 | bits = Image.FromFile(bitspath); 230 | 231 | PointF[] pts = new PointF[2]; 232 | pts[0].X = x; 233 | pts[0].Y = y; 234 | pts[1].X = x+width; 235 | pts[1].Y = y+height; 236 | matrix.TransformPoints(pts); 237 | 238 | DestBounds = new RectangleF(pts[0].X, pts[0].Y, pts[1].X - pts[0].X, pts[1].Y - pts[0].Y); 239 | } 240 | 241 | public List> GetContours() 242 | { 243 | var result = new List>(); 244 | 245 | return result; 246 | } 247 | } 248 | 249 | /// 250 | /// SVG Circle. We like polygons, so we just turn it into one of them. 251 | /// 252 | public class SVGCircle : SVGShapeBase, ISVGElement 253 | { 254 | private List points = new List(); 255 | 256 | public SVGCircle(XmlTextReader reader, Dictionary styleDictionary) 257 | : base(reader, styleDictionary) 258 | { 259 | float cx = 0; 260 | try 261 | { 262 | cx = float.Parse(reader.GetAttribute("cx")); 263 | } 264 | catch { } 265 | 266 | float cy = 0; 267 | try 268 | { 269 | cy = float.Parse(reader.GetAttribute("cy")); 270 | } 271 | catch { } 272 | float r = float.Parse(reader.GetAttribute("r")); 273 | 274 | for (double theta = 0.0; theta < 2.0*Math.PI; theta += Math.PI / 50.0) 275 | { 276 | double x = Math.Sin(theta) * r + cx; 277 | double y = Math.Cos(theta) * r + cy; 278 | 279 | points.Add(new PointF((float)x, (float)y)); 280 | } 281 | points = Transform(points); 282 | 283 | _path = new GraphicsPath(); 284 | _path.AddPolygon(points.ToArray()); 285 | } 286 | 287 | public List> GetContours() 288 | { 289 | var result = new List>(); 290 | result.Add(points); 291 | 292 | return result; 293 | } 294 | } 295 | 296 | /// 297 | /// SVG polygon. This maps directly to our canonical representation, 298 | /// so nothing fancy going on in here. 299 | /// 300 | public class SVGPolygon : SVGShapeBase, ISVGElement 301 | { 302 | private List> contours = new List>(); 303 | private List currentContour = new List(); 304 | 305 | 306 | public SVGPolygon(XmlTextReader reader, Dictionary styleDictionary) 307 | : base(reader, styleDictionary) 308 | { 309 | string data = reader.GetAttribute("points"); 310 | string[] textPoints = data.Split(new char[] { ' ', '\t' }); 311 | 312 | foreach (string textPoint in textPoints) 313 | { 314 | string[] ordinates = textPoint.Split(new char[] { ',' }); 315 | if (ordinates.Length > 1) 316 | { 317 | 318 | currentContour.Add(new PointF(float.Parse(ordinates[0]), float.Parse(ordinates[1]))); 319 | } 320 | } 321 | 322 | if (currentContour.Count > 2) 323 | { 324 | float deltaX = currentContour[0].X - currentContour[currentContour.Count - 1].X; 325 | float deltaY = currentContour[0].Y - currentContour[currentContour.Count - 1].Y; 326 | 327 | if (Math.Abs(deltaX) + Math.Abs(deltaY) > 0.001) 328 | { 329 | currentContour.Add(currentContour[0]); 330 | } 331 | } 332 | 333 | currentContour = Transform(currentContour); 334 | contours.Add(currentContour); 335 | 336 | _path = new GraphicsPath(); 337 | if (currentContour.Count > 2) 338 | { 339 | _path.AddPolygon(currentContour.ToArray()); 340 | } 341 | } 342 | 343 | public List> GetContours() 344 | { 345 | return contours; 346 | } 347 | } 348 | 349 | /// 350 | /// SVG path. The XML mini-language is full-featured and complex, making 351 | /// the parsing here the bulk of the work. Also, for curved portions of 352 | /// the path we approximate with polygons. 353 | /// 354 | public class SVGPath : SVGShapeBase, ISVGElement 355 | { 356 | private List> contours = new List>(); 357 | private List currentContour = new List(); 358 | 359 | enum ParseState 360 | { 361 | None, 362 | MoveToAbs, 363 | MoveToRel, 364 | CurveToAbs, 365 | CurveToRel, 366 | LineToAbs, 367 | LineToRel 368 | }; 369 | ParseState state = ParseState.None; 370 | 371 | public float GetFloat(string data, ref int index) 372 | { 373 | StringBuilder builder = new StringBuilder(); 374 | bool isnum = true; 375 | 376 | while (isnum) 377 | { 378 | if (index >= data.Length) 379 | { 380 | isnum = false; 381 | } 382 | else 383 | { 384 | switch (data[index]) 385 | { 386 | case '0': break; 387 | case '1': break; 388 | case '2': break; 389 | case '3': break; 390 | case '4': break; 391 | case '5': break; 392 | case '6': break; 393 | case '7': break; 394 | case '8': break; 395 | case '9': break; 396 | case '-': break; 397 | case '.': break; 398 | case 'e': break; 399 | case '+': break; 400 | 401 | default: isnum = false; break; 402 | } 403 | } 404 | 405 | if (isnum) 406 | { 407 | builder.Append(data[index]); 408 | ++index; 409 | } 410 | } 411 | 412 | return float.Parse(builder.ToString()); 413 | } 414 | 415 | public SVGPath(XmlTextReader reader, Dictionary styleDictionary) 416 | : base(reader, styleDictionary) 417 | { 418 | _path = new GraphicsPath(); 419 | 420 | string data = reader.GetAttribute("d"); 421 | if (data == null) 422 | { 423 | return; 424 | } 425 | 426 | int index = 0; 427 | bool done = false; 428 | 429 | while (index < data.Length) 430 | { 431 | SkipSpace(data, ref index); 432 | 433 | switch (data[index]) 434 | { 435 | case 'M': state = ParseState.MoveToAbs; ++index; break; 436 | case 'm': state = ParseState.MoveToRel; ++index; break; 437 | case 'c': state = ParseState.CurveToRel; ++index; break; 438 | case 'l': state = ParseState.LineToRel; ++index; break; 439 | case 'L': state = ParseState.LineToAbs; ++index; break; 440 | case 'z': state = ParseState.None; ++index; 441 | // Close current contour and open a new one 442 | currentContour.Add(currentContour.First()); 443 | currentContour = Transform(currentContour); 444 | contours.Add(currentContour); 445 | _path.AddPolygon(currentContour.ToArray()); 446 | currentContour = new List(); 447 | continue; 448 | 449 | case '0': break; 450 | case '1': break; 451 | case '2': break; 452 | case '3': break; 453 | case '4': break; 454 | case '5': break; 455 | case '6': break; 456 | case '7': break; 457 | case '8': break; 458 | case '9': break; 459 | case '-': break; 460 | case ' ': break; 461 | 462 | default: throw new ApplicationException(string.Format("Unexpected input {0}", data[index])); 463 | } 464 | 465 | if (done) 466 | { 467 | break; 468 | } 469 | 470 | SkipSpace(data, ref index); 471 | 472 | if (state == ParseState.MoveToAbs) 473 | { 474 | float x = GetFloat(data, ref index); 475 | SkipSpaceOrComma(data, ref index); 476 | float y = GetFloat(data, ref index); 477 | 478 | currentContour.Add(new PointF(x, y)); 479 | } 480 | else if (state == ParseState.MoveToRel) 481 | { 482 | float x = GetFloat(data, ref index); 483 | SkipSpaceOrComma(data, ref index); 484 | float y = GetFloat(data, ref index); 485 | 486 | currentContour.Add(new PointF(PreviousPoint().X + x, PreviousPoint().Y + y)); 487 | } 488 | else if (state == ParseState.LineToRel) 489 | { 490 | float x = GetFloat(data, ref index); 491 | SkipSpaceOrComma(data, ref index); 492 | float y = GetFloat(data, ref index); 493 | 494 | currentContour.Add(new PointF(PreviousPoint().X + x, PreviousPoint().Y + y)); 495 | } 496 | else if (state == ParseState.LineToAbs) 497 | { 498 | float x = GetFloat(data, ref index); 499 | SkipSpaceOrComma(data, ref index); 500 | float y = GetFloat(data, ref index); 501 | 502 | currentContour.Add(new PointF(x, y)); 503 | } 504 | else if (state == ParseState.CurveToRel) 505 | { 506 | float cx1 = GetFloat(data, ref index); 507 | if (data[index] != ',') 508 | { 509 | throw new ApplicationException("Expected comma"); 510 | } 511 | ++index; 512 | float cy1 = GetFloat(data, ref index); 513 | 514 | if (data[index] != ' ') 515 | { 516 | throw new ApplicationException("Expected space"); 517 | } 518 | ++index; 519 | 520 | float cx2 = GetFloat(data, ref index); 521 | if (data[index] != ',') 522 | { 523 | throw new ApplicationException("Expected comma"); 524 | } 525 | ++index; 526 | float cy2 = GetFloat(data, ref index); 527 | 528 | if (data[index] != ' ') 529 | { 530 | throw new ApplicationException("Expected space"); 531 | } 532 | ++index; 533 | 534 | float x = GetFloat(data, ref index); 535 | if (data[index] != ',') 536 | { 537 | throw new ApplicationException("Expected comma"); 538 | } 539 | ++index; 540 | float y = GetFloat(data, ref index); 541 | 542 | float lx = PreviousPoint().X; 543 | float ly = PreviousPoint().Y; 544 | 545 | AddBezierPoints(lx, ly, lx + cx1, ly + cy1, lx + cx2, ly + cy2, lx + x, ly + y); 546 | } 547 | } 548 | 549 | if (currentContour.Count > 0) 550 | { 551 | if (currentContour.Count <= 2) 552 | { 553 | // Happens sometimes. This is either a point or 554 | // a line. Empty area, so just toss it. 555 | } 556 | else 557 | { 558 | currentContour.Add(currentContour.First()); 559 | currentContour = Transform(currentContour); 560 | contours.Add(currentContour); 561 | _path.AddPolygon(currentContour.ToArray()); 562 | } 563 | } 564 | } 565 | 566 | private PointF PreviousPoint() 567 | { 568 | if (currentContour.Count > 0) 569 | { 570 | return currentContour.Last(); 571 | } 572 | if (contours.Count > 0) 573 | { 574 | return contours.Last().Last(); 575 | } 576 | return new PointF(0, 0); 577 | } 578 | 579 | private void AddBezierPoints(float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x3, float y3) 580 | { 581 | List pointList = new List(); 582 | 583 | // First subdivide the Bezier into 250 line segments. This number is fairly arbitrary 584 | // and anything we pick is wrong because for small curves you'll have multiple segments 585 | // smaller than a pixel and for huge curves no number is large enough. We pick something 586 | // fairly big and then the polygon gets thinned in two separate stages afterwards. Many 587 | // of these get reduced to just a handful of vertices by the time we emit GCode. 588 | pointList.Add(new PointF(x1, y1)); 589 | float stepDelta = 1.0f / 250.0f; 590 | 591 | for (float t = stepDelta; t < 1.0f; t += stepDelta) // Parametric value 592 | { 593 | float fW = 1 - t; 594 | float fA = fW * fW * fW; 595 | float fB = 3 * t * fW * fW; 596 | float fC = 3 * t * t * fW; 597 | float fD = t * t * t; 598 | 599 | float fX = fA * x1 + fB * cx1 + fC * cx2 + fD * x3; 600 | float fY = fA * y1 + fB * cy1 + fC * cy2 + fD * y3; 601 | 602 | pointList.Add(new PointF(fX, fY)); 603 | } 604 | pointList.Add(new PointF(x3, y3)); 605 | 606 | // Next thin the points based on a flatness test. 607 | bool done = true; 608 | do 609 | { 610 | done = true; 611 | int pointIndex = 0; 612 | do 613 | { 614 | PointF p1 = pointList[pointIndex]; 615 | PointF p2 = pointList[pointIndex + 1]; 616 | PointF p3 = pointList[pointIndex + 2]; 617 | PointF pb = new PointF((p1.X + p3.X) / 2, (p1.Y + p3.Y) / 2); 618 | 619 | double err = Math.Sqrt(Math.Abs(p2.X - pb.X) * Math.Abs(p2.X - pb.X) + 620 | Math.Abs(p2.Y - pb.Y) * Math.Abs(p2.Y - pb.Y)); 621 | double dist = Math.Sqrt(Math.Abs(p3.X - p1.X) * Math.Abs(p3.X - p1.X) + 622 | Math.Abs(p3.Y - p1.Y) * Math.Abs(p3.Y - p1.Y)); 623 | double relativeErr = err / dist; 624 | 625 | // If the subdivided portion is within a pixel at 1000dpi 626 | // then it's flat enough to remove the intermediate vertex. 627 | if (relativeErr < 0.001) 628 | { 629 | pointList.RemoveAt(pointIndex + 1); 630 | done = false; 631 | } 632 | 633 | ++pointIndex; 634 | } while (pointIndex < pointList.Count - 2); 635 | } while (!done); 636 | 637 | foreach (PointF point in pointList) 638 | { 639 | currentContour.Add(point); 640 | } 641 | } 642 | 643 | public void SkipSpace(string data, ref int index) 644 | { 645 | while (data[index] < 33) 646 | { 647 | ++index; 648 | } 649 | } 650 | 651 | public void SkipSpaceOrComma(string data, ref int index) 652 | { 653 | while (data[index] < 33 || data[index] == ',') 654 | { 655 | ++index; 656 | } 657 | } 658 | 659 | public List> GetContours() 660 | { 661 | return contours; 662 | } 663 | } 664 | 665 | /// 666 | /// Styles contain colors, stroke widths, etc. We use them to differentiate 667 | /// vector vs. raster portions of the document. 668 | /// 669 | public class SVGStyle 670 | { 671 | public string Name { get; set; } 672 | 673 | public bool OutlineWidthPresent { get; set; } 674 | public double OutlineWidth { get; set; } 675 | 676 | public bool OutlineColorPresent { get; set; } 677 | public Color OutlineColor { get; set; } 678 | 679 | public bool FillColorPresent { get; set; } 680 | public Color FillColor { get; set; } 681 | 682 | static public Color ParseColor(string c) 683 | { 684 | Color result; 685 | 686 | if ( c.Length == 7 && c[0] == '#' ) 687 | { 688 | string s1 = c.Substring(1, 2); 689 | string s2 = c.Substring(3, 2); 690 | string s3 = c.Substring(5, 2); 691 | 692 | byte r = 0; 693 | byte g = 0; 694 | byte b = 0; 695 | 696 | try 697 | { 698 | r = Convert.ToByte(s1, 16); 699 | g = Convert.ToByte(s2, 16); 700 | b = Convert.ToByte(s3, 16); 701 | } 702 | catch 703 | { 704 | } 705 | 706 | result = Color.FromArgb(r, g, b); 707 | } 708 | else 709 | { 710 | result = Color.FromName(c); 711 | } 712 | 713 | return result; 714 | 715 | } 716 | 717 | public SVGStyle(string name, string style) 718 | { 719 | Name = name; 720 | OutlineColorPresent = false; 721 | OutlineWidthPresent = false; 722 | FillColorPresent = false; 723 | 724 | style = style.Trim(new char[] { '{', '}' }); 725 | string[] stylePairs = style.Split(new char[] { ':', ';' }); 726 | 727 | if ((stylePairs.Count() & 1) != 0) 728 | { 729 | throw new ArgumentException("Failed to parse style"); 730 | } 731 | 732 | for (int index=0; index 756 | /// An SVG Document. Read from file and build in-memory representation. 757 | /// 758 | public class SVGDocument 759 | { 760 | private List shapes = new List(); 761 | 762 | public static SVGDocument LoadFromFile(string path) 763 | { 764 | DateTime start = DateTime.UtcNow; 765 | 766 | // All this nonsense is to prevent a 10 second delay when reading 767 | // the first SVG file. It gets hung up trying to resolve the DTD? 768 | MemoryStream ms = new MemoryStream(); 769 | { 770 | StreamReader sr = new StreamReader(path); 771 | StreamWriter sw = new StreamWriter(ms); 772 | 773 | while (sr.EndOfStream == false) 774 | { 775 | string line = sr.ReadLine(); 776 | if (line.StartsWith(" styleDictionary = new Dictionary(); 789 | 790 | while (reader.Read()) 791 | { 792 | if (reader.NodeType == XmlNodeType.Element) 793 | { 794 | if (reader.Name == "style") 795 | { 796 | // Inline style 797 | string styleData = reader.ReadElementContentAsString(); 798 | StringReader styleReader = new StringReader(styleData); 799 | string line; 800 | 801 | while ((line = styleReader.ReadLine()) != null) 802 | { 803 | string[] splitLine; 804 | 805 | line = line.Trim(); 806 | splitLine = line.Split(new char[] { ' ', '\t' }); 807 | 808 | string name = splitLine[0]; 809 | if (name.StartsWith(".")) 810 | { 811 | name = name.Substring(1); 812 | } 813 | if (splitLine.Count() == 2) 814 | { 815 | styleDictionary.Add(name, new SVGStyle(name, splitLine[1])); 816 | } 817 | 818 | }; 819 | } 820 | else if (reader.Name == "rect") 821 | { 822 | if (reader.GetAttribute("class") != null) 823 | { 824 | doc.AddShape(new SVGRect(reader, styleDictionary)); 825 | } 826 | } 827 | else if (reader.Name == "circle") 828 | { 829 | doc.AddShape(new SVGCircle(reader, styleDictionary)); 830 | } 831 | else if (reader.Name == "polygon") 832 | { 833 | doc.AddShape(new SVGPolygon(reader, styleDictionary)); 834 | } 835 | else if (reader.Name == "polyline") 836 | { 837 | doc.AddShape(new SVGPolygon(reader, styleDictionary)); 838 | } 839 | else if (reader.Name == "path") 840 | { 841 | doc.AddShape(new SVGPath(reader, styleDictionary)); 842 | } 843 | else if (reader.Name == "image") 844 | { 845 | doc.AddShape(new SVGImage(reader, styleDictionary, path)); 846 | } 847 | } 848 | } 849 | 850 | TimeSpan duration = DateTime.UtcNow - start; 851 | System.Console.WriteLine("### Load took {0}s", ((double)duration.TotalMilliseconds / 1000.0)); 852 | 853 | return doc; 854 | } 855 | 856 | public void AddShape(ISVGElement shape) 857 | { 858 | shapes.Add(shape); 859 | } 860 | 861 | public IEnumerable> GetContours() 862 | { 863 | // Enumerate each shape in the document 864 | foreach (ISVGElement shape in shapes) 865 | { 866 | foreach (var contour in shape.GetContours()) 867 | { 868 | yield return contour; 869 | } 870 | } 871 | } 872 | 873 | public void Render(Graphics gc, bool rasterOnly) 874 | { 875 | foreach (ISVGElement shape in shapes) 876 | { 877 | if (shape is SVGImage) 878 | { 879 | // Polymorphism? What's that? 880 | SVGImage img = shape as SVGImage; 881 | 882 | gc.DrawImage(img.bits, img.DestBounds); 883 | } 884 | 885 | if (shape.OutlineWidth < .01 && shape.FillColor.A == 0 && rasterOnly) 886 | { 887 | continue; 888 | } 889 | 890 | GraphicsPath p = shape.GetPath(); 891 | if (shape.FillColor.A > 0) 892 | { 893 | Brush b = new SolidBrush(shape.FillColor); 894 | gc.FillPath(b, p); 895 | b.Dispose(); 896 | } 897 | if (shape.OutlineWidth > 0 && shape.OutlineColor.A > 0) 898 | { 899 | Pen pen = new Pen(shape.OutlineColor, (float)shape.OutlineWidth); 900 | gc.DrawPath(pen, p); 901 | pen.Dispose(); 902 | } 903 | } 904 | } 905 | 906 | string GCodeHeader = 907 | @"(paperpixels SVG to GCode v0.1) 908 | N30 G17 (active plane) 909 | N35 G40 (turn compensation off) 910 | N40 G20 (inch mode) 911 | N45 G90 (Absolute mode, current coordinates) 912 | N50 G61 (Exact stop mode for raster scanning)"; 913 | 914 | string GCodeFooter = 915 | @"M5 (Laser Off) 916 | E1P0 (Program End) 917 | G0 X0 Y0 Z1 (Home and really turn off laser) 918 | M30 (End)"; 919 | 920 | /// 921 | /// This emits GCode suitable for driving a laser cutter to vector/raster 922 | /// the document. There are numerous assumptions made here so that it 923 | /// works exactly with my cheap Chinese laser cutter, my controller board 924 | /// and my Mach3 config. You may need to fiddle with things here to 925 | /// get the axes directions correct, etc. 926 | /// 927 | /// Output path for GCode file 928 | /// If true a raster path is emitted 929 | /// DPI resolution for raster 930 | /// IPS feed for raster scan 931 | /// If true a vector cut is emitted 932 | /// DPI resolution for vector cut 933 | /// IPS feed for vector cut 934 | /// Use constant velocity mode? Smooths out 935 | /// discontinuities that occur at polygon vertices/corners using 936 | /// lookahead. Mach3 does all this, this simply emits a GCode to 937 | /// turn this on. This, plus sufficient lookahead configured in 938 | /// Mach3 made my laser perform much smoother. 939 | /// Callback for progress notification 940 | public void EmitGCode(string path, bool raster, int rasterDpi, int rasterFeedRate, bool vector, int vectorDpi, int vectorFeedRate, bool vectorCV, Action progressDelegate) 941 | { 942 | // Open up file for writing 943 | StreamWriter gcode = new StreamWriter(path); 944 | 945 | // Emit header 946 | gcode.WriteLine(GCodeHeader); 947 | 948 | // BUGBUG: Should read size from SVG file header. But we usually are doing 11x11 949 | // This always assumes an 11" x 11" document. 950 | double docWidth = 11.0; 951 | double docHeight = 11.0; 952 | double bandHeight = 0.5; 953 | double totalProgress = docHeight / bandHeight + 1.0; 954 | double progress = 0; 955 | 956 | // First pass is raster engraving 957 | if (raster) 958 | { 959 | // Create a bitmap image used for banding 960 | Bitmap band = new Bitmap( 961 | (int)(docWidth * rasterDpi), // Band Width in px 962 | (int)(bandHeight * rasterDpi), // Band Height in px 963 | PixelFormat.Format32bppArgb); 964 | Graphics gc = Graphics.FromImage(band); 965 | 966 | // Now render each band of the image 967 | for (double bandTop = 0.0; bandTop <= docHeight - bandHeight; bandTop += bandHeight) 968 | { 969 | // Call progress method once per band 970 | if (progressDelegate != null) 971 | { 972 | progressDelegate(progress / totalProgress); 973 | } 974 | 975 | // Set up the GC transform to render this band 976 | gc.ResetTransform(); 977 | gc.FillRectangle(Brushes.White, 0, 0, 99999, 99999); 978 | gc.ScaleTransform(-rasterDpi, -rasterDpi); 979 | gc.TranslateTransform((float)-docWidth, (float)-bandTop); 980 | 981 | // Erase whatever was there before 982 | gc.FillRectangle(Brushes.White, -50, -50, 50, 50); 983 | 984 | // Render just the raster shapes into the band 985 | this.Render(gc, true); 986 | 987 | // Now scan the band and emit gcode. We access the bitmap data 988 | // directly for higher performance. The use of unsafe pointer 989 | // access here sped up perf significantly over GetPixel() which 990 | // is obvious but they don't teach PhD's this so I mention it here. 991 | unsafe 992 | { 993 | BitmapData lockedBits = band.LockBits( 994 | Rectangle.FromLTRB(0, 0, band.Width, band.Height), 995 | ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 996 | bool laserOn = false; 997 | int onStart = 0; 998 | for (int y = 0; y < band.Height; ++y) 999 | { 1000 | if (laserOn) 1001 | { 1002 | throw new ApplicationException("Expected laser off"); 1003 | } 1004 | 1005 | // Get the bits for this scanline using something I call a "pointer" 1006 | byte* pScanline = (byte*)((int)lockedBits.Scan0 + ((band.Height - 1) - y) * lockedBits.Stride); 1007 | for (int x = 0; x < band.Width; ++x) 1008 | { 1009 | int b = *pScanline++; 1010 | int g = *pScanline++; 1011 | int r = *pScanline++; 1012 | int a = *pScanline++; 1013 | int luma = r + g + b; 1014 | 1015 | if (luma < 400) 1016 | { 1017 | if (!laserOn) 1018 | { 1019 | // Found an "on" edge 1020 | onStart = x; 1021 | laserOn = true; 1022 | } 1023 | } 1024 | else 1025 | { 1026 | if (laserOn) 1027 | { 1028 | // Found an "off" edge 1029 | double fx = (double)onStart / (double)rasterDpi; 1030 | double fy = ((double)y / (double)rasterDpi) + bandTop; 1031 | fy = docHeight - fy + bandHeight; 1032 | fx = docWidth - fx; 1033 | gcode.WriteLine(string.Format("G1 X{0:0.0000} Y{1:0.0000} F{2}", fx, fy, rasterFeedRate)); 1034 | fx = (double)x / (double)rasterDpi; 1035 | fx = docWidth - fx; 1036 | gcode.WriteLine("G1 Z0 (Laser On)"); 1037 | gcode.WriteLine(string.Format("X{0:0.0000}", fx)); 1038 | gcode.WriteLine("Z0.002 (Laser Off)"); 1039 | 1040 | laserOn = false; 1041 | } 1042 | } 1043 | } 1044 | 1045 | // If we get here and laser is still on then we 1046 | // turn it off at the edge here. 1047 | if (laserOn && false) 1048 | { 1049 | double fx = (double)onStart / (double)rasterDpi; 1050 | double fy = ((double)y / (double)rasterDpi) + bandTop; 1051 | gcode.WriteLine(string.Format("G1 X{0:0.0000} Y{1:0.0000}", fx, fy)); 1052 | fx = (double)band.Width / (double)rasterDpi; 1053 | fx = docWidth - fx; 1054 | gcode.WriteLine("G1 Z0 (Laser On)"); 1055 | gcode.WriteLine(string.Format("X{0:0.0000} F{1:0.0000}", fx, rasterFeedRate)); 1056 | gcode.WriteLine("Z0.002 (Laser Off)"); 1057 | 1058 | laserOn = false; 1059 | } 1060 | } 1061 | 1062 | // Unlock band bits 1063 | band.UnlockBits(lockedBits); 1064 | 1065 | // Increment progress 1066 | progress += 1.0; 1067 | } 1068 | } 1069 | } 1070 | 1071 | // Pause inbetween for the operator to adjust power 1072 | // You need to create a M995 custom macro in Mach3 to 1073 | // stick up a dialog that says "Adjust Power for Vector" 1074 | gcode.WriteLine("(=================================================================================)"); 1075 | gcode.WriteLine("(Pause for operator power adjustment)"); 1076 | gcode.WriteLine("(Depends on macro M995 set up to prompt operator)"); 1077 | gcode.WriteLine("(=================================================================================)"); 1078 | gcode.WriteLine("M995"); 1079 | 1080 | // Second pass is vector cuts 1081 | double contourIncrement = 1.0 / GetVectorContours(100).Count(); 1082 | if (vectorCV) 1083 | { 1084 | gcode.WriteLine("G64 (Constant velocity mode for vector cuts)"); 1085 | } 1086 | foreach (var contour in GetVectorContours(vectorDpi)) 1087 | { 1088 | int contourFeed; 1089 | 1090 | if (vectorFeedRate > 0) 1091 | { 1092 | contourFeed = vectorFeedRate; 1093 | } 1094 | else 1095 | { 1096 | // This is trying to be overly cute and is probably 1097 | // not useful. It maps colors to different speeds. 1098 | contourFeed = (int)((1.0 - contour.Brightness) * 1000); 1099 | if (contour.Color.Name == "Blue") 1100 | { 1101 | contourFeed = 75; 1102 | } 1103 | else if (contour.Color.Name == "Aqua") 1104 | { 1105 | contourFeed = 40; 1106 | } 1107 | else if (contour.Color.Name == "Lime") 1108 | { 1109 | contourFeed = 30; 1110 | } 1111 | else if (contour.Color.Name == "Yellow") 1112 | { 1113 | contourFeed = 20; 1114 | } 1115 | else if (contour.Color.Name == "Red") 1116 | { 1117 | contourFeed = 10; 1118 | } 1119 | } 1120 | 1121 | bool first = true; 1122 | foreach (var point in contour.Points) 1123 | { 1124 | // Transform point to laser coordinate system 1125 | double laserX = point.X; 1126 | double laserY = 11.0 - point.Y; 1127 | 1128 | if (first) 1129 | { 1130 | // Rapid to the start of the contour 1131 | gcode.WriteLine(string.Format("G0 X{0:0.0000} Y{1:0.0000}", laserX, laserY)); 1132 | gcode.WriteLine(string.Format("G1 Z-0.002 F{0} (Turn on laser. Set feed for this contour)", contourFeed)); 1133 | first = false; 1134 | } 1135 | else 1136 | { 1137 | // Next point in contour 1138 | gcode.WriteLine(string.Format("X{0:0.0000} Y{1:0.0000}", laserX, laserY)); 1139 | } 1140 | } 1141 | gcode.WriteLine("Z0 (Turn off laser)"); 1142 | 1143 | progress += contourIncrement; 1144 | if (progressDelegate != null) 1145 | { 1146 | progressDelegate(progress / totalProgress); 1147 | } 1148 | } 1149 | 1150 | // Shut down 1151 | gcode.WriteLine(GCodeFooter); 1152 | gcode.Flush(); 1153 | gcode.Close(); 1154 | } 1155 | 1156 | /// 1157 | /// Get the points for all contours in all shapes. 1158 | /// 1159 | /// 1160 | public IEnumerable GetPoints() 1161 | { 1162 | // Enumerate each shape in the document 1163 | foreach (ISVGElement shape in shapes) 1164 | { 1165 | foreach (var contour in shape.GetContours()) 1166 | { 1167 | foreach (PointF point in contour) 1168 | { 1169 | yield return point; 1170 | } 1171 | } 1172 | } 1173 | } 1174 | 1175 | public int GetShapeCenter(ISVGElement shape) 1176 | { 1177 | double centerX = 99999.0; 1178 | double centerY = 99999.0; 1179 | 1180 | foreach (var contour in shape.GetContours()) 1181 | { 1182 | foreach (PointF point in contour) 1183 | { 1184 | if (point.X < centerX) 1185 | { 1186 | centerX = point.X; 1187 | } 1188 | if (point.Y < centerY) 1189 | { 1190 | centerY = point.Y; 1191 | } 1192 | } 1193 | } 1194 | 1195 | centerX = centerX * 10; 1196 | centerY = centerY * 10; 1197 | int x = (int)centerX / 1; 1198 | int y = (int)centerY / 1; 1199 | 1200 | return y * 10000 + x; 1201 | } 1202 | 1203 | 1204 | public double Distance(PointF a, PointF b) 1205 | { 1206 | double xd = Math.Abs(a.X - b.X); 1207 | double yd = Math.Abs(a.Y - b.Y); 1208 | 1209 | xd = xd * xd; 1210 | yd = yd * yd; 1211 | 1212 | return Math.Sqrt(xd + yd); 1213 | } 1214 | 1215 | /// 1216 | /// Given a specific DPI resolution, returns vectors to approximate 1217 | /// the shapes in the document at that resolution. This allows us to 1218 | /// thin out the tons of polygon edges for curved segments. 1219 | /// 1220 | /// 1221 | /// 1222 | public IEnumerable GetVectorContours(double dpi) 1223 | { 1224 | double threshhold = 1.0/dpi; 1225 | int total = 0; 1226 | int thin = 0; 1227 | 1228 | 1229 | foreach (ISVGElement shape in shapes) 1230 | { 1231 | if ((shape.OutlineWidth >= .01 && shape.OutlineColor.A == 255) || shape.FillColor.A == 255) 1232 | { 1233 | continue; 1234 | } 1235 | 1236 | foreach (var contour in shape.GetContours()) 1237 | { 1238 | List thinnedContour = new List(); 1239 | PointF lastPoint = contour.First(); 1240 | bool first = true; 1241 | 1242 | foreach (PointF point in contour) 1243 | { 1244 | ++total; 1245 | 1246 | if (first) 1247 | { 1248 | thinnedContour.Add(new PointF(point.X, point.Y)); 1249 | lastPoint = point; 1250 | first = false; 1251 | } 1252 | else 1253 | { 1254 | if (Distance(point, lastPoint) > threshhold) 1255 | { 1256 | ++thin; 1257 | thinnedContour.Add(point); 1258 | lastPoint = point; 1259 | } 1260 | } 1261 | } 1262 | 1263 | yield return new VectorContour() 1264 | { 1265 | Brightness = shape.OutlineColor.GetBrightness(), 1266 | Color = shape.OutlineColor, 1267 | Points = thinnedContour 1268 | }; 1269 | } 1270 | } 1271 | 1272 | System.Console.WriteLine("Thinned contour ({0}/{1}) = {2}%", thin, total, (int)((double)thin / (double)total * 100.0)); 1273 | } 1274 | } 1275 | } 1276 | -------------------------------------------------------------------------------- /bin/Debug/lasercam.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yergacheffe/lasercam/718dd79c44d7f9e0e2f9d81ae0a89be4edfd9cc5/bin/Debug/lasercam.exe -------------------------------------------------------------------------------- /bin/Debug/lasercam.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yergacheffe/lasercam/718dd79c44d7f9e0e2f9d81ae0a89be4edfd9cc5/bin/Debug/lasercam.pdb -------------------------------------------------------------------------------- /bin/Debug/lasercam.vshost.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yergacheffe/lasercam/718dd79c44d7f9e0e2f9d81ae0a89be4edfd9cc5/bin/Debug/lasercam.vshost.exe -------------------------------------------------------------------------------- /lasercam.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.21022 7 | 2.0 8 | {B746E1CC-DD93-4ACD-A7C2-D45249F5A5A0} 9 | WinExe 10 | Properties 11 | lasercam 12 | lasercam 13 | v3.5 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | true 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 3.5 38 | 39 | 40 | 3.5 41 | 42 | 43 | 3.5 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Form 54 | 55 | 56 | Form1.cs 57 | 58 | 59 | 60 | 61 | ResXFileCodeGenerator 62 | Resources.Designer.cs 63 | Designer 64 | 65 | 66 | True 67 | Resources.resx 68 | 69 | 70 | SettingsSingleFileGenerator 71 | Settings.Designer.cs 72 | 73 | 74 | True 75 | Settings.settings 76 | True 77 | 78 | 79 | 80 | 81 | 88 | -------------------------------------------------------------------------------- /lasercam.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual Studio 2008 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lasercam", "lasercam.csproj", "{B746E1CC-DD93-4ACD-A7C2-D45249F5A5A0}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {B746E1CC-DD93-4ACD-A7C2-D45249F5A5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {B746E1CC-DD93-4ACD-A7C2-D45249F5A5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {B746E1CC-DD93-4ACD-A7C2-D45249F5A5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {B746E1CC-DD93-4ACD-A7C2-D45249F5A5A0}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /svg-sample.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------