/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include #include "SvgImportUtils.h" #include "DbCurve.h" #include #include "Ge/GeCompositeCurve3d.h" namespace OdGeValidationUtils { extern bool estimateCurvesIntersections( const OdGeCurve3d&, const OdGeInterval&, const OdGeCurve3d&, const OdGeInterval&, const OdGeTol&, OdArray&, bool = true, bool = false); } void CSvgPathLoop::appendCurve(OdGeCurve3d* pCurve) { if (pCurve) m_curves.append(pCurve); } void CSvgPathLoop::getCurves(OdArray& arr) const { for (auto curve : m_curves) { if (curve->type() == OdGe::kCompositeCrv3d) continue; arr.append(curve.get()); } } bool CSvgPathLoop::isInside(const CSvgPathLoop& otherLoop) const { if (m_extents.isDisjoint(otherLoop.m_extents)) return false; bool bRes = false; for (const auto& pointTest : otherLoop.m_loopPoints) { const OdInt32 iSizePoints = m_loopPoints.size() - 1; double dAngle = 0.0; for (OdInt32 iPoint = 0; iPoint < iSizePoints; iPoint++) { OdGeVector2d inputVec1 = m_loopPoints.getAt(iPoint) - pointTest; OdGeVector2d inputVec2 = m_loopPoints.getAt(iPoint + 1) - pointTest; dAngle += inputVec1.angleToCCW(inputVec2); } OdGeVector2d inputVec1 = m_loopPoints.getAt(iSizePoints) - pointTest; OdGeVector2d inputVec2 = m_loopPoints.getAt(0) - pointTest; dAngle += inputVec1.angleToCCW(inputVec2); if (OdEqual(Od_abs(dAngle), Oda2PI)) { bRes = true; break; } } return bRes; } bool CSvgPathLoop::isInsideByRay(const CSvgPathLoop& otherLoop) const { if (m_extents.isDisjoint(otherLoop.m_extents)) return false; bool bRes = false; for (const auto& pointTest : otherLoop.m_loopPoints) { OdGePoint3d start = OdGePoint3d(pointTest.x, pointTest.y, 0); OdGePoint3d end = start + OdGeVector3d(1, 0, 0) * (2 * std::fabs(m_extents.maxPoint().x - pointTest.x)); OdGeLineSeg3d line(start, end); OdArray points; if (this->intersectWith(&line, &points) && (points.size() % 2 != 0)) { bRes = true; break; } } return bRes; } bool CSvgPathLoop::intersectWith(const CSvgPathLoop& otherLoop, OdArray* points) const { bool res = false; for (auto curve : otherLoop.m_curves) { if (curve->type() == OdGe::kCompositeCrv3d) continue; if (this->intersectWith(curve, points)) res = true; } return res; } bool CSvgPathLoop::intersectWith(const OdGeCurve3d* pCurve, OdArray* points) const { if (!pCurve) return false; bool res = false; for (auto curve : m_curves) { if (curve->type() == OdGe::kCompositeCrv3d) continue; OdGeInterval interval1; pCurve->getInterval(interval1); OdGeInterval interval2; curve->getInterval(interval2); OdArray arrInters; bool bHasInt = false; try { bHasInt = OdGeValidationUtils:: estimateCurvesIntersections(*pCurve, interval1, *curve, interval2, OdGeContext::gTol, arrInters); } catch(...) { } if (bHasInt && !arrInters.isEmpty()) { res = true; if (points) { for (const auto& pnt : arrInters) { if (!points->contains(pnt) && !isXTangentPoint(curve, pnt)) points->append(pnt); } } } } return res; } bool CSvgPathLoop::isXTangentPoint(const OdGeCurve3d* pCurve, const OdGePoint3d& pnt) { if (!pCurve) return false; double dParam(.0); if (!pCurve->isOn(pnt, dParam)) return false; OdGeVector3dArray derivatives; pCurve->evalPoint(dParam, 1, derivatives); if (derivatives.isEmpty()) return false; if (derivatives[0].isParallelTo(OdGeVector3d(1, 0, 0))) return true; return false; } double CSvgPathLoop::calcPolygonSignedArea(const OdGePoint3dArray& pnts, bool isClosed) { if (pnts.isEmpty()) return false; double res = 0.; for (OdUInt32 iPoint = 1; iPoint < pnts.size(); ++iPoint) { const OdGePoint3d& p0 = pnts[iPoint - 1]; const OdGePoint3d& p1 = pnts[iPoint]; res += (p0.x * p1.y - p0.y * p1.x); } if (!isClosed) { const OdGePoint3d& p0 = pnts[pnts.size() - 1]; const OdGePoint3d& p1 = pnts[0]; res += (p0.x * p1.y - p0.y * p1.x); } return 0.5 * res; } bool CSvgPathLoop::calcClockWise() const { OdGePoint3dArray pnts; for (auto curve : m_curves) { if (curve->type() == OdGe::kCompositeCrv3d) continue; OdGePoint3dArray pointArray; if (curve->type() == OdGe::kLineSeg3d) curve->appendSamplePoints(NULL, .0, pointArray); else curve->appendSamplePoints(10, pointArray); pointArray.removeLast(); pnts.append(pointArray); } return !OdPositive(calcPolygonSignedArea(pnts, false)); } void CSvgPathLoop::setCurves(const OdDbObjectIdArray& entIds) { m_entIds = entIds; OdGePoint3d curentEndPnt; for (auto id : entIds) { OdDbCurvePtr pDbCurve = OdDbCurve::cast(id.safeOpenObject()); if (pDbCurve.isNull()) continue; OdGeCurve3d* pGeCurve; if (pDbCurve->getOdGeCurve(pGeCurve) == eOk && pGeCurve) { if (m_curves.isEmpty()) pGeCurve->hasEndPoint(curentEndPnt); else { OdGePoint3d endPoint, startPnt; pGeCurve->hasEndPoint(endPoint); pGeCurve->hasStartPoint(startPnt); if (!curentEndPnt.isEqualTo(startPnt) && curentEndPnt.isEqualTo(endPoint)) pGeCurve->reverseParam(); pGeCurve->hasEndPoint(curentEndPnt); } if (pGeCurve->type() == OdGe::kCompositeCrv3d) m_curves.append(static_cast(pGeCurve)->getCurveList()); m_curves.append(pGeCurve); } } } void CSvgPathLoop::setSamplePoints() { if (m_curves.size() == 1) { OdGePoint3dArray pointArray; m_curves.first()->appendSamplePoints(20, pointArray); for (auto& pnt : pointArray) m_loopPoints.append(pnt.convert2d()); m_extents.addExt(m_curves.first()->getGeomExtents()); } else { for (auto curve : m_curves) { if (curve->type() == OdGe::kCompositeCrv3d) continue; OdGePoint3d pnt; curve->hasStartPoint(pnt); m_loopPoints.append(pnt.convert2d()); m_extents.addExt(curve->getGeomExtents()); } } } void CSvgPathLoop::init(const OdDbObjectIdArray& entIds) { setCurves(entIds); setSamplePoints(); m_bIsClockWise = calcClockWise(); } bool LoopNode::isZeroLoop() const { const LoopNode* curNode = this; int nZeroFactor = 0; while (curNode) { bool bCW = curNode->m_loop->isClockWise(); nZeroFactor = nZeroFactor + (bCW ? 1 : -1); curNode = curNode->m_parent; } return nZeroFactor == 0; } bool LoopNode::isExternal() const { if (!m_parent) return true; return (this->isZeroLoop() || m_parent->isZeroLoop()); } bool LoopNode::isNewHatch() const { if (!m_parent) return false; const LoopNode* curNode = this; bool bCW = curNode->m_loop->isClockWise(); curNode = curNode->m_parent; while (curNode) { if (curNode->m_loop->isClockWise() != bCW) return false; curNode = curNode->m_parent; } return m_children.empty(); } void LoopNode::buildChildren(LoopNode* parent, const OdArray& allLoops, std::vector& processed) { for (unsigned int i = 0; i < allLoops.length(); i++) { if (processed[i]) continue; // Check if the current outline is contained in the parent if (parent->m_loop->isInsideByRay(allLoops[i]) || parent->m_loop->intersectWith(allLoops[i])) { // Check that this path is not contained in other raw paths bool isImmediateChild = true; for (unsigned int j = 0; j < allLoops.length(); j++) { if (i == j || processed[j]) continue; if (allLoops[j].isInsideByRay(allLoops[i]) && parent->m_loop->isInsideByRay(allLoops[j])) { isImmediateChild = false; break; } } if (isImmediateChild) { std::unique_ptr pChild(new LoopNode(&allLoops[i], parent)); processed[i] = true; // Recursively build children for this contour buildChildren(pChild.get(), allLoops, processed); parent->m_children.push_back(std::move(pChild)); } } } } bool LoopNode::buildLoopHierarchy(const OdArray& allLoops, std::vector>& hierarchy) { std::vector processed(allLoops.length(), false); // First we find all the outer contours (those that are not contained in others) for (unsigned int i = 0; i < allLoops.length(); i++) { if (processed[i]) continue; bool isOuter = true; for (unsigned int j = 0; j < allLoops.length(); j++) { if (i == j) continue; if (allLoops[j].intersectWith(allLoops[i])) continue; if (allLoops[j].isInsideByRay(allLoops[i])) { isOuter = false; break; } } if (isOuter) { std::unique_ptr pNode(new LoopNode(&allLoops[i], nullptr)); processed[i] = true; // Find all nested contours for this outer one buildChildren(pNode.get(), allLoops, processed); hierarchy.push_back(std::move(pNode)); } } return true; }