From e97aa7d47e5594b81d8a5789a38876414562074e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Pascal?= <36571477+aurelienpascal@users.noreply.github.com> Date: Mon, 3 Mar 2025 17:35:13 +0100 Subject: [PATCH] TechDraw: Fix hatch drawing (#19458) * TechDraw: Fix hatch drawing (#16353) * TechDraw: Fix hatch drawing in Tech View --- src/Mod/TechDraw/App/DrawGeomHatch.cpp | 184 ++++++++++++------------- src/Mod/TechDraw/App/DrawGeomHatch.h | 4 +- src/Mod/TechDraw/App/HatchLine.cpp | 15 +- src/Mod/TechDraw/Gui/PATPathMaker.cpp | 51 +++---- src/Mod/TechDraw/Gui/PATPathMaker.h | 1 + 5 files changed, 109 insertions(+), 146 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawGeomHatch.cpp b/src/Mod/TechDraw/App/DrawGeomHatch.cpp index 2935199656..dd0fda8469 100644 --- a/src/Mod/TechDraw/App/DrawGeomHatch.cpp +++ b/src/Mod/TechDraw/App/DrawGeomHatch.cpp @@ -317,10 +317,14 @@ std::vector DrawGeomHatch::getTrimmedLines(DrawViewPart* source, Bnd_Box bBox; BRepBndLib::AddOptimal(face, bBox); bBox.SetGap(0.0); + gp_Vec translateVector(hatchOffset.x, hatchOffset.y, 0.); + auto cornerMin = bBox.CornerMin().Translated(-translateVector); + auto cornerMax = bBox.CornerMax().Translated(-translateVector); + bBox = Bnd_Box(cornerMin, cornerMax); for (auto& ls: lineSets) { PATLineSpec hl = ls.getPATLineSpec(); - std::vector candidates = DrawGeomHatch::makeEdgeOverlay(hl, bBox, scale); //completely cover face bbox with lines + std::vector candidates = DrawGeomHatch::makeEdgeOverlay(hl, bBox, scale, hatchRotation); //completely cover face bbox with lines //make Compound for this linespec BRep_Builder builder; @@ -331,14 +335,6 @@ std::vector DrawGeomHatch::getTrimmedLines(DrawViewPart* source, } TopoDS_Shape grid = gridComp; - if (hatchRotation != 0.0) { - double hatchRotationRad = hatchRotation * M_PI / 180.0; - gp_Ax1 gridAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Vec(gp::OZ().Direction())); - gp_Trsf xGridRotate; - xGridRotate.SetRotation(gridAxis, hatchRotationRad); - BRepBuilderAPI_Transform mkTransRotate(grid, xGridRotate, true); - grid = mkTransRotate.Shape(); - } gp_Trsf xGridTranslate; xGridTranslate.SetTranslation(DrawUtil::to(hatchOffset)); BRepBuilderAPI_Transform mkTransTranslate(grid, xGridTranslate, true); @@ -386,114 +382,104 @@ std::vector DrawGeomHatch::getTrimmedLines(DrawViewPart* source, } /* static */ -std::vector DrawGeomHatch::makeEdgeOverlay(PATLineSpec hatchLine, Bnd_Box bBox, double scale) +std::vector DrawGeomHatch::makeEdgeOverlay(PATLineSpec hatchLine, Bnd_Box bBox, double scale, double rotation) { - constexpr double RightAngleDegrees{90.0}; - constexpr double HalfCircleDegrees{180.0}; - std::vector result; + const size_t MaxNumberOfEdges = Preferences::getPreferenceGroup("PAT")->GetInt("MaxSeg", 10000l); + std::vector result; double minX, maxX, minY, maxY, minZ, maxZ; bBox.Get(minX, minY, minZ, maxX, maxY, maxZ); - //make the overlay bigger to cover rotations. might need to be bigger than 2x. - double widthX = maxX - minX; - double widthY = maxY - minY; - double width = std::max(widthX, widthY); + Base::Vector3d topLeft(minX, maxY, 0.); + Base::Vector3d topRight(maxX, maxY, 0.); + Base::Vector3d bottomLeft(minX, minY, 0.); + Base::Vector3d bottomRight(maxX, minY, 0.); - double centerX = (minX + maxX) / 2; - minX = centerX - width; - maxX = centerX + width; - double centerY = (minY + maxY) / 2; - minY = centerY - width; - maxY = centerY + width; + Base::Vector3d origin = hatchLine.getOrigin() * scale; + double interval = hatchLine.getInterval() * scale; + double offset = hatchLine.getOffset() * scale; + double angle = hatchLine.getAngle() + rotation; + origin.RotateZ(rotation * M_PI / 180.); - Base::Vector3d origin = hatchLine.getOrigin(); - double interval = hatchLine.getIntervalX() * scale; - double angle = hatchLine.getAngle(); + if (scale == 0. || interval == 0.) + return {}; - //only dealing with angles -180:180 for now - if (angle > RightAngleDegrees) { - angle = -(HalfCircleDegrees - angle); - } else if (angle < -RightAngleDegrees) { - angle = (HalfCircleDegrees + angle); + Base::Vector3d hatchDirection(cos(angle * M_PI / 180.), sin(angle * M_PI / 180.), 0.); + Base::Vector3d hatchPerpendicular(-hatchDirection.y, hatchDirection.x, 0.); + Base::Vector3d hatchIntervalAndOffset = offset * hatchDirection + interval * hatchPerpendicular; + + std::array orthogonalProjections = { + (topLeft - origin).Dot(hatchPerpendicular / interval), + (topRight - origin).Dot(hatchPerpendicular / interval), + (bottomLeft - origin).Dot(hatchPerpendicular / interval), + (bottomRight - origin).Dot(hatchPerpendicular / interval) + }; + auto minMaxIterators = std::minmax_element(orthogonalProjections.begin(), orthogonalProjections.end()); + int firstRepeatIndex = ceil(*minMaxIterators.first); + int lastRepeatIndex = floor(*minMaxIterators.second); + + std::vector dashParams = hatchLine.getDashParms().get(); + double globalDashStep = 0.; + if (dashParams.empty()) { + // we define a single dash with length equal to twice the diagonal of the bounding box + double diagonalLength = (topRight - bottomLeft).Length(); + dashParams.push_back(2. * diagonalLength); + globalDashStep = diagonalLength; } - double slope = hatchLine.getSlope(); - - if (angle == 0.0) { //odd case 1: horizontal lines - interval = hatchLine.getInterval() * scale; - double atomY = origin.y; - int repeatUp = (int) fabs((maxY - atomY)/interval); - int repeatDown = (int) fabs(((atomY - minY)/interval)); - int repeatTotal = repeatUp + repeatDown + 1; - double yStart = atomY - repeatDown * interval; - - // make repeats - for (int i = 0; i < repeatTotal; i++) { - Base::Vector3d newStart(minX, yStart + float(i)*interval, 0); - Base::Vector3d newEnd(maxX, yStart + float(i)*interval, 0); - TopoDS_Edge newLine = makeLine(newStart, newEnd); - result.push_back(newLine); + else { + for (auto& x : dashParams) { + x *= scale; + globalDashStep += std::abs(x); } - } else if (angle == RightAngleDegrees || - angle == -RightAngleDegrees) { //odd case 2: vertical lines - interval = hatchLine.getInterval() * scale; - double atomX = origin.x; - int repeatRight = (int) fabs((maxX - atomX)/interval); - int repeatLeft = (int) fabs((atomX - minX)/interval); - int repeatTotal = repeatRight + repeatLeft + 1; - double xStart = atomX - repeatLeft * interval; + } + if (globalDashStep == 0.) { + return {}; + } - // make repeats - for (int i = 0; i < repeatTotal; i++) { - Base::Vector3d newStart(xStart + float(i)*interval, minY, 0); - Base::Vector3d newEnd(xStart + float(i)*interval, maxY, 0); - TopoDS_Edge newLine = makeLine(newStart, newEnd); - result.push_back(newLine); + // we handle hatch as a set of parallel lines made of dashes, here we loop on each line + for (int i = firstRepeatIndex ; i <= lastRepeatIndex ; ++i) { + Base::Vector3d currentOrigin = origin + static_cast(i) * hatchIntervalAndOffset; + + int firstDashIndex, lastDashIndex; + if (std::abs(hatchDirection.x) > std::abs(hatchDirection.y)) { // we compute intersections with minX and maxX + firstDashIndex = (hatchDirection.x > 0.) + ? std::floor((minX - currentOrigin.x) / (globalDashStep * hatchDirection.x)) + : std::floor((maxX - currentOrigin.x) / (globalDashStep * hatchDirection.x)); + lastDashIndex = (hatchDirection.x > 0.) + ? std::ceil((maxX - currentOrigin.x) / (globalDashStep * hatchDirection.x)) + : std::ceil((minX - currentOrigin.x) / (globalDashStep * hatchDirection.x)); } -//TODO: check if this makes 2-3 extra lines. might be some "left" lines on "right" side of vv - } else if (angle > 0) { //oblique (bottom left -> top right) - //ex: 60, 0,0, 0,4.0, 25, -25 -// Base::Console().Message("TRACE - DGH-makeEdgeOverlay - making angle > 0\n"); - double xLeftAtom = origin.x + (minY - origin.y)/slope; //the "atom" is the fill line that passes through the - //pattern-origin (not necc. R2 origin) - double xRightAtom = origin.x + (maxY - origin.y)/slope; - int repeatRight = (int) fabs((maxX - xLeftAtom)/interval); - int repeatLeft = (int) fabs((xRightAtom - minX)/interval); - - double leftStartX = xLeftAtom - (repeatLeft * interval); - double leftEndX = xRightAtom - (repeatLeft * interval); - int repeatTotal = repeatRight + repeatLeft + 1; - - //make repeats - for (int i = 0; i < repeatTotal; i++) { - Base::Vector3d newStart(leftStartX + (float(i) * interval), minY, 0); - Base::Vector3d newEnd (leftEndX + (float(i) * interval), maxY, 0); - TopoDS_Edge newLine = makeLine(newStart, newEnd); - result.push_back(newLine); + else { // we compute intersections with minY and maxY + firstDashIndex = (hatchDirection.y > 0.) + ? std::floor((minY - currentOrigin.y) / (globalDashStep * hatchDirection.y)) + : std::floor((maxY - currentOrigin.y) / (globalDashStep * hatchDirection.y)); + lastDashIndex = (hatchDirection.y > 0.) + ? std::ceil((maxY - currentOrigin.y) / (globalDashStep * hatchDirection.y)) + : std::ceil((minY - currentOrigin.y) / (globalDashStep * hatchDirection.y)); } - } else { //oblique (bottom right -> top left) - // ex: -60, 0,0, 0,4.0, 25.0, -12.5, 12.5, -6 -// Base::Console().Message("TRACE - DGH-makeEdgeOverlay - making angle < 0\n"); - double xRightAtom = origin.x + ((minY - origin.y)/slope); //x-coord of left end of Atom line - double xLeftAtom = origin.x + ((maxY - origin.y)/slope); //x-coord of right end of Atom line - int repeatRight = (int) fabs((maxX - xLeftAtom)/interval); //number of lines to Right of Atom - int repeatLeft = (int) fabs((xRightAtom - minX)/interval); //number of lines to Left of Atom - double leftEndX = xLeftAtom - (repeatLeft * interval); - double leftStartX = xRightAtom - (repeatLeft * interval); - int repeatTotal = repeatRight + repeatLeft + 1; - // make repeats - for (int i = 0; i < repeatTotal; i++) { - Base::Vector3d newStart(leftStartX + float(i)*interval, minY, 0); - Base::Vector3d newEnd(leftEndX + float(i)*interval, maxY, 0); - TopoDS_Edge newLine = makeLine(newStart, newEnd); - result.push_back(newLine); + for (int j = firstDashIndex ; j < lastDashIndex ; ++j) { + Base::Vector3d current = currentOrigin + static_cast(j) * globalDashStep * hatchDirection; + for (auto dashParamsIterator = dashParams.begin() ; dashParamsIterator != dashParams.end() ; ++dashParamsIterator) { + double len = *dashParamsIterator; + Base::Vector3d next = current + std::abs(len) * hatchDirection; + if (len > 0. && (current.x >= minX || next.x >= minX) && (current.x <= maxX || next.x <= maxX) + && (current.y >= minY || next.y >= minY) && (current.y <= maxY || next.y <= maxY)) { + TopoDS_Edge newLine = makeLine(current, next); + result.push_back(newLine); + } + std::swap(current, next); + } + } + + if (result.size() > MaxNumberOfEdges) { + return {}; } } return result; } -TopoDS_Edge DrawGeomHatch::makeLine(Base::Vector3d s, Base::Vector3d e) +TopoDS_Edge DrawGeomHatch::makeLine(const Base::Vector3d& s, const Base::Vector3d& e) { gp_Pnt start(s.x, s.y, 0.0); gp_Pnt end(e.x, e.y, 0.0); @@ -527,7 +513,7 @@ std::vector DrawGeomHatch::getFaceOverlay(int iFace) for (auto& ls: m_lineSets) { PATLineSpec hl = ls.getPATLineSpec(); - std::vector candidates = DrawGeomHatch::makeEdgeOverlay(hl, bBox, ScalePattern.getValue()); + std::vector candidates = DrawGeomHatch::makeEdgeOverlay(hl, bBox, ScalePattern.getValue(), PatternRotation.getValue()); std::vector resultGeoms; for (auto& e: candidates) { TechDraw::BaseGeomPtr base = BaseGeom::baseFactory(e); diff --git a/src/Mod/TechDraw/App/DrawGeomHatch.h b/src/Mod/TechDraw/App/DrawGeomHatch.h index eb78e9acf7..82dcc101bd 100644 --- a/src/Mod/TechDraw/App/DrawGeomHatch.h +++ b/src/Mod/TechDraw/App/DrawGeomHatch.h @@ -94,8 +94,8 @@ public: Base::Vector3d hatchOffset = Base::Vector3d(0.0, 0.0, 0.0)); static std::vector makeEdgeOverlay(PATLineSpec hatchLine, Bnd_Box bBox, - double scale); - static TopoDS_Edge makeLine(Base::Vector3d start, Base::Vector3d end); + double scale, double rotation); + static TopoDS_Edge makeLine(const Base::Vector3d& start, const Base::Vector3d& end); static std::vector getDecodedSpecsFromFile(std::string fileSpec, std::string myPattern); static TopoDS_Face extractFace(DrawViewPart* source, int iface ); static std::string prefGeomHatchFile(); diff --git a/src/Mod/TechDraw/App/HatchLine.cpp b/src/Mod/TechDraw/App/HatchLine.cpp index 88b0041746..2c2814cac1 100644 --- a/src/Mod/TechDraw/App/HatchLine.cpp +++ b/src/Mod/TechDraw/App/HatchLine.cpp @@ -110,21 +110,8 @@ Base::Vector3d LineSet::getUnitDir() Base::Vector3d LineSet::getUnitOrtho() { - Base::Vector3d result; Base::Vector3d unit = getUnitDir(); - Base::Vector3d X(1.0, 0.0, 0.0); - Base::Vector3d Y(0.0, 1.0, 0.0); - if (unit.IsEqual(X, 0.000001)) { - result = Y; - } else if (unit.IsEqual(Y, 0.000001)) { - result = X; - } else { - double unitX = unit.x; - double unitY = unit.y; - result = Base::Vector3d(unitY, -unitX, 0.0); //perpendicular - } - result.Normalize(); //probably redundant - return result; + return Base::Vector3d(-unit.y, unit.x, 0.0); } diff --git a/src/Mod/TechDraw/Gui/PATPathMaker.cpp b/src/Mod/TechDraw/Gui/PATPathMaker.cpp index 57b41963e0..8b3d71fe7f 100644 --- a/src/Mod/TechDraw/Gui/PATPathMaker.cpp +++ b/src/Mod/TechDraw/Gui/PATPathMaker.cpp @@ -57,37 +57,10 @@ void PATPathMaker::lineSetToFillItems(LineSet& ls) m_segCount = 0; QPen pen = getPen(); for (auto& geom : ls.getGeoms()) { - //geom is a tdGeometry representation of 1 line in the pattern - if (ls.isDashed()) { - double offset = 0.0; - Base::Vector3d pStart = ls.getPatternStartPoint(geom, offset, m_fillScale); - offset = Rez::guiX(offset); - Base::Vector3d gStart(geom->getStartPoint().x, - geom->getStartPoint().y, - 0.0); - Base::Vector3d gEnd(geom->getEndPoint().x, - geom->getEndPoint().y, - 0.0); - if (DrawUtil::fpCompare(offset, 0.0, 0.00001)) { //no offset - QGraphicsPathItem* item1 = lineFromPoints(pStart, gEnd, ls.getDashSpec()); - item1->setPen(pen); - m_fillItems.push_back(item1); - if (!pStart.IsEqual(gStart, 0.00001)) { - QGraphicsPathItem* item2 = lineFromPoints(pStart, gStart, ls.getDashSpec().reversed()); - item2->setPen(pen); - m_fillItems.push_back(item2); - } - } else { //offset - pattern start not in g - double remain = dashRemain(decodeDashSpec(ls.getDashSpec()), offset); - QGraphicsPathItem* shortItem = geomToStubbyLine(geom, remain, ls); - shortItem->setPen(pen); - m_fillItems.push_back(shortItem); - } - } else { //not dashed - QGraphicsPathItem* fillItem = geomToLine(geom, ls); - fillItem->setPen(pen); - m_fillItems.push_back(fillItem); - } + //geom is a tdGeometry representation of 1 line in the pattern //not dashed + QGraphicsPathItem* fillItem = simpleLine(geom); + fillItem->setPen(pen); + m_fillItems.push_back(fillItem); if (m_segCount > m_maxSeg) { Base::Console().Warning("PAT segment count exceeded: %ld\n", m_segCount); @@ -124,6 +97,22 @@ QGraphicsPathItem* PATPathMaker::geomToLine(TechDraw::BaseGeomPtr base, LineSet return fillItem; } +/// create a simple line from geometry +QGraphicsPathItem* PATPathMaker::simpleLine(TechDraw::BaseGeomPtr base) +{ + QGraphicsPathItem* fillItem = new QGraphicsPathItem(m_parent); + Base::Vector3d start(base->getStartPoint().x, + base->getStartPoint().y, + 0.0); + Base::Vector3d end(base->getEndPoint().x, + base->getEndPoint().y, + 0.0); + fillItem->setPath(dashedPPath(std::vector(), + Rez::guiX(start), + Rez::guiX(end))); + return fillItem; +} + //! make a fragment (length = remain) of a dashed line, with pattern starting at +offset QGraphicsPathItem* PATPathMaker::geomToStubbyLine(TechDraw::BaseGeomPtr base, double remain, LineSet& ls) diff --git a/src/Mod/TechDraw/Gui/PATPathMaker.h b/src/Mod/TechDraw/Gui/PATPathMaker.h index e54966f82f..e51a6e559b 100644 --- a/src/Mod/TechDraw/Gui/PATPathMaker.h +++ b/src/Mod/TechDraw/Gui/PATPathMaker.h @@ -50,6 +50,7 @@ public: protected: QGraphicsPathItem* geomToLine(TechDraw::BaseGeomPtr base, TechDraw::LineSet& ls); + QGraphicsPathItem* simpleLine(TechDraw::BaseGeomPtr base); QGraphicsPathItem* geomToStubbyLine(TechDraw::BaseGeomPtr base, double offset, TechDraw::LineSet& ls); QGraphicsPathItem* lineFromPoints(Base::Vector3d start, Base::Vector3d end, TechDraw::DashSpec ds); std::vector offsetDash(const std::vector dv, const double offset);