Package bs.bs2d.slicer

Source Code of bs.bs2d.slicer.Slicer

package bs.bs2d.slicer;

import bs.bs2d.geom.JTSUtils;
import bs.properties.BSProperties;
import bs.properties.LayerProperties;
import bs.testing.TestFrame;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.TopologyException;
import com.vividsolutions.jts.geom.util.AffineTransformation;
import com.vividsolutions.jts.operation.linemerge.LineMerger;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.JPanel;
import javax.vecmath.Vector2d;

/**
* Can slice geometry into layers and generate gcode using a GCodeGenerator. The
* gcode is then stored in an internal GCodeAssembler and can either be
* retrieved as a string or written to a gcode file.
* Any geometry submitted for slicing is appended to the internally stored
* gcode. Use the reset method to clear clear the gcode and
* @author Djen
*/
public class Slicer {
   
    private BSProperties properties;
    private final GCodeAssembler gcode;
   
    private double filamentUsed = 0;

    public Slicer(BSProperties properties) {
        this.properties = properties;
        gcode = new GCodeAssembler();
    }
   
    /**
     * Slices the given geometry and keeps the generated gcode in this sclicer's
     * internal GCodeAssembler.
     * @param objects A list of objects to slice (it is assumed all objects are
     * adequately placed on the print bed)
     */
    public void slice(List<PrintObject> objects) throws TopologyException{
        System.out.println("slicing...");
       
        // calculate number of layers for each object
        int total = 0; //total number of layers
        for (int i = 0; i < objects.size(); i++) {
            PrintObject obj = objects.get(i);
            double height = obj.getHeight() - properties.first_layer_height;
            obj.layers = (int)(height / properties.layer_height) + 1;
            if(obj.layers > total)
                total = obj.layers;
        }
       
        gcode.setRetraction(properties.retract_speed, properties.retract_length, properties.retract_before_travel);
       
        initPrint();
       
        for (int i = 0; i < total; i++) {
            LayerProperties lp = new LayerProperties(i, properties);
            generateLayer(objects, lp);
        }
       
        finalizePrint();
       
        filamentUsed = gcode.getTotalExtrusionLength();
    }
   
    private void initPrint(){
       
        gcode.appendLine("; extrusion width = " + properties.extrusion_width);
        gcode.appendLine("; first layer extrusion width = " + properties.first_layer_extrusion_width);
        gcode.appendLine("G21 ; set units to millimeters");
        gcode.appendLine("M107 ; turn off fan");
        gcode.appendLine("M190 S" + properties.first_layer_bed_temperature + " ; wait for bed temperature to be reached");
        gcode.appendLine("M104 S" + properties.first_layer_temperature + " ; set temperature");
        gcode.appendLines(properties.start_gcode);
        gcode.appendLine("M109 S" + properties.first_layer_temperature + " ; wait for temperature to be reached");
        gcode.appendLine("G90 ; use absolute coordinates");
        gcode.appendLine("M82 ; use absolute distances for extrusion");
    }
   
    private void finalizePrint(){
        gcode.appendLines(properties.end_gcode);
        double fil = (int)(gcode.getTotalExtrusionLength() * 10) / 10.0;
        gcode.appendLine("; filament used = " + fil + "mm");
    }
   
    private void generateLayer(List<PrintObject> objects, LayerProperties lp) throws TopologyException{
        GCodeGenerator ggen = new GCodeGenerator(gcode, properties);
       
       
       
        ggen.initLayer(lp);
       
        // skirt
        if(lp.skirt){
            generateSkirt(ggen, objects, lp);
        }
       
        for (PrintObject obj : objects) {
            generateObjectLayer(ggen, obj, lp);
        }
       
       
        ggen.finalizeLayer();
    }

    private void generateSkirt(GCodeGenerator ggen, List<PrintObject> objects, LayerProperties lp) throws TopologyException{
        // assemble all shells in one geometry collection
        Geometry[] shells = new Geometry[objects.size()];
        for (int i = 0; i < objects.size(); i++){
            PrintObject o = objects.get(i);
            shells[i] = o.getTranslate().transform(o.getAreaSet().getShell());
        }
       
        Geometry totalShell;
        totalShell = JTSUtils.getFactory().createGeometryCollection(shells);
       
        LineString[] skirt;
        skirt = JTSUtils.getLineStrings(totalShell.convexHull().buffer(lp.skirt_distance));
       
        ggen.parse(skirt[0], lp.height, lp.extrusion_width, lp.perimeter_speed);
    }
   
    private void generateObjectLayer(GCodeGenerator ggen, PrintObject obj, LayerProperties lp) throws TopologyException{
        LineString[] lines;
        double lh = lp.height;
        double ew = lp.extrusion_width;
       
        Geometry shell = obj.getAreaSet().getShell();
        Envelope bounds = shell.getEnvelopeInternal(); // bounds for hatch lines
        // using bounds of outer shell to ascertain that hatch lines on
        // different layers align
       
        AffineTransformation t = obj.getTranslate();
       
        // currently not used --> always 0
        if(lp.outline_offset != 0)
            shell = shell.buffer(-lp.outline_offset);
       
        Geometry infillArea = shell.buffer(-lp.infill_offset);
       
        // outlines
        lines = getOutlines(shell, lp);
        ggen.parseAll(lines, lh, ew, lp.perimeter_speed, t);
       
        // infill
        if(isSolid(obj, lp.layer)){ // make solid infill
            lines = getInfill(infillArea, bounds, 1, lp.fill_angle, ew);
            ggen.parseAll(lines, lh, ew, lp.infill_speed, t);
        } else {
            MultiPolygon[] polys = obj.getAreaSet().getPolygons();
           
            // separators
            lines = getSeparators(polys, infillArea);
            ggen.parseAll(lines, lh, ew, lp.infill_speed, t);
           
            // infill
            for (int i = 0; i < polys.length; i++) {
                double offset = 0.5 * ew + lp.cap_length;
                Geometry g = polys[i].buffer(-offset).intersection(infillArea);
                MultiPolygon p = JTSUtils.toMultiPolygon(g);
                lines = getInfill(p, bounds, obj.getDensity(i), lp.fill_angle, ew);
                ggen.parseAll(lines, lh, ew, lp.infill_speed, t);
            }
        }
       
    }
   
    private boolean isSolid(PrintObject obj, int layer){
        return layer < properties.bottom_solid_layers ||
               layer >= obj.layers - properties.top_solid_layers;
    }
   
    /**
     * Generates the outline path for the print, that is the inward parallel
     * offset of the polygon outlines by half the extrusion width.
     * @param poly the shapes for wich to generate the outline path
     * @param ew the extrusion width for the outlines
     * @param perimtr the number of perimeters to generate
     * @return the outline path
     */
    private LineString[] getOutlines(Geometry poly, LayerProperties lp) throws TopologyException{
        ArrayList<LineString> lines = new ArrayList<>();
       
        for (int i = lp.perimeters-1; i >= 0; i--) {
            double offset = -lp.extrusion_width * (i + 0.5);
            MultiPolygon p = JTSUtils.toMultiPolygon(poly.buffer(offset));
            lines.addAll(Arrays.asList(JTSUtils.getLineStrings(p)));
        }
       
        return lines.toArray(new LineString[lines.size()]);
    }
   
    private LineString[] getSeparators(Geometry[] polys, Geometry infillArea) throws TopologyException{
        GeometryFactory gf = JTSUtils.getFactory();
        // create empty geometry
        Geometry union = gf.buildGeometry(new ArrayList(0));
       
        // get separators for each polygon
        for (Geometry poly : polys) {
           
            LineString[] lines = JTSUtils.getLineStrings(poly);
           
            MultiLineString mls;
            mls = gf.createMultiLineString(lines);
            Geometry g = infillArea.intersection(mls);
            lines = JTSUtils.getLineStrings(g);
           
            //unify line segments to delete duplicates
            for (LineString l : lines) {
                if(l.isEmpty())
                    continue;
                union = union.union(l);
            }
        }
       
        // merge segments
        LineMerger merger = new LineMerger();
        merger.add(union);
        union = gf.buildGeometry(merger.getMergedLineStrings());
       
        return JTSUtils.getLineStrings(union);
    }
   
    /**
     * Generates a hatch to infill the given areas.
     * @param infillArea the area to infill
     * @param bounds the bounding box for the hatch
     * @param density the fill density
     * @param angle the hatch angle
     * @param eWidth the extrusion width
     * @return a set of hatch lines
     */
    private LineString[] getInfill(Geometry infillArea, Envelope bounds,
                                   double density, double angle, double eWidth) throws TopologyException{
        // get hatch lines and clip to area
        LineString[] lines = getHatchLines(bounds, density, angle, eWidth);
        for (int i = 1; i < lines.length; i += 2) {
            lines[i] = (LineString)lines[i].reverse();
        }
        MultiLineString hatchLines;
        hatchLines = JTSUtils.getFactory().createMultiLineString(lines);
        Geometry intersection = hatchLines.intersection(infillArea);
        lines = JTSUtils.getLineStrings(intersection);
       
       
//        // split area outlines along hatch lines
//        MultiLineString outline;
//        outline = gf.createMultiLineString(JTSUtils.getLineStrings(hatchArea));
//        outline = (MultiLineString) outline.difference(hatchLines);
//       
//        // stitch hatch lines and outline segments together
//        LinkedList<LineString> hlines = new LinkedList<>();
//        hlines.addAll(Arrays.asList(JTSUtils.getLineStrings(hatchLines)));
//        LinkedList<LineString> olines = new LinkedList<>();
//        olines.addAll(Arrays.asList(JTSUtils.getLineStrings(outline)));
//       
//        ArrayList<LineString> stitch = new ArrayList<>();
//        LineString ls = hlines.removeFirst();
//        Point point;
//        while(true){
//            point = ls.getStartPoint();
//           
//            if(hlines.isEmpty())
//                break;
//        }
       
        return lines;
    }
   
    /**
     * Generates parallel hatch lines for the box defined by 'bounds'.
     * @param bounds the box to hatch
     * @param density the hatch density [0 1]
     * @param angle the hatch angle in °
     * @param eWidth the extrusion width
     * @return a set of hatch lines
     */
    private LineString[] getHatchLines(Envelope bounds, double density, double angle, double eWidth){
        GeometryFactory gf = JTSUtils.getFactory();
        double gap = eWidth / density; // distance between lines
           
        boolean lowAngles = angle < 45 || angle > 135;
       
        angle = Math.toRadians(angle % 180);
        double m, d1, d2, min, max;
       
        if(lowAngles){
            //horizontally oriented
            m = Math.tan(angle);
            d1 = bounds.getWidth();
            min = bounds.getMinY();
            max = bounds.getMaxY();
        } else {
            angle -= Math.PI / 2;
            m = -Math.tan(angle);
            d1 = bounds.getHeight();
            min = bounds.getMinX();
            max = bounds.getMaxX();
        }
       
        d2 = m * d1;
        double agap = Math.abs(gap / Math.cos(angle)); // gap along axis
       
        if(d2 >= 0){
            min -= d2;
        } else {
            max -= d2; // dy is negativ
        }
       
        int n = (int)((max - min) / agap);
        double missing = ((max - min) % agap);
       
        if(density == 1.0){// solid layer
            if(missing/agap > 0.3)
                n++;
            //TODO: put this back in when hatch stitching ist implemented
//            min += params.extrusionWidth;
//            max -= params.extrusionWidth;
        } else { // not solid
            missing = (missing + agap - eWidth) / 2;
            min += missing;
            max -= missing;
        }
        agap = (max - min) / (n-1);
       
        double xbase, ybase, dx, dy, xInc = 0, yInc = 0;
        if(lowAngles){
            xbase = bounds.getMinX();
            ybase = min;
            dx = d1;
            dy = d2;
            yInc = agap;
        } else {
            xbase = min;
            ybase = bounds.getMinY();
            dx = d2;
            dy = d1;
            xInc = agap;
        }
           
        LineString[] lines = new LineString[n];
        for (int i = 0; i < n; i++) {
            double x = xbase + i * xInc;
            double y = ybase + i * yInc;
            Coordinate p1 = new Coordinate(x, y);
            Coordinate p2 = new Coordinate(x + dx, y + dy);
            lines[i] = gf.createLineString(new Coordinate[]{p1, p2});
        }
        return lines;
    }

    /**
     * @return the properties
     */
    protected BSProperties getProperties() {
        return properties;
    }

    /**
     * @param properties the properties to set
     */
    protected void setProperties(BSProperties properties) {
        this.properties = properties;
    }

    /**
     * Returns the amount of filament used in the gcode currently stored in this
     * Slicer (in mm). This does not include any filament extruded in the start
     * or end code specified in the properties.
     * @return the filament used
     */
    public double getFilamentUsed() {
        return filamentUsed;
    }
   
    /**
     *
     * @return the internally stored gcode as a string
     */
    public String getGCode(){
        return gcode.getGcode().toString();
    }
   
    /**
     * Writes the internally stored gcode to the given file. The proper ".gcode"
     * file extension will be added if necessary.
     * @param outputFile the file to write the gcode to
     * @throws IOException if an I/O error occurs
     */
    public void writeToFile(File outputFile) throws IOException{
        gcode.writeToFile(outputFile);
    }
   
   
   
    // for testing...
    public static void main(String[] args){
       
        JPanel jp = new JPanel(){

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2 = (Graphics2D)g;
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
               
                Coordinate[] points = new Coordinate[]{
                    new Coordinate(0, 0),
                    new Coordinate(10, 0),
                    new Coordinate(10, 7),
                    new Coordinate(0, 7),
                    new Coordinate(0, 0),
                };
                MultiPolygon p1 = JTSUtils.toMultiPolygon(JTSUtils.getFactory().createPolygon(points));
               
                BSProperties props = new BSProperties();
                Slicer slicer = new Slicer(null);
               
                double ew = 0.37;
                double d = 0.5;
                double a = 40;
                int layers = 2;
                LayerProperties lp = new LayerProperties(0, props);
               
                MultiLineString outlines = JTSUtils.getFactory().createMultiLineString(slicer.getOutlines(p1, lp));
                System.out.println(outlines.getNumGeometries());
                
                LineString[] hl = slicer.getInfill(p1, p1.getEnvelopeInternal(), 1, lp.fill_angle, lp.extrusion_width);
                MultiLineString hatch = JTSUtils.getFactory().createMultiLineString(hl);
               
                AffineTransform t = AffineTransform.getTranslateInstance(50, 500);
                t.concatenate(AffineTransform.getScaleInstance(50, -50));
               
                AffineTransform gt = g2.getTransform();
                g2.transform(t);
               
                g2.setStroke(new BasicStroke((float)ew, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
               
//                g2.setColor(Color.ORANGE);
//                g2.draw(JTSUtils.toShape(buffer));
               
                g2.setColor(Color.RED);
                g2.draw(JTSUtils.toShape(hatch));
               
                g2.setColor(Color.BLUE);
                g2.draw(JTSUtils.toShape(outlines));
               
                g2.setTransform(gt);
               
            }
           
        };
        jp.setBackground(Color.WHITE);
        new TestFrame(jp).setVisible(true);
    }

   
   
}
TOP

Related Classes of bs.bs2d.slicer.Slicer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.