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

feat(editors/SingleLineDiagram): allow updating X/Y coordinates in SLD for Busbar/ConductingEquipment/PowerTransformer #455

Merged
merged 8 commits into from
Jan 10, 2022
73 changes: 47 additions & 26 deletions src/editors/SingleLineDiagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ import {
getPathNameAttribute,
getNameAttribute,
getDescriptionAttribute,
getCommonParentElement,
} from './singlelinediagram/foundation.js';
import {isSCLNamespace} from "../schemas.js";
import { wizards } from '../wizards/wizard-library.js';
import { wizards } from './singlelinediagram/wizards/wizard-library.js';
import {SingleSelectedEvent} from "@material/mwc-list/mwc-list-foundation";
import {translate} from "lit-translate";

Expand All @@ -64,7 +65,7 @@ export default class SingleLineDiagramPlugin extends LitElement {
// Container for giving the panzoom to.
@query('#panzoom') panzoomContainer!: HTMLElement;
// The main canvas to draw everything on.
@query('#svg') svg!: SVGElement;
@query('#svg') svg!: SVGGraphicsElement;

private get substations() : Element[] {
return Array.from(this.doc.querySelectorAll(':root > Substation'))
Expand Down Expand Up @@ -165,7 +166,9 @@ export default class SingleLineDiagramPlugin extends LitElement {
* @param powerTransformerElement - The PowerTransformer to draw.
*/
private drawPowerTransformer(parentGroup: SVGElement, powerTransformerElement: Element): void {
const powerTransformerGroup = createPowerTransformerElement(powerTransformerElement);
const powerTransformerGroup = createPowerTransformerElement(powerTransformerElement,
(event: Event) => this.openEditWizard(event, powerTransformerElement!)
);
parentGroup.appendChild(powerTransformerGroup);
}

Expand All @@ -191,11 +194,11 @@ export default class SingleLineDiagramPlugin extends LitElement {
this.getVoltageLevels(substationElement)
.forEach(voltageLevelElement => {
this.getBusBars(voltageLevelElement).forEach( busbarElement => {
this.drawBusBarConnections(substationElement, substationGroup, busbarElement);
this.drawBusBarConnections(substationElement, this.svg, busbarElement);
});

this.getBays(voltageLevelElement).forEach( bayElement => {
this.drawBayConnections(substationElement, substationGroup, bayElement);
this.drawBayConnections(substationElement, this.svg, bayElement);
});
});
}
Expand Down Expand Up @@ -232,8 +235,8 @@ export default class SingleLineDiagramPlugin extends LitElement {
terminal => terminal.getAttribute('cNodeName') !== 'grounded'
).length !== 0)
.forEach(conductingEquipmentElement => {
const conductingEquipmentGroup = createConductingEquipmentElement(conductingEquipmentElement, () =>
this.openEditWizard(conductingEquipmentElement!)
const conductingEquipmentGroup = createConductingEquipmentElement(conductingEquipmentElement,
(event: Event) => this.openEditWizard(event, conductingEquipmentElement!)
);
bayGroup.appendChild(conductingEquipmentGroup);
});
Expand All @@ -248,8 +251,8 @@ export default class SingleLineDiagramPlugin extends LitElement {
this.getConnectivityNode(bayElement)
.filter(cNode => getConnectedTerminals(cNode).length > 0)
.forEach(cNode => {
const cNodegroup = createConnectivityNodeElement(cNode, () =>
this.openEditWizard(cNode)
const cNodegroup = createConnectivityNodeElement(cNode,
(event: Event) => this.openEditWizard(event, cNode)
);

bayGroup.appendChild(cNodegroup);
Expand All @@ -267,11 +270,12 @@ export default class SingleLineDiagramPlugin extends LitElement {
this.getConnectivityNode(bayElement)
.forEach(cNode => {
this.findEquipment(rootElement, getPathNameAttribute(cNode))
.forEach(element => {
const sides = getDirections(element, cNode);
.forEach(equipmentElement => {
const commonParentElement = getCommonParentElement(cNode, equipmentElement, bayElement);
const sides = getDirections(equipmentElement, cNode);

const elementsTerminalPosition = getAbsolutePositionTerminal(
element,
equipmentElement,
sides.startDirection
);

Expand All @@ -281,7 +285,7 @@ export default class SingleLineDiagramPlugin extends LitElement {
);

rootGroup
.querySelectorAll(`g[id="${identity(bayElement)}"]`)
.querySelectorAll(`g[id="${identity(commonParentElement)}"]`)
.forEach(eq =>
drawCNodeConnections(
cNodePosition,
Expand All @@ -290,18 +294,18 @@ export default class SingleLineDiagramPlugin extends LitElement {
)
);

const terminalElement = element.querySelector(
const terminalElement = equipmentElement.querySelector(
`Terminal[connectivityNode="${cNode.getAttribute('pathName')}"]`
);

const terminal = createTerminalElement(
terminalElement!,
sides.startDirection,
() => this.openEditWizard(terminalElement!)
(event: Event) => this.openEditWizard(event, terminalElement!)
);

rootGroup
.querySelectorAll(`g[id="${identity(element)}"]`)
.querySelectorAll(`g[id="${identity(equipmentElement)}"]`)
.forEach(eq => eq.appendChild(terminal));
});
});
Expand All @@ -324,7 +328,8 @@ export default class SingleLineDiagramPlugin extends LitElement {
* @param busbarElement - The Busbar Element to draw.
*/
private drawBusBar(parentElement: Element, parentGroup: SVGElement, busbarElement: Element): void {
const busBarGroup = createBusBarElement(busbarElement, getBusBarLength(parentElement));
const busBarGroup = createBusBarElement(busbarElement, getBusBarLength(parentElement),
(event: Event) => this.openEditWizard(event, busbarElement));
parentGroup.appendChild(busBarGroup);
}

Expand All @@ -340,6 +345,7 @@ export default class SingleLineDiagramPlugin extends LitElement {

this.findEquipment(rootElement, pathName)
.forEach(element => {
const parentElement = element.parentElement;
const elementPosition = getAbsolutePosition(element);

const elementsTerminalSide =
Expand All @@ -360,7 +366,7 @@ export default class SingleLineDiagramPlugin extends LitElement {
);

rootGroup
.querySelectorAll(`g[id="${identity(busbarElement)}"]`)
.querySelectorAll(`g[id="${identity(parentElement)}"]`)
.forEach(eq =>
drawBusBarRoute(
busbarTerminalPosition,
Expand All @@ -372,7 +378,7 @@ export default class SingleLineDiagramPlugin extends LitElement {
const terminal = createTerminalElement(
terminalElement!,
elementsTerminalSide,
() => this.openEditWizard(terminalElement!)
(event: Event) => this.openEditWizard(event, terminalElement!)
);

rootGroup
Expand Down Expand Up @@ -403,14 +409,29 @@ export default class SingleLineDiagramPlugin extends LitElement {
* Open an Edit wizard for an element.
* @param element - The element to show the wizard for.
*/
openEditWizard(element: Element): void {
openEditWizard(event: Event, element: Element): void {
const wizard = wizards[<SCLTag>element.tagName].edit(element);
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
if (wizard) {
this.dispatchEvent(newWizardEvent(wizard));
event.stopPropagation();
}
}

firstUpdated(): void {
panzoom(this.panzoomContainer);
this.drawSubstationElements();

// Set the new size of the SVG.
const bbox = this.svg.getBBox();
this.svg.setAttribute("viewBox", (bbox.x-10)+" "+(bbox.y-10)+" "+(bbox.width+20)+" "+(bbox.height+20));
this.svg.setAttribute("width", (bbox.width+20) + "px");
this.svg.setAttribute("height",(bbox.height+20) + "px");

panzoom(this.panzoomContainer, {
zoomSpeed: 0.2,
maxZoom: 1.5,
minZoom: 0.2,
initialZoom: 0.5,
});
}

onSelect(event: SingleSelectedEvent): void {
Expand Down Expand Up @@ -474,8 +495,6 @@ export default class SingleLineDiagramPlugin extends LitElement {
<svg
xmlns="http://www.w3.org/2000/svg"
id="svg"
width="5000"
height="5000"
></svg>
</div>
</div>`;
Expand Down Expand Up @@ -516,9 +535,11 @@ export default class SingleLineDiagramPlugin extends LitElement {
pointer-events: bounding-box;
}

g[type='Busbar']:hover,
g[type='ConductingEquipment']:hover,
g[type='ConnectivityNode']:hover,
g[type='Terminal']:hover,
g[type='ConductingEquipment']:hover {
g[type='PowerTransformer']:hover,
g[type='Terminal']:hover {
outline: 2px dashed var(--mdc-theme-primary);
transition: transform 200ms linear, box-shadow 250ms linear;
}
Expand Down
31 changes: 25 additions & 6 deletions src/editors/singlelinediagram/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export interface Point {
y: number;
}

export const SCL_COORDINATES_NAMESPACE = 'http://www.iec.ch/61850/2003/SCLcoordinates';

/** Scope factor: the ConnectivityNode allocation algorithm works better with a scale factor which is bigger than 1. */
const COORDINATES_SCALE_FACTOR = 2;

Expand Down Expand Up @@ -46,11 +48,11 @@ export function getPathNameAttribute(element: Element): string | undefined {
*/
export function getRelativeCoordinates(element: Element): Point {
const x = element.getAttributeNS(
'http://www.iec.ch/61850/2003/SCLcoordinates',
SCL_COORDINATES_NAMESPACE,
'x'
);
const y = element.getAttributeNS(
'http://www.iec.ch/61850/2003/SCLcoordinates',
SCL_COORDINATES_NAMESPACE,
'y'
);

Expand Down Expand Up @@ -117,8 +119,7 @@ export function getConnectedTerminals(element: Element): Element[] {
* - Get all elements that are connected to this Connectivity Node.
* - Extract the SCL x and y coordinates of these Connectivity Nodes and add them up.
* - Divide the final x and y numbers by the number of connected elements. This way, you get an so-called average.
* @param doc - The full SCL document to scan for connected elements.
* @param cNodePathName - The pathName of the Connectivity Node to calculate the SCL x and y coordinates.
* @param cNodeElement - The Connectivity Node to calculate the X and Y Coordinates for.
* @returns The calculated SCL x and y coordinates for this Connectivity Node.
*/
export function calculateConnectivityNodeCoordinates(
Expand All @@ -131,6 +132,7 @@ export function calculateConnectivityNodeCoordinates(
const pathName = getPathNameAttribute(cNodeElement);

let nrOfConnections = 0;
let nrOfXConnections = 0;
let totalX = 0;
let totalY = 0;

Expand All @@ -147,15 +149,32 @@ export function calculateConnectivityNodeCoordinates(

const { x, y } = getAbsoluteCoordinates(equipment);

totalX += x!;
// Only if the Element is in the same bay, we will use that X-value to calculate the location
// of the Connectivity Node. This will cause the Connectivity Node to stay with the boundaries
// of the Bay and not causing al kind of overlays between bays.
if (equipment.parentElement === cNodeElement.parentElement) {
nrOfXConnections++;
totalX += x!;
}
totalY += y!;
});

if (nrOfConnections === 0) return { x: 0, y: 0 };
if (nrOfConnections === 1) return { x: totalX + 1, y: totalY + 1 };

return {
x: Math.round(totalX / nrOfConnections),
x: Math.round(totalX / nrOfXConnections),
y: Math.round(totalY / nrOfConnections),
};
}

export function getCommonParentElement(leftElement: Element, rightElement: Element, defaultParent: Element | null): Element | null {
let leftParentElement = leftElement.parentElement
while (leftParentElement) {
if (leftParentElement.contains(rightElement)) {
return leftParentElement;
}
leftParentElement = leftParentElement.parentElement;
}
return defaultParent;
}
27 changes: 17 additions & 10 deletions src/editors/singlelinediagram/sld-drawing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export function getAbsolutePositionBusBar(busbar: Element): Point {
* @param connectivityNode - The SCL element ConnectivityNode to get the position for.
* @returns A point containing the full x/y position in px.
*/
export function getAbsolutePositionConnectivityNode(element: Element): Point {
const absoluteCoordinates = calculateConnectivityNodeCoordinates(element);
export function getAbsolutePositionConnectivityNode(connectivityNode: Element): Point {
const absoluteCoordinates = calculateConnectivityNodeCoordinates(connectivityNode);
return {
x:
absoluteCoordinates.x! * SVG_GRID_SIZE + (SVG_GRID_SIZE - CNODE_SIZE) / 2,
Expand Down Expand Up @@ -142,7 +142,7 @@ function absoluteOffsetTerminal(
/**
* Get the absolute position in py for a equipments Terminal (based on the TERMINAL_OFFSET).
* @param equipment - The SCL elements ConductingEquipment or PowerTransformer.
* @param side - On which side does the terminal needs to be placed relative to the given point.
* @param direction - On which side does the terminal needs to be placed relative to the given point.
*/
export function getAbsolutePositionTerminal(
equipment: Element,
Expand Down Expand Up @@ -219,7 +219,7 @@ export function createVoltageLevelElement(voltageLevel: Element): SVGElement {

/**
* Create a Bay <g> element.
* @param voltageLevel - The Bay from the SCL document to use.
* @param bay - The Bay from the SCL document to use.
* @returns A Bay <g> element.
*/
export function createBayElement(bay: Element): SVGElement {
Expand Down Expand Up @@ -265,7 +265,7 @@ export function createTextElement(
export function createTerminalElement(
terminal: Element,
sideToDraw: Direction,
clickAction?: () => void
clickAction?: (event: Event) => void
): SVGElement {
const groupElement = createGroupElement(terminal);

Expand Down Expand Up @@ -303,9 +303,12 @@ export function createTerminalElement(
*/
export function createBusBarElement(
busBarElement: Element,
busbarLength: number
busbarLength: number,
clickAction?: (event: Event) => void
): SVGElement {
const groupElement = createGroupElement(busBarElement);
// Overwrite the type to make a distinction between Bays and Busbars.
groupElement.setAttribute('type', 'Busbar');

const busBarName = getNameAttribute(busBarElement)!;
const absolutePosition = getAbsolutePositionBusBar(busBarElement);
Expand All @@ -330,6 +333,8 @@ export function createBusBarElement(
);
groupElement.appendChild(text);

if (clickAction) groupElement.addEventListener('click', clickAction);

return groupElement;
}

Expand All @@ -340,7 +345,7 @@ export function createBusBarElement(
*/
export function createConductingEquipmentElement(
equipmentElement: Element,
clickAction?: () => void
clickAction?: (event: Event) => void
): SVGElement {
const groupElement = createGroupElement(equipmentElement);

Expand Down Expand Up @@ -377,7 +382,8 @@ export function createConductingEquipmentElement(
* @returns The Power Transformer SVG element.
*/
export function createPowerTransformerElement(
powerTransformerElement: Element
powerTransformerElement: Element,
clickAction?: (event: Event) => void
): SVGElement {
const groupElement = createGroupElement(powerTransformerElement);

Expand All @@ -403,19 +409,20 @@ export function createPowerTransformerElement(
);
groupElement.appendChild(text);

if (clickAction) groupElement.addEventListener('click', clickAction);

return groupElement;
}

/**
* Create a Connectivity Node element.
* @param cNodeElement - The SCL element ConnectivityNode
* @param position - The SCL position of the Connectivity Node.
* @param clickAction - The action to execute when the terminal is being clicked.
* @returns The Connectivity Node SVG element.
*/
export function createConnectivityNodeElement(
cNodeElement: Element,
clickAction?: () => void
clickAction?: (event: Event) => void
): SVGElement {
const groupElement = createGroupElement(cNodeElement);

Expand Down
Loading