├── README.md
├── tests.html
├── TestOpGenerator.js
├── PostProcessorTestDriver.js
└── GlowforgeColorific.cps
/README.md:
--------------------------------------------------------------------------------
1 | # glowforge-colorific-fusion360-post
2 |
3 | ## What is this?
4 | This is a Post (https://cam.autodesk.com/hsmposts) for Fusion 360 and Autodesk HSM that produces SVG files from CAM operations that can be loaded intot he Glowforge App.
5 | This work is based on the original Post provided by Autodesk and as such their copyright remains.
6 |
7 | ## Goal
8 | The goal of this post is to eliminate the need to perform any post-processing of the SVG in Inkscape or another SVG package. Several alternatives exist but none of them produce a properly formed SVG file and they all require processing in some way, weather to assign colors, join paths or make solids out of empty shells.
9 |
10 | ## Improvements
11 | * Produce propperly formed SVG paths that remain connected in the Glowforge App.
12 | * Automatically color each CAM op with a unique color so Glowforge App imports each one as a seperate steps.
13 | * Steps are ordered exactly as CAM ops are ordering in Fusion 360. This is acheived by sorting the color values used to work with Glowforge Apps internal ordering.
14 | * Automatic color generation for large numbers of operations (think power & speed test patterns).
15 | * Support engraving with filled shapes. This is configurable in each CAM Op so cuts and engraves can be mixed in the same setup.
16 | * Automatically detect the selected Stock Point of the CAM setup and compensate for this so the SVG content is not off screen.
17 | * Add an option to compensate for inverted Z axis in the setup.
18 | * Support centering the SVG content in a frame the size of the Glowforge bed (size is configurable)
19 | * Add comments to and names and IDs to SVG elements. Each op is numbered with a unique ID. Comments are supported as HTML comments.
20 | * Support centering the design in the machine workspace with the 'Use Work Area' option.
21 | * Show a red error box if your design is larger than the machine workspace size when 'Use Work Area' is on.
22 |
23 | ## Wishlist
24 | * Scoring support - this is no currently officially suppoorted by Glowforge. Dashed lines seem like the obvious choice but thye produce an error.
25 | * Feeds & Speeds for custom materials - Ideally this could vary by opperation and would be a part of the Tool used in the CAM operation. Needs support Glowforge App.
26 |
27 | # License
28 | Copyright (C) 2018 by Autodesk, Inc.
29 | All rights reserved.
30 |
31 | This work is based on an original work by Autodesk, Inc. Ths work is provided *Gratis* but *not Libre*, see: https://en.wikipedia.org/wiki/Gratis_versus_libre
32 | This was developed for my own personal use and posted so that others (including Autodesk, Inc.) might benefit from this effort.
33 |
34 | # Documentation
35 | TK
36 |
--------------------------------------------------------------------------------
/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
Description: Test all 9 possible Y/X stock point selections. Auto Stock Point detection should produce SVGs that are centered in the canvas.
21 |
Expected: A grid of 9 identical arrows in 3 rows. The arrows should be solid filled, centered and pointing up.
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Imperial Units Test
43 |
Description: Test all 9 possible Y/X stock point selections where the model and stock are in Inches. Auto Stock Point detection should produce SVGs that are centered in the canvas.
44 |
Expected: A grid of 9 identical arrows in 3 rows. The arrows should be solid filled, centered and pointing up.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
Use Work Area Test
66 |
Description: Test all 9 possible Y/X stock point selections in combination with a custom machine work area. Auto Stock Point detection should produce SVGs that are centered in the custom machine work area. The work area is configured as 20mm x 20mm.
67 |
Expected: A grid of 9 identical arrows in 3 rows. The arrows should be solid filled, centered and pointing up.
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
Violate Work Area Test
89 |
Description: The design can be larger than the specified Machine Work Area in X or Y. The work area is configured as 20mm x 20mm.
90 |
Expected: 2 cells with blue filled rectangles. A Red box with an extra thick line representes the specified machine area demonstating how the design wont fit.
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
Color Tests
101 |
Description: Designs can have a large number of operations. Each Op should get a unique color in ascending hex order.
102 |
Expected: 6 colored boxes with the default color set (the min color set size).
103 |
104 |
Expected: 15 colored boxes (the complete pre-defined color set).
105 |
106 |
Expected: 50 colored boxes (number suggested by the customer as 'enough').
107 |
108 |
Expected: 120 colored boxes (15 color set * 8).
109 |
110 |
Expected: 256 colored boxes (because powers of 2 are fun).
111 |
112 |
Expected: 1024 colored boxes (because powers of 2 are fun, #extra edition!).
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/TestOpGenerator.js:
--------------------------------------------------------------------------------
1 | // draws a box, clockwise, starting in the upper left corner
2 | function drawArrow(x, y, scale) {
3 | scale = scale ? scale : 1;
4 | return [
5 | move(x, y),
6 |
7 | // arrow
8 | move(x += 5 * scale, y),
9 | cut(x += 5 * scale, y -= 4 * scale),
10 | cut(x, y -= 2 * scale),
11 | cut(x -= 3 * scale, y),
12 | cut(x, y -= 4 * scale),
13 |
14 | cut(x -= 4 * scale, y),
15 |
16 | cut(x, y += 4 * scale),
17 | cut(x -= 3 * scale, y),
18 | cut(x, y += 2 * scale),
19 | cut(x += 5 * scale, y += 4 * scale),
20 | move(x, y),
21 | ]
22 | }
23 |
24 | // draws a box, clockwise, starting in the upper left corner
25 | function drawBoxWithSize(x, y, width, height) {
26 | return [
27 | move(x, y),
28 | cut(x += width, y),
29 | cut(x, y -= height),
30 | cut(x -= width, y),
31 | cut(x, y += height),
32 | ]
33 | }
34 |
35 | function drawBox(x, y) {
36 | return drawBoxWithSize(x, y, 10, 10);
37 | }
38 |
39 | function autoStockPointTest(lowerLeftX, lowerLeftY, upperRightX, upperRightY, targetId) {
40 | // lower left, upper right, origin vector
41 | return newTest(new Vector(lowerLeftX, lowerLeftY), new Vector(upperRightX, upperRightY), new Vector(0, 0), targetId)
42 | .withSection(JET_MODE_ETCHING, {}, drawArrow(lowerLeftX, upperRightY));
43 | }
44 |
45 | function autoStockPointInchesTest(lowerLeftX, lowerLeftY, upperRightX, upperRightY, targetId) {
46 | var mmToInches = 0.0393701;
47 |
48 | // lower left, upper right, origin vector
49 | return newTest(new Vector(lowerLeftX * mmToInches, lowerLeftY * mmToInches),
50 | new Vector(upperRightX * mmToInches, upperRightY * mmToInches),
51 | new Vector(0, 0), targetId)
52 | .withUnits(IN)
53 | .withSection(JET_MODE_ETCHING, {}, drawArrow(lowerLeftX * mmToInches, upperRightY * mmToInches, mmToInches));
54 | }
55 |
56 | function useWorkAreaTest(lowerLeftX, lowerLeftY, upperRightX, upperRightY, targetId) {
57 | // lower left, upper right, origin vector
58 | return newTest(new Vector(lowerLeftX, lowerLeftY), new Vector(upperRightX, upperRightY), new Vector(0, 0), targetId)
59 | .withSection(JET_MODE_ETCHING, {}, drawArrow(lowerLeftX, upperRightY))
60 | .withProperties({useWorkArea: true,
61 | workAreaWidth: 20,
62 | workAreaHeight: 20});
63 | }
64 |
65 | function violateWorkAreaTest(boxWidth, boxHeight, targetId) {
66 | // lower left, upper right, origin vector
67 | return newTest(new Vector(0, 0), new Vector(boxWidth, boxHeight), new Vector(0, 0), targetId)
68 | .withSection(JET_MODE_ETCHING, {}, drawBoxWithSize(0, boxHeight, boxWidth, boxHeight))
69 | .withSection(JET_MODE_ETCHING, {}, drawArrow(0, boxHeight))
70 | .withProperties({useWorkArea: true,
71 | workAreaWidth: 20,
72 | workAreaHeight: 20});
73 | }
74 |
75 | function colorTest(colorCount) {
76 | var columns = 15;
77 | var width = Math.ceil(13 * columns) - 3;
78 | var height = Math.ceil(13 * Math.ceil(colorCount / columns)) - 3;
79 |
80 | // lower left, upper right, origin vector
81 | var element = "colors-" + colorCount;
82 | var test = newTest(new Vector(0, 0), new Vector(width, height), new Vector(0, 0), element);
83 |
84 | var x = 0, y = height;
85 |
86 | var rows = 0;
87 | var j = 0;
88 | for (var i = 0; i < colorCount; i ++) {
89 | test.withSection(JET_MODE_ETCHING, {}, drawBox(x, y));
90 | x += 13;
91 | j++;
92 | if (j >= columns) {
93 | j = 0;
94 | x = 0;
95 | y -= 13;
96 | rows++;
97 | //move(0, 10 - (rows * (13)));
98 |
99 | }
100 | }
101 |
102 | test.validate = function () {
103 | pathElements = document.getElementById(element).getElementsByTagName("path");
104 | colorMap = {};
105 | for (var i = 0; i < pathElements.length; i++) {
106 | path = pathElements[i];
107 | var fill = path.getAttribute("fill");
108 | if (!colorMap[fill]) {
109 | colorMap[fill] = true;
110 | } else {
111 | return "Duplicate colors detected at index: " + i + " color: " + fill;
112 | }
113 | }
114 | }
115 |
116 | return test;
117 | }
118 |
119 | (function runAllTests() {
120 | var tests = [];
121 |
122 | // Auto Stock Point
123 | tests.push(autoStockPointTest(0, -10, 10, 0, 'auto-stock-point-upper-left'));
124 | tests.push(autoStockPointTest(-5, -10, 5, 0, 'auto-stock-point-upper-middle'));
125 | tests.push(autoStockPointTest(-10, -10, 0, 0, 'auto-stock-point-upper-right'));
126 | tests.push(autoStockPointTest(0, -5, 10, 5, 'auto-stock-point-middle-left'));
127 | tests.push(autoStockPointTest(-5, -5, 5, 5, 'auto-stock-point-middle-middle'));
128 | tests.push(autoStockPointTest(-10, -5, 0, 5, 'auto-stock-point-middle-right'));
129 | tests.push(autoStockPointTest(0, 0, 10, 10, 'auto-stock-point-lower-left'));
130 | tests.push(autoStockPointTest(-5, 0, 5, 10, 'auto-stock-point-lower-middle'));
131 | tests.push(autoStockPointTest(-10, 0, 0, 10, 'auto-stock-point-lower-right'));
132 |
133 | // Auto Stock Point
134 | tests.push(autoStockPointInchesTest(0, -10, 10, 0, 'auto-stock-point-in-upper-left'));
135 | tests.push(autoStockPointInchesTest(-5, -10, 5, 0, 'auto-stock-point-in-upper-middle'));
136 | tests.push(autoStockPointInchesTest(-10, -10, 0, 0, 'auto-stock-point-in-upper-right'));
137 | tests.push(autoStockPointInchesTest(0, -5, 10, 5, 'auto-stock-point-in-middle-left'));
138 | tests.push(autoStockPointInchesTest(-5, -5, 5, 5, 'auto-stock-point-in-middle-middle'));
139 | tests.push(autoStockPointInchesTest(-10, -5, 0, 5, 'auto-stock-point-in-middle-right'));
140 | tests.push(autoStockPointInchesTest(0, 0, 10, 10, 'auto-stock-point-in-lower-left'));
141 | tests.push(autoStockPointInchesTest(-5, 0, 5, 10, 'auto-stock-point-in-lower-middle'));
142 | tests.push(autoStockPointInchesTest(-10, 0, 0, 10, 'auto-stock-point-in-lower-right'));
143 |
144 | // use work area with auto stock point detection
145 | tests.push(useWorkAreaTest(0, -10, 10, 0, 'use-work-area-upper-left'));
146 | tests.push(useWorkAreaTest(-5, -10, 5, 0, 'use-work-area-upper-middle'));
147 | tests.push(useWorkAreaTest(-10, -10, 0, 0, 'use-work-area-upper-right'));
148 | tests.push(useWorkAreaTest(0, -5, 10, 5, 'use-work-area-middle-left'));
149 | tests.push(useWorkAreaTest(-5, -5, 5, 5, 'use-work-area-middle-middle'));
150 | tests.push(useWorkAreaTest(-10, -5, 0, 5, 'use-work-area-middle-right'));
151 | tests.push(useWorkAreaTest(0, 0, 10, 10, 'use-work-area-lower-left'));
152 | tests.push(useWorkAreaTest(-5, 0, 5, 10, 'use-work-area-lower-middle'));
153 | tests.push(useWorkAreaTest(-10, 0, 0, 10, 'use-work-area-lower-right'));
154 |
155 | // work area too small
156 | tests.push(violateWorkAreaTest(50, 10, 'violate-work-area-width'));
157 | tests.push(violateWorkAreaTest(10, 50, 'violate-work-area-height'));
158 |
159 | // color tests
160 | tests.push(colorTest(6));
161 | tests.push(colorTest(15));
162 | tests.push(colorTest(50));
163 | tests.push(colorTest(120));
164 | tests.push(colorTest(256));
165 | tests.push(colorTest(1024));
166 |
167 | for (var i = 0; i < tests.length; i++) {
168 | var test = tests[i];
169 | test.run();
170 | var result = test.validate();
171 | if (result) {
172 | console.error(result);
173 | }
174 | }
175 | })();
--------------------------------------------------------------------------------
/PostProcessorTestDriver.js:
--------------------------------------------------------------------------------
1 | // all the constants that you get access to (They arent all used but they were easy to get...)
2 | var NUL = "\u0000",
3 | SOH = "\u0001",
4 | STX = "\u0002",
5 | ETX = "\u0003",
6 | EOT = "\u0004",
7 | ENQ = "\u0005",
8 | ACK = "\u0006",
9 | BEL = "\u0007",
10 | BS = "\b",
11 | TAB = "\t",
12 | LF = "\n",
13 | VT = "\u000b",
14 | FF = "\f",
15 | CR = "\r",
16 | SO = "\u000e",
17 | SI = "\u000f",
18 | DLE = "\u0010",
19 | DC1 = "\u0011",
20 | DC2 = "\u0012",
21 | DC3 = "\u0013",
22 | DC4 = "\u0014",
23 | NAK = "\u0015",
24 | SYN = "\u0016",
25 | ETB = "\u0017",
26 | CAN = "\u0018",
27 | EM = "\u0019",
28 | SUB = "\u001a",
29 | ESC = "\u001b",
30 | FS = "\u001c",
31 | GS = "\u001d",
32 | RS = "\u001e",
33 | US = "\u001f",
34 | EOL = "\r\n",
35 | SP = " ",
36 | PATH_SEPARATOR = "/",
37 | IN = 0,
38 | MM = 1,
39 | ORIGINAL_UNIT = -1,
40 | DEG = 57.29577951308232,
41 | PLANE_XY = 0,
42 | PLANE_ZX = 1,
43 | PLANE_YZ = 2,
44 | X = 0,
45 | Y = 1,
46 | Z = 2,
47 | TYPE_MILLING = 0,
48 | TYPE_TURNING = 1,
49 | TYPE_WIRE = 2,
50 | TYPE_JET = 3,
51 | TYPE_ADDITIVE = 4,
52 | CAPABILITY_MILLING = 1,
53 | CAPABILITY_TURNING = 2,
54 | CAPABILITY_WIRE = 4,
55 | CAPABILITY_SETUP_SHEET = 8,
56 | CAPABILITY_INTERMEDIATE = 16,
57 | CAPABILITY_JET = 32,
58 | CAPABILITY_CASCADING = 64,
59 | CAPABILITY_ADDITIVE = 128,
60 | JET_MODE_THROUGH = 0,
61 | JET_MODE_ETCHING = 1,
62 | JET_MODE_VAPORIZE = 2,
63 | SPINDLE_PRIMARY = 0,
64 | SPINDLE_SECONDARY = 1,
65 | FEED_PER_MINUTE = 0,
66 | FEED_PER_REVOLUTION = 1,
67 | SPINDLE_CONSTANT_SPINDLE_SPEED = 0,
68 | SPINDLE_CONSTANT_SURFACE_SPEED = 1,
69 | TOOL_AXIS_X = 0,
70 | TOOL_AXIS_Y = 1,
71 | TOOL_AXIS_Z = 2,
72 | RADIUS_COMPENSATION_OFF = 0,
73 | RADIUS_COMPENSATION_LEFT = 1,
74 | RADIUS_COMPENSATION_RIGHT = 2,
75 | SINGULARITY_LINEARIZE_OFF = 0,
76 | SINGULARITY_LINEARIZE_LINEAR = 1,
77 | SINGULARITY_LINEARIZE_ROTARY = 2,
78 | COOLANT_OFF = 0,
79 | COOLANT_FLOOD = 1,
80 | COOLANT_MIST = 2,
81 | COOLANT_TOOL = 3,
82 | COOLANT_THROUGH_TOOL = 3,
83 | COOLANT_AIR = 4,
84 | COOLANT_AIR_THROUGH_TOOL = 5,
85 | COOLANT_SUCTION = 6,
86 | COOLANT_FLOOD_MIST = 7,
87 | COOLANT_FLOOD_THROUGH_TOOL = 8,
88 | EULER_XZX_S = 0,
89 | EULER_YXY_S = 1,
90 | EULER_ZYZ_S = 2,
91 | EULER_XZX_R = 3,
92 | EULER_YXY_R = 4,
93 | EULER_ZYZ_R = 5,
94 | EULER_XZY_S = 6,
95 | EULER_YXZ_S = 7,
96 | EULER_ZYX_S = 8,
97 | EULER_YZX_R = 9,
98 | EULER_ZXY_R = 10,
99 | EULER_XYZ_R = 11,
100 | EULER_XYX_S = 12,
101 | EULER_YZY_S = 13,
102 | EULER_ZXZ_S = 14,
103 | EULER_XYX_R = 15,
104 | EULER_YZY_R = 16,
105 | EULER_ZXZ_R = 17,
106 | EULER_XYZ_S = 18,
107 | EULER_YZX_S = 19,
108 | EULER_ZXY_S = 20,
109 | EULER_ZYX_R = 21,
110 | EULER_XZY_R = 22,
111 | EULER_YXZ_R = 23,
112 | MATERIAL_UNSPECIFIED = 0,
113 | MATERIAL_HSS = 1,
114 | MATERIAL_TI_COATED = 2,
115 | MATERIAL_CARBIDE = 3,
116 | MATERIAL_CERAMICS = 4,
117 | TOOL_UNSPECIFIED = 0,
118 | TOOL_DRILL = 1,
119 | TOOL_DRILL_CENTER = 2,
120 | TOOL_DRILL_SPOT = 3,
121 | TOOL_DRILL_BLOCK = 4,
122 | TOOL_MILLING_END_FLAT = 5,
123 | TOOL_MILLING_END_BALL = 6,
124 | TOOL_MILLING_END_BULLNOSE = 7,
125 | TOOL_MILLING_CHAMFER = 8,
126 | TOOL_MILLING_FACE = 9,
127 | TOOL_MILLING_SLOT = 10,
128 | TOOL_MILLING_RADIUS = 11,
129 | TOOL_MILLING_DOVETAIL = 12,
130 | TOOL_MILLING_TAPERED = 13,
131 | TOOL_MILLING_LOLLIPOP = 14,
132 | TOOL_TAP_RIGHT_HAND = 15,
133 | TOOL_TAP_LEFT_HAND = 16,
134 | TOOL_REAMER = 17,
135 | TOOL_BORING_BAR = 18,
136 | TOOL_COUNTER_BORE = 19,
137 | TOOL_COUNTER_SINK = 20,
138 | TOOL_HOLDER_ONLY = 21,
139 | TOOL_TURNING_GENERAL = 22,
140 | TOOL_TURNING_THREADING = 23,
141 | TOOL_TURNING_GROOVING = 24,
142 | TOOL_TURNING_BORING = 25,
143 | TOOL_TURNING_CUSTOM = 26,
144 | TOOL_PROBE = 27,
145 | TOOL_WIRE = 28,
146 | TOOL_WATER_JET = 29,
147 | TOOL_LASER_CUTTER = 30,
148 | TOOL_WELDER = 31,
149 | TOOL_GRINDER = 32,
150 | TOOL_MILLING_FORM = 33,
151 | TOOL_ROTARY_BROACH = 34,
152 | TOOL_SLOT_BROACH = 35,
153 | TOOL_PLASMA_CUTTER = 36,
154 | TOOL_MARKER = 37,
155 | TOOL_MILLING_THREAD = 38,
156 | TOOL_COMPENSATION_INSERT_CENTER = 0,
157 | TOOL_COMPENSATION_TIP = 1,
158 | TOOL_COMPENSATION_TIP_CENTER = 2,
159 | TOOL_COMPENSATION_TIP_TANGENT = 3,
160 | TURNING_INSERT_USER_DEFINED = 0,
161 | TURNING_INSERT_ISO_A = 1,
162 | TURNING_INSERT_ISO_B = 2,
163 | TURNING_INSERT_ISO_C = 3,
164 | TURNING_INSERT_ISO_D = 4,
165 | TURNING_INSERT_ISO_E = 5,
166 | TURNING_INSERT_ISO_H = 6,
167 | TURNING_INSERT_ISO_K = 7,
168 | TURNING_INSERT_ISO_L = 8,
169 | TURNING_INSERT_ISO_M = 9,
170 | TURNING_INSERT_ISO_O = 10,
171 | TURNING_INSERT_ISO_P = 11,
172 | TURNING_INSERT_ISO_R = 12,
173 | TURNING_INSERT_ISO_S = 13,
174 | TURNING_INSERT_ISO_T = 14,
175 | TURNING_INSERT_ISO_V = 15,
176 | TURNING_INSERT_ISO_W = 16,
177 | TURNING_INSERT_GROOVE_ROUND = 17,
178 | TURNING_INSERT_GROOVE_RADIUS = 18,
179 | TURNING_INSERT_GROOVE_SQUARE = 19,
180 | TURNING_INSERT_GROOVE_CHAMFER = 20,
181 | TURNING_INSERT_GROOVE_40DEG = 21,
182 | TURNING_INSERT_THREAD_ISO_DOUBLE_FULL = 22,
183 | TURNING_INSERT_THREAD_ISO_TRIPLE_FULL = 23,
184 | TURNING_INSERT_THREAD_UTS_DOUBLE_FULL = 24,
185 | TURNING_INSERT_THREAD_UTS_TRIPLE_FULL = 25,
186 | TURNING_INSERT_THREAD_ISO_DOUBLE_VPROFILE = 26,
187 | TURNING_INSERT_THREAD_ISO_TRIPLE_VPROFILE = 27,
188 | TURNING_INSERT_THREAD_UTS_DOUBLE_VPROFILE = 28,
189 | TURNING_INSERT_THREAD_UTS_TRIPLE_VPROFILE = 29,
190 | HOLDER_NONE = 0,
191 | HOLDER_ISO_A = 1,
192 | HOLDER_ISO_B = 2,
193 | HOLDER_ISO_C = 3,
194 | HOLDER_ISO_D = 4,
195 | HOLDER_ISO_E = 5,
196 | HOLDER_ISO_F = 6,
197 | HOLDER_ISO_G = 7,
198 | HOLDER_ISO_H = 8,
199 | HOLDER_ISO_J = 9,
200 | HOLDER_ISO_K = 10,
201 | HOLDER_ISO_L = 11,
202 | HOLDER_ISO_M = 12,
203 | HOLDER_ISO_N = 13,
204 | HOLDER_ISO_P = 14,
205 | HOLDER_ISO_Q = 15,
206 | HOLDER_ISO_R = 16,
207 | HOLDER_ISO_S = 17,
208 | HOLDER_ISO_T = 18,
209 | HOLDER_ISO_U = 19,
210 | HOLDER_ISO_V = 20,
211 | HOLDER_ISO_W = 21,
212 | HOLDER_ISO_Y = 22,
213 | HOLDER_OFFSET_PROFILE = 23,
214 | HOLDER_STRAIGHT_PROFILE = 24,
215 | HOLDER_GROOVE_EXTERNAL = 25,
216 | HOLDER_GROOVE_INTERNAL = 26,
217 | HOLDER_GROOVE_FACE = 27,
218 | HOLDER_THREAD_STRAIGHT = 28,
219 | HOLDER_THREAD_OFFSET = 29,
220 | HOLDER_THREAD_FACE = 30,
221 | HOLDER_BORING_BAR_ISO_F = 31,
222 | HOLDER_BORING_BAR_ISO_G = 32,
223 | HOLDER_BORING_BAR_ISO_J = 33,
224 | HOLDER_BORING_BAR_ISO_K = 34,
225 | HOLDER_BORING_BAR_ISO_L = 35,
226 | HOLDER_BORING_BAR_ISO_P = 36,
227 | HOLDER_BORING_BAR_ISO_Q = 37,
228 | HOLDER_BORING_BAR_ISO_S = 38,
229 | HOLDER_BORING_BAR_ISO_U = 39,
230 | HOLDER_BORING_BAR_ISO_W = 40,
231 | HOLDER_BORING_BAR_ISO_Y = 41,
232 | HOLDER_BORING_BAR_ISO_X = 42,
233 | HAS_PARAMETER = 1,
234 | HAS_RAPID = 2,
235 | HAS_LINEAR = 4,
236 | HAS_RAPID_5D = 8,
237 | HAS_LINEAR_5D = 16,
238 | HAS_MULTIAXIS = 24,
239 | HAS_DWELL = 32,
240 | HAS_CIRCULAR_CW = 64,
241 | HAS_CIRCULAR_CCW = 128,
242 | HAS_CIRCULAR = 192,
243 | HAS_CYCLE = 256,
244 | HAS_WELL_KNOWN_COMMAND = 512,
245 | HAS_MACHINE_COMMAND = 1024,
246 | HAS_SPINDLE_SPEED = 2048,
247 | HAS_COOLANT = 4096,
248 | HAS_SPLINE = 8192,
249 | HAS_COMMENT = 16384,
250 | RECORD_INVALID = 0,
251 | RECORD_TOOL_CHANGE = 1,
252 | RECORD_WELL_KNOWN_COMMAND = 2,
253 | RECORD_MACHINE_COMMAND = 3,
254 | RECORD_SPINDLE_SPEED = 4,
255 | RECORD_COOLANT = 5,
256 | RECORD_PARAMETER = 6,
257 | RECORD_LINEAR = 7,
258 | RECORD_LINEAR_5D = 8,
259 | RECORD_LINEAR_ZXN = 9,
260 | RECORD_CIRCULAR = 10,
261 | RECORD_SPLINE = 11,
262 | RECORD_BEZIER = 12,
263 | RECORD_NURBS = 13,
264 | RECORD_DWELL = 14,
265 | RECORD_CYCLE = 15,
266 | RECORD_CYCLE_OFF = 16,
267 | RECORD_COMMENT = 17,
268 | RECORD_WIDE_COMMENT = 18,
269 | RECORD_PASS_THROUGH = 19,
270 | RECORD_WIDE_PASS_THROUGH = 20,
271 | RECORD_SKIP = 21,
272 | RECORD_OPERATION = 22,
273 | RECORD_OPERATION_END = 23,
274 | COMMAND_STOP = 1,
275 | COMMAND_OPTIONAL_STOP = 2,
276 | COMMAND_END = 3,
277 | COMMAND_SPINDLE_CLOCKWISE = 4,
278 | COMMAND_SPINDLE_COUNTERCLOCKWISE = 5,
279 | COMMAND_START_SPINDLE = 6,
280 | COMMAND_STOP_SPINDLE = 7,
281 | COMMAND_ORIENTATE_SPINDLE = 8,
282 | COMMAND_LOAD_TOOL = 9,
283 | COMMAND_COOLANT_ON = 10,
284 | COMMAND_COOLANT_OFF = 11,
285 | COMMAND_ACTIVATE_SPEED_FEED_SYNCHRONIZATION = 12,
286 | COMMAND_DEACTIVATE_SPEED_FEED_SYNCHRONIZATION = 13,
287 | COMMAND_ACTIVATE_SPEED_FEED_SYNCHORNIZATION = 12,
288 | COMMAND_DEACTIVATE_SPEED_FEED_SYNCHORNIZATION = 13,
289 | COMMAND_LOCK_MULTI_AXIS = 14,
290 | COMMAND_UNLOCK_MULTI_AXIS = 15,
291 | COMMAND_EXACT_STOP = 16,
292 | COMMAND_START_CHIP_TRANSPORT = 17,
293 | COMMAND_STOP_CHIP_TRANSPORT = 18,
294 | COMMAND_OPEN_DOOR = 19,
295 | COMMAND_CLOSE_DOOR = 20,
296 | COMMAND_BREAK_CONTROL = 21,
297 | COMMAND_TOOL_MEASURE = 22,
298 | COMMAND_CALIBRATE = 23,
299 | COMMAND_VERIFY = 24,
300 | COMMAND_CLEAN = 25,
301 | COMMAND_ALARM = 26,
302 | COMMAND_ALERT = 27,
303 | COMMAND_CHANGE_PALLET = 28,
304 | COMMAND_POWER_ON = 29,
305 | COMMAND_POWER_OFF = 30,
306 | COMMAND_MAIN_CHUCK_OPEN = 31,
307 | COMMAND_MAIN_CHUCK_CLOSE = 32,
308 | COMMAND_SECONDARY_CHUCK_OPEN = 33,
309 | COMMAND_SECONDARY_CHUCK_CLOSE = 34,
310 | COMMAND_SECONDARY_SPINDLE_SYNCHRONIZATION_ACTIVATE = 35,
311 | COMMAND_SECONDARY_SPINDLE_SYNCHRONIZATION_DEACTIVATE = 36,
312 | COMMAND_SYNC_CHANNELS = 37,
313 | MOVEMENT_RAPID = 0,
314 | MOVEMENT_LEAD_IN = 1,
315 | MOVEMENT_CUTTING = 2,
316 | MOVEMENT_LEAD_OUT = 3,
317 | MOVEMENT_LINK_TRANSITION = 4,
318 | MOVEMENT_BRIDGING = 4,
319 | MOVEMENT_LINK_DIRECT = 5,
320 | MOVEMENT_RAMP_HELIX = 6,
321 | MOVEMENT_PIERCE_CIRCULAR = 6,
322 | MOVEMENT_RAMP_PROFILE = 7,
323 | MOVEMENT_PIERCE_PROFILE = 7,
324 | MOVEMENT_RAMP_ZIG_ZAG = 8,
325 | MOVEMENT_PIERCE_LINEAR = 8,
326 | MOVEMENT_RAMP = 9,
327 | MOVEMENT_PLUNGE = 10,
328 | MOVEMENT_PIERCE = 10,
329 | MOVEMENT_PREDRILL = 11,
330 | MOVEMENT_EXTENDED = 12,
331 | MOVEMENT_REDUCED = 13,
332 | MOVEMENT_FINISH_CUTTING = 14,
333 | MOVEMENT_HIGH_FEED = 15,
334 | PARAMETER_SPATIAL = 0,
335 | PARAMETER_ANGLE = 1,
336 | PARAMETER_ENUM = 2,
337 | HIGH_FEED_NO_MAPPING = 0,
338 | HIGH_FEED_MAP_MULTI = 1,
339 | HIGH_FEED_MAP_XY_Z = 2,
340 | HIGH_FEED_MAP_ANGULAR = 3,
341 | HIGH_FEED_MAP_CLEARANCE = 4,
342 | HIGH_FEED_MAP_ANY = 5,
343 | FLAG_CYCLE_REPEAT_PASS = 1;
344 |
345 | var currentSection = null;
346 | var movement = null;
347 | var currentTest = null;
348 | var unit = MM;
349 | var radiusCompensation = RADIUS_COMPENSATION_OFF;
350 |
351 | // dummy vector constructor
352 | function Vector(x, y, z) {
353 | this.x = x;
354 | this.y = y;
355 | this.z = z || 0; // auto set Z to zero
356 | }
357 |
358 | function line(isRapid, x, y) {
359 | return {
360 | isRapid: isRapid,
361 | x: x,
362 | y: y
363 | }
364 | }
365 |
366 | function cut(x, y) {
367 | return line(false, x, y);
368 | }
369 |
370 | function move(x, y) {
371 | return line(true, x, y);
372 | }
373 |
374 | /**
375 | * Tests are constructed by describing the lower left, upper right and origin vectors of the work
376 | */
377 | function newTest(lowerLeftVector, upperRightVector, originVector, targetId) {
378 | var sections = [];
379 | // this is the current position
380 | var x = originVector.x, y = originVector.y;
381 | var outputLines = [];
382 | var customProperties = {};
383 | var testUnits = MM;
384 |
385 | function applyProperties() {
386 | for (let [key, value] of new Map(Object.entries(customProperties))) {
387 | properties[key] = value;
388 | }
389 | }
390 |
391 | function processLines(lines) {
392 | for (var i = 0; i < lines.length; i++) {
393 | processLine(lines[i]);
394 | }
395 | }
396 |
397 | function processLine(line) {
398 | //console.log('line', line);
399 | if (line.isRapid === true) {
400 | movement = MOVEMENT_RAPID;
401 | onPower(false);
402 | onRapid(line.x, line.y, 0);
403 | }
404 | else {
405 | movement = MOVEMENT_CUTTING;
406 | onPower(true);
407 | onLinear(line.x, line.y, 0);
408 | }
409 | x = line.x;
410 | y = line.y;
411 | }
412 |
413 | // returned object becomes the current test as well as the interface for configuring the test
414 | var test = {
415 | getNumberOfSections: function getNumberOfSections() {
416 | return sections.length;
417 | },
418 | getWorkpiece: function getWorkpiece() {
419 | return {
420 | upper: upperRightVector,
421 | lower: lowerLeftVector
422 | }
423 | },
424 | withSection: function (jetMode, sectionParameters, lines) {
425 | var id = sections.length;
426 | var section = {
427 | getId: function getId() {
428 | return id;
429 | },
430 | jetMode: jetMode,
431 | workPlane: {
432 | forward: new Vector(0, 0, 1)
433 | },
434 | hasParameter: function hasParameter(key) {
435 | return !!sectionParameters[key];
436 | },
437 | getParameter: function getParameter(key) {
438 | return sectionParameters[key];
439 | },
440 | lines: lines
441 | }
442 |
443 | sections.push(section);
444 | return test;
445 | },
446 | withProperties(props) {
447 | customProperties = props;
448 | return test;
449 | },
450 | withUnits: function withUnits(units) {
451 | testUnits = units === MM ? MM : IN;
452 | return test;
453 | },
454 | getCurrentPosition: function() {
455 | return {x: x, y: y}
456 | },
457 | writeln: function writeln(line) {
458 | outputLines.push(line);
459 | },
460 | run: function run() {
461 | // assign global current test
462 | currentTest = test;
463 | unit = testUnits;
464 | // simlulate the CAM system setting user configurable properties
465 | resetProperties();
466 | applyProperties();
467 | onOpen();
468 | for (var i = 0; i < sections.length; i++) {
469 | currentSection = sections[i];
470 | onSection();
471 | processLines(currentSection.lines);
472 | onSectionEnd();
473 | currentSection = null;
474 | movement = null;
475 | }
476 | onClose();
477 | currentTest = null;
478 |
479 | // run all sections...
480 | var svg = outputLines.join("\n")
481 | //console.log(svg);
482 | document.getElementById(targetId).innerHTML = svg;
483 | },
484 | validate: function () {}
485 | };
486 |
487 | return test;
488 | }
489 |
490 | // we dont need to test anything but the laser
491 | var tool = {
492 | type: TOOL_LASER_CUTTER
493 | }
494 |
495 | // real working degrees to radians function
496 | function toRad (angle) {
497 | return angle * (Math.PI / 180);
498 | }
499 |
500 | // The return type is pretty extensive but only 1 function is used in out post: format(number)
501 | // https://cam.autodesk.com/posts/reference/classFormatNumber.html#a47d304db32feae2b6dbfb5281c153460
502 | // Returns the string for the specified value
503 | function createFormat(options) {
504 | return {
505 | format: function format(number) {
506 | return '' + (number * options.scale).toFixed(options.decimals);
507 | }
508 | };
509 | }
510 |
511 | // get section count, TODO: make this drivable from the test data input
512 | function getNumberOfSections() {
513 | return currentTest.getNumberOfSections();
514 | }
515 |
516 | function hasParameter(key) {
517 | return currentSection.hasParameter(key);
518 | }
519 | function getParameter(key) {
520 | return currentSection.getParameter(key);
521 | }
522 |
523 | // sure, why not...
524 | function isSameDirection() {
525 | return true;
526 | }
527 |
528 | // noop
529 | function setRotation() {}
530 |
531 | function error(str) {
532 | console.error(str);
533 | }
534 |
535 | function log(str) {
536 | console.log(str);
537 | }
538 |
539 | function writeln(str) {
540 | currentTest.writeln(str);
541 | }
542 |
543 | // i18n FTW!
544 | function localize(str) {
545 | return str;
546 | }
547 |
548 | function getWorkpiece() {
549 | return currentTest.getWorkpiece();
550 | }
551 |
552 | function getCurrentPosition() {
553 | return currentTest.getCurrentPosition();
554 | }
555 |
556 | function setCodePage(codePage) {
557 | }
558 |
--------------------------------------------------------------------------------
/GlowforgeColorific.cps:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright (C) 2018 by Autodesk, Inc.
3 | All rights reserved.
4 | */
5 |
6 | description = "Glowforge";
7 | vendor = "Glowforge";
8 | vendorUrl = "https://www.glowforge.com";
9 | legal = "Copyright (C) 2018 by Autodesk, Inc.";
10 | certificationLevel = 2;
11 |
12 | longDescription = "Generic post for Glowforge laser. The post will output the toolpath as SVG graphics which can then be uploaded directly to Glowforge.";
13 |
14 | extension = "svg";
15 | mimetype = "image/svg+xml";
16 | setCodePage("utf-8");
17 |
18 | capabilities = CAPABILITY_JET;
19 |
20 | minimumCircularSweep = toRad(0.01);
21 | maximumCircularSweep = toRad(90); // avoid potential center calculation errors for CNC
22 | allowHelicalMoves = true;
23 | allowedCircularPlanes = (1 << PLANE_XY); // only XY arcs
24 |
25 | // global prooperties variable
26 | properties = null;
27 | // made available for testing
28 | function resetProperties() {
29 | properties = {
30 | lineWidth: 0.1, // how wide lines are in the SVG
31 | margin: 2, // margin in mm
32 | checkForRadiusCompensation: false, // if enabled throw an error if compensation in control is used
33 | doNotFlipYAxis: false,
34 | useWorkArea: false, // center the toolpath in the machines work area, off by default
35 | autoStockPoint: true, // automatically translate the output paths for strage stock points, see the whole image no matter what you select
36 | // Glowforge Cutting area: aprox. 19.5″ (495 mm) wide and 11″ (279 mm) deep
37 | workAreaWidth: 495, // width in mm used when useWorkArea is enabled
38 | workAreaHeight: 279, // height in mm used when useWorkArea is enabled
39 | };
40 | }
41 | resetProperties();
42 |
43 | // user-defined property definitions
44 | propertyDefinitions = {
45 | lineWidth: {title: "SVG Stroke Width(mm)", description: "The width of lines in the SVG in mm.", type: "number"},
46 | margin: {title: "Margin(mm)", description: "Sets the margin in mm when 'Crop to Workpiece' is used.", type: "number"},
47 | checkForRadiusCompensation: {title: "Check Sideways Comp.", description: "Check every opperation for Sideways Compensation 'In Computer'. If this is not configured, throw an error.", type: "boolean"},
48 | doNotFlipYAxis: {title: "Flip Model", description: "If your part is upside down, check this box to flip it over. (Tip: checking 'Flip Z Axis' in the CAM setup also fixes this)", type: "boolean"},
49 | useWorkArea: {title:"Use Work Area", description:"Center the toolpaths in an image the size of the defined Work Area.", type:"boolean"},
50 | autoStockPoint: {title:"Auto Stock Point", description:"Make the final image completly visible reguardless of the selected stock point.", type:"boolean"},
51 | workAreaWidth: {title:"Work Area Width(mm", description:"Work Area Width in mm, used when 'Crop to Workpiece' is disabled. Typically the max cutting width of the Glowforge.", type:"number"},
52 | workAreaHeight: {title:"Work Area Height(mm)", description:"Height in mm, used when 'Crop to Workpiece' is disabled. Typically the max cutting height of the Glowforge.", type:"number"},
53 | };
54 |
55 | var POST_URL = "https://cam.autodesk.com/hsmposts?p=glowforge";
56 |
57 | // Recommended colors for color mapping.
58 | var COLOR_GREEN = "1FB714";
59 | var COLOR_YELLOW = "FBF305";
60 | var COLOR_DARK_GREEN = "006412";
61 | var COLOR_ORANGE = "FF6403";
62 | var COLOR_BROWN = "562C05";
63 | var COLOR_RED = "DD0907";
64 | var COLOR_TAN = "90713A";
65 | var COLOR_MAGENTA = "F20884";
66 | var COLOR_PURPLE = "4700A5";
67 | var COLOR_BLUE = "0000D3";
68 | var COLOR_CYAN = "02ABEA";
69 | var COLOR_BLACK = "000000";
70 |
71 | var COLOR_CYCLE = [COLOR_CYAN,
72 | COLOR_MAGENTA,
73 | COLOR_YELLOW,
74 | COLOR_RED,
75 | COLOR_GREEN,
76 | COLOR_BLUE,
77 | COLOR_ORANGE,
78 | COLOR_DARK_GREEN,
79 | COLOR_PURPLE,
80 | COLOR_BROWN,
81 | COLOR_TAN,
82 | COLOR_BLACK];
83 |
84 | // dont pick fewer colors than this
85 | var MIN_COLORS = 6;
86 |
87 | /** Global State **/
88 | function reset() {
89 | return {
90 | // ConverterFormat: converted from IN to MM as needed
91 | xyzFormat: createFormat({decimals:(3), scale:(unit === IN) ? 25.4 : 1}),
92 | // clamp to 3 decimals but dont convert
93 | decimalFormat: createFormat({decimals:(3), scale: 1}),
94 | // selected colors to use for this run
95 | activeColorCycle: null,
96 | // the hex string of the current color
97 | currentHexColor: null,
98 | // the index of the current color
99 | currentColorIndex: -1,
100 | // track if the next path element can be a move command
101 | allowMoveCommandNext: null,
102 | // is the work area too small?
103 | workAreaTooSmall: false,
104 | // is the llaser currently on?
105 | isLaserOn: false
106 | };
107 | }
108 | var state = null;
109 |
110 | // select a subset of colors so our preferred color pallet is used (and not simply the color with the lowest hex value first)
111 | function selectColors() {
112 | var requiredColors = Math.max(MIN_COLORS, getNumberOfSections()); // makes sure that more than enough colors get made
113 | var finalColorCycle = [];
114 | var numColors = COLOR_CYCLE.length;
115 |
116 | // if the number of default colors is too small, we will build lighter shades of those colors to fill in the extra needed colors:
117 | var alphaSteps = Math.ceil(requiredColors / numColors);
118 | var alphaStepSize = 1 / (alphaSteps + 1); // + 1 stops the last alpha blend stage for all colors being FFFFFF
119 | var alphaStepIndex = 0;
120 | var colorIndex = 0;
121 | var finalColorCycle = [];
122 |
123 | for (var i = 0; i < requiredColors; i++) {
124 | finalColorCycle.push(alphaBlendHexColor(COLOR_CYCLE[colorIndex], 1 - (alphaStepSize * alphaStepIndex)));
125 | colorIndex += 1; // next color
126 | if (colorIndex >= numColors) {
127 | colorIndex = 0; // start back at the first color
128 | alphaStepIndex++; // next lighter shade
129 | }
130 | }
131 |
132 | // reset all color related variables to allow re-runs
133 | state.activeColorCycle = sortColors(finalColorCycle);
134 | }
135 |
136 | // Glowforge doesn't respect the order of operations in the SVG file, it re-sorts them by the hex color value in ascending order
137 | // so here the color cycle is sorted to preserve op order from CAM.
138 | function sortColors(inputColors) {
139 | var mappedColors = inputColors.map(function buildHexColors(color, i) {
140 | return {hexColor: '#' + color, hexValue: parseInt(color, 16)};
141 | });
142 |
143 | mappedColors.sort(function compareHexValues(a, b) {
144 | if (a.hexValue < b.hexValue) {
145 | return -1;
146 | }
147 | if (a.hexValue > b.hexValue) {
148 | return 1;
149 | }
150 | return 0;
151 | });
152 |
153 | return mappedColors.map(function reduceToHexColor(color, i) {
154 | return color.hexColor;
155 | });
156 | }
157 |
158 | // returns a hex color that is alphaPercent lighter than the input color
159 | function alphaBlendHexColor(hexColorString, alphaPercent) {
160 | // alphaPercent needs to be converted from a float to a fraction of 255
161 | var alpha = alphaPercent;
162 |
163 | // hex color needs to be converted from a hex string to its constituent parts:
164 | var red = parseInt(hexColorString.substring(0, 2), 16);
165 | var green = parseInt(hexColorString.substring(2, 4), 16);
166 | var blue = parseInt(hexColorString.substring(4, 6), 16);
167 |
168 | return [alphaBlend(red, alpha), alphaBlend(green, alpha), alphaBlend(blue, alpha)].join('');
169 | }
170 |
171 | // returns properly padded 2 digit hex strings for RGB color channels
172 | function toHexColorChannel(decimal) {
173 | var hex = decimal.toString(16);
174 | return (hex.length === 1 ? '0' : '') + hex;
175 | }
176 |
177 | // Alpha blend a color channel white
178 | function alphaBlend(colorChannel, alpha) {
179 | return toHexColorChannel(Math.round((1 - alpha) * 255 + alpha * colorChannel));
180 | }
181 |
182 | // called on the start of each section, initalizes the first color from the active color cycle.
183 | function nextColor() {
184 | state.currentColorIndex = state.currentColorIndex + 1;
185 | if (state.currentColorIndex >= state.activeColorCycle.length) {
186 | error("Not enough colors were generated!");
187 | }
188 |
189 | state.currentHexColor = state.activeColorCycle[state.currentColorIndex];
190 | }
191 |
192 | // should the current sction be cut (using a stroke) or etched (using a fill)?
193 | var useFillForSection = false;
194 | /**
195 | * For Etch/Vaporize/Engrave, returns fill settings, otherwise none
196 | */
197 | function fill() {
198 | if (useFillForSection) {
199 | return "fill=\"" + state.currentHexColor + "\"";
200 | }
201 | return "fill=\"none\"";
202 | }
203 |
204 | /**
205 | * For through cuts, returns stroke settings, otherwise none
206 | */
207 | function stroke() {
208 | if (useFillForSection) {
209 | return "stroke=\"none\"";
210 | }
211 | return "stroke=\"" + state.currentHexColor + "\" stroke-width=\"" + properties.lineWidth + "\"";
212 | }
213 |
214 | // update the allowMoveCommandNext flag
215 | function allowMoveCommand() {
216 | state.allowMoveCommandNext = true;
217 | }
218 |
219 | var activePathElements = [];
220 | function addPathElement() {
221 | var args = [].slice.call(arguments);
222 |
223 | // only allow moves (M) in the SVG after the laser has been turned off and comes back on again
224 | if (args[0] === "M") {
225 | if (state.allowMoveCommandNext) {
226 | // reset the flag to wait for the next laser power cycle
227 | state.allowMoveCommandNext = false;
228 | }
229 | else {
230 | // skip rendering this move command since the laser has not been turned off
231 | return;
232 | }
233 | }
234 |
235 | activePathElements.push(args.join(" "));
236 | }
237 |
238 | function finishPath() {
239 | if (!activePathElements || activePathElements.length === 0) {
240 | error('An operation resulted in no detectable paths!');
241 | return;
242 | }
243 |
244 | var opComment = hasParameter("operation-comment") ? getParameter("operation-comment") : "[No Title]";
245 |
246 | writeln("");
247 | writeln(" " + opComment + " (" + localize("Op") + ": " + (1 + currentSection.getId()) + "/" + getNumberOfSections() + ")");
248 | writeln(" ")
253 | writeln("");
254 | activePathElements = [];
255 | allowMoveCommand();
256 | }
257 |
258 | // return true if the program should halt because of missing radius compensation in the computer.
259 | function isRadiusCompensationInvalid() {
260 | if (properties.checkForRadiusCompensation === true && (radiusCompensation != RADIUS_COMPENSATION_OFF)) {
261 | error("Operation: " + (1 + currentSection.getId()) + ". The Sideways Compensation type 'In Control' is not supported. This must be set to 'In Computer' in the passes tab.");
262 | }
263 | }
264 |
265 | /** Returns the given spatial value in MM. */
266 | function toMM(value) {
267 | return value * ((unit === IN) ? 25.4 : 1);
268 | }
269 |
270 | function printVector(v) {
271 | return v.x + "," + v.y;
272 | }
273 |
274 | function onOpen() {
275 | if (properties.margin < 0) {
276 | error(localize("Margin must be 0 or positive."));
277 | return;
278 | }
279 |
280 | // reset all per-run state
281 | state = reset();
282 |
283 | // select colors now that the number of ops is available
284 | selectColors();
285 |
286 | // convert everything to mm once up front:
287 | var box = {
288 | upper: {
289 | x: toMM(getWorkpiece().upper.x),
290 | y: toMM(getWorkpiece().upper.y)
291 | },
292 | lower: {
293 | x: toMM(getWorkpiece().lower.x),
294 | y: toMM(getWorkpiece().lower.y)
295 | }
296 | };
297 |
298 | var dx = box.upper.x - box.lower.x;
299 | var dy = box.upper.y - box.lower.y;
300 |
301 | // add margins to overall SVG size
302 | var width = dx + (2 * properties.margin);
303 | var height = dy + (2 * properties.margin);
304 |
305 | if (properties.useWorkArea === true) {
306 | // no margins in useWorkArea mode, you get the work area as your margins!
307 | width = Math.max(properties.workAreaWidth, dx);
308 | height = Math.max(properties.workAreaHeight, dy);
309 | state.workAreaTooSmall = width > properties.workAreaWidth || height > properties.workAreaHeight;
310 | }
311 | /*
312 | * Compensate for Stock Point, SVG Origin, Z axis orientation and margins
313 | *
314 | * The *correct* stock point to select is the lower left corner and the right Z axis orientation is pointing up from the stock towards the laser.
315 | * But to make the learning curve a little gentler we will compensate if you didnt do that.
316 | *
317 | * Auto Stock Point Compensation:
318 | * First, any stock point will produce the same image, here we correct for the stock point with a translation of the entire SVG contents
319 | * in x and y. We want to use the extents of the X and Y axes. Normally X comes from the lower right corner of the stock and Y from the
320 | * upper left (assuming a CAM origin in the lower left corner).
321 | *
322 | * Y Axis in SVG vs CAM:
323 | * If we do nothing the image would be upside down because in SVG the Y origin is at the TOP of the image (see https://www.w3.org/TR/SVG/coords.html#InitialCoordinateSystem).
324 | * So normally the Y axis must be flipped to compensate for this by scaling it to -1.
325 | *
326 | * Incorrect Z Axis Orientation:
327 | * If the user has the Z axis pointing into the stock the SVG image will be upside down (flipped in Y, twice!). This is annoying and is not obvious to fix
328 | * because X and Y look right in the UI. So the "Flip Model" parameter is provided and does *magic* by turning off the default Y flipping. Now the Y axis is only flipped once
329 | * like we need for the SVG origin. But the *lower* box point has to be used to get the Y extent in this case because the *CAM* is upside down (CAM origin is top left corner).
330 | * Unfortunatly the stock point selection changes the ratio between Y values in the upper and lower stock points, so its impossible to detect this without assuming a stock point.
331 | * So this is as good as we can do.
332 | *
333 | * Margins:
334 | * Add 1 magin width to these numbers so the image is centred.
335 | */
336 | var yAxisScale = properties.doNotFlipYAxis ? 1 : -1;
337 | var translateX = 0;
338 | var translateY = 0;
339 |
340 | if (properties.useWorkArea === true) {
341 | // FIXME: this is probably wrong if the design turns out to be bigger than the work area, e.g. (width - dx) will be negative!
342 | translateX = (-box.lower.x + ((width - dx) / 2));
343 | translateY = (box.upper.y + ((height - dy) / 2));
344 | }
345 | else if (properties.autoStockPoint === true) {
346 | translateX = (-1 * box.lower.x) + properties.margin;
347 | translateY = (-1 * yAxisScale * (properties.doNotFlipYAxis ? box.lower.y : box.upper.y)) + properties.margin;
348 | }
349 | // else dont translate anythng.
350 |
351 | writeln("");
352 | writeln("");
377 | }
378 |
379 | function onComment(text) {
380 | writeln('');
381 | }
382 |
383 | function onSection() {
384 | switch (tool.type) {
385 | case TOOL_WATER_JET: // allow any way for Epilog
386 | warning(localize("Using waterjet cutter but allowing it anyway."));
387 | break;
388 | case TOOL_LASER_CUTTER:
389 | break;
390 | case TOOL_PLASMA_CUTTER: // allow any way for Epilog
391 | warning(localize("Using plasma cutter but allowing it anyway."));
392 | break;
393 | case TOOL_MARKER: // allow any way for Epilog
394 | warning(localize("Using marker but allowing it anyway."));
395 | break;
396 | default:
397 | error(localize("The CNC does not support the required tool."));
398 | return;
399 | }
400 |
401 | // use Jet Mode to decide if the shape should be filled or have no fill
402 | switch (currentSection.jetMode) {
403 | case JET_MODE_THROUGH:
404 | useFillForSection = false;
405 | break;
406 | case JET_MODE_ETCHING:
407 | case JET_MODE_VAPORIZE:
408 | useFillForSection = true
409 | break
410 | default:
411 | error(localize("Unsupported cutting mode."));
412 | return;
413 | }
414 |
415 | var remaining = currentSection.workPlane;
416 | if (!isSameDirection(remaining.forward, new Vector(0, 0, 1))) {
417 | error(localize("Tool orientation is not supported."));
418 | return;
419 | }
420 | setRotation(remaining);
421 | nextColor();
422 | }
423 |
424 | function onParameter(name, value) {
425 | }
426 |
427 | function onDwell(seconds) {
428 | }
429 |
430 | function onCycle() {
431 | }
432 |
433 | function onCyclePoint(x, y, z) {
434 | }
435 |
436 | function onCycleEnd() {
437 | }
438 |
439 | function onPower(isLaserPowerOn) {
440 | // if the laser goes from off to on, this happens after a move, so a M should be emitted in the SVG
441 | // this check debounces multiple power on caommands in case the way they are emitted ever changes
442 | if (!state.isLaserOn && isLaserPowerOn) {
443 | allowMoveCommand();
444 | }
445 |
446 | state.isLaserOn = isLaserPowerOn;
447 | }
448 |
449 | // validate that the laser is on and that the movement type is a cutting move
450 | function isCuttingMove(movement) {
451 | return state.isLaserOn && (movement === MOVEMENT_CUTTING || movement == MOVEMENT_REDUCED || movement == MOVEMENT_FINISH_CUTTING);
452 | }
453 |
454 | function writeLine(x, y) {
455 | if (!isCuttingMove(movement)) {
456 | return;
457 | }
458 |
459 | isRadiusCompensationInvalid();
460 |
461 | var start = getCurrentPosition();
462 | if ((state.xyzFormat.format(start.x) == state.xyzFormat.format(x)) &&
463 | (state.xyzFormat.format(start.y) == state.xyzFormat.format(y))) {
464 | log('vertical move ignored');
465 | return; // ignore vertical
466 | }
467 |
468 | addPathElement("M", state.xyzFormat.format(start.x), state.xyzFormat.format(start.y));
469 | addPathElement("L", state.xyzFormat.format(x), state.xyzFormat.format(y));
470 | }
471 |
472 | function onRapid(x, y, z) {
473 | writeLine(x, y);
474 | }
475 |
476 | function onLinear(x, y, z, feed) {
477 | writeLine(x, y);
478 | }
479 |
480 | function onRapid5D(x, y, z, dx, dy, dz) {
481 | onRapid(x, y, z);
482 | }
483 |
484 | function onLinear5D(x, y, z, dx, dy, dz, feed) {
485 | onLinear(x, y, z);
486 | }
487 |
488 | function onCircular(clockwise, cx, cy, cz, x, y, z, feed) {
489 | if (!isCuttingMove(movement)) {
490 | return;
491 | }
492 |
493 | isRadiusCompensationInvalid();
494 |
495 | var start = getCurrentPosition();
496 |
497 | var largeArc = (getCircularSweep() > Math.PI) ? 1 : 0;
498 | var sweepFlag = isClockwise() ? 0 : 1;
499 | addPathElement("M", state.xyzFormat.format(start.x), state.xyzFormat.format(start.y));
500 | addPathElement("A", state.xyzFormat.format(getCircularRadius()), state.xyzFormat.format(getCircularRadius()), 0, largeArc, sweepFlag, state.xyzFormat.format(x), state.xyzFormat.format(y));
501 | }
502 |
503 | function onCommand() {
504 | }
505 |
506 | function onSectionEnd() {
507 | finishPath();
508 | }
509 |
--------------------------------------------------------------------------------