// SPDX-FileCopyrightText: 2024 Topola contributors // // SPDX-License-Identifier: MIT //! Module for handling Polygon properties use enum_dispatch::enum_dispatch; use geo::{ algorithm::{Centroid, ConvexHull}, IsConvex, LineString, Point, Polygon, }; use crate::{ drawing::{ dot::{FixedDotIndex, FixedDotWeight, GeneralDotWeight}, graph::{GetMaybeNet, PrimitiveIndex}, primitive::GetLimbs, rules::AccessRules, seg::SegIndex, Drawing, }, geometry::{compound::ManageCompounds, GetLayer, GetSetPos}, graph::{GenericIndex, GetIndex, MakeRef}, layout::{CompoundEntryLabel, CompoundWeight, Layout, LayoutEdit}, math::Circle, }; #[enum_dispatch] pub trait MakePolygon { fn shape(&self) -> Polygon; } #[derive(Debug)] pub struct PolyRef<'a, R> { pub index: GenericIndex, drawing: &'a Drawing, } impl<'a, R: 'a> MakeRef<'a, Layout> for GenericIndex { type Output = PolyRef<'a, R>; fn ref_(&self, layout: &'a Layout) -> PolyRef<'a, R> { PolyRef::new(*self, layout.drawing()) } } pub(super) fn add_poly_with_nodes_intern( layout: &mut Layout, recorder: &mut LayoutEdit, poly: GenericIndex, nodes: &[PrimitiveIndex], fillets: &[FixedDotIndex], layer: usize, maybe_net: Option, ) -> FixedDotIndex { #[derive(Clone, Copy)] struct Rto { pt: Point, idx: PrimitiveIndex, } impl rstar::RTreeObject for Rto { type Envelope = rstar::AABB<[f64; 2]>; fn envelope(&self) -> rstar::AABB<[f64; 2]> { rstar::AABB::from_point([self.pt.x(), self.pt.y()]) } } impl rstar::PointDistance for Rto { fn distance_2(&self, point: &[f64; 2]) -> f64 { let d_x = self.pt.0.x - point[0]; let d_y = self.pt.0.y - point[1]; d_x * d_x + d_y * d_y } } let poly_compound = poly.into(); let mut shape = Vec::with_capacity((nodes.len() + 1) / 2); // create a temporary RTree to speed up lookups from O(n²) to O(n log n) let mut temp_rtree = rstar::RTree::::new(); for &idx in nodes { layout.drawing.add_to_compound( recorder, GenericIndex::<()>::new(idx.index()), CompoundEntryLabel::Normal, poly_compound, ); let PrimitiveIndex::FixedDot(dot) = idx else { continue; }; // we don't have an apex yet let pt = layout.drawing.geometry().dot_weight(dot.into()).pos(); shape.push(pt.0); temp_rtree.insert(Rto { pt, idx }); } let shape = Polygon::new(LineString(shape), Vec::new()).into_inner().0; let apex = layout.add_fixed_dot_infringably( recorder, FixedDotWeight(GeneralDotWeight { circle: Circle { pos: shape.centroid().unwrap(), r: 100.0, }, layer, maybe_net, }), ); if !shape.is_convex() { // compute the convex hull let convex_hull_exterior = shape.convex_hull().into_inner().0; for pt in convex_hull_exterior { // note that the convex hull should be a strict subset of the points // on the exterior of `shape` assert!(temp_rtree.pop_nearest_neighbor(&[pt.x, pt.y]).is_some()); } // mark all elements which aren't in the convex hull for Rto { idx, .. } in temp_rtree { layout.drawing.add_to_compound( recorder, GenericIndex::<()>::new(idx.index()), 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::Apex, poly_compound); assert!(is_apex(&layout.drawing, apex)); apex } fn is_apex( drawing: &Drawing, dot: FixedDotIndex, ) -> bool { !drawing .primitive(dot) .segs() .iter() .any(|seg| matches!(seg, SegIndex::Fixed(..))) && drawing.primitive(dot).bends().is_empty() } impl<'a, R> PolyRef<'a, R> { pub fn new( index: GenericIndex, drawing: &'a Drawing, ) -> Self { Self { index, drawing } } pub fn apex(&self) -> FixedDotIndex { self.drawing .geometry() .compound_members(self.index.into()) .find_map(|(label, primitive_node)| { if label == CompoundEntryLabel::Apex { if let PrimitiveIndex::FixedDot(dot) = primitive_node { if is_apex(self.drawing, dot) { return Some(dot); } } } None }) .unwrap() } } impl GetLayer for PolyRef<'_, R> { fn layer(&self) -> usize { if let CompoundWeight::Poly(weight) = self.drawing.compound_weight(self.index.into()) { weight.layer() } else { unreachable!(); } } } impl GetMaybeNet for PolyRef<'_, R> { fn maybe_net(&self) -> Option { self.drawing.compound_weight(self.index.into()).maybe_net() } } impl MakePolygon for PolyRef<'_, R> { fn shape(&self) -> Polygon { Polygon::new( LineString( self.drawing .geometry() .compound_members(self.index.into()) .filter_map(|(_label, primitive_node)| { let PrimitiveIndex::FixedDot(dot) = primitive_node else { return None; }; if is_apex(self.drawing, dot) { None } else { Some(self.drawing.geometry().dot_weight(dot.into()).pos().0) } }) .collect(), ), vec![], ) } } #[enum_dispatch(GetLayer, GetMaybeNet)] #[derive(Debug, Clone, Copy, PartialEq)] pub enum PolyWeight { Solid(SolidPolyWeight), Pour(PourPolyWeight), } impl From> for GenericIndex { fn from(poly: GenericIndex) -> Self { GenericIndex::::new(poly.index()) } } #[derive(Debug, Clone, Copy, PartialEq)] pub struct SolidPolyWeight { pub layer: usize, pub maybe_net: Option, } impl GetLayer for SolidPolyWeight { fn layer(&self) -> usize { self.layer } } impl GetMaybeNet for SolidPolyWeight { fn maybe_net(&self) -> Option { self.maybe_net } } impl From> for GenericIndex { fn from(poly: GenericIndex) -> Self { GenericIndex::::new(poly.index()) } } #[derive(Debug, Clone, Copy, PartialEq)] pub struct PourPolyWeight { pub layer: usize, pub maybe_net: Option, } impl GetLayer for PourPolyWeight { fn layer(&self) -> usize { self.layer } } impl GetMaybeNet for PourPolyWeight { fn maybe_net(&self) -> Option { self.maybe_net } } impl From> for GenericIndex { fn from(poly: GenericIndex) -> Self { GenericIndex::::new(poly.index()) } }