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++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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)
: QDialog(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();
}