#include "nmWxDFNGenerateDlg.h" #include "nmDataAnalyzeManager.h" #include "nmObjLineCrack.h" #include "ZxSubAxisX.h" #include "ZxSubAxisY.h" #include "ZxPlot.h" #include "ZxObjBase.h" #include "nmDataOutline.h" #include "nmGuiPlot.h" #include #include #include #include nmWxDFNGenerateDlg::nmWxDFNGenerateDlg(QWidget* parent, nmGuiPlot* plot, nmDataOutline* outline) : iDlgBase(parent), m_pPlot(plot), m_pOutline(outline) { setWindowTitle(tr("Generate DFN")); setMinimumWidth(360); QVBoxLayout *mainL = new QVBoxLayout(this); // 裂缝数量 QHBoxLayout *cntL = new QHBoxLayout(); cntL->addWidget(new QLabel(tr("Number of fractures:"), this)); m_pCountSpin = new QSpinBox(this); m_pCountSpin->setRange(1, 1000); m_pCountSpin->setValue(5); cntL->addWidget(m_pCountSpin); mainL->addLayout(cntL); // 长度范围 QHBoxLayout *lenRangeL = new QHBoxLayout(); lenRangeL->addWidget(new QLabel(tr("Range of fracture length:"), this)); m_pMinLengthSpin = new QDoubleSpinBox(this); m_pMinLengthSpin->setRange(0.0, 1e6); m_pMinLengthSpin->setDecimals(2); m_pMinLengthSpin->setValue(100.0); lenRangeL->addWidget(m_pMinLengthSpin); m_pMaxLengthSpin = new QDoubleSpinBox(this); m_pMaxLengthSpin->setRange(0.0, 1e6); m_pMaxLengthSpin->setDecimals(2); m_pMaxLengthSpin->setValue(250.0); lenRangeL->addWidget(m_pMaxLengthSpin); mainL->addLayout(lenRangeL); // 角度范围 QHBoxLayout *angleRangeL = new QHBoxLayout(); angleRangeL->addWidget(new QLabel(tr("Range of fracture angle:"), this)); m_pMinAngleSpin = new QDoubleSpinBox(this); m_pMinAngleSpin->setRange(0.0, 360.0); m_pMinAngleSpin->setDecimals(1); m_pMinAngleSpin->setValue(0.0); angleRangeL->addWidget(m_pMinAngleSpin); m_pMaxAngleSpin = new QDoubleSpinBox(this); m_pMaxAngleSpin->setRange(0.0, 360.0); m_pMaxAngleSpin->setDecimals(1); m_pMaxAngleSpin->setValue(180.0); angleRangeL->addWidget(m_pMaxAngleSpin); mainL->addLayout(angleRangeL); // 分布方式 QHBoxLayout *distL = new QHBoxLayout(); distL->addWidget(new QLabel(tr("Distribution mode:"), this)); m_pDistCombo = new QComboBox(this); m_pDistCombo->addItem(tr("Uniform distribution")); m_pDistCombo->addItem(tr("Exponential distribution")); m_pDistCombo->addItem(tr("Normal distribution")); distL->addWidget(m_pDistCombo); mainL->addLayout(distL); // 按钮区 QHBoxLayout *btnL = new QHBoxLayout(); btnL->addStretch(); m_pBtnGenerate = new QPushButton(tr("Generate"), this); m_pBtnCancel = new QPushButton(tr("Cancel"), this); btnL->addWidget(m_pBtnGenerate); btnL->addWidget(m_pBtnCancel); mainL->addLayout(btnL); // 不显示上下箭头 m_pCountSpin -> setButtonSymbols(QAbstractSpinBox::NoButtons); m_pMinLengthSpin ->setButtonSymbols(QAbstractSpinBox::NoButtons); m_pMaxLengthSpin ->setButtonSymbols(QAbstractSpinBox::NoButtons); m_pMinAngleSpin ->setButtonSymbols(QAbstractSpinBox::NoButtons); m_pMaxAngleSpin ->setButtonSymbols(QAbstractSpinBox::NoButtons); connect(m_pBtnGenerate, SIGNAL(clicked()), this, SLOT(onBtnGenerateClicked())); connect(m_pBtnCancel, SIGNAL(clicked()), this, SLOT(onBtnCancelClicked())); } void nmWxDFNGenerateDlg::onBtnGenerateClicked() { // 验证参数合理性 if(m_pMinLengthSpin->value() <= 0 || m_pMaxLengthSpin->value() <= 0 || count() <= 0) { QMessageBox::warning(this, tr("Invalid parameters"), tr("Length and count must be positive.")); return; } if(m_pMinAngleSpin->value() > m_pMaxAngleSpin->value()) { QMessageBox::warning(this, tr("Invalid parameters"), tr("Minimum angle must be less than or equal to maximum angle.")); return; } // 清空原来的DFN图元数据 if(!nmDataAnalyzeManager::getCurrentInstance()->getDFNFractureDataList().isEmpty()) { nmGuiPlot* pCurPlot = nmDataAnalyzeManager::getCurrentInstance()->getPlot(); pCurPlot->deleteAllDFNPlots(); } generateFractures(); accept(); } void nmWxDFNGenerateDlg::onBtnCancelClicked() { reject(); } double nmWxDFNGenerateDlg::sampleLength() const { double mn = m_pMinLengthSpin->value(); double mx = m_pMaxLengthSpin->value(); int idx = m_pDistCombo->currentIndex(); switch(idx) { case 0: // 均匀 return mn + (mx - mn) * (double(qrand())/RAND_MAX); case 1: // 指数 { // 指数分布采样,期望值 = (mn+mx)/2 double lambda = 2.0 / (mn + mx); // 参数lambda double u = double(qrand())/RAND_MAX; while(u == 0) u = double(qrand())/RAND_MAX; // 避免log(0) double sample = -log(u) / lambda; // 将采样值限制在[mn, mx]范围内 if(sample < mn) sample = mn; if(sample > mx) sample = mx; return sample; } case 2: // 正态 { // 均值设为 (mn+mx)/2,标准差设为 (mx-mn)/6 double mean = (mn + mx) / 2.0; double sigma = (mx - mn) / 6.0; // Box-Muller变换生成正态分布随机数 static bool hasSpare = false; static double spare; if(hasSpare) { hasSpare = false; double sample = spare * sigma + mean; // 限制在范围内 if(sample < mn) sample = mn; if(sample > mx) sample = mx; return sample; } hasSpare = true; double u = double(qrand())/RAND_MAX; double v = double(qrand())/RAND_MAX; while(u == 0) u = double(qrand())/RAND_MAX; while(v == 0) v = double(qrand())/RAND_MAX; double mag = sigma * sqrt(-2.0 * log(u)); spare = mag * cos(2.0 * M_PI * v); double sample = mag * sin(2.0 * M_PI * v) + mean; // 限制在范围内 if(sample < mn) sample = mn; if(sample > mx) sample = mx; return sample; } } return mn; } double nmWxDFNGenerateDlg::sampleAngle() const { double mn = m_pMinAngleSpin->value(); double mx = m_pMaxAngleSpin->value(); int idx = m_pDistCombo->currentIndex(); switch(idx) { case 0: return (mn + (mx - mn) * (double(qrand())/RAND_MAX)) * M_PI/180.0; // 转成弧度 case 1: { // 指数分布采样,转成弧度 double lambda = 2.0 / (mn + mx); double u = double(qrand())/RAND_MAX; while(u == 0) u = double(qrand())/RAND_MAX; double sample = -log(u) / lambda; // 限制在范围内 if(sample < mn) sample = mn; if(sample > mx) sample = mx; return sample * M_PI/180.0; } case 2: { // 正态分布采样,转成弧度 double mean = (mn + mx) / 2.0; double sigma = (mx - mn) / 6.0; // Box-Muller变换 static bool hasSpareAngle = false; static double spareAngle; if(hasSpareAngle) { hasSpareAngle = false; double sample = spareAngle * sigma + mean; if(sample < mn) sample = mn; if(sample > mx) sample = mx; return sample * M_PI/180.0; } hasSpareAngle = true; double u = double(qrand())/RAND_MAX; double v = double(qrand())/RAND_MAX; while(u == 0) u = double(qrand())/RAND_MAX; while(v == 0) v = double(qrand())/RAND_MAX; double mag = sigma * sqrt(-2.0 * log(u)); spareAngle = mag * cos(2.0 * M_PI * v); double sample = mag * sin(2.0 * M_PI * v) + mean; if(sample < mn) sample = mn; if(sample > mx) sample = mx; return sample * M_PI/180.0; } } return mn * M_PI/180.0; } // 计算点到线段的最短距离 double nmWxDFNGenerateDlg::pointToLineDistance(const QPointF& point, const QLineF& line) const { QPointF p1 = line.p1(); QPointF p2 = line.p2(); double A = point.x() - p1.x(); double B = point.y() - p1.y(); double C = p2.x() - p1.x(); double D = p2.y() - p1.y(); double dot = A * C + B * D; double lenSq = C * C + D * D; if (lenSq == 0) { // 线段退化为点的情况 return QLineF(point, p1).length(); } double param = dot / lenSq; QPointF closestPoint; if (param < 0) { // 最近点是线段起点 closestPoint = p1; } else if (param > 1) { // 最近点是线段终点 closestPoint = p2; } else { // 最近点在线段上 closestPoint = QPointF(p1.x() + param * C, p1.y() + param * D); } return QLineF(point, closestPoint).length(); } // 检查两条线段是否过于接近 bool nmWxDFNGenerateDlg::isLinesTooClose(const QLineF& line1, const QLineF& line2, double tolerance) const { QPointF ip; // 检查线段数学相交 if(line1.intersect(line2, &ip) == QLineF::BoundedIntersection) { return true; } // 检查端点到线段距离(四个距离) double dist1 = pointToLineDistance(line1.p1(), line2); // 新线段端点1到已存在线段 double dist2 = pointToLineDistance(line1.p2(), line2); // 新线段端点2到已存在线段 double dist3 = pointToLineDistance(line2.p1(), line1); // 已存在线段端点1到新线段 double dist4 = pointToLineDistance(line2.p2(), line1); // 已存在线段端点2到新线段 // 找最小距离 double minDist = dist1; if (dist2 < minDist) minDist = dist2; if (dist3 < minDist) minDist = dist3; if (dist4 < minDist) minDist = dist4; return minDist < tolerance; } // 安全距离计算函数 double nmWxDFNGenerateDlg::calculateSafetyMargin(double fractureLength) const { if (fractureLength < 50.0) { return fractureLength * 5; } else if (fractureLength < 100.0) { return fractureLength * 1.5; } else { return fractureLength * 1; } //return fractureLength; } void nmWxDFNGenerateDlg::generateFractures() { // 获取用户输入的裂缝数量 int cnt = count(); // 获取绘图和轮廓上下文(由主窗口传入) nmGuiPlot* plot = m_pPlot; if(!plot || !plot->m_pPlot) { return; } nmDataOutline* outline = m_pOutline; NM_Data_Outline_Type type = NM_Rect_Outline_Type; if (outline) { type = outline->getOutlineType(); } // 圆心及半径,若为圆形轮廓 QPointF center; double R = 0; // 初始化边界框为一个默认的大矩形 double minX = -1000, maxX = 1000; double minY = -1000, maxY = 1000; if (outline && type == NM_Round_Outline_Type) { center = outline->getCenter(); R = outline->getRadius(); } else if (outline && type == NM_Polygon_Outline_Type) { // 多边形:获取顶点集合 QVector pts = outline->getOutlinePoints(); if (!pts.isEmpty()) { // 计算包围矩形,仅用于采样范围 QRectF br = QPolygonF(pts).boundingRect(); minX = br.left(); maxX = br.right(); minY = br.top(); maxY = br.bottom(); } } else if (outline && type == NM_Rect_Outline_Type) { // 矩形 QVector pts = outline->getOutlinePoints(); if (!pts.isEmpty()) { minX = maxX = pts[0].x(); minY = maxY = pts[0].y(); for (int i = 1; i < pts.size(); ++i) { minX = qMin(minX, pts[i].x()); maxX = qMax(maxX, pts[i].x()); minY = qMin(minY, pts[i].y()); maxY = qMax(maxY, pts[i].y()); } } } // 用于存放已成功放置的线段,避免后续相交 QVector existing; const int maxRetries = 100; // 每条裂缝最多尝试的随机放置次数 qsrand(QTime::currentTime().msec()); // 设置随机种子 int placed = 0; // 记录实际放置成功的裂缝数 for(int i = 0; i < cnt; ++i) { QLineF line; bool placedThis = false; for(int t = 0; t < maxRetries; ++t) { // 采样当前裂缝的长度,角度和安全距离 double len = sampleLength(); double angle = sampleAngle(); double margin = calculateSafetyMargin(len); QPointF p1, p2; if(type == NM_Round_Outline_Type) { // 圆形:在安全半径内采样中心点 double half = len / 2.0; double safeR = R - margin; if (safeR <= 0) { QMessageBox::warning(this, tr("Length too large"), tr("The specified length is too large to fit inside the circle.")); return; } double u = double(qrand())/RAND_MAX; double r = sqrt(u) * safeR; double ang = double(qrand())/RAND_MAX * 2 * M_PI; QPointF c(r*cos(ang)+center.x(), r*sin(ang)+center.y()); p1 = QPointF(c.x() + half*cos(angle), c.y() + half*sin(angle)); p2 = QPointF(c.x() - half*cos(angle), c.y() - half*sin(angle)); } else if(type == NM_Polygon_Outline_Type) { // 多边形采样:先在安全包围矩形内随机点,确保在多边形内 QPolygonF poly(outline->getOutlinePoints()); QRectF br = poly.boundingRect(); double pMinX = br.left() + margin; double pMaxX = br.right() - margin; double pMinY = br.top() + margin; double pMaxY = br.bottom() - margin; if (pMinX > pMaxX || pMinY > pMaxY) { QMessageBox::warning(this, tr("Length too large"), tr("The specified length is too large to fit inside the polygon.")); return; } // 随机采样起点,使用采样的角度 do { double u = double(qrand())/RAND_MAX; double v = double(qrand())/RAND_MAX; p1 = QPointF(pMinX + u*(pMaxX-pMinX), pMinY + v*(pMaxY-pMinY)); p2 = QPointF(p1.x() + len*cos(angle), p1.y() + len*sin(angle)); } while (!poly.containsPoint(p1,Qt::OddEvenFill) || !poly.containsPoint(p2,Qt::OddEvenFill)); } else { // 矩形在安全矩形内随机位置 double safeMinX = minX + margin; double safeMaxX = maxX - margin; double safeMinY = minY + margin; double safeMaxY = maxY - margin; if (safeMinX > safeMaxX || safeMinY > safeMaxY) { QMessageBox::warning(this, tr("Length too large"), tr("The specified length is too large to fit inside the region.\n" "Please choose a smaller length.")); return; } double u = double(qrand())/RAND_MAX; double v = double(qrand())/RAND_MAX; p1 = QPointF(safeMinX + u*(safeMaxX-safeMinX), safeMinY + v*(safeMaxY-safeMinY)); p2 = QPointF(p1.x() + len*cos(angle), p1.y() + len*sin(angle)); } // 统一相交检测 line = QLineF(p1, p2); // 检查是否与已放置的任意线段相交 bool inter = false; // 增加容差检查 double tolerance = 20.0; for(int j = 0; j < existing.size(); ++j) { QPointF ip; if(isLinesTooClose(line, existing[j], tolerance)) { inter = true; break; } } // 如果不相交,则接受此线段 if(!inter) { placedThis = true; break; } } if (!placedThis) { qWarning("Failed to place fracture %d", i); continue; } // 成功放置:绘制并记录 existing.append(line); QVector pts; pts << line.p1() << line.p2(); QVector scPts = plot->m_pPlot->getPosForValue(pts); QString name = tr("DFN").arg(placed + 1); nmObjBase* obj = plot->appendOneObj(NMOT_Line_Fracture, name, scPts); nmObjLineCrack* crack = dynamic_cast(obj); if (crack) { crack->afterCreated(); // 设置属性为 DFN nmDataAttribute attr = crack->getFractureData()->getFractureType(); attr.setValue(tr("DFN")); crack->getFractureData()->setFractureType(attr); crack->setPen(QPen(QBrush(QColor("#00bfff")), 0.4f, Qt::SolidLine)); crack->setLockForDFN(); } ++placed; } } int nmWxDFNGenerateDlg::count() const { return m_pCountSpin->value(); }