/*
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.panel;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.FileChooser;
import org.cirqwizard.geom.Point;
import org.cirqwizard.layers.Panel;
import org.cirqwizard.layers.PanelBoard;
import org.cirqwizard.logging.LoggerFactory;
import org.cirqwizard.settings.ApplicationConstants;
import org.cirqwizard.settings.SettingsFactory;
import java.io.File;
import java.io.IOException;
public class PanelValidator
{
private Panel panel;
private VBox errorBox;
private CheckBox ignoreErrorCheckBox;
private Runnable saveAndRefresh;
public PanelValidator(Panel panel, VBox errorBox, CheckBox ignoreErrorCheckbox, Runnable saveAndRefresh)
{
this.panel = panel;
this.errorBox = errorBox;
this.ignoreErrorCheckBox = ignoreErrorCheckbox;
this.saveAndRefresh = saveAndRefresh;
}
public void validateBoards()
{
errorBox.getChildren().clear();
panel.getBoards().stream().forEach(b ->
{
if (!validateFit(panel, b))
errorBox.getChildren().add(createErrorLabel("Board " + trimBoardName(b.getFilename()) +
" does not fit in the panel"));
if (!validatePinClearance(panel, b))
errorBox.getChildren().add(createErrorLabel("Board " + trimBoardName(b.getFilename()) +
" overlaps with registration pins"));
if (!b.getBoard().hasLayers())
{
Text text = new Text("Board " + trimBoardName(b.getFilename()) +
" could not be found. Perhaps the files were moved?");
text.getStyleClass().add("label");
Hyperlink hyperlink = new Hyperlink("Locate files");
hyperlink.setOnAction(event -> locateMissingFiles(b));
TextFlow flow = new TextFlow(text, hyperlink);
flow.getStyleClass().add("error-box");
errorBox.getChildren().add(flow);
}
});
for (int i = 0; i < panel.getBoards().size(); i++)
{
for (int j = i + 1; j < panel.getBoards().size(); j++)
{
PanelBoard b1 = panel.getBoards().get(i);
PanelBoard b2 = panel.getBoards().get(j);
if (!validateBoardsOverlap(b1, b2))
errorBox.getChildren().add(createErrorLabel("Boards " + trimBoardName(b1.getFilename()) + " and " +
trimBoardName(b2.getFilename()) + " overlap"));
}
}
if (!errorBox.getChildren().isEmpty() && errorBox.getChildren().stream().noneMatch(n -> n instanceof TextFlow))
errorBox.getChildren().add(ignoreErrorCheckBox);
errorBox.setVisible(!errorBox.getChildren().isEmpty());
errorBox.setManaged(errorBox.isVisible());
ignoreErrorCheckBox.setSelected(false);
errorBox.getParent().layout();
errorBox.getParent().layout();
errorBox.getParent().layout();
}
private String trimBoardName(String fullName)
{
return fullName.substring(fullName.lastIndexOf(File.separatorChar) + 1, fullName.length());
}
private void locateMissingFiles(PanelBoard board)
{
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All supported files", "*.sol", "*.cmp"));
File file = chooser.showOpenDialog(null);
if (file != null)
{
try
{
board.setFilename(file.getAbsolutePath().substring(0,
file.getAbsolutePath().lastIndexOf('.')));
board.loadBoard();
if (board.getBoard().hasLayers())
{
validateBoards();
saveAndRefresh.run();
}
}
catch (IOException e)
{
LoggerFactory.logException("Could not load board data", e);
}
}
}
private Label createErrorLabel(String message)
{
Label label = new Label(message);
label.setWrapText(true);
label.getStyleClass().add("error-box");
label.setPrefWidth(1000);
label.setMinSize(200, Label.USE_PREF_SIZE);
return label;
}
private boolean validateFit(Panel panel, PanelBoard board)
{
return !(board.getX() < 0 || board.getY() < 0 ||
board.getX() + board.getBoard().getWidth() > panel.getSize().getWidth() ||
board.getY() + board.getBoard().getHeight() > panel.getSize().getHeight());
}
private boolean validatePinClearance(Panel panel, PanelBoard board)
{
for (Point p : panel.getPinLocations())
if (boardContainsPoint(board, p, ApplicationConstants.REGISTRATION_PIN_RADIUS))
return false;
return true;
}
private Point[] getBoardExtremePoints(PanelBoard board)
{
int offset = board.isGenerateOutline() ?
SettingsFactory.getContourMillingSettings().getGenerationToolDiameter().getValue() : 0;
return new Point[]{
new Point(board.getX() - offset, board.getY() - offset),
new Point(board.getX() - offset, board.getY() + board.getBoard().getHeight() + offset),
new Point(board.getX() + board.getBoard().getWidth() + offset,
board.getY() + board.getBoard().getHeight() + offset),
new Point(board.getX() + board.getBoard().getWidth() + offset, board.getY() - offset)
};
}
private boolean boardContainsPoint(PanelBoard board, Point point, int radius)
{
Point[] extremePoints = getBoardExtremePoints(board);
if (point.getX() >= extremePoints[0].getX() && point.getX() <= extremePoints[3].getX() &&
point.getY() >= extremePoints[0].getY() && point.getY() <= extremePoints[1].getY())
return true;
if (lineIntersectsCircle(extremePoints[0], extremePoints[1], point, radius))
return true;
if (lineIntersectsCircle(extremePoints[1], extremePoints[2], point, radius))
return true;
if (lineIntersectsCircle(extremePoints[2], extremePoints[3], point, radius))
return true;
if (lineIntersectsCircle(extremePoints[3], extremePoints[0], point, radius))
return true;
return false;
}
private boolean lineIntersectsCircle(Point lineFrom, Point lineTo, Point circleCenter, int radius)
{
double lineLength = lineFrom.distanceTo(lineTo);
Point directionVector = lineTo.subtract(lineFrom);
double dx = directionVector.getX() / lineLength;
double dy = directionVector.getY() / lineLength;
Point tt = circleCenter.subtract(lineFrom);
double t = dx * tt.getX() + dy * tt.getY();
Point circleCenterProjection = new Point((int)(t * dx * lineFrom.getX()), (int)(t * dy * lineFrom.getY()));
return circleCenterProjection.distanceTo(circleCenter) <= radius;
}
private boolean validateBoardsOverlap(PanelBoard board1, PanelBoard board2)
{
for (Point p : getBoardExtremePoints(board2))
if (boardContainsPoint(board1, p, 0))
return false;
return true;
}
}