diff --git a/lclayout/router.py b/lclayout/router.py
index ad48bbb21656bf587b3c74444462296c2eed6fd4..ca38f90b6190ae1815f7c50b484c647a8769c30c 100644
--- a/lclayout/router.py
+++ b/lclayout/router.py
@@ -462,6 +462,9 @@ class DefaultRouter():
             else:
                 assert False, 'Routing graph is not connected.'
 
+        # Set via weights based on terminal costs.
+        _set_terminal_costs(graph, shapes, terminals_by_net, tech)
+
         self._routing_graph = graph
 
         # TODO: SPLIT HERE
@@ -614,6 +617,38 @@ class DefaultRouter():
 
             # Merge the polygons on all layers.
             # _merge_all_layers(shapes)
+
+def _set_terminal_costs(
+    graph: nx.Graph,
+    shapes: Dict[str, db.Shape],
+    terminals_by_net: Dict[str, List]
+):
+    """
+    Set via weights based on terminal costs.
+    """
+    regions_by_layer = {
+        # Convert Shapes objects into db.Region for fast 'inside' checks.
+        layer: db.Region(s) for layer, s in shapes.items()
+    }
+    for net, ts in terminals_by_net:
+        for terminal_node in ts:
+            layer, (x, y) = terminal_node
+            possible_via_layers = (data['layer'] for _, _, data in via_layers.edges(layer, data=True))
+
+            for via_layer in possible_via_layers:
+                via_cost = compute_terminal_cost(
+                    via_layer,
+                    layer,
+                    (x, y),
+                    regions_by_layer[layer],
+                    tech
+                )
+
+                other_node = via_layer, (x, y) # Node on the other side of the via.
+
+                if terminal_node in graph and other_node in graph:
+                    # Scale the via cost
+                    graph[terminal_node][other_node]['weight'] *= via_cost + 1
             
 def _report_track_usage(xs: List[int], ys: List[int], routing_trees: Dict[Any, nx.Graph]):
     """
diff --git a/lclayout/routing_graph.py b/lclayout/routing_graph.py
index 819f7495fabb7a74fdd43f46945899f2d3f6420d..f6bed27e9f55a10dc875483a5582e50d88fedc35 100644
--- a/lclayout/routing_graph.py
+++ b/lclayout/routing_graph.py
@@ -405,6 +405,36 @@ def _extract_terminal_nodes_from_shape(routing_nodes: Dict[Any, Set[Tuple[int, i
     logger.debug(f"routing_terminals: {routing_terminals}")
     return routing_terminals,weights
 
+def compute_terminal_cost(
+    via_layer: str,
+    layer: str,
+    coord: Tuple[int, int],
+    layer_region: db.Region,
+    tech
+    ) -> int:
+    """
+    Compute the cost of placing a via on `via_layer` which connects to `layer` at `(x, y)`.
+    The cost is derived from the existing geometries on the layer.
+
+    TODO: Move to new module 'pin_access_analysis'
+
+    Returns an unscaled cost. 0 for vias which can be placed without extending the pin shape, 
+    1 for vias which would extend the pin shape.
+    """
+    
+    enc = tech.minimum_enclosure.get((layer, via_layer), 0)
+    via_size = tech.via_size[via_layer]
+    
+    d = enc + via_size // 2
+
+    # Test if the via would be completely enclosed in existing shapes.
+    is_enclosed = is_inside(coord, layer_region, d)
+
+    if is_enclosed:
+        # Via can be placed without extending the pin shape.
+        return 0
+    else:
+        return 1
 
 def extract_terminal_nodes_by_lvs(graph: nx.Graph,
                                   pin_shapes_by_net: Dict[str, List[List[Tuple[str, db.Polygon]]]],