refactor(specctra/design): Place fillet circles, netless for now

This commit is contained in:
Mikolaj Wielgus 2025-08-30 16:52:17 +02:00
parent 4a057d3499
commit 521bb0598a
3 changed files with 113 additions and 3 deletions

View File

@ -105,6 +105,8 @@ impl Ratsnest {
for node in layout.drawing().layer_primitive_nodes(layer) { for node in layout.drawing().layer_primitive_nodes(layer) {
if let PrimitiveIndex::FixedDot(dot) = node { 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() { if layout.drawing().compounds(dot).next().is_none() {
handle_rvw( handle_rvw(
layout.drawing().primitive(dot).maybe_net(), layout.drawing().primitive(dot).maybe_net(),

View File

@ -4,9 +4,11 @@
use std::ops::Sub; use std::ops::Sub;
use geo::{point, Distance, Euclidean, Line, Point}; use geo::{point, Distance, Euclidean, Length, Line, Point};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::math;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct Circle { pub struct Circle {
pub pos: Point, pub pos: Point,
@ -129,3 +131,36 @@ pub fn intersect_circle_segment(circle: &Circle, segment: &Line) -> Vec<Point> {
v 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::<Euclidean>();
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::<Euclidean>() * radius).into()
} else {
segment1.end_point()
- (diameter_ray.delta() / diameter_ray.length::<Euclidean>() * radius).into()
};
Circle {
pos: center,
r: radius,
}
}

View File

@ -8,7 +8,8 @@
use std::collections::{btree_map::Entry as BTreeMapEntry, BTreeMap}; 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 specctra_core::math::PointWithRotation;
use crate::{ use crate::{
@ -22,7 +23,7 @@ use crate::{
}, },
geometry::{primitive::PrimitiveShape, GetLayer, GetWidth}, geometry::{primitive::PrimitiveShape, GetLayer, GetWidth},
layout::{poly::SolidPolyWeight, Layout}, layout::{poly::SolidPolyWeight, Layout},
math::Circle, math::{self, Circle},
specctra::{ specctra::{
mesadata::SpecctraMesadata, mesadata::SpecctraMesadata,
read::ListTokenizer, read::ListTokenizer,
@ -733,6 +734,78 @@ impl SpecctraDesign {
maybe_pin, maybe_pin,
&nodes[..], &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<SpecctraMesadata>,
place: PointWithRotation,
pin: PointWithRotation,
coords: &[structure::Point],
_width: f64,
layer: usize,
maybe_net: Option<usize>,
_maybe_pin: Option<String>,
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::<Euclidean>() >= 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::<Euclidean>() >= 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 { fn pos(place: PointWithRotation, pin: PointWithRotation, x: f64, y: f64, flip: bool) -> Point {