/**************************************************************************** ** ** This file is part of the LibreCAD project, a 2D CAD program ** ** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl) ** Copyright (C) 2001-2003 RibbonSoft. All rights reserved. ** ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file gpl-2.0.txt included in the ** packaging of this file. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ** ** This copyright notice MUST APPEAR in all copies of the script! ** **********************************************************************/ #include #include #include #include #include #include #include "rs_hatch.h" #include "rs_arc.h" #include "rs_circle.h" #include "rs_ellipse.h" #include "rs_line.h" #include "rs_graphicview.h" #include "rs_dialogfactory.h" #include "rs_infoarea.h" #include "rs_information.h" #include "rs_painter.h" #include "rs_pattern.h" #include "rs_patternlist.h" #include "rs_math.h" #include "rs_debug.h" RS_HatchData::RS_HatchData(bool _solid, double _scale, double _angle, const QString& _pattern): solid(_solid) ,scale(_scale) ,angle(_angle) ,pattern(_pattern) { //std::cout << "RS_HatchData: " << pattern.latin1() << "\n"; } std::ostream& operator << (std::ostream& os, const RS_HatchData& td) { os << "(" << td.pattern.toLatin1().data() << ")"; return os; } /** * Constructor. */ RS_Hatch::RS_Hatch(RS_EntityContainer* parent, const RS_HatchData& d) : RS_EntityContainer(parent), data(d) { hatch = nullptr; updateRunning = false; needOptimization = true; updateError = HATCH_UNDEFINED; } /** * Validates the hatch. */ bool RS_Hatch::validate() { bool ret = true; // loops: for(auto l: entities){ if (l->rtti()==RS2::EntityContainer) { RS_EntityContainer* loop = (RS_EntityContainer*)l; ret = loop->optimizeContours() && ret; } } return ret; } RS_Entity* RS_Hatch::clone() const{ RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::clone()"); RS_Hatch* t = new RS_Hatch(*this); t->setOwner(isOwner()); t->initId(); t->detach(); t->update(); // t->hatch = nullptr; RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::clone(): OK"); return t; } /** * @return Number of loops. */ int RS_Hatch::countLoops() const{ if (data.solid) { return count(); } else { return count() - 1; } } bool RS_Hatch::isContainer() const { return !isSolid(); } /** * Recalculates the borders of this hatch. */ void RS_Hatch::calculateBorders() { RS_DEBUG->print("RS_Hatch::calculateBorders"); activateContour(true); RS_EntityContainer::calculateBorders(); RS_DEBUG->print("RS_Hatch::calculateBorders: size: %f,%f", getSize().x, getSize().y); activateContour(false); } /** * Updates the Hatch. Called when the * hatch or it's data, position, alignment, .. changes. * * Refill hatch with pattern. Move, scale, rotate, trim, etc. */ void RS_Hatch::update() { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update"); updateError = HATCH_OK; if (updateRunning) { RS_DEBUG->print(RS_Debug::D_NOTICE, "RS_Hatch::update: skip hatch in updating process"); return; } if (updateEnabled==false) { RS_DEBUG->print(RS_Debug::D_NOTICE, "RS_Hatch::update: skip hatch forbidden to update"); return; } if (data.solid==true) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: processing solid hatch"); calculateBorders(); return; } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: contour has %d loops", count()); updateRunning = true; // save attributes for the current hatch RS_Layer* hatch_layer = this->getLayer(); RS_Pen hatch_pen = this->getPen(); // delete old hatch: if (hatch) { removeEntity(hatch); hatch = nullptr; } if (isUndone()) { RS_DEBUG->print(RS_Debug::D_NOTICE, "RS_Hatch::update: skip undone hatch"); updateRunning = false; return; } if (!validate()) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Hatch::update: invalid contour in hatch found"); updateRunning = false; updateError = HATCH_INVALID_CONTOUR; return; } // search for pattern RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: requesting pattern"); RS_Pattern* pat = RS_PATTERNLIST->requestPattern(data.pattern); if (!pat) { updateRunning = false; RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Hatch::update: requesting pattern: not found"); updateError = HATCH_PATTERN_NOT_FOUND; return; } else { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: requesting pattern: OK"); // make a working copy of hatch pattern RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: cloning pattern"); pat = (RS_Pattern*)pat->clone(); if (pat) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: cloning pattern: OK"); } else { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Hatch::update: error while cloning hatch pattern"); return; } } // scale pattern RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: scaling pattern"); pat->scale(RS_Vector(0.0,0.0), RS_Vector(data.scale, data.scale)); pat->calculateBorders(); forcedCalculateBorders(); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: scaling pattern: OK"); // find out how many pattern-instances we need in x/y: int px1, py1, px2, py2; double f; RS_Hatch* copy = (RS_Hatch*)this->clone(); copy->rotate(RS_Vector(0.0,0.0), -data.angle); copy->forcedCalculateBorders(); // create a pattern over the whole contour. RS_Vector pSize = pat->getSize(); RS_Vector rot_center=pat->getMin(); // RS_Vector cPos = getMin(); RS_Vector cSize = getSize(); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: pattern size: %f/%f", pSize.x, pSize.y); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: contour size: %f/%f", cSize.x, cSize.y); // check pattern sizes for sanity if (cSize.x<1.0e-6 || cSize.y<1.0e-6 || pSize.x<1.0e-6 || pSize.y<1.0e-6 || cSize.x>RS_MAXDOUBLE-1 || cSize.y>RS_MAXDOUBLE-1 || pSize.x>RS_MAXDOUBLE-1 || pSize.y>RS_MAXDOUBLE-1) { delete pat; delete copy; updateRunning = false; RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Hatch::update: contour size or pattern size too small"); updateError = HATCH_TOO_SMALL; return; } // avoid huge memory consumption: else if ( cSize.x* cSize.y/(pSize.x*pSize.y)>1e4) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Hatch::update: contour size too large or pattern size too small"); delete pat; delete copy; updateError = HATCH_AREA_TOO_BIG; return; } // calculate pattern pieces quantity f = copy->getMin().x/pSize.x; px1 = (int)floor(f); f = copy->getMin().y/pSize.y; py1 = (int)floor(f); f = copy->getMax().x/pSize.x; px2 = (int)ceil(f); f = copy->getMax().y/pSize.y; py2 = (int)ceil(f); RS_Vector dvx=RS_Vector(data.angle)*pSize.x; RS_Vector dvy=RS_Vector(data.angle+M_PI*0.5)*pSize.y; pat->rotate(rot_center, data.angle); pat->move(-rot_center); RS_EntityContainer tmp; // container for untrimmed lines // adding array of patterns to tmp: RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: creating pattern carpet"); for (int px=px1; pxclone(); te->move(dvx*px + dvy*py); tmp.addEntity(te); } } } // clean memory delete pat; pat = nullptr; delete copy; copy = nullptr; RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: creating pattern carpet: OK"); // cut pattern to contour shape RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: cutting pattern carpet"); RS_EntityContainer tmp2; // container for small cut lines RS_Line* line = nullptr; RS_Arc* arc = nullptr; RS_Circle* circle = nullptr; RS_Ellipse* ellipse = nullptr; for(auto e: tmp) { if (!e) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Hatch::update: nullptr entity found"); continue; } line = nullptr; arc = nullptr; circle = nullptr; ellipse = nullptr; RS_Vector startPoint; RS_Vector endPoint; RS_Vector center = RS_Vector(false); bool reversed=false; switch(e->rtti()) { case RS2::EntityLine: line=static_cast(e); startPoint = line->getStartpoint(); endPoint = line->getEndpoint(); break; case RS2::EntityArc: arc=static_cast(e); startPoint = arc->getStartpoint(); endPoint = arc->getEndpoint(); center = arc->getCenter(); reversed = arc->isReversed(); break; case RS2::EntityCircle: circle=static_cast(e); startPoint = circle->getCenter() + RS_Vector(circle->getRadius(), 0.0); endPoint = startPoint; center = circle->getCenter(); break; case RS2::EntityEllipse: ellipse = static_cast(e); startPoint = ellipse->getStartpoint(); endPoint = ellipse->getEndpoint(); center = ellipse->getCenter(); reversed = ellipse->isReversed(); break; default: continue; } // getting all intersections of this pattern line with the contour: QList is; for(auto loop: entities){ if (loop->isContainer()) { for(auto p: * static_cast(loop)){ RS_VectorSolutions sol = RS_Information::getIntersection(e, p, true); for (const RS_Vector& vp: sol) { if (vp.valid) { is.append(vp); RS_DEBUG->print(RS_Debug::D_DEBUGGING, " pattern line intersection: %f/%f", vp.x, vp.y); } } } } } QList is2; //to be filled with sorted intersections is2.append(startPoint); // sort the intersection points into is2 (only if there are intersections): if(is.size() == 1) { //only one intersection is2.append(is.first()); } else if(is.size() > 1) { RS_Vector sp = startPoint; double sa = center.angleTo(sp); if(ellipse ) sa=ellipse->getEllipseAngle(sp); bool done; double minDist; double dist = 0.0; RS_Vector av; RS_Vector v; RS_Vector last{}; do { // very long while(!done) loop done = true; minDist = RS_MAXDOUBLE; av.valid = false; for (int i = 0; i < is.size(); ++i) { v = is.at(i); double a; switch(e->rtti()){ case RS2::EntityLine: dist = sp.distanceTo(v); break; case RS2::EntityArc: case RS2::EntityCircle: a = center.angleTo(v); dist = reversed? fmod(sa - a + 2.*M_PI,2.*M_PI): fmod(a - sa + 2.*M_PI,2.*M_PI); break; case RS2::EntityEllipse: a = ellipse->getEllipseAngle(v); dist = reversed? fmod(sa - a + 2.*M_PI,2.*M_PI): fmod(a - sa + 2.*M_PI,2.*M_PI); break; default: break; } if (distRS_TOLERANCE) { is2.append(av); last = av; } is.removeOne(av); av.valid = false; } } while(!done); } is2.append(endPoint); // add small cut lines / arcs to tmp2: for (int i = 1; i < is2.size(); ++i) { auto v1 = is2.at(i-1); auto v2 = is2.at(i); if (line) { tmp2.addEntity(new RS_Line{&tmp2, v1, v2}); } else if (arc || circle) { if(fabs(center.angleTo(v2)-center.angleTo(v1)) > RS_TOLERANCE_ANGLE) {//don't create an arc with a too small angle tmp2.addEntity(new RS_Arc(&tmp2, RS_ArcData(center, center.distanceTo(v1), center.angleTo(v1), center.angleTo(v2), reversed))); } } } } // end for very very long for(auto e: tmp) loop // updating hatch / adding entities that are inside RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: cutting pattern carpet: OK"); //RS_EntityContainer* rubbish = new RS_EntityContainer(getGraphic()); // add the hatch pattern entities hatch = new RS_EntityContainer(this); hatch->setPen(hatch_pen); hatch->setLayer(hatch_layer); hatch->setFlag(RS2::FlagTemp); //calculateBorders(); for(auto e: tmp2){ RS_Vector middlePoint; RS_Vector middlePoint2; if (e->rtti()==RS2::EntityLine) { RS_Line* line = static_cast(e); middlePoint = line->getMiddlePoint(); middlePoint2 = line->getNearestDist(line->getLength()/2.1, line->getStartpoint()); } else if (e->rtti()==RS2::EntityArc) { RS_Arc* arc = static_cast(e); middlePoint = arc->getMiddlePoint(); middlePoint2 = arc->getNearestDist(arc->getLength()/2.1, arc->getStartpoint()); } else { middlePoint = RS_Vector{false}; middlePoint2 = RS_Vector{false}; } if (middlePoint.valid) { bool onContour=false; if (RS_Information::isPointInsideContour( middlePoint, this, &onContour) || RS_Information::isPointInsideContour(middlePoint2, this)) { RS_Entity* te = e->clone(); te->setPen(hatch_pen); te->setLayer(hatch_layer); te->reparent(hatch); hatch->addEntity(te); } } } addEntity(hatch); //getGraphic()->addEntity(rubbish); forcedCalculateBorders(); // deactivate contour: activateContour(false); updateRunning = false; RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Hatch::update: OK"); } /** * Activates of deactivates the hatch boundary. */ void RS_Hatch::activateContour(bool on) { RS_DEBUG->print("RS_Hatch::activateContour: %d", (int)on); for(auto e: entities){ if (!e->isUndone()) { if (!e->getFlag(RS2::FlagTemp)) { RS_DEBUG->print("RS_Hatch::activateContour: set visible"); e->setVisible(on); } else { RS_DEBUG->print("RS_Hatch::activateContour: entity temp"); } } else { RS_DEBUG->print("RS_Hatch::activateContour: entity undone"); } } RS_DEBUG->print("RS_Hatch::activateContour: OK"); } /** * Overrides drawing of subentities. This is only ever called for solid fills. */ void RS_Hatch::draw(RS_Painter* painter, RS_GraphicView* view, double& /*patternOffset*/) { if (!data.solid) { foreach (auto se, entities){ view->drawEntity(painter,se); } return; } //area of solid fill. Use polygon approximation, except trivial cases QPainterPath path; QList paClosed; QPolygon pa; if (needOptimization==true) { foreach (auto l, entities){ if (l->rtti()==RS2::EntityContainer) { RS_EntityContainer* loop = (RS_EntityContainer*)l; loop->optimizeContours(); } } needOptimization = false; } foreach (auto l, entities){ l->setLayer(getLayer()); if (l->rtti()==RS2::EntityContainer) { RS_EntityContainer* loop = (RS_EntityContainer*)l; // edges: for(auto e: *loop){ e->setLayer(getLayer()); switch (e->rtti()) { case RS2::EntityLine: { QPoint pt1(RS_Math::round(view->toGuiX(e->getStartpoint().x)), RS_Math::round(view->toGuiY(e->getStartpoint().y))); QPoint pt2(RS_Math::round(view->toGuiX(e->getEndpoint().x)), RS_Math::round(view->toGuiY(e->getEndpoint().y))); if(!pa.size() || (pa.last() - pt1).manhattanLength() >= 1) { pa << pt1; } pa << pt2; } break; case RS2::EntityArc: { QPolygon pa2; RS_Arc* arc=static_cast(e); painter->createArc(pa2, view->toGui(arc->getCenter()), view->toGuiDX(arc->getRadius()) ,arc->getAngle1(),arc->getAngle2(),arc->isReversed()); if(pa.size() &&pa2.size()&&(pa.last()-pa2.first()).manhattanLength()<1) pa2.remove(0,1); pa<(e); RS_Vector c=view->toGui(circle->getCenter()); double r=view->toGuiDX(circle->getRadius()); path.addEllipse(QPoint(c.x,c.y),r,r); } break; case RS2::EntityEllipse: if(static_cast(e)->isArc()) { QPolygon pa2; auto ellipse=static_cast(e); painter->createEllipse(pa2, view->toGui(ellipse->getCenter()), view->toGuiDX(ellipse->getMajorRadius()), view->toGuiDX(ellipse->getMinorRadius()), ellipse->getAngle() ,ellipse->getAngle1(),ellipse->getAngle2(),ellipse->isReversed() ); if(pa.size() && pa2.size()&&(pa.last()-pa2.first()).manhattanLength()<1) pa2.remove(0,1); pa<(e); painter->createEllipse(pa2, view->toGui(ellipse->getCenter()), view->toGuiDX(ellipse->getMajorRadius()), view->toGuiDX(ellipse->getMinorRadius()), ellipse->getAngle(), ellipse->getAngle1(), ellipse->getAngle2(), ellipse->isReversed() ); path.addPolygon(pa2); } break; default: break; } if( pa.size()>2 && pa.first() == pa.last()) { paClosed<2){ pa<brush()); const RS_Pen pen=painter->getPen(); painter->setBrush(pen.getColor()); painter->disablePen(); painter->drawPath(path); painter->setBrush(brush); painter->setPen(pen); } //must be called after update() double RS_Hatch::getTotalArea() { double totalArea=0.; // loops: for(auto l: entities){ if (l!=hatch && l->rtti()==RS2::EntityContainer) { totalArea += l->areaLineIntegral(); } } return totalArea; } double RS_Hatch::getDistanceToPoint( const RS_Vector& coord, RS_Entity** entity, RS2::ResolveLevel level, double solidDist) const { if (data.solid==true) { if (entity) { *entity = const_cast(this); } bool onContour; if (RS_Information::isPointInsideContour( coord, const_cast(this), &onContour)) { // distance is the snap range: return solidDist; } return RS_MAXDOUBLE; } else { return RS_EntityContainer::getDistanceToPoint(coord, entity, level, solidDist); } } void RS_Hatch::move(const RS_Vector& offset) { RS_EntityContainer::move(offset); update(); } void RS_Hatch::rotate(const RS_Vector& center, const double& angle) { RS_EntityContainer::rotate(center, angle); data.angle = RS_Math::correctAngle(data.angle+angle); update(); } void RS_Hatch::rotate(const RS_Vector& center, const RS_Vector& angleVector) { RS_EntityContainer::rotate(center, angleVector); data.angle = RS_Math::correctAngle(data.angle+angleVector.angle()); update(); } void RS_Hatch::scale(const RS_Vector& center, const RS_Vector& factor) { RS_EntityContainer::scale(center, factor); data.scale *= factor.x; update(); } void RS_Hatch::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) { RS_EntityContainer::mirror(axisPoint1, axisPoint2); double ang = axisPoint1.angleTo(axisPoint2); data.angle = RS_Math::correctAngle(data.angle + ang*2.0); update(); } void RS_Hatch::stretch(const RS_Vector& firstCorner, const RS_Vector& secondCorner, const RS_Vector& offset) { RS_EntityContainer::stretch(firstCorner, secondCorner, offset); update(); } /** * Dumps the point's data to stdout. */ std::ostream& operator << (std::ostream& os, const RS_Hatch& p) { os << " Hatch: " << p.getData() << "\n"; return os; }