use crate::types::{Coord, CoordPoint, CoordRect};
pub const DEFAULT_GRID_MILS: f64 = 10.0;
pub const DEFAULT_COMPONENT_SPACING_MILS: f64 = 100.0;
pub const DEFAULT_WIRE_SPACING_MILS: f64 = 10.0;
#[derive(Debug, Clone, Copy)]
pub struct Grid {
pub spacing: Coord,
pub snap_enabled: bool,
}
impl Default for Grid {
fn default() -> Self {
Self {
spacing: Coord::from_mils(DEFAULT_GRID_MILS),
snap_enabled: true,
}
}
}
impl Grid {
pub fn with_mils(mils: f64) -> Self {
Self {
spacing: Coord::from_mils(mils),
snap_enabled: true,
}
}
pub fn snap(&self, point: CoordPoint) -> CoordPoint {
if !self.snap_enabled {
return point;
}
let spacing = self.spacing.to_raw();
if spacing == 0 {
return point;
}
CoordPoint {
x: Coord::from_raw(((point.x.to_raw() + spacing / 2) / spacing) * spacing),
y: Coord::from_raw(((point.y.to_raw() + spacing / 2) / spacing) * spacing),
}
}
pub fn snap_coord(&self, coord: Coord) -> Coord {
if !self.snap_enabled {
return coord;
}
let spacing = self.spacing.to_raw();
if spacing == 0 {
return coord;
}
Coord::from_raw(((coord.to_raw() + spacing / 2) / spacing) * spacing)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Orientation {
#[default]
Normal,
Rotated90,
Rotated180,
Rotated270,
MirroredX,
MirroredY,
MirroredXRotated90,
MirroredYRotated90,
}
impl Orientation {
pub fn to_altium(&self) -> i32 {
match self {
Orientation::Normal => 0,
Orientation::Rotated90 => 1,
Orientation::Rotated180 => 2,
Orientation::Rotated270 => 3,
Orientation::MirroredX => 0, Orientation::MirroredY => 0,
Orientation::MirroredXRotated90 => 1,
Orientation::MirroredYRotated90 => 1,
}
}
pub fn is_mirrored(&self) -> bool {
matches!(
self,
Orientation::MirroredX
| Orientation::MirroredY
| Orientation::MirroredXRotated90
| Orientation::MirroredYRotated90
)
}
pub fn rotation_degrees(&self) -> f64 {
match self {
Orientation::Normal | Orientation::MirroredX | Orientation::MirroredY => 0.0,
Orientation::Rotated90
| Orientation::MirroredXRotated90
| Orientation::MirroredYRotated90 => 90.0,
Orientation::Rotated180 => 180.0,
Orientation::Rotated270 => 270.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
pub fn unit_vector(&self) -> (i32, i32) {
match self {
Direction::Up => (0, 1),
Direction::Down => (0, -1),
Direction::Left => (-1, 0),
Direction::Right => (1, 0),
}
}
pub fn opposite(&self) -> Direction {
match self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
pub fn perpendicular(&self) -> [Direction; 2] {
match self {
Direction::Up | Direction::Down => [Direction::Left, Direction::Right],
Direction::Left | Direction::Right => [Direction::Up, Direction::Down],
}
}
}
#[derive(Debug, Clone)]
pub struct PlacedComponent {
pub index: usize,
pub designator: String,
pub lib_reference: String,
pub bounds: CoordRect,
pub pin_locations: Vec<PinLocation>,
}
#[derive(Debug, Clone)]
pub struct PinLocation {
pub designator: String,
pub name: String,
pub location: CoordPoint,
pub direction: Direction,
}
#[derive(Debug, Clone)]
pub struct PlacementSuggestion {
pub location: CoordPoint,
pub orientation: Orientation,
pub score: f64,
pub reason: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WireSegment {
pub start: CoordPoint,
pub end: CoordPoint,
}
impl WireSegment {
pub fn new(start: CoordPoint, end: CoordPoint) -> Self {
Self { start, end }
}
pub fn is_horizontal(&self) -> bool {
self.start.y == self.end.y
}
pub fn is_vertical(&self) -> bool {
self.start.x == self.end.x
}
pub fn bounds(&self) -> CoordRect {
CoordRect::new(self.start, self.end)
}
pub fn contains_point(&self, point: CoordPoint) -> bool {
let bounds = self.bounds();
if !bounds.contains(point) {
return false;
}
if self.is_horizontal() || self.is_vertical() {
return true;
}
let dx1 = point.x.to_raw() - self.start.x.to_raw();
let dy1 = point.y.to_raw() - self.start.y.to_raw();
let dx2 = self.end.x.to_raw() - self.start.x.to_raw();
let dy2 = self.end.y.to_raw() - self.start.y.to_raw();
let cross = dx1 as i64 * dy2 as i64 - dy1 as i64 * dx2 as i64;
cross == 0
}
}
#[derive(Debug, Clone, Default)]
pub struct RoutingPath {
pub segments: Vec<WireSegment>,
pub junctions: Vec<CoordPoint>,
}
impl RoutingPath {
pub fn new() -> Self {
Self::default()
}
pub fn add_segment(&mut self, segment: WireSegment) {
self.segments.push(segment);
}
pub fn vertices(&self) -> Vec<CoordPoint> {
if self.segments.is_empty() {
return Vec::new();
}
let mut vertices = vec![self.segments[0].start];
for segment in &self.segments {
vertices.push(segment.end);
}
vertices
}
pub fn total_length(&self) -> Coord {
let mut length = Coord::ZERO;
for segment in &self.segments {
let dx = (segment.end.x - segment.start.x).abs();
let dy = (segment.end.y - segment.start.y).abs();
length = length + dx + dy; }
length
}
}
#[derive(Debug, Clone)]
pub struct ValidationError {
pub kind: ValidationErrorKind,
pub message: String,
pub location: Option<CoordPoint>,
pub components: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationErrorKind {
ComponentOverlap,
WireCrossesComponent,
UnconnectedPin,
MissingJunction,
DuplicateDesignator,
InvalidNetConnection,
OffGrid,
}
#[derive(Debug, Clone)]
pub enum EditOperation {
AddComponent { index: usize, designator: String },
RemoveComponent { index: usize, designator: String },
MoveComponent {
index: usize,
from: CoordPoint,
to: CoordPoint,
},
AddWire { index: usize },
RemoveWire { index: usize },
AddJunction { index: usize, location: CoordPoint },
AddNetLabel { index: usize, net_name: String },
AddPowerPort { index: usize, net_name: String },
Batch { operations: Vec<EditOperation> },
}