list = new ArrayList<>();
130 | list.add(shape);
131 | groups.put(group, list);
132 | }
133 | } else {
134 | others.add(shape);
135 | }
136 | }
137 | // List grouped shapes
138 | StringBuilder buf = new StringBuilder("\n\n\n\n");
139 | shapeLookup.clear();
140 | groupLookup.clear();
141 | int groupId = 1;
142 | int shapeId = 1;
143 | buf.append("Groups:\n");
144 | if (groups.size() > 1) {
145 | for (CADShapeGroup group : groups.keySet()) {
146 | buf.append(" ").append(getGroupLink(groupId++));
147 | List list = groups.get(group);
148 | for (CADShape shape : list) {
149 | buf.append(" ").append(getShapeLink(shape.getMenuName(), shapeId));
150 | shapeLookup.put(shapeId++, shape);
151 | }
152 | }
153 | } else {
154 | buf.append(" none\n");
155 | }
156 | // List ungrouped items, if any
157 | buf.append("Other:\n");
158 | if (others.size() > 0) {
159 | for (CADShape shape : others) {
160 | buf.append(" ").append(getShapeLink(shape.getMenuName(), shapeId));
161 | shapeLookup.put(shapeId++, shape);
162 | }
163 | } else {
164 | buf.append(" none\n");
165 | }
166 | buf.append("\n\n");
167 | return buf.toString();
168 | }
169 |
170 | private static String getGroupLink (int code) {
171 | return "Group: " + code + "\n";
172 | }
173 |
174 | private static String getShapeLink (String test, int code) {
175 | return "" + test + "\n";
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/CADShapeSpline.java:
--------------------------------------------------------------------------------
1 | import java.awt.*;
2 | import java.awt.geom.*;
3 | import java.awt.geom.Point2D.Double;
4 | import java.io.Serializable;
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.prefs.Preferences;
9 |
10 | class CADShapeSpline extends CADShape implements Serializable, LaserCut.StateMessages {
11 | private static final long serialVersionUID = 1175193935200692376L;
12 | private List points = new ArrayList<>();
13 | private Point2D.Double movePoint;
14 | private boolean pathClosed;
15 | private Path2D.Double path = new Path2D.Double();
16 | public double scale = 100.0;
17 | transient public double lastScale;
18 |
19 | CADShapeSpline () {
20 | lastScale = scale;
21 | }
22 |
23 | @Override
24 | void createAndPlace (DrawSurface surface, LaserCut laserCut, Preferences prefs) {
25 | surface.placeShape(this);
26 | }
27 |
28 | @Override
29 | String getMenuName () {
30 | return "Spline Curve";
31 | }
32 |
33 | // Implement StateMessages interface
34 | public String getStateMsg () {
35 | if (pathClosed) {
36 | return "Click and drag am existing point to move it\n - - \nclick on line to add new point" +
37 | "\n - - \nor SHIFT click on lower right bound point to rotate";
38 | } else {
39 | String[] nextPnt = {"first", "second", "third", "additional"};
40 | return "Click to add " + (nextPnt[Math.min(nextPnt.length - 1, points.size())]) + " control point" +
41 | (points.size() >= (nextPnt.length - 1) ? "\n - - \nor click 1st control point to complete spline" : "");
42 | }
43 | }
44 |
45 | @Override
46 | protected List getEditFields () {
47 | return Arrays.asList(
48 | "xLoc|in",
49 | "yLoc|in",
50 | "rotation|deg{degrees to rotate}",
51 | "scale|%"
52 | );
53 | }
54 |
55 | List getScaledPoints () {
56 | List scaledPts = new ArrayList<>();
57 | for (Point2D.Double cp : points) {
58 | scaledPts.add(new Point2D.Double(cp.x * scale / 100, cp.y * scale / 100));
59 | }
60 | return scaledPts;
61 | };
62 |
63 | @Override
64 | boolean isShapeClicked (Point2D.Double point, double zoomFactor) {
65 | return super.isShapeClicked(point, zoomFactor) || isPositionClicked(point, zoomFactor);
66 | }
67 |
68 | boolean isPathClosed () {
69 | return pathClosed;
70 | }
71 |
72 | /**
73 | * See if we clicked on an existing Catmull-Rom Control Point other than origin
74 | *
75 | * @param surface Reference to DrawSurface
76 | * @param point Point clicked in Workspace coordinates (inches)
77 | * @param gPoint Closest grid point clicked in Workspace coordinates (inches)
78 | * @return true if clicked
79 | */
80 | @Override
81 | boolean isControlPoint (DrawSurface surface, Point2D.Double point, Point2D.Double gPoint) {
82 | // Note: mse is in unrotated coords relative to the points, such as mse: x = 0.327, y = -0.208
83 | Point2D.Double mse = Utils2D.rotatePoint(new Point2D.Double(point.x - xLoc, point.y - yLoc), -rotation);
84 | for (int ii = 0; ii < points.size(); ii++) {
85 | Point2D.Double cp = points.get(ii);
86 | double dist = mse.distance(cp) * LaserCut.SCREEN_PPI;
87 | if (dist < 5) {
88 | if (ii == 0 && !pathClosed) {
89 | surface.pushToUndoStack();
90 | pathClosed = true;
91 | updatePath();
92 | }
93 | // Note: movePoint is relative to coords of points
94 | movePoint = cp;
95 | return true;
96 | }
97 | }
98 | int idx;
99 | if (pathClosed && (idx = getInsertionPoint(point)) >= 0) {
100 | surface.pushToUndoStack();
101 | points.add(idx + 1, movePoint = Utils2D.rotatePoint(new Point2D.Double(gPoint.x - xLoc, gPoint.y - yLoc), -rotation));
102 | updatePath();
103 | return true;
104 | }
105 | if (!pathClosed) {
106 | surface.pushToUndoStack();
107 | points.add(Utils2D.rotatePoint(movePoint = new Point2D.Double(gPoint.x - xLoc, gPoint.y - yLoc), -rotation));
108 | updatePath();
109 | return true;
110 | }
111 | return false;
112 | }
113 |
114 | /**
115 | * See if we clicked on spline cadShape to add new control point
116 | *
117 | * @param point Point clicked in Workspace coordinates (inches)
118 | * @return index into points List where we need to add new point
119 | */
120 | int getInsertionPoint (Point2D.Double point) {
121 | Point2D.Double mse = Utils2D.rotatePoint(new Point2D.Double(point.x - xLoc, point.y - yLoc), -rotation);
122 | int idx = 1;
123 | Point2D.Double chk = points.get(idx);
124 | for (Line2D.Double[] lines : Utils2D.transformShapeToLines(getShape(), 1, .01)) {
125 | for (Line2D.Double line : lines) {
126 | double dist = line.ptSegDist(mse) * LaserCut.SCREEN_PPI;
127 | if (dist < 5) {
128 | return idx - 1;
129 | }
130 | // Advance idx as we pass control points
131 | if (idx < points.size() && chk.distance(line.getP2()) < .000001) {
132 | chk = points.get(Math.min(points.size() - 1, ++idx));
133 | }
134 | }
135 | }
136 | return -1;
137 | }
138 |
139 | boolean isMovingControlPoint () {
140 | return movePoint != null;
141 | }
142 |
143 | @Override
144 | void doMovePoints (Double point) {
145 | if (movePoint != null) {
146 | Point2D.Double mse = Utils2D.rotatePoint(new Point2D.Double(point.x - xLoc, point.y - yLoc), -rotation);
147 | double dx = mse.x - movePoint.x;
148 | double dy = mse.y - movePoint.y;
149 | movePoint.x += dx;
150 | movePoint.y += dy;
151 | updatePath();
152 | }
153 | }
154 |
155 | @Override
156 | void cancelMove () {
157 | movePoint = null;
158 | }
159 |
160 | @Override
161 | Shape getShape () {
162 | return path;
163 | }
164 |
165 | @Override
166 | void updateShape () {
167 | super.updateShape();
168 | if (scale != lastScale) {
169 | // transform all the points to new scale;
170 | points = getScaledPoints();
171 | lastScale = scale;
172 | updatePath();
173 | }
174 | }
175 |
176 | private void updatePath () {
177 | path = Utils2D.convertPointsToBezier(points, pathClosed);
178 | updateShape();
179 | }
180 |
181 | @Override
182 | public void resize (double dx, double dy) {
183 | Rectangle2D.Double bnds = Utils2D.boundsOf(points);
184 | scale = (Math.min(dx / bnds.getWidth(), dy / bnds.getHeight())) * 200;
185 | if (scale != lastScale) {
186 | // transform all the points to new scale;
187 | points = getScaledPoints();
188 | lastScale = scale;
189 | updatePath();
190 | }
191 | }
192 |
193 | @Override
194 | void draw (Graphics g, double zoom, boolean keyRotate, boolean keyResize, boolean keyOption) {
195 | super.draw(g, zoom, keyRotate, keyResize, keyOption);
196 | Graphics2D g2 = (Graphics2D) g;
197 | // Draw all Catmull-Rom Control Points
198 | g2.setColor(isSelected ? Color.red : pathClosed ? Color.lightGray : Color.darkGray);
199 | if (isSelected){
200 | for (Point2D.Double cp : points) {
201 | Point2D.Double np = Utils2D.rotatePoint(cp, rotation);
202 | double mx = (xLoc + np.x) * zoom * LaserCut.SCREEN_PPI;
203 | double my = (yLoc + np.y) * zoom * LaserCut.SCREEN_PPI;
204 | double mWid = 3;
205 | g2.fill(new Rectangle.Double(mx - mWid, my - mWid, mWid * 2, mWid * 2));
206 | }
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/FileChooserMenu.java:
--------------------------------------------------------------------------------
1 | import javax.imageio.ImageIO;
2 | import javax.swing.*;
3 | import javax.swing.filechooser.FileNameExtensionFilter;
4 | import java.awt.*;
5 | import java.awt.image.BufferedImage;
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.nio.file.Files;
9 | import java.util.concurrent.TimeUnit;
10 | import java.util.prefs.Preferences;
11 | import static javax.swing.JOptionPane.*;
12 |
13 | public class FileChooserMenu extends LaserCut.MyMenuItem {
14 | private static final int IMG_WID = 200;
15 | private static final int IMG_HYT = 200;
16 | private static final int IMG_BORDER = 30;
17 | String currentPath;
18 | String lastImgFile;
19 | JLabel imgLabel;
20 | JFileChooser fileChooser;
21 |
22 | /**
23 | * Create all the file dialogs for LaserCut
24 | * @param lcut reference to LaserCut (used to invoke message dialogs)
25 | * @param msg message at top of file dialog
26 | * @param ext file extention
27 | * @param key code for key shortcut, else zero (0)
28 | * @param save true if save file dialog, else false
29 | * @param preview true if file dialog displays a preview image (save must also be false)
30 | */
31 | FileChooserMenu (LaserCut lcut, String msg, String ext, int key, boolean save, boolean preview) {
32 | super(msg, key);
33 | Preferences prefs = lcut.getPreferences();
34 | fileChooser = new JFileChooser();
35 | imgLabel = new JLabel();
36 | addActionListener(ev -> {
37 | fileChooser = new JFileChooser();
38 | String lastFile = prefs.get("lastFile", "");
39 | fileChooser.setSelectedFile(new File(lastFile));
40 | imgLabel = new JLabel();
41 | fileChooser.setDialogTitle(msg);
42 | fileChooser.setDialogType(save ? JFileChooser.SAVE_DIALOG : JFileChooser.OPEN_DIALOG);
43 | FileNameExtensionFilter nameFilter = new FileNameExtensionFilter(ext.toUpperCase() + " files (*." + ext + ")", ext);
44 | fileChooser.addChoosableFileFilter(nameFilter);
45 | fileChooser.setFileFilter(nameFilter);
46 | fileChooser.setCurrentDirectory(new File(currentPath = prefs.get("default." + ext + ".dir", "/")));
47 | currentPath = fileChooser.getCurrentDirectory().getPath();
48 | JPanel accessories = new JPanel(new BorderLayout());
49 | boolean hasAccesory = false;
50 | if (preview) {
51 | JPanel panel = new JPanel(new BorderLayout());
52 | panel.setPreferredSize(new Dimension(IMG_WID + IMG_BORDER, IMG_HYT + IMG_BORDER));
53 | panel.setBorder(BorderFactory.createLineBorder(Color.black));
54 | imgLabel.setHorizontalAlignment(JLabel.CENTER);
55 | imgLabel.setVerticalAlignment(JLabel.CENTER);
56 | panel.add(imgLabel, BorderLayout.CENTER);
57 | Dimension dim1 = fileChooser.getPreferredSize();
58 | fileChooser.setPreferredSize(new Dimension((int) (dim1.width * 1.25), dim1.height));
59 | accessories.add(panel, BorderLayout.WEST);
60 | hasAccesory = true;
61 | }
62 | JComponent temp = getAccessory(prefs, save);
63 | if (temp != null) {
64 | accessories.add(temp, BorderLayout.EAST);
65 | hasAccesory = true;
66 | }
67 | if (hasAccesory) {
68 | fileChooser.setAccessory(accessories);
69 | }
70 | fileChooser.addPropertyChangeListener(evt -> {
71 | // Update currentPath as the user navigates directories
72 | String path = fileChooser.getCurrentDirectory().getPath();
73 | if (!path.equals(currentPath)) {
74 | prefs.put("default." + ext + ".dir", currentPath = path);
75 | }
76 | if (preview) {
77 | // Display a file preview image
78 | String propName = evt.getPropertyName();
79 | if ("SelectedFileChangedProperty".equals(propName)) {
80 | SwingWorker worker = new SwingWorker() {
81 |
82 | protected Image doInBackground () {
83 | // LOad the preview image
84 | if (evt.getPropertyName().equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
85 | File file = fileChooser.getSelectedFile();
86 | String newFile = file.getAbsolutePath();
87 | if (!newFile.equals(lastImgFile)) {
88 | lastImgFile = newFile;
89 | try {
90 | BufferedImage buf = getPreview(file);
91 | if (buf != null) {
92 | int wid = buf.getWidth();
93 | int hyt = buf.getHeight();
94 | if (wid < hyt) {
95 | double ratio = (double) wid / hyt;
96 | return buf.getScaledInstance((int) (IMG_WID * ratio), IMG_HYT, BufferedImage.SCALE_FAST);
97 | } else {
98 | double ratio = (double) hyt / wid;
99 | return buf.getScaledInstance(IMG_WID, (int) (IMG_HYT * ratio), BufferedImage.SCALE_FAST);
100 | }
101 | }
102 | } catch (Exception ex) {
103 | imgLabel.setText("Unable to preview");
104 | imgLabel.setIcon(null);
105 | ex.printStackTrace();
106 | }
107 | }
108 | }
109 | return null;
110 | }
111 |
112 | protected void done () {
113 | // display the preview image
114 | try {
115 | Image img = get(1L, TimeUnit.NANOSECONDS);
116 | if (img != null) {
117 | imgLabel.setIcon(new ImageIcon(img));
118 | imgLabel.setText(null);
119 | }
120 | } catch (Exception ex) {
121 | imgLabel.setText(" Error");
122 | imgLabel.setIcon(null);
123 | ex.printStackTrace();
124 | }
125 | }
126 | };
127 | worker.execute();
128 | }
129 | }
130 | });
131 | if (openDialog(lcut, save)) {
132 | File sFile = fileChooser.getSelectedFile();
133 | if (save && !sFile.exists()) {
134 | String fPath = sFile.getPath();
135 | if (!fPath.contains(".")) {
136 | sFile = new File(fPath + "." + ext);
137 | }
138 | }
139 | try {
140 | if (!save || (!sFile.exists() || lcut.showWarningDialog("Overwrite Existing file?"))) {
141 | processFile(sFile);
142 | }
143 | } catch (Exception ex) {
144 | lcut.showErrorDialog(save ? "Unable to save file" : "Unable to open file");
145 | //ex.printStackTrace();
146 | }
147 | prefs.put("default." + ext, sFile.getAbsolutePath());
148 | }
149 | });
150 | }
151 |
152 | // Override in subclass to get an accessory component
153 | JComponent getAccessory (Preferences prefs, boolean save) {
154 | return null;
155 | }
156 |
157 | private boolean openDialog (Component comp, boolean save) {
158 | // Open an open or save dialog
159 | if (save) {
160 | return fileChooser.showSaveDialog(comp) == JFileChooser.APPROVE_OPTION;
161 | } else {
162 | return fileChooser.showOpenDialog(comp) == JFileChooser.APPROVE_OPTION;
163 | }
164 | }
165 |
166 | // Override to perform open, or save operation
167 | void processFile (File sFile) throws Exception {
168 | }
169 |
170 | // Override, as needed to load a preview image
171 | BufferedImage getPreview (File file) throws Exception {
172 | return ImageIO.read(Files.newInputStream(file.toPath()));
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/JSSCPort.java:
--------------------------------------------------------------------------------
1 | import java.util.*;
2 | import java.util.concurrent.ArrayBlockingQueue;
3 | import java.util.prefs.Preferences;
4 | import java.util.regex.Pattern;
5 |
6 | import jssc.*;
7 |
8 | import javax.swing.*;
9 | import javax.swing.event.MenuEvent;
10 | import javax.swing.event.MenuListener;
11 |
12 | /*
13 | * Encapsulates JSSC functionality into an easy to use class
14 | * See: https://code.google.com/p/java-simple-serial-connector/
15 | * And: https://github.com/scream3r/java-simple-serial-connector/releases
16 | *
17 | * Author: Wayne Holder, 2015-2019 (first version 10/30/2015)
18 | */
19 |
20 | public class JSSCPort implements SerialPortEventListener {
21 | private static final Map baudRates = new LinkedHashMap<>();
22 | private final ArrayBlockingQueue queue = new ArrayBlockingQueue<>(1000);
23 | private static Pattern macPat = Pattern.compile("cu.");
24 | private static final int dataBits = 8, stopBits = SerialPort.STOPBITS_1, parity = SerialPort.PARITY_NONE;
25 | private static final int flowCtrl = SerialPort.FLOWCONTROL_NONE;
26 | private static final int eventMasks = 0; // See: SerialPort.MASK_RXCHAR, MASK_TXEMPTY, MASK_CTS, MASK_DSR
27 | private final Preferences prefs;
28 | private String portName;
29 | private int baudRate;
30 | private SerialPort serialPort;
31 | private final String prefix;
32 | private final List rxHandlers = new ArrayList<>();
33 |
34 | interface RXEvent {
35 | void rxChar (byte cc);
36 | }
37 |
38 | static {
39 | baudRates.put("110", SerialPort.BAUDRATE_110);
40 | baudRates.put("300", SerialPort.BAUDRATE_300);
41 | baudRates.put("600", SerialPort.BAUDRATE_600);
42 | baudRates.put("1200", SerialPort.BAUDRATE_1200);
43 | baudRates.put("2400", 2400); // Note: constant missing in JSSC
44 | baudRates.put("4800", SerialPort.BAUDRATE_4800);
45 | baudRates.put("7200", 7200); // For misconfigured Arduino
46 | baudRates.put("9600", SerialPort.BAUDRATE_9600);
47 | baudRates.put("14400", SerialPort.BAUDRATE_14400);
48 | baudRates.put("19200", SerialPort.BAUDRATE_19200);
49 | baudRates.put("38400", SerialPort.BAUDRATE_38400);
50 | baudRates.put("57600", SerialPort.BAUDRATE_57600);
51 | baudRates.put("115200", SerialPort.BAUDRATE_115200);
52 | baudRates.put("128000", SerialPort.BAUDRATE_128000);
53 | baudRates.put("256000", SerialPort.BAUDRATE_256000);
54 | }
55 |
56 | JSSCPort (String prefix, Preferences prefs) {
57 | this.prefix = prefix;
58 | this.prefs = prefs;
59 | // Determine OS Type
60 | switch (SerialNativeInterface.getOsType()) {
61 | case SerialNativeInterface.OS_LINUX:
62 | macPat = Pattern.compile("(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm)[0-9]{1,3}");
63 | break;
64 | case SerialNativeInterface.OS_MAC_OS_X:
65 | break;
66 | case SerialNativeInterface.OS_WINDOWS:
67 | macPat = Pattern.compile("");
68 | break;
69 | default:
70 | macPat = Pattern.compile("tty.*");
71 | break;
72 | }
73 | portName = prefs.get(prefix + "serial.port", null);
74 | baudRate = prefs.getInt(prefix + "serial.baud", SerialPort.BAUDRATE_115200);
75 | }
76 |
77 | boolean hasSerial () {
78 | return portName != null;
79 | }
80 |
81 | boolean open (RXEvent handler) throws SerialPortException {
82 | if (serialPort != null) {
83 | if (serialPort.isOpened()) {
84 | close();
85 | }
86 | }
87 | if (portName != null) {
88 | try {
89 | setRXHandler(handler);
90 | serialPort = new SerialPort(portName);
91 | serialPort.openPort();
92 | serialPort.purgePort(SerialPort.PURGE_RXCLEAR | SerialPort.PURGE_TXCLEAR);
93 | serialPort.setParams(baudRate, dataBits, stopBits, parity, false, false); // baud, 8 bits, 1 stop bit, no parity
94 | serialPort.setEventsMask(eventMasks);
95 | serialPort.setFlowControlMode(flowCtrl);
96 | serialPort.addEventListener(this);
97 | return true;
98 | } catch (SerialPortException ex) {
99 | prefs.remove(prefix + "serial.port");
100 | throw ex;
101 | }
102 | }
103 | return false;
104 | }
105 |
106 | public void close () {
107 | if (serialPort != null && serialPort.isOpened()) {
108 | try {
109 | synchronized (this) {
110 | rxHandlers.clear();
111 | }
112 | serialPort.removeEventListener();
113 | serialPort.purgePort(SerialPort.PURGE_RXCLEAR | SerialPort.PURGE_TXCLEAR);
114 | serialPort.closePort();
115 | serialPort = null;
116 | } catch (SerialPortException ex) {
117 | ex.printStackTrace();
118 | }
119 | }
120 | }
121 |
122 | public void serialEvent (SerialPortEvent se) {
123 | try {
124 | if (se.getEventType() == SerialPortEvent.RXCHAR) {
125 | int rxCount = se.getEventValue();
126 | byte[] inChars = serialPort.readBytes(rxCount);
127 | if (rxHandlers.size() > 0) {
128 | for (byte cc : inChars) {
129 | for (RXEvent handler : rxHandlers) {
130 | handler.rxChar(cc);
131 | }
132 | }
133 | } else {
134 | for (byte cc : inChars) {
135 | if (queue.remainingCapacity() > 0) {
136 | queue.add((int) cc);
137 | }
138 | }
139 | }
140 | }
141 | } catch (Exception ex) {
142 | ex.printStackTrace();
143 | }
144 | }
145 |
146 | void setRXHandler (RXEvent handler) {
147 | synchronized (this) {
148 | rxHandlers.add(handler);
149 | }
150 | }
151 |
152 | void removeRXHandler (RXEvent handler) {
153 | synchronized (this) {
154 | rxHandlers.remove(handler);
155 | }
156 | }
157 |
158 | void sendByte (byte data) throws SerialPortException {
159 | serialPort.writeByte(data);
160 | }
161 |
162 | void sendString (String data) throws SerialPortException {
163 | serialPort.writeString(data);
164 | }
165 |
166 | JMenu getPortMenu () {
167 | JMenu menu = new JMenu("Port");
168 | menu.addMenuListener(new MenuListener() {
169 | @Override
170 | public void menuSelected (MenuEvent e) {
171 | // Populate menu on demand
172 | menu.removeAll();
173 | ButtonGroup group = new ButtonGroup();
174 | for (String pName : SerialPortList.getPortNames(macPat)) {
175 | JRadioButtonMenuItem item = new JRadioButtonMenuItem(pName, pName.equals(portName));
176 | menu.setVisible(true);
177 | menu.add(item);
178 | group.add(item);
179 | item.addActionListener((ev) -> {
180 | portName = ev.getActionCommand();
181 | prefs.put(prefix + "serial.port", portName);
182 | });
183 | }
184 | }
185 |
186 | @Override
187 | public void menuDeselected (MenuEvent e) { }
188 |
189 | @Override
190 | public void menuCanceled (MenuEvent e) { }
191 | });
192 | return menu;
193 | }
194 |
195 | JMenu getBaudMenu () {
196 | JMenu menu = new JMenu("Baud Rate");
197 | ButtonGroup group = new ButtonGroup();
198 | for (String bRate : baudRates.keySet()) {
199 | int rate = baudRates.get(bRate);
200 | JRadioButtonMenuItem item = new JRadioButtonMenuItem(bRate, baudRate == rate);
201 | menu.add(item);
202 | menu.setVisible(true);
203 | group.add(item);
204 | item.addActionListener((ev) -> {
205 | String cmd = ev.getActionCommand();
206 | prefs.putInt(prefix + "serial.baud", baudRate = Integer.parseInt(cmd));
207 | });
208 | }
209 | return menu;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/CADArbitraryPolygon.java:
--------------------------------------------------------------------------------
1 | import java.awt.*;
2 | import java.awt.geom.*;
3 | import java.awt.geom.Point2D.Double;
4 | import java.io.Serializable;
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.prefs.Preferences;
9 |
10 | class CADArbitraryPolygon extends CADShape implements Serializable, LaserCut.StateMessages {
11 | private static final long serialVersionUID = 1175193935200692376L;
12 | private List points = new ArrayList<>();
13 | private Point2D.Double movePoint;
14 | private boolean pathClosed;
15 | private Path2D.Double path = new Path2D.Double();
16 | public double scale = 100.0;
17 | transient public double lastScale;
18 |
19 |
20 | CADArbitraryPolygon () {
21 | lastScale = scale;
22 | }
23 |
24 | @Override
25 | void createAndPlace (DrawSurface surface, LaserCut laserCut, Preferences prefs) {
26 | surface.placeShape(this);
27 | }
28 |
29 | @Override
30 | String getMenuName () {
31 | return "Arbitrary Polygon";
32 | }
33 |
34 | // Implement StateMessages interface
35 | public String getStateMsg () {
36 | if (pathClosed) {
37 | return "Click and drag am existing point to move it\n - - \nclick on line to add new point" +
38 | "\n - - \nor SHIFT click on lower right bound point to rotate";
39 | } else {
40 | String[] nextPnt = {"first", "second", "third", "additional"};
41 | return "Click to add " + (nextPnt[Math.min(nextPnt.length - 1, points.size())]) + " point" +
42 | (points.size() >= (nextPnt.length - 1) ? "\n - -\nor click 1st point to complete polygon)" : "");
43 | }
44 | }
45 |
46 | @Override
47 | protected List getEditFields () {
48 | return Arrays.asList(
49 | "xLoc|in",
50 | "yLoc|in",
51 | "rotation|deg{degrees to rotate}",
52 | "scale|%"
53 | );
54 | }
55 |
56 | List getScaledPoints () {
57 | List scaledPts = new ArrayList<>();
58 | for (Point2D.Double cp : points) {
59 | scaledPts.add(new Point2D.Double(cp.x * scale / 100, cp.y * scale / 100));
60 | }
61 | return scaledPts;
62 | };
63 |
64 | @Override
65 | boolean isShapeClicked (Point2D.Double point, double zoomFactor) {
66 | return super.isShapeClicked(point, zoomFactor) || isPositionClicked(point, zoomFactor);
67 | }
68 |
69 | boolean isPathClosed () {
70 | return pathClosed;
71 | }
72 |
73 | /**
74 | * See if we clicked on an existing Control Point other than origin
75 | *
76 | * @param surface Reference to DrawSurface
77 | * @param point Point clicked in Workspace coordinates (inches)
78 | * @param gPoint Closest grid point clicked in Workspace coordinates (inches)
79 | * @return true if clicked
80 | */
81 | @Override
82 | boolean isControlPoint (DrawSurface surface, Point2D.Double point, Point2D.Double gPoint) {
83 | // Note: mse is in unrotated coords relative to the points, such as mse: x = 0.327, y = -0.208
84 | Point2D.Double mse = Utils2D.rotatePoint(new Point2D.Double(point.x - xLoc, point.y - yLoc), -rotation);
85 | for (int ii = 0; ii < points.size(); ii++) {
86 | Point2D.Double cp = points.get(ii);
87 | double dist = mse.distance(cp) * LaserCut.SCREEN_PPI;
88 | if (dist < 5) {
89 | if (ii == 0 && !pathClosed) {
90 | surface.pushToUndoStack();
91 | pathClosed = true;
92 | updatePath();
93 | }
94 | // Note: movePoint is relative to coords of points
95 | movePoint = cp;
96 | return true;
97 | }
98 | }
99 | int idx;
100 | if (pathClosed && (idx = getInsertionPoint(point)) >= 0) {
101 | surface.pushToUndoStack();
102 | points.add(idx + 1, movePoint = Utils2D.rotatePoint(new Point2D.Double(gPoint.x - xLoc, gPoint.y - yLoc), -rotation));
103 | updatePath();
104 | return true;
105 | }
106 | if (!pathClosed) {
107 | surface.pushToUndoStack();
108 | points.add(Utils2D.rotatePoint(movePoint = new Point2D.Double(gPoint.x - xLoc, gPoint.y - yLoc), -rotation));
109 | updatePath();
110 | return true;
111 | }
112 | return false;
113 | }
114 |
115 | /**
116 | * See if we clicked on spline cadShape to add new control point
117 | *
118 | * @param point Point clicked in Workspace coordinates (inches)
119 | * @return index into points List where we need to add new point
120 | */
121 | int getInsertionPoint (Point2D.Double point) {
122 | Point2D.Double mse = Utils2D.rotatePoint(new Point2D.Double(point.x - xLoc, point.y - yLoc), -rotation);
123 | int idx = 1;
124 | Point2D.Double chk = points.get(idx);
125 | for (Line2D.Double[] lines : Utils2D.transformShapeToLines(getShape(), 1, .01)) {
126 | for (Line2D.Double line : lines) {
127 | double dist = line.ptSegDist(mse) * LaserCut.SCREEN_PPI;
128 | if (dist < 5) {
129 | return idx - 1;
130 | }
131 | // Advance idx as we pass control points
132 | if (idx < points.size() && chk.distance(line.getP2()) < .000001) {
133 | chk = points.get(Math.min(points.size() - 1, ++idx));
134 | }
135 | }
136 | }
137 | return -1;
138 | }
139 |
140 | boolean isMovingControlPoint () {
141 | return movePoint != null;
142 | }
143 |
144 | @Override
145 | void doMovePoints (Double point) {
146 | if (movePoint != null) {
147 | Point2D.Double mse = Utils2D.rotatePoint(new Point2D.Double(point.x - xLoc, point.y - yLoc), -rotation);
148 | double dx = mse.x - movePoint.x;
149 | double dy = mse.y - movePoint.y;
150 | movePoint.x += dx;
151 | movePoint.y += dy;
152 | updatePath();
153 | }
154 | }
155 |
156 | @Override
157 | void cancelMove () {
158 | movePoint = null;
159 | }
160 |
161 | @Override
162 | Shape getShape () {
163 | return path;
164 | }
165 |
166 | @Override
167 | void updateShape () {
168 | super.updateShape();
169 | if (scale != lastScale) {
170 | // transform all the points to new scale;
171 | points = getScaledPoints();
172 | lastScale = scale;
173 | updatePath();
174 | }
175 | }
176 |
177 | private void updatePath () {
178 | if (pathClosed) {
179 | path = Utils2D.convertPointsToPath(points.toArray(new Point2D.Double[0]), true);
180 | } else {
181 | Point2D.Double[] pnts = points.toArray(new Point2D.Double[points.size() + 1]);
182 | // Duplicate last point so we can draw a curve through all points in the path
183 | pnts[pnts.length - 1] = pnts[pnts.length - 2];
184 | path = Utils2D.convertPointsToPath(pnts, false);
185 | }
186 | updateShape();
187 | }
188 |
189 | @Override
190 | public void resize (double dx, double dy) {
191 | Rectangle2D.Double bnds = Utils2D.boundsOf(points);
192 | scale = (Math.min(dx / bnds.getWidth(), dy / bnds.getHeight())) * 200;
193 | if (scale != lastScale) {
194 | // transform all the points to new scale;
195 | points = getScaledPoints();
196 | lastScale = scale;
197 | updatePath();
198 | }
199 | }
200 |
201 | @Override
202 | void draw (Graphics g, double zoom, boolean keyRotate, boolean keyResize, boolean keyOption) {
203 | super.draw(g, zoom, keyRotate, keyResize, keyOption);
204 | Graphics2D g2 = (Graphics2D) g;
205 | // Draw all the Control Points
206 | g2.setColor(isSelected ? Color.red : pathClosed ? Color.lightGray : Color.darkGray);
207 | if (isSelected){
208 | for (Point2D.Double cp : points) {
209 | Point2D.Double np = Utils2D.rotatePoint(cp, rotation);
210 | double mx = (xLoc + np.x) * zoom * LaserCut.SCREEN_PPI;
211 | double my = (yLoc + np.y) * zoom * LaserCut.SCREEN_PPI;
212 | double mWid = 3;
213 | g2.fill(new Rectangle.Double(mx - mWid, my - mWid, mWid * 2, mWid * 2));
214 | }
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/CNCTools.java:
--------------------------------------------------------------------------------
1 | import java.awt.geom.Line2D;
2 | import java.awt.geom.Point2D;
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | class CNCTools {
7 |
8 | static class PLine {
9 | private static final double LARGE = 1.0e12; // Avoid divide by zero...
10 | private double slope;
11 | private final double intercept;
12 |
13 | /**
14 | * PLine class represents a line of infinite length. This constructor creates a line that passes
15 | * through
16 | * @param line parallel reference line
17 | * @param point point that PLine passes through
18 | */
19 | private PLine (Line2D.Double line, Point2D.Double point) {
20 | double dx = line.x2 - line.x1;
21 | slope = dx == 0 ? LARGE : (line.y2 - line.y1) / dx;
22 | intercept = point.y - slope * point.x;
23 | }
24 |
25 | /**
26 | * PLine class represents a line of infinite length. This constructor creates a line that passes through
27 | * point dst and, if perpendicular is true, rotates the line 90 degrees so that is perpendicular to the line
28 | * that passes from point src to point dst, else like passes through both points
29 | * @param src point from here to dst establishes the perpendicular
30 | * @param dst point that PLine passes through
31 | */
32 | private PLine (Point2D.Double src, Point2D.Double dst, boolean perpendicular) {
33 | double dx = dst.x - src.x;
34 | slope = dx == 0 ? LARGE : (dst.y - src.y) / dx;
35 | if (perpendicular) {
36 | slope = -1 / (slope == 0 ? LARGE : slope);
37 | }
38 | intercept = dst.y - slope * dst.x;
39 | }
40 |
41 | /**
42 | * PLine class represents a line of infinite length. This constructor creates a line that passes through line
43 | * @param line line specifing ininite line
44 | */
45 | private PLine (Line2D.Double line) {
46 | this(new Point2D.Double(line.x1, line.y1), new Point2D.Double(line.x2, line.y2), false);
47 | }
48 |
49 | Point2D.Double intersects (PLine l2) {
50 | if (slope == l2.slope) {
51 | return null;
52 | }
53 | double div = slope - l2.slope;
54 | double x = div == 0 ? LARGE : (l2.intercept - intercept) / div;
55 | return new Point2D.Double(x, slope * x + intercept);
56 | }
57 | }
58 |
59 | /**
60 | * Takes an array of lines that forms a closed cadShape and computes a parallel path around either the
61 | * interior, or exterior of the cadShape depending on the setting of the parameter outside.
62 | * @param lines array of lines for a cadShape for which this code will compute a parallel path
63 | * @param radius offset distance for parallel path (radius of CNC tool)
64 | * @param outside true if parallel path should be around the outside of the cadShape, else inside
65 | * @return array of points for the parallel path
66 | */
67 | static Point2D.Double[] getParallelPath (Line2D.Double[] lines, double radius, boolean outside) {
68 | // Prune any between points that are parallel (on the line from prior point to next point)
69 | List tmp = new ArrayList<>();
70 | for (int ii = 0; ii < lines.length; ii++) {
71 | Point2D.Double p1 = (Point2D.Double) lines[ii].getP1();
72 | Point2D.Double p2 = (Point2D.Double) lines[(ii + 1) % lines.length].getP1();
73 | Point2D.Double p3 = (Point2D.Double) lines[(ii + 2) % lines.length].getP1();
74 | PLine l1 = new PLine(p1, p2, false);
75 | PLine l2 = new PLine(p2, p3, false);
76 | if (l1.slope != l2.slope) {
77 | tmp.add(p2);
78 | }
79 | }
80 | Point2D.Double[] points = tmp.toArray(new Point2D.Double[0]);
81 | PLine[] pLines = new PLine[points.length];
82 | boolean clockwise = isClockwise(points);
83 | for (int ii = 0; ii < points.length; ii++) {
84 | int jj = (ii + 1) % points.length;
85 | double d = points[ii].distance(points[jj]);
86 | double t = radius / d;
87 | // Compute point "ext" that extends "radius" distance from point cadShape[n] toward point cadShape[n+1]
88 | Point2D.Double ext = new Point2D.Double((1 - t) * points[ii].x + t * points[jj].x, (1 - t) * points[ii].y + t * points[jj].y);
89 | // Compute the normal to point cadShape[n] by rotating point "ext" +/- 90 degrees around point cadShape[n]
90 | Point2D.Double normal;
91 | if (clockwise ^ outside) {
92 | // Rotate point ext -90 degrees (clockwise) around point cadShape[ii]
93 | normal = new Point2D.Double(-(ext.y - points[ii].y) + points[ii].x, (ext.x - points[ii].x) + points[ii].y);
94 | } else {
95 | // Rotate point ext 90 degrees (counter clockwise) around point cadShape[ii]
96 | normal = new Point2D.Double((ext.y - points[ii].y) + points[ii].x, -(ext.x - points[ii].x) + points[ii].y);
97 | }
98 | pLines[ii] = new PLine(new Line2D.Double(points[ii], points[jj]), normal);
99 | }
100 | // Check for shortcut paths and compute path intersections
101 | List oPoints = new ArrayList<>();
102 | for (int ii = 0; ii < points.length; ii++) {
103 | int jj = (ii + 1) % points.length;
104 | Point2D.Double np1 = pLines[ii].intersects(pLines[(ii + 1) % points.length]);
105 | Point2D.Double np2 = pLines[(ii + 1) % points.length].intersects(pLines[(ii + 2) % points.length]);
106 | Point2D.Double ref = points[jj];
107 | // Compute point "tip" that extends "radius" distance from cadShape's point to the intersection
108 | double d = ref.distance(np1);
109 | double t = radius / d;
110 | Point2D.Double tip = new Point2D.Double((1 - t) * ref.x + t * np1.x, (1 - t) * ref.y + t * np1.y);
111 | // Compute line through tip that's perpendicular to the line from cadShape's point to the intersection
112 | PLine perp = new PLine(ref, tip, true);
113 | // Compute intersection with perpendicular and pLines[ii] and pLines[jj]
114 | Point2D.Double p1 = pLines[ii].intersects(perp);
115 | Point2D.Double p2 = pLines[jj].intersects(perp);
116 | double dist = np1.distance(p2) + p2.distance(np2) - np1.distance(np2);
117 | if (dist < .0001) {
118 | oPoints.add(p1);
119 | oPoints.add(p2);
120 | } else {
121 | oPoints.add(np1);
122 | }
123 | }
124 | return oPoints.toArray(new Point2D.Double[0]);
125 | }
126 |
127 | /**
128 | * Prunes sections of a closed path that cross back over themselves by scanning for overlapping line segments
129 | * Note: extremely inefficient algorithm (approx n^2 / 2 where n is numebr of input points), but it works
130 | * Todo: https://www.geeksforgeeks.org/given-a-set-of-line-segments-find-if-any-two-segments-intersect/
131 | * @param points set of input points to prune
132 | * @return pruned array of points
133 | */
134 | static Point2D.Double[] pruneOverlap (Point2D.Double[] points) {
135 | List oPoints = new ArrayList<>();
136 | int exclude = 0;
137 | loop1:
138 | for (int ii = 0; ii < points.length - 1; ii++) {
139 | Line2D.Double l1 = new Line2D.Double(points[ii], points[(ii + 1) % points.length]);
140 | for (int jj = ii + 1; jj < points.length; jj++) {
141 | Line2D.Double l2 = new Line2D.Double(points[jj], points[(jj + 1) % points.length]);
142 | if (l1.intersectsLine(l2) && !l1.getP2().equals(l2.getP1()) && !l1.getP1().equals(l2.getP2())) {
143 | Point2D.Double xx = getIntersection(l1, l2);
144 | oPoints.add(xx);
145 | ii = jj - 1;
146 | exclude = jj + 1;
147 | continue loop1;
148 | }
149 | }
150 | if (ii >= exclude) {
151 | oPoints.add(points[ii]);
152 | }
153 | }
154 | oPoints.add(points[points.length - 1]);
155 | return oPoints.toArray(new Point2D.Double[0]);
156 | }
157 |
158 | /*
159 | * Computes intersection point for two lines
160 | */
161 | public static Point2D.Double getIntersection (Line2D.Double l1, Line2D.Double l2) {
162 | return (new PLine(l1)).intersects(new PLine(l2));
163 | }
164 |
165 | /**
166 | * Scans a set of points forming a closed cadShape to detect if points are in clockwise, or counterclockwise order
167 | * @param points a set of lines forming a closed cadShape
168 | * @return true if cadShape is drawn in clockwise order, else false
169 | */
170 | private static boolean isClockwise (Point2D.Double[] points) {
171 | double sum = 0;
172 | for (int ii = 0; ii < points.length; ii++) {
173 | double x1 = points[ii].x;
174 | double y1 = points[ii].y;
175 | double x2 = points[(ii + 1) % points.length].x;
176 | double y2 = points[(ii + 1) % points.length].y;
177 | sum += (x2 - x1) * (y2 + y1);
178 | }
179 | return sum < 0;
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
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 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/src/ShapeOptimizer.java:
--------------------------------------------------------------------------------
1 | import java.awt.*;
2 | import java.awt.geom.AffineTransform;
3 | import java.awt.geom.Path2D;
4 | import java.awt.geom.PathIterator;
5 | import java.awt.geom.Point2D;
6 | import java.util.ArrayList;
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | /**
12 | * ShapeOptimizer: This code tries to reconnected disconnected line and spline segments into a
13 | * continuous path. This code is invoked by manually selecting a set of shapes and then using
14 | * the Edit menu item "Combine Selected Paths". After processing, all selected shapes are put
15 | * into a group with both newly-reconnected paths and paths that could not be connected.
16 | *
17 | * Limitations: the code assumes that the starting and ending points of the segments to reconnect
18 | * match identically (to float level precision) in order to figure out when the starting point of
19 | * a given segment can be connected to the ending point of another segment. However, it does try
20 | * to reconnect segements that match end to end, or start to start by flipping the start and end
21 | * points, as needed.
22 | */
23 |
24 | public class ShapeOptimizer {
25 | static class ShapeSeg {
26 | int type;
27 | double sx, sy, ex, ey;
28 | double[] coords;
29 | boolean used;
30 |
31 | ShapeSeg (int type, double sx, double sy, double[] coords) {
32 | this.type = type;
33 | this.sx = sx;
34 | this.sy = sy;
35 | this.coords = coords;
36 | switch (type) {
37 | case PathIterator.SEG_LINETO: // 1 (sx c01)
38 | ex = coords[0];
39 | ey = coords[1];
40 | break;
41 | case PathIterator.SEG_QUADTO: // 2 (sx c01 c02)
42 | ex = coords[2];
43 | ey = coords[3];
44 | break;
45 | case PathIterator.SEG_CUBICTO: // 3 (sx c01 c02 c03)
46 | ex = coords[4];
47 | ey = coords[5];
48 | break;
49 | }
50 | }
51 |
52 | // Reverse path order of a segment
53 | void flip () {
54 | switch (type) {
55 | case PathIterator.SEG_LINETO: // 1
56 | coords[0] = sx;
57 | coords[1] = sy;
58 | sx = ex;
59 | sy = ey;
60 | ex = coords[0];
61 | ey = coords[1];
62 | break;
63 | case PathIterator.SEG_QUADTO: // 2
64 | coords[2] = sx;
65 | coords[3] = sy;
66 | sx = ex;
67 | sy = ey;
68 | ex = coords[2];
69 | ey = coords[3];
70 | break;
71 | case PathIterator.SEG_CUBICTO: // 3
72 | double tx = coords[0];
73 | double ty = coords[1];
74 | coords[0] = coords[2];
75 | coords[1] = coords[3];
76 | coords[2] = tx;
77 | coords[3] = ty;
78 | //
79 | coords[4] = sx;
80 | coords[5] = sy;
81 | sx = ex;
82 | sy = ey;
83 | ex = coords[4];
84 | ey = coords[5];
85 | break;
86 | }
87 | }
88 |
89 | Point2D.Float getStart () {
90 | return new Point2D.Float((float) sx, (float) sy);
91 | }
92 |
93 | Point2D.Float getEnd () {
94 | return new Point2D.Float((float) ex, (float) ey);
95 | }
96 | }
97 |
98 | static class Vert {
99 | List segs = new ArrayList<>();
100 |
101 | ShapeSeg getSeg (ShapeSeg from, Point2D.Float vert) {
102 | for (ShapeSeg seg : segs) {
103 | if (seg == from) {
104 | // Don't connect to self
105 | continue;
106 | }
107 | Point2D.Float start = seg.getStart();
108 | if (!seg.used && start.equals(vert)) {
109 | segs.remove(seg);
110 | return seg;
111 | }
112 | Point2D.Float end = seg.getEnd();
113 | if (!seg.used && end.equals(vert)) {
114 | seg.flip();
115 | segs.remove(seg);
116 | return seg;
117 | }
118 | }
119 | return null;
120 | }
121 | }
122 |
123 | /**
124 | * Experimental code designed to connect a set line segments into a continous path
125 | * Note: this code can only connect line segments when the end points have identical 'float' precision
126 | * x and y coordinates.
127 | * @param shape Shape onject containing the line segments to analyze
128 | * @return List of Shape objects (some may be rebuilt into continuous paths)
129 | */
130 | static List optimizeShape (Shape shape) {
131 | List segs = new ArrayList<>();
132 | // Break Shape into List of ShapeSeg objects
133 | PathIterator pi = shape.getPathIterator(new AffineTransform());
134 | double ex = 0, ey = 0;
135 | while (!pi.isDone()) {
136 | double[] coords = new double[6]; // p1.x, p1.y, p2.x, p2.y, p3.x, p3.y
137 | int type = pi.currentSegment(coords);
138 | switch (type) {
139 | case PathIterator.SEG_MOVETO:
140 | ex = coords[0];
141 | ey = coords[1];
142 | break;
143 | case PathIterator.SEG_LINETO:
144 | // lineTo(coords[0], coords[1]);
145 | segs.add(new ShapeSeg(type, ex, ey, coords));
146 | ex = coords[0];
147 | ey = coords[1];
148 | break;
149 | case PathIterator.SEG_QUADTO:
150 | // quadTo(coords[0], coords[1], coords[2], coords[3]);
151 | segs.add(new ShapeSeg(type, ex, ey, coords));
152 | ex = coords[2];
153 | ey = coords[3];
154 | break;
155 | case PathIterator.SEG_CUBICTO:
156 | // curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
157 | segs.add(new ShapeSeg(type, ex, ey, coords));
158 | ex = coords[4];
159 | ey = coords[5];
160 | break;
161 | case PathIterator.SEG_CLOSE:
162 | // closePath();
163 | break;
164 | }
165 | pi.next();
166 | }
167 | // Build Map of vertices with a list of segment (key is float precision)
168 | Map verts = new HashMap<>();
169 | for (ShapeSeg seg : segs) {
170 | // Insert starting point
171 | Point2D.Float start = seg.getStart();
172 | Vert vert = verts.get(start);
173 | if (vert == null) {
174 | verts.put(start, vert = new Vert());
175 | }
176 | vert.segs.add(seg);
177 | // Insert ending point
178 | Point2D.Float end = seg.getEnd();
179 | vert = verts.get(end);
180 | if (vert == null) {
181 | verts.put(end, vert = new Vert());
182 | }
183 | vert.segs.add(seg);
184 | }
185 | // Scan for connected segments and organize into sequences
186 | List opts = new ArrayList<>();
187 | for (ShapeSeg seg : segs) {
188 | if (!seg.used) {
189 | Point2D.Float end = seg.getEnd();
190 | Vert vert = verts.get(end);
191 | ShapeSeg con = vert.getSeg(seg, end);
192 | if (con != null) {
193 | opts.add(seg);
194 | seg.used = true;
195 | while (con != null && !con.used) {
196 | con.used = true;
197 | opts.add(con);
198 | end = con.getEnd();
199 | vert = verts.get(end);
200 | con = vert.getSeg(con, end);
201 | }
202 | } else {
203 | opts.add(seg);
204 | seg.used = true;
205 | }
206 | }
207 | }
208 | segs = opts;
209 | List out = new ArrayList<>();
210 | // Combine list of reorganized ShapeSeg objects into a List of Shape objects
211 | Path2D.Double path = new Path2D.Double();
212 | ex = Double.MAX_VALUE; ey = Double.MAX_VALUE;
213 | for (ShapeSeg seg : segs) {
214 | if ((float) seg.sx != (float) ex || (float) seg.sy != (float) ey) {
215 | path = new Path2D.Double();
216 | out.add(path);
217 | path.moveTo(ex = seg.sx, ey = seg.sy);
218 | }
219 | switch (seg.type) {
220 | case PathIterator.SEG_LINETO: // 1
221 | path.lineTo(ex = seg.coords[0], ey = seg.coords[1]);
222 | break;
223 | case PathIterator.SEG_QUADTO: // 2
224 | path.quadTo(seg.coords[0], seg.coords[1], ex = seg.coords[2], ey = seg.coords[3]);
225 | break;
226 | case PathIterator.SEG_CUBICTO: // 3
227 | path.curveTo(seg.coords[0], seg.coords[1], seg.coords[2], seg.coords[3], ex = seg.coords[4], ey = seg.coords[5]);
228 | break;
229 | case PathIterator.SEG_CLOSE: // 4
230 | // Close and write out the current curve
231 | path.closePath();
232 | break;
233 | }
234 | }
235 | return out;
236 | }
237 |
238 | public static void main (String[] args) {
239 | // Create square with disconnected and misordered line segments
240 | Path2D.Double path = new Path2D.Double();
241 | path.moveTo(1, 1); // 1,1 -> 2,1 top
242 | path.lineTo(2, 1);
243 | path.moveTo(1, 2); // 1,2 -> 2,2 bot
244 | path.lineTo(2, 2);
245 | path.moveTo(2, 2); // 2,2 -> 2,1 right
246 | path.lineTo(2, 1);
247 | path.moveTo(1, 1); // 1,1 -> 1,2 left
248 | path.lineTo(1, 2);
249 | // Combine segments into continuous path
250 | List list = optimizeShape(path);
251 | System.out.println(list.size());
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/GerberZip.java:
--------------------------------------------------------------------------------
1 | import java.awt.geom.Path2D;
2 | import java.awt.geom.Point2D;
3 | import java.awt.geom.Rectangle2D;
4 | import java.io.File;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.util.*;
8 | import java.util.zip.ZipEntry;
9 | import java.util.zip.ZipFile;
10 |
11 | // Crude Excellon Drill File Parser (just enough read the DRILL.TXT from Osmond PCB Gerber files)
12 | // See: https://web.archive.org/web/20071030075236/http://www.excellon.com/manuals/program.htm
13 |
14 | public class GerberZip {
15 | private String excellon, outline;
16 |
17 | GerberZip (File zipFile) throws IOException {
18 | ZipFile zip = new ZipFile(zipFile);
19 | Enumeration extends ZipEntry> entries = zip.entries();
20 | while (entries.hasMoreElements()) {
21 | ZipEntry entry = entries.nextElement();
22 | InputStream stream = zip.getInputStream(entry);
23 | byte[] data = new byte[stream.available()];
24 | stream.read(data);
25 | String tmp = new String(data);
26 | stream.close();
27 | if (entry.getName().equals("DRILL.TXT")) {
28 | excellon = tmp;
29 | } else if (entry.getName().equals("OUTLINE.GER")) {
30 | outline = tmp;
31 | }
32 | }
33 | zip.close();
34 | }
35 |
36 | static class ExcellonHole {
37 | double xLoc;
38 | double yLoc;
39 | double diameter;
40 |
41 | ExcellonHole (double x, double y, double dia) {
42 | xLoc = x;
43 | yLoc = y;
44 | diameter = dia;
45 | }
46 | }
47 |
48 | List getShapes () {
49 | List holes = parseExcellon();
50 | List> outlines = parseOutlines();
51 | Rectangle2D.Double bounds = GerberZip.getBounds(outlines);
52 | // System.out.println("PCB Size: " + bounds.getWidth() + " inches, " + bounds.getHeight() + " inches");
53 | double yBase = bounds.getHeight();
54 | List gShapes = new ArrayList<>();
55 | for (GerberZip.ExcellonHole hole : holes) {
56 | gShapes.add(new CADOval(hole.xLoc, yBase - hole.yLoc, hole.diameter, hole.diameter, 0));
57 | }
58 | // Build shapes for all outlines
59 | for (List points : outlines) {
60 | Path2D.Double path = new Path2D.Double();
61 | boolean first = true;
62 | for (Point2D.Double point : points) {
63 | if (first) {
64 | path.moveTo(point.getX() - bounds.width / 2, yBase - point.getY() - bounds.height / 2);
65 | first = false;
66 | } else {
67 | path.lineTo(point.getX() - bounds.width / 2, yBase - point.getY() - bounds.height / 2);
68 | }
69 | }
70 | CADShape outline = new CADShape(path, 0, 0, 0);
71 | gShapes.add(outline);
72 | }
73 | CADShapeGroup group = new CADShapeGroup();
74 | for (CADShape cShape : gShapes) {
75 | group.addToGroup(cShape);
76 | }
77 | return gShapes;
78 | }
79 |
80 | public static void main (String[] args) throws Exception {
81 | GerberZip gerber = new GerberZip(new File("Test/Gerber Files/archive.zip"));
82 | List holes = gerber.parseExcellon();
83 | for (ExcellonHole hole : holes) {
84 | System.out.println(hole.xLoc + "," + hole.yLoc + "," + hole.diameter);
85 | }
86 | Rectangle2D.Double bounds = getBounds(gerber.parseOutlines());
87 | System.out.println("PCB Size: " + bounds.getWidth() + " inches, " + bounds.getHeight() + " inches");
88 | }
89 |
90 | List parseExcellon () {
91 | int holeType = 0;
92 | Map tools = new TreeMap<>();
93 | List holes = new ArrayList<>();
94 | StringTokenizer tok = new StringTokenizer(excellon);
95 | while (tok.hasMoreElements()) {
96 | String line = tok.nextToken();
97 | if (line.startsWith("T")) {
98 | if (line.contains("C")) {
99 | // Read hold definition
100 | int idx = line.indexOf("C");
101 | String type = line.substring(1, idx);
102 | String val = line.substring(idx + 1);
103 | tools.put(Integer.parseInt(type), Double.parseDouble(val));
104 | } else {
105 | // Read beginning of hole list marker
106 | String type = line.substring(1);
107 | holeType = Integer.parseInt(type);
108 | }
109 | } else if (line.startsWith("X") && line.contains("Y")) {
110 | // Read hole position
111 | int idx = line.indexOf("Y");
112 | String xVal = line.substring(1, idx);
113 | String yVal = line.substring(idx + 1);
114 | double xx = parseExcellonValue(xVal);
115 | double yy = parseExcellonValue(yVal);
116 | double diameter = tools.get(holeType);
117 | holes.add(new ExcellonHole(xx, yy, diameter));
118 | }
119 | }
120 | return holes;
121 | }
122 |
123 | List> parseOutlines () {
124 | double lineWid = 0;
125 | Map apertures = new HashMap<>();
126 | StringTokenizer tok = new StringTokenizer(outline, "\n\r");
127 | List> outlines = new ArrayList<>();
128 | List points = new ArrayList<>();
129 | double lastX = 0, lastY = 0;
130 | while (tok.hasMoreElements()) {
131 | String line = tok.nextToken();
132 | if (line.startsWith("%")) {
133 | line = line.substring(1);
134 | if (line.startsWith("ADD")) {
135 | line = line.substring(3);
136 | int idx = line.indexOf("C");
137 | int aNum = Integer.parseInt(line.substring(0, idx));
138 | int idx2 = line.indexOf(",");
139 | int idx3 = line.indexOf("*");
140 | if (idx2 > 0 && idx3 > idx2) {
141 | double aSize = Double.parseDouble(line.substring(idx2 + 1, idx3));
142 | apertures.put(aNum, aSize);
143 | }
144 | }
145 | } else {
146 | String[] items = line.split("\\*");
147 | for (String item : items) {
148 | if (item.startsWith("G01")) {
149 | item = item.substring(3);
150 | }
151 | if (item.startsWith("D")) {
152 | int aNum = Integer.parseInt(item.substring(1));
153 | lineWid = apertures.get(aNum);
154 | // Hmmm... Omsond has a fixed x/y offset of 1 mil and lineWid has no effect on the outline
155 | }
156 | if (item.startsWith("X") && item.contains("Y") && item.contains("D")) {
157 | int yIdx = item.indexOf("Y");
158 | int dIdx = item.indexOf("D");
159 | double xVal = parseExcellonValue(item.substring(1, yIdx)) - lineWid;
160 | double yVal = parseExcellonValue(item.substring(yIdx + 1, dIdx));
161 | int dNum = Integer.parseInt(item.substring(dIdx + 1));
162 | if (dNum == 2) {
163 | points = new ArrayList<>();
164 | outlines.add(points);
165 | }
166 | points.add(new Point2D.Double(lastX = xVal, lastY = yVal));
167 | } else if (item.startsWith("X") && item.contains("Y")) {
168 | int yIdx = item.indexOf("Y");
169 | double xVal = parseExcellonValue(item.substring(1, yIdx));
170 | double yVal = parseExcellonValue(item.substring(yIdx + 1));
171 | points.add(new Point2D.Double(lastX = xVal, lastY = yVal));
172 | } else if (item.startsWith("X") && item.contains("D")) {
173 | int dIdx = item.indexOf("D");
174 | double xVal = parseExcellonValue(item.substring(1, dIdx));
175 | int dNum = Integer.parseInt(item.substring(dIdx + 1));
176 | if (dNum == 2) {
177 | points = new ArrayList<>();
178 | outlines.add(points);
179 | }
180 | points.add(new Point2D.Double(lastX = xVal, lastY));
181 | } else if (item.startsWith("X")) {
182 | double xVal = parseExcellonValue(item.substring(1));
183 | points.add(new Point2D.Double(lastX = xVal, lastY));
184 | } else if (item.startsWith("Y") && item.contains("D")) {
185 | int dIdx = item.indexOf("D");
186 | double yVal = parseExcellonValue(item.substring(1, dIdx));
187 | int dNum = Integer.parseInt(item.substring(dIdx + 1));
188 | if (dNum == 2) {
189 | points = new ArrayList<>();
190 | outlines.add(points);
191 | }
192 | points.add(new Point2D.Double(lastX, lastY = yVal));
193 | } else if (item.startsWith("Y")) {
194 | double yVal = parseExcellonValue(item.substring(1));
195 | points.add(new Point2D.Double(lastX, lastY = yVal));
196 | }
197 | }
198 | }
199 | }
200 | return outlines;
201 | }
202 |
203 | public static Rectangle2D.Double getBounds (List> outlines) {
204 | double xMax = 0, yMax = 0;
205 | double xMin = Double.MAX_VALUE, yMin = Double.MAX_VALUE;
206 | for (List points : outlines) {
207 | for (Point2D.Double point : points) {
208 | xMax = Math.max(xMax, point.getX());
209 | xMin = Math.min(xMin, point.getX());
210 | yMax = Math.max(yMax, point.getY());
211 | yMin = Math.min(yMin, point.getY());
212 | }
213 | }
214 | return new Rectangle2D.Double(xMin, yMin, xMax, yMax);
215 | }
216 |
217 | private static double parseExcellonValue(String val) {
218 | StringBuilder valBuilder = new StringBuilder(val);
219 | while (valBuilder.length() < 2) {
220 | valBuilder.append("0");
221 | }
222 | val = valBuilder.toString();
223 | return Double.parseDouble(val.substring(0, 2) + "." + val.substring(2));
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/images/LaserCut Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/CADText.java:
--------------------------------------------------------------------------------
1 | import java.awt.*;
2 | import java.awt.font.GlyphVector;
3 | import java.awt.font.TextAttribute;
4 | import java.awt.geom.AffineTransform;
5 | import java.awt.geom.Path2D;
6 | import java.awt.geom.Rectangle2D;
7 | import java.awt.image.BufferedImage;
8 | import java.io.Serializable;
9 | import java.util.*;
10 | import java.util.List;
11 |
12 | class CADText extends CADShape implements Serializable {
13 | private static final long serialVersionUID = 4314642313295298841L;
14 | public String text, fontName, fontStyle;
15 | public int fontSize;
16 | public double tracking;
17 | private static final Map styles = new HashMap<>();
18 | private static final List fonts = new ArrayList<>();
19 |
20 | static {
21 | // Define available font styles
22 | styles.put("plain", Font.PLAIN);
23 | styles.put("bold", Font.BOLD);
24 | styles.put("italic", Font.ITALIC);
25 | styles.put("bold-italic", Font.BOLD + Font.ITALIC);
26 | // Define available fonts
27 | String[] availFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
28 | Map aMap = new HashMap<>();
29 | for (String tmp : availFonts) {
30 | aMap.put(tmp, tmp);
31 | }
32 | addIfAvailable(aMap, "American Typewriter");
33 | addIfAvailable(aMap, "Arial");
34 | addIfAvailable(aMap, "Arial Black");
35 | addIfAvailable(aMap, "Bauhaus 93");
36 | addIfAvailable(aMap, "Bradley Hand");
37 | addIfAvailable(aMap, "Brush Script");
38 | addIfAvailable(aMap, "Casual");
39 | addIfAvailable(aMap, "Chalkboard");
40 | addIfAvailable(aMap, "Comic Sans MS");
41 | addIfAvailable(aMap, "Edwardian Script ITC");
42 | addIfAvailable(aMap, "Freehand");
43 | addIfAvailable(aMap, "Giddyup Std");
44 | addIfAvailable(aMap, "Helvetica");
45 | addIfAvailable(aMap, "Hobo Std");
46 | addIfAvailable(aMap, "Impact");
47 | addIfAvailable(aMap, "Marker Felt");
48 | addIfAvailable(aMap, "OCR A Std");
49 | addIfAvailable(aMap, "Times New Roman");
50 | addIfAvailable(aMap, "Stencil");
51 | fonts.add("Vector 1");
52 | fonts.add("Vector 2");
53 | fonts.add("Vector 3");
54 | }
55 |
56 | @Override
57 | String getMenuName () {
58 | return "Text";
59 | }
60 |
61 | private static void addIfAvailable (Map avail, String font) {
62 | if (avail.containsKey(font)) {
63 | fonts.add(font);
64 | }
65 | }
66 |
67 | /**
68 | * Default constructor is used to instantiate subclasses in "Shapes" Menu
69 | */
70 | @SuppressWarnings("unused")
71 | CADText () {
72 | // Set typical initial values, which user can edit before saving
73 | text = "Test";
74 | fontName = "Helvetica";
75 | fontStyle = "plain";
76 | fontSize = 24;
77 | tracking = 0;
78 | engrave = true;
79 | }
80 |
81 | CADText (double xLoc, double yLoc, String text, String fontName, String fontStyle, int fontSize, double tracking, double rotation) {
82 | this.text = text;
83 | this.fontName = fontName;
84 | this.fontStyle = fontStyle;
85 | this.fontSize = fontSize;
86 | this.tracking = tracking;
87 | setLocationAndOrientation(xLoc, yLoc, rotation);
88 | }
89 |
90 | @Override
91 | void resize (double dx, double dy) {
92 | double width = dx * 2;
93 | int newPnts = fontSize;
94 | boolean changed = false;
95 | double wid;
96 | double sWid = getSWid(fontSize);
97 | if (sWid < width) {
98 | while ((wid = getSWid(++newPnts)) < width) {
99 | fontSize = newPnts;
100 | changed = true;
101 | }
102 | } else {
103 | while (newPnts > 8 && (wid = getSWid(--newPnts)) > width) {
104 | fontSize = newPnts;
105 | changed = true;
106 | }
107 | }
108 | if (changed) {
109 | buildShape();
110 | }
111 | }
112 |
113 | private double getSWid (int points) {
114 | BufferedImage bi = new BufferedImage(5, 5, BufferedImage.TYPE_INT_RGB);
115 | Graphics gg = bi.getGraphics();
116 | Font fnt = new Font(fontName, styles.get(fontStyle), points);
117 | FontMetrics fm = gg.getFontMetrics(fnt);
118 | return (double) fm.stringWidth(text) / 72.0;
119 | }
120 |
121 | @Override
122 | String[] getParameterNames () {
123 | StringBuilder fontNames = new StringBuilder("fontName");
124 | for (String font : fonts) {
125 | fontNames.append(":");
126 | fontNames.append(font);
127 | }
128 | return new String[]{
129 | "text{text to display}",
130 | fontNames + "{font name}",
131 | "fontStyle:plain:bold:italic:bold-italic{font style}",
132 | "fontSize|pts{font size in points}",
133 | "tracking{controls spacing of glyphs}"};
134 | }
135 |
136 | @Override
137 | Shape buildShape () {
138 | if (fontName.startsWith("Vector")) {
139 | Path2D.Double path = new Path2D.Double();
140 | VectorFont font = VectorFont.getFont(fontName);
141 | int[][][] stroke = font.font;
142 | int lastX = 1000, lastY = 1000;
143 | int xOff = 0;
144 | for (int ii = 0; ii < text.length(); ii++) {
145 | char cc = text.charAt(ii);
146 | cc = cc >= 32 & cc <= 127 ? cc : '_'; // Substitute '_' for codes outside printable ASCII range
147 | int[][] glyph = stroke[cc - 32];
148 | int left = glyph[0][0];
149 | int right = glyph[0][1];
150 | for (int jj = 1; jj < glyph.length; jj++) {
151 | int x1 = glyph[jj][0] - left;
152 | int y1 = glyph[jj][1];
153 | int x2 = glyph[jj][2] - left;
154 | int y2 = glyph[jj][3];
155 |
156 | if (x1 != lastX || y1 != lastY) {
157 | path.moveTo(x1 + xOff, y1);
158 | }
159 | path.lineTo(x2 + xOff, lastY = y2);
160 | lastX = x2;
161 | }
162 | int step = right - left;
163 | xOff += step;
164 | }
165 | AffineTransform at = new AffineTransform();
166 | double scale = fontSize / (72.0 * font.height);
167 | at.scale(scale, scale);
168 | Shape text = at.createTransformedShape(path);
169 | Rectangle2D bounds = text.getBounds2D();
170 | at = new AffineTransform();
171 | at.translate(-bounds.getX(), -bounds.getY());
172 | text = at.createTransformedShape(text);
173 | bounds = text.getBounds2D();
174 | at = new AffineTransform();
175 | at.translate(-bounds.getWidth() / 2, -bounds.getHeight() / 2);
176 | return at.createTransformedShape(text);
177 | } else {
178 | // Code from: http://www.java2s.com/Tutorial/Java/0261__2D-Graphics/GenerateShapeFromText.htm
179 | BufferedImage img = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB);
180 | Graphics2D g2 = img.createGraphics();
181 | Font font = new Font(fontName, styles.get(fontStyle), fontSize);
182 | HashMap attrs = new HashMap<>();
183 | attrs.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
184 | attrs.put(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON);
185 | attrs.put(TextAttribute.TRACKING, tracking);
186 | font = font.deriveFont(attrs);
187 | g2.setFont(font);
188 | try {
189 | GlyphVector vect = font.createGlyphVector(g2.getFontRenderContext(), text);
190 | AffineTransform at = new AffineTransform();
191 | at.scale(1 / 72.0, 1 / 72.0);
192 | Shape text = at.createTransformedShape(vect.getOutline());
193 | Rectangle2D bounds = text.getBounds2D();
194 | at = new AffineTransform();
195 | at.translate(-bounds.getX(), -bounds.getY());
196 | text = at.createTransformedShape(text);
197 | bounds = text.getBounds2D();
198 | at = new AffineTransform();
199 | at.translate(-bounds.getWidth() / 2, -bounds.getHeight() / 2);
200 | return at.createTransformedShape(text);
201 | } finally {
202 | g2.dispose();
203 | }
204 | }
205 | }
206 |
207 | static class VectorFont {
208 | private static final Map vFonts = new HashMap<>();
209 | int[][][] font;
210 | final int height;
211 |
212 | VectorFont (String name) {
213 | height = 32;
214 | switch (name) {
215 | case "Vector 1":
216 | font = getFontData("hershey1.txt");
217 | break;
218 | case "Vector 2":
219 | font = getFontData("hershey2.txt");
220 | break;
221 | case "Vector 3":
222 | font = getFontData("hershey3.txt");
223 | break;
224 | }
225 | }
226 |
227 | static VectorFont getFont (String name) {
228 | VectorFont font = vFonts.get(name);
229 | if (font == null) {
230 | vFonts.put(name, font = new VectorFont(name));
231 | }
232 | return font;
233 | }
234 |
235 | private int[][][] getFontData (String name) {
236 | String data = Utils2D.getResourceFile("fonts/" + name);
237 | StringTokenizer tok = new StringTokenizer(data, "\n");
238 | int[][][] font = new int[128][][];
239 | while (tok.hasMoreElements()) {
240 | String line = tok.nextToken();
241 | if (line.charAt(3) == ':') {
242 | char cc = line.charAt(1);
243 | line = line.substring(4);
244 | String[] vecs = line.split("\\|");
245 | int[][] vec = new int[vecs.length][];
246 | font[cc - 32] = vec;
247 | for (int ii = 0; ii < vecs.length; ii++) {
248 | String[] coords = vecs[ii].split(",");
249 | int[] tmp = new int[coords.length];
250 | for (int jj = 0; jj < tmp.length; jj++) {
251 | tmp[jj] = Integer.parseInt(coords[jj]);
252 | vec[ii] = tmp;
253 | }
254 | }
255 | }
256 | }
257 | return font;
258 | }
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/src/CADMusicStrip.java:
--------------------------------------------------------------------------------
1 | import java.awt.*;
2 | import java.awt.geom.Ellipse2D;
3 | import java.awt.geom.Line2D;
4 | import java.awt.geom.Path2D;
5 | import java.awt.geom.Point2D;
6 | import java.io.File;
7 | import java.io.Serializable;
8 | import java.nio.file.Files;
9 | import java.util.*;
10 | import java.util.List;
11 |
12 | /**
13 | * Example music file
14 | * 6E
15 | * 6D, 4D, 3C
16 | * 4F#
17 | * etc.
18 | *
19 | * Resources:
20 | * musicboxmaniacs.com/
21 | * www.youtube.com/watch?v=apcsggbbBFw
22 | */
23 |
24 | class CADMusicStrip extends CADShape implements Serializable, LaserCut.Updatable, LaserCut.NorResizable {
25 | private static final long serialVersionUID = 7398125917619364676L;
26 | private static final Map noteIndex = new HashMap<>();
27 | private static final double xStep = 4.0;
28 | private static final double yStep = 2.017;
29 | private static final double xOff = Utils2D.mmToInches(12.0);
30 | private static final double yOff = Utils2D.mmToInches(6.0);
31 | private static final double holeDiam = Utils2D.mmToInches(2.0);
32 | private boolean checkClicked;
33 | public int columns = 60;
34 | public double width, height;
35 | public boolean[][] notes;
36 | private transient Shape rect;
37 | private transient int lastCol = 0;
38 |
39 | static {
40 | // Only notes marked 'true' are available on 30 note music box player
41 | Note[] noteList = {
42 | // Oct Note Used
43 | new Note(3, "C", true), // 3C
44 | new Note(3, "C#", false),
45 | new Note(3, "D#", false),
46 | new Note(3, "E", false),
47 | new Note(3, "F", false),
48 | new Note(3, "G", true), // 3G
49 | new Note(3, "G#", false),
50 | new Note(3, "A", true), // 3A
51 | new Note(3, "A#", false),
52 | new Note(3, "B", true), // 3B
53 | new Note(4, "C", true), // 4C
54 | new Note(4, "C#", true), // 4C#
55 | new Note(4, "D", true), // 4D
56 | new Note(4, "D#", false),
57 | new Note(4, "E", true), // 3E
58 | new Note(4, "F", true), // 4F
59 | new Note(4, "F#", true), // 4F#
60 | new Note(4, "G", true), // 4G
61 | new Note(4, "G#", true), // 4G#
62 | new Note(4, "A", true), // 4A
63 | new Note(4, "A#", true), // 4A#
64 | new Note(4, "B", true), // 4B
65 | new Note(5, "C", true), // 5C
66 | new Note(5, "C#", true), // 5C#
67 | new Note(5, "D", true), // 5D
68 | new Note(5, "D#", true), // 5D#
69 | new Note(5, "E", true), // 5E
70 | new Note(5, "F", true), // %F
71 | new Note(5, "F#", true), // 5F#
72 | new Note(5, "G", true), // 5
73 | new Note(5, "G#", true), // 5G#
74 | new Note(5, "A", true), // 5A
75 | new Note(5, "A#", true), // 5A#
76 | new Note(5, "B", true), // 5B
77 | new Note(6, "C", true), // 6C
78 | new Note(6, "C#", false),
79 | new Note(6, "D", true), // 6D
80 | new Note(6, "D#", false),
81 | new Note(6, "E", true), // 6E
82 | };
83 | int idx = 0;
84 | for (int ii = noteList.length - 1; ii >= 0; ii--) {
85 | Note note = noteList[ii];
86 | if (note.used) {
87 | noteIndex.put(note.note, idx++);
88 | }
89 | }
90 | int dum = 0;
91 | }
92 |
93 | static class Note {
94 | String note;
95 | boolean used;
96 |
97 | Note (int octave, String note, boolean used) {
98 | this.note = octave + note;
99 | this.used = used;
100 | }
101 | }
102 |
103 | void readMusicBoxFile (File sFile) throws Exception {
104 | Scanner lines = new Scanner(Files.newInputStream(sFile.toPath()));
105 | List cols = new ArrayList<>();
106 | while (lines.hasNextLine()) {
107 | Scanner line = new Scanner(lines.nextLine().trim());
108 | List notes = new ArrayList<>();
109 | while (line.hasNext()) {
110 | String item = line.next();
111 | System.out.println(item);
112 | item = item.endsWith(",") ? item.substring(0, item.length() - 1) : item;
113 | notes.add(item);
114 | }
115 | cols.add(notes.toArray(new String[0]));
116 | notes = new ArrayList<>();
117 | }
118 | String[][] song = cols.toArray(new String[cols.size()][0]);
119 | notes = new boolean[song.length][30];
120 | for (int ii = 0; ii < song.length; ii++) {
121 | for (String note : song[ii]) {
122 | if (noteIndex.containsKey(note)) {
123 | notes[ii][noteIndex.get(note)] = true;
124 | }
125 | }
126 | }
127 | width = Utils2D.mmToInches(song.length * 4 + 16);
128 | height = Utils2D.mmToInches(70);
129 | }
130 |
131 | @Override
132 | String getMenuName () {
133 | return "Music Strip";
134 | }
135 |
136 | @Override
137 | void updateStateAfterParameterEdit () {
138 | if (notes == null) {
139 | notes = new boolean[columns][30];
140 | } else {
141 | // Resize array and copy notes from old array
142 | boolean[][] nNotes = new boolean[columns][30];
143 | for (int ii = 0; ii < Math.min(notes.length, nNotes.length); ii++) {
144 | System.arraycopy(notes[ii], 0, nNotes[ii], 0, notes[ii].length);
145 | }
146 | notes = nNotes;
147 | }
148 | width = Utils2D.mmToInches(columns * 4 + 16);
149 | height = Utils2D.mmToInches(70);
150 | }
151 |
152 | @Override
153 | void draw (Graphics g, double zoom, boolean keyRotate, boolean keyResize, boolean keyOption) {
154 | Graphics2D g2 = (Graphics2D) g.create();
155 | Stroke thick = new BasicStroke(1.0f);
156 | Stroke thin = new BasicStroke(0.8f);
157 | double mx = (xLoc + xOff) * zoom * LaserCut.SCREEN_PPI;
158 | double my = (yLoc + yOff) * zoom * LaserCut.SCREEN_PPI;
159 | double zf = zoom / LaserCut.SCREEN_PPI;
160 | g2.setFont(new Font("Arial", Font.PLAIN, (int) (7 * zf)));
161 | for (int ii = 0; ii <= notes.length; ii++) {
162 | double sx = mx + Utils2D.mmToInches(ii * xStep * zoom * LaserCut.SCREEN_PPI);
163 | g2.setColor((ii & 1) == 0 ? Color.black : isSelected ? Color.black : Color.lightGray);
164 | g2.setStroke((ii & 1) == 0 ? thick : thin);
165 | g2.draw(new Line2D.Double(sx, my, sx, my + Utils2D.mmToInches(29 * yStep * zoom * LaserCut.SCREEN_PPI)));
166 | for (int jj = 0; jj < 30; jj++) {
167 | double sy = my + Utils2D.mmToInches(jj * yStep * zoom * LaserCut.SCREEN_PPI);
168 | g2.setColor(jj == 0 || jj == 29 ? Color.black : isSelected ? Color.black : Color.lightGray);
169 | g2.setStroke(jj == 0 || jj == 29 ? thick : thin);
170 | g2.draw(new Line2D.Double(mx, sy, mx + Utils2D.mmToInches(columns * xStep * zoom * LaserCut.SCREEN_PPI), sy));
171 | if (ii == lastCol) {
172 | g2.setColor(Color.red);
173 | //g2.drawString(symb[jj], (int) (sx - 14 * zf), (int) (sy + 2.5 * zf));
174 | }
175 | }
176 | }
177 | g2.dispose();
178 | super.draw(g, zoom, false, keyResize, keyOption);
179 | }
180 |
181 | // Implement Updatable interface
182 | public boolean updateInternalState (Point2D.Double point) {
183 | // See if user clicked on one of the note spots (Note: point in screen inch coords)
184 | double xx = Utils2D.inchesToMM(point.x - xLoc - xOff);
185 | double yy = Utils2D.inchesToMM(point.y - yLoc - yOff);
186 | double gridX = Math.floor((xx / xStep) + 0.5);
187 | double gridY = Math.floor((yy / yStep) + 0.5);
188 | double dX = xx - gridX * xStep;
189 | double dY = yy - gridY * yStep;
190 | double dist = Math.sqrt(dX * dX + dY * dY);
191 | //System.out.println(df.format(gridX) + ", " + df.format(gridY) + " - " + df.format(dist));
192 | if (dist <= 1.5 && gridX >= 0 && gridX < notes.length && gridY >= 0 && gridY < 30) {
193 | // Used has clicked in a note circle
194 | notes[(int) gridX][(int) gridY] ^= true;
195 | lastCol = (int) gridX;
196 | updateShape();
197 | return true;
198 | }
199 | return gridX >= 0 && gridX < notes.length && gridY >= 0 && gridY < 30;
200 | }
201 |
202 | @Override
203 | Shape getShape () {
204 | if (checkClicked && rect != null) {
205 | return rect;
206 | }
207 | return super.getShape();
208 | }
209 |
210 | @Override
211 | boolean isShapeClicked (Point2D.Double point, double zoomFactor) {
212 | checkClicked = true;
213 | boolean clicked = super.isShapeClicked(point, zoomFactor);
214 | checkClicked = false;
215 | return clicked;
216 | }
217 |
218 | @Override
219 | Shape buildShape () {
220 | Path2D.Double path = new Path2D.Double();
221 | double xx = -width / 2;
222 | double yy = -height / 2;
223 | // Draw enclosing box with notched corner to indicate orientation of strip
224 | path.moveTo(xx, yy);
225 | path.lineTo(xx + width, yy);
226 | path.lineTo(xx + width, yy + height);
227 | path.lineTo(xx + .2, yy + height);
228 | path.lineTo(xx, yy - .4 + height);
229 | path.lineTo(xx, yy);
230 | // Draw the holes that need to be cut for active notes
231 | double rad = holeDiam / 2;
232 | for (int ii = 0; ii < notes.length; ii++) {
233 | double sx = xx + xOff + Utils2D.mmToInches(ii * xStep);
234 | for (int jj = 0; jj < 30; jj++) {
235 | double sy = yy + yOff + Utils2D.mmToInches(jj * yStep);
236 | if (notes[ii][jj]) {
237 | path.append(new Ellipse2D.Double(sx - rad, sy - rad, holeDiam, holeDiam), false);
238 | }
239 | }
240 | }
241 | return path;
242 | }
243 |
244 | @Override
245 | protected java.util.List getEditFields () {
246 | return Arrays.asList(
247 | "columns",
248 | "xLoc|in",
249 | "yLoc|in");
250 | }
251 |
252 | @Override
253 | protected List getPlaceFields () {
254 | return Arrays.asList("columns", "xLoc|in", "yLoc|in");
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/src/GearGen.java:
--------------------------------------------------------------------------------
1 |
2 | import javax.swing.*;
3 | import java.awt.*;
4 | import java.awt.geom.*;
5 | import java.util.ArrayList;
6 | import java.util.Scanner;
7 |
8 | // See: http://printobjects.me/catalogue/ujava-gear-generator-involute-and-fillet_520801/
9 | // Also: http://khkgears.net/gear-knowledge/gear-technical-reference/calculation-gear-dimensions/
10 | // And: http://fab.cba.mit.edu/classes/863.09/people/cranor/How_to_Make_(Almost)_Anything/David_Cranor/Entries/2009/10/12_Entry_1_files/module.pdf
11 |
12 | public class GearGen {
13 | private static final double PPI = java.awt.Toolkit.getDefaultToolkit().getScreenResolution();
14 | private static boolean DEBUG;
15 |
16 | // Derived from GearGen.java in LibLasercut
17 |
18 | /**
19 | * Test code for GearGen
20 | */
21 | @SuppressWarnings("ConstantIfStatement")
22 | public static void main (String[] args) {
23 | DEBUG = true;
24 | ShapeWindow sWin = new ShapeWindow();
25 | if (true) {
26 | Shape gear = generateGear(.45, 30, 10, 20, .25, Utils2D.mmToInches(3));
27 | sWin.addShape(gear);
28 | } else {
29 | Scanner sc = new Scanner(System.in);
30 | System.out.print("Module (mm diameter/teeth, commonly 0.5, 0.8, 1.00, 1.25, 1.50, 2.50, or 3 ): ");
31 | double module = sc.nextDouble();
32 | System.out.print("Pressure Angle in degrees (commonly 14.5, 20, or 25): ");
33 | double pressAngle = sc.nextDouble();
34 | System.out.print("Number of Teeth: ");
35 | int numTeeth = sc.nextInt();
36 | double minShift = Math.max(-0.5, (30.0 - numTeeth) / 40.0);
37 | double maxShift = 0.6;
38 | System.out.print("Profile Shift (recommended [" + minShift + ", " + maxShift + "]): ");
39 | // Note: profileShift needed for gears with small number of teeth
40 | // See: http://khkgears.net/gear-knowledge/abcs-gears-b/gear-profile-shift/
41 | double profileShift = sc.nextDouble();
42 | System.out.print("Number of Output Points for Involute Curve and Fillet Segments: ");
43 | int numPoints = sc.nextInt();
44 | Shape gear = generateGear(module, numTeeth, numPoints, pressAngle, profileShift, 3);
45 | sWin.addShape(gear);
46 | }
47 | }
48 |
49 | /**
50 | * Generate Involute Spur Gear based on provided parameters
51 | * @param module module = reference diameter / number of teeth (gears must have same module to mesh)
52 | * @param numTeeth total number of teeth in gear
53 | * @param numPoints number of points used when drawing tooth profile
54 | * @param pressAngle pressure angle (typically 20 degrees)
55 | * @param profileShift adjusts center distance
56 | * @param holeSize size of hole in center of gear
57 | * @return generated gear Shape
58 | */
59 | public static Shape generateGear (double module, int numTeeth, int numPoints, double pressAngle, double profileShift, double holeSize) {
60 | pressAngle = Math.PI / 180.0 * pressAngle;
61 | double pitchDiameter = module * numTeeth; // Pitch Diameter = Module * Teeth
62 | double baseDiameter = pitchDiameter * Math.cos(pressAngle); // Base Circle Diameter = Pitch Diameter × Cosine(Pressure Angle)
63 | double dedendum = 1.157 * module; // Dedendum = 1.157 × Module
64 | double workDepth = 2 * module; // Working Depth = 2 × Module
65 | double wholeDepth = 2.157 * module; // Whole Depth = 2.157 × Module
66 | double outerDiameter = module * (numTeeth + 2); // Outside Diameter = Module × (Teeth + 2)
67 | double tipRadius = 0.25 * module; // Tip Radius = 0.25 x Module
68 | double U = -(Math.PI / 4.0 + (1.0 - 0.25) * Math.tan(pressAngle) + 0.25 / Math.cos(pressAngle));
69 | double V = 0.25 - 1.0;
70 | ArrayList points = new ArrayList<>();
71 | // Generate Involute Points
72 | double thetaMin = 2.0 / numTeeth * (U + (V + profileShift) / Math.tan(pressAngle));
73 | double thetaMax = 1.0 / (numTeeth * Math.cos(pressAngle)) * Math.sqrt(Math.pow((2 + numTeeth + 2 * profileShift), 2) -
74 | Math.pow(numTeeth * Math.cos(pressAngle), 2)) - (1 + 2 * profileShift / numTeeth) * Math.tan(pressAngle) -
75 | Math.PI / (2.0 * numTeeth);
76 | double thetaInc = (thetaMax - thetaMin) / numPoints;
77 | double lastY = 0;
78 | for (int ii = numPoints - 1; ii > 0; ii--) {
79 | double theta = thetaMin + thetaInc * ii;
80 | double xx = numTeeth * module / 2.0 * (Math.sin(theta) - ((theta + Math.PI / (2.0 * numTeeth)) * Math.cos(pressAngle) +
81 | 2 * profileShift / numTeeth * Math.sin(pressAngle)) * Math.cos(theta + pressAngle));
82 | double yy = numTeeth * module / 2.0 * (Math.cos(theta) + ((theta + Math.PI / (2.0 * numTeeth)) * Math.cos(pressAngle) +
83 | 2 * profileShift / numTeeth * Math.sin(pressAngle)) * Math.sin(theta + pressAngle));
84 | points.add(new Point2D.Double(xx, yy));
85 | lastY = yy;
86 | }
87 | // Generate Fillet Points
88 | thetaMin = 2.0 / numTeeth * (U + (V + profileShift) / Math.tan(pressAngle));
89 | thetaMax = 2.0 * U / numTeeth;
90 | thetaInc = (thetaMax - thetaMin) / numPoints;
91 | for (int ii = numPoints; ii < 2 * numPoints; ii++) {
92 | double theta = thetaMin + thetaInc * (ii - numPoints);
93 | double L = Math.sqrt(1 + 4 * Math.pow((V + profileShift) / (2 * U - numTeeth * theta), 2));
94 | double Q = 2 * 0.25 / L * (V + profileShift) / (2 * U - numTeeth * theta) + V + numTeeth / 2.0 + profileShift;
95 | double P = 0.25 / L + (U - numTeeth * theta / 2.0);
96 | double xx = module * (P * Math.cos(theta) + Q * Math.sin(theta));
97 | double yy = module * (-P * Math.sin(theta) + Q * Math.cos(theta));
98 | if (yy < lastY) {
99 | // Prevents backtracking line when numTeeth < 8
100 | points.add(new Point2D.Double(xx, yy));
101 | }
102 | }
103 | Point2D.Double[] pointArray = points.toArray(new Point2D.Double[0]);
104 | // Generate tooth Shape from left/right outlines
105 | Path2D.Double tooth = new Path2D.Double();
106 | tooth.moveTo(pointArray[pointArray.length - 1].x, pointArray[pointArray.length - 1].y);
107 | // Draw left section
108 | for (int ii = pointArray.length - 2; ii >= 0; ii--) {
109 | tooth.lineTo(pointArray[ii].x, pointArray[ii].y);
110 | }
111 | // Mirror left section around Y axis to draw right section and fill gap at tip of tooth
112 | for (Point2D.Double point : pointArray) {
113 | tooth.lineTo(-point.x, point.y);
114 | }
115 | // Rotate and place teeth around pitch diameter to generate gear
116 | Path2D.Double gear = new Path2D.Double();
117 | AffineTransform rot = new AffineTransform();
118 | boolean connect = false;
119 | for (int angle = 0; angle < numTeeth; angle++) {
120 | // Use 'connect' to control adding line from prior tooth to newly-placed tooth
121 | gear.append(tooth.getPathIterator(rot), connect);
122 | connect = true;
123 | rot.rotate(Math.toRadians(360.0 - 360.0 / numTeeth));
124 | }
125 | gear.closePath();
126 | Area area = new Area(gear);
127 | if (holeSize > 0) {
128 | Ellipse2D.Double hole = new Ellipse2D.Double(-holeSize / 2, -holeSize / 2, holeSize, holeSize);
129 | gear.append(hole.getPathIterator(new AffineTransform()), false);
130 | }
131 | if (DEBUG) {
132 | System.out.println("module: " + module);
133 | System.out.println("pitch diameter: " + pitchDiameter);
134 | System.out.println("circular pitch: " + pitchDiameter * Math.PI / numTeeth);
135 | System.out.println("extended pitch diameter: " + (pitchDiameter + 2 * profileShift));
136 | System.out.println("profile shift: " + profileShift);
137 | System.out.println("base diameter: " + baseDiameter);
138 | System.out.println("addendum: " + module);
139 | System.out.println("dedendum: " + dedendum);
140 | System.out.println("working depth: " + workDepth);
141 | System.out.println("whole depth: " + wholeDepth);
142 | System.out.println("outer diameter: " + outerDiameter);
143 | System.out.println("tip radius: " + tipRadius);
144 | }
145 | return gear;
146 | }
147 |
148 | static class ShapeWindow extends JFrame {
149 | private transient Image offScr;
150 | private Dimension lastDim;
151 | private final ArrayList shapes = new ArrayList<>();
152 |
153 | ShapeWindow () {
154 | setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
155 | setSize(2000, 2000);
156 | setVisible(true);
157 | }
158 |
159 | void addShape (Shape shape) {
160 | shapes.add(shape);
161 | repaint();
162 | }
163 |
164 | public void paint (Graphics g) {
165 | Dimension d = getSize();
166 | if (offScr == null || (lastDim != null && (d.width != lastDim.width || d.height != lastDim.height)))
167 | offScr = createImage(d.width, d.height);
168 | lastDim = d;
169 | double cx = d.getWidth() / 2;
170 | double cy = d.getHeight() / 2;
171 | Graphics2D g2 = (Graphics2D) offScr.getGraphics();
172 | g2.setBackground(getBackground());
173 | g2.clearRect(0, 0, d.width, d.height);
174 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
175 | g2.setStroke(new BasicStroke(0.5f));
176 | g2.setColor(Color.black);
177 | // Translate Shape to position and scale up to true size on screen using mm units
178 | AffineTransform at = AffineTransform.getTranslateInstance(cx, cy);
179 | at.scale(PPI, PPI);
180 | for (Shape shape :shapes) {
181 | shape = at.createTransformedShape(shape);
182 | g2.draw(shape);
183 | }
184 | g.drawImage(offScr, 0, 0, this);
185 | }
186 | }
187 | }
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/resources/fonts/hershey1.txt:
--------------------------------------------------------------------------------
1 | // Font: Roman Simplex - Bounds {-11, -16, 11, 16} 22 x 32 - Note: {left,right},{x1,y1,x2,y2},..
2 | ' ':-8,8
3 | '!':-5,5|0,-12,0,2|0,7,-1,8|-1,8,0,9|0,9,1,8|1,8,0,7
4 | '"':-8,8|-4,-12,-4,-5|4,-12,4,-5
5 | '#':-10,11|1,-16,-6,16|7,-16,0,16|-6,-3,8,-3|-7,3,7,3
6 | '$':-10,10|-2,-16,-2,13|2,-16,2,13|7,-9,5,-11|5,-11,2,-12|2,-12,-2,-12|-2,-12,-5,-11|-5,-11,-7,-9|-7,-9,-7,-7|-7,-7,-6,-5|-6,-5,-5,-4|-5,-4,-3,-3|-3,-3,3,-1|3,-1,5,0|5,0,6,1|6,1,7,3|7,3,7,6|7,6,5,8|5,8,2,9|2,9,-2,9|-2,9,-5,8|-5,8,-7,6
7 | '%':-12,12|9,-12,-9,9|-4,-12,-2,-10|-2,-10,-2,-8|-2,-8,-3,-6|-3,-6,-5,-5|-5,-5,-7,-5|-7,-5,-9,-7|-9,-7,-9,-9|-9,-9,-8,-11|-8,-11,-6,-12|-6,-12,-4,-12|-4,-12,-2,-11|-2,-11,1,-10|1,-10,4,-10|4,-10,7,-11|7,-11,9,-12|5,2,3,3|3,3,2,5|2,5,2,7|2,7,4,9|4,9,6,9|6,9,8,8|8,8,9,6|9,6,9,4|9,4,7,2|7,2,5,2
8 | '&':-13,13|10,-3,10,-4|10,-4,9,-5|9,-5,8,-5|8,-5,7,-4|7,-4,6,-2|6,-2,4,3|4,3,2,6|2,6,0,8|0,8,-2,9|-2,9,-6,9|-6,9,-8,8|-8,8,-9,7|-9,7,-10,5|-10,5,-10,3|-10,3,-9,1|-9,1,-8,0|-8,0,-1,-4|-1,-4,0,-5|0,-5,1,-7|1,-7,1,-9|1,-9,0,-11|0,-11,-2,-12|-2,-12,-4,-11|-4,-11,-5,-9|-5,-9,-5,-7|-5,-7,-4,-4|-4,-4,-2,-1|-2,-1,3,6|3,6,5,8|5,8,7,9|7,9,9,9|9,9,10,8|10,8,10,7
9 | ''':-5,5|0,-10,-1,-11|-1,-11,0,-12|0,-12,1,-11|1,-11,1,-9|1,-9,0,-7|0,-7,-1,-6
10 | '(':-7,7|4,-16,2,-14|2,-14,0,-11|0,-11,-2,-7|-2,-7,-3,-2|-3,-2,-3,2|-3,2,-2,7|-2,7,0,11|0,11,2,14|2,14,4,16
11 | ')':-7,7|-4,-16,-2,-14|-2,-14,0,-11|0,-11,2,-7|2,-7,3,-2|3,-2,3,2|3,2,2,7|2,7,0,11|0,11,-2,14|-2,14,-4,16
12 | '*':-8,8|0,-12,0,0|-5,-9,5,-3|5,-9,-5,-3
13 | '+':-13,13|0,-9,0,9|-9,0,9,0
14 | ',':-5,5|1,8,0,9|0,9,-1,8|-1,8,0,7|0,7,1,8|1,8,1,10|1,10,0,12|0,12,-1,13
15 | '-':-13,13|-9,0,9,0
16 | '.':-5,5|0,7,-1,8|-1,8,0,9|0,9,1,8|1,8,0,7
17 | '/':-11,11|9,-16,-9,16
18 | '0':-10,10|-1,-12,-4,-11|-4,-11,-6,-8|-6,-8,-7,-3|-7,-3,-7,0|-7,0,-6,5|-6,5,-4,8|-4,8,-1,9|-1,9,1,9|1,9,4,8|4,8,6,5|6,5,7,0|7,0,7,-3|7,-3,6,-8|6,-8,4,-11|4,-11,1,-12|1,-12,-1,-12
19 | '1':-10,10|-4,-8,-2,-9|-2,-9,1,-12|1,-12,1,9
20 | '2':-10,10|-6,-7,-6,-8|-6,-8,-5,-10|-5,-10,-4,-11|-4,-11,-2,-12|-2,-12,2,-12|2,-12,4,-11|4,-11,5,-10|5,-10,6,-8|6,-8,6,-6|6,-6,5,-4|5,-4,3,-1|3,-1,-7,9|-7,9,7,9
21 | '3':-10,10|-5,-12,6,-12|6,-12,0,-4|0,-4,3,-4|3,-4,5,-3|5,-3,6,-2|6,-2,7,1|7,1,7,3|7,3,6,6|6,6,4,8|4,8,1,9|1,9,-2,9|-2,9,-5,8|-5,8,-6,7|-6,7,-7,5
22 | '4':-10,10|3,-12,-7,2|-7,2,8,2|3,-12,3,9
23 | '5':-10,10|5,-12,-5,-12|-5,-12,-6,-3|-6,-3,-5,-4|-5,-4,-2,-5|-2,-5,1,-5|1,-5,4,-4|4,-4,6,-2|6,-2,7,1|7,1,7,3|7,3,6,6|6,6,4,8|4,8,1,9|1,9,-2,9|-2,9,-5,8|-5,8,-6,7|-6,7,-7,5
24 | '6':-10,10|6,-9,5,-11|5,-11,2,-12|2,-12,0,-12|0,-12,-3,-11|-3,-11,-5,-8|-5,-8,-6,-3|-6,-3,-6,2|-6,2,-5,6|-5,6,-3,8|-3,8,0,9|0,9,1,9|1,9,4,8|4,8,6,6|6,6,7,3|7,3,7,2|7,2,6,-1|6,-1,4,-3|4,-3,1,-4|1,-4,0,-4|0,-4,-3,-3|-3,-3,-5,-1|-5,-1,-6,2
25 | '7':-10,10|7,-12,-3,9|-7,-12,7,-12
26 | '8':-10,10|-2,-12,-5,-11|-5,-11,-6,-9|-6,-9,-6,-7|-6,-7,-5,-5|-5,-5,-3,-4|-3,-4,1,-3|1,-3,4,-2|4,-2,6,0|6,0,7,2|7,2,7,5|7,5,6,7|6,7,5,8|5,8,2,9|2,9,-2,9|-2,9,-5,8|-5,8,-6,7|-6,7,-7,5|-7,5,-7,2|-7,2,-6,0|-6,0,-4,-2|-4,-2,-1,-3|-1,-3,3,-4|3,-4,5,-5|5,-5,6,-7|6,-7,6,-9|6,-9,5,-11|5,-11,2,-12|2,-12,-2,-12
27 | '9':-10,10|6,-5,5,-2|5,-2,3,0|3,0,0,1|0,1,-1,1|-1,1,-4,0|-4,0,-6,-2|-6,-2,-7,-5|-7,-5,-7,-6|-7,-6,-6,-9|-6,-9,-4,-11|-4,-11,-1,-12|-1,-12,0,-12|0,-12,3,-11|3,-11,5,-9|5,-9,6,-5|6,-5,6,0|6,0,5,5|5,5,3,8|3,8,0,9|0,9,-2,9|-2,9,-5,8|-5,8,-6,6
28 | ':':-5,5|0,-5,-1,-4|-1,-4,0,-3|0,-3,1,-4|1,-4,0,-5|0,7,-1,8|-1,8,0,9|0,9,1,8|1,8,0,7
29 | ';':-5,5|0,-5,-1,-4|-1,-4,0,-3|0,-3,1,-4|1,-4,0,-5|1,8,0,9|0,9,-1,8|-1,8,0,7|0,7,1,8|1,8,1,10|1,10,0,12|0,12,-1,13
30 | '<':-12,12|8,-9,-8,0|-8,0,8,9
31 | '=':-13,13|-9,-3,9,-3|-9,3,9,3
32 | '>':-12,12|-8,-9,8,0|8,0,-8,9
33 | '?':-9,9|-6,-7,-6,-8|-6,-8,-5,-10|-5,-10,-4,-11|-4,-11,-2,-12|-2,-12,2,-12|2,-12,4,-11|4,-11,5,-10|5,-10,6,-8|6,-8,6,-6|6,-6,5,-4|5,-4,4,-3|4,-3,0,-1|0,-1,0,2|0,7,-1,8|-1,8,0,9|0,9,1,8|1,8,0,7
34 | '@':-13,14|5,-4,4,-6|4,-6,2,-7|2,-7,-1,-7|-1,-7,-3,-6|-3,-6,-4,-5|-4,-5,-5,-2|-5,-2,-5,1|-5,1,-4,3|-4,3,-2,4|-2,4,1,4|1,4,3,3|3,3,4,1|-1,-7,-3,-5|-3,-5,-4,-2|-4,-2,-4,1|-4,1,-3,3|-3,3,-2,4|5,-7,4,1|4,1,4,3|4,3,6,4|6,4,8,4|8,4,10,2|10,2,11,-1|11,-1,11,-3|11,-3,10,-6|10,-6,9,-8|9,-8,7,-10|7,-10,5,-11|5,-11,2,-12|2,-12,-1,-12|-1,-12,-4,-11|-4,-11,-6,-10|-6,-10,-8,-8|-8,-8,-9,-6|-9,-6,-10,-3|-10,-3,-10,0|-10,0,-9,3|-9,3,-8,5|-8,5,-6,7|-6,7,-4,8|-4,8,-1,9|-1,9,2,9|2,9,5,8|5,8,7,7|7,7,8,6|6,-7,5,1|5,1,5,3|5,3,6,4
35 | 'A':-9,9|0,-12,-8,9|0,-12,8,9|-5,2,5,2
36 | 'B':-11,10|-7,-12,-7,9|-7,-12,2,-12|2,-12,5,-11|5,-11,6,-10|6,-10,7,-8|7,-8,7,-6|7,-6,6,-4|6,-4,5,-3|5,-3,2,-2|-7,-2,2,-2|2,-2,5,-1|5,-1,6,0|6,0,7,2|7,2,7,5|7,5,6,7|6,7,5,8|5,8,2,9|2,9,-7,9
37 | 'C':-10,11|8,-7,7,-9|7,-9,5,-11|5,-11,3,-12|3,-12,-1,-12|-1,-12,-3,-11|-3,-11,-5,-9|-5,-9,-6,-7|-6,-7,-7,-4|-7,-4,-7,1|-7,1,-6,4|-6,4,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,3,9|3,9,5,8|5,8,7,6|7,6,8,4
38 | 'D':-11,10|-7,-12,-7,9|-7,-12,0,-12|0,-12,3,-11|3,-11,5,-9|5,-9,6,-7|6,-7,7,-4|7,-4,7,1|7,1,6,4|6,4,5,6|5,6,3,8|3,8,0,9|0,9,-7,9
39 | 'E':-10,9|-6,-12,-6,9|-6,-12,7,-12|-6,-2,2,-2|-6,9,7,9
40 | 'F':-10,8|-6,-12,-6,9|-6,-12,7,-12|-6,-2,2,-2
41 | 'G':-10,11|8,-7,7,-9|7,-9,5,-11|5,-11,3,-12|3,-12,-1,-12|-1,-12,-3,-11|-3,-11,-5,-9|-5,-9,-6,-7|-6,-7,-7,-4|-7,-4,-7,1|-7,1,-6,4|-6,4,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,3,9|3,9,5,8|5,8,7,6|7,6,8,4|8,4,8,1|3,1,8,1
42 | 'H':-11,11|-7,-12,-7,9|7,-12,7,9|-7,-2,7,-2
43 | 'I':-4,4|0,-12,0,9
44 | 'J':-8,8|4,-12,4,4|4,4,3,7|3,7,2,8|2,8,0,9|0,9,-2,9|-2,9,-4,8|-4,8,-5,7|-5,7,-6,4|-6,4,-6,2
45 | 'K':-11,10|-7,-12,-7,9|7,-12,-7,2|-2,-3,7,9
46 | 'L':-10,7|-6,-12,-6,9|-6,9,6,9
47 | 'M':-12,12|-8,-12,-8,9|-8,-12,0,9|8,-12,0,9|8,-12,8,9
48 | 'N':-11,11|-7,-12,-7,9|-7,-12,7,9|7,-12,7,9
49 | 'O':-11,11|-2,-12,-4,-11|-4,-11,-6,-9|-6,-9,-7,-7|-7,-7,-8,-4|-8,-4,-8,1|-8,1,-7,4|-7,4,-6,6|-6,6,-4,8|-4,8,-2,9|-2,9,2,9|2,9,4,8|4,8,6,6|6,6,7,4|7,4,8,1|8,1,8,-4|8,-4,7,-7|7,-7,6,-9|6,-9,4,-11|4,-11,2,-12|2,-12,-2,-12
50 | 'P':-11,10|-7,-12,-7,9|-7,-12,2,-12|2,-12,5,-11|5,-11,6,-10|6,-10,7,-8|7,-8,7,-5|7,-5,6,-3|6,-3,5,-2|5,-2,2,-1|2,-1,-7,-1
51 | 'Q':-11,11|-2,-12,-4,-11|-4,-11,-6,-9|-6,-9,-7,-7|-7,-7,-8,-4|-8,-4,-8,1|-8,1,-7,4|-7,4,-6,6|-6,6,-4,8|-4,8,-2,9|-2,9,2,9|2,9,4,8|4,8,6,6|6,6,7,4|7,4,8,1|8,1,8,-4|8,-4,7,-7|7,-7,6,-9|6,-9,4,-11|4,-11,2,-12|2,-12,-2,-12|1,5,7,11
52 | 'R':-11,10|-7,-12,-7,9|-7,-12,2,-12|2,-12,5,-11|5,-11,6,-10|6,-10,7,-8|7,-8,7,-6|7,-6,6,-4|6,-4,5,-3|5,-3,2,-2|2,-2,-7,-2|0,-2,7,9
53 | 'S':-10,10|7,-9,5,-11|5,-11,2,-12|2,-12,-2,-12|-2,-12,-5,-11|-5,-11,-7,-9|-7,-9,-7,-7|-7,-7,-6,-5|-6,-5,-5,-4|-5,-4,-3,-3|-3,-3,3,-1|3,-1,5,0|5,0,6,1|6,1,7,3|7,3,7,6|7,6,5,8|5,8,2,9|2,9,-2,9|-2,9,-5,8|-5,8,-7,6
54 | 'T':-8,8|0,-12,0,9|-7,-12,7,-12
55 | 'U':-11,11|-7,-12,-7,3|-7,3,-6,6|-6,6,-4,8|-4,8,-1,9|-1,9,1,9|1,9,4,8|4,8,6,6|6,6,7,3|7,3,7,-12
56 | 'V':-9,9|-8,-12,0,9|8,-12,0,9
57 | 'W':-12,12|-10,-12,-5,9|0,-12,-5,9|0,-12,5,9|10,-12,5,9
58 | 'X':-10,10|-7,-12,7,9|7,-12,-7,9
59 | 'Y':-9,9|-8,-12,0,-2|0,-2,0,9|8,-12,0,-2
60 | 'Z':-10,10|7,-12,-7,9|-7,-12,7,-12|-7,9,7,9
61 | '[':-7,7|-3,-16,-3,16|-2,-16,-2,16|-3,-16,4,-16|-3,16,4,16
62 | '\':-7,7|-7,-12,7,12
63 | ']':-7,7|2,-16,2,16|3,-16,3,16|-4,-16,3,-16|-4,16,3,16
64 | '^':-8,8|-2,-6,0,-9|0,-9,2,-6|-5,-3,0,-8|0,-8,5,-3|0,-8,0,9
65 | '_':-8,8|-8,11,8,11
66 | '`':-5,5|1,-12,0,-11|0,-11,-1,-9|-1,-9,-1,-7|-1,-7,0,-6|0,-6,1,-7|1,-7,0,-8
67 | 'a':-9,10|6,-5,6,9|6,-2,4,-4|4,-4,2,-5|2,-5,-1,-5|-1,-5,-3,-4|-3,-4,-5,-2|-5,-2,-6,1|-6,1,-6,3|-6,3,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,2,9|2,9,4,8|4,8,6,6
68 | 'b':-10,9|-6,-12,-6,9|-6,-2,-4,-4|-4,-4,-2,-5|-2,-5,1,-5|1,-5,3,-4|3,-4,5,-2|5,-2,6,1|6,1,6,3|6,3,5,6|5,6,3,8|3,8,1,9|1,9,-2,9|-2,9,-4,8|-4,8,-6,6
69 | 'c':-9,9|6,-2,4,-4|4,-4,2,-5|2,-5,-1,-5|-1,-5,-3,-4|-3,-4,-5,-2|-5,-2,-6,1|-6,1,-6,3|-6,3,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,2,9|2,9,4,8|4,8,6,6
70 | 'd':-9,10|6,-12,6,9|6,-2,4,-4|4,-4,2,-5|2,-5,-1,-5|-1,-5,-3,-4|-3,-4,-5,-2|-5,-2,-6,1|-6,1,-6,3|-6,3,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,2,9|2,9,4,8|4,8,6,6
71 | 'e':-9,9|-6,1,6,1|6,1,6,-1|6,-1,5,-3|5,-3,4,-4|4,-4,2,-5|2,-5,-1,-5|-1,-5,-3,-4|-3,-4,-5,-2|-5,-2,-6,1|-6,1,-6,3|-6,3,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,2,9|2,9,4,8|4,8,6,6
72 | 'f':-5,7|5,-12,3,-12|3,-12,1,-11|1,-11,0,-8|0,-8,0,9|-3,-5,4,-5
73 | 'g':-9,10|6,-5,6,11|6,11,5,14|5,14,4,15|4,15,2,16|2,16,-1,16|-1,16,-3,15|6,-2,4,-4|4,-4,2,-5|2,-5,-1,-5|-1,-5,-3,-4|-3,-4,-5,-2|-5,-2,-6,1|-6,1,-6,3|-6,3,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,2,9|2,9,4,8|4,8,6,6
74 | 'h':-9,10|-5,-12,-5,9|-5,-1,-2,-4|-2,-4,0,-5|0,-5,3,-5|3,-5,5,-4|5,-4,6,-1|6,-1,6,9
75 | 'i':-4,4|-1,-12,0,-11|0,-11,1,-12|1,-12,0,-13|0,-13,-1,-12|0,-5,0,9
76 | 'j':-5,5|0,-12,1,-11|1,-11,2,-12|2,-12,1,-13|1,-13,0,-12|1,-5,1,12|1,12,0,15|0,15,-2,16|-2,16,-4,16
77 | 'k':-9,8|-5,-12,-5,9|5,-5,-5,5|-1,1,6,9
78 | 'l':-4,4|0,-12,0,9
79 | 'm':-15,15|-11,-5,-11,9|-11,-1,-8,-4|-8,-4,-6,-5|-6,-5,-3,-5|-3,-5,-1,-4|-1,-4,0,-1|0,-1,0,9|0,-1,3,-4|3,-4,5,-5|5,-5,8,-5|8,-5,10,-4|10,-4,11,-1|11,-1,11,9
80 | 'n':-9,10|-5,-5,-5,9|-5,-1,-2,-4|-2,-4,0,-5|0,-5,3,-5|3,-5,5,-4|5,-4,6,-1|6,-1,6,9
81 | 'o':-9,10|-1,-5,-3,-4|-3,-4,-5,-2|-5,-2,-6,1|-6,1,-6,3|-6,3,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,2,9|2,9,4,8|4,8,6,6|6,6,7,3|7,3,7,1|7,1,6,-2|6,-2,4,-4|4,-4,2,-5|2,-5,-1,-5
82 | 'p':-10,9|-6,-5,-6,16|-6,-2,-4,-4|-4,-4,-2,-5|-2,-5,1,-5|1,-5,3,-4|3,-4,5,-2|5,-2,6,1|6,1,6,3|6,3,5,6|5,6,3,8|3,8,1,9|1,9,-2,9|-2,9,-4,8|-4,8,-6,6
83 | 'q':-9,10|6,-5,6,16|6,-2,4,-4|4,-4,2,-5|2,-5,-1,-5|-1,-5,-3,-4|-3,-4,-5,-2|-5,-2,-6,1|-6,1,-6,3|-6,3,-5,6|-5,6,-3,8|-3,8,-1,9|-1,9,2,9|2,9,4,8|4,8,6,6
84 | 'r':-7,6|-3,-5,-3,9|-3,1,-2,-2|-2,-2,0,-4|0,-4,2,-5|2,-5,5,-5
85 | 's':-8,9|6,-2,5,-4|5,-4,2,-5|2,-5,-1,-5|-1,-5,-4,-4|-4,-4,-5,-2|-5,-2,-4,0|-4,0,-2,1|-2,1,3,2|3,2,5,3|5,3,6,5|6,5,6,6|6,6,5,8|5,8,2,9|2,9,-1,9|-1,9,-4,8|-4,8,-5,6
86 | 't':-5,7|0,-12,0,5|0,5,1,8|1,8,3,9|3,9,5,9|-3,-5,4,-5
87 | 'u':-9,10|-5,-5,-5,5|-5,5,-4,8|-4,8,-2,9|-2,9,1,9|1,9,3,8|3,8,6,5|6,-5,6,9
88 | 'v':-8,8|-6,-5,0,9|6,-5,0,9
89 | 'w':-11,11|-8,-5,-4,9|0,-5,-4,9|0,-5,4,9|8,-5,4,9
90 | 'x':-8,9|-5,-5,6,9|6,-5,-5,9
91 | 'y':-8,8|-6,-5,0,9|6,-5,0,9|0,9,-2,13|-2,13,-4,15|-4,15,-6,16|-6,16,-7,16
92 | 'z':-8,9|6,-5,-5,9|-5,-5,6,-5|-5,9,6,9
93 | '{':-7,7|2,-16,0,-15|0,-15,-1,-14|-1,-14,-2,-12|-2,-12,-2,-10|-2,-10,-1,-8|-1,-8,0,-7|0,-7,1,-5|1,-5,1,-3|1,-3,-1,-1|0,-15,-1,-13|-1,-13,-1,-11|-1,-11,0,-9|0,-9,1,-8|1,-8,2,-6|2,-6,2,-4|2,-4,1,-2|1,-2,-3,0|-3,0,1,2|1,2,2,4|2,4,2,6|2,6,1,8|1,8,0,9|0,9,-1,11|-1,11,-1,13|-1,13,0,15|-1,1,1,3|1,3,1,5|1,5,0,7|0,7,-1,8|-1,8,-2,10|-2,10,-2,12|-2,12,-1,14|-1,14,0,15|0,15,2,16
94 | '|':-4,4|0,-16,0,16
95 | '}':-7,7|-2,-16,0,-15|0,-15,1,-14|1,-14,2,-12|2,-12,2,-10|2,-10,1,-8|1,-8,0,-7|0,-7,-1,-5|-1,-5,-1,-3|-1,-3,1,-1|0,-15,1,-13|1,-13,1,-11|1,-11,0,-9|0,-9,-1,-8|-1,-8,-2,-6|-2,-6,-2,-4|-2,-4,-1,-2|-1,-2,3,0|3,0,-1,2|-1,2,-2,4|-2,4,-2,6|-2,6,-1,8|-1,8,0,9|0,9,1,11|1,11,1,13|1,13,0,15|1,1,-1,3|-1,3,-1,5|-1,5,0,7|0,7,1,8|1,8,2,10|2,10,2,12|2,12,1,14|1,14,0,15|0,15,-2,16
96 | '~':-12,12|-9,3,-9,1|-9,1,-8,-2|-8,-2,-6,-3|-6,-3,-4,-3|-4,-3,-2,-2|-2,-2,2,1|2,1,4,2|4,2,6,2|6,2,8,1|8,1,9,-1|-9,1,-8,-1|-8,-1,-6,-2|-6,-2,-4,-2|-4,-2,-2,-1|-2,-1,2,2|2,2,4,3|4,3,6,3|6,3,8,2|8,2,9,-1|9,-1,9,-3
97 | '':-7,7|-1,-12,-3,-11|-3,-11,-4,-9|-4,-9,-4,-7|-4,-7,-3,-5|-3,-5,-1,-4|-1,-4,1,-4|1,-4,3,-5|3,-5,4,-7|4,-7,4,-9|4,-9,3,-11|3,-11,1,-12|1,-12,-1,-12
98 |
99 |
--------------------------------------------------------------------------------
/src/ZingLaser.java:
--------------------------------------------------------------------------------
1 | import com.t_oster.liblasercut.*;
2 | import com.t_oster.liblasercut.drivers.EpilogZing;
3 | import com.t_oster.liblasercut.utils.BufferedImageAdapter;
4 |
5 | import javax.swing.*;
6 | import javax.swing.text.DefaultCaret;
7 | import java.awt.*;
8 | import java.awt.event.ItemEvent;
9 | import java.awt.geom.AffineTransform;
10 | import java.awt.geom.Line2D;
11 | import java.awt.geom.Point2D;
12 | import java.awt.geom.Rectangle2D;
13 | import java.awt.image.BufferedImage;
14 | import java.util.*;
15 | import java.util.List;
16 | import java.util.prefs.Preferences;
17 |
18 | class ZingLaser implements LaserCut.OutputDevice {
19 | private static final double ZING_PPI = 500;
20 | private static final int ZING_SPEED_DEFAUlT = 55;
21 | private static final int ZING_FREQ_DEFAUlT = 500;
22 | private static final int ZING_CUT_POWER_DEFAUlT = 85;
23 | private static final int ZING_ENGRAVE_POWER_DEFAUlT = 5;
24 | private static final int ZING_RASTER_POWER_DEFAUlT = 50;
25 | private static final Rectangle2D.Double zingFullSize = new Rectangle2D.Double(0, 0, 16, 12);
26 | private static final Rectangle2D.Double zing12x12Size = new Rectangle2D.Double(0, 0, 12, 12);
27 | private final LaserCut laserCut;
28 | private final Preferences prefs;
29 |
30 | ZingLaser (LaserCut laserCut, Preferences prefs) {
31 | this.laserCut = laserCut;
32 | this.prefs = prefs;
33 | }
34 |
35 | // Implemented for LaserCut.OutputDevice
36 | public String getName () {
37 | return "Epilog Zing";
38 | }
39 |
40 | // Implemented for LaserCut.OutputDevice
41 | public Rectangle2D.Double getWorkspaceSize () {
42 | return zingFullSize;
43 | }
44 |
45 | // Implemented for LaserCut.OutputDevice
46 | public void closeDevice () {
47 | // Nothing to do
48 | }
49 |
50 | // Implemented for LaserCut.OutputDevice
51 | public double getZoomFactor () {
52 | return 1.0;
53 | }
54 |
55 | public JMenu getDeviceMenu () {
56 | JMenu zingMenu = new JMenu(getName());
57 | // Add "Send to Zing" Submenu Item
58 | JMenuItem sendToZing = new JMenuItem("Send Job to " + getName());
59 | sendToZing.addActionListener(ev -> {
60 | String zingIpAddress = prefs.get("zing.ip", "10.0.1.201");
61 | if (zingIpAddress == null || zingIpAddress.length() == 0) {
62 | laserCut.showErrorDialog("Please set the " + getName() + "'s IP Address in " + getName() + "->Zing Settings");
63 | return;
64 | }
65 | if (laserCut.showWarningDialog("Press OK to Send Job to " + getName())) {
66 | EpilogZing lasercutter = new EpilogZing(zingIpAddress);
67 | // Set Properties for Materials, such as for 3 mm birch plywood, Set: 60% speed, 80% power, 0 focus, 500 Hz.
68 | PowerSpeedFocusFrequencyProperty cutProperties = new PowerSpeedFocusFrequencyProperty();
69 | cutProperties.setProperty("speed", prefs.getInt("zing.speed", ZING_SPEED_DEFAUlT));
70 | cutProperties.setProperty("power", prefs.getInt("zing.power", ZING_CUT_POWER_DEFAUlT));
71 | cutProperties.setProperty("frequency", prefs.getInt("zing.freq", ZING_FREQ_DEFAUlT));
72 | cutProperties.setProperty("focus", 0.0f);
73 | PowerSpeedFocusFrequencyProperty engraveProperties = new PowerSpeedFocusFrequencyProperty();
74 | engraveProperties.setProperty("speed", prefs.getInt("zing.espeed", ZING_SPEED_DEFAUlT));
75 | engraveProperties.setProperty("power", prefs.getInt("zing.epower", ZING_ENGRAVE_POWER_DEFAUlT));
76 | engraveProperties.setProperty("frequency", prefs.getInt("zing.efreq", ZING_FREQ_DEFAUlT));
77 | engraveProperties.setProperty("focus", 0.0f);
78 | PowerSpeedFocusFrequencyProperty rasterProperties = new PowerSpeedFocusFrequencyProperty();
79 | rasterProperties.setProperty("speed", prefs.getInt("zing.rspeed", ZING_SPEED_DEFAUlT));
80 | rasterProperties.setProperty("power", prefs.getInt("zing.rpower", ZING_RASTER_POWER_DEFAUlT));
81 | rasterProperties.setProperty("frequency", ZING_FREQ_DEFAUlT);
82 | rasterProperties.setProperty("focus", 0.0f);
83 | boolean planPath = prefs.getBoolean("zing.pathplan", true);
84 | LaserJob job = new LaserJob("laserCut", "laserCut", "laserCut"); // title, name, user
85 | // Process raster engrave passes, if any
86 | for (CADShape shape : laserCut.surface.getDesign()) {
87 | if (shape instanceof CADRasterImage && shape.engrave) {
88 | CADRasterImage raster = (CADRasterImage) shape;
89 | double[] scale = raster.getScale(ZING_PPI);
90 | Rectangle2D bb = raster.getScaledRotatedBounds(scale);
91 | AffineTransform at = raster.getScaledRotatedTransform(bb, scale);
92 | BufferedImage scaledImg = raster.getScaledRotatedImage(bb, scale);
93 | Point2D.Double offset = raster.getScaledRotatedOrigin(at, bb);
94 | int xLoc = (int) Math.round(shape.xLoc * ZING_PPI - offset.x);
95 | int yLoc = (int) Math.round(shape.yLoc * ZING_PPI - offset.y);
96 | com.t_oster.liblasercut.platform.Point loc = new com.t_oster.liblasercut.platform.Point(xLoc, yLoc);
97 | if (raster.engrave3D) {
98 | Raster3dPart rp = new Raster3dPart(new BufferedImageAdapter(scaledImg),
99 | rasterProperties, new com.t_oster.liblasercut.platform.Point(xLoc, yLoc), ZING_PPI);
100 | job.addPart(rp);
101 | } else {
102 | RasterPart rp = new RasterPart(new BlackWhiteRaster(new BufferedImageAdapter(scaledImg),
103 | BlackWhiteRaster.DitherAlgorithm.AVERAGE), new PowerSpeedFocusProperty(), loc, ZING_PPI);
104 | job.addPart(rp);
105 | }
106 | }
107 | }
108 | // Process cut and vector engrave passes
109 | for (int ii = 0; ii < 2; ii++) {
110 | boolean doCut = ii == 1;
111 | // Transform all the shapesInGroup into a series of line segments
112 | int lastX = 0, lastY = 0;
113 | VectorPart vp = new VectorPart(doCut ? cutProperties : engraveProperties, ZING_PPI);
114 | // Loop detects pen up/pen down based on start and end points of line segments
115 | boolean hasVector = false;
116 | List shapes = laserCut.surface.selectLaserItems(doCut, planPath);
117 | for (CADShape shape : shapes) {
118 | for (Line2D.Double[] lines : shape.getListOfScaledLines(ZING_PPI, .001)) {
119 | if (lines.length > 0) {
120 | hasVector = true;
121 | boolean first = true;
122 | for (Line2D.Double line : lines) {
123 | Point p1 = new Point((int) Math.round(line.x1), (int) Math.round(line.y1));
124 | Point p2 = new Point((int) Math.round(line.x2), (int) Math.round(line.y2));
125 | if (first) {
126 | vp.moveto(p1.x, p1.y);
127 | vp.lineto(lastX = p2.x, lastY = p2.y);
128 | } else {
129 | if (lastX != p1.x || lastY != p1.y) {
130 | vp.moveto(p1.x, p1.y);
131 | }
132 | vp.lineto(lastX = p2.x, lastY = p2.y);
133 | }
134 | first = false;
135 | }
136 | }
137 | }
138 | if (hasVector) {
139 | job.addPart(vp);
140 | }
141 | }
142 | }
143 | new ZingSender(laserCut, lasercutter, job);
144 | }
145 | });
146 | zingMenu.add(sendToZing);
147 |
148 | // Build JComboBox List of Materials for "Zing Settings" parameters dialog
149 | String[] materials = Utils2D.getResourceFile("/materials/zing.materials").split("===");
150 | JComboBox