Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fix for not connected routes in SLD Editor #391

Merged
merged 7 commits into from
Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 82 additions & 43 deletions src/editors/SingleLineDiagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import panzoom from 'panzoom';
import { Side } from '../../public/js/ortho-connector.js';
import {
getAbsolutePosition,
SVG_GRID_SIZE,
drawRoute,
drawRouteBetweenElements,
DEFAULT_ELEMENT_SIZE,
createTerminalElement,
createBusBarElement,
createVoltageLevelElement,
createBayElement,
createConductingEquipmentElement,
createConnectivityNodeElement,
getElementDimensions,
getAbsolutePositionConnectivityNode,
getBusBarLength,
createPowerTransformerElement,
Expand All @@ -31,6 +31,7 @@ import {
isBusBar,
getConnectedTerminals,
getPathNameAttribute,
getNameAttribute,
} from './singlelinediagram/foundation.js';

/**
Expand Down Expand Up @@ -174,47 +175,69 @@ export default class SingleLineDiagramPlugin extends LitElement {
Array.from(bay.querySelectorAll('ConnectivityNode'))
.filter(cNode => cNode.getAttribute('name') !== 'grounded')
.forEach(cNode => {
const cnPosition = getAbsolutePositionConnectivityNode(cNode);

Array.from(
this.doc.querySelectorAll('ConductingEquipment, PowerTransformer')
)
.filter(element =>
element.querySelector(
// For each Connectivity Node, the routes must be drawn.
const cNodeAbsolutePosition = getAbsolutePositionConnectivityNode(cNode);
const cNodeDimensions = getElementDimensions(getNameAttribute(bay)!, getNameAttribute(cNode)!, this.svg);

// Get all the connected Conducting Equipments to this specific Connectivity Node..
Array.from(this.doc.querySelectorAll('ConductingEquipment,PowerTransformer'))
.filter(cEquipment =>
cEquipment.querySelector(
`Terminal[connectivityNode="${cNode.getAttribute('pathName')}"]`
)
)
.forEach(element => {
const elementPosition = getAbsolutePosition(element);
const terminalElement = element.querySelector(
`Terminal[connectivityNode="${cNode.getAttribute('pathName')}"]`
);

.forEach(cEquipment => {
const cEquipmentAbsolutePosition = getAbsolutePosition(cEquipment);
let sideToDrawTerminalOn: Side;

if (elementPosition.y! > cnPosition.y!) {
const sidesOfRoutes = drawRoute(
cnPosition,
elementPosition,
/**
* TODO: ConductingEquipment dimensions are just the defaults,
* retrieving dimensions the same way as for ConnectivityNode doesn't work.
*
* Instead, just insert DEFAULT_ELEMENT_SIZE for height and width for ConductingEquipment.
*/
if (cEquipmentAbsolutePosition.y! > cNodeAbsolutePosition.y!) {
const sidesOfRoutes = drawRouteBetweenElements(
cNodeAbsolutePosition,
cEquipmentAbsolutePosition,
cNodeDimensions,
{
height: DEFAULT_ELEMENT_SIZE,
width: DEFAULT_ELEMENT_SIZE
},
this.svg
);
sideToDrawTerminalOn = sidesOfRoutes.pointBSide;
} else {
const sidesOfRoutes = drawRoute(
elementPosition,
cnPosition,
const sidesOfRoutes = drawRouteBetweenElements(
cEquipmentAbsolutePosition,
cNodeAbsolutePosition,
{
height: DEFAULT_ELEMENT_SIZE,
width: DEFAULT_ELEMENT_SIZE
},
cNodeDimensions,
this.svg
);
sideToDrawTerminalOn = sidesOfRoutes.pointASide;
}

/**
* Add the terminal belonging to the connected Conducting Equipment.
*/
const terminalElement = cEquipment.querySelector(
`Terminal[connectivityNode="${cNode.getAttribute('pathName')}"]`
);

// Create the Terminal SVG element and add it to the Conducting Equipment group.
const terminal = createTerminalElement(
elementPosition,
cEquipmentAbsolutePosition,
sideToDrawTerminalOn,
terminalElement!
);

this.svg
.querySelectorAll(`g[id="${identity(element)}"]`)
.querySelectorAll(`g[id="${identity(cEquipment)}"]`)
.forEach(eq => eq.appendChild(terminal));
});
});
Expand All @@ -227,46 +250,62 @@ export default class SingleLineDiagramPlugin extends LitElement {
const busBarPosition = getAbsolutePosition(busBar);

Array.from(this.doc.querySelectorAll('ConductingEquipment'))
.filter(element =>
element.querySelector(`Terminal[connectivityNode="${pathName}"]`)
.filter(cEquipment =>
cEquipment.querySelector(`Terminal[connectivityNode="${pathName}"]`)
)
.forEach(element => {
const eqPosition = getAbsolutePosition(element);
const terminalElement = element.querySelector(
`Terminal[connectivityNode="${pathName}"]`
);
.forEach(cEquipment => {
const cEquipmentAbsolutePosition = getAbsolutePosition(cEquipment);

let sideToDrawTerminalOn: Side;

// Height of busbar shape should be 1, because it's smaller.
const customShape = { width: DEFAULT_ELEMENT_SIZE, height: 1 };

// The X coordinate of
if (busBarPosition.y! > eqPosition.y!) {
const sidesOfRoutes = drawRoute(
eqPosition,
{ x: eqPosition.x, y: busBarPosition.y },
if (busBarPosition.y! > cEquipmentAbsolutePosition.y!) {
const sidesOfRoutes = drawRouteBetweenElements(
cEquipmentAbsolutePosition,
// The x of the busbar position should equal the x of the Conducting Equipment,
// so it will be a straight line.
{
x: cEquipmentAbsolutePosition.x!,
// The drawRoute function draws the routes to the middle of the elements,
// for the Bus Bar the height is 1, so we extract the value that is added in the function.
y: busBarPosition.y! - ((DEFAULT_ELEMENT_SIZE - customShape.height) / 2)
},
customShape,
customShape,
this.svg,
customShape
);
sideToDrawTerminalOn = sidesOfRoutes.pointASide;
} else {
const sidesOfRoutes = drawRoute(
{ x: eqPosition.x, y: busBarPosition.y },
eqPosition,
const sidesOfRoutes = drawRouteBetweenElements(
{
x: cEquipmentAbsolutePosition.x!,
y: busBarPosition.y! - ((DEFAULT_ELEMENT_SIZE - customShape.height) / 2)
},
cEquipmentAbsolutePosition,
customShape,
customShape,
this.svg,
customShape
);
sideToDrawTerminalOn = sidesOfRoutes.pointBSide;
}

/**
* Add the terminal belonging to the connected Conducting Equipment.
*/
const terminalElement = cEquipment.querySelector(
`Terminal[connectivityNode="${pathName}"]`
);

const terminal = createTerminalElement(
eqPosition,
cEquipmentAbsolutePosition,
sideToDrawTerminalOn,
terminalElement!
);

this.svg
.querySelectorAll(` g[id="${identity(element)}"]`)
.querySelectorAll(` g[id="${identity(cEquipment)}"]`)
.forEach(eq => eq.appendChild(terminal));
});
});
Expand Down
64 changes: 52 additions & 12 deletions src/editors/singlelinediagram/sld-drawing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,24 +350,32 @@ export function createConnectivityNodeElement(
* @param shape - A custom shape defining custom height and width of the shapes.
* @returns The sides where the routes are being drawn next to both points.
*/
export function drawRoute(
export function drawRouteBetweenElements(
pointA: Point,
pointB: Point,
pointAShape: Shape,
pointBShape: Shape,
svgToDrawOn: HTMLElement,
shape?: Shape
): PointSides {
/**
* The point on each side of the route should be in the middle of the element,
* so we have to do a little conversion of the 'left' and 'top' coordinate.
*/
const positionMiddleOfA = convertRoutePointToMiddleOfElement(pointA, pointAShape);
const positionMiddleOfB = convertRoutePointToMiddleOfElement(pointB, pointBShape);

const shapeA = {
left: pointA.x!,
top: pointA.y!,
width: shape?.width ?? DEFAULT_ELEMENT_SIZE,
height: shape?.height ?? DEFAULT_ELEMENT_SIZE,
left: positionMiddleOfA.x!,
top: positionMiddleOfA.y!,
width: pointAShape?.width,
height: pointAShape?.height,
};

const shapeB = {
left: pointB.x!,
top: pointB.y!,
width: shape?.width ?? DEFAULT_ELEMENT_SIZE,
height: shape?.height ?? DEFAULT_ELEMENT_SIZE,
left: positionMiddleOfB.x!,
top: positionMiddleOfB.y!,
width: pointBShape?.width,
height: pointBShape?.height,
};

// Get the preferred sides.
Expand Down Expand Up @@ -399,13 +407,33 @@ export function drawRoute(
line.setAttribute('d', d);
line.setAttribute('fill', 'transparent');
line.setAttribute('stroke', 'currentColor');
line.setAttribute('stroke-width', '1');
line.setAttribute('stroke-width', '1.5');

svgToDrawOn.appendChild(line);

return sides;
}

/**
* Get the dimensions of a specific element within a specific bay.
* @param bayName - The name of the bay.
* @param elementName - The name of the element.
* @param svg - The SVG to search on.
* @returns The shape (width and height) of the specific element.
*/
export function getElementDimensions(bayName: string, elementName: string, svg: HTMLElement): Shape {
let {height, width} = {height: 0, width: 0};

svg.querySelectorAll(
`g[id="${bayName}"] > g[id="${elementName}"]`
).forEach(b => {
height = b.getBoundingClientRect().height;
width = b.getBoundingClientRect().width;
});

return {height, width};
}

/**
* Small simple algorithm deciding on which side the route should be drawn
* of two ends of a route.
Expand Down Expand Up @@ -486,7 +514,19 @@ function getAbsolutePositionTerminal(
}

/**
* Calculate length of the busbar that is depending on the most far right equipment
* Convert a top left coordinate to the middle of an element.
* @param point - The top left point of the element.
* @param shape - The shape of the element.
* @returns The point of the element in the middle.
*/
function convertRoutePointToMiddleOfElement(point: Point, shape: Shape): Point {
return {
x: point.x! + ((DEFAULT_ELEMENT_SIZE - shape.width) / 2),
y: point.y! + ((DEFAULT_ELEMENT_SIZE - shape.height) / 2)
}
}

/* Calculate length of the busbar that is depending on the most far right equipment
* @param root - Either the whole SCL file or the voltage level where the bus bar resides
* @returns - the length of the bus bar
*/
Expand Down