/** * This file is part of VisiCut. * Copyright (C) 2011 - 2013 Thomas Oster <thomas.oster@rwth-aachen.de> * RWTH Aachen University - 52062 Aachen, Germany * * VisiCut is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VisiCut 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with VisiCut. If not, see <http://www.gnu.org/licenses/>. **/ package com.t_oster.visicut.model; import com.t_oster.liblasercut.LaserJob; import com.t_oster.liblasercut.LaserProperty; import com.t_oster.liblasercut.VectorPart; import com.t_oster.liblasercut.platform.Util; import com.t_oster.liblasercut.utils.ShapeConverter; import com.t_oster.liblasercut.vectoroptimizers.VectorOptimizer; import com.t_oster.liblasercut.vectoroptimizers.VectorOptimizer.OrderStrategy; import com.t_oster.visicut.model.graphicelements.GraphicObject; import com.t_oster.visicut.model.graphicelements.GraphicSet; import com.t_oster.visicut.model.graphicelements.ShapeDecorator; import com.t_oster.visicut.model.graphicelements.ShapeObject; import com.t_oster.visicut.model.graphicelements.lssupport.LaserScriptShape; import com.t_oster.visicut.model.graphicelements.lssupport.ScriptInterfaceLogUi; import com.t_oster.liblasercut.laserscript.ScriptInterpreter; import com.t_oster.liblasercut.laserscript.VectorPartScriptInterface; import com.t_oster.visicut.managers.PreferencesManager; import java.awt.BasicStroke; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.io.FileReader; import java.io.IOException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.script.ScriptException; /** * This class represents a Line Profile, * which means a kind of line which can be * cut with the lasercutten in a specified * Material * * @author Thomas Oster <thomas.oster@rwth-aachen.de> */ public class VectorProfile extends LaserProfile { public VectorProfile() { this.setName("cut"); } protected OrderStrategy orderStrategy = OrderStrategy.INNER_FIRST; /** * Get the value of orderStrategy * * @return the value of orderStrategy */ public OrderStrategy getOrderStrategy() { if (orderStrategy == null) { orderStrategy = OrderStrategy.INNER_FIRST; } return orderStrategy; } /** * Set the value of orderStrategy * * @param orderStrategy new value of orderStrategy */ public void setOrderStrategy(OrderStrategy orderStrategy) { this.orderStrategy = orderStrategy; } protected boolean useOutline = false; /** * Get the value of useOutline * * @return the value of useOutline */ public boolean isUseOutline() { return useOutline; } /** * Set the value of useOutline * * @param useOutline new value of useOutline */ public void setUseOutline(boolean useOutline) { this.useOutline = useOutline; } protected boolean isCut = true; /** * Get the value of isCut * * @return the value of isCut */ public boolean isIsCut() { return isCut; } /** * Set the value of isCut * * @param isCut new value of isCut */ public void setIsCut(boolean isCut) { this.isCut = isCut; } protected float width = 1; /** * Get the value of width * * @return the value of width */ public float getWidth() { return width; } /** * Set the value of width * * @param width new value of width */ public void setWidth(float width) { this.width = width; } private GraphicSet calculateOuterShape(GraphicSet objects) { final Area outerShape = new Area(); for (GraphicObject o : objects) { if (o instanceof ShapeObject) { outerShape.add(new Area(((ShapeObject) o).getShape())); } else { outerShape.add(new Area(o.getBoundingBox())); } } GraphicSet result = new GraphicSet(); result.setBasicTransform(objects.getBasicTransform()); result.setTransform(objects.getTransform()); result.add(new ShapeDecorator(outerShape)); return result; } @Override public void renderPreview(Graphics2D gg, GraphicSet objects, MaterialProfile material, AffineTransform mm2px) { //TODO calculate outline gg.setColor(this.isCut ? material.getCutColor() : material.getEngraveColor()); Stroke bak = gg.getStroke(); Stroke s = new BasicStroke((float) ((mm2px.getScaleX()+mm2px.getScaleY())*this.getWidth()/2), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); gg.setStroke(s); if (this.isUseOutline()) { objects = this.calculateOuterShape(objects); } for (GraphicObject e : objects) { try { Shape sh = (e instanceof ShapeObject) ? ((ShapeObject) e).getShape() : e.getBoundingBox(); if (objects.getTransform() != null) { sh = objects.getTransform().createTransformedShape(sh); } //all coordinates are assumed to be milimeters, so we transform to desired resolution double factor = Util.dpi2dpmm(this.getDPI()); AffineTransform mm2laserPx = AffineTransform.getScaleInstance(factor, factor); sh = mm2laserPx.createTransformedShape(sh); AffineTransform laserPx2PreviewPx = mm2laserPx.createInverse(); laserPx2PreviewPx.concatenate(mm2px); if (sh == null) { //WTF?? System.out.println("Error extracting Shape from: " + ((ShapeObject) e).toString()); } else { PathIterator iter = sh.getPathIterator(null, 1); int startx = 0; int starty = 0; int lastx = 0; int lasty = 0; while (!iter.isDone()) { double[] test = new double[8]; int result = iter.currentSegment(test); //transform coordinates to preview-coordinates laserPx2PreviewPx.transform(test, 0, test, 0, 1); if (result == PathIterator.SEG_MOVETO) { startx = (int) test[0]; starty = (int) test[1]; lastx = (int) test[0]; lasty = (int) test[1]; } else if (result == PathIterator.SEG_LINETO) { gg.drawLine(lastx, lasty, (int) test[0], (int) test[1]); lastx = (int) test[0]; lasty = (int) test[1]; } else if (result == PathIterator.SEG_CLOSE) { gg.drawLine(lastx, lasty, startx, starty); } iter.next(); } } } catch (NoninvertibleTransformException ex) { Logger.getLogger(VectorProfile.class.getName()).log(Level.SEVERE, null, ex); } } gg.setStroke(bak); } @Override public void addToLaserJob(LaserJob job, GraphicSet objects, List<LaserProperty> laserProperties) { if (this.isUseOutline()) { objects = this.calculateOuterShape(objects); } double factor = Util.dpi2dpmm(this.getDPI()); AffineTransform mm2laserpx = AffineTransform.getScaleInstance(factor, factor); VectorPart part = new VectorPart(laserProperties.get(0), this.getDPI()); boolean optimize = true; for (LaserProperty prop : laserProperties) { part.setProperty(prop); ShapeConverter conv = new ShapeConverter(); for (GraphicObject e : objects) { if (e instanceof LaserScriptShape) { //since the profiles are enumerated file-wise and a laserscript is always //one file and creates one shape, this whole file should not be optimized optimize = false; ScriptInterpreter i = new ScriptInterpreter(); AffineTransform mm2laser = new AffineTransform(objects.getTransform()); mm2laser.preConcatenate(mm2laserpx); try { i.execute(new FileReader(((LaserScriptShape) e).getScriptSource()), new ScriptInterfaceLogUi(new VectorPartScriptInterface(part, mm2laser)), !PreferencesManager.getInstance().getPreferences().isDisableSandbox()); } catch (ScriptException exx) { throw new RuntimeException(exx); } catch (IOException ex) { Logger.getLogger(VectorProfile.class.getName()).log(Level.SEVERE, null, ex); } } else { Shape sh = (e instanceof ShapeObject) ? ((ShapeObject) e).getShape() : e.getBoundingBox(); if (objects.getTransform() != null) { sh = objects.getTransform().createTransformedShape(sh); } sh = mm2laserpx.createTransformedShape(sh); conv.addShape(sh, part); } } } if (optimize) { VectorOptimizer vo = VectorOptimizer.create(this.getOrderStrategy()); job.addPart(vo.optimize(part)); } else { job.addPart(part); } } @Override public LaserProfile clone() { VectorProfile cp = new VectorProfile(); cp.description = description; cp.isCut = isCut; cp.name = name; cp.orderStrategy = orderStrategy; cp.thumbnailPath = thumbnailPath; cp.width = width; cp.useOutline = useOutline; cp.setDPI(getDPI()); return cp; } @Override public int hashCode() { return super.hashCodeBase() * 31 + orderStrategy.hashCode()*7 + (useOutline?1:0) + (isCut?3:0) + (int)width; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final VectorProfile other = (VectorProfile) obj; if (this.orderStrategy != other.orderStrategy) { return false; } if (this.useOutline != other.useOutline) { return false; } if (this.isCut != other.isCut) { return false; } if (Float.floatToIntBits(this.width) != Float.floatToIntBits(other.width)) { return false; } return super.equalsBase(obj); } }