Merge branch 'new_osg' of http://brigecode.icu:16623/PM/DYTSrouce into new_osg

This commit is contained in:
pimin 2025-11-11 17:45:10 +08:00
commit d14e69682e
17 changed files with 503 additions and 187 deletions

View File

@ -62,14 +62,54 @@ void PathComponent::Begin() {
animationPath_->clear();
const std::vector<double>& steps = timeStep_->GetSteps();
// 优先使用路径文件的时间列;若不存在则按当前时间范围等间距生成时间点
std::vector<double> mappedSteps;
const bool hasPathTimes = transformPath_ && !transformPath_->GetTimes().empty();
double rangeMin = 0.0, rangeMax = 0.0, rangeStep = 0.0;
timeStep_->GetRange(rangeMin, rangeMax, rangeStep);
if (hasPathTimes) {
const auto& times = transformPath_->GetTimes();
if (timeStep_->HasManualRange()) {
// 将路径文件时间列线性映射到用户指定的手动区间,保证相对时间关系不变
double srcMin = times.front();
double srcMax = times.back();
double dstMin = timeStep_->GetManualStart();
double dstMax = timeStep_->GetManualEnd();
double srcSpan = std::max(1e-9, srcMax - srcMin);
mappedSteps.reserve(times.size());
for (double t : times) {
double alpha = (t - srcMin) / srcSpan;
mappedSteps.push_back(dstMin + alpha * (dstMax - dstMin));
}
} else {
mappedSteps = times;
}
} else {
const std::vector<Transform>& transforms = transformPath_->GetTransforms();
int num = (int)transforms.size();
if (num > 0) {
mappedSteps.resize(num);
double start = timeStep_->HasManualRange() ? timeStep_->GetManualStart() : rangeMin;
double end = timeStep_->HasManualRange() ? timeStep_->GetManualEnd() : rangeMax;
double step = rangeStep;
// 若步长无效,则回退为均匀分布
if (step <= 0.0) {
step = (num > 1) ? std::max(0.0, (end - start)) / double(num - 1) : 0.0;
}
for (int i = 0; i < num; ++i) {
double t = start + step * double(i);
if (t > end) t = end;
mappedSteps[i] = t;
}
}
}
const std::vector<Transform>& transforms = transformPath_->GetTransforms();
int num = std::min(steps.size(), transforms.size());
int num = std::min(mappedSteps.size(), transforms.size());
for (int index = 0; index < num; ++index) {
const Transform& transform = transforms[index];
osg::AnimationPath::ControlPoint controlPoint(transform.GetLocation() /*+ osg::Vec3(index * 10, 0, 0)*/,
OsgUtils::HPRToQuat(transform.GetRotation()), transform.GetScale());
animationPath_->insert(steps[index], controlPoint);
animationPath_->insert(mappedSteps[index], controlPoint);
}
deltaTime_ = 0;

View File

@ -54,7 +54,13 @@ void FrameTitleBar::SetMainWidget(QWidget* widget) {
}
}
void FrameTitleBar::OnMaximized(bool maximized) {
ui->sys_max->setVisible(!maximized);
// 根据配置位决定是否显示最大/还原按钮,并切换图标
const bool hasMaxButton = (ftbButton_ & FTB_MAX) != 0;
ui->sys_max->setVisible(hasMaxButton);
if (!hasMaxButton) {
return;
}
ui->sys_max->setIcon(QPixmap(maximized ? ":/res/sys_restore.png" : ":/res/sys_max.png"));
}
QPushButton* FrameTitleBar::InsertPushButtonMenu(const QString& name, int index) {

View File

@ -32,18 +32,18 @@ bool FramelessDelegate::nativeEvent(const QByteArray & eventType, void* message,
}
void FramelessDelegate::SetTitleBar(FrameTitleBar* titleBar) {
/* if (nullptr == titleBar || titleBar_ == titleBar) {
if (nullptr == titleBar || titleBar_ == titleBar) {
return;
}
if (nullptr != titleBar_) {
titleBar_->removeEventFilter(this);
titleBar_->deleteLater();
}
titleBar_ = titleBar;
if (nullptr != titleBar_) {
// 跟踪标题栏显示/隐藏,以便在显示时注册为可移动区域
titleBar_->installEventFilter(this);
}*/
}
}
void FramelessDelegate::SetTitle(const QString& title) {
@ -60,8 +60,6 @@ bool FramelessDelegate::eventFilter(QObject* watched, QEvent* event) {
OnHide();
} else if (QEvent::Close == event->type()) {
OnClose();
} else if (QEvent::WinIdChange == event->type()) {
isFirstShow_ = true;
}
//#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
else if (QEvent::ScreenChangeInternal == event->type()) {

View File

@ -358,6 +358,8 @@ void FramelessDelegateWin::SetNativeWindowLong() {
}
HWND hwnd = reinterpret_cast<HWND>(mainWidget_->winId());
unsigned long style = BorderlessFlag;
// 避免父窗口与子窗口重叠绘制导致闪烁,启用剪裁样式
style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
if (!mainWidget_->windowFlags().testFlag(Qt::WindowMinimizeButtonHint) ||
mainWidget_->maximumSize() != QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) {

View File

@ -8,6 +8,9 @@ FramelessWindow::FramelessWindow(QWidget* parent)
: QFrame(parent) {
setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
// 初始化平台相关的无边框委托,用于命中测试和缩放。
delegate_ = FramelessDelegate::Create(this);
}
FramelessWindow::~FramelessWindow() {
@ -15,6 +18,10 @@ FramelessWindow::~FramelessWindow() {
void FramelessWindow::SetTitleBar(FrameTitleBar* titleBar) {
titleBar->SetMainWidget(this);
if (delegate_) {
// 通知委托当前标题栏,以便把它作为可拖动区域处理。
delegate_->SetTitleBar(titleBar);
}
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@ -22,5 +29,9 @@ bool FramelessWindow::nativeEvent(const QByteArray& eventType, void* message, qi
#else
bool FramelessWindow::nativeEvent(const QByteArray & eventType, void* message, long* result) {
#endif
// 先让无边框委托处理原生事件(如 WM_NCHITTEST 以支持边缘拖动缩放)。
if (delegate_ && delegate_->nativeEvent(eventType, message, result)) {
return true;
}
return QFrame::nativeEvent(eventType, message, result);
}

View File

@ -139,6 +139,16 @@ void PlayManagerMenu::OnTimestepChanged(Timestep* timestep) {
ui->lbUp->setText(QString("x%1").arg(step));
}
);
// 当时间范围更新(例如用户在属性面板设置了起止时间)时,刷新显示与滑条范围
connect(timestep, &Timestep::RangeChanged, [this](double minTime, double maxTime, double step) {
minTime_ = minTime;
maxTime_ = maxTime;
ui->lbtime->setText(QString::number(minTime_, 'f', 3));
ui->lbtimeTotal->setText(QString("/%1(s)").arg(maxTime_));
ui->lbUp->setText(QString("x%1").arg(step));
ui->horizontalSlider->setRange((int)(minTime_ * 1000), (int)(maxTime_ * 1000));
ui->horizontalSlider->setValue((int)(minTime_ * 1000));
});
connect(timestep, &Timestep::StatusChanged, [this](int statue) {
Timestep::PlayStatus state = static_cast<Timestep::PlayStatus>(statue);

View File

@ -7916,7 +7916,6 @@ public:
QMap<const QtProperty*, QtProperty*> m_properyToName;
QMap<const QtProperty*, QtProperty*> m_properyToDescription;
QMap<const QtProperty*, QtProperty*> m_properyToTimestep;
QMap<const QtProperty*, QtProperty*> m_properyToSimMatlab;
QMap<const QtProperty*, QtProperty*> m_properyToMatlabParam;
QMap<const QtProperty*, QtProperty*> m_properyToWavePath;
@ -7926,7 +7925,6 @@ public:
QMap<const QtProperty*, QtProperty*> m_nameToPropery;
QMap<const QtProperty*, QtProperty*> m_descriptionToPropery;
QMap<const QtProperty*, QtProperty*> m_timestepToPropery;
QMap<const QtProperty*, QtProperty*> m_simMatlabToPropery;
QMap<const QtProperty*, QtProperty*> m_matlabParamToPropery;
QMap<const QtProperty*, QtProperty*> m_wavePathToPropery;
@ -7951,6 +7949,16 @@ public:
QMap<const QtProperty*, QtProperty*> m_properyToHomeRange;
QMap<const QtProperty*, QtProperty*> m_homeRangeToPropery;
// Simulation Time group (manual range)
QMap<const QtProperty*, QtProperty*> m_properyToSimTimeGroup;
QMap<const QtProperty*, QtProperty*> m_simTimeGroupToPropery;
QMap<const QtProperty*, QtProperty*> m_properyToSimStart;
QMap<const QtProperty*, QtProperty*> m_simStartToPropery;
QMap<const QtProperty*, QtProperty*> m_properyToSimEnd;
QMap<const QtProperty*, QtProperty*> m_simEndToPropery;
QMap<const QtProperty*, QtProperty*> m_properyToSimStep;
QMap<const QtProperty*, QtProperty*> m_simStepToPropery;
// Grouped file entries: Curve
QMap<const QtProperty*, QtProperty*> m_properyToCurveGroup;
QMap<const QtProperty*, QtProperty*> m_curveGroupToPropery;
@ -8015,10 +8023,6 @@ void QtWorkspacePropertyManagerPrivate::slotStringChanged(QtProperty* property,
QWorkspaceAttribute c = m_values[prop];
c.SetDescription(value);
q_ptr->setValue(prop, c);
} else if (QtProperty* prop = m_timestepToPropery.value(property, 0)) {
QWorkspaceAttribute c = m_values[prop];
c.SetTimeStep(value);
q_ptr->setValue(prop, c);
} else if (QtProperty* prop = m_commondPathToPropery.value(property, 0)) {
QWorkspaceAttribute c = m_values[prop];
c.SetCommondFilePath(value);
@ -8145,6 +8149,18 @@ void QtWorkspacePropertyManagerPrivate::slotDoubleChanged(QtProperty* property,
QWorkspaceAttribute c = m_values[prop];
c.SetHomeViewpointRange(value);
q_ptr->setValue(prop, c);
} else if (QtProperty* prop = m_simStartToPropery.value(property, 0)) {
QWorkspaceAttribute c = m_values[prop];
c.SetSimulationStart(value);
q_ptr->setValue(prop, c);
} else if (QtProperty* prop = m_simEndToPropery.value(property, 0)) {
QWorkspaceAttribute c = m_values[prop];
c.SetSimulationEnd(value);
q_ptr->setValue(prop, c);
} else if (QtProperty* prop = m_simStepToPropery.value(property, 0)) {
QWorkspaceAttribute c = m_values[prop];
c.SetSimulationStep(value);
q_ptr->setValue(prop, c);
}
}
@ -8159,10 +8175,7 @@ void QtWorkspacePropertyManagerPrivate::slotPropertyDestroyed(QtProperty* proper
m_descriptionToPropery.remove(property);
}
if (QtProperty* subProp = m_timestepToPropery.value(property, nullptr)) {
m_timestepToPropery[subProp] = 0;
m_timestepToPropery.remove(property);
}
// Removed: timestep property mapping cleanup (no longer tracked)
if (QtProperty* subProp = m_simMatlabToPropery.value(property, nullptr)) {
m_simMatlabToPropery[subProp] = 0;
@ -8184,6 +8197,44 @@ void QtWorkspacePropertyManagerPrivate::slotPropertyDestroyed(QtProperty* proper
m_rdPathToPropery[subProp] = 0;
m_rdPathToPropery.remove(property);
}
// HomeViewpoint numeric children
if (QtProperty* subProp = m_homeLonToPropery.value(property, nullptr)) {
m_homeLonToPropery[subProp] = 0;
m_homeLonToPropery.remove(property);
}
if (QtProperty* subProp = m_homeLatToPropery.value(property, nullptr)) {
m_homeLatToPropery[subProp] = 0;
m_homeLatToPropery.remove(property);
}
if (QtProperty* subProp = m_homeAltToPropery.value(property, nullptr)) {
m_homeAltToPropery[subProp] = 0;
m_homeAltToPropery.remove(property);
}
if (QtProperty* subProp = m_homeHeadingToPropery.value(property, nullptr)) {
m_homeHeadingToPropery[subProp] = 0;
m_homeHeadingToPropery.remove(property);
}
if (QtProperty* subProp = m_homePitchToPropery.value(property, nullptr)) {
m_homePitchToPropery[subProp] = 0;
m_homePitchToPropery.remove(property);
}
if (QtProperty* subProp = m_homeRangeToPropery.value(property, nullptr)) {
m_homeRangeToPropery[subProp] = 0;
m_homeRangeToPropery.remove(property);
}
// Simulation Time children
if (QtProperty* subProp = m_simStartToPropery.value(property, nullptr)) {
m_simStartToPropery[subProp] = 0;
m_simStartToPropery.remove(property);
}
if (QtProperty* subProp = m_simEndToPropery.value(property, nullptr)) {
m_simEndToPropery[subProp] = 0;
m_simEndToPropery.remove(property);
}
if (QtProperty* subProp = m_simStepToPropery.value(property, nullptr)) {
m_simStepToPropery[subProp] = 0;
m_simStepToPropery.remove(property);
}
}
QtWorkspacePropertyManager::QtWorkspacePropertyManager(QObject* parent)
@ -8286,7 +8337,6 @@ void QtWorkspacePropertyManager::setValue(QtProperty* property, const QWorkspace
d_ptr->m_stringProperyManager->setValue(d_ptr->m_properyToName[property], value.GetName());
d_ptr->m_stringProperyManager->setValue(d_ptr->m_properyToDescription[property], value.GetDescription());
d_ptr->m_filesProperyManager->setValue(d_ptr->m_properyToTimestep[property], value.GetTimeStep());
d_ptr->m_filesProperyManager->setValue(d_ptr->m_properyToCommondPath[property], value.GetCommondFilePath());
// Sync HomeViewpoint numeric fields
if (QtProperty* p = d_ptr->m_properyToHomeLon.value(property, nullptr))
@ -8301,6 +8351,13 @@ void QtWorkspacePropertyManager::setValue(QtProperty* property, const QWorkspace
d_ptr->m_doubleProperyManager->setValueOnly(p, value.GetHomeViewpointPitch());
if (QtProperty* p = d_ptr->m_properyToHomeRange.value(property, nullptr))
d_ptr->m_doubleProperyManager->setValueOnly(p, value.GetHomeViewpointRange());
// Sync Simulation Time Start/End
if (QtProperty* p = d_ptr->m_properyToSimStart.value(property, nullptr))
d_ptr->m_doubleProperyManager->setValueOnly(p, value.GetSimulationStart());
if (QtProperty* p = d_ptr->m_properyToSimEnd.value(property, nullptr))
d_ptr->m_doubleProperyManager->setValueOnly(p, value.GetSimulationEnd());
if (QtProperty* p = d_ptr->m_properyToSimStep.value(property, nullptr))
d_ptr->m_doubleProperyManager->setValueOnly(p, value.GetSimulationStep());
auto syncGroup = [&](FileEntryType type,
QMap<const QtProperty*, QtProperty*>& propToGroup,
@ -8383,12 +8440,7 @@ void QtWorkspacePropertyManager::initializeProperty(QtProperty* property) {
d_ptr->m_descriptionToPropery[prop] = property;
property->addSubProperty(prop);
prop = d_ptr->m_filesProperyManager->addProperty();
prop->setPropertyName(tr("Timestep"));
d_ptr->m_filesProperyManager->setValueOnly(prop, val.GetTimeStep());
d_ptr->m_properyToTimestep[property] = prop;
d_ptr->m_timestepToPropery[prop] = property;
property->addSubProperty(prop);
// Removed: Timestep file path property (no longer file-based)
// Deprecated properties (SimMatlab/MatlabParam/WavePath/ReportPath/RDPath) removed from UI
@ -8435,6 +8487,41 @@ void QtWorkspacePropertyManager::initializeProperty(QtProperty* property) {
addDouble(tr("Range"), val.GetHomeViewpointRange(), d_ptr->m_properyToHomeRange, d_ptr->m_homeRangeToPropery,
0.0, 1000000.0, 2, 10.0);
// Simulation Time group (English labels)
QtProperty* simGroup = d_ptr->m_groupProperyManager->addProperty();
simGroup->setPropertyName(tr("Simulation Time"));
d_ptr->m_properyToSimTimeGroup[property] = simGroup;
d_ptr->m_simTimeGroupToPropery[simGroup] = property;
property->addSubProperty(simGroup);
auto addSimDouble = [&](const QString& name, double init,
QMap<const QtProperty*, QtProperty*>& propToChild,
QMap<const QtProperty*, QtProperty*>& childToProp) {
QtProperty* dp = d_ptr->m_doubleProperyManager->addProperty();
dp->setPropertyName(name);
d_ptr->m_doubleProperyManager->setValueOnly(dp, init);
d_ptr->m_doubleProperyManager->setRange(dp, -1e12, 1e12);
d_ptr->m_doubleProperyManager->setDecimals(dp, 6);
d_ptr->m_doubleProperyManager->setSingleStep(dp, 0.1);
simGroup->addSubProperty(dp);
propToChild[property] = dp;
childToProp[dp] = property;
};
addSimDouble(tr("Simulation Start"), val.GetSimulationStart(), d_ptr->m_properyToSimStart, d_ptr->m_simStartToPropery);
addSimDouble(tr("Simulation End"), val.GetSimulationEnd(), d_ptr->m_properyToSimEnd, d_ptr->m_simEndToPropery);
// Simulation Step (playback speed multiplier)
{
QtProperty* dp = d_ptr->m_doubleProperyManager->addProperty();
dp->setPropertyName(tr("Simulation Step"));
d_ptr->m_doubleProperyManager->setValueOnly(dp, val.GetSimulationStep());
d_ptr->m_doubleProperyManager->setRange(dp, 0.25, 8.0);
d_ptr->m_doubleProperyManager->setDecimals(dp, 2);
d_ptr->m_doubleProperyManager->setSingleStep(dp, 0.25);
simGroup->addSubProperty(dp);
d_ptr->m_properyToSimStep[property] = dp;
d_ptr->m_simStepToPropery[dp] = property;
}
// Add grouped file sections
#if 0 // Hide resource groups (Curves/Surfaces/Tables/Lights/Polars/Images) in workspace property box
auto addGroup = [&](FileEntryType type, const QString& groupName,
@ -8523,12 +8610,7 @@ void QtWorkspacePropertyManager::uninitializeProperty(QtProperty* property) {
}
d_ptr->m_properyToDescription.remove(property);
prop = d_ptr->m_timestepToPropery[property];
if (prop) {
d_ptr->m_timestepToPropery.remove(prop);
delete prop;
}
d_ptr->m_properyToTimestep.remove(property);
// Removed: Timestep file path cleanup
prop = d_ptr->m_simMatlabToPropery[property];
if (prop) {
@ -8597,6 +8679,26 @@ void QtWorkspacePropertyManager::uninitializeProperty(QtProperty* property) {
}
d_ptr->m_properyToHomeViewGroup.remove(property);
// Simulation Time cleanup
auto removeSimChild = [&](QMap<const QtProperty*, QtProperty*>& propToChild,
QMap<const QtProperty*, QtProperty*>& childToProp) {
QtProperty* child = propToChild.value(property, nullptr);
if (child) {
childToProp.remove(child);
delete child;
}
propToChild.remove(property);
};
removeSimChild(d_ptr->m_properyToSimStart, d_ptr->m_simStartToPropery);
removeSimChild(d_ptr->m_properyToSimEnd, d_ptr->m_simEndToPropery);
removeSimChild(d_ptr->m_properyToSimStep, d_ptr->m_simStepToPropery);
QtProperty* simGroup = d_ptr->m_properyToSimTimeGroup.value(property, nullptr);
if (simGroup) {
d_ptr->m_simTimeGroupToPropery.remove(simGroup);
delete simGroup;
}
d_ptr->m_properyToSimTimeGroup.remove(property);
// Cleanup grouped file properties
auto cleanupGroup = [&](QMap<const QtProperty*, QtProperty*>& propToGroup,
QMap<const QtProperty*, QtProperty*>& groupToProp,

View File

@ -62,35 +62,65 @@ const QString QWorkspaceAttribute::GetDescription() const {
}
void QWorkspaceAttribute::SetTimeStep(const QString& timestep) {
if (nullptr == workspace_) {
return;
}
// 已移除:时间步长文件路径设置/获取(不再从文件读取)
Timestep* obj = workspace_->GetTimestep();
if (nullptr == obj) {
workspace_->SetTimestepPath(timestep);
return;
}
const QString& path = obj->GetPath();
if (path == timestep) {
return;
}
workspace_->SetTimestepPath(timestep);
void QWorkspaceAttribute::SetSimulationStart(double start) {
if (!workspace_) return;
Timestep* t = workspace_->GetTimestep();
if (!t) return;
double minTime = 0.0, maxTime = 0.0, step = 0.0;
t->GetRange(minTime, maxTime, step);
t->SetManualRange(start, maxTime);
}
const QString QWorkspaceAttribute::GetTimeStep() const {
if (nullptr == workspace_) {
return "";
}
double QWorkspaceAttribute::GetSimulationStart() const {
if (!workspace_) return 0.0;
Timestep* t = workspace_->GetTimestep();
if (!t) return 0.0;
double minTime = 0.0, maxTime = 0.0, step = 0.0;
t->GetRange(minTime, maxTime, step);
return minTime;
}
Timestep* timestep = workspace_->GetTimestep();
if (nullptr == timestep) {
return "";
}
return timestep->GetPath();
void QWorkspaceAttribute::SetSimulationEnd(double end) {
if (!workspace_) return;
Timestep* t = workspace_->GetTimestep();
if (!t) return;
double minTime = 0.0, maxTime = 0.0, step = 0.0;
t->GetRange(minTime, maxTime, step);
t->SetManualRange(minTime, end);
}
double QWorkspaceAttribute::GetSimulationEnd() const {
if (!workspace_) return 0.0;
Timestep* t = workspace_->GetTimestep();
if (!t) return 0.0;
double minTime = 0.0, maxTime = 0.0, step = 0.0;
t->GetRange(minTime, maxTime, step);
return maxTime;
}
void QWorkspaceAttribute::SetSimulationRange(double start, double end) {
if (!workspace_) return;
Timestep* t = workspace_->GetTimestep();
if (!t) return;
t->SetManualRange(start, end);
}
void QWorkspaceAttribute::SetSimulationStep(double step) {
if (!workspace_) return;
Timestep* t = workspace_->GetTimestep();
if (!t) return;
t->SetSpeed(step);
}
double QWorkspaceAttribute::GetSimulationStep() const {
if (!workspace_) return 1.0;
Timestep* t = workspace_->GetTimestep();
if (!t) return 1.0;
double minTime = 0.0, maxTime = 0.0, step = 0.0;
t->GetRange(minTime, maxTime, step);
return step;
}
// Deprecated workspace path setters/getters removed

View File

@ -62,8 +62,17 @@ public:
void SetDescription(const QString& desc);
const QString GetDescription() const;
void SetTimeStep(const QString& timestep);
const QString GetTimeStep() const;
// 已移除:时间步长文件路径(不再从文件读取)
// Simulation time (manual range) for playback
void SetSimulationStart(double start);
double GetSimulationStart() const;
void SetSimulationEnd(double end);
double GetSimulationEnd() const;
void SetSimulationRange(double start, double end);
// Playback speed (step multiplier)
void SetSimulationStep(double step);
double GetSimulationStep() const;
// Deprecated fields removed: SimMatlab/MatlabParam/WavePath/ReportPath/RDPath

View File

@ -2,6 +2,7 @@
#include <QFile>
#include <QTextStream>
#include <QRegExp>
#include "common/SpdLogger.h"
@ -35,36 +36,47 @@ TransformPath* TransformPath::LoadFromFile(const QString& path, QObject* parent)
std::vector<Transform> transforms;
std::vector<double> times;
while (!in.atEnd()) {
QString line = in.readLine();
if (line.trimmed().isEmpty()) {
continue;
}
// 期望格式:时间 经度 纬度 高度 方位角(Heading) 俯仰角(Pitch) 翻滚角(Roll)
// 示例0.700000 \t 119.500000 \t 24.500000 \t 1000.000000 \t 0.000000 \t 0.000000 \t 0.000000
const QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts);
if (parts.size() < 7) {
LOG_WARN("TransformPath: invalid line (need 7 numbers): {}", line.toStdString());
return {};
}
bool ok = false;
// 解析各字段
const double time = parts[0].toDouble(&ok); if (!ok) { LOG_WARN("invalid time: {}", parts[0].toStdString()); return {}; }
const double longitude= parts[1].toDouble(&ok); if (!ok) { LOG_WARN("invalid longitude: {}", parts[1].toStdString()); return {}; }
const double latitude = parts[2].toDouble(&ok); if (!ok) { LOG_WARN("invalid latitude: {}", parts[2].toStdString()); return {}; }
const double altitude = parts[3].toDouble(&ok); if (!ok) { LOG_WARN("invalid altitude: {}", parts[3].toStdString()); return {}; }
const double heading = parts[4].toDouble(&ok); if (!ok) { LOG_WARN("invalid heading: {}", parts[4].toStdString()); return {}; }
const double pitch = parts[5].toDouble(&ok); if (!ok) { LOG_WARN("invalid pitch: {}", parts[5].toStdString()); return {}; }
const double roll = parts[6].toDouble(&ok); if (!ok) { LOG_WARN("invalid roll: {}", parts[6].toStdString()); return {}; }
Transform transform;
// 位置映射:经度->x纬度->y高度->z
transform.GetLocation().set(longitude, latitude, altitude);
// 旋转向量采用代码库约定顺序Pitch, Roll, Heading与 OsgUtils::HPRToQuat/QuatToHPR 保持一致
transform.GetRotation().set(pitch, roll, heading);
for (int i = 0; i < 3; ++i) {
QString line = in.readLine();
bool ok;
double value = line.toDouble(&ok);
if (ok) {
transform.GetLocation()[i] = value;
} else {
LOG_WARN("Failed to convert line to double: {}", line.toStdString());
return {};
}
}
for (int i = 0; i < 3; ++i) {
QString line = in.readLine();
bool ok;
double value = line.toDouble(&ok);
if (ok) {
transform.GetRotation()[i] = value;
} else {
LOG_WARN("Failed to convert line to double: {}", line.toStdString());
return {};
}
}
// 当前 PathComponent 使用 WorkSpace 的 Timestep 作为时间轴,这里暂不保存 time 字段
// 现在记录时间以支持按路径文件驱动回放
times.push_back(time);
transforms.push_back(transform);
}
file.close();
return new TransformPath(std::move(transforms), parent);
TransformPath* tp = new TransformPath(std::move(transforms), parent);
tp->SetTimes(std::move(times));
return tp;
}

View File

@ -21,6 +21,15 @@ public:
return transforms_;
}
const std::vector<double>& GetTimes() const {
return times_;
}
void SetTimes(std::vector<double> times) {
times_ = std::move(times);
}
private:
std::vector<Transform> transforms_;
std::vector<double> times_;
};

View File

@ -1,5 +1,9 @@
#include "workspace/Timestep.h"
#include <algorithm>
#include <limits>
#include <cmath>
#include <QFile>
#include <QTextStream>
@ -8,47 +12,47 @@
#include "common/RecourceHelper.h"
#include "common/SpdLogger.h"
Timestep::Timestep(const std::vector<double>& steps, const QString& path, WorkSpace* parent /*= nullptr*/) noexcept
Timestep::Timestep(WorkSpace* parent /*= nullptr*/) noexcept
: QObject((QObject*)parent)
, steps_(steps)
, path_(path)
, workSpace_(parent) {
maxTime_ = *steps_.rbegin();
// 默认最大时间为 0由数据或手动区间设置
// 初始化为 1.0x,如果列表中没有 1.0,则取最接近中间的默认索引
auto it = std::find(speedLevels_.begin(), speedLevels_.end(), 1.0);
speedIndex_ = it != speedLevels_.end() ? int(std::distance(speedLevels_.begin(), it)) : speedIndex_;
currentStep_ = speedLevels_[speedIndex_];
}
Timestep* Timestep::Load(const QString& path, WorkSpace* workSpace) {
const QString filePath = QString("%1/%2").arg(workSpace->GetDir()).arg(path);
LOG_INFO("Load timestep: {}", filePath.toStdString());
void Timestep::SetManualRange(double start, double end) {
hasManualRange_ = true;
manualStart_ = start;
manualEnd_ = end;
// 当设置手动区间时,更新最大时间使播放边界正确
maxTime_ = end;
// 通知 UI 更新范围与步进
double minTime = 0.0, maxTime = 0.0, step = 0.0;
GetRange(minTime, maxTime, step);
emit RangeChanged(minTime, maxTime, step);
}
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_WARN("Cannot open file for reading: {}", file.errorString().toLocal8Bit().constData());
return nullptr;
}
QTextStream in(&file);
std::vector<double> numbers;
while (!in.atEnd()) {
QString line = in.readLine();
bool ok;
double value = line.toDouble(&ok);
if (ok) {
numbers.push_back(value);
} else {
LOG_WARN("Cannot open file for reading: {}", line.toStdString());
}
}
file.close();
Timestep* timestep = new Timestep(numbers, path, workSpace);
return timestep;
void Timestep::ClearManualRange() {
hasManualRange_ = false;
manualStart_ = 0.0;
manualEnd_ = 0.0;
// 恢复为数据驱动的最大时间,保留当前 maxTime_
// 通知 UI 更新范围与步进
double minTime = 0.0, maxTime = 0.0, step = 0.0;
GetRange(minTime, maxTime, step);
emit RangeChanged(minTime, maxTime, step);
}
void Timestep::GetRange(double& minTime, double& maxTime, double& step) {
minTime = *steps_.begin();
maxTime = maxTime_;
if (hasManualRange_) {
minTime = manualStart_;
maxTime = manualEnd_;
} else {
minTime = 0.0;
maxTime = maxTime_;
}
step = currentStep_;
}
@ -84,13 +88,18 @@ bool Timestep::IsStoped() {
void Timestep::Start() {
LOG_INFO("enter");
playStatus_ = PlayStatus::PS_Started;
current_ = 0.0;
current_ = hasManualRange_ ? manualStart_ : 0.0;
if (nullptr == workSpace_) {
LOG_WARN("workSpace_ is nullptr");
return;
}
workSpace_->Begin();
// 重置速度为 1x 并通知 UI
auto it = std::find(speedLevels_.begin(), speedLevels_.end(), 1.0);
speedIndex_ = it != speedLevels_.end() ? int(std::distance(speedLevels_.begin(), it)) : speedIndex_;
currentStep_ = speedLevels_[speedIndex_];
emit StepChanged(currentStep_);
emit StatusChanged((int)playStatus_);
}
@ -116,24 +125,60 @@ void Timestep::Pause() {
void Timestep::Stop() {
LOG_INFO("enter");
current_ = maxTime_;
current_ = hasManualRange_ ? manualEnd_ : maxTime_;
playStatus_ = PlayStatus::PS_Stoped;
if (nullptr == workSpace_) {
LOG_WARN("workSpace_ is nullptr");
return;
}
workSpace_->End();
// 停止时也恢复为 1x避免停后再次播放仍是异常倍率
auto it = std::find(speedLevels_.begin(), speedLevels_.end(), 1.0);
speedIndex_ = it != speedLevels_.end() ? int(std::distance(speedLevels_.begin(), it)) : speedIndex_;
currentStep_ = speedLevels_[speedIndex_];
emit StepChanged(currentStep_);
emit StatusChanged((int)playStatus_);
}
void Timestep::Up() {
currentSpeed *= 2;
currentStep_ *= currentSpeed;
emit StepChanged(currentSpeed);
// 提升到下一个倍率(封顶)
if (speedIndex_ < int(speedLevels_.size()) - 1) {
++speedIndex_;
}
currentStep_ = speedLevels_[speedIndex_];
emit StepChanged(currentStep_);
}
void Timestep::SetDataMaxTime(double end) {
maxTime_ = end;
double minTime = 0.0, maxTime = 0.0, step = 0.0;
GetRange(minTime, maxTime, step);
emit RangeChanged(minTime, maxTime, step);
}
void Timestep::Down() {
currentSpeed *= 0.5;
currentStep_ *= currentSpeed;
emit StepChanged(currentSpeed);
// 降到上一个倍率(保底)
if (speedIndex_ > 0) {
--speedIndex_;
}
currentStep_ = speedLevels_[speedIndex_];
emit StepChanged(currentStep_);
}
void Timestep::SetSpeed(double speed) {
if (speedLevels_.empty()) {
return;
}
int bestIdx = 0;
double bestDiff = std::numeric_limits<double>::max();
for (int i = 0; i < static_cast<int>(speedLevels_.size()); ++i) {
double diff = std::fabs(speedLevels_[i] - speed);
if (diff < bestDiff) {
bestDiff = diff;
bestIdx = i;
}
}
speedIndex_ = bestIdx;
currentStep_ = speedLevels_[speedIndex_];
emit StepChanged(currentStep_);
}

View File

@ -8,7 +8,7 @@
class WorkSpace;
class Timestep : public QObject {
Q_OBJECT
Q_OBJECT
public:
enum class PlayStatus : uint8_t {
PS_Started,
@ -17,23 +17,14 @@ public:
};
public:
explicit Timestep(const std::vector<double>& steps, const QString& path, WorkSpace* parent = nullptr) noexcept;
explicit Timestep(WorkSpace* parent = nullptr) noexcept;
~Timestep() override = default;
static Timestep* Load(const QString& path, WorkSpace* workSpace = nullptr);
const std::vector<double>& GetSteps() const {
return steps_;
}
const QString& GetPath() const {
return path_;
}
void GetRange(double& minTime, double& maxTime, double& step);
void Update(double dt);
double GetCurrent() const {
return current_;
}
void GetRange(double& minTime, double& maxTime, double& step);
void Update(double dt);
double GetCurrent() const {
return current_;
}
bool IsPause();
bool IsStoped();
@ -42,29 +33,48 @@ public:
void Pause();
void Stop();
void Up();
void Down();
void Up();
void Down();
// 设置播放速度(倍率),会选取最接近的预设倍率并触发 StepChanged
void SetSpeed(double speed);
// 设置数据驱动的默认最大时间(非手动区间),用于没有文件步长时的播放边界
void SetDataMaxTime(double end);
WorkSpace* GetWorkSpace() const {
return workSpace_;
}
// 手动时间区间(可选):允许用户指定起止时间,替代或重映射步骤
void SetManualRange(double start, double end);
void ClearManualRange();
bool HasManualRange() const { return hasManualRange_; }
double GetManualStart() const { return manualStart_; }
double GetManualEnd() const { return manualEnd_; }
Q_SIGNALS:
void StatusChanged(int);
void StepChanged(double);
void TimeChanged(double);
void StatusChanged(int);
void StepChanged(double);
void TimeChanged(double);
// 当时间范围或步进倍率更新时通知 UI例如设置手动区间或清除
void RangeChanged(double minTime, double maxTime, double step);
private:
std::vector<double> steps_;
QString path_;
double current_{ 0.0 };
double maxTime_{ 0.0 };
double currentStep_{ 1.0 };
double currentSpeed = { 1.0 };
double current_{ 0.0 };
double maxTime_{ 0.0 };
// 播放速度(倍率),与 UI 显示一致,比如 0.5x、1x、2x
double currentStep_{ 1.0 };
// 有限倍率列表,防止无限放大/缩小导致无法恢复
std::vector<double> speedLevels_{ 0.25, 0.5, 1.0, 2.0, 4.0, 8.0 };
int speedIndex_{ 2 }; // 默认指向 1.0x
PlayStatus playStatus_{ PlayStatus::PS_Stoped };
WorkSpace* workSpace_{ nullptr };
bool hasManualRange_{ false };
double manualStart_{ 0.0 };
double manualEnd_{ 0.0 };
};

View File

@ -209,6 +209,11 @@ void WorkSpace::RemoveEntity(Entity* entity) {
auto itor = std::find_if(entities_.begin(), entities_.end(),
[this, entity](const auto* item) { return item == entity; });
if (itor != entities_.end()) {
bool isTrackEntity = entity == trackedEntity_;
LOG_INFO("remove entity: {}, isTrackEntity:{}", entity->GetName().toLocal8Bit().constData(), isTrackEntity);
if (isTrackEntity) {
trackedEntity_ = nullptr;
}
emit EntityRemoved(entity);
entities_.erase(itor);
}
@ -258,24 +263,11 @@ void WorkSpace::SetActiveScene(OEScene* scene) {
//homeViewpoint_ = vp;
}
bool WorkSpace::SetTimestep(Timestep* timestep) {
if (!timestep) {
return false;
void WorkSpace::EnsureTimestep() {
if (nullptr == timestep_) {
timestep_ = new Timestep(this);
emit TimestepChanged(timestep_);
}
if (nullptr != timestep_ && timestep_ != timestep) {
timestep_->deleteLater();
}
timestep_ = timestep;
emit TimestepChanged(timestep_);
return true;
}
bool WorkSpace::SetTimestepPath(const QString& path) {
Timestep* timestep = Timestep::Load(path, this);
return SetTimestep(timestep);
}
bool WorkSpace::SetLampStatus(class LampStatus* lampStatus) {
@ -363,9 +355,7 @@ void WorkSpace::OnLoaded() {
if (nullptr != lampStatus_) {
emit LampStatusChanged(lampStatus_);
}
if (nullptr != timestep_) {
emit TimestepChanged(timestep_);
}
EnsureTimestep();
// Execute commands configured for onLoad
ExecuteCommands(CommandWhen::OnLoad);
}

View File

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <map>
#include <memory>
@ -100,8 +100,7 @@ public:
dyt_check(nullptr != scene_);
return scene_;
}
bool SetTimestep(class Timestep* timestep);
bool SetTimestepPath(const QString& path);
// Timestep lifecycle is managed internally; external setting removed
class Timestep* GetTimestep() const {
return timestep_;
}
@ -132,8 +131,9 @@ public:
void OnLoaded();
// 通知:某类型的文件条目属性已更新(不改变数量,仅触发刷新)
void NotifyFileEntryUpdated(FileEntryType type, std::shared_ptr<FileEntry> fileEntry = nullptr);
// Ensure a timestep exists; create if missing and emit change
void EnsureTimestep();
Q_SIGNALS:
void EntityAdded(class Entity* entity);
@ -171,3 +171,5 @@ public:
friend class WorkSpaceXMLParse;
};

View File

@ -2,6 +2,7 @@
#include "workspace/WorkSpace.h"
#include "entities/EntitiesManager.h"
#include "workspace/Timestep.h"
#include "common/SpdLogger.h"
#include "workspace/WorkSpaceManager.h"
@ -59,14 +60,35 @@ bool WorkSpaceXMLParse::ParseTimestep(const tinyxml2::XMLElement* element) {
LOG_WARN("element is nullptr");
return false;
}
const char* path = element->Attribute("path");
if (nullptr == path) {
LOG_WARN("element not has path");
// 确保存在 Timestep 实例
workSpace_->EnsureTimestep();
Timestep* t = workSpace_->GetTimestep();
if (!t) {
return false;
}
return workSpace_->SetTimestepPath(path);
// 解析属性manual(0/1)、start、end、step
int manualFlag = 0;
element->QueryIntAttribute("manual", &manualFlag);
double start = 0.0;
double end = 0.0;
double step = 0.0;
bool hasStart = (element->QueryDoubleAttribute("start", &start) == tinyxml2::XML_SUCCESS);
bool hasEnd = (element->QueryDoubleAttribute("end", &end) == tinyxml2::XML_SUCCESS);
bool hasStep = (element->QueryDoubleAttribute("step", &step) == tinyxml2::XML_SUCCESS);
if (hasStep) {
t->SetSpeed(step);
}
// 若标记为手动或具有完整的 start/end则设置手动范围否则按数据最大时间设置边界
if (manualFlag != 0 || (hasStart && hasEnd)) {
t->SetManualRange(hasStart ? start : 0.0, hasEnd ? end : 0.0);
} else if (hasEnd) {
t->SetDataMaxTime(end);
}
return true;
}
bool WorkSpaceXMLParse::ParseLamp(const tinyxml2::XMLElement* element) {

View File

@ -34,9 +34,9 @@ bool WorkSpaceXMLWrite::Save(const QString& path) {
}
doc.LinkEndChild(scene);
SaveTimeStep(scene);
SaveChart(scene, &doc);
SaveTimeStep(scene);
SaveLamp(scene);
SaveCommond(scene);
SaveFiles(scene, &doc);
@ -66,13 +66,31 @@ bool WorkSpaceXMLWrite::SaveScene(tinyxml2::XMLElement* scene) {
return true;
}
// Removed: SaveTimeStep — timestep is no longer file-based
// 新增:保存 Timestep 的手动范围与步长倍率
bool WorkSpaceXMLWrite::SaveTimeStep(tinyxml2::XMLElement* scene) {
Timestep* timestep = workSpace_->GetTimestep();
if (nullptr == timestep) {
if (nullptr == workSpace_) {
return false;
}
tinyxml2::XMLElement* timestepXml = scene->InsertNewChildElement("timestep");
timestepXml->SetAttribute("path", timestep->GetPath().toStdString().c_str());
auto* t = workSpace_->GetTimestep();
if (!t) {
// 若尚未创建,确保存在一个默认实例,以保证文件的一致性
workSpace_->EnsureTimestep();
t = workSpace_->GetTimestep();
}
if (!t) {
return false;
}
double start = 0.0, end = 0.0, step = 1.0;
t->GetRange(start, end, step);
tinyxml2::XMLElement* timestep = scene->InsertNewChildElement("timestep");
timestep->SetAttribute("manual", t->HasManualRange() ? 1 : 0);
timestep->SetAttribute("start", start);
timestep->SetAttribute("end", end);
timestep->SetAttribute("step", step);
return true;
}