/** * Run through a GSG expression looking at its leaves and return a list of the distinct leaves. * Note: leaf and leaf.complement() are not considered distinct. Recursive internal call. * * @param expression * @return */ private void uniqueList_r(ArrayList<RrCSG> list) { switch (op) { case LEAF: RrCSG entry; for (int i = 0; i < list.size(); i++) { entry = list.get(i); if (this == entry || complement() == entry) return; } list.add(this); break; case NULL: case UNIVERSE: Debug.e("uniqueList_r: null or universe at a leaf."); break; case UNION: case INTERSECTION: c1.uniqueList_r(list); c2.uniqueList_r(list); break; default: Debug.e("uniqueList_r: invalid operator."); } return; }
public BooleanGridList offset(LayerRules lc, boolean outline) { boolean foundation = lc.getLayingSupport(); if (outline && foundation) Debug.e("Offsetting a foundation outline!"); BooleanGridList result = new BooleanGridList(); for (int i = 0; i < size(); i++) { Attributes att = attribute(i); if (att == null) Debug.e("BooleanGridList.offset(): null attribute!"); else { Extruder[] es = lc.getPrinter().getExtruders(); Extruder e; int shells; if (foundation) { e = es[0]; // By convention extruder 0 builds the foundation shells = 1; } else { e = att.getExtruder(); shells = e.getShells(); } if (outline) { for (int shell = 0; shell < shells; shell++) result.add(get(i).offset(-((double) shell + 0.5) * e.getExtrusionSize()), att); } else { // Must be a hatch. Only do it if the gap is +ve or we're building the foundation double offSize; if (foundation) offSize = 3; else offSize = -((double) shells + 0.5) * e.getExtrusionSize() + e.getInfillOverlap(); if (e.getExtrusionInfillWidth() > 0 || foundation) // Z valuesn't mattere here result.add(get(i).offset(offSize), att); } } } return result; }
/** * Prune the set to a box * * @param b * @return pruned box as new CSG object */ public RrCSG prune(RrRectangle b) { RrCSG result = this; switch (op) { case LEAF: RrInterval i = hp.value(b); if (i.empty()) Debug.e("RrCSG.prune(RrBox): empty interval!"); else if (i.neg()) result = universe(); else if (i.pos()) result = nothing(); break; case NULL: case UNIVERSE: break; case UNION: result = union(c1.prune(b), c2.prune(b)); break; case INTERSECTION: result = intersection(c1.prune(b), c2.prune(b)); break; default: Debug.e("RrCSG.prune(RrBox): dud op value!"); } return result; }
/** * Convert to a string * * @param result * @param white * @return string representation */ private String toString_r(String result, String white) { switch (op) { case LEAF: result = result + white + hp.toString() + "\n"; break; case NULL: result = result + white + "0\n"; break; case UNIVERSE: result = result + white + "U\n"; break; case UNION: result = result + white + "+\n"; white = white + " "; result = c1.toString_r(result, white); result = c2.toString_r(result, white); break; case INTERSECTION: result = result + white + "&\n"; white = white + " "; result = c1.toString_r(result, white); result = c2.toString_r(result, white); break; default: Debug.e("toString_r(): invalid operator."); } return result; }
/** * The interval value of a box (analagous to point) * * @param b * @return value of a box */ public RrInterval value(RrRectangle b) { RrInterval result; switch (op) { case LEAF: result = hp.value(b); break; case NULL: result = new RrInterval(1, 1.01); // Is this clever? Or dumb? break; case UNIVERSE: result = new RrInterval(-1.01, -1); // Ditto. break; case UNION: result = RrInterval.min(c1.value(b), c2.value(b)); break; case INTERSECTION: result = RrInterval.max(c1.value(b), c2.value(b)); break; default: Debug.e("value(RrBox): invalid operator."); result = new RrInterval(); } return result; }
/** * "Potential" value of a point; i.e. a membership test -ve means inside; 0 means on the surface; * +ve means outside * * @param p * @return potential value of a point */ public double value(Rr2Point p) { double result = 1; // RrCSG c = leaf(p); switch (op) { case LEAF: result = hp.value(p); break; case NULL: result = 1; break; case UNIVERSE: result = -1; break; case UNION: result = Math.min(c1.value(p), c2.value(p)); break; case INTERSECTION: result = Math.max(c1.value(p), c2.value(p)); break; default: Debug.e("RrCSG.value(): dud operator."); } return result; }
/** * leaf find the half-plane that generates the value for a point * * @param p * @return leaf? */ public RrCSG leaf(Rr2Point p) { RrCSG result, r1, r2; switch (op) { case LEAF: result = this; break; case NULL: result = this; break; case UNIVERSE: result = this; break; case UNION: r1 = c1.leaf(p); r2 = c2.leaf(p); if (r1.value(p) < r2.value(p)) return r1; else return r2; case INTERSECTION: r1 = c1.leaf(p); r2 = c2.leaf(p); if (r1.value(p) > r2.value(p)) return r1; else return r2; default: Debug.e("leaf(Rr2Point): invalid operator."); result = nothing(); } return result; }
/** * Offset by a distance (+ve or -ve) TODO: this should keep track of complements * * @param d * @return offset CSG object by distance d */ public RrCSG offset(double d) { RrCSG result; switch (op) { case LEAF: result = new RrCSG(hp.offset(d)); break; case NULL: case UNIVERSE: result = this; break; case UNION: result = union(c1.offset(d), c2.offset(d)); break; case INTERSECTION: result = intersection(c1.offset(d), c2.offset(d)); break; default: Debug.e("offset(): invalid operator."); result = nothing(); } return result; }
/** * Replace duplicate of leaf with leaf itself TODO: this should also use known complements * * @param leaf * @param tolerance */ private void replaceAllSameLeaves(RrCSG leaf, double tolerance) { int same; switch (op) { case LEAF: case NULL: case UNIVERSE: // System.out.println("replace_all_same_leaves(): at a leaf!"); break; case UNION: case INTERSECTION: RrHalfPlane hp = leaf.hp; if (c1.op == RrCSGOp.LEAF) { same = RrHalfPlane.same(hp, c1.hp, tolerance); if (same == 0) c1 = leaf; if (same == -1) c1 = leaf.complement(); } else c1.replaceAllSameLeaves(leaf, tolerance); if (c2.op == RrCSGOp.LEAF) { same = RrHalfPlane.same(hp, c2.hp, tolerance); if (same == 0) c2 = leaf; if (same == -1) c2 = leaf.complement(); } else c2.replaceAllSameLeaves(leaf, tolerance); break; default: Debug.e("replace_all_same(): invalid operator."); } }
/** * Deep copy * * @param c */ public RrCSG(RrCSG c) { if (c == u || c == n) Debug.e("RrCSG deep copy: copying null or universal set."); if (c.hp != null) hp = new RrHalfPlane(c.hp); else hp = null; if (c.c1 != null) c1 = new RrCSG(c.c1); else c1 = null; if (c.c2 != null) c2 = new RrCSG(c.c2); else c2 = null; comp = null; // This'll be built if it's needed op = c.op; complexity = c.complexity; }
/** * Replace duplicate of all leaves with the last instance of each; also link up complements. * * @param root * @param tolerance * @return simplified CSG object */ private void simplify_r(RrCSG root, double tolerance) { switch (op) { case LEAF: root.replaceAllSameLeaves(this, tolerance); break; case NULL: case UNIVERSE: // System.out.println("simplify_r(): at a leaf!"); break; case UNION: case INTERSECTION: c1.simplify_r(root, tolerance); c2.simplify_r(root, tolerance); break; default: Debug.e("simplify_r(): invalid operator."); } }
/** * Lazy evaluation for complement. * * @return complement */ public RrCSG complement() { if (comp != null) return comp; RrCSG result; switch (op) { case LEAF: result = new RrCSG(hp.complement()); break; case NULL: return universe(); case UNIVERSE: return nothing(); case UNION: result = intersection(c1.complement(), c2.complement()); break; case INTERSECTION: result = union(c1.complement(), c2.complement()); break; default: Debug.e("complement(): invalid operator."); return nothing(); } // Remember, so we don't have to do it again. // (I do hope that the Java garbage collector is up to // spotting this deadly embrace, or we - I mean it - has // a memory leak.) // It turned out it was dumb. Hence addition of destroy() above... comp = result; result.comp = this; return comp; }
/** * Make a list with a single entry: the union of all the entries. Set its attributes to that of * extruder 0 in the extruder list. * * @param a * @return */ public BooleanGridList union(Extruder[] es) { BooleanGridList result = new BooleanGridList(); if (size() <= 0) return result; BooleanGrid contents = get(0); Attributes att = attribute(0); Boolean foundAttribute0 = false; if (att.getExtruder() == es[0]) foundAttribute0 = true; for (int i = 1; i < size(); i++) { if (!foundAttribute0) { if (attribute(i).getExtruder() == es[0]) { att = attribute(i); foundAttribute0 = true; } } contents = BooleanGrid.union(contents, get(i)); } if (!foundAttribute0) Debug.e("RrCSGPolygonList.union(): Attribute of extruder 0 not found."); result.add(contents, att); return result; }
/** * Plot a polygon * * @throws IOException * @throws ReprapException * @return */ private void plot(RrPolygon p, boolean firstOneInLayer) throws ReprapException, IOException { Attributes att = p.getAttributes(); Printer printer = layerConditions.getPrinter(); double outlineFeedrate = att.getExtruder().getOutlineFeedrate(); double infillFeedrate = att.getExtruder().getInfillFeedrate(); boolean acc = att.getExtruder().getMaxAcceleration() > 0; // Preferences.loadGlobalBool("Accelerating"); // int leng = p.size(); if (p.size() <= 1) { startNearHere = null; return; } // If the length of the plot is <0.05mm, don't bother with it. // This will not spot an attempt to plot 10,000 points in 1mm. double plotDist = 0; Rr2Point lastPoint = p.point(0); for (int i = 1; i < p.size(); i++) { Rr2Point n = p.point(i); plotDist += Rr2Point.d(lastPoint, n); lastPoint = n; } if (plotDist < Preferences.machineResolution() * 0.5) { Debug.d("Rejected line with " + p.size() + " points, length: " + plotDist); startNearHere = null; return; } printer.selectExtruder(att); // Don't do these with mid-point starting // if(p.isClosed() && att.getExtruder().incrementedStart()) // p = p.incrementedStart(layerConditions); // else if(p.isClosed() && att.getExtruder().randomStart()) // p = p.randomStart(); int stopExtruding = p.size() + 10; int stopValve = stopExtruding; double extrudeBackLength = att.getExtruder().getExtrusionOverRun(); double valveBackLength = att.getExtruder().getValveOverRun(); if (extrudeBackLength >= valveBackLength) { if (extrudeBackLength > 0) stopExtruding = p.backStep(extrudeBackLength); if (valveBackLength > 0) stopValve = p.backStep(valveBackLength); } else { if (valveBackLength > 0) stopValve = p.backStep(valveBackLength); if (extrudeBackLength > 0) stopExtruding = p.backStep(extrudeBackLength); } if (printer.isCancelled()) return; // If getMinLiftedZ() is negative, never lift the head Boolean lift = att.getExtruder().getMinLiftedZ() >= 0; if (acc) p.setSpeeds( att.getExtruder().getSlowXYFeedrate(), p.isClosed() ? outlineFeedrate : infillFeedrate, att.getExtruder().getMaxAcceleration()); if (extrudeBackLength <= 0) stopExtruding = Integer.MAX_VALUE; else if (acc) stopExtruding = p.findBackPoint(extrudeBackLength); if (valveBackLength <= 0) stopValve = Integer.MAX_VALUE; else if (acc) stopValve = p.findBackPoint(valveBackLength); currentFeedrate = att.getExtruder().getFastXYFeedrate(); singleMove(p.point(0)); if (acc) currentFeedrate = p.speed(0); else { if (p.isClosed()) { currentFeedrate = outlineFeedrate; } else { currentFeedrate = infillFeedrate; } } plot(p.point(0), p.point(1), false, false); // Print any lead-in. printer.printStartDelay(firstOneInLayer); boolean extrudeOff = false; boolean valveOff = false; boolean oldexoff; // if(p.isClosed()) // { // for(int j = 1; j <= p.size(); j++) // { // int i = j%p.size(); // Rr2Point next = p.point((j+1)%p.size()); // // if (printer.isCancelled()) // { // printer.stopMotor(); // singleMove(posNow()); // move(posNow(), posNow(), lift, true, true); // return; // } // if(acc) // currentFeedrate = p.speed(i); // // oldexoff = extrudeOff; // extrudeOff = j > stopExtruding || j == p.size(); // valveOff = j > stopValve || j == p.size(); // // plot(p.point(i), next, extrudeOff, valveOff); // if(oldexoff ^ extrudeOff) // printer.printEndReverse(); // } // } else // { for (int i = 1; i < p.size(); i++) { Rr2Point next = p.point((i + 1) % p.size()); if (printer.isCancelled()) { printer.stopMotor(); singleMove(posNow()); move(posNow(), posNow(), lift, lift, true); return; } if (acc) currentFeedrate = p.speed(i); oldexoff = extrudeOff; extrudeOff = i > stopExtruding || i == p.size() - 1; valveOff = i > stopValve || i == p.size() - 1; // if(oldexoff ^ extrudeOff) // printer.printEndReverse(); plot(p.point(i), next, extrudeOff, valveOff); if (oldexoff ^ extrudeOff) printer.printEndReverse(); } // } if (p.isClosed()) move(p.point(0), p.point(0), false, false, true); move(posNow(), posNow(), lift, lift, true); // The last point is near where we want to start next if (p.isClosed()) startNearHere = p.point(0); else startNearHere = p.point(p.size() - 1); if (simulationPlot != null) { RrPolygonList pgl = new RrPolygonList(); pgl.add(p); simulationPlot.add(pgl); } }
/** @throws Exception */ public void produce() throws Exception { int movementSpeedZ; boolean subtractive; boolean interLayerCooling; try { subtractive = Preferences.loadGlobalBool("Subtractive"); movementSpeedZ = Preferences.loadGlobalInt("MovementSpeedZ(0..255)"); } catch (Exception ex) { movementSpeedZ = 212; subtractive = false; System.err.println( "Warning: could not load Z MovementSpeed and/or subtractive flag, using default"); } try { interLayerCooling = Preferences.loadGlobalBool("InterLayerCooling"); } catch (Exception ex) { interLayerCooling = true; System.err.println("Warning: could not load InterLayerCooling flag, using default"); } reprap.setSpeedZ(movementSpeedZ); Debug.d("Intialising reprap"); reprap.initialise(); Debug.d("Selecting material 0"); reprap.selectExtruder(0); Debug.d("Setting temperature"); reprap.getExtruder().heatOn(); // A "warmup" segment to get things in working order if (!subtractive) { reprap.setSpeed(reprap.getExtruder().getXYSpeed()); reprap.moveTo(1, 1, 0, false, false); // Workaround to get the thing to start heating up reprap.printTo(1, 1, 0, true); if (reprap.getExtruder().getNozzleClearTime() <= 0) { Debug.d("Printing warmup segments, moving to (1,1)"); // Take it slow and easy. Debug.d("Printing warmup segments, printing to (1,60)"); reprap.moveTo(1, 25, 0, false, false); reprap.setSpeed( LinePrinter.speedFix( reprap.getExtruder().getXYSpeed(), reprap.getExtruder().getOutlineSpeed())); reprap.printTo(1, 60, 0, false); Debug.d("Printing warmup segments, printing to (3,60)"); reprap.printTo(3, 60, 0, false); Debug.d("Printing warmup segments, printing to (3,25)"); reprap.printTo(3, 25, 0, true); Debug.d("Warmup complete"); } reprap.setSpeed(reprap.getFastSpeed()); } // This should now split off layers one at a time // and pass them to the LayerProducer. boolean isEvenLayer = true; STLSlice stlc; double zMax; bld.mouseToWorld(); stlc = new STLSlice(bld.getSTLs()); zMax = stlc.maxZ(); double startZ; double endZ; double stepZ; if (subtractive) { // Subtractive construction works from the top, downwards startZ = zMax; endZ = 0; stepZ = -reprap.getExtruder().getExtrusionHeight(); reprap.setZManual(startZ); } else { // Normal constructive fabrication, start at the bottom and work up. startZ = 0; endZ = zMax; stepZ = reprap.getExtruder().getExtrusionHeight(); } int layerNumber = 0; for (double z = startZ; subtractive ? z > endZ : z < endZ; z += stepZ) { if (reprap.isCancelled()) break; Debug.d("Commencing layer at " + z); // Change Z height reprap.moveTo(reprap.getX(), reprap.getY(), z, false, false); if (reprap.isCancelled()) break; // Pretend we've just finished a layer first time; // All other times we really will have. if (layerNumber == 0 || interLayerCooling) { reprap.finishedLayer(layerNumber); reprap.betweenLayers(layerNumber); } RrCSGPolygonList slice = stlc.slice(z + reprap.getExtruder().getExtrusionHeight() * 0.5); BranchGroup lowerShell = stlc.getBelow(); LayerProducer layer = null; if (slice.size() > 0) layer = new LayerProducer( reprap, z, slice, lowerShell, isEvenLayer ? evenHatchDirection : oddHatchDirection, layerNumber, endZ); if (layerNumber == 0 || interLayerCooling) { reprap.startingLayer(layerNumber); } if (reprap.isCancelled()) break; if (layer != null) { layer.plot(); layer.destroy(); } layer = null; slice.destroy(); stlc.destroyLayer(); isEvenLayer = !isEvenLayer; layerNumber++; } if (subtractive) reprap.moveTo(0, 0, startZ, true, true); else reprap.moveTo(0, 0, reprap.getZ(), true, true); reprap.terminate(); }
/** * This takes a complicated expression assumed to contain multiple instances of leafA and leafB * and returns the equivalent CSG expression involving at most leafA and leafB once (except for * non-manifold shapes). * * @param leafA * @param leafB * @return equivalent CSG expression involving at most leafA and leafB once */ private RrCSG crossCategorise(RrCSG leafA, RrCSG leafB) { RrHalfPlane a = leafA.plane(); RrHalfPlane b = leafB.plane(); Rr2Point an = a.normal(); Rr2Point bn = b.normal(); Rr2Point v02 = Rr2Point.add(an, bn); Rr2Point v31 = Rr2Point.sub(bn, an); Rr2Point x, x0, x1, x2, x3; int category = 0; try { x = a.cross_point(b); v02 = v02.norm(); v31 = v31.norm(); x2 = Rr2Point.add(x, v02); x0 = Rr2Point.sub(x, v02); x1 = Rr2Point.add(x, v31); x3 = Rr2Point.sub(x, v31); if (value(x0) <= 0) category |= 1; if (value(x1) <= 0) category |= 2; if (value(x2) <= 0) category |= 4; if (value(x3) <= 0) category |= 8; switch (category) { case 0: return nothing(); case 1: return intersection(leafA, leafB); case 2: return intersection(leafA, leafB.complement()); case 3: return leafA; case 4: return intersection(leafA.complement(), leafB.complement()); case 5: Debug.e("RrCSG crossCategorise: non-manifold shape (case 0101)!"); return union( intersection(leafA, leafB), intersection(leafA.complement(), leafB.complement())); case 6: return leafB.complement(); case 7: return union(leafA, leafB.complement()); case 8: return intersection(leafA.complement(), leafB); case 9: return leafB; case 10: Debug.e("RrCSG crossCategorise: non-manifold shape (case 1010)!"); return union( RrCSG.intersection(leafA.complement(), leafB), intersection(leafA, leafB.complement())); case 11: return union(leafA, leafB); case 12: return leafA.complement(); case 13: return union(leafA.complement(), leafB); case 14: return union(leafA.complement(), leafB.complement()); case 15: return universe(); default: Debug.e("RrCSG crossCategorise: bitwise | doesn't seem to work..."); return this; } } catch (Exception e) { // leafA and leafB are parallel x0 = Rr2Point.mul(Rr2Point.add(a.pLine().origin(), b.pLine().origin()), 0.5); x1 = Rr2Point.mul(Rr2Point.sub(a.pLine().origin(), b.pLine().origin()), 3); x2 = Rr2Point.add(x0, x1); x1 = Rr2Point.sub(x0, x1); if (value(x0) <= 0) category |= 1; if (value(x1) <= 0) category |= 2; if (value(x2) <= 0) category |= 4; if (leafA.value(x0) <= 0) leafA = leafA.complement(); if (leafB.value(x0) <= 0) leafB = leafB.complement(); switch (category) { case 0: return nothing(); case 1: return intersection(leafA.complement(), leafB.complement()); case 2: return leafB; case 3: return leafA.complement(); case 4: return leafA; case 5: return union(leafA, leafB); case 6: return leafB.complement(); case 7: return universe(); default: Debug.e("RrCSG crossCategorise: bitwise | doesn't seem to work..."); return this; } } }