/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.cirqwizard.fx; import javafx.beans.property.Property; import javafx.beans.property.SimpleListProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; import javafx.geometry.Point2D; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import org.cirqwizard.generation.toolpath.Toolpath; import org.cirqwizard.gerber.GerberPrimitive; import org.cirqwizard.layers.LayerElement; import java.io.IOException; import java.util.List; public class PCBPane extends javafx.scene.layout.Region { private static final double DEFAULT_SCALE = 0.005; public static final Color BACKGROUND_COLOR = Color.web("#ddfbdd"); public static final Color ENABLED_TOOLPATH_COLOR = Color.web("#191970"); public static final Color PASTE_TOOLPATH_COLOR = Color.GOLD; public static final Color DISABLED_TOOLPATH_COLOR = Color.web("#dcdcdc"); public static final Color SELECTED_TOOLPATH_COLOR = Color.CYAN; public static final Color TOP_TRACE_COLOR = Color.RED; public static final Color BOTTOM_TRACE_COLOR = Color.BLUE; public static final Color DRILL_POINT_COLOR = Color.BLACK; public static final Color CONTOUR_COLOR = Color.MAGENTA; public static final Color SOLDER_PAD_COLOR = Color.NAVY; public static final Color PCB_BORDER = Color.BLACK; private Property<Double> scaleProperty = new SimpleObjectProperty<>(DEFAULT_SCALE); private double boardWidth; private double boardHeight; private java.util.List<? extends LayerElement> gerberPrimitives; private Property<ObservableList<Toolpath>> toolpaths = new SimpleListProperty<>(); private Canvas canvas; private Rectangle selectionRectangle; private Color gerberColor = TOP_TRACE_COLOR; private Color toolpathColor = ENABLED_TOOLPATH_COLOR; private boolean flipHorizontal = false; public PCBPane() { scaleProperty.addListener((v, oldV, newV) -> repaint()); toolpaths.addListener((v, oldV, newV) -> repaint()); } public Property<ObservableList<Toolpath>> toolpathsProperty() { return toolpaths; } public void setGerberPrimitives(List<? extends LayerElement> gerberPrimitives) { this.gerberPrimitives = gerberPrimitives; repaint(); } public void setGerberColor(Color gerberColor) { this.gerberColor = gerberColor; } public void setToolpathColor(Color toolpathColor) { this.toolpathColor = toolpathColor; } public boolean isFlipHorizontal() { return flipHorizontal; } public void setFlipHorizontal(boolean flipHorizontal) { this.flipHorizontal = flipHorizontal; repaint(); } public void repaint() { getChildren().remove(canvas); renderImage(); getChildren().add(canvas); } public void repaint(List<? extends Toolpath> toolpaths) { GraphicsContext g = canvas.getGraphicsContext2D(); toolpaths.forEach(t -> renderToolpath(g, t)); } private void renderImage() { canvas = new Canvas(boardWidth * scaleProperty.getValue() + 1, boardHeight * scaleProperty.getValue() + 1); GraphicsContext g = canvas.getGraphicsContext2D(); g.setFill(BACKGROUND_COLOR); g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); g.setStroke(PCB_BORDER); g.setLineWidth(1); g.strokeRect(0, 0, canvas.getWidth() - 1, canvas.getHeight() - 1); g.scale(scaleProperty.getValue() * (flipHorizontal ? -1 : 1), -scaleProperty.getValue()); g.translate(flipHorizontal ? -boardWidth : 0, -boardHeight); if (gerberPrimitives != null) gerberPrimitives.forEach(p -> renderPrimitive(g, p)); if (toolpaths.getValue() != null) toolpaths.getValue().forEach(t -> renderToolpath(g, t)); } private void renderPrimitive(GraphicsContext g, LayerElement element) { if (!element.isVisible()) return; Color color = gerberColor; if ((element instanceof GerberPrimitive) && ((GerberPrimitive)element).getPolarity() == GerberPrimitive.Polarity.CLEAR) color = BACKGROUND_COLOR; g.setStroke(color); g.setFill(color); element.render(g); } private void renderToolpath(GraphicsContext g, Toolpath toolpath) { Color color = toolpath.isEnabled() ? toolpathColor : DISABLED_TOOLPATH_COLOR; if (toolpath.isSelected()) color = SELECTED_TOOLPATH_COLOR; g.setStroke(color); g.setFill(color); toolpath.render(g); } public void setSelection(Point2D point, double width, double height) { if (selectionRectangle != null) getChildren().remove(selectionRectangle); selectionRectangle = new Rectangle(); selectionRectangle.setStrokeWidth(0.5); selectionRectangle.getStyleClass().add("pcb-selection-rect"); // It seems that in this case transforms get converted to int somewhere down the road. So can't use them here selectionRectangle.setX(point.getX() * scaleProperty().getValue()); selectionRectangle.setY((-point.getY() - height + boardHeight) * scaleProperty().getValue()); selectionRectangle.setWidth(width * scaleProperty().getValue()); selectionRectangle.setHeight(height * scaleProperty().getValue()); getChildren().add(selectionRectangle); } public void clearSelection() { if (selectionRectangle != null) { getChildren().remove(selectionRectangle); selectionRectangle = null; } } public Property<Double> scaleProperty() { return scaleProperty; } public void setBoardWidth(double boardWidth) { this.boardWidth = boardWidth; } public void setBoardHeight(double boardHeight) { this.boardHeight = boardHeight; } }