void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_mm3_per_mm) { if (currentPosition.x == x && currentPosition.y == y && currentPosition.z == z) return; #ifdef ASSERT_INSANE_OUTPUT assert(speed < 200 && speed > 1); // normal F values occurring in UM2 gcode (this code should not be compiled for release) assert(currentPosition != no_point3); assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(300)); // no crazy positions (this code should not be compiled for release) #endif //ASSERT_INSANE_OUTPUT if (extrusion_mm3_per_mm < 0) logWarning("Warning! Negative extrusion move!"); if (flavor == EGCodeFlavor::BFB) { writeMoveBFB(x, y, z, speed, extrusion_mm3_per_mm); return; } double extrusion_per_mm = extrusion_mm3_per_mm; if (!is_volumatric) { extrusion_per_mm = extrusion_mm3_per_mm / extruder_attr[current_extruder].filament_area; } Point gcode_pos = getGcodePos(x,y, current_extruder); if (extrusion_mm3_per_mm > 0.000001) { Point3 diff = Point3(x,y,z) - getPosition(); if (isZHopped > 0) { *output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z) << "\n"; isZHopped = 0; } double prime_volume = extruder_attr[current_extruder].prime_volume; current_e_value += (is_volumatric) ? prime_volume : prime_volume / extruder_attr[current_extruder].filament_area; if (extruder_attr[current_extruder].retraction_e_amount_current) { if (firmware_retract) { // note that BFB is handled differently *output_stream << "G11\n"; //Assume default UM2 retraction settings. if (prime_volume > 0) { *output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n"; currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed; } estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), 25.0); } else { current_e_value += extruder_attr[current_extruder].retraction_e_amount_current; *output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n"; currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed; estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed); } if (getCurrentExtrudedVolume() > 10000.0) //According to https://github.com/Ultimaker/CuraEngine/issues/14 having more then 21m of extrusion causes inaccuracies. So reset it every 10m, just to be sure. { resetExtrusionValue(); } extruder_attr[current_extruder].retraction_e_amount_current = 0.0; } else if (prime_volume > 0.0) { current_e_value += prime_volume; *output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n"; currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed; estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed); } extruder_attr[current_extruder].prime_volume = 0.0; current_e_value += extrusion_per_mm * diff.vSizeMM(); *output_stream << "G1"; } else { *output_stream << "G0"; if (commandSocket) { // we should send this travel as a non-retraction move cura::Polygons travelPoly; PolygonRef travel = travelPoly.newPoly(); travel.add(Point(currentPosition.x, currentPosition.y)); travel.add(Point(x, y)); commandSocket->sendPolygons(extruder_attr[current_extruder].retraction_e_amount_current ? PrintFeatureType::MoveRetraction : PrintFeatureType::MoveCombing, layer_nr, travelPoly, extruder_attr[current_extruder].retraction_e_amount_current ? MM2INT(0.2) : MM2INT(0.1)); } } if (currentSpeed != speed) { *output_stream << " F" << (speed * 60); currentSpeed = speed; } *output_stream << std::setprecision(3) << " X" << INT2MM(gcode_pos.X) << " Y" << INT2MM(gcode_pos.Y); if (z != currentPosition.z + isZHopped) *output_stream << " Z" << INT2MM(z + isZHopped); if (extrusion_mm3_per_mm > 0.000001) *output_stream << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value; *output_stream << "\n"; currentPosition = Point3(x, y, z); estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), speed); }
void generateTroctInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, int posZ) { Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100); PointMatrix matrix(rotation); outline.applyMatrix(matrix); AABB boundary(outline); // ignore infill for areas smaller than line spacing if((abs(boundary.min.X - boundary.max.X) + abs(boundary.min.Y - boundary.max.Y)) < lineSpacing){ return; } // fix to normalise against diagonal infill lineSpacing = lineSpacing * 2; uint64_t Zscale = SQRT2MUL(lineSpacing); int offset = abs(posZ % (Zscale) - (Zscale/2)) - (Zscale/4); boundary.min.X = ((boundary.min.X / lineSpacing) - 1) * lineSpacing; boundary.min.Y = ((boundary.min.Y / lineSpacing) - 1) * lineSpacing; unsigned int lineCountX = (boundary.max.X - boundary.min.X + (lineSpacing - 1)) / lineSpacing; unsigned int lineCountY = (boundary.max.Y - boundary.min.Y + (lineSpacing - 1)) / lineSpacing; int rtMod = int(rotation / 90) % 2; // with an odd number of lines, sides need to be swapped around if(rtMod == 1){ rtMod = (lineCountX + int(rotation / 90)) % 2; } // draw non-horizontal walls of octohedrons Polygons po; PolygonRef p = po.newPoly(); for(unsigned int ly=0; ly < lineCountY;){ for(size_t it = 0; it < 2; ly++, it++){ int side = (2*((ly + it + rtMod) % 2) - 1); int y = (ly * lineSpacing) + boundary.min.Y + lineSpacing / 2 - (offset/2 * side); int x = boundary.min.X-(offset/2); if(it == 1){ x = (lineCountX * (lineSpacing)) + boundary.min.X + lineSpacing / 2 - (offset/2); } p.add(Point(x,y)); for(unsigned int lx=0; lx < lineCountX; lx++){ if(it == 1){ side = (2*((lx + ly + it + rtMod + lineCountX) % 2) - 1); y = (ly * lineSpacing) + boundary.min.Y + lineSpacing / 2 + (offset/2 * side); x = ((lineCountX - lx - 1) * lineSpacing) + boundary.min.X + lineSpacing / 2; p.add(Point(x+lineSpacing-abs(offset/2), y)); p.add(Point(x+abs(offset/2), y)); } else { side = (2*((lx + ly + it + rtMod) % 2) - 1); y = (ly * lineSpacing) + boundary.min.Y + lineSpacing / 2 + (offset/2 * side); x = (lx * lineSpacing) + boundary.min.X + lineSpacing / 2; p.add(Point(x+abs(offset/2), y)); p.add(Point(x+lineSpacing-abs(offset/2), y)); } } x = (lineCountX * lineSpacing) + boundary.min.X + lineSpacing / 2 - (offset/2); if(it == 1){ x = boundary.min.X-(offset/2); } y = (ly * lineSpacing) + boundary.min.Y + lineSpacing / 2 - (offset/2 * side); p.add(Point(x,y)); } } // Generate tops / bottoms of octohedrons if(abs((abs(offset) - Zscale/4)) < (extrusionWidth/2)){ uint64_t startLine = (offset < 0) ? 0 : 1; uint64_t coverWidth = OCTSLEN(lineSpacing); vector<Point> points; for(size_t xi = 0; xi < (lineCountX+1); xi++){ for(size_t yi = 0; yi < (lineCountY); yi += 2){ points.push_back(Point(boundary.min.X + OCTDLEN(lineSpacing) + (xi - startLine + rtMod) * lineSpacing, boundary.min.Y + OCTDLEN(lineSpacing) + (yi + (xi%2)) * lineSpacing + extrusionWidth/2)); } } uint64_t order = 0; for(Point pp : points){ PolygonRef p = po.newPoly(); for(size_t yi = 0; yi <= coverWidth; yi += extrusionWidth) { if(order == 0){ p.add(Point(pp.X, pp.Y + yi)); p.add(Point(pp.X + coverWidth + extrusionWidth, pp.Y + yi)); } else { p.add(Point(pp.X + coverWidth + extrusionWidth, pp.Y + yi)); p.add(Point(pp.X, pp.Y + yi)); } order = (order + 1) % 2; } } } // intersect with outline polygon(s) Polygons pi = po.intersection(outline); // Hack to add intersection to result. There doesn't seem // to be a direct way to do this for(unsigned int polyNr=0; polyNr < pi.size(); polyNr++) { PolygonRef p = result.newPoly(); // = result.newPoly() for(unsigned int i=0; i < pi[polyNr].size(); i++) { Point p0 = pi[polyNr][i]; p.add(matrix.unapply(Point(p0.X,p0.Y))); } } }
void AreaSupport::handleWallStruts( Polygons& supportLayer_this, int supportMinAreaSqrt, int supportTowerDiameter ) { for (unsigned int p = 0; p < supportLayer_this.size(); p++) { PolygonRef poly = supportLayer_this[p]; if (poly.size() < 6) // might be a single wall { PolygonRef poly = supportLayer_this[p]; int best = -1; int best_length2 = -1; for (unsigned int i = 0; i < poly.size(); i++) { int length2 = vSize2(poly[i] - poly[(i+1) % poly.size()]); if (length2 > best_length2) { best = i; best_length2 = length2; } } if (best_length2 < supportMinAreaSqrt * supportMinAreaSqrt) break; // this is a small area, not a wall! // an estimate of the width of the area int width = sqrt( poly.area() * poly.area() / best_length2 ); // sqrt (a^2 / l^2) instead of a / sqrt(l^2) // add square tower (strut) in the middle of the wall if (width < supportMinAreaSqrt) { Point mid = (poly[best] + poly[(best+1) % poly.size()] ) / 2; Polygons struts; PolygonRef strut = struts.newPoly(); strut.add(mid + Point( supportTowerDiameter/2, supportTowerDiameter/2)); strut.add(mid + Point(-supportTowerDiameter/2, supportTowerDiameter/2)); strut.add(mid + Point(-supportTowerDiameter/2, -supportTowerDiameter/2)); strut.add(mid + Point( supportTowerDiameter/2, -supportTowerDiameter/2)); supportLayer_this = supportLayer_this.unionPolygons(struts); } } } }
void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_mm3_per_mm) { if (currentPosition.x == x && currentPosition.y == y && currentPosition.z == z) return; assert(speed < 200 && speed > 1); // normal F values occurring in UM2 gcode (this code should not be compiled for release) assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(300)); // no crazy positions (this code should not be compiled for release) if (extrusion_mm3_per_mm < 0) logWarning("Warning! Negative extrusion move!"); double extrusion_per_mm = extrusion_mm3_per_mm; if (!is_volumatric) { extrusion_per_mm = extrusion_mm3_per_mm / getFilamentArea(current_extruder); } Point gcode_pos = getGcodePos(x,y, current_extruder); if (flavor == EGCodeFlavor::BFB) { //For Bits From Bytes machines, we need to handle this completely differently. As they do not use E values but RPM values. float fspeed = speed * 60; float rpm = extrusion_per_mm * speed * 60; const float mm_per_rpm = 4.0; //All BFB machines have 4mm per RPM extrusion. rpm /= mm_per_rpm; if (rpm > 0) { if (isRetracted) { if (currentSpeed != double(rpm)) { //fprintf(f, "; %f e-per-mm %d mm-width %d mm/s\n", extrusion_per_mm, lineWidth, speed); //fprintf(f, "M108 S%0.1f\r\n", rpm); *output_stream << "M108 S" << std::setprecision(1) << rpm << "\r\n"; currentSpeed = double(rpm); } //Add M101 or M201 to enable the proper extruder. *output_stream << "M" << int((current_extruder + 1) * 100 + 1) << "\r\n"; isRetracted = false; } //Fix the speed by the actual RPM we are asking, because of rounding errors we cannot get all RPM values, but we have a lot more resolution in the feedrate value. // (Trick copied from KISSlicer, thanks Jonathan) fspeed *= (rpm / (roundf(rpm * 100) / 100)); //Increase the extrusion amount to calculate the amount of filament used. Point3 diff = Point3(x,y,z) - getPosition(); extrusion_amount += extrusion_per_mm * diff.vSizeMM(); }else{ //If we are not extruding, check if we still need to disable the extruder. This causes a retraction due to auto-retraction. if (!isRetracted) { *output_stream << "M103\r\n"; isRetracted = true; } } *output_stream << std::setprecision(3) << "G1 X" << INT2MM(gcode_pos.X) << " Y" << INT2MM(gcode_pos.Y) << " Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << "\r\n"; } else { //Normal E handling. if (extrusion_mm3_per_mm > 0.000001) { Point3 diff = Point3(x,y,z) - getPosition(); if (isZHopped > 0) { // TinyG G1: Straight feed *output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z) << "\n"; isZHopped = 0; } extrusion_amount += (is_volumatric) ? last_coasted_amount_mm3 : last_coasted_amount_mm3 / getFilamentArea(current_extruder); if (isRetracted) { if (flavor == EGCodeFlavor::ULTIGCODE || flavor == EGCodeFlavor::REPRAP_VOLUMATRIC) { *output_stream << "G11\n"; //TODO try this code and see what happens //Assume default UM2 retraction settings. if (last_coasted_amount_mm3 > 0) { *output_stream << "G1 F" << (retractionPrimeSpeed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount << "\n"; } estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), 25.0); }else{ // TinyG checked *output_stream << "G1 F" << (retractionPrimeSpeed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount << "\n"; currentSpeed = retractionPrimeSpeed; estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), currentSpeed); } if (getExtrusionAmountMM3(current_extruder) > 10000.0) //According to https://github.com/Ultimaker/CuraEngine/issues/14 having more then 21m of extrusion causes inaccuracies. So reset it every 10m, just to be sure. resetExtrusionValue(); isRetracted = false; } else { if (last_coasted_amount_mm3 > 0) { *output_stream << "G1 F" << (retractionPrimeSpeed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount << "\n"; estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), currentSpeed); } } last_coasted_amount_mm3 = 0; extrusion_amount += extrusion_per_mm * diff.vSizeMM(); // TinyG TODO: add one axis *output_stream << "G1"; }else{ *output_stream << "G0"; if (commandSocket) { // we should send this travel as a non-retraction move cura::Polygons travelPoly; PolygonRef travel = travelPoly.newPoly(); travel.add(Point(currentPosition.x, currentPosition.y)); travel.add(Point(x, y)); commandSocket->sendPolygons(isRetracted ? MoveRetractionType : MoveCombingType, layer_nr, travelPoly, isRetracted ? MM2INT(0.2) : MM2INT(0.1)); } } if (currentSpeed != speed) { *output_stream << " F" << (speed * 60); currentSpeed = speed; } *output_stream << std::setprecision(3) << " X" << INT2MM(gcode_pos.X) << " Y" << INT2MM(gcode_pos.Y); if (z != currentPosition.z) *output_stream << " Z" << INT2MM(z + isZHopped); if (extrusion_mm3_per_mm > 0.000001) *output_stream << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount; *output_stream << "\n"; } currentPosition = Point3(x, y, z); startPosition = currentPosition; estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), speed); }
void PathOrderOptimizer::optimize() { std::vector<bool> picked; for(unsigned int i=0;i<polygons.size(); i++) { int best = -1; float bestDist = 0xFFFFFFFFFFFFFFFFLL; PolygonRef poly = polygons[i]; for(unsigned int j=0; j<poly.size(); j++) { float dist = vSize2f(poly[j] - startPoint); if (dist < bestDist) { best = j; bestDist = dist; } } polyStart.push_back(best); picked.push_back(false); } Point p0 = startPoint; for(unsigned int n=0; n<polygons.size(); n++) { int best = -1; float bestDist = 0xFFFFFFFFFFFFFFFFLL; for(unsigned int i=0;i<polygons.size(); i++) { if (picked[i] || polygons[i].size() < 1) continue; if (polygons[i].size() == 2) { float dist = vSize2f(polygons[i][0] - p0); if (dist < bestDist) { best = i; bestDist = dist; polyStart[i] = 0; } dist = vSize2f(polygons[i][1] - p0); if (dist < bestDist) { best = i; bestDist = dist; polyStart[i] = 1; } }else{ float dist = vSize2f(polygons[i][polyStart[i]] - p0); if (dist < bestDist) { best = i; bestDist = dist; } } } if (best > -1) { if (polygons[best].size() == 2) { p0 = polygons[best][(polyStart[best] + 1) % 2]; }else{ p0 = polygons[best][polyStart[best]]; } picked[best] = true; polyOrder.push_back(best); } } p0 = startPoint; for(unsigned int n=0; n<polyOrder.size(); n++) { int nr = polyOrder[n]; int best = -1; float bestDist = 0xFFFFFFFFFFFFFFFFLL; for(unsigned int i=0;i<polygons[nr].size(); i++) { float dist = vSize2f(polygons[nr][i] - p0); if (dist < bestDist) { best = i; bestDist = dist; } } polyStart[nr] = best; if (polygons[nr].size() <= 2) { p0 = polygons[nr][(best + 1) % 2]; }else{ p0 = polygons[nr][best]; } } }
void generateZigZagIninfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation) { if (in_outline.size() == 0) return; Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2); if (outline.size() == 0) return; PointMatrix matrix(rotation); outline.applyMatrix(matrix); auto addLine = [&](Point from, Point to) { PolygonRef p = result.newPoly(); p.add(matrix.unapply(from)); p.add(matrix.unapply(to)); }; AABB boundary(outline); int scanline_min_idx = boundary.min.X / lineSpacing; int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx; std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments for(int n=0; n<lineCount; n++) cutList.push_back(std::vector<int64_t>()); for(unsigned int polyNr=0; polyNr < outline.size(); polyNr++) { std::vector<Point> firstBoundarySegment; std::vector<Point> boundarySegment; bool isFirstBoundarySegment = true; bool firstBoundarySegmentEndsInEven; bool isEvenScanSegment = false; Point p0 = outline[polyNr][outline[polyNr].size()-1]; for(unsigned int i=0; i < outline[polyNr].size(); i++) { Point p1 = outline[polyNr][i]; int64_t xMin = p1.X, xMax = p0.X; if (xMin == xMax) { p0 = p1; continue; } if (xMin > xMax) { xMin = p0.X; xMax = p1.X; } int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ... int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number) int direction = 1; if (p0.X > p1.X) { direction = -1; scanline_idx1 += 1; // only consider the scanlines in between the scansegments } else scanline_idx0 += 1; // only consider the scanlines in between the scansegments if (isFirstBoundarySegment) firstBoundarySegment.push_back(p0); else boundarySegment.push_back(p0); for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction) { int x = scanline_idx * lineSpacing; int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X); cutList[scanline_idx - scanline_min_idx].push_back(y); bool last_isEvenScanSegment = isEvenScanSegment; if (scanline_idx % 2 == 0) isEvenScanSegment = true; else isEvenScanSegment = false; if (!isFirstBoundarySegment) { if (last_isEvenScanSegment && !isEvenScanSegment) { // add whole boundarySegment (including the just obtained point) for (unsigned int p = 1; p < boundarySegment.size(); p++) { addLine(boundarySegment[p-1], boundarySegment[p]); } addLine(boundarySegment[boundarySegment.size()-1], Point(x,y)); boundarySegment.clear(); } else if (isEvenScanSegment) // we are either in an end piece or an uneven boundary segment { boundarySegment.clear(); boundarySegment.emplace_back(x,y); } else boundarySegment.clear(); } if (isFirstBoundarySegment) { firstBoundarySegment.emplace_back(x,y); firstBoundarySegmentEndsInEven = isEvenScanSegment; isFirstBoundarySegment = false; boundarySegment.emplace_back(x,y); } } if (!isFirstBoundarySegment && isEvenScanSegment) boundarySegment.push_back(p1); p0 = p1; } if (!isFirstBoundarySegment && isEvenScanSegment && !firstBoundarySegmentEndsInEven) { for (unsigned int i = 1; i < firstBoundarySegment.size() ; i++) addLine(firstBoundarySegment[i-1], firstBoundarySegment[i]); } } addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth); }
void SlicerLayer::makePolygons(OptimizedVolume* ov, bool keepNoneClosed, bool extensiveStitching) { for(unsigned int startSegment=0; startSegment < segmentList.size(); startSegment++) { if (segmentList[startSegment].addedToPolygon) continue; Polygon poly; poly.add(segmentList[startSegment].start); unsigned int segmentIndex = startSegment; bool canClose; while(true) { canClose = false; segmentList[segmentIndex].addedToPolygon = true; Point p0 = segmentList[segmentIndex].end; poly.add(p0); int nextIndex = -1; OptimizedFace* face = &ov->faces[segmentList[segmentIndex].faceIndex]; for(unsigned int i=0;i<3;i++) { if (face->touching[i] > -1 && faceToSegmentIndex.find(face->touching[i]) != faceToSegmentIndex.end()) { Point p1 = segmentList[faceToSegmentIndex[face->touching[i]]].start; Point diff = p0 - p1; if (shorterThen(diff, MM2INT(0.01))) { if (faceToSegmentIndex[face->touching[i]] == (int)startSegment) canClose = true; if (segmentList[faceToSegmentIndex[face->touching[i]]].addedToPolygon) continue; nextIndex = faceToSegmentIndex[face->touching[i]]; } } } if (nextIndex == -1) break; segmentIndex = nextIndex; } if (canClose) polygonList.add(poly); else openPolygonList.add(poly); } //Clear the segmentList to save memory, it is no longer needed after this point. segmentList.clear(); //Connecting polygons that are not closed yet, as models are not always perfect manifold we need to join some stuff up to get proper polygons //First link up polygon ends that are within 2 microns. for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() < 1) continue; for(unsigned int j=0;j<openPolygonList.size();j++) { if (openPolygonList[j].size() < 1) continue; Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0]; int64_t distSquared = vSize2(diff); if (distSquared < MM2INT(0.02) * MM2INT(0.02)) { if (i == j) { polygonList.add(openPolygonList[i]); openPolygonList[i].clear(); break; }else{ for(unsigned int n=0; n<openPolygonList[j].size(); n++) openPolygonList[i].add(openPolygonList[j][n]); openPolygonList[j].clear(); } } } } //Next link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time. while(1) { int64_t bestScore = MM2INT(10.0) * MM2INT(10.0); unsigned int bestA = -1; unsigned int bestB = -1; bool reversed = false; for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() < 1) continue; for(unsigned int j=0;j<openPolygonList.size();j++) { if (openPolygonList[j].size() < 1) continue; Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0]; int64_t distSquared = vSize2(diff); if (distSquared < bestScore) { bestScore = distSquared; bestA = i; bestB = j; reversed = false; } if (i != j) { Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][openPolygonList[j].size()-1]; int64_t distSquared = vSize2(diff); if (distSquared < bestScore) { bestScore = distSquared; bestA = i; bestB = j; reversed = true; } } } } if (bestScore >= MM2INT(10.0) * MM2INT(10.0)) break; if (bestA == bestB) { polygonList.add(openPolygonList[bestA]); openPolygonList[bestA].clear(); }else{ if (reversed) { if (openPolygonList[bestA].polygonLength() > openPolygonList[bestB].polygonLength()) { for(unsigned int n=openPolygonList[bestB].size()-1; int(n)>=0; n--) openPolygonList[bestA].add(openPolygonList[bestB][n]); openPolygonList[bestB].clear(); }else{ for(unsigned int n=openPolygonList[bestA].size()-1; int(n)>=0; n--) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } }else{ for(unsigned int n=0; n<openPolygonList[bestB].size(); n++) openPolygonList[bestA].add(openPolygonList[bestB][n]); openPolygonList[bestB].clear(); } } } if (extensiveStitching) { //For extensive stitching find 2 open polygons that are touching 2 closed polygons. // Then find the sortest path over this polygon that can be used to connect the open polygons, // And generate a path over this shortest bit to link up the 2 open polygons. // (If these 2 open polygons are the same polygon, then the final result is a closed polyon) while(1) { unsigned int bestA = -1; unsigned int bestB = -1; gapCloserResult bestResult; bestResult.len = LLONG_MAX; bestResult.polygonIdx = -1; bestResult.pointIdxA = -1; bestResult.pointIdxB = -1; for(unsigned int i=0; i<openPolygonList.size(); i++) { if (openPolygonList[i].size() < 1) continue; { gapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[i][openPolygonList[i].size()-1]); if (res.len > 0 && res.len < bestResult.len) { bestA = i; bestB = i; bestResult = res; } } for(unsigned int j=0; j<openPolygonList.size(); j++) { if (openPolygonList[j].size() < 1 || i == j) continue; gapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[j][openPolygonList[j].size()-1]); if (res.len > 0 && res.len < bestResult.len) { bestA = i; bestB = j; bestResult = res; } } } if (bestResult.len < LLONG_MAX) { if (bestA == bestB) { if (bestResult.pointIdxA == bestResult.pointIdxB) { polygonList.add(openPolygonList[bestA]); openPolygonList[bestA].clear(); } else if (bestResult.AtoB) { PolygonRef poly = polygonList.newPoly(); for(unsigned int j = bestResult.pointIdxA; j != bestResult.pointIdxB; j = (j + 1) % polygonList[bestResult.polygonIdx].size()) poly.add(polygonList[bestResult.polygonIdx][j]); for(unsigned int j = openPolygonList[bestA].size() - 1; int(j) >= 0; j--) poly.add(openPolygonList[bestA][j]); openPolygonList[bestA].clear(); } else { unsigned int n = polygonList.size(); polygonList.add(openPolygonList[bestA]); for(unsigned int j = bestResult.pointIdxB; j != bestResult.pointIdxA; j = (j + 1) % polygonList[bestResult.polygonIdx].size()) polygonList[n].add(polygonList[bestResult.polygonIdx][j]); openPolygonList[bestA].clear(); } } else { if (bestResult.pointIdxA == bestResult.pointIdxB) { for(unsigned int n=0; n<openPolygonList[bestA].size(); n++) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } else if (bestResult.AtoB) { Polygon poly; for(unsigned int n = bestResult.pointIdxA; n != bestResult.pointIdxB; n = (n + 1) % polygonList[bestResult.polygonIdx].size()) poly.add(polygonList[bestResult.polygonIdx][n]); for(unsigned int n=poly.size()-1;int(n) >= 0; n--) openPolygonList[bestB].add(poly[n]); for(unsigned int n=0; n<openPolygonList[bestA].size(); n++) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } else { for(unsigned int n = bestResult.pointIdxB; n != bestResult.pointIdxA; n = (n + 1) % polygonList[bestResult.polygonIdx].size()) openPolygonList[bestB].add(polygonList[bestResult.polygonIdx][n]); for(unsigned int n = openPolygonList[bestA].size() - 1; int(n) >= 0; n--) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } } } else { break; } } } /* int q=0; for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() < 2) continue; if (!q) log("***\n"); log("S: %f %f\n", float(openPolygonList[i][0].X), float(openPolygonList[i][0].Y)); log("E: %f %f\n", float(openPolygonList[i][openPolygonList[i].size()-1].X), float(openPolygonList[i][openPolygonList[i].size()-1].Y)); q = 1; } */ //if (q) exit(1); if (keepNoneClosed) { for(unsigned int n=0; n<openPolygonList.size(); n++) { if (openPolygonList[n].size() > 0) polygonList.add(openPolygonList[n]); } } //Clear the openPolygonList to save memory, the only reason to keep it after this is for debugging. //openPolygonList.clear(); //Remove all the tiny polygons, or polygons that are not closed. As they do not contribute to the actual print. int snapDistance = MM2INT(1.0); for(unsigned int i=0;i<polygonList.size();i++) { int length = 0; for(unsigned int n=1; n<polygonList[i].size(); n++) { length += vSize(polygonList[i][n] - polygonList[i][n-1]); if (length > snapDistance) break; } if (length < snapDistance) { polygonList.remove(i); i--; } } //Finally optimize all the polygons. Every point removed saves time in the long run. optimizePolygons(polygonList); }
/* * algorithm: * 1. for each line segment of each polygon: * store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections * (zigzag): add boundary segments to result * 2. for each scanline: * sort the associated intersections * and connect them using the even-odd rule * * rough explanation of the zigzag algorithm: * while walking around (each) polygon (1.) * if polygon intersects with even scanline * start boundary segment (add each following segment to the [result]) * when polygon intersects with a scanline again * stop boundary segment (stop adding segments to the [result]) * (see infill/ZigzagConnectorProcessor.h for actual implementation details) * * * we call the areas between two consecutive scanlines a 'scansegment'. * Scansegment x is the area between scanline x and scanline x+1 * Edit: the term scansegment is wrong, since I call a boundary segment leaving from an even scanline to the left as belonging to an even scansegment, * while I also call a boundary segment leaving from an even scanline toward the right as belonging to an even scansegment. */ void Infill::generateLinearBasedInfill(const int outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags, int64_t extra_shift) { if (line_distance == 0) { return; } if (in_outline.size() == 0) { return; } int shift = extra_shift + this->shift; Polygons outline; if (outline_offset != 0) { outline = in_outline.offset(outline_offset); if (perimeter_gaps) { perimeter_gaps->add(in_outline.difference(outline.offset(infill_line_width / 2 + perimeter_gaps_extra_offset))); } } else { outline = in_outline; } outline = outline.offset(infill_overlap); if (outline.size() == 0) { return; } outline.applyMatrix(rotation_matrix); if (shift < 0) { shift = line_distance - (-shift) % line_distance; } else { shift = shift % line_distance; } AABB boundary(outline); int scanline_min_idx = computeScanSegmentIdx(boundary.min.X - shift, line_distance); int line_count = computeScanSegmentIdx(boundary.max.X - shift, line_distance) + 1 - scanline_min_idx; std::vector<std::vector<int64_t> > cut_list; // mapping from scanline to all intersections with polygon segments for(int scanline_idx = 0; scanline_idx < line_count; scanline_idx++) { cut_list.push_back(std::vector<int64_t>()); } for(unsigned int poly_idx = 0; poly_idx < outline.size(); poly_idx++) { PolygonRef poly = outline[poly_idx]; Point p0 = poly.back(); zigzag_connector_processor.registerVertex(p0); // always adds the first point to ZigzagConnectorProcessorEndPieces::first_zigzag_connector when using a zigzag infill type for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++) { Point p1 = poly[point_idx]; if (p1.X == p0.X) { zigzag_connector_processor.registerVertex(p1); // TODO: how to make sure it always adds the shortest line? (in order to prevent overlap with the zigzag connectors) // note: this is already a problem for normal infill, but hasn't really cothered anyone so far. p0 = p1; continue; } int scanline_idx0; int scanline_idx1; // this way of handling the indices takes care of the case where a boundary line segment ends exactly on a scanline: // in case the next segment moves back from that scanline either 2 or 0 scanline-boundary intersections are created // otherwise only 1 will be created, counting as an actual intersection int direction = 1; if (p0.X < p1.X) { scanline_idx0 = computeScanSegmentIdx(p0.X - shift, line_distance) + 1; // + 1 cause we don't cross the scanline of the first scan segment scanline_idx1 = computeScanSegmentIdx(p1.X - shift, line_distance); // -1 cause the vertex point is handled in the next segment (or not in the case which looks like >) } else { direction = -1; scanline_idx0 = computeScanSegmentIdx(p0.X - shift, line_distance); // -1 cause the vertex point is handled in the previous segment (or not in the case which looks like >) scanline_idx1 = computeScanSegmentIdx(p1.X - shift, line_distance) + 1; // + 1 cause we don't cross the scanline of the first scan segment } for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1 + direction; scanline_idx += direction) { int x = scanline_idx * line_distance + shift; int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X); assert(scanline_idx - scanline_min_idx >= 0 && scanline_idx - scanline_min_idx < int(cut_list.size()) && "reading infill cutlist index out of bounds!"); cut_list[scanline_idx - scanline_min_idx].push_back(y); Point scanline_linesegment_intersection(x, y); zigzag_connector_processor.registerScanlineSegmentIntersection(scanline_linesegment_intersection, scanline_idx % 2 == 0); } zigzag_connector_processor.registerVertex(p1); p0 = p1; } zigzag_connector_processor.registerPolyFinished(); } if (cut_list.size() == 0) { return; } if (connected_zigzags && cut_list.size() == 1 && cut_list[0].size() <= 2) { return; // don't add connection if boundary already contains whole outline! } addLineInfill(result, rotation_matrix, scanline_min_idx, line_distance, boundary, cut_list, shift); }
void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive_stitching) { Polygons openPolygonList; for(unsigned int startSegment=0; startSegment < segmentList.size(); startSegment++) { if (segmentList[startSegment].addedToPolygon) continue; Polygon poly; poly.add(segmentList[startSegment].start); unsigned int segmentIndex = startSegment; bool canClose; while(true) { canClose = false; segmentList[segmentIndex].addedToPolygon = true; Point p0 = segmentList[segmentIndex].end; poly.add(p0); int nextIndex = -1; const MeshFace& face = mesh->faces[segmentList[segmentIndex].faceIndex]; for(unsigned int i=0;i<3;i++) { decltype(face_idx_to_segment_index.begin()) it; if (face.connected_face_index[i] > -1 && (it = face_idx_to_segment_index.find(face.connected_face_index[i])) != face_idx_to_segment_index.end()) { int index = (*it).second; Point p1 = segmentList[index].start; Point diff = p0 - p1; if (shorterThen(diff, MM2INT(0.01))) { if (index == static_cast<int>(startSegment)) canClose = true; if (segmentList[index].addedToPolygon) continue; nextIndex = index; } } } if (nextIndex == -1) break; segmentIndex = nextIndex; } if (canClose) polygonList.add(poly); else openPolygonList.add(poly); } //Clear the segmentList to save memory, it is no longer needed after this point. segmentList.clear(); //Connecting polygons that are not closed yet, as models are not always perfect manifold we need to join some stuff up to get proper polygons //First link up polygon ends that are within 2 microns. for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() < 1) continue; for(unsigned int j=0;j<openPolygonList.size();j++) { if (openPolygonList[j].size() < 1) continue; Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0]; int64_t distSquared = vSize2(diff); if (distSquared < MM2INT(0.02) * MM2INT(0.02)) { if (i == j) { polygonList.add(openPolygonList[i]); openPolygonList[i].clear(); break; }else{ for(unsigned int n=0; n<openPolygonList[j].size(); n++) openPolygonList[i].add(openPolygonList[j][n]); openPolygonList[j].clear(); } } } } //Next link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time. while(1) { int64_t bestScore = MM2INT(10.0) * MM2INT(10.0); unsigned int bestA = -1; unsigned int bestB = -1; bool reversed = false; for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() < 1) continue; for(unsigned int j=0;j<openPolygonList.size();j++) { if (openPolygonList[j].size() < 1) continue; Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0]; int64_t distSquared = vSize2(diff); if (distSquared < bestScore) { bestScore = distSquared; bestA = i; bestB = j; reversed = false; } if (i != j) { Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][openPolygonList[j].size()-1]; int64_t distSquared = vSize2(diff); if (distSquared < bestScore) { bestScore = distSquared; bestA = i; bestB = j; reversed = true; } } } } if (bestScore >= MM2INT(10.0) * MM2INT(10.0)) break; if (bestA == bestB) { polygonList.add(openPolygonList[bestA]); openPolygonList[bestA].clear(); }else{ if (reversed) { if (openPolygonList[bestA].polygonLength() > openPolygonList[bestB].polygonLength()) { for(unsigned int n=openPolygonList[bestB].size()-1; int(n)>=0; n--) openPolygonList[bestA].add(openPolygonList[bestB][n]); openPolygonList[bestB].clear(); }else{ for(unsigned int n=openPolygonList[bestA].size()-1; int(n)>=0; n--) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } }else{ for(unsigned int n=0; n<openPolygonList[bestB].size(); n++) openPolygonList[bestA].add(openPolygonList[bestB][n]); openPolygonList[bestB].clear(); } } } if (extensive_stitching) { //For extensive stitching find 2 open polygons that are touching 2 closed polygons. // Then find the sortest path over this polygon that can be used to connect the open polygons, // And generate a path over this shortest bit to link up the 2 open polygons. // (If these 2 open polygons are the same polygon, then the final result is a closed polyon) while(1) { unsigned int bestA = -1; unsigned int bestB = -1; gapCloserResult bestResult; bestResult.len = POINT_MAX; bestResult.polygonIdx = -1; bestResult.pointIdxA = -1; bestResult.pointIdxB = -1; for(unsigned int i=0; i<openPolygonList.size(); i++) { if (openPolygonList[i].size() < 1) continue; { gapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[i][openPolygonList[i].size()-1]); if (res.len > 0 && res.len < bestResult.len) { bestA = i; bestB = i; bestResult = res; } } for(unsigned int j=0; j<openPolygonList.size(); j++) { if (openPolygonList[j].size() < 1 || i == j) continue; gapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[j][openPolygonList[j].size()-1]); if (res.len > 0 && res.len < bestResult.len) { bestA = i; bestB = j; bestResult = res; } } } if (bestResult.len < POINT_MAX) { if (bestA == bestB) { if (bestResult.pointIdxA == bestResult.pointIdxB) { polygonList.add(openPolygonList[bestA]); openPolygonList[bestA].clear(); } else if (bestResult.AtoB) { PolygonRef poly = polygonList.newPoly(); for(unsigned int j = bestResult.pointIdxA; j != bestResult.pointIdxB; j = (j + 1) % polygonList[bestResult.polygonIdx].size()) poly.add(polygonList[bestResult.polygonIdx][j]); for(unsigned int j = openPolygonList[bestA].size() - 1; int(j) >= 0; j--) poly.add(openPolygonList[bestA][j]); openPolygonList[bestA].clear(); } else { unsigned int n = polygonList.size(); polygonList.add(openPolygonList[bestA]); for(unsigned int j = bestResult.pointIdxB; j != bestResult.pointIdxA; j = (j + 1) % polygonList[bestResult.polygonIdx].size()) polygonList[n].add(polygonList[bestResult.polygonIdx][j]); openPolygonList[bestA].clear(); } } else { if (bestResult.pointIdxA == bestResult.pointIdxB) { for(unsigned int n=0; n<openPolygonList[bestA].size(); n++) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } else if (bestResult.AtoB) { Polygon poly; for(unsigned int n = bestResult.pointIdxA; n != bestResult.pointIdxB; n = (n + 1) % polygonList[bestResult.polygonIdx].size()) poly.add(polygonList[bestResult.polygonIdx][n]); for(unsigned int n=poly.size()-1;int(n) >= 0; n--) openPolygonList[bestB].add(poly[n]); for(unsigned int n=0; n<openPolygonList[bestA].size(); n++) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } else { for(unsigned int n = bestResult.pointIdxB; n != bestResult.pointIdxA; n = (n + 1) % polygonList[bestResult.polygonIdx].size()) openPolygonList[bestB].add(polygonList[bestResult.polygonIdx][n]); for(unsigned int n = openPolygonList[bestA].size() - 1; int(n) >= 0; n--) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } } } else { break; } } } if (keep_none_closed) { for(unsigned int n=0; n<openPolygonList.size(); n++) { if (openPolygonList[n].size() > 0) polygonList.add(openPolygonList[n]); } } for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() > 0) openPolygons.newPoly() = openPolygonList[i]; } //Remove all the tiny polygons, or polygons that are not closed. As they do not contribute to the actual print. int snapDistance = MM2INT(1.0); for(unsigned int i=0;i<polygonList.size();i++) { int length = 0; for(unsigned int n=1; n<polygonList[i].size(); n++) { length += vSize(polygonList[i][n] - polygonList[i][n-1]); if (length > snapDistance) break; } if (length < snapDistance) { polygonList.remove(i); i--; } } //Finally optimize all the polygons. Every point removed saves time in the long run. optimizePolygons(polygonList); int xy_offset = mesh->getSettingInMicrons("xy_offset"); if (xy_offset != 0) { polygonList = polygonList.offset(xy_offset); } }
unsigned int moveInside(Polygons& polygons, Point& from, int distance, int64_t maxDist2) { Point ret = from; int64_t bestDist2 = maxDist2; unsigned int bestPoly = NO_INDEX; for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++) { PolygonRef poly = polygons[poly_idx]; if (poly.size() < 2) continue; Point p0 = poly[poly.size()-2]; Point p1 = poly.back(); bool projected_p_beyond_prev_segment = dot(p1 - p0, from - p0) > vSize2(p1 - p0); for(Point& p2 : poly) { // X = A + Normal( B - A ) * ((( B - A ) dot ( P - A )) / VSize( A - B )); // X = P projected on AB Point& a = p1; Point& b = p2; Point& p = from; Point ab = b - a; Point ap = p - a; int64_t ab_length = vSize(ab); int64_t ax_length = dot(ab, ap) / ab_length; if (ax_length < 0) // x is projected to before ab { if (projected_p_beyond_prev_segment) { // case which looks like: > . projected_p_beyond_prev_segment = false; Point& x = p1; int64_t dist2 = vSize2(x - p); if (dist2 < bestDist2) { bestDist2 = dist2; if (distance == 0) { ret = x; } else { ret = x + normal(crossZ(normal(a, distance*4) + normal(p1 - p0, distance*4)), distance); } // *4 to retain more precision for the eventual normalization bestPoly = poly_idx; } } else { projected_p_beyond_prev_segment = false; p0 = p1; p1 = p2; continue; } } else if (ax_length > ab_length) // x is projected to beyond ab { projected_p_beyond_prev_segment = true; p0 = p1; p1 = p2; continue; } else { projected_p_beyond_prev_segment = false; Point x = a + ab * ax_length / ab_length; int64_t dist2 = vSize2(x - from); if (dist2 < bestDist2) { bestDist2 = dist2; if (distance == 0) { ret = x; } else { ret = x + crossZ(normal(ab, distance)); } bestPoly = poly_idx; } } p0 = p1; p1 = p2; } } if (bestDist2 < maxDist2) { from = ret; return bestPoly; } return NO_INDEX; }
unsigned int Polygons::findInside(Point p, bool border_result) { Polygons& thiss = *this; if (size() < 1) { return false; } int64_t min_x[size()]; std::fill_n(min_x, size(), std::numeric_limits<int64_t>::max()); // initialize with int.max int crossings[size()]; std::fill_n(crossings, size(), 0); // initialize with zeros for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++) { PolygonRef poly = thiss[poly_idx]; Point p0 = poly.back(); for(Point& p1 : poly) { short comp = pointLiesOnTheRightOfLine(p, p0, p1); if (comp == 1) { crossings[poly_idx]++; int64_t x; if (p1.Y == p0.Y) { x = p0.X; } else { x = p0.X + (p1.X-p0.X) * (p.Y-p0.Y) / (p1.Y-p0.Y); } if (x < min_x[poly_idx]) { min_x[poly_idx] = x; } } else if (border_result && comp == 0) { return poly_idx; } p0 = p1; } } int64_t min_x_uneven = std::numeric_limits<int64_t>::max(); unsigned int ret = NO_INDEX; unsigned int n_unevens = 0; for (unsigned int array_idx = 0; array_idx < size(); array_idx++) { if (crossings[array_idx] % 2 == 1) { n_unevens++; if (min_x[array_idx] < min_x_uneven) { min_x_uneven = min_x[array_idx]; ret = array_idx; } } } if (n_unevens % 2 == 0) { ret = NO_INDEX; } return ret; }
bool getNextPointWithDistance(Point from, int64_t dist, const PolygonRef poly, int start_idx, int poly_start_idx, GivenDistPoint& result) { Point prev_poly_point = poly[(start_idx + poly_start_idx) % poly.size()]; for (unsigned int prev_idx = start_idx; prev_idx < poly.size(); prev_idx++) { int next_idx = (prev_idx + 1 + poly_start_idx) % poly.size(); // last checked segment is between last point in poly and poly[0]... Point& next_poly_point = poly[next_idx]; if ( !shorterThen(next_poly_point - from, dist) ) { /* * x r * p.---------+---+------------.n * L| / * | / dist * |/ * f. * * f=from * p=prev_poly_point * n=next_poly_point * x= f projected on pn * r=result point at distance [dist] from f */ Point pn = next_poly_point - prev_poly_point; if (shorterThen(pn, 100)) // when precision is limited { Point middle = (next_poly_point + prev_poly_point) / 2; int64_t dist_to_middle = vSize(from - middle); if (dist_to_middle - dist < 100 && dist_to_middle - dist > -100) { result.location = middle; result.pos = prev_idx; return true; } else { prev_poly_point = next_poly_point; continue; } } Point pf = from - prev_poly_point; Point px = dot(pf, pn) / vSize(pn) * pn / vSize(pn); Point xf = pf - px; if (!shorterThen(xf, dist)) // line lies wholly further than pn { prev_poly_point = next_poly_point; continue; } int64_t xr_dist = std::sqrt(dist*dist - vSize2(xf)); // inverse Pythagoras if (vSize(pn - px) - xr_dist < 1) // r lies beyond n { prev_poly_point = next_poly_point; continue; } Point xr = xr_dist * pn / vSize(pn); Point pr = px + xr; result.location = prev_poly_point + pr; result.pos = prev_idx; return true; } prev_poly_point = next_poly_point; } return false; }
void LineOrderOptimizer::optimize() { int gridSize = 5000; // the size of the cells in the hash grid. TODO SparsePointGridInclusive<unsigned int> line_bucket_grid(gridSize); bool picked[polygons.size()]; memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++) /// find closest point to initial starting point within each polygon +initialize picked { int best_point_idx = -1; float best_point_dist = std::numeric_limits<float>::infinity(); PolygonRef poly = polygons[poly_idx]; for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++) /// get closest point from polygon { float dist = vSize2f(poly[point_idx] - startPoint); if (dist < best_point_dist) { best_point_idx = point_idx; best_point_dist = dist; } } polyStart.push_back(best_point_idx); assert(poly.size() == 2); line_bucket_grid.insert(poly[0], poly_idx); line_bucket_grid.insert(poly[1], poly_idx); } Point incoming_perpundicular_normal(0, 0); Point prev_point = startPoint; for (unsigned int order_idx = 0; order_idx < polygons.size(); order_idx++) /// actual path order optimizer { int best_line_idx = -1; float best_score = std::numeric_limits<float>::infinity(); // distance score for the best next line /// check if single-line-polygon is close to last point for(unsigned int close_line_idx : line_bucket_grid.getNearbyVals(prev_point, gridSize)) { if (picked[close_line_idx] || polygons[close_line_idx].size() < 1) { continue; } updateBestLine(close_line_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal); } if (best_line_idx == -1) /// if single-line-polygon hasn't been found yet { for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++) { if (picked[poly_idx] || polygons[poly_idx].size() < 1) /// skip single-point-polygons { continue; } assert(polygons[poly_idx].size() == 2); updateBestLine(poly_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal); } } if (best_line_idx > -1) /// should always be true; we should have been able to identify the best next polygon { PolygonRef best_line = polygons[best_line_idx]; assert(best_line.size() == 2); int line_start_point_idx = polyStart[best_line_idx]; int line_end_point_idx = line_start_point_idx * -1 + 1; /// 1 -> 0 , 0 -> 1 Point& line_start = best_line[line_start_point_idx]; Point& line_end = best_line[line_end_point_idx]; prev_point = line_end; incoming_perpundicular_normal = turn90CCW(normal(line_end - line_start, 1000)); picked[best_line_idx] = true; polyOrder.push_back(best_line_idx); } else { logError("Failed to find next closest line.\n"); } } }
void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh) { if (mesh.getSettingAsCount("wall_line_count") == 0) { return; } int64_t fuzziness = mesh.getSettingInMicrons("magic_fuzzy_skin_thickness"); int64_t avg_dist_between_points = mesh.getSettingInMicrons("magic_fuzzy_skin_point_dist"); int64_t min_dist_between_points = avg_dist_between_points * 3 / 4; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value int64_t range_random_point_dist = avg_dist_between_points / 2; for (unsigned int layer_nr = 0; layer_nr < mesh.layers.size(); layer_nr++) { SliceLayer& layer = mesh.layers[layer_nr]; for (SliceLayerPart& part : layer.parts) { Polygons results; Polygons& skin = (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)? part.outline : part.insets[0]; for (PolygonRef poly : skin) { // generate points in between p0 and p1 PolygonRef result = results.newPoly(); int64_t dist_left_over = rand() % (min_dist_between_points / 2); // the distance to be traversed on the line before making the first new point Point* p0 = &poly.back(); for (Point& p1 : poly) { // 'a' is the (next) new point between p0 and p1 Point p0p1 = p1 - *p0; int64_t p0p1_size = vSize(p0p1); int64_t dist_last_point = dist_left_over + p0p1_size * 2; // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + rand() % range_random_point_dist) { int r = rand() % (fuzziness * 2) - fuzziness; Point perp_to_p0p1 = crossZ(p0p1); Point fuzz = normal(perp_to_p0p1, r); Point pa = *p0 + normal(p0p1, p0pa_dist) + fuzz; result.add(pa); dist_last_point = p0pa_dist; } dist_left_over = p0p1_size - dist_last_point; p0 = &p1; } while (result.size() < 3 ) { unsigned int point_idx = poly.size() - 2; result.add(poly[point_idx]); if (point_idx == 0) { break; } point_idx--; } if (result.size() < 3) { result.clear(); for (Point& p : poly) result.add(p); } } skin = results; sendPolygons(Inset0Type, layer_nr, skin, mesh.getSettingInMicrons("wall_line_width_0")); } } }
void LineOrderOptimizer::optimize() { int gridSize = 5000; // the size of the cells in the hash grid. BucketGrid2D<unsigned int> line_bucket_grid(gridSize); bool* picked = new bool[polygons.size()]; // bool picked[polygons.size()]; memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked { int best = -1; float bestDist = std::numeric_limits<float>::infinity(); PolygonRef poly = polygons[i_polygon]; for(unsigned int i_point=0; i_point<poly.size(); i_point++) /// get closest point from polygon { float dist = vSize2f(poly[i_point] - startPoint); if (dist < bestDist) { best = i_point; bestDist = dist; } } polyStart.push_back(best); assert(poly.size() == 2); line_bucket_grid.insert(poly[0], i_polygon); line_bucket_grid.insert(poly[1], i_polygon); } Point incommingPerpundicularNormal(0, 0); Point prev_point = startPoint; for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// actual path order optimizer { int best = -1; float bestDist = std::numeric_limits<float>::infinity(); for(unsigned int i_close_line_polygon : line_bucket_grid.findNearbyObjects(prev_point)) /// check if single-line-polygon is close to last point { if (picked[i_close_line_polygon] || polygons[i_close_line_polygon].size() < 1) continue; checkIfLineIsBest(i_close_line_polygon, best, bestDist, prev_point, incommingPerpundicularNormal); } if (best == -1) /// if single-line-polygon hasn't been found yet { for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) { if (picked[i_polygon] || polygons[i_polygon].size() < 1) /// skip single-point-polygons continue; assert(polygons[i_polygon].size() == 2); checkIfLineIsBest(i_polygon, best, bestDist, prev_point, incommingPerpundicularNormal); } } if (best > -1) /// should always be true; we should have been able to identify the best next polygon { assert(polygons[best].size() == 2); int endIdx = polyStart[best] * -1 + 1; /// 1 -> 0 , 0 -> 1 prev_point = polygons[best][endIdx]; incommingPerpundicularNormal = crossZ(normal(polygons[best][endIdx] - polygons[best][polyStart[best]], 1000)); picked[best] = true; polyOrder.push_back(best); } else logError("Failed to find next closest line.\n"); } prev_point = startPoint; for(unsigned int n=0; n<polyOrder.size(); n++) /// decide final starting points in each polygon { int nr = polyOrder[n]; PolygonRef poly = polygons[nr]; int best = -1; float bestDist = std::numeric_limits<float>::infinity(); bool orientation = poly.orientation(); for(unsigned int i=0;i<poly.size(); i++) { float dist = vSize2f(polygons[nr][i] - prev_point); Point n0 = normal(poly[(i+poly.size()-1)%poly.size()] - poly[i], 2000); Point n1 = normal(poly[i] - poly[(i + 1) % poly.size()], 2000); float dot_score = dot(n0, n1) - dot(crossZ(n0), n1); if (orientation) dot_score = -dot_score; if (dist + dot_score < bestDist) { best = i; bestDist = dist + dot_score; } } polyStart[nr] = best; assert(poly.size() == 2); prev_point = poly[best *-1 + 1]; /// 1 -> 0 , 0 -> 1 } }
void PathOrderOptimizer::optimize() { const float incommingPerpundicularNormalScale = 0.0001f; std::map<uint32_t, std::vector<unsigned int>> location_to_polygon_map; std::vector<bool> picked; for(unsigned int i=0; i<polygons.size(); i++) { int best = -1; float bestDist = 0xFFFFFFFFFFFFFFFFLL; PolygonRef poly = polygons[i]; for(unsigned int j=0; j<poly.size(); j++) { float dist = vSize2f(poly[j] - startPoint); if (dist < bestDist) { best = j; bestDist = dist; } } polyStart.push_back(best); picked.push_back(false); if (poly.size() == 2) { Point p0 = poly[0]; Point p1 = poly[1]; location_to_polygon_map[hashPoint(p0)].push_back(i); location_to_polygon_map[hashPoint(p1)].push_back(i); } } Point incommingPerpundicularNormal(0, 0); Point p0 = startPoint; for(unsigned int n=0; n<polygons.size(); n++) { int best = -1; float bestDist = 0xFFFFFFFFFFFFFFFFLL; for(unsigned int i : location_to_polygon_map[hashPoint(p0)]) { if (picked[i] || polygons[i].size() < 1) continue; float dist = vSize2f(polygons[i][0] - p0); dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i][1] - polygons[i][0], 1000))) * incommingPerpundicularNormalScale; if (dist < bestDist) { best = i; bestDist = dist; polyStart[i] = 0; } dist = vSize2f(polygons[i][1] - p0); dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i][0] - polygons[i][1], 1000))) * incommingPerpundicularNormalScale; if (dist < bestDist) { best = i; bestDist = dist; polyStart[i] = 1; } } if (best == -1) { for(unsigned int i=0; i<polygons.size(); i++) { if (picked[i] || polygons[i].size() < 1) continue; if (polygons[i].size() == 2) { float dist = vSize2f(polygons[i][0] - p0); dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i][1] - polygons[i][0], 1000))) * incommingPerpundicularNormalScale; if (dist < bestDist) { best = i; bestDist = dist; polyStart[i] = 0; } dist = vSize2f(polygons[i][1] - p0); dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i][0] - polygons[i][1], 1000))) * incommingPerpundicularNormalScale; if (dist < bestDist) { best = i; bestDist = dist; polyStart[i] = 1; } } else { float dist = vSize2f(polygons[i][polyStart[i]] - p0); if (dist < bestDist) { best = i; bestDist = dist; } } } } if (best > -1) { if (polygons[best].size() == 2) { int endIdx = (polyStart[best] + 1) % 2; p0 = polygons[best][endIdx]; incommingPerpundicularNormal = crossZ(normal(polygons[best][endIdx] - polygons[best][polyStart[best]], 1000)); } else { p0 = polygons[best][polyStart[best]]; incommingPerpundicularNormal = Point(0, 0); } picked[best] = true; polyOrder.push_back(best); } } p0 = startPoint; for(int nr : polyOrder) { PolygonRef poly = polygons[nr]; if (poly.size() > 2) { int best = -1; float bestDist = 0xFFFFFFFFFFFFFFFFLL; bool orientation = poly.orientation(); for(unsigned int i=0; i<poly.size(); i++) { const int64_t dot_score_scale = 2000; float dist = vSize2f(polygons[nr][i] - p0); Point n0 = normal(poly[(i+poly.size()-1)%poly.size()] - poly[i], dot_score_scale); Point n1 = normal(poly[i] - poly[(i + 1) % poly.size()], dot_score_scale); float dot_score = dot(n0, n1) - dot(crossZ(n0), n1); if (orientation) dot_score = -dot_score; dist += dot_score; if (dist < bestDist) { best = i; bestDist = dist; } } polyStart[nr] = best; } if (poly.size() <= 2) { p0 = poly[(polyStart[nr] + 1) % 2]; } else { p0 = poly[polyStart[nr]]; } } }
void PathOrderOptimizer::optimize() { bool* picked=new bool[polygons.size()]; // bool picked[polygons.size()]; memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked { int best = -1; float bestDist = std::numeric_limits<float>::infinity(); PolygonRef poly = polygons[i_polygon]; for(unsigned int i_point=0; i_point<poly.size(); i_point++) /// get closest point in polygon { float dist = vSize2f(poly[i_point] - startPoint); if (dist < bestDist) { best = i_point; bestDist = dist; } } polyStart.push_back(best); //picked.push_back(false); /// initialize all picked values as false assert(poly.size() != 2); } Point prev_point = startPoint; for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// actual path order optimizer { int best = -1; float bestDist = std::numeric_limits<float>::infinity(); for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) { if (picked[i_polygon] || polygons[i_polygon].size() < 1) /// skip single-point-polygons continue; assert (polygons[i_polygon].size() != 2); float dist = vSize2f(polygons[i_polygon][polyStart[i_polygon]] - prev_point); if (dist < bestDist) { best = i_polygon; bestDist = dist; } } if (best > -1) /// should always be true; we should have been able to identify the best next polygon { assert(polygons[best].size() != 2); prev_point = polygons[best][polyStart[best]]; picked[best] = true; polyOrder.push_back(best); } else logError("Failed to find next closest polygon.\n"); } prev_point = startPoint; for(unsigned int n=0; n<polyOrder.size(); n++) /// decide final starting points in each polygon { int i_polygon = polyOrder[n]; int best = getClosestPointInPolygon(prev_point, i_polygon); polyStart[i_polygon] = best; prev_point = polygons[i_polygon][best]; } }
/*! * adapted from generateLineInfill(.) * * generate lines within the area of [in_outline], at regular intervals of [lineSpacing] * idea: * intersect a regular grid of 'scanlines' with the area inside [in_outline] * sigzag: * include pieces of boundary, connecting the lines, forming an accordion like zigzag instead of separate lines |_|^|_| * * we call the areas between two consecutive scanlines a 'scansegment' * * algorithm: * 1. for each line segment of each polygon: * store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections * (zigzag): add boundary segments to result * 2. for each scanline: * sort the associated intersections * and connect them using the even-odd rule * * zigzag algorithm: * while walking around (each) polygon (1.) * if polygon intersects with even scanline * start boundary segment (add each following segment to the [result]) * when polygon intersects with a scanline again * stop boundary segment (stop adding segments to the [result]) * if polygon intersects with even scanline again (instead of odd) * dont add the last line segment to the boundary (unless [connect_zigzags]) * * * <-- * ___ * | | | * | | | * | |___| * --> * * ^ = even scanline * * start boundary from even scanline! :D * * * _____ * | | | , * | | | | * |_____| |__/ * * ^ ^ ^ scanlines * ^ disconnected end piece */ void generateZigZagIninfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags) { // if (in_outline.size() == 0) return; // Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2); Polygons empty; Polygons outline = in_outline.difference(empty); // copy if (outline.size() == 0) return; PointMatrix matrix(rotation); outline.applyMatrix(matrix); auto addLine = [&](Point from, Point to) { PolygonRef p = result.newPoly(); p.add(matrix.unapply(from)); p.add(matrix.unapply(to)); }; AABB boundary(outline); int scanline_min_idx = boundary.min.X / lineSpacing; int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx; std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments for(int n=0; n<lineCount; n++) cutList.push_back(std::vector<int64_t>()); for(unsigned int polyNr=0; polyNr < outline.size(); polyNr++) { std::vector<Point> firstBoundarySegment; std::vector<Point> unevenBoundarySegment; // stored cause for connected_zigzags a boundary segment which ends in an uneven scanline needs to be included bool isFirstBoundarySegment = true; bool firstBoundarySegmentEndsInEven; bool isEvenScanSegment = false; Point p0 = outline[polyNr][outline[polyNr].size()-1]; Point lastPoint = p0; for(unsigned int i=0; i < outline[polyNr].size(); i++) { Point p1 = outline[polyNr][i]; int64_t xMin = p1.X, xMax = p0.X; if (xMin == xMax) { lastPoint = p1; p0 = p1; continue; } if (xMin > xMax) { xMin = p0.X; xMax = p1.X; } int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ... int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number) int direction = 1; if (p0.X > p1.X) { direction = -1; scanline_idx1 += 1; // only consider the scanlines in between the scansegments } else scanline_idx0 += 1; // only consider the scanlines in between the scansegments if (isFirstBoundarySegment) firstBoundarySegment.push_back(p0); for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction) { int x = scanline_idx * lineSpacing; int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X); cutList[scanline_idx - scanline_min_idx].push_back(y); bool last_isEvenScanSegment = isEvenScanSegment; if (scanline_idx % 2 == 0) isEvenScanSegment = true; else isEvenScanSegment = false; if (!isFirstBoundarySegment) { if (last_isEvenScanSegment && (connect_zigzags || !isEvenScanSegment)) addLine(lastPoint, Point(x,y)); else if (connect_zigzags && !last_isEvenScanSegment && !isEvenScanSegment) // if we end an uneven boundary in an uneven segment { // add whole unevenBoundarySegment (including the just obtained point) for (unsigned int p = 1; p < unevenBoundarySegment.size(); p++) { addLine(unevenBoundarySegment[p-1], unevenBoundarySegment[p]); } addLine(unevenBoundarySegment[unevenBoundarySegment.size()-1], Point(x,y)); unevenBoundarySegment.clear(); } if (connect_zigzags && last_isEvenScanSegment && !isEvenScanSegment) unevenBoundarySegment.push_back(Point(x,y)); else unevenBoundarySegment.clear(); } lastPoint = Point(x,y); if (isFirstBoundarySegment) { firstBoundarySegment.emplace_back(x,y); firstBoundarySegmentEndsInEven = isEvenScanSegment; isFirstBoundarySegment = false; } } if (!isFirstBoundarySegment) { if (isEvenScanSegment) addLine(lastPoint, p1); else if (connect_zigzags) unevenBoundarySegment.push_back(p1); } lastPoint = p1; p0 = p1; } if (isEvenScanSegment || isFirstBoundarySegment || connect_zigzags) { for (unsigned int i = 1; i < firstBoundarySegment.size() ; i++) { if (i < firstBoundarySegment.size() - 1 || !firstBoundarySegmentEndsInEven || connect_zigzags) // only add last element if connect_zigzags or boundary segment ends in uneven scanline addLine(firstBoundarySegment[i-1], firstBoundarySegment[i]); } } else if (!firstBoundarySegmentEndsInEven) addLine(firstBoundarySegment[firstBoundarySegment.size()-2], firstBoundarySegment[firstBoundarySegment.size()-1]); } if (cutList.size() == 0) return; if (connect_zigzags && cutList.size() == 1 && cutList[0].size() <= 2) return; // don't add connection if boundary already contains whole outline! addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth); }