Enterprise Architect -> 如何使用对 .eap 文件 (.mdb) 的 SQL 查询获取端节点的边缘

Enterprise Architect -> How to get the edge of the end node using a SQL Query on the .eap-File (.mdb)

我必须在服务器上没有安装 EA 的情况下仅使用 .eap 文件绘制一些 EA 图。所以我通过 ODBC 将其作为 MDB 文件打开。

我知道有属性 t_diagramlinks.Geometry(边={1,2,3,4})和属性 t_connector.Start_Edge 以及属性 t_connector.End_Edge

具有属性 Geometry 的 table t_diagramlinks 依赖于图表。 具有属性 .Start_Edge.End_Edge 的 table t_connector 不依赖于图 --> 可能存在未在图上绘制的连接。

我知道t_diagramlinks的SX、SY、EX、EY是相对于绘制在图上的每个节点的原点的坐标。

问题:EX / EY 有时为零并且没有将结束线绘制到节点的边缘。估计跟鼠标释放位置有关

"My Interpretation" 下面是我的渲染器根据我的假设生成的结果。

"EA Interpretation" 是 EA 实际渲染的内容,我也希望在我的渲染器中看到它。

问题

I am using in t_diagramlinks.Geometry the csv-Value EDGE - but where do i find this for the end node?

您需要使用 Euclide。 SX,SY/EX,EY 是起始元素和结束元素之间最短中心连接的相对位移。

For which purpose are the attributes Start_Edge an End_Edge in the table "t_connector" when it is not diagram-dependend?

它们用于合格的属性。

编辑:详细说明您的基本问题。 t_diagramlinks.path 保存连接器的弯曲点(如果指定)。因此,为了找到连接器实际连接元素的点,您必须找到离该元素最近的弯曲点。现在在这个弯曲和元素的中间之间你将有一个 natural 附着点。相对于添加 SX-Y (/EX-Y) 以制作手动移动的渲染附着点。

以上内容有保留意见。我从来没有核实过细节,但看到这些数字就用了我的胃。我可能会详细研究以更新我的内幕书,但不能保证。

第二次编辑:现在我知道 "My Interpretation" 是您的渲染器根据您的假设生成的内容,这是(最有可能;见上文)的故事。为了呈现连接器,EA 将使用以下信息:

  • 两个连通元素的坐标系
  • 根据元素的中点坐标计算
  • 连接器的路径属性(如果不为空)
  • 离相关元素最近的弯曲点(如果不为空)
  • 连接器的偏移因子 SX-Y 和 EX-Y

从起始元素的中点开始,绘制一条虚拟线到最近的弯曲点或结束元素的中间(除非见下文)。这样你就可以在元素的矩形框架上计算虚拟附着点(即使用例有一个矩形框架)。现在你将那个点移动 SX-Y,这将(/应该?)总是沿着元素框架的边缘移动。现在您有了起始元素的虚拟附着点。

在另一边(结束元素;我的 "unless" 从上面)你会做类似的事情来计算结束的虚拟附件。我不知道 EA 这样做的真正顺序(我没有代码洞察力)。因此,如果您在两侧都有手动偏移,则计算将根据将虚拟连接绘制到另一侧的顺序给出不同的结果(因此:是否尊重另一侧的偏移)。基本上我认为你可以忽略 99.9% 的所有情况,其余的只是无关紧要的噪音。

所以现在你知道虚拟端点了,你要么直接连接它们,要么,如果给定路径,你通过弯曲点连接它们。

再说一遍:全都持保留意见。这只是从外面观察,但可能不会太远。还有一个事实是你有不同的线条样式,圆边(这里没有考虑)和贝塞尔线(甚至更多的龙之地)。

非常感谢。我选择用数学方法计算 End-Edge:

我的问题是,我无法在 link 关系上检测到目标端节点的结束边缘。此外还有 8 种不同的 link 类型,它们决定了 link 的布局。因此,正如 Thomas 提到的,我必须检测终点之前的最后一点。如果是路径,则路径的最后一个节点是端点之前的点。如果没有路径,则 startnodes 起点是终点之前的最后一个点。但是,如果定义了路径并且 link 模式已设置为 1,我可能无法处理连接路径,因为 Conn_Path 属性 包含自定义行 - 但在自定义之后用户已选择直接 link(不会被删除)。

后面的数学用的是线性函数y=m*x+b,直线用4条适合端节点边缘的直线描述。

所以你可以做如下算法:

完整的算法使用以下方法:

1.) 确定起点和终点之间的直线(直线完全水平或垂直平行于坐标系有两种特殊情况)

2.) 创建一个由四条直线(2 条垂直线/2 条水平线)组成的矩形

3.) 确定第一条直线与矩形线的交点

4.) 排除不属于矩形的点

5.) 确定矩形上距离最短的点=> 这是搜索到的结束边缘点

我用来做路由的书面 javascript 代码如下:

                //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Erzeuge eine eigene Link-Klasse für das Routing der Pfeile, die von Hand gezogen wurden
// und über spezielle Attribute in der EAP-Datei definiert werden
// Ruft die Superklasse von go.Link im Konstruktor auf
function MultiNodePathLink() {
    go.Link.call(this);
}
go.Diagram.inherit(MultiNodePathLink, go.Link); // Erben von go.Link

// ignores this.routing, this.adjusting, this.corner, this.smoothness, this.curviness
/** @override */
MultiNodePathLink.prototype.computePoints = function () {

    // Die this Referenz ist hier ist ein geerbter ein go.Link. der bei Links
    var startNode = this.fromNode;
    var startNodeX = startNode.location.M;  // X-Koordinate vom Startknoten
    var startNodeY = startNode.location.N; // Y-Koordinate vom Startknoten

    var endNode = this.toNode;
    var endNodeX = endNode.location.M;  // X-Koordinate vom Startknoten
    var endNodeY = endNode.location.N; // Y-Koordinate vom Startknoten

    var startNodeData = startNode.data; // Das sind die Daten
    var endNodeData = endNode.data; // Das sind die Daten

    // Die Link-Daten
    var linkProperties = this.data;
    //** Das Feld Style in [t_diagramlink] bestimmt die Connector-Darstellung  **/
    // http://www.capri-soft.de/blog/?p=2904
    /*
     *  1 = Direct                    Mode=1
     *  2 = Auto Routing              Mode=2
     *  3 = Custom Line               Mode=3
     *  4 = Tree Vertical             Mode=3;TREE=V
     *  5 = Tree Horizontal           Mode=3;TREE=H
     *  6 = Lateral Vertical          Mode=3;TREE=LV
     *  7 = Lateral Horizontal        Mode=3;TREE=LH
     *  8 = Orthogonal Square         Mode=3;TREE=OS
     *  9 = Orthogonal Rounded        Mode=3;TREE=OR
     */
    var styleStringArray = linkProperties.style.split(";");
    var mode = -1;
    var tree = '';
    for (var i = 0; i < styleStringArray.length; i++) {
        if (styleStringArray[i].trim().indexOf('Mode=') > -1) {
            mode = styleStringArray[i].replace('Mode=', '');
        }

        if (styleStringArray[i].trim().indexOf('TREE=') > -1) {
            tree = styleStringArray[i].replace('TREE=', '');
        }
    }



    // In der Tabelle t_diagramlinks in der Freitextspalte "Geometry" wird in einem CSV-String
    // gespeichert, wie der Link letztendlich auf dem Diagram gezogen wurde
    var geometryString = linkProperties.geometry.split(";");
    // SX and SY are relative to the centre of the start object
    var sx = geometryString[0].replace("SX=", "");
    var sy = geometryString[1].replace("SY=", "");
    // EX and EY are relative to the centre of the end object
    var ex = geometryString[2].replace("EX=", "");
    var ey = geometryString[3].replace("EY=", "");

    // SX=-67;SY=-43;EX=-12;EY=-40;EDGE=3;$LLB=;
    // LLT=;LMT=;LMB=CX=30:CY=13:OX=11:OY=-2:HDN=0:BLD=0:ITA=0:UND=0:CLR=-1:ALN=1:DIR=0:ROT=0;
    // LRT=;LRB=;IRHS=;ILHS=;

    // EDGE ranges in value from 1-4, with 1=Top, 2=Right, 3=Bottom, 4=Left (Outgoing Point of the Start Object)
    var edge = geometryString[4].replace("EDGE=", "");

    // Hier beginnt das Custom-Routing
    this.clearPoints();
    if (linkProperties.start_object_name == 'System Verification Test Reports' && linkProperties.end_object_name == 'System test specifications') {
        var test = 'irrsinn';
    }
    // Hier werden die Wege definiert für das gecustomizte Link Routing
    // Geht der Link nach oben oder unten wird die Y-Koordinate des Startknotens genutzt (Weil Orthogonales Routing)
    var startConnX = null;
    var startConnY = null;
    if (edge == 1) { // Ecke oben
        startConnX = Math.abs(startNodeX) + Math.abs((startNode.actualBounds.width / 2) + new Number(sx));
        startConnY = Math.abs(startNodeY);
    }
    else if (edge == 3) { // Ecke unten
        startConnX = Math.abs(startNodeX) + Math.abs((startNode.actualBounds.width / 2) + new Number(sx));
        startConnY = Math.abs(startNodeY) + new Number(startNode.actualBounds.height);
    }
    else if (edge == 2) { // Ecke rechts
        startConnX = Math.abs(startNodeX) + startNode.actualBounds.width;
        startConnY = Math.abs(startNodeY) + Math.abs((startNode.actualBounds.height / 2) - new Number(sy));
    }
    else if (edge == 4) { // Ecke links
        startConnX = new Number(Math.abs(startNodeX));
        startConnY = Math.round(startNodeY) + Math.round((startNode.actualBounds.height / 2) - new Number(sy));
    }
    else {
        alert('Die Edge konnte nicht entdeckt werden! Ist der Geometry String in der EAP Datei richtig?');
    }

    this.addPoint(new go.Point(Math.round(startConnX), Math.round(startConnY)));

    // Abfrage: Gibt es einen letzten Path Punkt?
    var lastPathPunkt=false;
    var lastPathPunktX, lastPathPunktY;

    if (mode != 1)
    {
        // Routing über die Zwischenwege
        if (typeof linkProperties.conn_path !== "undefined" && linkProperties.conn_path !== "") {
            var splittedArray = linkProperties.conn_path.split(";");
            if (splittedArray.length > 1) {
                // Hier ist mindestens ein Wert vorhanden da auch der erste mit Semikolon abgeschlossen wird im Path vom EA
                for (var i = 0; i < splittedArray.length - 1; i++) {
                    var einMittelPunkt = splittedArray[i];
                    var mittelPunktArray = einMittelPunkt.split(":");
                    this.addPoint(new go.Point(Math.abs(new Number(mittelPunktArray[0])), Math.abs(new Number(mittelPunktArray[1]))))
                    lastPathPunktX = Math.abs(new Number(mittelPunktArray[0]));
                    lastPathPunktY = Math.abs(new Number(mittelPunktArray[1]));
                    lastPathPunkt = true;
                }
            }
        }
    }

    // Wenn es keinen Pfad gab,muss der letzte Punkt mit dem Startknoten identisch sein
    if (lastPathPunkt == false) {
        lastPathPunktX = Math.abs(Math.round(startConnX));
        lastPathPunktY = Math.abs(Math.round(startConnY));
    }

    // End-Routing
    // Der Endpunkt in EA in Document Coordinates
    var endConnX = Math.abs(endNodeX) + Math.abs((endNode.actualBounds.width / 2) + new Number(ex));
    var endConnY = Math.abs(endNodeY) + Math.abs((endNode.actualBounds.height / 2) - new Number(ey));

    // Spezialfälle bei horizontalen und vertikalen Linien:
    if (endConnX == lastPathPunktX) {
        // Es liegt eine vertikale Gerade (z.B. von oben nach unten) vor
        this.addPoint(new go.Point(Math.round(lastPathPunktX), Math.round(lastPathPunktY)));
        this.addPoint(new go.Point(Math.round(endConnX), Math.round(endConnY)));

    } else if (endConnY == lastPathPunktY) {
        // Es liegt eine horizontale Gerade (z.B. von rechts nach links) vor
        this.addPoint(new go.Point(Math.round(lastPathPunktX), Math.round(lastPathPunktY)));
        this.addPoint(new go.Point(Math.round(endConnX), Math.round(endConnY)));
    } else {
        // Es ist keine Gerade sondern ein Gerade, die mit y=m*x+b beschrieben werden kann

        // 1.) Gerade zwischen Start- und Endpunkt ermittelnhn
        //      Ye-Ys
        //  m = -----    b=Ys-m*Xs oder b=Ye-m*Xe
        //      Xe-Xs
        var m = (endConnY - lastPathPunktY) / (endConnX - lastPathPunktX);
        var b = lastPathPunktY - m * lastPathPunktX

        // 2.) Ermittlung der horizontalen und vertikalen Geraden des Rechteckes und dem Schnittpunkten
        // Die Geraden, die das Rechteck definieren:
        var rY1 = endNodeY;
        var rY2 = endNodeY + endNode.actualBounds.height;
        var rX1 = endNodeX;
        var rX2 = endNodeX + endNode.actualBounds.width;
        // (rX1, rY1) -zu-> (rX2, rY2)

        // Horizontale Geraden:
        //     y - b
        // x = -----
        //       m


        var lengthToPoint = [];

        var sX1 = (rY1 - b) / m; // S1(sX1|rY1)
        if (sX1 >= rX1 && sX1 <= rX2) {
            // Der Schnittpunkt sX1 ist am Rechteck
            // Distanz: d=SQRT((y2-y1)^2+(x2-x1)^2)
            var dS1 = Math.sqrt(Math.pow(rY1 - lastPathPunktY, 2) + Math.pow(sX1 - lastPathPunktX, 2));

            lengthToPoint.push({
                "distanz": dS1,
                "x": sX1,
                "y": rY1
            });

        }

        var sX2 = (rY2 - b) / m; // S2(sX2|rY2)
        if (sX2 >= rX1 && sX2 <= rX2) {
            // Der Schnittpunkt sX2 ist am Rechteck
            // Distanz: d=SQRT((y2-y1)^2+(x2-x1)^2)
            var dS2 = Math.sqrt(Math.pow(rY2 - lastPathPunktY, 2) + Math.pow(sX2 - lastPathPunktX, 2));

            lengthToPoint.push({
                "distanz": dS2,
                "x": sX2,
                "y": rY2
            });
        }

        // Vertikale Geraden:
        //
        // y = m*x + b

        var sY1 = m * rX1 + b; // S3(rX1|sY1)
        if (sY1 >= rY1 && sY1 <= rY2) {
            // Der Schnittpunkt sY1 ist am Rechteck
            // Distanz: d=SQRT((y2-y1)^2+(x2-x1)^2)
            var dS3 = Math.sqrt(Math.pow(sY1 - lastPathPunktY, 2) + Math.pow(rX1 - lastPathPunktX, 2));

            lengthToPoint.push({
                "distanz": dS3,
                "x": rX1,
                "y": sY1
            });
        }

        var sY2 = m * rX2 + b; // S4(rX2|sY2)
        if (sY2 >= rY1 && sY2 <= rY2) {
            // Der Schnittpunkt sY2 ist am Rechteck
            // Distanz: d=SQRT((y2-y1)^2+(x2-x1)^2)
            var dS4 = Math.sqrt(Math.pow(sY2 - lastPathPunktY, 2) + Math.pow(rX2 - lastPathPunktX, 2));

            lengthToPoint.push({
                "distanz": dS4,
                "x": rX2,
                "y": sY2
            });
        }

        // Sortiere alle Punkte nach Distanz - der mit der kleinsten Entfernung isses
        lengthToPoint.sort(function (a, b) { return a.distanz - b.distanz });

        if (lengthToPoint.length > 0)
        {
            this.addPoint(new go.Point(Math.round(lengthToPoint[0].x), Math.round(lengthToPoint[0].y)));
        }
        else
        {
            this.addPoint(new go.Point(Math.round(lastPathPunktX), Math.round(lastPathPunktY)));
        }


    }

    return true;
};
// end MultiNodePathLink class