Skip to content

Line Symbol Clamping

This guide covers clamping branch corners to line-type node symbols (busbars). Unlike point symbols, line symbols have extent: connections can attach anywhere along the line segment.

Full Example

View the complete code: 13_clamp_line_symbols.py

Line Symbol Types

PyPtP supports two line-type node symbols:

SymbolEnum ValueOrientation
Vertical busbarNodePresentationSymbol.VERTICAL_LINEExtends up and down
Horizontal busbarNodePresentationSymbol.HORIZONTAL_LINEExtends left and right

Line Extent Calculation

Line symbols extend from their center position based on the size property:

extent = size × 10 pixels in each direction

Vertical Line Example

A vertical busbar at (100, 300) with size=3:

Vertical Busbar Extent (size=3)
150200100150ABC
extent = size × 10 pixels in each direction
ATop: y - 30(100, 270)
BCenter: (100, 300)
CBottom: y + 30(100, 330)

Horizontal Line Example

A horizontal busbar at (100, 500) with size=2:

Horizontal Busbar Extent (size=2)
10015020050100ABC
ALeft: x - 20(80, 500)
BCenter: (100, 500)
CRight: x + 20(120, 500)

Clamping Behavior

For line symbols, clamp_point() finds the nearest point on the line segment, not just the center:

python
from pyptp.elements.enums import NodePresentationSymbol

# Vertical busbar at (100, 300) with size=3
# Line extends from (100, 270) to (100, 330)
busbar = NodeLV(
    NodeLV.General(name="VerticalBusbar"),
    presentations=[
        NodePresentation(
            sheet=sheet_guid,
            x=100,
            y=300,
            symbol=NodePresentationSymbol.VERTICAL_LINE,
            size=3,
        )
    ],
)

busbar_pres = busbar.presentations[0]

# Point below the line → clamps to bottom edge
busbar_pres.clamp_point((100, 340))  # Returns (100, 330)

# Point above the line → clamps to top edge
busbar_pres.clamp_point((100, 250))  # Returns (100, 270)

# Point on the line → returns that point
busbar_pres.clamp_point((100, 290))  # Returns (100, 290)

# Point to the side → clamps to nearest point on line
busbar_pres.clamp_point((120, 310))  # Returns (100, 310)

Vertical Busbar Connection

python
from pyptp import NetworkLV, configure_logging
from pyptp.elements.enums import NodePresentationSymbol
from pyptp.elements.lv.link import LinkLV
from pyptp.elements.lv.node import NodeLV
from pyptp.elements.lv.presentations import BranchPresentation, NodePresentation
from pyptp.elements.lv.sheet import SheetLV

configure_logging(level="INFO")

network = NetworkLV()

sheet = SheetLV(SheetLV.General(name="Line Symbol Examples"))
sheet.register(network)
sheet_guid = sheet.general.guid

# Vertical busbar at (100, 300) with size=3
busbar = NodeLV(
    NodeLV.General(name="VerticalBusbar"),
    presentations=[
        NodePresentation(
            sheet=sheet_guid,
            x=100,
            y=300,
            symbol=NodePresentationSymbol.VERTICAL_LINE,
            size=3,
        )
    ],
)
busbar.register(network)

# Load node
load = NodeLV(
    NodeLV.General(name="Load"),
    presentations=[NodePresentation(sheet=sheet_guid, x=300, y=350)],
)
load.register(network)

# Get presentations
busbar_pres = busbar.presentations[0]
load_pres = load.presentations[0]

# Connect - point (100, 340) is below busbar, clamps to (100, 330)
feeder = LinkLV(
    LinkLV.General(name="Feeder", node1=busbar.general.guid, node2=load.general.guid),
    presentations=[
        BranchPresentation(
            sheet=sheet_guid,
            first_corners=[
                busbar_pres.clamp_point((100, 340)),  # → (100, 330)
                (150, 340),
                (200, 350),
            ],
            second_corners=[load_pres.clamp_point((300, 350))],
        )
    ],
)
feeder.register(network)

Horizontal Busbar Connection

python
# Horizontal busbar at (100, 500) with size=2
# Line extends from (80, 500) to (120, 500)
busbar = NodeLV(
    NodeLV.General(name="HorizontalBusbar"),
    presentations=[
        NodePresentation(
            sheet=sheet_guid,
            x=100,
            y=500,
            symbol=NodePresentationSymbol.HORIZONTAL_LINE,
            size=2,
        )
    ],
)
busbar.register(network)

load = NodeLV(
    NodeLV.General(name="Load"),
    presentations=[NodePresentation(sheet=sheet_guid, x=300, y=500)],
)
load.register(network)

busbar_pres = busbar.presentations[0]
load_pres = load.presentations[0]

# Point (150, 500) is beyond the line, clamps to right edge (120, 500)
feeder = LinkLV(
    LinkLV.General(name="Feeder", node1=busbar.general.guid, node2=load.general.guid),
    presentations=[
        BranchPresentation(
            sheet=sheet_guid,
            first_corners=[
                busbar_pres.clamp_point((150, 500)),  # → (120, 500)
                (200, 500),
            ],
            second_corners=[load_pres.clamp_point((300, 500))],
        )
    ],
)
feeder.register(network)

contains_point() Method

Check if a point is within the node's clickable area:

python
# Vertical busbar (100, 270) to (100, 330)
busbar_pres.contains_point((100, 300))  # True - on line
busbar_pres.contains_point((100, 340))  # False - below line
busbar_pres.contains_point((105, 300))  # True - within tolerance

This is useful for hit-testing and validation.

Size Reference Table

SizeExtentTotal Length
1±10 px20 px
2±20 px40 px
3±30 px60 px
4±40 px80 px
5±50 px100 px

Complete Example

python
"""Clamp branch corners to line symbol nodes.

Line symbols (VERTICAL_LINE, HORIZONTAL_LINE) have extent based on their size.
clamp_point() finds the nearest point on the line segment, not just the center.
"""

from pyptp import NetworkLV, configure_logging
from pyptp.elements.enums import NodePresentationSymbol
from pyptp.elements.lv.link import LinkLV
from pyptp.elements.lv.node import NodeLV
from pyptp.elements.lv.presentations import BranchPresentation, NodePresentation
from pyptp.elements.lv.sheet import SheetLV
from pyptp.ptp_log import logger

configure_logging(level="INFO")

network = NetworkLV()

sheet = SheetLV(SheetLV.General(name="Line Symbol Examples"))
sheet.register(network)
sheet_guid = sheet.general.guid

# Example 1: Vertical line node
busbar = NodeLV(
    NodeLV.General(name="VerticalBusbar"),
    presentations=[
        NodePresentation(
            sheet=sheet_guid,
            x=100,
            y=300,
            symbol=NodePresentationSymbol.VERTICAL_LINE,
            size=3,
        )
    ],
)
busbar.register(network)

load = NodeLV(
    NodeLV.General(name="VerticalLoad"),
    presentations=[NodePresentation(sheet=sheet_guid, x=300, y=350)],
)
load.register(network)

busbar_pres = busbar.presentations[0]
load_pres = load.presentations[0]

feeder = LinkLV(
    LinkLV.General(name="VerticalFeeder", node1=busbar.general.guid, node2=load.general.guid),
    presentations=[
        BranchPresentation(
            sheet=sheet_guid,
            first_corners=[busbar_pres.clamp_point((100, 340)), (150, 340), (200, 350)],
            second_corners=[load_pres.clamp_point((300, 350))],
        )
    ],
)
feeder.register(network)

# Example 2: Horizontal line node
busbar2 = NodeLV(
    NodeLV.General(name="HorizontalBusbar"),
    presentations=[
        NodePresentation(
            sheet=sheet_guid,
            x=100,
            y=500,
            symbol=NodePresentationSymbol.HORIZONTAL_LINE,
            size=2,
        )
    ],
)
busbar2.register(network)

load2 = NodeLV(
    NodeLV.General(name="HorizontalLoad"),
    presentations=[NodePresentation(sheet=sheet_guid, x=300, y=500)],
)
load2.register(network)

busbar2_pres = busbar2.presentations[0]
load2_pres = load2.presentations[0]

feeder2 = LinkLV(
    LinkLV.General(name="HorizontalFeeder", node1=busbar2.general.guid, node2=load2.general.guid),
    presentations=[
        BranchPresentation(
            sheet=sheet_guid,
            first_corners=[busbar2_pres.clamp_point((150, 500)), (200, 500)],
            second_corners=[load2_pres.clamp_point((300, 500))],
        )
    ],
)
feeder2.register(network)

network.save("clamp_line_symbols_example.gnf")
logger.info("Saved clamp_line_symbols_example.gnf")