/*
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.generation;
import org.cirqwizard.generation.toolpath.LinearToolpath;
import org.cirqwizard.generation.toolpath.Toolpath;
import org.cirqwizard.geom.Line;
import org.cirqwizard.geom.Point;
import org.cirqwizard.gerber.Flash;
import org.cirqwizard.gerber.GerberPrimitive;
import org.cirqwizard.gerber.LinearShape;
import org.cirqwizard.gerber.Region;
import org.cirqwizard.gerber.appertures.OvalAperture;
import org.cirqwizard.gerber.appertures.RectangularAperture;
import org.cirqwizard.logging.LoggerFactory;
import org.cirqwizard.math.MathUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
public class DispensingToolpathGenerator
{
private List<GerberPrimitive> elements;
public DispensingToolpathGenerator(List<GerberPrimitive> primitives)
{
this.elements = primitives;
}
public List<Toolpath> generate(int needleDiameter)
{
List<Toolpath> toolpaths = new ArrayList<>();
for (GerberPrimitive element : elements)
{
if (element instanceof Flash)
{
Flash flash = (Flash)element;
if (flash.getAperture() instanceof RectangularAperture)
{
RectangularAperture aperture = (RectangularAperture)flash.getAperture();
int[] dimensions = aperture.getDimensions();
boolean vertical = dimensions[1] > dimensions[0];
int passes = Math.max(1, dimensions[vertical ? 0 : 1] / (needleDiameter * 2));
for (int i = 0; i < passes; i++)
{
LinearToolpath toolpath;
if (vertical)
{
int x = (int)(flash.getX() - dimensions[0] / 2 + (double)dimensions[0] / (passes + 1) * (i + 1));
int y = flash.getY() - dimensions[1] / 2 + needleDiameter;
toolpath = new LinearToolpath(needleDiameter, new Point(x, y), new Point(x, flash.getY() + (dimensions[1] / 2) - needleDiameter));
}
else
{
int x = flash.getX() - dimensions[0] / 2 + needleDiameter;
int y = (int)(flash.getY() - dimensions[1] / 2 + (double)dimensions[1] / (passes + 1) * (i + 1));
toolpath = new LinearToolpath(needleDiameter, new Point(x, y), new Point(flash.getX() + (dimensions[0] / 2) - needleDiameter, y));
}
toolpaths.add(toolpath);
}
}
else if (flash.getAperture() instanceof OvalAperture)
{
OvalAperture aperture = (OvalAperture) flash.getAperture();
Point from, to;
int width;
if (aperture.isHorizontal())
{
from = new Point(flash.getX() - aperture.getWidth() / 2 + (aperture.getHeight() / 2 - needleDiameter / 2), flash.getY() + aperture.getHeight() / 2);
to = new Point(flash.getX() + aperture.getWidth() / 2 - (aperture.getHeight() / 2 - needleDiameter / 2), flash.getY() + aperture.getHeight() / 2);
width = aperture.getHeight();
}
else
{
from = new Point(flash.getX() - aperture.getWidth() / 2, flash.getY() - aperture.getHeight() / 2 + (aperture.getWidth() / 2 - needleDiameter / 2));
to = new Point(flash.getX() - aperture.getWidth() / 2, flash.getY() + aperture.getHeight() / 2 - (aperture.getWidth() / 2 - needleDiameter / 2));
width = aperture.getWidth();
}
fillRectangle(toolpaths, from, to, width, needleDiameter);
}
else
System.out.println("Circular apertures not supported at the moment: " + flash.getAperture());
}
else if (element instanceof Region)
{
Region region = (Region) element;
LinearShape longestSide = null;
for (GerberPrimitive p : region.getSegments())
{
if (p instanceof LinearShape)
{
if (longestSide == null ||
longestSide.getFrom().distanceTo(longestSide.getTo()) < ((LinearShape) p).getFrom().distanceTo(((LinearShape) p).getTo()))
longestSide = (LinearShape) p;
}
}
if (longestSide == null)
continue;
double largestWidth = 0;
for (GerberPrimitive p : region.getSegments())
{
if (p instanceof LinearShape)
{
double d = calculatePerpendicular(longestSide.getFrom(), longestSide.getTo(), ((LinearShape) p).getFrom());
if (Math.abs(d) > Math.abs(largestWidth))
largestWidth = d;
d = calculatePerpendicular(longestSide.getFrom(), longestSide.getTo(), ((LinearShape) p).getTo());
if (Math.abs(d) > Math.abs(largestWidth))
largestWidth = d;
}
}
fillRectangle(toolpaths, longestSide.getFrom(), longestSide.getTo(), (int) largestWidth, needleDiameter);
}
else if (element instanceof LinearShape)
{
LinearShape line = (LinearShape) element;
Line l = new Line(line.getFrom(), line.getTo());
double angle = l.angleToX();
if (line.getAperture() instanceof RectangularAperture)
{
Point appertureOffset = new Point((int)(line.getAperture().getWidth() / 2 * Math.cos(angle)),
(int)(line.getAperture().getWidth() / 2 * Math.sin(angle)));
l.setTo(l.getTo().add(appertureOffset));
appertureOffset = new Point(-appertureOffset.getX(), -appertureOffset.getY());
l.setFrom(l.getFrom().add(appertureOffset));
}
angle += Math.PI / 2;
Point offset = new Point((int)(line.getAperture().getWidth() / 2 * Math.cos(angle)),
(int)(line.getAperture().getWidth() / 2 * Math.sin(angle)));
fillRectangle(toolpaths, l.getFrom().add(offset), l.getTo().add(offset), line.getAperture().getWidth(), needleDiameter);
}
else
{
LoggerFactory.getApplicationLogger().log(Level.WARNING, "Unexpected element on solder paste level: " + element);
}
}
return toolpaths;
}
private void fillRectangle(List<Toolpath> toolpaths, Point from, Point to, int width, int needleDiameter)
{
// Shortening tool path for needle radius
double angle = new Line(from, to).angleToX();
from = from.add(new Point((int) (Math.cos(angle) * needleDiameter / 2), (int) (Math.sin(angle) * needleDiameter / 2)));
to = to.subtract(new Point((int) (Math.cos(angle) * needleDiameter / 2), (int) (Math.sin(angle) * needleDiameter / 2)));
angle = MathUtil.bindAngle(angle - Math.PI / 2);
int passes = Math.max(1, Math.abs(width) / (needleDiameter + needleDiameter / 2));
for (int i = 0; i < passes; i++)
{
// Offsetting
int offset = width / (passes + 1) * (i + 1);
Point offsetVector = new Point((int)(Math.cos(angle) * offset), (int)(Math.sin(angle) * offset));
toolpaths.add(new LinearToolpath(needleDiameter, from.add(offsetVector), to.add(offsetVector)));
}
}
private double calculatePerpendicular(Point from, Point to, Point p)
{
double dx = to.getX() - from.getX();
double dy = to.getY() - from.getY();
return (dx * (from.getY() - p.getY()) - dy * (from.getX() - p.getX())) / Math.sqrt(dx * dx + dy * dy);
}
}