package org.reprap.geometry.polyhedra; import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; import java.util.List; import java.util.ArrayList; import org.reprap.geometry.LayerRules; import org.reprap.geometry.polygons.BooleanGrid; import org.reprap.geometry.polygons.BooleanGridList; import org.reprap.geometry.polygons.CSG2D; import org.reprap.geometry.polygons.HalfPlane; import org.reprap.geometry.polygons.Interval; import org.reprap.geometry.polygons.Point2D; import org.reprap.geometry.polygons.Polygon; import org.reprap.geometry.polygons.PolygonList; import org.reprap.geometry.polygons.Rectangle; import org.reprap.Attributes; import org.reprap.Extruder; import org.reprap.Preferences; import org.reprap.RFO; import org.reprap.utilities.Debug; import javax.media.j3d.BranchGroup; import javax.media.j3d.GeometryArray; import javax.media.j3d.Group; import javax.media.j3d.SceneGraphObject; import javax.media.j3d.Shape3D; import javax.media.j3d.Transform3D; import javax.vecmath.Matrix4d; import javax.vecmath.Point3d; import javax.vecmath.Tuple3d; /** * This class holds a list of STLObjects that represents everything that is to be built. * * An STLObject may consist of items from several STL files, possible of different materials. * But they are all tied together relative to each other in space. * * @author Adrian * */ public class AllSTLsToBuild { /** * Class to hold infill patterns * @author ensab * */ class InFillPatterns { BooleanGridList bridges; BooleanGridList insides; BooleanGridList surfaces; PolygonList hatchedPolygons; InFillPatterns() { bridges = new BooleanGridList(); insides = new BooleanGridList(); surfaces = new BooleanGridList(); hatchedPolygons = new PolygonList(); } InFillPatterns(InFillPatterns ifp) { bridges = ifp.bridges; insides = ifp.insides; surfaces = ifp.surfaces; hatchedPolygons = ifp.hatchedPolygons; } } /** * 3D bounding box * @author ensab * */ class BoundingBox { private Rectangle XYbox; private Interval Zint; public BoundingBox(Point3d p0) { Zint = new Interval(p0.z, p0.z); XYbox = new Rectangle(new Interval(p0.x, p0.x), new Interval(p0.y, p0.y)); } public BoundingBox(BoundingBox b) { Zint = new Interval(b.Zint); XYbox = new Rectangle(b.XYbox); } public void expand(Point3d p0) { Zint.expand(p0.z); XYbox.expand(new Point2D(p0.x, p0.y)); } public void expand(BoundingBox b) { Zint.expand(b.Zint); XYbox.expand(b.XYbox); } } /** * Line segment consisting of two points. * @author Adrian * */ class LineSegment { /** * The ends of the line segment */ public Point2D a = null, b = null; /** * The attribute (i.e. RepRap material) of the segment. */ public Attributes att = null; // protected void finalize() throws Throwable // { // a = null; // b = null; // att = null; // super.finalize(); // } /** * Constructor takes two intersection points with an STL triangle edge. * @param p * @param q */ public LineSegment(Point2D p, Point2D q, Attributes at) { if(at == null) Debug.e("LineSegment(): null attributes!"); a = p; b = q; att = at; } } /** * Ring buffer cache to hold previously computed slices for doing * infill and support material calculations. * @author ensab * */ class SliceCache { private BooleanGridList[][] sliceRing; private BooleanGridList[][] supportRing; private int[] layerNumber; private int ringPointer; private final int noLayer = Integer.MIN_VALUE; private int ringSize = 10; public SliceCache(LayerRules lr) { if(lr == null) Debug.e("SliceCache(): null LayerRules!"); ringSize = lr.sliceCacheSize(); sliceRing = new BooleanGridList[ringSize][stls.size()]; supportRing = new BooleanGridList[ringSize][stls.size()]; layerNumber = new int[ringSize]; ringPointer = 0; for(int layer = 0; layer < ringSize; layer++) for(int stl = 0; stl < stls.size(); stl++) { sliceRing[layer][stl] = null; supportRing[layer][stl] = null; layerNumber[layer] = noLayer; } } private int getTheRingLocationForWrite(int layer) { for(int i = 0; i < ringSize; i++) if(layerNumber[i] == layer) return i; int rp = ringPointer; for(int s = 0; s < stls.size(); s++) { sliceRing[rp][s] = null; supportRing[rp][s] = null; } ringPointer++; if(ringPointer >= ringSize) ringPointer = 0; return rp; } public void setSlice(BooleanGridList slice, int layer, int stl) { int rp = getTheRingLocationForWrite(layer); layerNumber[rp] = layer; sliceRing[rp][stl] = slice; } public void setSupport(BooleanGridList support, int layer, int stl) { int rp = getTheRingLocationForWrite(layer); layerNumber[rp] = layer; supportRing[rp][stl] = support; } private int getTheRingLocationForRead(int layer) { int rp = ringPointer; for(int i = 0; i < ringSize; i++) { rp--; if(rp < 0) rp = ringSize - 1; if(layerNumber[rp] == layer) return rp; } return -1; } public BooleanGridList getSlice(int layer, int stl) { int rp = getTheRingLocationForRead(layer); if(rp >= 0) return sliceRing[rp][stl]; return null; } public BooleanGridList getSupport(int layer, int stl) { int rp = getTheRingLocationForRead(layer); if(rp >= 0) return supportRing[rp][stl]; return null; } } /** * OpenSCAD file extension */ private static final String scad = ".scad"; /** * The list of things to be built */ private List<STLObject> stls; /** * The building layer rules */ private LayerRules layerRules = null; /** * A plan box round each item */ private List<Rectangle> rectangles; /** * New list of things to be built for reordering */ private List<STLObject> newstls; /** * The XYZ box around everything */ //private RrRectangle XYbox; private BoundingBox XYZbox; /** * The lowest and highest points */ private Interval Zrange; /** * Is the list editable? */ private boolean frozen; /** * Recently computed slices */ private SliceCache cache; /** * Simple constructor * */ public AllSTLsToBuild() { stls = new ArrayList<STLObject>(); rectangles = null; newstls = null; XYZbox = null; Zrange = null; frozen = false; cache = null; layerRules = null; } /** * Add a new STLObject * @param s */ public void add(STLObject s) { if(frozen) Debug.e("AllSTLsToBuild.add(): adding an item to a frozen list."); stls.add(s); } /** * Add a new collection * @param s */ public void add(AllSTLsToBuild a) { if(frozen) Debug.e("AllSTLsToBuild.add(): adding a collection to a frozen list."); for(int i = 0; i < a.size(); i++) stls.add(a.get(i)); } /** * Get the i-th STLObject * @param i * @return */ public STLObject get(int i) { return stls.get(i); } /** * Delete an object * @param i */ public void remove(int i) { if(frozen) Debug.e("AllSTLsToBuild.remove(): removing an item from a frozen list."); stls.remove(i); } /** * Find an object in the list * @param st * @return */ private int findSTL(STLObject st) { if(size() <= 0) { Debug.e("AllSTLsToBuild.findSTL(): no objects to pick from!"); return -1; } int index = -1; for(int i = 0; i < size(); i++) if(get(i) == st) { index = i; break; } if(index < 0) { Debug.e("AllSTLsToBuild.findSTL(): dud object submitted."); return -1; } return index; } /** * Find an object in the list and return the next one. * @param st * @return */ public STLObject getNextOne(STLObject st) { int index = findSTL(st); index++; if(index >= size()) index = 0; return get(index); } /** * Return the number of objects. * @return */ public int size() { return stls.size(); } /** * Create an OpenSCAD (http://www.openscad.org/) program that will read everything * in in the same pattern as it is stored here. It can then * be written by OpenSCAD as a single STL. * @return */ public String toSCAD() { String result = "union()\n{\n"; for(int i = 0; i < size(); i++) result += get(i).toSCAD(); result += "}"; return result; } /** * Write everything to an OpenSCAD program. * @param fn the directory to write into */ public void saveSCAD(String fn) { if(fn.charAt(fn.length()-1) == File.separator.charAt(0)) fn = fn.substring(0, fn.length()-1); int sepIndex = fn.lastIndexOf(File.separator); int fIndex = fn.indexOf("file:"); String name = fn.substring(sepIndex + 1, fn.length()); String path; if(sepIndex >= 0) { if(fIndex >= 0) path = fn.substring(fIndex + 5, sepIndex + 1); else path = fn.substring(0, sepIndex + 1); } else path = ""; path += name + File.separator; name += scad; if(!RFO.checkFile(path, name)) return; File file= new File(path); if(!file.exists()) file.mkdir(); RFO.copySTLs(this, path); try { PrintWriter out = new PrintWriter(new FileWriter(path+name)); out.println(toSCAD()); out.close(); } catch (Exception e) { Debug.e("AllSTLsToBuild.saveSCAD(): can't open file: " + path+name); } } /** * Reorder the list under user control. The user sends items from the * old list one by one. These are added to a new list in that order. * When there's only one left that is added last automatically. * * Needless to say, this process must be carried through to completion. * The function returns true while the process is ongoing, false when * it's complete. * * @param st * @return */ public boolean reorderAdd(STLObject st) { if(frozen) Debug.d("AllSTLsToBuild.reorderAdd(): attempting to reorder a frozen list."); if(newstls == null) newstls = new ArrayList<STLObject>(); int index = findSTL(st); newstls.add(get(index)); stls.remove(index); if(stls.size() > 1) return true; newstls.add(get(0)); stls = newstls; newstls = null; cache = null; // Just in case... return false; } /** * Scan everything loaded and set up the bounding boxes */ public void setBoxes() { //if(XYZbox != null) // Already done? // return; rectangles = new ArrayList<Rectangle>(); for(int i = 0; i < stls.size(); i++) rectangles.add(null); BoundingBox s; for(int i = 0; i < stls.size(); i++) { STLObject stl = stls.get(i); Transform3D trans = stl.getTransform(); BranchGroup bg = stl.getSTL(); java.util.Enumeration<?> enumKids = bg.getAllChildren(); while(enumKids.hasMoreElements()) { Object ob = enumKids.nextElement(); if(ob instanceof BranchGroup) { BranchGroup bg1 = (BranchGroup)ob; Attributes att = (Attributes)(bg1.getUserData()); if(XYZbox == null) { XYZbox = BBox(att.getPart(), trans); if(rectangles.get(i) == null) rectangles.set(i, new Rectangle(XYZbox.XYbox)); else rectangles.set(i, Rectangle.union(rectangles.get(i), XYZbox.XYbox)); } else { s = BBox(att.getPart(), trans); if(s != null) { XYZbox.expand(s); if(rectangles.get(i) == null) rectangles.set(i, new Rectangle(s.XYbox)); else rectangles.set(i, Rectangle.union(rectangles.get(i), s.XYbox)); } } } } if(rectangles.get(i) == null) Debug.e("AllSTLsToBuild:ObjectPlanRectangle(): object " + i + " is empty"); } } /** * Freeze the list - no more editing. * Also compute the XY box round everything. * Also compute the individual plan boxes round each STLObject. */ private void freeze() { if(frozen) return; if(layerRules == null) Debug.e("AllSTLsToBuild.freeze(): layerRules not set!"); frozen = true; if(cache == null) cache = new SliceCache(layerRules); setBoxes(); } /** * Run through a Shape3D and find its enclosing XYZ box * @param shape * @param trans * @param z */ private BoundingBox BBoxPoints(Shape3D shape, Transform3D trans) { BoundingBox b = null; GeometryArray g = (GeometryArray)shape.getGeometry(); Point3d p1 = new Point3d(); Point3d q1 = new Point3d(); if(g != null) { for(int i = 0; i < g.getVertexCount(); i++) { g.getCoordinate(i, p1); trans.transform(p1, q1); if(b == null) b = new BoundingBox(q1); else b.expand(q1); } } return b; } /** * Unpack the Shape3D(s) from value and find their exclosing XYZ box * @param value * @param trans * @param z */ private BoundingBox BBox(Object value, Transform3D trans) { BoundingBox b = null; BoundingBox s; if(value instanceof SceneGraphObject) { SceneGraphObject sg = (SceneGraphObject)value; if(sg instanceof Group) { Group g = (Group)sg; java.util.Enumeration<?> enumKids = g.getAllChildren( ); while(enumKids.hasMoreElements()) { if(b == null) b = BBox(enumKids.nextElement(), trans); else { s = BBox(enumKids.nextElement(), trans); if(s != null) b.expand(s); } } } else if (sg instanceof Shape3D) { b = BBoxPoints((Shape3D)sg, trans); } } return b; } /** * Return the XY box round everything * @return */ public Rectangle ObjectPlanRectangle() { if(XYZbox == null) Debug.e("AllSTLsToBuild.ObjectPlanRectangle(): null XYZbox!"); return XYZbox.XYbox; } /** * Find the top of the highest object. * Calling this freezes the list. * @return */ public double maxZ() { if(XYZbox == null) Debug.e("AllSTLsToBuild.maxZ(): null XYZbox!"); return XYZbox.Zint.high(); } /** * Stitch together the some of the edges to form a polygon. * @param edges * @return */ private Polygon getNextPolygon(ArrayList<LineSegment> edges) { if(!frozen) { Debug.e("AllSTLsToBuild:getNextPolygon() called for an unfrozen list!"); freeze(); } if(edges.size() <= 0) return null; LineSegment next = edges.get(0); edges.remove(0); Polygon result = new Polygon(next.att, true); result.add(next.a); result.add(next.b); Point2D start = next.a; Point2D end = next.b; boolean first = true; while(edges.size() > 0) { double d2 = Point2D.dSquared(start, end); if(first) d2 = Math.max(d2, 1); first = false; boolean aEnd = false; int index = -1; for(int i = 0; i < edges.size(); i++) { double dd = Point2D.dSquared(edges.get(i).a, end); if(dd < d2) { d2 = dd; aEnd = true; index = i; } dd = Point2D.dSquared(edges.get(i).b, end); if(dd < d2) { d2 = dd; aEnd = false; index = i; } } if(index >= 0) { next = edges.get(index); edges.remove(index); int ipt = result.size() - 1; if(aEnd) { result.set(ipt, Point2D.mul(Point2D.add(next.a, result.point(ipt)), 0.5)); result.add(next.b); end = next.b; } else { result.set(ipt, Point2D.mul(Point2D.add(next.b, result.point(ipt)), 0.5)); result.add(next.a); end = next.a; } } else return result; } Debug.d("AllSTLsToBuild.getNextPolygon(): exhausted edge list!"); return result; } /** * Get all the polygons represented by the edges. * @param edges * @return */ private PolygonList simpleCull(ArrayList<LineSegment> edges) { if(!frozen) { Debug.e("AllSTLsToBuild:simpleCull() called for an unfrozen list!"); freeze(); } PolygonList result = new PolygonList(); Polygon next = getNextPolygon(edges); while(next != null) { if(next.size() >= 3) result.add(next); next = getNextPolygon(edges); } return result; } /** * Compute the support hatching polygons for this set of patterns * @param stl * @param layerConditions * @return */ public PolygonList computeSupport(int stl) { // No more additions or movements, please freeze(); // We start by computing the union of everything in this layer because // that is everywhere that support _isn't_ needed. // We give the union the attribute of the first thing found, though // clearly it will - in general - represent many different substances. // But it's only going to be subtracted from other shapes, so what it's made // from doesn't matter. int layer = layerRules.getModelLayer(); BooleanGridList thisLayer = slice(stl, layer); BooleanGrid unionOfThisLayer; Attributes a; if(thisLayer.size() > 0) { unionOfThisLayer = thisLayer.get(0); a = unionOfThisLayer.attribute(); }else { a = stls.get(stl).attributes(0); unionOfThisLayer = BooleanGrid.nullBooleanGrid(); } for(int i = 1; i < thisLayer.size(); i++) unionOfThisLayer = BooleanGrid.union(unionOfThisLayer, thisLayer.get(i), a); // Expand the union of this layer a bit, so that any support is a little clear of // this layer's boundaries. BooleanGridList allThis = new BooleanGridList(); allThis.add(unionOfThisLayer); allThis = allThis.offset(layerRules, true, 2); // 2mm gap is a bit of a hack... if(allThis.size() > 0) unionOfThisLayer = allThis.get(0); else unionOfThisLayer = BooleanGrid.nullBooleanGrid(); // Get the layer above and union it with this layer. That's what needs // support on the next layer down. BooleanGridList previousSupport = cache.getSupport(layer+1, stl); cache.setSupport(BooleanGridList.unions(previousSupport, thisLayer), layer, stl); // Now we subtract the union of this layer from all the stuff requiring support in the layer above. BooleanGridList support = new BooleanGridList(); if(previousSupport != null) { for(int i = 0; i < previousSupport.size(); i++) { BooleanGrid above = previousSupport.get(i); a = above.attribute(); Extruder e = a.getExtruder().getSupportExtruder(); if(e != null) { if(layerRules.extruderActiveThisLayer(e.getID())) support.add(BooleanGrid.difference(above, unionOfThisLayer, a)); } } support = support.unionDuplicates(); } // Now force the attributes of the support pattern to be the support extruders // for all the materials in it. If the material isn't active in this layer, remove it from the list for(int i = 0; i < support.size(); i++) { Extruder e = support.attribute(i).getExtruder().getSupportExtruder(); if(e == null) { Debug.e("AllSTLsToBuild.computeSupport(): null support extruder specified!"); continue; } support.get(i).forceAttribute(new Attributes(e.getMaterial(), null, null, e.getAppearance())); } // Finally compute the support hatch. PolygonList result = support.hatch(layerRules, false, null, true); return result; } /** * This finds an individual land in landPattern * @param landPattern * @return */ private BooleanGrid findLand(BooleanGrid landPattern) { Point2D seed = landPattern.findSeed(); if(seed == null) return null; return landPattern.floodCopy(seed); } /** * This finds the bridges that cover cen. It assumes that there is only one material at one point in space... * @param unSupported * @param cen1 * @return */ int findBridges(BooleanGridList unSupported, Point2D cen) { for(int i = 0; i < unSupported.size(); i++) { BooleanGrid bridges = unSupported.get(i); if(bridges.get(cen)) return i; } return -1; } // private void nameAtt(BooleanGridList bgl, String name) // { // for(int i = 0; i < bgl.size(); i++) // { // Attributes a = bgl.get(i).attribute(); // Debug.e(name + " has material: " + a.getMaterial()); // } // } /** * Compute the bridge infill for unsupported polygons for a slice. This is very heuristic... * * @param infill * @param lands * @param layerConditions * @return * */ public InFillPatterns bridgeHatch(InFillPatterns infill, BooleanGridList lands, LayerRules layerConditions) { InFillPatterns result = new InFillPatterns(infill); BooleanGridList b; for(int i = 0; i < lands.size(); i++) { BooleanGrid landPattern = lands.get(i); BooleanGrid land1; // Find a land while((land1 = findLand(landPattern)) != null) { // Find the middle of the land Point2D cen1 = land1.findCentroid(); // Wipe this land from the land pattern landPattern = BooleanGrid.difference(landPattern, land1); if(cen1 == null) { Debug.e("AllSTLsToBuild.bridges(): First land found with no centroid!"); continue; } // Find the bridge that goes with the land int bridgesIndex = findBridges(result.bridges, cen1); if(bridgesIndex < 0) { Debug.d("AllSTLsToBuild.bridges(): Land found with no corresponding bridge."); continue; } BooleanGrid bridges = result.bridges.get(bridgesIndex); // The bridge must cover the land too BooleanGrid bridge = bridges.floodCopy(cen1); if(bridge == null) continue; // Find the other land (the first has been wiped) BooleanGrid land2 = null; land2 = BooleanGrid.intersection(bridge, landPattern); // Find the middle of this land Point2D cen2 = land2.findCentroid(); if(cen2 == null) { Debug.d("AllSTLsToBuild.bridges(): Second land found with no centroid."); // No second land implies a ring of support - just infill it. result.hatchedPolygons.add(bridge.hatch(layerConditions.getHatchDirection(bridge.attribute().getExtruder(), false), bridge.attribute().getExtruder().getExtrusionInfillWidth(), bridge.attribute())); // Remove this bridge (in fact, just its lands) from the other infill patterns. b = new BooleanGridList(); b.add(bridge); result.insides = BooleanGridList.differences(result.insides, b); result.surfaces = BooleanGridList.differences(result.surfaces, b); } else { // Wipe this land from the land pattern landPattern = BooleanGrid.difference(landPattern, land2); // (Roughly) what direction does the bridge go in? Point2D centroidDirection = Point2D.sub(cen2, cen1).norm(); Point2D bridgeDirection = centroidDirection; // Fine the edge of the bridge that is nearest parallel to that, and use that as the fill direction double spMax = Double.NEGATIVE_INFINITY; double sp; PolygonList bridgeOutline = bridge.allPerimiters(bridge.attribute()); for(int pol = 0; pol < bridgeOutline.size(); pol++) { Polygon polygon = bridgeOutline.polygon(i); for(int vertex1 = 0; vertex1 < polygon.size(); vertex1++) { int vertex2 = vertex1+1; if(vertex2 >= polygon.size()) // We know the polygon must be closed... vertex2 = 0; Point2D edge = Point2D.sub(polygon.point(vertex2), polygon.point(vertex1)); if((sp = Math.abs(Point2D.mul(edge, centroidDirection))) > spMax) { spMax = sp; bridgeDirection = edge; } } } // Build the bridge result.hatchedPolygons.add(bridge.hatch(new HalfPlane(new Point2D(0,0), bridgeDirection), bridge.attribute().getExtruder().getExtrusionInfillWidth(), bridge.attribute())); // Remove this bridge (in fact, just its lands) from the other infill patterns. b = new BooleanGridList(); b.add(bridge); result.insides = BooleanGridList.differences(result.insides, b); result.surfaces = BooleanGridList.differences(result.surfaces, b); } // remove the bridge from the bridge patterns. b = new BooleanGridList(); b.add(bridge); result.bridges = BooleanGridList.differences(result.bridges, b); } } return result; } /** * Set the building layer rules as soon as we know them * @param lr */ public void setLayerRules(LayerRules lr) { layerRules = lr; } /** * Select from a slice (allLayer) just those parts of it that will be plotted this layer * @param allLayer * @param infill * @param support * @return */ private BooleanGridList neededThisLayer(BooleanGridList allLayer, boolean infill, boolean support) { BooleanGridList neededSlice = new BooleanGridList(); for(int i = 0; i < allLayer.size(); i++) { Extruder e; if(infill) e = allLayer.get(i).attribute().getExtruder().getInfillExtruder(); else if(support) e = allLayer.get(i).attribute().getExtruder().getSupportExtruder(); else e = allLayer.get(i).attribute().getExtruder(); if(e != null) if(layerRules.extruderActiveThisLayer(e.getID())) neededSlice.add(allLayer.get(i)); } return neededSlice; } /** * Compute the infill hatching polygons for this set of patterns * @param stl * @param layerConditions * @param startNearHere * @return */ public PolygonList computeInfill(int stl) { // Where the result will be stored. InFillPatterns infill = new InFillPatterns(); // No more additions or movements, please freeze(); // Where are we and what does the current slice look like? int layer = layerRules.getModelLayer(); BooleanGridList slice = slice(stl, layer); int surfaceLayers = 1; for(int i = 0; i < slice.size(); i++) { Extruder e = slice.get(i).attribute().getExtruder(); if(e.getSurfaceLayers() > surfaceLayers) surfaceLayers = e.getSurfaceLayers(); } // Get the bottom out of the way - no fancy calculations needed. if(layer <= surfaceLayers) { slice = slice.offset(layerRules, false, -1); slice = neededThisLayer(slice, false, false); infill.hatchedPolygons = slice.hatch(layerRules, true, null, false); return infill.hatchedPolygons; } // If we are solid but the slices above or below us weren't, we need some fine infill as // we are (at least partly) surface. // The intersection of the slices above does not need surface infill... // How many do we need to consider? BooleanGridList above = slice(stl, layer+1); for(int i = 2; i <= surfaceLayers; i++) above = BooleanGridList.intersections(slice(stl, layer+i), above); // ...nor does the intersection of those below. BooleanGridList below = slice(stl, layer-1); for(int i = 2; i <= surfaceLayers; i++) below = BooleanGridList.intersections(slice(stl, layer-i), below); // The bit of the slice with nothing above it needs fine infill... BooleanGridList nothingabove = BooleanGridList.differences(slice, above); // ...as does the bit with nothing below. BooleanGridList nothingbelow = BooleanGridList.differences(slice, below); // Find the region that is not surface. infill.insides = BooleanGridList.differences(slice, nothingbelow); infill.insides = BooleanGridList.differences(infill.insides, nothingabove); // Parts with nothing under them that have no support material // need to have bridges constructed to do the best for in-air infill. infill.bridges = nothingbelow.cullNoSupport(); // The remainder with nothing under them will be supported by support material // and so needs no special treatment. nothingbelow = nothingbelow.cullSupport(); // All the parts of this slice that need surface infill infill.surfaces = BooleanGridList.unions(nothingbelow, nothingabove); // Make the bridges fatter, then crop them to the slice. // This will make them interpenetrate at their ends/sides to give // bridge landing areas. infill.bridges = infill.bridges.offset(layerRules, false, 2); infill.bridges = BooleanGridList.intersections(infill.bridges, slice); // Find the landing areas as a separate set of shapes that go with the bridges. BooleanGridList lands = BooleanGridList.intersections(infill.bridges, BooleanGridList.unions(infill.insides,infill.surfaces)); // Shapes will be outlined, and so need to be shrunk to allow for that. But they // must not also shrink from each other internally. So initially expand them so they overlap infill.bridges = infill.bridges.offset(layerRules, false, 1); infill.insides = infill.insides.offset(layerRules, false, 1); infill.surfaces = infill.surfaces.offset(layerRules, false, 1); // Now intersect them with the slice so the outer edges are back where they should be. infill.bridges = BooleanGridList.intersections(infill.bridges, slice); infill.insides = BooleanGridList.intersections(infill.insides, slice); infill.surfaces = BooleanGridList.intersections(infill.surfaces, slice); // Now shrink them so the edges are in a bit to allow the outlines to // be put round the outside. The inner joins should now shrink back to be // adjacent to each other as they should be. infill.bridges = infill.bridges.offset(layerRules, false, -1); infill.insides = infill.insides.offset(layerRules, false, -1); infill.surfaces = infill.surfaces.offset(layerRules, false, -1); // Generate the infill patterns. We do the bridges first, as each bridge subtracts its // lands from the other two sets of shapes. We want that, so they don't get infilled twice. infill = bridgeHatch(infill, lands, layerRules); infill.insides = neededThisLayer(infill.insides, true, false); infill.hatchedPolygons.add(infill.insides.hatch(layerRules, false, null, false)); infill.surfaces = neededThisLayer(infill.surfaces, false, false); infill.hatchedPolygons.add(infill.surfaces.hatch(layerRules, true, null, false)); return infill.hatchedPolygons; } /** * Compute the polygon to lay down for the machine to wipe its nose on. * @param a * @return */ public Polygon shieldPolygon(Attributes a) { Rectangle rr = ObjectPlanRectangle(); Point2D corner = Point2D.add(rr.sw(), new Point2D(-3, -3)); Polygon ell = new Polygon(a, false); ell.add(corner); ell.add(Point2D.add(corner, new Point2D(0, 10))); ell.add(Point2D.add(corner, new Point2D(-2, 10))); ell.add(Point2D.add(corner, new Point2D(-2, -2))); ell.add(Point2D.add(corner, new Point2D(20, -2))); ell.add(Point2D.add(corner, new Point2D(20, 0))); ell.add(corner); return ell; } /** * Compute the outline polygons for this set of patterns. * @param layerConditions * @param hatchedPolygons * @param shield * @return */ public PolygonList computeOutlines(int stl, PolygonList hatchedPolygons, boolean shield) { // No more additions or movements, please freeze(); // The shapes to outline. BooleanGridList slice = slice(stl, layerRules.getModelLayer()); // Pick out the ones we need to do at this height slice = neededThisLayer(slice, false, false); if(slice.size() <= 0) return new PolygonList(); PolygonList borderPolygons; // Are we building the raft under things? If so, there is no border. if(layerRules.getLayingSupport()) { borderPolygons = null; } else { BooleanGridList offBorder = slice.offset(layerRules, true, -1); borderPolygons = offBorder.borders(); } // If we've got polygons to plot, amend them so they start in the middle // of a hatch (this gives cleaner boundaries). Also add the nose-wipe shield // if it's been asked for. if(borderPolygons != null && borderPolygons.size() > 0) { borderPolygons.middleStarts(hatchedPolygons, layerRules, slice); try { if(shield && Preferences.loadGlobalBool("Shield") || (borderPolygons.polygon(0).getAttributes().getExtruder().getPurgeTime() <= 0 && layerRules.getMachineLayer() == 0)) borderPolygons.add(0, shieldPolygon(borderPolygons.polygon(0).getAttributes())); } catch (Exception ex) {} } return borderPolygons; } /** * Generate a set of pixel-map representations, one for each extruder, for * STLObject stl at height z. * * @param stlIndex * @param z * @param extruders * @return */ @SuppressWarnings("unchecked") private BooleanGridList slice(int stlIndex, int layer) { if(!frozen) { Debug.e("AllSTLsToBuild.slice() called when unfrozen!"); freeze(); } if(layer < 0) return new BooleanGridList(); // Is the result in the cache? If so, just use that. BooleanGridList result = cache.getSlice(layer, stlIndex); if(result != null) return result; // Haven't got it in the cache, so we need to compute it // Anything there? if(rectangles.get(stlIndex) == null) return new BooleanGridList(); // Probably... double z = layerRules.getModelZ(layer) + layerRules.getZStep()*0.5; Extruder[] extruders = layerRules.getPrinter().getExtruders(); result = new BooleanGridList(); CSG2D csgp = null; PolygonList pgl = new PolygonList(); int extruderID; // Bin the edges and CSGs (if any) by extruder ID. ArrayList<LineSegment>[] edges = new ArrayList[extruders.length]; ArrayList<CSG3D>[] csgs = new ArrayList[extruders.length]; Attributes[] atts = new Attributes[extruders.length]; for(extruderID = 0; extruderID < extruders.length; extruderID++) { if(extruders[extruderID].getID() != extruderID) Debug.e("AllSTLsToBuild.slice(): extruder " + extruderID + "out of sequence: " + extruders[extruderID].getID()); edges[extruderID] = new ArrayList<LineSegment>(); csgs[extruderID] = new ArrayList<CSG3D>(); } // Generate all the edges for STLObject i at this z STLObject stlObject = stls.get(stlIndex); Transform3D trans = stlObject.getTransform(); Matrix4d m4 = new Matrix4d(); trans.get(m4); //BranchGroup bg = stlObject.getSTL(); for(int i = 0; i < stlObject.getCount(); i++) { BranchGroup bg1 = stlObject.getSTL(i); Attributes attr = (Attributes)(bg1.getUserData()); atts[attr.getExtruder().getID()] = attr; CSG3D csg = stlObject.getCSG(i); if(csg != null) csgs[attr.getExtruder().getID()].add(csg.transform(m4)); else recursiveSetEdges(attr.getPart(), trans, z, attr, edges); } // Turn them into lists of polygons, one for each extruder, then // turn those into pixelmaps. for(extruderID = 0; extruderID < edges.length; extruderID++) { // Deal with CSG shapes (much simpler and faster). for(int i = 0; i < csgs[extruderID].size(); i++) { csgp = CSG2D.slice(csgs[extruderID].get(i), z); result.add(new BooleanGrid(csgp, rectangles.get(stlIndex), atts[extruderID])); } // Deal with STL-generated edges if(edges[extruderID].size() > 0) { pgl = simpleCull(edges[extruderID]); if(pgl.size() > 0) { // Remove wrinkles pgl = pgl.simplify(Preferences.gridRes()*1.5); // Fix small radii pgl = pgl.arcCompensate(); csgp = pgl.toCSG(Preferences.tiny()); // We use the plan rectangle of the entire stl object to store the bitmap, even though this slice may be // much smaller than the whole. This allows booleans on slices to be computed much more // quickly as each is in the same rectangle so the bit patterns match exactly. But it does use more memory. result.add(new BooleanGrid(csgp, rectangles.get(stlIndex), pgl.polygon(0).getAttributes())); } } } // We may need this later... cache.setSlice(result, layer, stlIndex); return result; } public void destroyLayer() {} /** * Add the edge where the plane z cuts the triangle (p, q, r) (if it does). * Also update the triangulation of the object below the current slice used * for the simulation window. * @param p * @param q * @param r * @param z */ private void addEdge(Point3d p, Point3d q, Point3d r, double z, Attributes att, ArrayList<LineSegment> edges[]) { Point3d odd = null, even1 = null, even2 = null; int pat = 0; //boolean twoBelow = false; if(p.z < z) pat = pat | 1; if(q.z < z) pat = pat | 2; if(r.z < z) pat = pat | 4; switch(pat) { // All above case 0: return; // All below case 7: return; // q, r below, p above case 6: //twoBelow = true; // p below, q, r above case 1: odd = p; even1 = q; even2 = r; break; // p, r below, q above case 5: //twoBelow = true; // q below, p, r above case 2: odd = q; even1 = r; even2 = p; break; // p, q below, r above case 3: //twoBelow = true; // r below, p, q above case 4: odd = r; even1 = p; even2 = q; break; default: Debug.e("addEdge(): the | function doesn't seem to work..."); } // Work out the intersection line segment (e1 -> e2) between the z plane and the triangle even1.sub((Tuple3d)odd); even2.sub((Tuple3d)odd); double t = (z - odd.z)/even1.z; Point2D e1 = new Point2D(odd.x + t*even1.x, odd.y + t*even1.y); //Point3d e3_1 = new Point3d(e1.x(), e1.y(), z); //e1 = new Rr2Point(toGrid(e1.x()), toGrid(e1.y())); e1 = new Point2D(e1.x(), e1.y()); t = (z - odd.z)/even2.z; Point2D e2 = new Point2D(odd.x + t*even2.x, odd.y + t*even2.y); //Point3d e3_2 = new Point3d(e2.x(), e2.y(), z); //e2 = new Rr2Point(toGrid(e2.x()), toGrid(e2.y())); e2 = new Point2D(e2.x(), e2.y()); // Too short? if(!Point2D.same(e1, e2, Preferences.lessGridSquare())) edges[att.getExtruder().getID()].add(new LineSegment(e1, e2, att)); } /** * Run through a Shape3D and set edges from it at plane z * Apply the transform first * @param shape * @param trans * @param z */ private void addAllEdges(Shape3D shape, Transform3D trans, double z, Attributes att, ArrayList<LineSegment> edges[]) { GeometryArray g = (GeometryArray)shape.getGeometry(); Point3d p1 = new Point3d(); Point3d p2 = new Point3d(); Point3d p3 = new Point3d(); Point3d q1 = new Point3d(); Point3d q2 = new Point3d(); Point3d q3 = new Point3d(); if(g.getVertexCount()%3 != 0) { Debug.e("addAllEdges(): shape3D with vertices not a multiple of 3!"); } if(g != null) { for(int i = 0; i < g.getVertexCount(); i+=3) { g.getCoordinate(i, p1); g.getCoordinate(i+1, p2); g.getCoordinate(i+2, p3); trans.transform(p1, q1); trans.transform(p2, q2); trans.transform(p3, q3); addEdge(q1, q2, q3, z, att, edges); } } } /** * Unpack the Shape3D(s) from value and set edges from them * @param value * @param trans * @param z */ private void recursiveSetEdges(Object value, Transform3D trans, double z, Attributes att, ArrayList<LineSegment> edges[]) { if(value instanceof SceneGraphObject) { SceneGraphObject sg = (SceneGraphObject)value; if(sg instanceof Group) { Group g = (Group)sg; java.util.Enumeration<?> enumKids = g.getAllChildren( ); while(enumKids.hasMoreElements()) recursiveSetEdges(enumKids.nextElement(), trans, z, att, edges); } else if (sg instanceof Shape3D) { addAllEdges((Shape3D)sg, trans, z, att, edges); } } } }