From 521bb0598a5257f5d3b3a8a1d349472a00ee1339 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Sat, 30 Aug 2025 16:52:17 +0200 Subject: [PATCH] refactor(specctra/design): Place fillet circles, netless for now --- src/autorouter/ratsnest.rs | 2 + src/math/circle.rs | 37 +++++++++++++++++- src/specctra/design.rs | 77 +++++++++++++++++++++++++++++++++++++- 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/src/autorouter/ratsnest.rs b/src/autorouter/ratsnest.rs index 51412e8..1352a84 100644 --- a/src/autorouter/ratsnest.rs +++ b/src/autorouter/ratsnest.rs @@ -105,6 +105,8 @@ impl Ratsnest { for node in layout.drawing().layer_primitive_nodes(layer) { if let PrimitiveIndex::FixedDot(dot) = node { + // Dots that are parts of polys are ignored because ratlines + // should only go to their centerpoints. if layout.drawing().compounds(dot).next().is_none() { handle_rvw( layout.drawing().primitive(dot).maybe_net(), diff --git a/src/math/circle.rs b/src/math/circle.rs index c5ee570..ea5ed1b 100644 --- a/src/math/circle.rs +++ b/src/math/circle.rs @@ -4,9 +4,11 @@ use std::ops::Sub; -use geo::{point, Distance, Euclidean, Line, Point}; +use geo::{point, Distance, Euclidean, Length, Line, Point}; use serde::{Deserialize, Serialize}; +use crate::math; + #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub struct Circle { pub pos: Point, @@ -129,3 +131,36 @@ pub fn intersect_circle_segment(circle: &Circle, segment: &Line) -> Vec { v } + +/// Find the filleting circle of line segments `segment1` and `segment2`. +pub fn fillet_circle(segment1: &Line, segment2: &Line) -> Circle { + // Turn segment1 delta vector counterclockwisely by 90 degrees. + let diameter_ray = Line::new( + segment1.end_point(), + point! {x: segment1.end_point().x() - segment1.delta().y, y: segment1.end_point().y() + segment1.delta().x}, + ); + + // Radius is the distance from the diameter line to segment2.start_point(). + let radius = (diameter_ray.delta().y * segment2.start_point().x() + - diameter_ray.delta().x * segment2.start_point().y() + + diameter_ray.end_point().x() * diameter_ray.start_point().y() + - diameter_ray.end_point().y() * diameter_ray.start_point().x()) + .abs() + / diameter_ray.length::(); + + let center = + if math::perp_dot_product(Point::from(segment1.delta()), Point::from(segment2.delta())) + >= 0.0 + { + segment1.end_point() + + (diameter_ray.delta() / diameter_ray.length::() * radius).into() + } else { + segment1.end_point() + - (diameter_ray.delta() / diameter_ray.length::() * radius).into() + }; + + Circle { + pos: center, + r: radius, + } +} diff --git a/src/specctra/design.rs b/src/specctra/design.rs index ffbbfeb..998e361 100644 --- a/src/specctra/design.rs +++ b/src/specctra/design.rs @@ -8,7 +8,8 @@ use std::collections::{btree_map::Entry as BTreeMapEntry, BTreeMap}; -use geo::{Point, Rotate}; +use geo::{Euclidean, Length, Line, Point, Rotate}; +use itertools::Itertools; use specctra_core::math::PointWithRotation; use crate::{ @@ -22,7 +23,7 @@ use crate::{ }, geometry::{primitive::PrimitiveShape, GetLayer, GetWidth}, layout::{poly::SolidPolyWeight, Layout}, - math::Circle, + math::{self, Circle}, specctra::{ mesadata::SpecctraMesadata, read::ListTokenizer, @@ -733,6 +734,78 @@ impl SpecctraDesign { maybe_pin, &nodes[..], ); + + Self::add_polygon_fillet_circles( + recorder, board, place, pin, coords, width, layer, maybe_net, None, flip, + ); + } + + fn add_polygon_fillet_circles( + recorder: &mut BoardEdit, + board: &mut Board, + place: PointWithRotation, + pin: PointWithRotation, + coords: &[structure::Point], + _width: f64, + layer: usize, + maybe_net: Option, + _maybe_pin: Option, + flip: bool, + ) { + let MIN_FIRST_CHAIN_ELEMENT_LENGTH = 100.0; + let mut maybe_first_chain_segment = None; + + let first_pos = Self::pos(place, pin, coords[0].x, coords[0].y, flip); + let last_pos = Self::pos( + place, + pin, + coords.last().unwrap().x, + coords.last().unwrap().y, + flip, + ); + + let last_first_segment = Line::new(last_pos, first_pos); + + if last_first_segment.length::() >= MIN_FIRST_CHAIN_ELEMENT_LENGTH { + maybe_first_chain_segment = Some((Line::new(last_pos, first_pos), 0)); + } + + for (index, coord_triple) in coords + .iter() + .circular_tuple_windows::<(_, _, _)>() + .enumerate() + { + let curr_pos0 = Self::pos(place, pin, coord_triple.0.x, coord_triple.0.y, flip); + let curr_pos1 = Self::pos(place, pin, coord_triple.1.x, coord_triple.1.y, flip); + let curr_pos2 = Self::pos(place, pin, coord_triple.2.x, coord_triple.2.y, flip); + let curr_segment01 = Line::new(curr_pos0, curr_pos1); + let curr_segment12 = Line::new(curr_pos1, curr_pos2); + + if math::angle_between(curr_segment01.delta().into(), curr_segment12.delta().into()) + .abs() + > 30.0_f64.to_radians() + || curr_segment12.length::() >= MIN_FIRST_CHAIN_ELEMENT_LENGTH + { + if let Some((first_chain_segment, first_chain_index)) = maybe_first_chain_segment { + if index - first_chain_index >= 3 { + let circle = math::fillet_circle(&first_chain_segment, &curr_segment12); + + board.add_fixed_dot_infringably( + recorder, + FixedDotWeight(GeneralDotWeight { + circle, + layer, + maybe_net: None, // TODO. + //maybe_net, + }), + None, + ); + } + } + + maybe_first_chain_segment = Some((Line::new(curr_pos1, curr_pos2), index)); + } + } } fn pos(place: PointWithRotation, pin: PointWithRotation, x: f64, y: f64, flip: bool) -> Point {