Clamping Branch Corners
This guide covers using NodePresentation.clamp_point() to snap imprecise coordinates to valid node connection points. This is essential when working with imported data or programmatically generated layouts.
Full Example
View the complete code: 12_clamp_branch_corners.py
Why Clamping?
Branch corners must precisely match node positions for correct rendering. In practice, coordinates often have slight errors from:
- External data imports: GIS systems, CAD exports, spreadsheets
- Coordinate transformations: Scaling, rotation, projection changes
- Manual entry: Typos, rounding differences
- Algorithmic generation: Layout algorithms with floating-point precision
The clamp_point() method snaps coordinates to the nearest valid connection point on a node.
Basic Usage
from pyptp import NetworkLV, configure_logging
from pyptp.elements.lv.node import NodeLV
from pyptp.elements.lv.link import LinkLV
from pyptp.elements.lv.presentations import NodePresentation, BranchPresentation
from pyptp.elements.lv.sheet import SheetLV
configure_logging(level="INFO")
network = NetworkLV()
sheet = SheetLV(SheetLV.General(name="Clamp Example"))
sheet.register(network)
sheet_guid = sheet.general.guid
# Create nodes at precise positions
substation = NodeLV(
NodeLV.General(name="Substation"),
presentations=[NodePresentation(sheet=sheet_guid, x=100, y=100)],
)
substation.register(network)
load = NodeLV(
NodeLV.General(name="Load"),
presentations=[NodePresentation(sheet=sheet_guid, x=300, y=100)],
)
load.register(network)The Problem: Imprecise Coordinates
Imagine coordinates from an external source with slight errors:
# These should connect at (100, 100) and (300, 100)
# but are a few pixels off
imported_first_corners = [(105, 98), (200, 100)]
imported_second_corners = [(295, 102)]Without clamping, these misalignments cause validation warnings and visual glitches.
The Solution: clamp_point()
# Get the node presentations
substation_pres = substation.presentations[0]
load_pres = load.presentations[0]
# Clamp the first point of each corner list
first_corners = [
substation_pres.clamp_point(imported_first_corners[0]), # Snaps (105, 98) → (100, 100)
*imported_first_corners[1:], # Keep remaining points as-is
]
second_corners = [
load_pres.clamp_point(imported_second_corners[0]), # Snaps (295, 102) → (300, 100)
]Creating the Branch
feeder = LinkLV(
LinkLV.General(
name="Feeder",
node1=substation.general.guid,
node2=load.general.guid,
),
presentations=[
BranchPresentation(
sheet=sheet_guid,
first_corners=first_corners,
second_corners=second_corners,
)
],
)
feeder.register(network)How clamp_point() Works
For standard point symbols, clamp_point() simply returns the node's center position:
# Node at (100, 100) with default point symbol
node_pres.clamp_point((105, 98)) # Returns (100, 100)
node_pres.clamp_point((95, 103)) # Returns (100, 100)
node_pres.clamp_point((100, 100)) # Returns (100, 100)For line symbols (busbars), it finds the nearest point on the line segment. See Line Symbol Clamping for details.
Batch Processing
When importing many branches, create a helper function:
def create_clamped_branch(network, node1, node2, raw_first, raw_second, sheet_guid):
"""Create branch with clamped corner coordinates."""
# Find presentations on the target sheet
pres1 = next(p for p in node1.presentations if p.sheet == sheet_guid)
pres2 = next(p for p in node2.presentations if p.sheet == sheet_guid)
# Clamp first point of each list
first_corners = [pres1.clamp_point(raw_first[0]), *raw_first[1:]]
second_corners = [pres2.clamp_point(raw_second[0]), *raw_second[1:]]
branch = LinkLV(
LinkLV.General(node1=node1.general.guid, node2=node2.general.guid),
presentations=[
BranchPresentation(
sheet=sheet_guid,
first_corners=first_corners,
second_corners=second_corners,
)
],
)
branch.register(network)
return branch
# Usage
for row in imported_data:
create_clamped_branch(
network,
nodes[row.from_id],
nodes[row.to_id],
row.path_from_source,
row.path_from_target,
sheet_guid,
)When to Clamp
| Scenario | Clamp? | Reason |
|---|---|---|
| Manual creation | No | You control the coordinates |
| External imports | Yes | Source may have precision issues |
| Layout algorithms | Maybe | Depends on algorithm precision |
| Copy/paste operations | No | Coordinates should match |
| Coordinate transforms | Yes | Floating-point accumulation |
Complete Example
"""Clamp branch corners to node connection points.
Use NodePresentation.clamp_point() to snap imprecise coordinates to valid
connection points. This is useful when importing data with slightly off
coordinates or programmatically generating layouts.
"""
from pyptp import NetworkLV, configure_logging
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="Clamp Example"))
sheet.register(network)
sheet_guid = sheet.general.guid
# Two nodes we want to connect
substation = NodeLV(
NodeLV.General(name="Substation"),
presentations=[NodePresentation(sheet=sheet_guid, x=100, y=100)],
)
substation.register(network)
load = NodeLV(
NodeLV.General(name="Load"),
presentations=[NodePresentation(sheet=sheet_guid, x=300, y=100)],
)
load.register(network)
# Coordinates from external source with slight errors
imported_first_corners = [(105, 98), (200, 100)]
imported_second_corners = [(295, 102)]
# Clamp the first point of each corner list
substation_pres = substation.presentations[0]
load_pres = load.presentations[0]
first_corners = [
substation_pres.clamp_point(imported_first_corners[0]),
*imported_first_corners[1:],
]
second_corners = [
load_pres.clamp_point(imported_second_corners[0]),
]
feeder = LinkLV(
LinkLV.General(name="Feeder", node1=substation.general.guid, node2=load.general.guid),
presentations=[
BranchPresentation(
sheet=sheet_guid,
first_corners=first_corners,
second_corners=second_corners,
)
],
)
feeder.register(network)
network.save("clamp_corners_example.gnf")
logger.info("Saved clamp_corners_example.gnf")