You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxDFNGenerateDlg.cpp

498 lines
14 KiB
C++

#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 <QMessageBox>
#include <QTime>
#include <cmath>
#include <QComboBox>
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<QPointF> 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<QPointF> 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<QLineF> 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<QPointF> pts;
pts << line.p1() << line.p2();
QVector<QPointF> 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<nmObjLineCrack*>(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();
}