diff --git a/src/autorouter/autoroute.rs b/src/autorouter/autoroute.rs index b76545c..f5285ef 100644 --- a/src/autorouter/autoroute.rs +++ b/src/autorouter/autoroute.rs @@ -188,7 +188,7 @@ impl Step, Option, AutorouteContinue .board .layout() .drawing() - .loose_band_uid(band_termseg.into()) + .find_loose_band_uid(band_termseg.into()) .expect("a completely routed band should've Seg's as ends"); autorouter.ratsnest.assign_band_termseg_to_ratline( diff --git a/src/autorouter/selection.rs b/src/autorouter/selection.rs index afd639b..1d16dcb 100644 --- a/src/autorouter/selection.rs +++ b/src/autorouter/selection.rs @@ -122,7 +122,10 @@ impl BandSelector { _ => return None, }; - Self::try_from_uid(board, &board.layout().drawing().loose_band_uid(loose).ok()?) + Self::try_from_uid( + board, + &board.layout().drawing().find_loose_band_uid(loose).ok()?, + ) } pub fn try_from_uid( diff --git a/src/board/mod.rs b/src/board/mod.rs index 939f91e..0914517 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -65,7 +65,7 @@ impl<'a> ResolvedSelector<'a> { } else { loose.and_then(|loose| { Some(ResolvedSelector::Band { - band_uid: board.layout().drawing().loose_band_uid(loose).ok()?, + band_uid: board.layout().drawing().find_loose_band_uid(loose).ok()?, }) }) } diff --git a/src/drawing/drawing.rs b/src/drawing/drawing.rs index 4379f21..dfd228e 100644 --- a/src/drawing/drawing.rs +++ b/src/drawing/drawing.rs @@ -23,7 +23,7 @@ use crate::{ primitive::{ GenericPrimitive, GetCore, GetJoints, GetLimbs, GetOtherJoint, MakePrimitiveShape, }, - rules::{AccessRules, GetConditions}, + rules::AccessRules, seg::{ FixedSegIndex, FixedSegWeight, LoneLooseSegIndex, LoneLooseSegWeight, SegIndex, SegWeight, SeqLooseSegIndex, SeqLooseSegWeight, @@ -31,7 +31,7 @@ use crate::{ }, geometry::{ edit::{ApplyGeometryEdit, GeometryEdit}, - primitive::{AccessPrimitiveShape, PrimitiveShape}, + primitive::PrimitiveShape, recording_with_rtree::RecordingGeometryWithRtree, with_rtree::BboxedIndex, AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel, @@ -751,7 +751,7 @@ impl Drawing { (from, to, offset) }; - let rail_outer_bows = self.bend_outward_bows(rail); + let rail_outer_bows = self.collect_bend_outward_bows(rail); // Commenting out these two makes the crash go away. self.move_dot_with_infringement_filtering( @@ -950,7 +950,7 @@ impl Drawing { .move_dot(recorder, dot, to); for limb in dot.primitive(self).limbs() { - if let Some(infringement) = self.detect_infringement_except(limb, predicate) { + if let Some(infringement) = self.find_infringement_except(limb, predicate) { // Restore previous state. self.recording_geometry_with_rtree .move_dot(recorder, dot, old_pos); @@ -958,7 +958,7 @@ impl Drawing { } } - if let Some(infringement) = self.detect_infringement_except(dot.into(), predicate) { + if let Some(infringement) = self.find_infringement_except(dot.into(), predicate) { // Restore previous state. self.recording_geometry_with_rtree .move_dot(recorder, dot, old_pos); @@ -985,7 +985,7 @@ impl Drawing { self.recording_geometry_with_rtree .shift_bend(recorder, bend, offset); - if let Some(infringement) = self.detect_infringement_except(bend.into(), predicate) { + if let Some(infringement) = self.find_infringement_except(bend.into(), predicate) { // Restore previous state. self.recording_geometry_with_rtree .shift_bend(recorder, bend, old_offset); @@ -1068,7 +1068,7 @@ impl Drawing { node: PrimitiveIndex, predicate: &impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool, ) -> Result<(), Infringement> { - if let Some(infringement) = self.detect_infringement_except(node, predicate) { + if let Some(infringement) = self.find_infringement_except(node, predicate) { if let Ok(dot) = node.try_into() { self.recording_geometry_with_rtree.remove_dot(recorder, dot); } else if let Ok(seg) = node.try_into() { @@ -1082,114 +1082,6 @@ impl Drawing { Ok(()) } - fn detect_infringement_except( - &self, - infringer: PrimitiveIndex, - predicate: &impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool, - ) -> Option { - self.find_infringement( - infringer, - self.locate_possible_infringees(infringer) - .filter_map(|infringee_node| { - if let GenericNode::Primitive(primitive_node) = infringee_node { - Some(primitive_node) - } else { - None - } - }) - .filter(|infringee| predicate(&self, infringer, *infringee)), - ) - } - - fn locate_possible_infringees( - &self, - node: PrimitiveIndex, - ) -> impl Iterator>> + '_ { - let limiting_shape = node.primitive(self).shape().inflate( - node.primitive(self) - .maybe_net() - .map(|net| self.rules.largest_clearance(Some(net))) - .unwrap_or(0.0), - ); - - self.recording_geometry_with_rtree - .rtree() - .locate_in_envelope_intersecting( - &limiting_shape.envelope_3d(0.0, node.primitive(self).layer()), - ) - .map(|wrapper| wrapper.data) - } - - fn find_infringement( - &self, - infringer: PrimitiveIndex, - it: impl Iterator, - ) -> Option { - let mut inflated_shape = infringer.primitive(self).shape(); // Unused temporary value just for initialization. - let conditions = infringer.primitive(self).conditions(); - - it.filter(|infringee| { - // Infringement with loose dots resulted in false positives for - // line-of-sight paths. - !matches!(infringer, PrimitiveIndex::LooseDot(..)) - && !matches!(infringee, PrimitiveIndex::LooseDot(..)) - }) - .filter(|infringee| !self.are_connectable(infringer, *infringee)) - .find_map(|primitive_node| { - let infringee_conditions = primitive_node.primitive(self).conditions(); - - let epsilon = 1.0; - inflated_shape = infringer.primitive(self).shape().inflate( - match (&conditions, infringee_conditions) { - (None, _) | (_, None) => 0.0, - (Some(lhs), Some(rhs)) => { - // Note the epsilon comparison. - // XXX: Epsilon is probably too large. But what should - // it be exactly then? - (self.rules.clearance(lhs, &rhs) - epsilon).clamp(0.0, f64::INFINITY) - } - }, - ); - - inflated_shape - .intersects(&primitive_node.primitive(self).shape()) - .then_some(Infringement(inflated_shape, primitive_node)) - }) - } - - fn detect_collision_except( - &self, - collider: PrimitiveIndex, - predicate: &impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool, - ) -> Option { - let shape = collider.primitive(self).shape(); - - self.recording_geometry_with_rtree - .rtree() - .locate_in_envelope_intersecting(&shape.full_height_envelope_3d(0.0, 2)) - .filter_map(|wrapper| { - if let GenericNode::Primitive(collidee) = wrapper.data { - Some(collidee) - } else { - None - } - }) - // NOTE: Collisions can happen between two same-net loose - // segs, so these cases in particular are not filtered out - // here, unlike what is done in infringement code. - .filter(|&collidee| collider != collidee) - .filter(|&collidee| { - !self.are_connectable(collider, collidee) - || ((matches!(collider, PrimitiveIndex::LoneLooseSeg(..)) - || matches!(collider, PrimitiveIndex::SeqLooseSeg(..))) - && (matches!(collidee, PrimitiveIndex::LoneLooseSeg(..)) - || matches!(collidee, PrimitiveIndex::SeqLooseSeg(..)))) - }) - .filter(|collidee| predicate(&self, collider, *collidee)) - .find(|collidee| shape.intersects(&collidee.primitive(self).shape())) - .map(|collidee| Collision(shape, collidee)) - } - pub fn primitive_nodes(&self) -> impl Iterator + '_ { self.recording_geometry_with_rtree .rtree() @@ -1253,17 +1145,6 @@ impl Drawing { } } - fn are_connectable(&self, node1: PrimitiveIndex, node2: PrimitiveIndex) -> bool { - if let (Some(node1_net), Some(node2_net)) = ( - node1.primitive(self).maybe_net(), - node2.primitive(self).maybe_net(), - ) { - node1_net == node2_net - } else { - true - } - } - fn test_if_looses_dont_infringe_each_other(&self) -> bool { !self .primitive_nodes() @@ -1277,7 +1158,7 @@ impl Drawing { ) }) .any(|node| { - self.find_infringement( + self.infringements_among( node, self.locate_possible_infringees(node) .filter_map(|n| { @@ -1297,6 +1178,7 @@ impl Drawing { ) }), ) + .next() .is_some() }) } diff --git a/src/drawing/query.rs b/src/drawing/query.rs index e16c289..5702335 100644 --- a/src/drawing/query.rs +++ b/src/drawing/query.rs @@ -3,6 +3,17 @@ // SPDX-License-Identifier: MIT use petgraph::visit::Walker; +use specctra_core::rules::GetConditions; + +use crate::{ + drawing::{ + graph::{GetMaybeNet, MakePrimitive}, + primitive::MakePrimitiveShape, + Collision, Infringement, + }, + geometry::{primitive::AccessPrimitiveShape, GenericNode, GetLayer}, + graph::GenericIndex, +}; use super::{ band::{BandTermsegIndex, BandUid}, @@ -24,10 +35,10 @@ pub struct BandUidError { /// Routines implementing various queries on drawing. A query is a routine that /// returns indices of one or more primitives. impl Drawing { - pub fn loose_band_uid(&self, start_loose: LooseIndex) -> Result { + pub fn find_loose_band_uid(&self, start_loose: LooseIndex) -> Result { match ( - self.loose_band_first_seg(start_loose), - self.loose_band_last_seg(start_loose), + self.find_loose_band_first_seg(start_loose), + self.find_loose_band_last_seg(start_loose), ) { (Some(first), Some(last)) => Ok(BandUid::from((first, last))), (Some(x), None) | (None, Some(x)) => Err(BandUidError { maybe_end: Some(x) }), @@ -35,18 +46,54 @@ impl Drawing { } } - pub fn bend_outward_bows(&self, bend: LooseBendIndex) -> Vec { + fn find_loose_band_first_seg(&self, start_loose: LooseIndex) -> Option { + if let LooseIndex::LoneSeg(seg) = start_loose { + return Some(BandTermsegIndex::Lone(seg)); + } + + let mut loose = start_loose; + let mut prev = None; + + loop { + if let Some(next_loose) = self.loose(loose).prev_loose(prev) { + prev = Some(loose); + loose = next_loose; + } else { + return loose.try_into().ok(); + } + } + } + + fn find_loose_band_last_seg(&self, start_loose: LooseIndex) -> Option { + if let LooseIndex::LoneSeg(seg) = start_loose { + return Some(BandTermsegIndex::Lone(seg)); + } + + let mut loose = start_loose; + let mut next = None; + + loop { + if let Some(prev_loose) = self.loose(loose).next_loose(next) { + next = Some(loose); + loose = prev_loose; + } else { + return loose.try_into().ok(); + } + } + } + + pub fn collect_bend_outward_bows(&self, bend: LooseBendIndex) -> Vec { let mut v = vec![]; let mut outwards = self.primitive(bend).outwards(); while let Some(next) = outwards.walk_next(self) { - v.append(&mut self.bend_bow(next)); + v.append(&mut self.collect_bend_bow(next)); } v } - fn bend_bow(&self, bend: LooseBendIndex) -> Vec { + fn collect_bend_bow(&self, bend: LooseBendIndex) -> Vec { let mut v: Vec = vec![]; v.push(bend.into()); @@ -65,39 +112,123 @@ impl Drawing { v } - fn loose_band_first_seg(&self, start_loose: LooseIndex) -> Option { - if let LooseIndex::LoneSeg(seg) = start_loose { - return Some(BandTermsegIndex::Lone(seg)); - } - - let mut loose = start_loose; - let mut prev = None; - - loop { - if let Some(next_loose) = self.loose(loose).prev_loose(prev) { - prev = Some(loose); - loose = next_loose; - } else { - return loose.try_into().ok(); - } - } + pub(super) fn find_infringement_except( + &self, + infringer: PrimitiveIndex, + predicate: &impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool, + ) -> Option { + self.infringements_among( + infringer, + self.locate_possible_infringees(infringer) + .filter_map(|infringee_node| { + if let GenericNode::Primitive(primitive_node) = infringee_node { + Some(primitive_node) + } else { + None + } + }) + .filter(|infringee| predicate(&self, infringer, *infringee)), + ) + .next() } - fn loose_band_last_seg(&self, start_loose: LooseIndex) -> Option { - if let LooseIndex::LoneSeg(seg) = start_loose { - return Some(BandTermsegIndex::Lone(seg)); - } + pub(super) fn infringements_among<'a>( + &'a self, + infringer: PrimitiveIndex, + it: impl Iterator + 'a, + ) -> impl Iterator + 'a { + let mut inflated_shape = infringer.primitive(self).shape(); // Unused temporary value just for initialization. + let conditions = infringer.primitive(self).conditions(); - let mut loose = start_loose; - let mut next = None; + it.filter(move |infringee| { + // Infringement with loose dots resulted in false positives for + // line-of-sight paths. + !matches!(infringer, PrimitiveIndex::LooseDot(..)) + && !matches!(infringee, PrimitiveIndex::LooseDot(..)) + }) + .filter(move |infringee| !self.are_connectable(infringer, *infringee)) + .filter_map(move |primitive_node| { + let infringee_conditions = primitive_node.primitive(self).conditions(); - loop { - if let Some(prev_loose) = self.loose(loose).next_loose(next) { - next = Some(loose); - loose = prev_loose; - } else { - return loose.try_into().ok(); - } + let epsilon = 1.0; + inflated_shape = infringer.primitive(self).shape().inflate( + match (&conditions, infringee_conditions) { + (None, _) | (_, None) => 0.0, + (Some(lhs), Some(rhs)) => { + // Note the epsilon comparison. + // XXX: Epsilon is probably too large. But what should + // it be exactly then? + (self.rules().clearance(lhs, &rhs) - epsilon).clamp(0.0, f64::INFINITY) + } + }, + ); + + inflated_shape + .intersects(&primitive_node.primitive(self).shape()) + .then_some(Infringement(inflated_shape, primitive_node)) + }) + } + + pub(super) fn locate_possible_infringees( + &self, + node: PrimitiveIndex, + ) -> impl Iterator>> + '_ { + let limiting_shape = node.primitive(self).shape().inflate( + node.primitive(self) + .maybe_net() + .map(|net| self.rules().largest_clearance(Some(net))) + .unwrap_or(0.0), + ); + + self.recording_geometry_with_rtree() + .rtree() + .locate_in_envelope_intersecting( + &limiting_shape.envelope_3d(0.0, node.primitive(self).layer()), + ) + .map(|wrapper| wrapper.data) + } + + pub(super) fn detect_collision_except( + &self, + collider: PrimitiveIndex, + predicate: &impl Fn(&Self, PrimitiveIndex, PrimitiveIndex) -> bool, + ) -> Option { + let shape = collider.primitive(self).shape(); + + self.recording_geometry_with_rtree() + .rtree() + .locate_in_envelope_intersecting(&shape.full_height_envelope_3d(0.0, 2)) + .filter_map(|wrapper| { + if let GenericNode::Primitive(collidee) = wrapper.data { + Some(collidee) + } else { + None + } + }) + // NOTE: Collisions can happen between two same-net loose + // segs, so these cases in particular are not filtered out + // here, unlike what is done in infringement code. + .filter(|&collidee| collider != collidee) + .filter(|&collidee| { + !self.are_connectable(collider, collidee) + || ((matches!(collider, PrimitiveIndex::LoneLooseSeg(..)) + || matches!(collider, PrimitiveIndex::SeqLooseSeg(..))) + && (matches!(collidee, PrimitiveIndex::LoneLooseSeg(..)) + || matches!(collidee, PrimitiveIndex::SeqLooseSeg(..)))) + }) + .filter(|collidee| predicate(&self, collider, *collidee)) + .find(|collidee| shape.intersects(&collidee.primitive(self).shape())) + .map(|collidee| Collision(shape, collidee)) + } + + fn are_connectable(&self, node1: PrimitiveIndex, node2: PrimitiveIndex) -> bool { + if let (Some(node1_net), Some(node2_net)) = ( + node1.primitive(self).maybe_net(), + node2.primitive(self).maybe_net(), + ) { + node1_net == node2_net + } else { + true } } } diff --git a/src/layout/collect_bands.rs b/src/layout/collect_bands.rs index dcc27b6..43ce7b4 100644 --- a/src/layout/collect_bands.rs +++ b/src/layout/collect_bands.rs @@ -74,7 +74,7 @@ impl Layout { (loose, shape) }) .filter_map(move |(loose, shape)| { - let band_uid = self.drawing.loose_band_uid(loose).ok()?; + let band_uid = self.drawing.find_loose_band_uid(loose).ok()?; let loose_hline = orig_hline.orthogonal_through(&match shape { PrimitiveShape::Seg(seg) => { let seg_hline = LineInGeneralForm::from(seg.middle_line()); diff --git a/src/router/ng/eval.rs b/src/router/ng/eval.rs index 9bcb147..026dc36 100644 --- a/src/router/ng/eval.rs +++ b/src/router/ng/eval.rs @@ -167,7 +167,7 @@ impl AstarContext { sub.label.clone(), layout .drawing() - .loose_band_uid(fin.into()) + .find_loose_band_uid(fin.into()) .expect("a completely routed band should've Seg's as ends"), ); Ok(( diff --git a/src/router/ng/mod.rs b/src/router/ng/mod.rs index 13bc3ba..586bfe4 100644 --- a/src/router/ng/mod.rs +++ b/src/router/ng/mod.rs @@ -583,7 +583,7 @@ fn cane_around( .all_rails(core.petgraph_index()) .filter_map(|bi| { if let BendIndex::Loose(lbi) = bi { - if layout.drawing().loose_band_uid(lbi.into()).ok() == Some(inner) { + if layout.drawing().find_loose_band_uid(lbi.into()).ok() == Some(inner) { Some(lbi) } else { None