2026-05-08 18:03:54 +08:00
|
|
|
|
#include "diagramEditor/editorDiagramLayoutEngine.h"
|
|
|
|
|
|
#include "diagramEditor/editorDirectionManager.h"
|
2026-05-11 18:38:15 +08:00
|
|
|
|
#include "graphicsDataModel/diagramEditorModel.h"
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 主入口函数
|
|
|
|
|
|
QRectF DiagramLayoutEngine::executeLayout(
|
|
|
|
|
|
QMap<QString, DiagramEditorRouteInfo>& routes,
|
|
|
|
|
|
QMap<QString, DiagramEditorComponentInfo>& components,
|
2026-05-28 10:43:54 +08:00
|
|
|
|
QMap<QString, QMap<int,DirectionOccupancyRecord>>& directionOccupancyMap,
|
|
|
|
|
|
QMap<QString, DirectionOccupancyRouteInfo>& routeDirectionMap,
|
2026-05-08 18:03:54 +08:00
|
|
|
|
const LayoutConfig& config,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
context.initComponentsCache(components);
|
2026-05-28 10:43:54 +08:00
|
|
|
|
context.initDirectionOccupancyCache(directionOccupancyMap);
|
|
|
|
|
|
context.initRouteDirectionMapCache(routeDirectionMap);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
context.itemCache.clear();
|
|
|
|
|
|
|
|
|
|
|
|
QString mainRouteName = findMainRoute(routes);
|
|
|
|
|
|
if (mainRouteName.isEmpty()) {
|
|
|
|
|
|
qWarning() << "No main route found";
|
|
|
|
|
|
return QRectF();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// 1. 布局主线
|
|
|
|
|
|
layoutMainRoute(routes[mainRouteName], config, context);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-14 20:11:48 +08:00
|
|
|
|
// 设置一级支线父方向
|
|
|
|
|
|
for (auto it = routes.begin(); it != routes.end(); ++it) {
|
|
|
|
|
|
if (it->bMainRoute) continue;
|
|
|
|
|
|
if(it->sParentRoute == mainRouteName) //一级支线方向
|
|
|
|
|
|
it->preferDirection = config.subDirection();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (auto it = routes.begin(); it != routes.end(); ++it) {
|
|
|
|
|
|
if (it->bMainRoute) continue;
|
|
|
|
|
|
if(it->sParentRoute == mainRouteName)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
it->preferDirection = config.mainDirection(); //二级支线方向(与主线同向)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// 2. 布局所有支线(✅ 此时方向数据必须是最新的)
|
2026-05-08 18:03:54 +08:00
|
|
|
|
for (auto it = routes.begin(); it != routes.end(); ++it) {
|
|
|
|
|
|
if (it->sRouteName == mainRouteName) continue;
|
|
|
|
|
|
layoutBranchRoute(*it, config, context);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 15:40:30 +08:00
|
|
|
|
auto mapSameDirNode = getCommonDirectionNode(routes,context); //获取相同占位的点(特殊处理)
|
|
|
|
|
|
if(!mapSameDirNode.isEmpty()){
|
|
|
|
|
|
updateConfilicNode(routes,context,config,mapSameDirNode);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// ✅ 3. 所有布局完成之后,才更新 components
|
2026-05-08 18:03:54 +08:00
|
|
|
|
if (!context.saveToModel) {
|
2026-05-11 18:38:15 +08:00
|
|
|
|
for(auto& compo:components){ //从compo缓存中读取计算过的数据
|
|
|
|
|
|
auto mapCatch = context.componentsCache;
|
|
|
|
|
|
compo.nUsedDirection = mapCatch[compo.sName].nUsedDirection;
|
|
|
|
|
|
compo.nRotate = mapCatch[compo.sName].nRotate;
|
|
|
|
|
|
compo.deltaPos = mapCatch[compo.sName].deltaPos;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-29 17:42:30 +08:00
|
|
|
|
directionOccupancyMap = context.directionOccupancyMap;
|
|
|
|
|
|
routeDirectionMap = context.routeDirectionMap;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// 4. 计算边界
|
2026-05-08 18:03:54 +08:00
|
|
|
|
if (!context.saveToModel) {
|
|
|
|
|
|
return calculateBoundingRect(components);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
return QRectF();
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 查找主线
|
|
|
|
|
|
QString DiagramLayoutEngine::findMainRoute(
|
|
|
|
|
|
const QMap<QString, DiagramEditorRouteInfo>& routes) {
|
|
|
|
|
|
|
|
|
|
|
|
QString mainRoute;
|
|
|
|
|
|
bool hasMain = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找标记为主线
|
|
|
|
|
|
for (auto it = routes.begin(); it != routes.end(); ++it) {
|
|
|
|
|
|
if (it->bMainRoute) {
|
|
|
|
|
|
mainRoute = it->sRouteName;
|
|
|
|
|
|
hasMain = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 未设置主线,选择设备最多的线路
|
|
|
|
|
|
if (!hasMain) {
|
|
|
|
|
|
int maxCount = 0;
|
|
|
|
|
|
for (auto it = routes.begin(); it != routes.end(); ++it) {
|
|
|
|
|
|
int count = it->lstCompo.size();
|
|
|
|
|
|
if (count > maxCount) {
|
|
|
|
|
|
maxCount = count;
|
|
|
|
|
|
mainRoute = it->sRouteName;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标记为主线
|
|
|
|
|
|
if (!mainRoute.isEmpty()) {
|
|
|
|
|
|
const_cast<QMap<QString, DiagramEditorRouteInfo>&>(routes)[mainRoute].bMainRoute = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return mainRoute;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 布局主线
|
|
|
|
|
|
void DiagramLayoutEngine::layoutMainRoute(
|
|
|
|
|
|
DiagramEditorRouteInfo& route,
|
|
|
|
|
|
const LayoutConfig& config,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
2026-05-28 10:43:54 +08:00
|
|
|
|
// 1. 设置当前线路上下文
|
|
|
|
|
|
QString originalRouteId = context.currentRouteId;
|
|
|
|
|
|
bool originalIsRemoving = context.isRemovingRoute;
|
|
|
|
|
|
|
|
|
|
|
|
context.currentRouteId = route.sRouteName;
|
|
|
|
|
|
context.isRemovingRoute = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 执行布局计算
|
|
|
|
|
|
layoutMainRouteInternal(route, config, context);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 恢复上下文
|
|
|
|
|
|
context.currentRouteId = originalRouteId;
|
|
|
|
|
|
context.isRemovingRoute = originalIsRemoving;
|
|
|
|
|
|
|
|
|
|
|
|
qInfo() << "Main route" << route.sRouteName << "layout completed";
|
|
|
|
|
|
/*Direction mainDir = config.mainDirection();
|
2026-05-08 18:03:54 +08:00
|
|
|
|
auto& components = route.lstCompo;
|
|
|
|
|
|
|
|
|
|
|
|
if (components.isEmpty()) return;
|
|
|
|
|
|
|
|
|
|
|
|
int nSeg = components.size() / 2;
|
2026-05-14 20:11:48 +08:00
|
|
|
|
int nSegIndex =
|
|
|
|
|
|
(mainDir == Direction::Down || mainDir == Direction::Right)
|
|
|
|
|
|
? -nSeg
|
|
|
|
|
|
: nSeg;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < components.size(); ++i) {
|
|
|
|
|
|
DiagramEditorComponentInfo& compo = components[i];
|
|
|
|
|
|
|
|
|
|
|
|
Direction dir = mainDir;
|
2026-05-22 16:08:15 +08:00
|
|
|
|
int mainDirFlag = static_cast<int>(mainDir);
|
|
|
|
|
|
int oppositeFlag = static_cast<int>(DirectionManager::getOpposite(mainDir));
|
|
|
|
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
|
// 队首:只占用出方向
|
|
|
|
|
|
compo.nUsedDirection |= mainDirFlag;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (i == components.size() - 1) {
|
|
|
|
|
|
// 队尾:只占用入方向
|
|
|
|
|
|
compo.nUsedDirection |= oppositeFlag;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
// 中间节点:入方向 + 出方向(一次性占满)
|
|
|
|
|
|
compo.nUsedDirection |= (mainDirFlag | oppositeFlag);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-14 20:11:48 +08:00
|
|
|
|
QPoint delta(0, 0);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
int spacing = DirectionManager::isHorizontal(mainDir)
|
|
|
|
|
|
? config.horizontalSpacing()
|
|
|
|
|
|
: config.verticalSpacing();
|
|
|
|
|
|
|
2026-05-14 20:11:48 +08:00
|
|
|
|
if (DirectionManager::isHorizontal(mainDir))
|
2026-05-08 18:03:54 +08:00
|
|
|
|
delta.setX(nSegIndex * spacing);
|
2026-05-14 20:11:48 +08:00
|
|
|
|
else
|
2026-05-08 18:03:54 +08:00
|
|
|
|
delta.setY(nSegIndex * spacing);
|
|
|
|
|
|
|
|
|
|
|
|
int rotate = DirectionManager::getRotationAngle(mainDir);
|
|
|
|
|
|
|
2026-05-14 20:11:48 +08:00
|
|
|
|
// ✅ 主线方向 → 统一交给 updateComponent
|
2026-05-22 16:08:15 +08:00
|
|
|
|
updateComponent(compo, compo.nUsedDirection, delta, rotate, context); //**方向赋值重复,待修改
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-14 20:11:48 +08:00
|
|
|
|
nSegIndex += (mainDir == Direction::Up || mainDir == Direction::Left)
|
|
|
|
|
|
? -1
|
|
|
|
|
|
: 1;
|
2026-05-28 10:43:54 +08:00
|
|
|
|
}*/
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiagramLayoutEngine::layoutMainRouteInternal(
|
|
|
|
|
|
DiagramEditorRouteInfo& route,
|
|
|
|
|
|
const LayoutConfig& config,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
QString routeId = route.sRouteName;
|
|
|
|
|
|
auto& components = route.lstCompo;
|
|
|
|
|
|
if (components.isEmpty()) return;
|
|
|
|
|
|
|
|
|
|
|
|
Direction mainDir = config.mainDirection();
|
|
|
|
|
|
int mainDirFlag = static_cast<int>(mainDir);
|
|
|
|
|
|
int oppositeFlag = static_cast<int>(DirectionManager::getOpposite(mainDir));
|
|
|
|
|
|
bool isHorizontal = DirectionManager::isHorizontal(mainDir);
|
|
|
|
|
|
int spacing = isHorizontal ? config.horizontalSpacing() : config.verticalSpacing();
|
2026-06-02 11:50:45 +08:00
|
|
|
|
if(components.size() == 2) //两个元件的主线拉长(特殊处理)
|
|
|
|
|
|
spacing *= 2;
|
2026-05-28 10:43:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算分段索引
|
|
|
|
|
|
int nSeg = components.size() - 1; // 实际段数
|
|
|
|
|
|
int nSegIndex = (mainDir == Direction::Down || mainDir == Direction::Right)
|
|
|
|
|
|
? -nSeg : nSeg;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < components.size(); ++i) {
|
|
|
|
|
|
DiagramEditorComponentInfo& compo = components[i];
|
|
|
|
|
|
const QString& compoName = compo.sName;
|
|
|
|
|
|
|
|
|
|
|
|
int dirMask = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算方向占用掩码
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
|
dirMask = mainDirFlag; // 队首:出方向
|
|
|
|
|
|
} else if (i == components.size() - 1) {
|
|
|
|
|
|
dirMask = oppositeFlag; // 队尾:入方向
|
|
|
|
|
|
} else {
|
|
|
|
|
|
dirMask = mainDirFlag | oppositeFlag; // 中间:入方向 + 出方向
|
|
|
|
|
|
}
|
|
|
|
|
|
// 计算位置偏移
|
|
|
|
|
|
QPoint delta(0, 0);
|
|
|
|
|
|
if (isHorizontal) {
|
|
|
|
|
|
delta.setX(nSegIndex * spacing);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
delta.setY(nSegIndex * spacing);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-02 11:50:45 +08:00
|
|
|
|
int rotate = 0;
|
|
|
|
|
|
if(i == 0 && (compo.nType == 6)) //单port元件队首反向
|
|
|
|
|
|
rotate = DirectionManager::getRotationAngle(DirectionManager::getOpposite(mainDir));
|
|
|
|
|
|
else
|
|
|
|
|
|
DirectionManager::getOpposite(mainDir);
|
2026-05-28 10:43:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新组件
|
|
|
|
|
|
updateComponent(compo, dirMask, delta, rotate, context);
|
|
|
|
|
|
|
|
|
|
|
|
//recordRouteDirectionOccupancy(routeId, compoName, dirMask ,context);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新索引
|
|
|
|
|
|
nSegIndex += (mainDir == Direction::Up || mainDir == Direction::Left)
|
|
|
|
|
|
? -1 : 1;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 拆分支线
|
|
|
|
|
|
void DiagramLayoutEngine::splitBranchRoute(
|
|
|
|
|
|
DiagramEditorRouteInfo& route,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
route.lstOrder.clear();
|
|
|
|
|
|
route.lstReverse.clear();
|
|
|
|
|
|
|
|
|
|
|
|
if (route.lstCompo.isEmpty()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查首尾元件
|
|
|
|
|
|
int firstDir = getComponentDirection(route.lstCompo.first().sName, context);
|
|
|
|
|
|
int lastDir = getComponentDirection(route.lstCompo.last().sName, context);
|
|
|
|
|
|
|
|
|
|
|
|
if (firstDir != 0) {
|
|
|
|
|
|
// 情况1: 首元件是节点
|
|
|
|
|
|
route.lstOrder = route.lstCompo;
|
|
|
|
|
|
} else if (lastDir != 0) {
|
|
|
|
|
|
// 情况2: 末元件是节点
|
|
|
|
|
|
for (auto it = route.lstCompo.rbegin(); it != route.lstCompo.rend(); ++it) {
|
|
|
|
|
|
route.lstReverse.append(*it);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 情况3: 查找中间节点
|
|
|
|
|
|
int nodeIndex = -1;
|
|
|
|
|
|
for (int i = 0; i < route.lstCompo.size(); ++i) {
|
|
|
|
|
|
int dir = getComponentDirection(route.lstCompo[i].sName, context);
|
|
|
|
|
|
if (dir != 0) {
|
|
|
|
|
|
nodeIndex = i;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nodeIndex == -1) {
|
|
|
|
|
|
// 没有节点,默认为正序
|
|
|
|
|
|
route.lstOrder = route.lstCompo;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 反向序列: 从节点到开头
|
|
|
|
|
|
for (int i = nodeIndex; i >= 0; --i) {
|
|
|
|
|
|
route.lstReverse.append(route.lstCompo[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正序序列: 从节点到结尾
|
|
|
|
|
|
for (int i = nodeIndex; i < route.lstCompo.size(); ++i) {
|
|
|
|
|
|
route.lstOrder.append(route.lstCompo[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 布局支线
|
|
|
|
|
|
void DiagramLayoutEngine::layoutBranchRoute(
|
|
|
|
|
|
DiagramEditorRouteInfo& route,
|
|
|
|
|
|
const LayoutConfig& config,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
2026-05-28 10:43:54 +08:00
|
|
|
|
// 1. 设置当前线路上下文
|
|
|
|
|
|
QString originalRouteId = context.currentRouteId;
|
|
|
|
|
|
bool originalIsRemoving = context.isRemovingRoute;
|
|
|
|
|
|
|
|
|
|
|
|
context.currentRouteId = route.sRouteName;
|
|
|
|
|
|
context.isRemovingRoute = false;
|
|
|
|
|
|
|
2026-05-08 18:03:54 +08:00
|
|
|
|
splitBranchRoute(route, context);
|
|
|
|
|
|
|
|
|
|
|
|
if (route.lstOrder.size() > 1) {
|
2026-05-28 10:43:54 +08:00
|
|
|
|
layoutBranchSequence(route.lstOrder, route.preferDirection,
|
|
|
|
|
|
config, context, true, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 布局反序序列
|
|
|
|
|
|
if (route.lstReverse.size() > 1) {
|
2026-06-04 15:40:30 +08:00
|
|
|
|
layoutBranchSequence(route.lstReverse, DirectionManager::getOpposite(route.preferDirection),
|
2026-05-28 10:43:54 +08:00
|
|
|
|
config, context, false, -1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 恢复上下文
|
|
|
|
|
|
context.currentRouteId = originalRouteId;
|
|
|
|
|
|
context.isRemovingRoute = originalIsRemoving;
|
|
|
|
|
|
|
|
|
|
|
|
qInfo() << "Branch route" << route.sRouteName << "layout completed";
|
|
|
|
|
|
/*if (route.lstOrder.size() > 1) {
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// ✅ 第一个元件的真实方向,由 determineBranchDirection 决定
|
|
|
|
|
|
Direction startDir =
|
|
|
|
|
|
determineBranchDirection(route.lstOrder[0],
|
2026-05-14 20:11:48 +08:00
|
|
|
|
route.preferDirection,
|
2026-05-11 18:38:15 +08:00
|
|
|
|
context);
|
|
|
|
|
|
|
|
|
|
|
|
int polarity = 1;
|
|
|
|
|
|
layoutBranchSequence(route.lstOrder,
|
|
|
|
|
|
startDir,
|
|
|
|
|
|
config,
|
|
|
|
|
|
context,
|
|
|
|
|
|
true,
|
|
|
|
|
|
polarity);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (route.lstReverse.size() > 1) {
|
2026-05-11 18:38:15 +08:00
|
|
|
|
Direction startDir =
|
|
|
|
|
|
determineBranchDirection(route.lstReverse[0],
|
2026-05-14 20:11:48 +08:00
|
|
|
|
route.preferDirection,
|
2026-05-11 18:38:15 +08:00
|
|
|
|
context);
|
|
|
|
|
|
int polarity = -1;
|
|
|
|
|
|
layoutBranchSequence(route.lstReverse,
|
|
|
|
|
|
startDir,
|
|
|
|
|
|
config,
|
|
|
|
|
|
context,
|
|
|
|
|
|
false,
|
|
|
|
|
|
polarity);
|
2026-05-28 10:43:54 +08:00
|
|
|
|
}*/
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 布局分支序列
|
|
|
|
|
|
void DiagramLayoutEngine::layoutBranchSequence(
|
|
|
|
|
|
QList<DiagramEditorComponentInfo>& sequence,
|
|
|
|
|
|
Direction branchDir,
|
|
|
|
|
|
const LayoutConfig& config,
|
|
|
|
|
|
Context& context,
|
2026-05-11 18:38:15 +08:00
|
|
|
|
bool isOrder,
|
2026-06-04 15:40:30 +08:00
|
|
|
|
int polarity,
|
|
|
|
|
|
double sizeFacotr) {
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (sequence.size() < 2) return;
|
|
|
|
|
|
|
2026-05-28 10:43:54 +08:00
|
|
|
|
QString routeId = context.currentRouteId;
|
|
|
|
|
|
bool isVertical = DirectionManager::isVertical(branchDir);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// ✅ 2. 间距由主线方向决定
|
2026-05-14 20:11:48 +08:00
|
|
|
|
int spacing = isVertical
|
|
|
|
|
|
? config.verticalSpacing()
|
|
|
|
|
|
: config.horizontalSpacing();
|
2026-06-04 15:40:30 +08:00
|
|
|
|
spacing *= sizeFacotr;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
for (int i = 0; i < sequence.size(); ++i) {
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-06-04 15:40:30 +08:00
|
|
|
|
DiagramEditorComponentInfo& compo = sequence[i];
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// ✅ 当前元件作为“起点”
|
|
|
|
|
|
QPoint basePos = getComponentPosition(sequence[i].sName, context);
|
|
|
|
|
|
Direction dir =
|
2026-06-04 15:40:30 +08:00
|
|
|
|
determineBranchDirection(sequence[i], branchDir, context, polarity);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// ✅ 如果后面还有元件,才计算偏移
|
|
|
|
|
|
if (i + 1 < sequence.size()) {
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
DiagramEditorComponentInfo& next = sequence[i + 1];
|
|
|
|
|
|
int offset = (i + 1) * polarity * spacing;
|
|
|
|
|
|
QPoint nextPos = basePos;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-14 20:11:48 +08:00
|
|
|
|
if (isVertical) {
|
2026-05-11 18:38:15 +08:00
|
|
|
|
nextPos.setY(basePos.y() + offset);
|
2026-05-14 20:11:48 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
nextPos.setX(basePos.x() + offset);
|
2026-05-11 18:38:15 +08:00
|
|
|
|
}
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
QPoint deltaPos = nextPos - basePos;
|
|
|
|
|
|
Direction nextConnectionDir =
|
|
|
|
|
|
DirectionManager::getOpposite(dir);
|
2026-06-04 15:40:30 +08:00
|
|
|
|
int nextRotate =
|
2026-05-11 18:38:15 +08:00
|
|
|
|
DirectionManager::getRotationAngle(dir);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-06-04 15:40:30 +08:00
|
|
|
|
int curRotate = nextRotate; //当前元件旋转,默认与下个相同
|
|
|
|
|
|
//if(i == 0 && (compo.nType == 11 || compo.nType == 12)) //单port元件队首反向(避雷器,带电指示器)
|
|
|
|
|
|
// curRotate = DirectionManager::getRotationAngle(DirectionManager::getOpposite(dir));
|
|
|
|
|
|
|
2026-05-28 10:43:54 +08:00
|
|
|
|
updateComponent(sequence[i], int(dir),
|
2026-06-04 15:40:30 +08:00
|
|
|
|
basePos, curRotate, context);
|
2026-05-28 10:43:54 +08:00
|
|
|
|
|
2026-05-22 16:08:15 +08:00
|
|
|
|
updateComponent(next, int(nextConnectionDir),
|
2026-06-04 15:40:30 +08:00
|
|
|
|
nextPos, nextRotate, context);
|
2026-05-11 18:38:15 +08:00
|
|
|
|
}
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-14 20:11:48 +08:00
|
|
|
|
|
2026-05-08 18:03:54 +08:00
|
|
|
|
// 确定支线方向
|
|
|
|
|
|
Direction DiagramLayoutEngine::determineBranchDirection(
|
|
|
|
|
|
const DiagramEditorComponentInfo& currentNode,
|
|
|
|
|
|
Direction preferredDir,
|
2026-06-04 15:40:30 +08:00
|
|
|
|
Context& context,
|
|
|
|
|
|
int forward) {
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
|
|
|
|
|
int usedDirections = getComponentDirection(currentNode.sName, context);
|
2026-05-14 20:11:48 +08:00
|
|
|
|
bool horizontal = DirectionManager::isHorizontal(preferredDir);
|
|
|
|
|
|
|
|
|
|
|
|
if (horizontal) {
|
|
|
|
|
|
bool left = DirectionManager::isDirectionOccupied(usedDirections, Direction::Left);
|
|
|
|
|
|
bool right = DirectionManager::isDirectionOccupied(usedDirections, Direction::Right);
|
|
|
|
|
|
|
|
|
|
|
|
if (!left && right) return Direction::Left;
|
|
|
|
|
|
if (left && !right) return Direction::Right;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
} else {
|
2026-05-14 20:11:48 +08:00
|
|
|
|
bool up = DirectionManager::isDirectionOccupied(usedDirections, Direction::Up);
|
|
|
|
|
|
bool down = DirectionManager::isDirectionOccupied(usedDirections, Direction::Down);
|
|
|
|
|
|
|
|
|
|
|
|
if (!up && down) return Direction::Up;
|
|
|
|
|
|
if (up && !down) return Direction::Down;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
2026-05-14 20:11:48 +08:00
|
|
|
|
|
|
|
|
|
|
return preferredDir;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取组件位置
|
|
|
|
|
|
QPoint DiagramLayoutEngine::getComponentPosition(
|
|
|
|
|
|
const QString& componentName,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
if (context.saveToModel) {
|
|
|
|
|
|
QStandardItem* item = getNameItem(componentName, context);
|
|
|
|
|
|
if (item) {
|
|
|
|
|
|
QVariant posData = item->data(Qt::UserRole + 2);
|
|
|
|
|
|
if (posData.isValid() && posData.canConvert<QPoint>()) {
|
|
|
|
|
|
return posData.toPoint();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-11 18:38:15 +08:00
|
|
|
|
return QPoint(0, 0);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
} else {
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// ✅ 核心修复:优先使用 componentsCache
|
2026-05-08 18:03:54 +08:00
|
|
|
|
if (context.componentsCache.contains(componentName)) {
|
|
|
|
|
|
return context.componentsCache[componentName].deltaPos;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
// ✅ 终极兜底:如果 cache 里也没有,从原始 components 取
|
|
|
|
|
|
QStandardItem* item = getNameItem(componentName, context);
|
|
|
|
|
|
if (item) {
|
|
|
|
|
|
QVariant posData = item->data(Qt::UserRole + 2);
|
|
|
|
|
|
if (posData.isValid() && posData.canConvert<QPoint>()) {
|
|
|
|
|
|
return posData.toPoint(); // ✅ 返回真实的 deltaPos
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return QPoint(0, 0);
|
|
|
|
|
|
}
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取组件方向
|
|
|
|
|
|
int DiagramLayoutEngine::getComponentDirection(
|
|
|
|
|
|
const QString& compoName,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
if (context.saveToModel) {
|
|
|
|
|
|
QStandardItem* item = getNameItem(compoName, context);
|
|
|
|
|
|
return item ? item->data().toInt() : 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (context.componentsCache.contains(compoName)) {
|
|
|
|
|
|
return context.componentsCache[compoName].nUsedDirection;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取名称项
|
|
|
|
|
|
QStandardItem* DiagramLayoutEngine::getNameItem(
|
|
|
|
|
|
const QString& name,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
if (!m_model)
|
|
|
|
|
|
return nullptr;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
|
2026-05-11 18:38:15 +08:00
|
|
|
|
return m_model->getNameItem(name, context.sourceId);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新组件
|
|
|
|
|
|
void DiagramLayoutEngine::updateComponent(
|
|
|
|
|
|
DiagramEditorComponentInfo& compo,
|
2026-05-22 16:08:15 +08:00
|
|
|
|
int dir,
|
2026-05-08 18:03:54 +08:00
|
|
|
|
const QPoint& position,
|
|
|
|
|
|
int rotate,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
2026-05-28 10:43:54 +08:00
|
|
|
|
// 1. 如果是添加操作,记录方向占用
|
|
|
|
|
|
if (dir != 0 && !context.isRemovingRoute) {
|
|
|
|
|
|
QString routeId = context.currentRouteId;
|
|
|
|
|
|
if (!routeId.isEmpty()) {
|
|
|
|
|
|
// 记录方向占用
|
|
|
|
|
|
bool success = recordRouteDirectionOccupancy(routeId, compo.sName, dir,context);
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
// 使用统一的缓存更新函数
|
|
|
|
|
|
int nTotalDir = getComponentTotalOccupiedDirections(compo.sName,context);
|
|
|
|
|
|
updateComponentDirections(compo.sName, nTotalDir, DirectionOperation::ADD, context);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 18:03:54 +08:00
|
|
|
|
if (context.saveToModel) {
|
|
|
|
|
|
QStandardItem* item = getNameItem(compo.sName, context);
|
|
|
|
|
|
if (item) {
|
2026-06-04 15:40:30 +08:00
|
|
|
|
auto it = context.componentsCache.find(compo.sName);
|
|
|
|
|
|
if(it != context.componentsCache.end())
|
|
|
|
|
|
item->setData(it->nUsedDirection, Qt::UserRole + 1); //同步占用到standardModel
|
2026-05-08 18:03:54 +08:00
|
|
|
|
item->setData(position, Qt::UserRole + 2);
|
|
|
|
|
|
item->setData(rotate, Qt::UserRole + 5);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2026-05-28 10:43:54 +08:00
|
|
|
|
//compo.nUsedDirection = DirectionManager::markDirectionOccupied(compo.nUsedDirection, dir);
|
2026-05-08 18:03:54 +08:00
|
|
|
|
compo.deltaPos = position;
|
|
|
|
|
|
compo.nRotate = rotate;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新缓存
|
2026-05-28 10:43:54 +08:00
|
|
|
|
//context.componentsCache[compo.sName].nUsedDirection |= dir;
|
2026-05-11 18:38:15 +08:00
|
|
|
|
context.componentsCache[compo.sName].deltaPos = compo.deltaPos;
|
|
|
|
|
|
context.componentsCache[compo.sName].nRotate = compo.nRotate;
|
2026-05-08 18:03:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-28 10:43:54 +08:00
|
|
|
|
bool DiagramLayoutEngine::recordRouteDirectionOccupancy(
|
|
|
|
|
|
const QString& routeId,
|
|
|
|
|
|
const QString& compoName,
|
|
|
|
|
|
int directionMask,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
if (directionMask == 0 || routeId.isEmpty() || compoName.isEmpty()) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 检查冲突
|
|
|
|
|
|
auto& dirRecords = context.directionOccupancyMap[compoName];
|
|
|
|
|
|
bool allRecorded = true;
|
|
|
|
|
|
|
|
|
|
|
|
for (int dirIndex = 0; dirIndex < 4; ++dirIndex) {
|
|
|
|
|
|
int dirBit = 1 << dirIndex;
|
|
|
|
|
|
|
|
|
|
|
|
if ((directionMask & dirBit) == 0) {
|
|
|
|
|
|
continue; // 不是要记录的方向
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查方向冲突
|
|
|
|
|
|
if (!dirRecords[dirIndex].routeId.isEmpty() &&
|
|
|
|
|
|
dirRecords[dirIndex].routeId != routeId) {
|
|
|
|
|
|
qWarning() << "方向冲突: 组件" << compoName << "方向"
|
|
|
|
|
|
<< getDirectionName(dirBit) << "已被线路"
|
|
|
|
|
|
<< dirRecords[dirIndex].routeId << "占用";
|
|
|
|
|
|
allRecorded = false;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!allRecorded) {
|
|
|
|
|
|
return false; // 有冲突,不记录
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 记录到全局表
|
|
|
|
|
|
for (int dirIndex = 0; dirIndex < 4; ++dirIndex) {
|
|
|
|
|
|
int dirBit = 1 << dirIndex;
|
|
|
|
|
|
|
|
|
|
|
|
if ((directionMask & dirBit) == 0) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dirRecords[dirIndex] = DirectionOccupancyRecord{
|
|
|
|
|
|
.routeId = routeId,
|
|
|
|
|
|
.directionMask = dirBit,
|
|
|
|
|
|
.timestamp = QDateTime::currentMSecsSinceEpoch()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
qDebug() << "记录: 线路" << routeId << "占用组件" << compoName
|
|
|
|
|
|
<< "方向" << getDirectionName(dirBit);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 更新线路的routeInfo(关键变化:只存储实际占用的方向)
|
|
|
|
|
|
DirectionOccupancyRouteInfo& routeInfo = context.routeDirectionMap[routeId];
|
|
|
|
|
|
|
|
|
|
|
|
if (routeInfo.contains(compoName)) {
|
|
|
|
|
|
// 合并方向
|
|
|
|
|
|
int existingMask = routeInfo.getDirection(compoName);
|
|
|
|
|
|
routeInfo.updateDirection(compoName, existingMask | directionMask);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 添加组件
|
|
|
|
|
|
routeInfo.addComponent(compoName, directionMask);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiagramLayoutEngine::updateComponentDirections(const QString& compoName,
|
|
|
|
|
|
int directionMask,
|
|
|
|
|
|
DirectionOperation operation,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
if (compoName.isEmpty()) {
|
|
|
|
|
|
qWarning() << "Component name is empty";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (directionMask == 0) {
|
|
|
|
|
|
qDebug() << "Direction mask is zero, skipping update for" << compoName;
|
|
|
|
|
|
return true; // 方向掩码为0也算成功
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证方向掩码
|
|
|
|
|
|
/*if (!isValidDirectionMask(directionMask)) {
|
|
|
|
|
|
qWarning() << "Invalid direction mask:" << directionMask
|
|
|
|
|
|
<< "for component" << compoName;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 更新缓存
|
|
|
|
|
|
bool cacheUpdated = updateComponentCacheInternal(compoName, directionMask, operation, context);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 如果需要,更新模型
|
|
|
|
|
|
bool modelUpdated = true; // 默认成功
|
|
|
|
|
|
if (context.saveToModel) {
|
|
|
|
|
|
modelUpdated = updateComponentModel(compoName, directionMask, operation, context);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return cacheUpdated && modelUpdated;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiagramLayoutEngine::updateComponentCacheInternal(const QString& compoName,
|
|
|
|
|
|
int directionMask,
|
|
|
|
|
|
DirectionOperation operation,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
auto it = context.componentsCache.find(compoName);
|
|
|
|
|
|
|
|
|
|
|
|
switch (operation) {
|
|
|
|
|
|
case DirectionOperation::ADD: {
|
|
|
|
|
|
if (it == context.componentsCache.end()) {
|
|
|
|
|
|
// 创建新缓存条目
|
|
|
|
|
|
DiagramEditorComponentInfo info;
|
|
|
|
|
|
info.nUsedDirection = directionMask;
|
|
|
|
|
|
context.componentsCache[compoName] = info;
|
|
|
|
|
|
|
|
|
|
|
|
qDebug() << "catch create compo" << compoName
|
|
|
|
|
|
<< "used dir:" << directionMask;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
int oldDir = it->nUsedDirection;
|
|
|
|
|
|
it->nUsedDirection |= directionMask;
|
|
|
|
|
|
|
|
|
|
|
|
if (oldDir != it->nUsedDirection) {
|
|
|
|
|
|
qDebug() << "catch add compo" << compoName << "direction:"
|
|
|
|
|
|
<< oldDir << "→"
|
|
|
|
|
|
<< it->nUsedDirection;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case DirectionOperation::REMOVE: {
|
|
|
|
|
|
if (it == context.componentsCache.end()) {
|
|
|
|
|
|
qWarning() << compoName << "does't exist";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int oldDir = it->nUsedDirection;
|
|
|
|
|
|
it->nUsedDirection &= ~directionMask;
|
|
|
|
|
|
|
|
|
|
|
|
qDebug() << "catch remove compo" << compoName << "direction:"
|
|
|
|
|
|
<< oldDir << "→"
|
|
|
|
|
|
<< it->nUsedDirection;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有占用的方向,清理缓存
|
|
|
|
|
|
/*if (it->nUsedDirection == 0) {
|
|
|
|
|
|
context.componentsCache.erase(it);
|
|
|
|
|
|
qDebug() << "[缓存] 移除组件" << compoName << "(无占用方向)";
|
|
|
|
|
|
}*/
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case DirectionOperation::SET: {
|
|
|
|
|
|
if (it == context.componentsCache.end()) {
|
|
|
|
|
|
DiagramEditorComponentInfo info;
|
|
|
|
|
|
info.nUsedDirection = directionMask;
|
|
|
|
|
|
context.componentsCache[compoName] = info;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
it->nUsedDirection = directionMask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
qDebug() << "catch set compo" << compoName
|
|
|
|
|
|
<< "direction:" << directionMask;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
qWarning() << "catch unknown";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiagramLayoutEngine::updateComponentModel(const QString& compoName,
|
|
|
|
|
|
int directionMask,
|
|
|
|
|
|
DirectionOperation operation,
|
|
|
|
|
|
Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
QStandardItem* item = getNameItem(compoName, context);
|
|
|
|
|
|
if (!item) {
|
|
|
|
|
|
qWarning() << "model can't find" << compoName << " model";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int currentDir = item->data().toInt(); // 当前模型中的方向
|
|
|
|
|
|
int newDir = currentDir;
|
|
|
|
|
|
|
|
|
|
|
|
switch (operation) {
|
|
|
|
|
|
case DirectionOperation::ADD:
|
|
|
|
|
|
newDir = currentDir | directionMask;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case DirectionOperation::REMOVE:
|
|
|
|
|
|
newDir = currentDir & ~directionMask;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case DirectionOperation::SET:
|
|
|
|
|
|
newDir = directionMask;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
qWarning() << "[model] unknown";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (currentDir != newDir) {
|
|
|
|
|
|
item->setData(newDir);
|
|
|
|
|
|
qDebug() << "[model] update compo" << compoName << "direction:"
|
|
|
|
|
|
<< currentDir << "→"
|
|
|
|
|
|
<< newDir;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 18:03:54 +08:00
|
|
|
|
// 计算边界矩形
|
|
|
|
|
|
QRectF DiagramLayoutEngine::calculateBoundingRect(
|
|
|
|
|
|
const QMap<QString, DiagramEditorComponentInfo>& components) {
|
|
|
|
|
|
|
|
|
|
|
|
if (components.isEmpty()) {
|
|
|
|
|
|
return QRectF();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
qreal minX = 0, maxX = 0, minY = 0, maxY = 0;
|
|
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
|
|
|
|
for (auto it = components.begin(); it != components.end(); ++it) {
|
|
|
|
|
|
if (it->nUsedDirection == 0) continue; // 未使用的组件
|
|
|
|
|
|
|
|
|
|
|
|
QPointF pos = it->deltaPos;
|
|
|
|
|
|
|
|
|
|
|
|
if (first) {
|
|
|
|
|
|
minX = pos.x();
|
|
|
|
|
|
maxX = pos.x() + m_compoWidth;
|
|
|
|
|
|
minY = pos.y();
|
|
|
|
|
|
maxY = pos.y() + m_compoHeight;
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
minX = qMin(minX, pos.x());
|
|
|
|
|
|
maxX = qMax(maxX, pos.x() + m_compoWidth);
|
|
|
|
|
|
minY = qMin(minY, pos.y());
|
|
|
|
|
|
maxY = qMax(maxY, pos.y() + m_compoHeight);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (first) {
|
|
|
|
|
|
return QRectF(); // 没有使用的组件
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return QRectF(minX, minY, maxX - minX, maxY - minY);
|
|
|
|
|
|
}
|
2026-05-14 20:11:48 +08:00
|
|
|
|
|
|
|
|
|
|
Direction DiagramLayoutEngine::getRouteDirection(
|
|
|
|
|
|
const QString& routeName,
|
|
|
|
|
|
const QMap<QString, DiagramEditorRouteInfo>& routes)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!routes.contains(routeName))
|
|
|
|
|
|
return Direction::Invalid;
|
|
|
|
|
|
|
|
|
|
|
|
const auto& route = routes[routeName];
|
|
|
|
|
|
|
|
|
|
|
|
if (route.bMainRoute)
|
|
|
|
|
|
return Direction::Invalid; // 主线没有父方向
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 主线 → 支线
|
|
|
|
|
|
if (route.preferDirection == Direction::Invalid)
|
|
|
|
|
|
return Direction::Invalid; // 等待填充
|
|
|
|
|
|
|
|
|
|
|
|
return route.preferDirection;
|
|
|
|
|
|
}
|
2026-05-28 10:43:54 +08:00
|
|
|
|
|
|
|
|
|
|
int DiagramLayoutEngine::getComponentTotalOccupiedDirections(const QString& compoName, Context& context) {
|
|
|
|
|
|
auto it = context.directionOccupancyMap.find(compoName);
|
|
|
|
|
|
if (it == context.directionOccupancyMap.end()) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int totalMask = 0;
|
|
|
|
|
|
for (int dirIndex = 0; dirIndex < 4; ++dirIndex) {
|
|
|
|
|
|
if (!it.value()[dirIndex].routeId.isEmpty()) {
|
|
|
|
|
|
totalMask |= it.value()[dirIndex].directionMask;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return totalMask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int DiagramLayoutEngine::getComponentDirectionFromCache(const QString& compoName,const QString& routeId,Context& context)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!context.routeDirectionMap.contains(routeId)) {
|
|
|
|
|
|
qDebug() << "Route" << routeId << "not found in direction map";
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 从全局方向占用表中查找
|
|
|
|
|
|
if (context.directionOccupancyMap.contains(compoName)) {
|
|
|
|
|
|
const auto& dirRecords = context.directionOccupancyMap[compoName];
|
|
|
|
|
|
|
|
|
|
|
|
int directionMask = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历4个方向,检查哪些被指定线路占用
|
|
|
|
|
|
for (int i = 0; i < dirRecords.size(); ++i) {
|
|
|
|
|
|
if (dirRecords[i].routeId == routeId) {
|
|
|
|
|
|
directionMask |= dirRecords[i].directionMask;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (directionMask != 0) {
|
|
|
|
|
|
qDebug() << "Found direction mask" << directionMask
|
|
|
|
|
|
<< "for component" << compoName
|
|
|
|
|
|
<< "in route" << routeId << "from global occupancy map";
|
|
|
|
|
|
return directionMask;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 未找到
|
|
|
|
|
|
qDebug() << "Component" << compoName << "not found in route" << routeId;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 15:40:30 +08:00
|
|
|
|
QMap<int, QList<QString>> DiagramLayoutEngine::getCommonDirectionNode(QMap<QString, DiagramEditorRouteInfo>& routes,Context& context)
|
|
|
|
|
|
{
|
|
|
|
|
|
QMap<int, QList<QString>> directionToNames;
|
|
|
|
|
|
QList<QString> lstMain; //主线设备
|
|
|
|
|
|
for(auto& route:routes){
|
|
|
|
|
|
if(!route.bMainRoute)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
for(auto& compo:route.lstCompo){
|
|
|
|
|
|
lstMain.append(compo.sName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (auto it = context.componentsCache.constBegin(); //寻找相同方位占用的node
|
|
|
|
|
|
it != context.componentsCache.constEnd();
|
|
|
|
|
|
++it)
|
|
|
|
|
|
{
|
|
|
|
|
|
const DiagramEditorComponentInfo& info = it.value();
|
|
|
|
|
|
|
|
|
|
|
|
//if (info.nCategory != 1) //新建时catch中分类无信息
|
|
|
|
|
|
// continue;
|
|
|
|
|
|
|
|
|
|
|
|
directionToNames[info.nUsedDirection].append(it.key());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//从 directionToNames 中移除出现在 lstMain 的node
|
|
|
|
|
|
for (auto it = directionToNames.begin(); it != directionToNames.end(); )
|
|
|
|
|
|
{
|
|
|
|
|
|
QList<QString>& nameList = it.value();
|
|
|
|
|
|
|
|
|
|
|
|
// 移除在 lstMain 中出现的node
|
|
|
|
|
|
nameList.erase(
|
|
|
|
|
|
std::remove_if(nameList.begin(), nameList.end(),
|
|
|
|
|
|
[&](const QString& name) {
|
|
|
|
|
|
return lstMain.contains(name);
|
|
|
|
|
|
}),
|
|
|
|
|
|
nameList.end()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果该方向已无设备,删除该方向
|
|
|
|
|
|
if (nameList.isEmpty())
|
|
|
|
|
|
it = directionToNames.erase(it);
|
|
|
|
|
|
else
|
|
|
|
|
|
++it;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QSet<int> invalidDirections = {
|
|
|
|
|
|
8, 4, 2, 1, // 单方向
|
|
|
|
|
|
12, 3 // 直线组合
|
|
|
|
|
|
};
|
|
|
|
|
|
// 只保留拐角node
|
|
|
|
|
|
for (auto it = directionToNames.begin(); it != directionToNames.end(); )
|
|
|
|
|
|
{
|
|
|
|
|
|
int dir = it.key();
|
|
|
|
|
|
|
|
|
|
|
|
if(it.value().size() == 1){ //删除孤立数据
|
|
|
|
|
|
it = directionToNames.erase(it);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (invalidDirections.contains(dir) || it.value().isEmpty())
|
|
|
|
|
|
it = directionToNames.erase(it);
|
|
|
|
|
|
else
|
|
|
|
|
|
++it;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return directionToNames;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiagramLayoutEngine::updateConfilicNode(QMap<QString, DiagramEditorRouteInfo>& routes,Context& context,const LayoutConfig& config,QMap<int, QList<QString>> directionToNames)
|
|
|
|
|
|
{
|
|
|
|
|
|
QMap<int, QList<QPair<QString, QString>>> directionConflicts;
|
|
|
|
|
|
QList<QString> lstMain; //主线设备
|
|
|
|
|
|
QMap<QString,QList<QString>> mapSub; //支线设备
|
|
|
|
|
|
for(auto& route:routes){
|
|
|
|
|
|
if(!route.bMainRoute)
|
|
|
|
|
|
{
|
|
|
|
|
|
QList<QString> lstSub;
|
|
|
|
|
|
|
|
|
|
|
|
for(auto& compo:route.lstCompo){
|
|
|
|
|
|
lstSub.append(compo.sName);
|
|
|
|
|
|
}
|
|
|
|
|
|
mapSub.insert(route.sRouteName,lstSub);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
for(auto& compo:route.lstCompo){
|
|
|
|
|
|
lstMain.append(compo.sName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历 directionToNames 的每个方向分支
|
|
|
|
|
|
for (auto dirIt = directionToNames.constBegin();
|
|
|
|
|
|
dirIt != directionToNames.constEnd();
|
|
|
|
|
|
++dirIt)
|
|
|
|
|
|
{
|
|
|
|
|
|
int direction = dirIt.key();
|
|
|
|
|
|
const QList<QString>& devList = dirIt.value();
|
|
|
|
|
|
|
|
|
|
|
|
QList<QPair<QString, QString>> conflicts;
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历当前方向分支中的每个设备
|
|
|
|
|
|
for (const QString& dev : devList)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 遍历所有 sub 线路
|
|
|
|
|
|
for (const QList<QString>& subDevs : mapSub)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!subDevs.contains(dev))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找 lstMain 中同时存在于该 sub 的设备
|
|
|
|
|
|
for (const QString& mainDev : lstMain)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (subDevs.contains(mainDev))
|
|
|
|
|
|
{
|
|
|
|
|
|
conflicts.append(qMakePair(dev, mainDev));
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!conflicts.isEmpty() &&
|
|
|
|
|
|
conflicts.last().first == dev)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!conflicts.isEmpty())
|
|
|
|
|
|
directionConflicts[direction] = conflicts;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(!directionConflicts.isEmpty()){
|
|
|
|
|
|
for(auto& lstPair:directionConflicts){
|
|
|
|
|
|
if(lstPair.size() > 1){ //默认处理倒数第2个
|
|
|
|
|
|
QString sObj = lstPair[lstPair.size()-2].first;
|
|
|
|
|
|
relayoutTargetRoute(sObj,routes,context,config);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiagramLayoutEngine::relayoutTargetRoute(const QString& obj,QMap<QString, DiagramEditorRouteInfo>& routes,Context& context,const LayoutConfig& config)
|
|
|
|
|
|
{
|
|
|
|
|
|
for(auto& route:routes){ //对包含本node的线路重绘制
|
|
|
|
|
|
bool containObj = false;
|
|
|
|
|
|
for(auto& compo:route.lstCompo){
|
|
|
|
|
|
if(obj == compo.sName){
|
|
|
|
|
|
containObj = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if(containObj){
|
|
|
|
|
|
clearRouteDirectionOccupancy(route.sRouteName,context); //重构时先移除占用
|
|
|
|
|
|
if (route.lstOrder.size() > 1) {
|
|
|
|
|
|
layoutBranchSequence(route.lstOrder, route.preferDirection,
|
|
|
|
|
|
config, context, true, 1, 2.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (route.lstReverse.size() > 1) {
|
|
|
|
|
|
layoutBranchSequence(route.lstReverse, DirectionManager::getOpposite(route.preferDirection),
|
|
|
|
|
|
config, context, false, -1, 2.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-28 10:43:54 +08:00
|
|
|
|
bool DiagramLayoutEngine::clearRouteDirectionOccupancy(const QString& routeId,Context& context) {
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 查找要清理的线路
|
|
|
|
|
|
auto routeIt = context.routeDirectionMap.find(routeId);
|
|
|
|
|
|
if (routeIt == context.routeDirectionMap.end()) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DirectionOccupancyRouteInfo& routeToClear = routeIt.value();
|
|
|
|
|
|
|
|
|
|
|
|
qDebug() << "=== 清理线路:" << routeId << "===";
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 从全局表中清理这个线路的所有占用
|
|
|
|
|
|
for (const QString& compoName : routeToClear.getAllComponents()) {
|
|
|
|
|
|
int routeDirection = routeToClear.getDirection(compoName);
|
|
|
|
|
|
|
|
|
|
|
|
qDebug() << "clear compo" << compoName << "direction:" << routeDirection;
|
|
|
|
|
|
|
|
|
|
|
|
// 从全局表中清理
|
|
|
|
|
|
auto& dirRecords = context.directionOccupancyMap[compoName];
|
|
|
|
|
|
|
|
|
|
|
|
for (int dirIndex = 0; dirIndex < 4; ++dirIndex) {
|
|
|
|
|
|
int dirBit = 1 << dirIndex;
|
|
|
|
|
|
|
|
|
|
|
|
if ((routeDirection & dirBit) == 0) {
|
|
|
|
|
|
continue; // 不是这个线路占用的方向
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!dirRecords[dirIndex].routeId.isEmpty() &&
|
|
|
|
|
|
dirRecords[dirIndex].routeId == routeId) {
|
|
|
|
|
|
|
|
|
|
|
|
qDebug() << " clear direction" << getDirectionName(dirBit);
|
|
|
|
|
|
dirRecords[dirIndex] = DirectionOccupancyRecord{};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新组件缓存
|
|
|
|
|
|
updateComponentDirections(compoName, routeDirection, DirectionOperation::REMOVE, context);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 从routeDirectionMap中删除这个线路
|
|
|
|
|
|
context.routeDirectionMap.erase(routeIt);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 清理空的组件条目(可选)
|
|
|
|
|
|
//cleanupEmptyComponents(context);
|
|
|
|
|
|
|
|
|
|
|
|
qDebug() << "=== route" << routeId << "clear ===";
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|