use std::fmt;
use std::io::{Read, Write};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crate::error::Result;
use crate::io::reader::decode_windows_1252;
use crate::io::writer::encode_windows_1252;
use crate::types::{Coord, ParameterCollection};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u16)]
pub enum RuleKind {
#[default]
Clearance = 0,
ParallelSegment = 1,
Width = 2,
Length = 3,
MatchedLengths = 4,
StubLength = 5,
PlaneConnect = 6,
RoutingTopology = 7,
RoutingPriority = 8,
RoutingLayers = 9,
RoutingCorners = 10,
RoutingVias = 11,
PlaneClearance = 12,
SolderMaskExpansion = 13,
PasteMaskExpansion = 14,
ShortCircuit = 15,
UnRoutedNet = 16,
ViasUnderSMD = 17,
MaximumViaCount = 18,
MinimumAnnularRing = 19,
PolygonConnect = 20,
AcuteAngle = 21,
RoomDefinition = 22,
SMDToCorner = 23,
ComponentClearance = 24,
ComponentOrientations = 25,
PermittedLayers = 26,
NetsToIgnore = 27,
SignalStimulus = 28,
OvershootFalling = 29,
OvershootRising = 30,
UndershootFalling = 31,
UndershootRising = 32,
MaxMinImpedance = 33,
SignalTopValue = 34,
SignalBaseValue = 35,
FlightTimeRising = 36,
FlightTimeFalling = 37,
LayerStack = 38,
SlopeRising = 39,
SlopeFalling = 40,
SupplyNets = 41,
HoleSize = 42,
TestPointStyle = 43,
TestPointUsage = 44,
UnconnectedPin = 45,
SMDToPlane = 46,
SMDNeckDown = 47,
LayerPairs = 48,
FanoutControl = 49,
Height = 50,
DiffPairsRouting = 51,
HoleToHoleClearance = 52,
MinimumSolderMaskSliver = 53,
SilkToSolderMaskClearance = 54,
SilkToSilkClearance = 55,
NetAntennae = 56,
AssemblyTestpoint = 57,
AssemblyTestPointUsage = 58,
SilkToBoardRegionClearance = 59,
UnpouredPolygon = 62,
Unknown(u16),
}
impl RuleKind {
pub fn from_id(id: u16) -> Self {
match id {
0 => RuleKind::Clearance,
1 => RuleKind::ParallelSegment,
2 => RuleKind::Width,
3 => RuleKind::Length,
4 => RuleKind::MatchedLengths,
5 => RuleKind::StubLength,
6 => RuleKind::PlaneConnect,
7 => RuleKind::RoutingTopology,
8 => RuleKind::RoutingPriority,
9 => RuleKind::RoutingLayers,
10 => RuleKind::RoutingCorners,
11 => RuleKind::RoutingVias,
12 => RuleKind::PlaneClearance,
13 => RuleKind::SolderMaskExpansion,
14 => RuleKind::PasteMaskExpansion,
15 => RuleKind::ShortCircuit,
16 => RuleKind::UnRoutedNet,
17 => RuleKind::ViasUnderSMD,
18 => RuleKind::MaximumViaCount,
19 => RuleKind::MinimumAnnularRing,
20 => RuleKind::PolygonConnect,
21 => RuleKind::AcuteAngle,
22 => RuleKind::RoomDefinition,
23 => RuleKind::SMDToCorner,
24 => RuleKind::ComponentClearance,
25 => RuleKind::ComponentOrientations,
26 => RuleKind::PermittedLayers,
27 => RuleKind::NetsToIgnore,
28 => RuleKind::SignalStimulus,
29 => RuleKind::OvershootFalling,
30 => RuleKind::OvershootRising,
31 => RuleKind::UndershootFalling,
32 => RuleKind::UndershootRising,
33 => RuleKind::MaxMinImpedance,
34 => RuleKind::SignalTopValue,
35 => RuleKind::SignalBaseValue,
36 => RuleKind::FlightTimeRising,
37 => RuleKind::FlightTimeFalling,
38 => RuleKind::LayerStack,
39 => RuleKind::SlopeRising,
40 => RuleKind::SlopeFalling,
41 => RuleKind::SupplyNets,
42 => RuleKind::HoleSize,
43 => RuleKind::TestPointStyle,
44 => RuleKind::TestPointUsage,
45 => RuleKind::UnconnectedPin,
46 => RuleKind::SMDToPlane,
47 => RuleKind::SMDNeckDown,
48 => RuleKind::LayerPairs,
49 => RuleKind::FanoutControl,
50 => RuleKind::Height,
51 => RuleKind::DiffPairsRouting,
52 => RuleKind::HoleToHoleClearance,
53 => RuleKind::MinimumSolderMaskSliver,
54 => RuleKind::SilkToSolderMaskClearance,
55 => RuleKind::SilkToSilkClearance,
56 => RuleKind::NetAntennae,
57 => RuleKind::AssemblyTestpoint,
58 => RuleKind::AssemblyTestPointUsage,
59 => RuleKind::SilkToBoardRegionClearance,
62 => RuleKind::UnpouredPolygon,
other => RuleKind::Unknown(other),
}
}
pub fn to_id(self) -> u16 {
match self {
RuleKind::Clearance => 0,
RuleKind::ParallelSegment => 1,
RuleKind::Width => 2,
RuleKind::Length => 3,
RuleKind::MatchedLengths => 4,
RuleKind::StubLength => 5,
RuleKind::PlaneConnect => 6,
RuleKind::RoutingTopology => 7,
RuleKind::RoutingPriority => 8,
RuleKind::RoutingLayers => 9,
RuleKind::RoutingCorners => 10,
RuleKind::RoutingVias => 11,
RuleKind::PlaneClearance => 12,
RuleKind::SolderMaskExpansion => 13,
RuleKind::PasteMaskExpansion => 14,
RuleKind::ShortCircuit => 15,
RuleKind::UnRoutedNet => 16,
RuleKind::ViasUnderSMD => 17,
RuleKind::MaximumViaCount => 18,
RuleKind::MinimumAnnularRing => 19,
RuleKind::PolygonConnect => 20,
RuleKind::AcuteAngle => 21,
RuleKind::RoomDefinition => 22,
RuleKind::SMDToCorner => 23,
RuleKind::ComponentClearance => 24,
RuleKind::ComponentOrientations => 25,
RuleKind::PermittedLayers => 26,
RuleKind::NetsToIgnore => 27,
RuleKind::SignalStimulus => 28,
RuleKind::OvershootFalling => 29,
RuleKind::OvershootRising => 30,
RuleKind::UndershootFalling => 31,
RuleKind::UndershootRising => 32,
RuleKind::MaxMinImpedance => 33,
RuleKind::SignalTopValue => 34,
RuleKind::SignalBaseValue => 35,
RuleKind::FlightTimeRising => 36,
RuleKind::FlightTimeFalling => 37,
RuleKind::LayerStack => 38,
RuleKind::SlopeRising => 39,
RuleKind::SlopeFalling => 40,
RuleKind::SupplyNets => 41,
RuleKind::HoleSize => 42,
RuleKind::TestPointStyle => 43,
RuleKind::TestPointUsage => 44,
RuleKind::UnconnectedPin => 45,
RuleKind::SMDToPlane => 46,
RuleKind::SMDNeckDown => 47,
RuleKind::LayerPairs => 48,
RuleKind::FanoutControl => 49,
RuleKind::Height => 50,
RuleKind::DiffPairsRouting => 51,
RuleKind::HoleToHoleClearance => 52,
RuleKind::MinimumSolderMaskSliver => 53,
RuleKind::SilkToSolderMaskClearance => 54,
RuleKind::SilkToSilkClearance => 55,
RuleKind::NetAntennae => 56,
RuleKind::AssemblyTestpoint => 57,
RuleKind::AssemblyTestPointUsage => 58,
RuleKind::SilkToBoardRegionClearance => 59,
RuleKind::UnpouredPolygon => 62,
RuleKind::Unknown(id) => id,
}
}
pub fn name(&self) -> &'static str {
match self {
RuleKind::Clearance => "Clearance",
RuleKind::ParallelSegment => "ParallelSegment",
RuleKind::Width => "Width",
RuleKind::Length => "Length",
RuleKind::MatchedLengths => "MatchedLengths",
RuleKind::StubLength => "StubLength",
RuleKind::PlaneConnect => "PlaneConnect",
RuleKind::RoutingTopology => "RoutingTopology",
RuleKind::RoutingPriority => "RoutingPriority",
RuleKind::RoutingLayers => "RoutingLayers",
RuleKind::RoutingCorners => "RoutingCorners",
RuleKind::RoutingVias => "RoutingVias",
RuleKind::PlaneClearance => "PlaneClearance",
RuleKind::SolderMaskExpansion => "SolderMaskExpansion",
RuleKind::PasteMaskExpansion => "PasteMaskExpansion",
RuleKind::ShortCircuit => "ShortCircuit",
RuleKind::UnRoutedNet => "UnRoutedNet",
RuleKind::ViasUnderSMD => "ViasUnderSMD",
RuleKind::MaximumViaCount => "MaximumViaCount",
RuleKind::MinimumAnnularRing => "MinimumAnnularRing",
RuleKind::PolygonConnect => "PolygonConnect",
RuleKind::AcuteAngle => "AcuteAngle",
RuleKind::RoomDefinition => "RoomDefinition",
RuleKind::SMDToCorner => "SMDToCorner",
RuleKind::ComponentClearance => "ComponentClearance",
RuleKind::ComponentOrientations => "ComponentOrientations",
RuleKind::PermittedLayers => "PermittedLayers",
RuleKind::NetsToIgnore => "NetsToIgnore",
RuleKind::SignalStimulus => "SignalStimulus",
RuleKind::OvershootFalling => "OvershootFalling",
RuleKind::OvershootRising => "OvershootRising",
RuleKind::UndershootFalling => "UndershootFalling",
RuleKind::UndershootRising => "UndershootRising",
RuleKind::MaxMinImpedance => "MaxMinImpedance",
RuleKind::SignalTopValue => "SignalTopValue",
RuleKind::SignalBaseValue => "SignalBaseValue",
RuleKind::FlightTimeRising => "FlightTimeRising",
RuleKind::FlightTimeFalling => "FlightTimeFalling",
RuleKind::LayerStack => "LayerStack",
RuleKind::SlopeRising => "SlopeRising",
RuleKind::SlopeFalling => "SlopeFalling",
RuleKind::SupplyNets => "SupplyNets",
RuleKind::HoleSize => "HoleSize",
RuleKind::TestPointStyle => "Testpoint",
RuleKind::TestPointUsage => "TestPointUsage",
RuleKind::UnconnectedPin => "UnConnectedPin",
RuleKind::SMDToPlane => "SMDToPlane",
RuleKind::SMDNeckDown => "SMDNeckDown",
RuleKind::LayerPairs => "LayerPairs",
RuleKind::FanoutControl => "FanoutControl",
RuleKind::Height => "Height",
RuleKind::DiffPairsRouting => "DiffPairsRouting",
RuleKind::HoleToHoleClearance => "HoleToHoleClearance",
RuleKind::MinimumSolderMaskSliver => "MinimumSolderMaskSliver",
RuleKind::SilkToSolderMaskClearance => "SilkToSolderMaskClearance",
RuleKind::SilkToSilkClearance => "SilkToSilkClearance",
RuleKind::NetAntennae => "NetAntennae",
RuleKind::AssemblyTestpoint => "AssemblyTestpoint",
RuleKind::AssemblyTestPointUsage => "AssemblyTestPointUsage",
RuleKind::SilkToBoardRegionClearance => "SilkToBoardRegionClearance",
RuleKind::UnpouredPolygon => "UnpouredPolygon",
RuleKind::Unknown(_) => "Unknown",
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"Clearance" => Some(RuleKind::Clearance),
"ParallelSegment" => Some(RuleKind::ParallelSegment),
"Width" => Some(RuleKind::Width),
"Length" => Some(RuleKind::Length),
"MatchedLengths" => Some(RuleKind::MatchedLengths),
"StubLength" => Some(RuleKind::StubLength),
"PlaneConnect" => Some(RuleKind::PlaneConnect),
"RoutingTopology" => Some(RuleKind::RoutingTopology),
"RoutingPriority" => Some(RuleKind::RoutingPriority),
"RoutingLayers" => Some(RuleKind::RoutingLayers),
"RoutingCorners" => Some(RuleKind::RoutingCorners),
"RoutingVias" => Some(RuleKind::RoutingVias),
"PlaneClearance" => Some(RuleKind::PlaneClearance),
"SolderMaskExpansion" => Some(RuleKind::SolderMaskExpansion),
"PasteMaskExpansion" => Some(RuleKind::PasteMaskExpansion),
"ShortCircuit" => Some(RuleKind::ShortCircuit),
"UnRoutedNet" => Some(RuleKind::UnRoutedNet),
"ViasUnderSMD" => Some(RuleKind::ViasUnderSMD),
"MaximumViaCount" => Some(RuleKind::MaximumViaCount),
"MinimumAnnularRing" => Some(RuleKind::MinimumAnnularRing),
"PolygonConnect" => Some(RuleKind::PolygonConnect),
"AcuteAngle" => Some(RuleKind::AcuteAngle),
"RoomDefinition" => Some(RuleKind::RoomDefinition),
"SMDToCorner" => Some(RuleKind::SMDToCorner),
"ComponentClearance" => Some(RuleKind::ComponentClearance),
"ComponentOrientations" => Some(RuleKind::ComponentOrientations),
"PermittedLayers" => Some(RuleKind::PermittedLayers),
"NetsToIgnore" => Some(RuleKind::NetsToIgnore),
"SignalStimulus" => Some(RuleKind::SignalStimulus),
"OvershootFalling" => Some(RuleKind::OvershootFalling),
"OvershootRising" => Some(RuleKind::OvershootRising),
"UndershootFalling" => Some(RuleKind::UndershootFalling),
"UndershootRising" => Some(RuleKind::UndershootRising),
"MaxMinImpedance" => Some(RuleKind::MaxMinImpedance),
"SignalTopValue" => Some(RuleKind::SignalTopValue),
"SignalBaseValue" => Some(RuleKind::SignalBaseValue),
"FlightTimeRising" => Some(RuleKind::FlightTimeRising),
"FlightTimeFalling" => Some(RuleKind::FlightTimeFalling),
"LayerStack" => Some(RuleKind::LayerStack),
"SlopeRising" => Some(RuleKind::SlopeRising),
"SlopeFalling" => Some(RuleKind::SlopeFalling),
"SupplyNets" => Some(RuleKind::SupplyNets),
"HoleSize" => Some(RuleKind::HoleSize),
"Testpoint" | "TestPointStyle" => Some(RuleKind::TestPointStyle),
"TestPointUsage" => Some(RuleKind::TestPointUsage),
"UnConnectedPin" | "UnconnectedPin" => Some(RuleKind::UnconnectedPin),
"SMDToPlane" => Some(RuleKind::SMDToPlane),
"SMDNeckDown" => Some(RuleKind::SMDNeckDown),
"LayerPairs" => Some(RuleKind::LayerPairs),
"FanoutControl" => Some(RuleKind::FanoutControl),
"Height" => Some(RuleKind::Height),
"DiffPairsRouting" => Some(RuleKind::DiffPairsRouting),
"HoleToHoleClearance" => Some(RuleKind::HoleToHoleClearance),
"MinimumSolderMaskSliver" => Some(RuleKind::MinimumSolderMaskSliver),
"SilkToSolderMaskClearance" => Some(RuleKind::SilkToSolderMaskClearance),
"SilkToSilkClearance" => Some(RuleKind::SilkToSilkClearance),
"NetAntennae" => Some(RuleKind::NetAntennae),
"AssemblyTestpoint" => Some(RuleKind::AssemblyTestpoint),
"AssemblyTestPointUsage" => Some(RuleKind::AssemblyTestPointUsage),
"SilkToBoardRegionClearance" => Some(RuleKind::SilkToBoardRegionClearance),
"UnpouredPolygon" => Some(RuleKind::UnpouredPolygon),
_ => None,
}
}
}
impl fmt::Display for RuleKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RuleKind::Unknown(id) => write!(f, "Unknown({})", id),
_ => write!(f, "{}", self.name()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NetScope {
#[default]
AnyNet,
InNetClass,
Net,
FromTo,
Board,
DifferentialPair,
}
impl NetScope {
pub fn parse(s: &str) -> Self {
match s {
"AnyNet" => NetScope::AnyNet,
"InNetClass" => NetScope::InNetClass,
"Net" => NetScope::Net,
"FromTo" => NetScope::FromTo,
"Board" => NetScope::Board,
"DifferentialPair" => NetScope::DifferentialPair,
_ => NetScope::AnyNet,
}
}
pub fn to_str(&self) -> &'static str {
match self {
NetScope::AnyNet => "AnyNet",
NetScope::InNetClass => "InNetClass",
NetScope::Net => "Net",
NetScope::FromTo => "FromTo",
NetScope::Board => "Board",
NetScope::DifferentialPair => "DifferentialPair",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LayerKind {
#[default]
SameLayer,
AnyLayer,
OnLayer,
InLayerClass,
}
impl LayerKind {
pub fn parse(s: &str) -> Self {
match s {
"SameLayer" => LayerKind::SameLayer,
"AnyLayer" => LayerKind::AnyLayer,
"OnLayer" => LayerKind::OnLayer,
"InLayerClass" => LayerKind::InLayerClass,
_ => LayerKind::SameLayer,
}
}
pub fn to_str(&self) -> &'static str {
match self {
LayerKind::SameLayer => "SameLayer",
LayerKind::AnyLayer => "AnyLayer",
LayerKind::OnLayer => "OnLayer",
LayerKind::InLayerClass => "InLayerClass",
}
}
}
#[derive(Debug, Clone)]
pub struct PcbRule {
pub kind: RuleKind,
pub name: String,
pub enabled: bool,
pub priority: i32,
pub comment: String,
pub unique_id: String,
pub net_scope: NetScope,
pub layer_kind: LayerKind,
pub scope1_expression: String,
pub scope2_expression: String,
pub defined_by_logical_document: bool,
pub params: ParameterCollection,
}
impl Default for PcbRule {
fn default() -> Self {
PcbRule {
kind: RuleKind::Clearance,
name: String::new(),
enabled: true,
priority: 1,
comment: String::new(),
unique_id: String::new(),
net_scope: NetScope::AnyNet,
layer_kind: LayerKind::SameLayer,
scope1_expression: "All".to_string(),
scope2_expression: "All".to_string(),
defined_by_logical_document: false,
params: ParameterCollection::new(),
}
}
}
impl PcbRule {
pub fn new(kind: RuleKind, name: &str) -> Self {
PcbRule {
kind,
name: name.to_string(),
..Default::default()
}
}
pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
let kind_id = reader.read_u16::<LittleEndian>()?;
let kind = RuleKind::from_id(kind_id);
let data_size = reader.read_u32::<LittleEndian>()? as usize;
if data_size == 0 {
return Ok(PcbRule {
kind,
..Default::default()
});
}
let mut data = vec![0u8; data_size];
reader.read_exact(&mut data)?;
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
let param_str = decode_windows_1252(&data[..end]);
let params = ParameterCollection::from_string(¶m_str);
let name = params
.get("NAME")
.map(|v| v.as_str().to_string())
.unwrap_or_default();
let enabled = params
.get("ENABLED")
.map(|v| v.as_bool_or(true))
.unwrap_or(true);
let priority = params.get("PRIORITY").map(|v| v.as_int_or(1)).unwrap_or(1);
let comment = params
.get("COMMENT")
.map(|v| v.as_str().to_string())
.unwrap_or_default();
let unique_id = params
.get("UNIQUEID")
.map(|v| v.as_str().to_string())
.unwrap_or_default();
let net_scope = params
.get("NETSCOPE")
.map(|v| NetScope::parse(v.as_str()))
.unwrap_or_default();
let layer_kind = params
.get("LAYERKIND")
.map(|v| LayerKind::parse(v.as_str()))
.unwrap_or_default();
let scope1_expression = params
.get("SCOPE1EXPRESSION")
.map(|v| v.as_str().to_string())
.unwrap_or_else(|| "All".to_string());
let scope2_expression = params
.get("SCOPE2EXPRESSION")
.map(|v| v.as_str().to_string())
.unwrap_or_else(|| "All".to_string());
let defined_by_logical_document = params
.get("DEFINEDBYLOGICALDOCUMENT")
.map(|v| v.as_bool_or(false))
.unwrap_or(false);
Ok(PcbRule {
kind,
name,
enabled,
priority,
comment,
unique_id,
net_scope,
layer_kind,
scope1_expression,
scope2_expression,
defined_by_logical_document,
params,
})
}
pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
let mut params = self.params.clone();
params.add("RULEKIND", self.kind.name());
params.add("NAME", &self.name);
params.add("ENABLED", if self.enabled { "TRUE" } else { "FALSE" });
params.add("PRIORITY", &self.priority.to_string());
params.add("COMMENT", &self.comment);
params.add("UNIQUEID", &self.unique_id);
params.add("NETSCOPE", self.net_scope.to_str());
params.add("LAYERKIND", self.layer_kind.to_str());
params.add("SCOPE1EXPRESSION", &self.scope1_expression);
params.add("SCOPE2EXPRESSION", &self.scope2_expression);
params.add(
"DEFINEDBYLOGICALDOCUMENT",
if self.defined_by_logical_document {
"TRUE"
} else {
"FALSE"
},
);
let param_str = params.to_string();
let mut data = encode_windows_1252(¶m_str);
data.push(0);
writer.write_u16::<LittleEndian>(self.kind.to_id())?;
writer.write_u32::<LittleEndian>(data.len() as u32)?;
writer.write_all(&data)?;
Ok(())
}
pub fn binary_size(&self) -> usize {
let param_str = self.params.to_string();
6 + param_str.len() + 1
}
pub fn clearance(&self) -> Option<Coord> {
self.params.get("GAP").and_then(|v| v.as_coord().ok())
}
pub fn set_clearance(&mut self, value: Coord) {
self.params.add_coord("GAP", value);
}
pub fn min_width(&self) -> Option<Coord> {
self.params.get("MINWIDTH").and_then(|v| v.as_coord().ok())
}
pub fn set_min_width(&mut self, value: Coord) {
self.params.add_coord("MINWIDTH", value);
}
pub fn max_width(&self) -> Option<Coord> {
self.params.get("MAXWIDTH").and_then(|v| v.as_coord().ok())
}
pub fn set_max_width(&mut self, value: Coord) {
self.params.add_coord("MAXWIDTH", value);
}
pub fn preferred_width(&self) -> Option<Coord> {
self.params.get("PREFWIDTH").and_then(|v| v.as_coord().ok())
}
pub fn set_preferred_width(&mut self, value: Coord) {
self.params.add_coord("PREFWIDTH", value);
}
pub fn min_hole_size(&self) -> Option<Coord> {
self.params
.get("MINHOLESIZE")
.and_then(|v| v.as_coord().ok())
}
pub fn set_min_hole_size(&mut self, value: Coord) {
self.params.add_coord("MINHOLESIZE", value);
}
pub fn max_hole_size(&self) -> Option<Coord> {
self.params
.get("MAXHOLESIZE")
.and_then(|v| v.as_coord().ok())
}
pub fn set_max_hole_size(&mut self, value: Coord) {
self.params.add_coord("MAXHOLESIZE", value);
}
pub fn solder_mask_expansion(&self) -> Option<Coord> {
self.params.get("EXPANSION").and_then(|v| v.as_coord().ok())
}
pub fn set_solder_mask_expansion(&mut self, value: Coord) {
self.params.add_coord("EXPANSION", value);
}
pub fn paste_mask_expansion(&self) -> Option<Coord> {
self.params.get("EXPANSION").and_then(|v| v.as_coord().ok())
}
pub fn set_paste_mask_expansion(&mut self, value: Coord) {
self.params.add_coord("EXPANSION", value);
}
pub fn diff_pair_min_gap(&self) -> Option<Coord> {
self.params.get("MINLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn set_diff_pair_min_gap(&mut self, value: Coord) {
self.params.add_coord("MINLIMIT", value);
}
pub fn diff_pair_max_gap(&self) -> Option<Coord> {
self.params.get("MAXLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn set_diff_pair_max_gap(&mut self, value: Coord) {
self.params.add_coord("MAXLIMIT", value);
}
pub fn diff_pair_preferred_gap(&self) -> Option<Coord> {
self.params
.get("MOSTFREQGAP")
.and_then(|v| v.as_coord().ok())
}
pub fn set_diff_pair_preferred_gap(&mut self, value: Coord) {
self.params.add_coord("MOSTFREQGAP", value);
}
pub fn diff_pair_max_uncoupled_length(&self) -> Option<Coord> {
self.params
.get("MAXUNCOUPLEDLENGTH")
.and_then(|v| v.as_coord().ok())
}
pub fn set_diff_pair_max_uncoupled_length(&mut self, value: Coord) {
self.params.add_coord("MAXUNCOUPLEDLENGTH", value);
}
pub fn via_min_diameter(&self) -> Option<Coord> {
self.params.get("MINWIDTH").and_then(|v| v.as_coord().ok())
}
pub fn set_via_min_diameter(&mut self, value: Coord) {
self.params.add_coord("MINWIDTH", value);
}
pub fn via_max_diameter(&self) -> Option<Coord> {
self.params.get("MAXWIDTH").and_then(|v| v.as_coord().ok())
}
pub fn set_via_max_diameter(&mut self, value: Coord) {
self.params.add_coord("MAXWIDTH", value);
}
pub fn via_preferred_diameter(&self) -> Option<Coord> {
self.params.get("PREFWIDTH").and_then(|v| v.as_coord().ok())
}
pub fn set_via_preferred_diameter(&mut self, value: Coord) {
self.params.add_coord("PREFWIDTH", value);
}
pub fn annular_ring(&self) -> Option<Coord> {
self.params
.get("ANNULARRING")
.and_then(|v| v.as_coord().ok())
}
pub fn set_annular_ring(&mut self, value: Coord) {
self.params.add_coord("ANNULARRING", value);
}
pub fn hole_to_hole_clearance(&self) -> Option<Coord> {
self.params.get("GAP").and_then(|v| v.as_coord().ok())
}
pub fn set_hole_to_hole_clearance(&mut self, value: Coord) {
self.params.add_coord("GAP", value);
}
pub fn min_solder_mask_sliver(&self) -> Option<Coord> {
self.params.get("MINLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn set_min_solder_mask_sliver(&mut self, value: Coord) {
self.params.add_coord("MINLIMIT", value);
}
pub fn plane_connect_style(&self) -> Option<String> {
self.params
.get("CONNECTSTYLE")
.map(|v| v.as_str().to_string())
}
pub fn set_plane_connect_style(&mut self, style: &str) {
self.params.add("CONNECTSTYLE", style);
}
pub fn thermal_relief_air_gap(&self) -> Option<Coord> {
self.params
.get("AIRGAPWIDTH")
.and_then(|v| v.as_coord().ok())
}
pub fn set_thermal_relief_air_gap(&mut self, value: Coord) {
self.params.add_coord("AIRGAPWIDTH", value);
}
pub fn thermal_relief_conductor_width(&self) -> Option<Coord> {
self.params
.get("CONDUCTORWIDTH")
.and_then(|v| v.as_coord().ok())
}
pub fn set_thermal_relief_conductor_width(&mut self, value: Coord) {
self.params.add_coord("CONDUCTORWIDTH", value);
}
pub fn thermal_relief_conductors(&self) -> Option<i32> {
self.params.get("CONDUCTORS").map(|v| v.as_int_or(4))
}
pub fn set_thermal_relief_conductors(&mut self, count: i32) {
self.params.add("CONDUCTORS", &count.to_string());
}
pub fn routing_corner_style(&self) -> Option<String> {
self.params.get("STYLE").map(|v| v.as_str().to_string())
}
pub fn set_routing_corner_style(&mut self, style: &str) {
self.params.add("STYLE", style);
}
pub fn routing_topology(&self) -> Option<String> {
self.params.get("TOPOLOGY").map(|v| v.as_str().to_string())
}
pub fn set_routing_topology(&mut self, topology: &str) {
self.params.add("TOPOLOGY", topology);
}
pub fn component_clearance(&self) -> Option<Coord> {
self.params.get("GAP").and_then(|v| v.as_coord().ok())
}
pub fn set_component_clearance(&mut self, value: Coord) {
self.params.add_coord("GAP", value);
}
pub fn max_height(&self) -> Option<Coord> {
self.params.get("MAXLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn set_max_height(&mut self, value: Coord) {
self.params.add_coord("MAXLIMIT", value);
}
pub fn min_length(&self) -> Option<Coord> {
self.params.get("MINLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn set_min_length(&mut self, value: Coord) {
self.params.add_coord("MINLIMIT", value);
}
pub fn max_length(&self) -> Option<Coord> {
self.params.get("MAXLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn set_max_length(&mut self, value: Coord) {
self.params.add_coord("MAXLIMIT", value);
}
pub fn matched_length_tolerance(&self) -> Option<Coord> {
self.params.get("TOLERANCE").and_then(|v| v.as_coord().ok())
}
pub fn set_matched_length_tolerance(&mut self, value: Coord) {
self.params.add_coord("TOLERANCE", value);
}
pub fn max_stub_length(&self) -> Option<Coord> {
self.params.get("MAXLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn set_max_stub_length(&mut self, value: Coord) {
self.params.add_coord("MAXLIMIT", value);
}
pub fn min_impedance(&self) -> Option<f64> {
self.params.get("MINLIMIT").map(|v| v.as_double_or(0.0))
}
pub fn set_min_impedance(&mut self, value: f64) {
self.params.add("MINLIMIT", &value.to_string());
}
pub fn max_impedance(&self) -> Option<f64> {
self.params.get("MAXLIMIT").map(|v| v.as_double_or(0.0))
}
pub fn set_max_impedance(&mut self, value: f64) {
self.params.add("MAXLIMIT", &value.to_string());
}
pub fn vias_under_smd_allowed(&self) -> bool {
self.params
.get("ALLOWVIAS")
.map(|v| v.as_bool_or(false))
.unwrap_or(false)
}
pub fn set_vias_under_smd_allowed(&mut self, allowed: bool) {
self.params
.add("ALLOWVIAS", if allowed { "TRUE" } else { "FALSE" });
}
pub fn max_via_count(&self) -> Option<i32> {
self.params.get("MAXVIACOUNT").map(|v| v.as_int_or(0))
}
pub fn set_max_via_count(&mut self, count: i32) {
self.params.add("MAXVIACOUNT", &count.to_string());
}
pub fn fanout_direction(&self) -> Option<String> {
self.params.get("DIRECTION").map(|v| v.as_str().to_string())
}
pub fn set_fanout_direction(&mut self, direction: &str) {
self.params.add("DIRECTION", direction);
}
pub fn fanout_style(&self) -> Option<String> {
self.params.get("STYLE").map(|v| v.as_str().to_string())
}
pub fn set_fanout_style(&mut self, style: &str) {
self.params.add("STYLE", style);
}
pub fn generic_min_limit(&self) -> Option<Coord> {
self.params.get("MINLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn generic_max_limit(&self) -> Option<Coord> {
self.params.get("MAXLIMIT").and_then(|v| v.as_coord().ok())
}
pub fn generic_gap(&self) -> Option<Coord> {
self.params.get("GAP").and_then(|v| v.as_coord().ok())
}
}
pub fn read_rules<R: Read>(reader: &mut R, data_len: usize) -> Result<Vec<PcbRule>> {
let mut rules = Vec::new();
let mut bytes_read = 0;
while bytes_read < data_len {
let start_pos = bytes_read;
if bytes_read + 6 > data_len {
break;
}
match PcbRule::read_from(reader) {
Ok(rule) => {
let rule_size = rule.binary_size();
bytes_read += rule_size;
rules.push(rule);
}
Err(e) => {
if start_pos == bytes_read {
return Err(e);
}
break;
}
}
}
Ok(rules)
}
pub fn write_rules<W: Write>(writer: &mut W, rules: &[PcbRule]) -> Result<()> {
for rule in rules {
rule.write_to(writer)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_rule_kind_roundtrip() {
let kinds = [
RuleKind::Clearance,
RuleKind::Width,
RuleKind::RoutingLayers,
RuleKind::DiffPairsRouting,
];
for kind in &kinds {
let id = kind.to_id();
let parsed = RuleKind::from_id(id);
assert_eq!(*kind, parsed);
}
}
#[test]
fn test_rule_kind_name() {
assert_eq!(RuleKind::Clearance.name(), "Clearance");
assert_eq!(RuleKind::Width.name(), "Width");
assert_eq!(RuleKind::DiffPairsRouting.name(), "DiffPairsRouting");
}
#[test]
fn test_rule_serialization() {
let mut rule = PcbRule::new(RuleKind::Clearance, "TestClearance");
rule.enabled = true;
rule.priority = 1;
rule.scope1_expression = "All".to_string();
rule.scope2_expression = "All".to_string();
rule.params.add("GAP", "10mil");
let mut buffer = Vec::new();
rule.write_to(&mut buffer).unwrap();
let mut cursor = Cursor::new(&buffer);
let parsed = PcbRule::read_from(&mut cursor).unwrap();
assert_eq!(parsed.kind, RuleKind::Clearance);
assert_eq!(parsed.name, "TestClearance");
assert!(parsed.enabled);
assert_eq!(parsed.priority, 1);
}
}