clean up magic numbers, enable arbitrary gridding, add cache invalidatino
This commit is contained in:
parent
a77ae781a7
commit
519dd48131
19 changed files with 574 additions and 358 deletions
|
|
@ -198,7 +198,23 @@ class CollisionEngine:
|
|||
del self.dynamic_dilated[obj_id]
|
||||
|
||||
def lock_net(self, net_id: str) -> None:
|
||||
""" Convert a routed net into static obstacles. """
|
||||
self._locked_nets.add(net_id)
|
||||
|
||||
# Move all segments of this net to static obstacles
|
||||
to_move = [obj_id for obj_id, (nid, _) in self.dynamic_geometries.items() if nid == net_id]
|
||||
for obj_id in to_move:
|
||||
poly = self.dynamic_geometries[obj_id][1]
|
||||
self.add_static_obstacle(poly)
|
||||
|
||||
# Remove from dynamic index (without triggering the locked-net guard)
|
||||
self.dynamic_tree = None
|
||||
self.dynamic_grid = {}
|
||||
self._dynamic_tree_dirty = True
|
||||
for obj_id in to_move:
|
||||
self.dynamic_index.delete(obj_id, self.dynamic_dilated[obj_id].bounds)
|
||||
del self.dynamic_geometries[obj_id]
|
||||
del self.dynamic_dilated[obj_id]
|
||||
|
||||
def unlock_net(self, net_id: str) -> None:
|
||||
self._locked_nets.discard(net_id)
|
||||
|
|
@ -208,44 +224,71 @@ class CollisionEngine:
|
|||
reach = self.ray_cast(start_port, start_port.orientation, max_dist=length + 0.01)
|
||||
return reach < length - 0.001
|
||||
|
||||
def _is_in_safety_zone_fast(self, idx: int, start_port: Port | None, end_port: Port | None) -> bool:
|
||||
""" Fast port-based check to see if a collision might be in a safety zone. """
|
||||
sz = self.safety_zone_radius
|
||||
b = self._static_bounds_array[idx]
|
||||
if start_port:
|
||||
if (b[0]-sz <= start_port.x <= b[2]+sz and
|
||||
b[1]-sz <= start_port.y <= b[3]+sz): return True
|
||||
if end_port:
|
||||
if (b[0]-sz <= end_port.x <= b[2]+sz and
|
||||
b[1]-sz <= end_port.y <= b[3]+sz): return True
|
||||
return False
|
||||
|
||||
def check_move_static(self, result: ComponentResult, start_port: Port | None = None, end_port: Port | None = None) -> bool:
|
||||
if not self.static_dilated: return False
|
||||
self.metrics['static_tree_queries'] += 1
|
||||
self._ensure_static_tree()
|
||||
if self.static_tree is None: return False
|
||||
|
||||
# 1. Fast total bounds check
|
||||
tb = result.total_bounds
|
||||
s_bounds = self._static_bounds_array
|
||||
possible_total = (tb[0] < s_bounds[:, 2]) & (tb[2] > s_bounds[:, 0]) & \
|
||||
(tb[1] < s_bounds[:, 3]) & (tb[3] > s_bounds[:, 1])
|
||||
|
||||
if not numpy.any(possible_total):
|
||||
return False
|
||||
hits = self.static_tree.query(box(*tb))
|
||||
if hits.size == 0: return False
|
||||
|
||||
# 2. Per-polygon AABB check
|
||||
bounds_list = result.bounds
|
||||
any_possible = False
|
||||
for b in bounds_list:
|
||||
possible = (b[0] < s_bounds[:, 2]) & (b[2] > s_bounds[:, 0]) & \
|
||||
(b[1] < s_bounds[:, 3]) & (b[3] > s_bounds[:, 1])
|
||||
if numpy.any(possible):
|
||||
any_possible = True
|
||||
break
|
||||
|
||||
if not any_possible:
|
||||
return False
|
||||
|
||||
# 3. Real geometry check (Triggers Lazy Evaluation)
|
||||
test_geoms = result.dilated_geometry if result.dilated_geometry else result.geometry
|
||||
for i, poly in enumerate(result.geometry):
|
||||
hits = self.static_tree.query(test_geoms[i], predicate='intersects')
|
||||
for hit_idx in hits:
|
||||
obj_id = self.static_obj_ids[hit_idx]
|
||||
if self._is_in_safety_zone(poly, obj_id, start_port, end_port): continue
|
||||
return True
|
||||
# 2. Per-hit check
|
||||
s_bounds = self._static_bounds_array
|
||||
move_poly_bounds = result.bounds
|
||||
for hit_idx in hits:
|
||||
obs_b = s_bounds[hit_idx]
|
||||
|
||||
# Check if any polygon in the move actually hits THIS obstacle's AABB
|
||||
poly_hits_obs_aabb = False
|
||||
for pb in move_poly_bounds:
|
||||
if (pb[0] < obs_b[2] and pb[2] > obs_b[0] and
|
||||
pb[1] < obs_b[3] and pb[3] > obs_b[1]):
|
||||
poly_hits_obs_aabb = True
|
||||
break
|
||||
|
||||
if not poly_hits_obs_aabb: continue
|
||||
|
||||
# Safety zone check (Fast port-based)
|
||||
if self._is_in_safety_zone_fast(hit_idx, start_port, end_port):
|
||||
# If near port, we must use the high-precision check
|
||||
obj_id = self.static_obj_ids[hit_idx]
|
||||
# Triggers lazy evaluation of geometry only if needed
|
||||
poly_move = result.geometry[0] # Simplification: assume 1 poly for now or loop
|
||||
# Actually, better loop over move polygons for high-fidelity
|
||||
collision_found = False
|
||||
for p_move in result.geometry:
|
||||
if not self._is_in_safety_zone(p_move, obj_id, start_port, end_port):
|
||||
collision_found = True; break
|
||||
if not collision_found: continue
|
||||
return True
|
||||
|
||||
# Not in safety zone and AABBs overlap - check real intersection
|
||||
# This is the most common path for real collisions or near misses
|
||||
obj_id = self.static_obj_ids[hit_idx]
|
||||
raw_obstacle = self.static_geometries[obj_id]
|
||||
test_geoms = result.dilated_geometry if result.dilated_geometry else result.geometry
|
||||
|
||||
for i, p_test in enumerate(test_geoms):
|
||||
if p_test.intersects(raw_obstacle):
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_move_congestion(self, result: ComponentResult, net_id: str) -> int:
|
||||
if not self.dynamic_geometries: return 0
|
||||
tb = result.total_dilated_bounds
|
||||
if tb is None: return 0
|
||||
self._ensure_dynamic_grid()
|
||||
|
|
@ -316,21 +359,30 @@ class CollisionEngine:
|
|||
Only returns True if the collision is ACTUALLY inside a safety zone.
|
||||
"""
|
||||
raw_obstacle = self.static_geometries[obj_id]
|
||||
sz = self.safety_zone_radius
|
||||
|
||||
# Fast path: check if ports are even near the obstacle
|
||||
obs_b = raw_obstacle.bounds
|
||||
near_start = start_port and (obs_b[0]-sz <= start_port.x <= obs_b[2]+sz and
|
||||
obs_b[1]-sz <= start_port.y <= obs_b[3]+sz)
|
||||
near_end = end_port and (obs_b[0]-sz <= end_port.x <= obs_b[2]+sz and
|
||||
obs_b[1]-sz <= end_port.y <= obs_b[3]+sz)
|
||||
|
||||
if not near_start and not near_end:
|
||||
return False
|
||||
|
||||
if not geometry.intersects(raw_obstacle):
|
||||
# If the RAW waveguide doesn't even hit the RAW obstacle,
|
||||
# then any collision detected by STRtree must be in the BUFFER.
|
||||
# Buffer collisions are NOT in safety zone.
|
||||
return False
|
||||
|
||||
sz = self.safety_zone_radius
|
||||
self.metrics['safety_zone_checks'] += 1
|
||||
intersection = geometry.intersection(raw_obstacle)
|
||||
if intersection.is_empty: return False # Should be impossible if intersects was True
|
||||
if intersection.is_empty: return False
|
||||
|
||||
ix_bounds = intersection.bounds
|
||||
if start_port:
|
||||
if start_port and near_start:
|
||||
if (abs(ix_bounds[0] - start_port.x) < sz and abs(ix_bounds[1] - start_port.y) < sz and
|
||||
abs(ix_bounds[2] - start_port.x) < sz and abs(ix_bounds[3] - start_port.y) < sz): return True
|
||||
if end_port:
|
||||
if end_port and near_end:
|
||||
if (abs(ix_bounds[0] - end_port.x) < sz and abs(ix_bounds[1] - end_port.y) < sz and
|
||||
abs(ix_bounds[2] - end_port.x) < sz and abs(ix_bounds[3] - end_port.y) < sz): return True
|
||||
return False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue