├── 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 |
20 |
--------------------------------------------------------------------------------