diff --git a/src/board/mod.rs b/src/board/mod.rs index 0914517..f18a689 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -158,10 +158,11 @@ impl Board { weight: PolyWeight, maybe_pin: Option, nodes: &[PrimitiveIndex], + fillets: &[FixedDotIndex], ) -> GenericIndex { let (poly, apex) = self.layout - .add_poly_with_nodes(&mut recorder.layout_edit, weight, nodes); + .add_poly_with_nodes(&mut recorder.layout_edit, weight, nodes, fillets); if let Some(pin) = maybe_pin { for i in nodes { diff --git a/src/drawing/primitive.rs b/src/drawing/primitive.rs index 9d4be8c..93d8d6c 100644 --- a/src/drawing/primitive.rs +++ b/src/drawing/primitive.rs @@ -295,7 +295,7 @@ impl GetOuterGears for FixedDot<'_, CW, Cel, R> { impl GetPrevNextInChain for FixedDot<'_, CW, Cel, R> { fn next_in_chain(&self, maybe_prev: Option) -> Option { self.drawing - .clearance_intersectors(self.index.into()) + .overlapees(self.index.into()) .find_map(|infringement| { let PrimitiveIndex::FixedDot(intersectee) = infringement.1 else { return None; diff --git a/src/drawing/query.rs b/src/drawing/query.rs index 17f4454..eb75772 100644 --- a/src/drawing/query.rs +++ b/src/drawing/query.rs @@ -151,7 +151,7 @@ impl Drawing { infringer: PrimitiveIndex, it: impl Iterator + 'a, ) -> impl Iterator + 'a { - self.clearance_intersectors_among(infringer, it) + self.overlapees_among(infringer, it) .filter(move |infringement| { // Infringement with loose dots resulted in false positives for // line-of-sight paths. @@ -161,24 +161,25 @@ impl Drawing { .filter(move |infringement| !self.are_connectable(infringer, infringement.1)) } - pub fn clearance_intersectors<'a>( + pub fn overlapees<'a>( &'a self, - intersector: PrimitiveIndex, + overlapper: PrimitiveIndex, ) -> impl Iterator + 'a { - self.clearance_intersectors_among( - intersector, - self.locate_possible_infringees(intersector) + self.overlapees_among( + overlapper, + self.locate_possible_infringees(overlapper) .filter_map(move |infringee_node| { if let GenericNode::Primitive(primitive_node) = infringee_node { Some(primitive_node) } else { None } - }), + }) + .filter(move |&overlapee| overlapper != overlapee), ) } - pub(super) fn clearance_intersectors_among<'a>( + pub(super) fn overlapees_among<'a>( &'a self, intersector: PrimitiveIndex, it: impl Iterator + 'a, diff --git a/src/layout/layout.rs b/src/layout/layout.rs index 7c1cd15..2bcfd13 100644 --- a/src/layout/layout.rs +++ b/src/layout/layout.rs @@ -53,7 +53,8 @@ pub enum CompoundWeight { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CompoundEntryLabel { Normal, - NotInConvexHull, + Apex, + Fillet, } /// The alias to differ node types @@ -254,13 +255,14 @@ impl Layout { recorder: &mut LayoutEdit, weight: PolyWeight, nodes: &[PrimitiveIndex], + fillets: &[FixedDotIndex], ) -> (GenericIndex, FixedDotIndex) { let layer = weight.layer(); let maybe_net = weight.maybe_net(); let poly = self.add_poly(recorder, weight); ( poly, - add_poly_with_nodes_intern(self, recorder, poly, nodes, layer, maybe_net), + add_poly_with_nodes_intern(self, recorder, poly, nodes, fillets, layer, maybe_net), ) } @@ -361,6 +363,7 @@ impl Layout { if self .drawing() .geometry() + // TODO: Add `.compounds()` method working on `PrimitiveIndex`. .compounds(GenericIndex::<()>::new(primitive.petgraph_index())) .next() .is_some() @@ -380,7 +383,7 @@ impl Layout { let apex = loop { // this returns None if the via is not present on this layer let (entry_label, dot) = dots.next()?; - if entry_label == CompoundEntryLabel::NotInConvexHull { + if entry_label == CompoundEntryLabel::Apex { if let Some((dot, weight)) = handle_fixed_dot(&self.drawing, dot) { if weight.layer() == active_layer { break dot; diff --git a/src/layout/poly.rs b/src/layout/poly.rs index 01c62f0..ac16fdc 100644 --- a/src/layout/poly.rs +++ b/src/layout/poly.rs @@ -49,6 +49,7 @@ pub(super) fn add_poly_with_nodes_intern( recorder: &mut LayoutEdit, poly: GenericIndex, nodes: &[PrimitiveIndex], + fillets: &[FixedDotIndex], layer: usize, maybe_net: Option, ) -> FixedDotIndex { @@ -123,19 +124,22 @@ pub(super) fn add_poly_with_nodes_intern( layout.drawing.add_to_compound( recorder, GenericIndex::<()>::new(idx.petgraph_index()), - CompoundEntryLabel::NotInConvexHull, + CompoundEntryLabel::Apex, poly_compound, ); } } + for fillet in fillets { + layout + .drawing + .add_to_compound(recorder, *fillet, CompoundEntryLabel::Fillet, poly_compound) + } + // maybe this should be a different edge label - layout.drawing.add_to_compound( - recorder, - apex, - CompoundEntryLabel::NotInConvexHull, - poly_compound, - ); + layout + .drawing + .add_to_compound(recorder, apex, CompoundEntryLabel::Apex, poly_compound); assert!(is_apex(&layout.drawing, apex)); apex @@ -166,7 +170,7 @@ impl<'a, R> PolyRef<'a, R> { .geometry() .compound_members(self.index.into()) .find_map(|(label, primitive_node)| { - if label == CompoundEntryLabel::NotInConvexHull { + if label == CompoundEntryLabel::Apex { if let PrimitiveIndex::FixedDot(dot) = primitive_node { if is_apex(self.drawing, dot) { return Some(dot); diff --git a/src/router/navmesh.rs b/src/router/navmesh.rs index 2a08285..28b6c8c 100644 --- a/src/router/navmesh.rs +++ b/src/router/navmesh.rs @@ -254,7 +254,7 @@ impl Navmesh { // The existence of a constraint edge does not (!) guarantee that this // edge exactly will be present in the triangulation. It appears that - // Spade splits a constraint edge into two if an endpoint of another + // Spade splits a constraint edge in two if an endpoint of another // constraint lies on it. // // So now we go over all the constraints and make sure that @@ -324,7 +324,7 @@ impl Navmesh { overlapping_prenavnodes_unions: &mut UnionFind>, prenavnode: PrenavmeshNodeIndex, ) { - for overlap in layout.drawing().clearance_intersectors(prenavnode.into()) { + for overlap in layout.drawing().overlapees(prenavnode.into()) { let PrimitiveIndex::FixedDot(overlapee) = overlap.1 else { continue; }; diff --git a/src/router/prenavmesh.rs b/src/router/prenavmesh.rs index 716ce9a..5dab71e 100644 --- a/src/router/prenavmesh.rs +++ b/src/router/prenavmesh.rs @@ -19,8 +19,8 @@ use crate::{ Drawing, }, geometry::{shape::AccessShape, GetLayer}, - graph::GetPetgraphIndex, - layout::Layout, + graph::{GenericIndex, GetPetgraphIndex}, + layout::{CompoundEntryLabel, Layout}, triangulation::{GetTrianvertexNodeIndex, Triangulation}, }; @@ -153,32 +153,51 @@ impl Prenavmesh { for node in layout.drawing().layer_primitive_nodes(layer) { let primitive = node.primitive(layout.drawing()); - if let Some(primitive_net) = primitive.maybe_net() { - if node == origin.into() - || node == destination.into() - || Some(primitive_net) != maybe_net - { - match node { - PrimitiveIndex::FixedDot(dot) => { - this.triangulation - .add_vertex(PrenavmeshWeight::new_from_fixed_dot(layout, dot))?; + let Some(primitive_net) = primitive.maybe_net() else { + continue; + }; + + if node == origin.into() + || node == destination.into() + || Some(primitive_net) != maybe_net + { + match node { + PrimitiveIndex::FixedDot(dot) => { + layout + .drawing() + // TODO: Add `.compounds()` method working on `PrimitiveIndex`. + .compounds(GenericIndex::<()>::new(dot.petgraph_index())) + .find(|(label, _)| *label == CompoundEntryLabel::Fillet) + .is_some(); + + // Do not add prenavnodes for primitives that have been filleted. + // For now, we do this by detecting if the primitive overlaps + // a fillet. + // TODO: This method is simplistic and will obviously result in + // false positives in some cases, so in the future, instead of this, + // create a fillet compound type and check for compound membership. + if Self::is_fixed_dot_filleted(layout, dot) { + continue; } - PrimitiveIndex::LoneLooseSeg(seg) => { - this.add_constraint(PrenavmeshConstraint::new_from_lone_loose_seg( - layout, seg, - ))?; - } - PrimitiveIndex::SeqLooseSeg(seg) => { - this.add_constraint(PrenavmeshConstraint::new_from_seq_loose_seg( - layout, seg, - ))?; - } - PrimitiveIndex::FixedBend(bend) => { - this.triangulation - .add_vertex(PrenavmeshWeight::new_from_fixed_bend(layout, bend))?; - } - _ => (), + + this.triangulation + .add_vertex(PrenavmeshWeight::new_from_fixed_dot(layout, dot))?; } + PrimitiveIndex::LoneLooseSeg(seg) => { + this.add_constraint(PrenavmeshConstraint::new_from_lone_loose_seg( + layout, seg, + ))?; + } + PrimitiveIndex::SeqLooseSeg(seg) => { + this.add_constraint(PrenavmeshConstraint::new_from_seq_loose_seg( + layout, seg, + ))?; + } + PrimitiveIndex::FixedBend(bend) => { + this.triangulation + .add_vertex(PrenavmeshWeight::new_from_fixed_bend(layout, bend))?; + } + _ => (), } } } @@ -186,36 +205,46 @@ impl Prenavmesh { for node in layout.drawing().layer_primitive_nodes(layer) { let primitive = node.primitive(layout.drawing()); - if let Some(primitive_net) = primitive.maybe_net() { - if node == origin.into() - || node == destination.into() - || Some(primitive_net) != maybe_net - { - // If you have a band that was routed from a polygonal pad, - // when you will start a new routing some of the constraint - // edges created from the loose segs of a band will - // intersect some of the constraint edges created from the - // fixed segs constituting the pad boundary. - // - // Such constraint intersections are erroneous and cause - // Spade to throw a panic at runtime. So, to prevent this - // from occuring, we iterate over the layout for the second - // time, after all the constraint edges from bands have been - // placed, and only then add constraint edges created from - // fixed segs that do not cause an intersection. - match node { - PrimitiveIndex::FixedSeg(seg) => { - let constraint = PrenavmeshConstraint::new_from_fixed_seg(layout, seg); + let Some(primitive_net) = primitive.maybe_net() else { + continue; + }; - if !this - .triangulation - .intersects_constraint(&constraint.0, &constraint.1) - { - this.add_constraint(constraint); - } + if node == origin.into() + || node == destination.into() + || Some(primitive_net) != maybe_net + { + // If you have a band that was routed from a polygonal pad, + // when you will start a new routing some of the constraint + // edges created from the loose segs of a band will + // intersect some of the constraint edges created from the + // fixed segs constituting the pad boundary. + // + // Such constraint intersections are erroneous and cause + // Spade to throw a panic at runtime. So, to prevent this + // from occuring, we iterate over the layout for the second + // time, after all the constraint edges from bands have been + // placed, and only then add constraint edges created from + // fixed segs that do not cause an intersection. + match node { + PrimitiveIndex::FixedSeg(seg) => { + let (from_dot, to_dot) = layout.drawing().primitive(seg).joints(); + + if Self::is_fixed_dot_filleted(layout, from_dot) + && Self::is_fixed_dot_filleted(layout, to_dot) + { + continue; + } + + let constraint = PrenavmeshConstraint::new_from_fixed_seg(layout, seg); + + if !this + .triangulation + .intersects_constraint(&constraint.0, &constraint.1) + { + this.add_constraint(constraint); } - _ => (), } + _ => (), } } } @@ -223,6 +252,31 @@ impl Prenavmesh { Ok(this) } + fn is_fixed_dot_filleted(layout: &Layout, dot: FixedDotIndex) -> bool { + layout + .drawing() + .compounds(GenericIndex::<()>::new(dot.petgraph_index())) + .find(|(label, _)| + // Fillets fail this test for some reason that I did not investigate, so + // I added this condition. + *label == CompoundEntryLabel::Fillet + // Exclude apices because they may overlap fillets. + || *label == CompoundEntryLabel::Apex) + .is_none() + && layout + .drawing() + .overlapees(dot.into()) + .find(|overlapee| { + layout + .drawing() + // TODO: Add `.compounds()` method working on `PrimitiveIndex`. + .compounds(GenericIndex::<()>::new(overlapee.1.petgraph_index())) + .find(|(label, _)| *label == CompoundEntryLabel::Fillet) + .is_some() + }) + .is_some() + } + fn add_constraint(&mut self, constraint: PrenavmeshConstraint) -> Result<(), InsertionError> { self.triangulation .add_constraint_edge(constraint.0, constraint.1)?; diff --git a/src/specctra/design.rs b/src/specctra/design.rs index 998e361..f6120b7 100644 --- a/src/specctra/design.rs +++ b/src/specctra/design.rs @@ -15,7 +15,7 @@ use specctra_core::math::PointWithRotation; use crate::{ board::{edit::BoardEdit, AccessMesadata, Board}, drawing::{ - dot::{FixedDotWeight, GeneralDotWeight}, + dot::{FixedDotIndex, FixedDotWeight, GeneralDotWeight}, graph::{GetMaybeNet, MakePrimitive}, primitive::MakePrimitiveShape, seg::{FixedSegWeight, GeneralSegWeight}, @@ -584,6 +584,7 @@ impl SpecctraDesign { seg3.into(), seg4.into(), ], + &[], ); } @@ -728,15 +729,16 @@ impl SpecctraDesign { .into(), ); + let fillets = Self::add_polygon_fillet_circles( + recorder, board, place, pin, coords, width, layer, maybe_net, None, flip, + ); + board.add_poly_with_nodes( recorder, SolidPolyWeight { layer, maybe_net }.into(), maybe_pin, &nodes[..], - ); - - Self::add_polygon_fillet_circles( - recorder, board, place, pin, coords, width, layer, maybe_net, None, flip, + &fillets[..], ); } @@ -751,9 +753,10 @@ impl SpecctraDesign { maybe_net: Option, _maybe_pin: Option, flip: bool, - ) { + ) -> Vec { let MIN_FIRST_CHAIN_ELEMENT_LENGTH = 100.0; let mut maybe_first_chain_segment = None; + let mut fillets = vec![]; let first_pos = Self::pos(place, pin, coords[0].x, coords[0].y, flip); let last_pos = Self::pos( @@ -790,22 +793,23 @@ impl SpecctraDesign { if index - first_chain_index >= 3 { let circle = math::fillet_circle(&first_chain_segment, &curr_segment12); - board.add_fixed_dot_infringably( + fillets.push(board.add_fixed_dot_infringably( recorder, FixedDotWeight(GeneralDotWeight { circle, layer, - maybe_net: None, // TODO. - //maybe_net, + maybe_net, }), None, - ); + )); } } maybe_first_chain_segment = Some((Line::new(curr_pos1, curr_pos2), index)); } } + + fillets } fn pos(place: PointWithRotation, pin: PointWithRotation, x: f64, y: f64, flip: bool) -> Point { diff --git a/tests/single_layer.rs b/tests/single_layer.rs index 08d3bdc..d962ced 100644 --- a/tests/single_layer.rs +++ b/tests/single_layer.rs @@ -39,7 +39,7 @@ fn test_tht_de9_to_tht_de9() { #[test] fn test_0603_breakout() { let mut autorouter = common::load_design("tests/single_layer/0603_breakout/0603_breakout.dsn"); - common::assert_navnode_count(&mut autorouter, "R1-2", "J1-2", 54); + common::assert_navnode_count(&mut autorouter, "R1-2", "J1-2", 22); let mut invoker = common::create_invoker_and_assert(autorouter); common::replay_and_assert( &mut invoker, @@ -93,7 +93,7 @@ fn test_4x_3rd_order_smd_lc_filters() { let mut autorouter = common::load_design( "tests/single_layer/4x_3rd_order_smd_lc_filters/4x_3rd_order_smd_lc_filters.dsn", ); - common::assert_navnode_count(&mut autorouter, "J1-1", "L1-1", 2062); + common::assert_navnode_count(&mut autorouter, "J1-1", "L1-1", 558); let mut invoker = common::create_invoker_and_assert(autorouter); common::replay_and_assert( &mut invoker, @@ -130,7 +130,7 @@ fn test_tht_3pin_xlr_to_tht_3pin_xlr() { fn test_vga_dac_breakout() { let mut autorouter = common::load_design("tests/single_layer/vga_dac_breakout/vga_dac_breakout.dsn"); - common::assert_navnode_count(&mut autorouter, "J1-2", "R4-1", 944); + common::assert_navnode_count(&mut autorouter, "J1-2", "R4-1", 272); let mut invoker = common::create_invoker_and_assert(autorouter); common::replay_and_assert( &mut invoker,