mirror of https://github.com/VLSIDA/OpenRAM.git
Add A* algorithm for navigation router
This commit is contained in:
parent
909ac6ce68
commit
cd339ebbd0
|
|
@ -3,29 +3,35 @@
|
||||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from openram.base.vector import vector
|
|
||||||
from .navigation_node import navigation_node
|
from .navigation_node import navigation_node
|
||||||
|
|
||||||
|
|
||||||
class navigation_blockage:
|
class navigation_blockage:
|
||||||
""" This class represents a blockage on the navigation graph. """
|
""" This class represents a blockage on the navigation graph. """
|
||||||
|
|
||||||
def __init__(self, ll, ur):
|
def __init__(self, ll, ur, layer=0):
|
||||||
|
|
||||||
self.ll = ll
|
self.ll = ll
|
||||||
self.ur = ur
|
self.ur = ur
|
||||||
|
self.layer = layer
|
||||||
|
|
||||||
|
|
||||||
def create_corner_nodes(self):
|
@property
|
||||||
|
def rect(self):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
return self.ll, self.ur
|
||||||
|
|
||||||
|
|
||||||
|
def create_corner_nodes(self, offset):
|
||||||
""" Create nodes on all 4 corners of this blockage. """
|
""" Create nodes on all 4 corners of this blockage. """
|
||||||
|
|
||||||
corners = []
|
self.corners = []
|
||||||
corners.append(navigation_node(vector(self.ll[0], self.ll[1])))
|
self.corners.append(navigation_node([self.ll[0], self.ll[1], self.layer], offset, -1, -1))
|
||||||
corners.append(navigation_node(vector(self.ll[0], self.ur[1])))
|
self.corners.append(navigation_node([self.ll[0], self.ur[1], self.layer], offset, -1, 1))
|
||||||
corners.append(navigation_node(vector(self.ur[0], self.ll[1])))
|
self.corners.append(navigation_node([self.ur[0], self.ll[1], self.layer], offset, 1, -1))
|
||||||
corners.append(navigation_node(vector(self.ur[0], self.ur[1])))
|
self.corners.append(navigation_node([self.ur[0], self.ur[1], self.layer], offset, 1, 1))
|
||||||
corners[0].add_neighbor(corners[1])
|
self.corners[0].add_neighbor(self.corners[1])
|
||||||
corners[0].add_neighbor(corners[2])
|
self.corners[0].add_neighbor(self.corners[2])
|
||||||
corners[3].add_neighbor(corners[1])
|
self.corners[3].add_neighbor(self.corners[1])
|
||||||
corners[3].add_neighbor(corners[2])
|
self.corners[3].add_neighbor(self.corners[2])
|
||||||
return corners
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
|
import heapq
|
||||||
from openram import debug
|
from openram import debug
|
||||||
from openram.base.vector import vector
|
|
||||||
from .navigation_node import navigation_node
|
from .navigation_node import navigation_node
|
||||||
from .navigation_blockage import navigation_blockage
|
from .navigation_blockage import navigation_blockage
|
||||||
|
|
||||||
|
|
@ -12,8 +12,9 @@ from .navigation_blockage import navigation_blockage
|
||||||
class navigation_graph:
|
class navigation_graph:
|
||||||
""" This is the navigation graph created from the blockages. """
|
""" This is the navigation graph created from the blockages. """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, track_width):
|
||||||
pass
|
|
||||||
|
self.track_width = track_width
|
||||||
|
|
||||||
|
|
||||||
def is_probe_blocked(self, p1, p2):
|
def is_probe_blocked(self, p1, p2):
|
||||||
|
|
@ -43,11 +44,10 @@ class navigation_graph:
|
||||||
debug.info(0, "Creating the navigation graph for source '{0}' and target'{1}'.".format(layout_source, layout_target))
|
debug.info(0, "Creating the navigation graph for source '{0}' and target'{1}'.".format(layout_source, layout_target))
|
||||||
|
|
||||||
# Find the region to be routed and only include objects inside that region
|
# Find the region to be routed and only include objects inside that region
|
||||||
s_ll, sou_ur = layout_source.rect
|
s_ll, s_ur = layout_source.rect
|
||||||
t_ll, tar_ur = layout_target.rect
|
t_ll, t_ur = layout_target.rect
|
||||||
ll = vector(min(s_ll.x, t_ll.x), max(s_ll.y, t_ll.y))
|
region = (s_ll.min(t_ll), s_ur.min(t_ur))
|
||||||
ur = vector(max(sou_ur.x, tar_ur.x), min(sou_ur.y, tar_ur.y))
|
debug.info(0, "Routing region is ll: '{0}' ur: '{1}'".format(region[0], region[1]))
|
||||||
region = (ll, ur)
|
|
||||||
|
|
||||||
# Instantiate "navigation blockage" objects from layout blockages
|
# Instantiate "navigation blockage" objects from layout blockages
|
||||||
self.nav_blockages = []
|
self.nav_blockages = []
|
||||||
|
|
@ -65,37 +65,155 @@ class navigation_graph:
|
||||||
|
|
||||||
# Create the corner nodes
|
# Create the corner nodes
|
||||||
for blockage in self.nav_blockages:
|
for blockage in self.nav_blockages:
|
||||||
self.nodes.extend(blockage.create_corner_nodes())
|
blockage.create_corner_nodes(self.track_width / 2)
|
||||||
|
|
||||||
|
# These nodes will be connected to create the final graph
|
||||||
|
connect_objs = []
|
||||||
|
connect_objs.extend(self.nodes)
|
||||||
|
connect_objs.extend(self.nav_blockages)
|
||||||
|
|
||||||
# Create intersection nodes
|
# Create intersection nodes
|
||||||
# NOTE: Intersection nodes are used to connect boundaries of blockages
|
# NOTE: Intersection nodes are used to connect boundaries of blockages
|
||||||
# perpendicularly.
|
# perpendicularly.
|
||||||
new_nodes = []
|
debug.info(0, "Number of objects: {}".format(len(connect_objs)))
|
||||||
debug.info(0, "Number of blockages: {}".format(len(self.nav_blockages)))
|
for i in range(len(connect_objs)):
|
||||||
debug.info(0, "Number of nodes: {}".format(len(self.nodes)))
|
obj1 = connect_objs[i]
|
||||||
for i in range(len(self.nodes)):
|
for j in range(i + 1, len(connect_objs)):
|
||||||
debug.info(3, "Creating intersections for node #{}".format(i))
|
obj2 = connect_objs[j]
|
||||||
node1 = self.nodes[i]
|
node1, node2 = get_closest_nodes(obj1, obj2)
|
||||||
for j in range(i + 1, len(self.nodes)):
|
|
||||||
node2 = self.nodes[j]
|
|
||||||
# Skip if the nodes are already connected
|
|
||||||
if node1 in node2.neighbors:
|
|
||||||
continue
|
|
||||||
# Try two different corners
|
# Try two different corners
|
||||||
for k in [0, 1]:
|
for k in [0, 1]:
|
||||||
# Create a node at the perpendicular corner of these two nodes
|
# Create a node at the perpendicular corner of these two nodes
|
||||||
corner = navigation_node(vector(node1.position[k], node2.position[int(not k)]))
|
x_node = node1 if k else node2
|
||||||
|
y_node = node2 if k else node1
|
||||||
|
corner = navigation_node([x_node.center[0], y_node.center[1], 0])
|
||||||
# Skip this corner if the perpendicular connection is blocked
|
# Skip this corner if the perpendicular connection is blocked
|
||||||
if self.is_probe_blocked(corner.position, node1.position) or self.is_probe_blocked(corner.position, node2.position):
|
if self.is_probe_blocked(corner.center, node1.center) or self.is_probe_blocked(corner.center, node2.center):
|
||||||
continue
|
continue
|
||||||
|
# Check if this new node stands on an existing connection
|
||||||
|
self.remove_intersected_neighbors(node1, corner, k)
|
||||||
|
self.remove_intersected_neighbors(node2, corner, not(k))
|
||||||
|
# Add this new node to the graph
|
||||||
corner.add_neighbor(node1)
|
corner.add_neighbor(node1)
|
||||||
corner.add_neighbor(node2)
|
corner.add_neighbor(node2)
|
||||||
new_nodes.append(corner)
|
self.nodes.append(corner)
|
||||||
self.nodes.extend(new_nodes)
|
|
||||||
debug.info(0, "Number of nodes after intersections: {}".format(len(self.nodes)))
|
# Add corner nodes from blockages after intersections
|
||||||
|
for blockage in self.nav_blockages:
|
||||||
|
self.nodes.extend(blockage.corners)
|
||||||
|
debug.info(0, "Number of nodes after corners: {}".format(len(self.nodes)))
|
||||||
|
|
||||||
|
|
||||||
|
def remove_intersected_neighbors(self, node, corner, axis):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
a = node.center
|
||||||
|
mid = corner.center
|
||||||
|
for neighbor in node.neighbors:
|
||||||
|
b = neighbor.center
|
||||||
|
if a[not(axis)] == b[not(axis)] and is_between(a[axis], b[axis], mid[axis]):
|
||||||
|
neighbor.remove_neighbor(node)
|
||||||
|
neighbor.add_neighbor(corner)
|
||||||
|
|
||||||
|
|
||||||
|
def find_shortest_path(self, source, target):
|
||||||
|
"""
|
||||||
|
Find the shortest path from the source node to target node using the
|
||||||
|
A* algorithm.
|
||||||
|
"""
|
||||||
|
|
||||||
|
source = self.nodes[0]
|
||||||
|
target = self.nodes[1]
|
||||||
|
|
||||||
|
# Heuristic function to calculate the scores
|
||||||
|
h = lambda node: target.center.distance(node.center)
|
||||||
|
|
||||||
|
queue = []
|
||||||
|
close_set = set()
|
||||||
|
came_from = {}
|
||||||
|
g_scores = {}
|
||||||
|
f_scores = {}
|
||||||
|
|
||||||
|
# Initialize score values for the source node
|
||||||
|
g_scores[source.id] = 0
|
||||||
|
f_scores[source.id] = h(source)
|
||||||
|
|
||||||
|
heapq.heappush(queue, (f_scores[source.id], source.id, source))
|
||||||
|
|
||||||
|
# Run the A* algorithm
|
||||||
|
while len(queue) > 0:
|
||||||
|
# Get the closest node from the queue
|
||||||
|
current = heapq.heappop(queue)[2]
|
||||||
|
|
||||||
|
# Return if already discovered
|
||||||
|
if current in close_set:
|
||||||
|
continue
|
||||||
|
close_set.add(current)
|
||||||
|
|
||||||
|
# Check if we've reached the target
|
||||||
|
if current == target:
|
||||||
|
path = []
|
||||||
|
while current.id in came_from:
|
||||||
|
path.append(current)
|
||||||
|
current = came_from[current.id]
|
||||||
|
path.append(current)
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Update neighbor scores
|
||||||
|
for node in current.neighbors:
|
||||||
|
tentative_score = current.get_edge_cost(node) + g_scores[current.id]
|
||||||
|
if node.id not in g_scores or tentative_score < g_scores[node.id]:
|
||||||
|
came_from[node.id] = current
|
||||||
|
g_scores[node.id] = tentative_score
|
||||||
|
f_scores[node.id] = tentative_score + h(node)
|
||||||
|
heapq.heappush(queue, (f_scores[node.id], node.id, node))
|
||||||
|
|
||||||
|
# Return None if not connected
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def is_between(a, b, mid):
|
def is_between(a, b, mid):
|
||||||
""" Return if 'mid' is between 'a' and 'b'. """
|
""" Return if 'mid' is between 'a' and 'b'. """
|
||||||
|
|
||||||
return (a < mid and mid < b) or (b < mid and mid < a)
|
return (a < mid and mid < b) or (b < mid and mid < a)
|
||||||
|
|
||||||
|
|
||||||
|
def get_closest_nodes(a, b):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
if isinstance(a, navigation_node) and isinstance(b, navigation_node):
|
||||||
|
return a, b
|
||||||
|
if isinstance(a, navigation_blockage) and isinstance(b, navigation_blockage):
|
||||||
|
min_dist = float("inf")
|
||||||
|
min_a = None
|
||||||
|
min_b = None
|
||||||
|
for node_a in a.corners:
|
||||||
|
for node_b in b.corners:
|
||||||
|
dist = node_a.center.distance(node_b.center)
|
||||||
|
if dist < min_dist:
|
||||||
|
min_dist = dist
|
||||||
|
min_a = node_a
|
||||||
|
min_b = node_b
|
||||||
|
return min_a, min_b
|
||||||
|
if isinstance(a, navigation_node):
|
||||||
|
min_dist = float("inf")
|
||||||
|
min_a = None
|
||||||
|
min_b = None
|
||||||
|
for node_b in b.corners:
|
||||||
|
dist = a.center.distance(node_b.center)
|
||||||
|
if dist < min_dist:
|
||||||
|
min_dist = dist
|
||||||
|
min_a = a
|
||||||
|
min_b = node_b
|
||||||
|
return min_a, min_b
|
||||||
|
if isinstance(b, navigation_node):
|
||||||
|
min_dist = float("inf")
|
||||||
|
min_a = None
|
||||||
|
min_b = None
|
||||||
|
for node_a in a.corners:
|
||||||
|
dist = b.center.distance(node_a.center)
|
||||||
|
if dist < min_dist:
|
||||||
|
min_dist = dist
|
||||||
|
min_a = node_a
|
||||||
|
min_b = b
|
||||||
|
return min_a, min_b
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,28 @@
|
||||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
|
from openram.base.vector import vector
|
||||||
|
from openram.base.vector3d import vector3d
|
||||||
|
|
||||||
|
|
||||||
class navigation_node:
|
class navigation_node:
|
||||||
""" This class represents a node on the navigation graph. """
|
""" This class represents a node on the navigation graph. """
|
||||||
|
|
||||||
def __init__(self, position):
|
# This is used to assign unique ids to nodes
|
||||||
|
next_id = 0
|
||||||
|
|
||||||
self.position = position
|
def __init__(self, center, offset=None, horizontal=1, vertical=1):
|
||||||
|
|
||||||
|
self.id = navigation_node.next_id
|
||||||
|
navigation_node.next_id += 1
|
||||||
|
if isinstance(center, vector):
|
||||||
|
self.center = vector3d(center[0], center[1], 0)
|
||||||
|
elif isinstance(center, vector3d):
|
||||||
|
self.center = center
|
||||||
|
else:
|
||||||
|
self.center = vector3d(center)
|
||||||
|
if offset:
|
||||||
|
self.center += vector3d(offset * horizontal, offset * vertical, 0)
|
||||||
self.neighbors = []
|
self.neighbors = []
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,3 +41,12 @@ class navigation_node:
|
||||||
if node in self.neighbors:
|
if node in self.neighbors:
|
||||||
self.neighbors.remove(node)
|
self.neighbors.remove(node)
|
||||||
node.neighbors.remove(self)
|
node.neighbors.remove(self)
|
||||||
|
|
||||||
|
|
||||||
|
def get_edge_cost(self, node):
|
||||||
|
""" Return the cost of going to node. """
|
||||||
|
|
||||||
|
if node in self.neighbors:
|
||||||
|
return self.center.distance(node.center)
|
||||||
|
else:
|
||||||
|
return float("inf")
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
from openram import debug
|
from openram import debug
|
||||||
from openram.base.pin_layout import pin_layout
|
from openram.base.pin_layout import pin_layout
|
||||||
from openram.base.vector import vector
|
from openram.base.vector import vector
|
||||||
|
from openram.base.vector3d import vector3d
|
||||||
from openram.gdsMill import gdsMill
|
from openram.gdsMill import gdsMill
|
||||||
from openram.tech import GDS
|
from openram.tech import GDS
|
||||||
from openram.tech import layer as tech_layer
|
from openram.tech import layer as tech_layer
|
||||||
|
|
@ -26,6 +27,7 @@ class navigation_router(router_tech):
|
||||||
self.layers = layers
|
self.layers = layers
|
||||||
self.design = design
|
self.design = design
|
||||||
self.gds_filename = OPTS.openram_temp + "temp.gds"
|
self.gds_filename = OPTS.openram_temp + "temp.gds"
|
||||||
|
self.track_width = 1
|
||||||
self.pins = {}
|
self.pins = {}
|
||||||
self.all_pins = set()
|
self.all_pins = set()
|
||||||
self.blockages = []
|
self.blockages = []
|
||||||
|
|
@ -49,12 +51,18 @@ class navigation_router(router_tech):
|
||||||
self.find_blockages()
|
self.find_blockages()
|
||||||
|
|
||||||
# Create the navigation graph
|
# Create the navigation graph
|
||||||
self.nav = navigation_graph()
|
|
||||||
pin_iter = iter(self.pins["vdd"])
|
pin_iter = iter(self.pins["vdd"])
|
||||||
vdd_0 = next(pin_iter)
|
vdd_0 = next(pin_iter)
|
||||||
vdd_1 = next(pin_iter)
|
vdd_1 = next(pin_iter)
|
||||||
|
self.nav = navigation_graph(self.track_width)
|
||||||
self.nav.create_graph(vdd_0, vdd_1, self.blockages)
|
self.nav.create_graph(vdd_0, vdd_1, self.blockages)
|
||||||
|
|
||||||
|
# Find the shortest path from source to target
|
||||||
|
path = self.nav.find_shortest_path(vdd_0, vdd_1)
|
||||||
|
|
||||||
|
# Create the path shapes on layout
|
||||||
|
self.add_path(path)
|
||||||
|
|
||||||
self.write_debug_gds(source=vdd_0, target=vdd_1)
|
self.write_debug_gds(source=vdd_0, target=vdd_1)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -68,8 +76,8 @@ class navigation_router(router_tech):
|
||||||
layer, boundary = shape
|
layer, boundary = shape
|
||||||
# gdsMill boundaries are in (left, bottom, right, top) order
|
# gdsMill boundaries are in (left, bottom, right, top) order
|
||||||
# so repack and snap to the grid
|
# so repack and snap to the grid
|
||||||
ll = vector(boundary[0], boundary[1]).snap_to_grid()
|
ll = vector(boundary[0], boundary[1])
|
||||||
ur = vector(boundary[2], boundary[3]).snap_to_grid()
|
ur = vector(boundary[2], boundary[3])
|
||||||
rect = [ll, ur]
|
rect = [ll, ur]
|
||||||
pin = pin_layout(pin_name, rect, layer)
|
pin = pin_layout(pin_name, rect, layer)
|
||||||
pin_set.add(pin)
|
pin_set.add(pin)
|
||||||
|
|
@ -80,7 +88,7 @@ class navigation_router(router_tech):
|
||||||
|
|
||||||
def find_blockages(self):
|
def find_blockages(self):
|
||||||
""" """
|
""" """
|
||||||
debug.info(1, "Finding all blockages")
|
debug.info(1, "Finding all blockages...")
|
||||||
|
|
||||||
for lpp in [self.vert_lpp, self.horiz_lpp]:
|
for lpp in [self.vert_lpp, self.horiz_lpp]:
|
||||||
shapes = self.layout.getAllShapes(lpp)
|
shapes = self.layout.getAllShapes(lpp)
|
||||||
|
|
@ -92,8 +100,7 @@ class navigation_router(router_tech):
|
||||||
rect = [ll, ur]
|
rect = [ll, ur]
|
||||||
new_shape = pin_layout("blockage{}".format(len(self.blockages)),
|
new_shape = pin_layout("blockage{}".format(len(self.blockages)),
|
||||||
rect,
|
rect,
|
||||||
lpp)
|
lpp).inflated_pin()
|
||||||
|
|
||||||
# If there is a rectangle that is the same in the pins,
|
# If there is a rectangle that is the same in the pins,
|
||||||
# it isn't a blockage
|
# it isn't a blockage
|
||||||
if new_shape not in self.all_pins and not self.pin_contains(new_shape):
|
if new_shape not in self.all_pins and not self.pin_contains(new_shape):
|
||||||
|
|
@ -107,6 +114,30 @@ class navigation_router(router_tech):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def add_path(self, path):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
for i in range(len(path) - 1):
|
||||||
|
self.connect_nodes(path[i], path[i + 1])
|
||||||
|
|
||||||
|
|
||||||
|
def connect_nodes(self, a, b):
|
||||||
|
""" Connect nodes 'a' and 'b' with a wire. """
|
||||||
|
|
||||||
|
# Calculate the shape of the wire
|
||||||
|
track_offset = vector3d(self.track_width / 2, self.track_width / 2, 0)
|
||||||
|
ll = a.center.min(b.center) - track_offset
|
||||||
|
ur = a.center.max(b.center) + track_offset
|
||||||
|
|
||||||
|
debug.info(0, "Adding wire: ({}, {})".format(ll, ur))
|
||||||
|
|
||||||
|
# Add the shape to the layout
|
||||||
|
self.design.add_rect(layer="text",
|
||||||
|
offset=(ll[0], ll[1]),
|
||||||
|
width=ur.x - ll.x,
|
||||||
|
height=ur.y - ll.y)
|
||||||
|
|
||||||
|
|
||||||
def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None):
|
def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None):
|
||||||
""" """
|
""" """
|
||||||
|
|
||||||
|
|
@ -120,30 +151,16 @@ class navigation_router(router_tech):
|
||||||
|
|
||||||
# Display the inflated blockage
|
# Display the inflated blockage
|
||||||
for blockage in self.nav.nav_blockages:
|
for blockage in self.nav.nav_blockages:
|
||||||
ll, ur = blockage.ll, blockage.ur
|
self.add_object_info(blockage, "blockage")
|
||||||
self.design.add_rect(layer="text",
|
|
||||||
offset=ll,
|
|
||||||
width=ur.x - ll.x,
|
|
||||||
height=ur.y - ll.y)
|
|
||||||
self.design.add_label(text="blockage",
|
|
||||||
layer="text",
|
|
||||||
offset=ll)
|
|
||||||
for node in self.nav.nodes:
|
for node in self.nav.nodes:
|
||||||
self.design.add_rect_center(layer="text",
|
offset = (node.center.x, node.center.y)
|
||||||
offset=node.position,
|
self.design.add_label(text="O",
|
||||||
width=1,
|
|
||||||
height=1)
|
|
||||||
self.design.add_label(text="-0-",
|
|
||||||
layer="text",
|
layer="text",
|
||||||
offset=node.position)
|
offset=offset)
|
||||||
if source:
|
if source:
|
||||||
self.design.add_label(text="source",
|
self.add_object_info(source, "source")
|
||||||
layer="text",
|
|
||||||
offset=source.rect[0])
|
|
||||||
if target:
|
if target:
|
||||||
self.design.add_label(text="target",
|
self.add_object_info(target, "target")
|
||||||
layer="text",
|
|
||||||
offset=target.rect[0])
|
|
||||||
|
|
||||||
|
|
||||||
def del_router_info(self):
|
def del_router_info(self):
|
||||||
|
|
@ -151,3 +168,16 @@ class navigation_router(router_tech):
|
||||||
|
|
||||||
lpp = tech_layer["text"]
|
lpp = tech_layer["text"]
|
||||||
self.design.objs = [x for x in self.design.objs if x.lpp != lpp]
|
self.design.objs = [x for x in self.design.objs if x.lpp != lpp]
|
||||||
|
|
||||||
|
|
||||||
|
def add_object_info(self, obj, label):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
ll, ur = obj.rect
|
||||||
|
self.design.add_rect(layer="text",
|
||||||
|
offset=ll,
|
||||||
|
width=ur.x - ll.x,
|
||||||
|
height=ur.y - ll.y)
|
||||||
|
self.design.add_label(text=label,
|
||||||
|
layer="text",
|
||||||
|
offset=ll)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue