added local fork of gerber-types-rs

This commit is contained in:
Hlars 2024-08-23 12:02:52 +02:00
parent 7913541282
commit ff45b5ef03
18 changed files with 3439 additions and 0 deletions

3
gerber-types-rs/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target
Cargo.lock
*.swp

View File

@ -0,0 +1,44 @@
# Changelog
This project follows semantic versioning.
Possible log types:
- `[added]` for new features.
- `[changed]` for changes in existing functionality.
- `[deprecated]` for once-stable features removed in upcoming releases.
- `[removed]` for deprecated features removed in this release.
- `[fixed]` for any bug fixes.
- `[security]` to invite users to upgrade in case of vulnerabilities.
### v0.3.0 (2022-07-05)
- [fixed] Fix whitespace in G04 comment serialization (#33)
- [changed] Updated dependencies
- [changed] A fixed MSRV was dropped
Thanks @NemoAndrea for contributions!
### v0.2.0 (2021-01-06)
This release requires at least Rust 1.31 (2018 edition).
- [added] Implement constructors for `Circle` and `Rectangular`
- [added] Derive Clone for all structs and enums (#16)
- [added] Derive PartialEq and Eq where possible
- [added] Implement `From<FunctionCode>` and `From<ExtendedCode>` for Command
- [added] Impl `From<>` for Command, FunctionCode and ExtendedCode
- [added] New builder-style constructors (#22)
- [added] Support for more FileFunction and FileAttribute variants (#26, #30)
- [changed] Derive Copy for some trivial enums
- [changed] Create new internal `PartialGerberCode` trait (#18)
- [changed] Split up code into more modules
- [changed] Upgraded all dependencies
- [changed] Require Rust 1.31+
Thanks @connorkuehl and @twitchyliquid64 for contributions!
### v0.1.1 (2017-06-10)
- First crates.io release

View File

@ -0,0 +1,24 @@
[package]
name = "gerber-types"
version = "0.3.0"
documentation = "https://docs.rs/gerber-types/"
repository = "https://github.com/dbrgn/gerber-types-rs"
license = "MIT OR Apache-2.0"
authors = ["Danilo Bargen <mail@dbrgn.ch>"]
description = "Types and code generation for Gerber files (RS-274X)."
readme = "README.md"
keywords = ["gerber", "pcb", "rs274x", "cad", "cam"]
include = [
"**/*.rs",
"Cargo.toml",
"README.md",
"LICENSE-*",
]
edition = "2018"
[dependencies]
chrono = "0.4"
conv = "0.3"
num-rational = "0.4"
thiserror = "1"
uuid = "1"

View File

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,19 @@
Copyright (c) 2016-2021 Danilo Bargen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47
gerber-types-rs/README.md Normal file
View File

@ -0,0 +1,47 @@
# Rust Gerber Library
[![Build status][build-status-badge]][build-status]
[![Crates.io][crates-io-badge]][crates-io]
- [Docs (released)](https://docs.rs/gerber-types/)
This crate implements the basic building blocks of Gerber X2 (compatible with
Gerber RS-274X) code. It focusses on the low level types (to be used like an
AST) and code generation and does not do any semantic checking.
For example, you can use an aperture without defining it. This will generate
syntactically valid but semantially invalid Gerber code, but this module won't
complain.
The plan is to write a high-level wrapper library on top of this. Early drafts
[are in progress](https://github.com/dbrgn/gerber-rs) but the design isn't
fixed yet.
Current Gerber X2 spec: https://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf
## Example
You can find an example in the [`examples`
directory](https://github.com/dbrgn/gerber-types-rs/blob/main/examples/polarities-apertures.rs).
It's still quite verbose, the goal is to make the API a bit more ergonomic in
the future. (This library has a low-level focus though, so it will never get a
high-level API. That is the task of other libraries.)
To generate Gerber code for that example:
$ cargo run --example polarities-apertures
## License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
<!-- Badges -->
[build-status]: https://github.com/dbrgn/gerber-types-rs/actions?query=workflow%3ACI
[build-status-badge]: https://img.shields.io/github/workflow/status/dbrgn/gerber-types-rs/CI/main
[crates-io]: https://crates.io/crates/gerber-types
[crates-io-badge]: https://img.shields.io/crates/v/gerber-types.svg

View File

@ -0,0 +1,24 @@
# Releasing
Set variables:
$ export VERSION=X.Y.Z
$ export GPG_KEY=EA456E8BAF0109429583EED83578F667F2F3A5FA
Update version numbers:
$ vim -p Cargo.toml
Update changelog:
$ vim CHANGELOG.md
Commit & tag:
$ git commit -S${GPG_KEY} -m "Release v${VERSION}"
$ git tag -s -u ${GPG_KEY} v${VERSION} -m "Version ${VERSION}"
Publish:
$ cargo publish
$ git push && git push --tags

View File

@ -0,0 +1,410 @@
//! Attributes.
use std::io::Write;
use chrono::{DateTime, Utc};
use uuid::Uuid;
use crate::errors::GerberResult;
use crate::traits::PartialGerberCode;
// FileAttribute
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileAttribute {
Part(Part),
FileFunction(FileFunction),
FilePolarity(FilePolarity),
GenerationSoftware(GenerationSoftware),
CreationDate(DateTime<Utc>),
ProjectId {
id: String,
guid: Uuid,
revision: String,
},
Md5(String),
UserDefined {
name: String,
value: Vec<String>,
},
}
impl<W: Write> PartialGerberCode<W> for FileAttribute {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
FileAttribute::Part(ref part) => {
write!(writer, "Part,")?;
part.serialize_partial(writer)?;
}
FileAttribute::FileFunction(ref function) => {
write!(writer, "FileFunction,")?;
match function {
FileFunction::Copper {
ref layer,
ref pos,
ref copper_type,
} => {
write!(writer, "Copper,L{},", layer)?;
pos.serialize_partial(writer)?;
if let Some(ref t) = *copper_type {
write!(writer, ",")?;
t.serialize_partial(writer)?;
}
}
FileFunction::Profile(ref plating) => {
write!(writer, "Profile,")?;
plating.serialize_partial(writer)?;
}
FileFunction::Soldermask { ref pos, ref index } => {
write!(writer, "Soldermask,")?;
pos.serialize_partial(writer)?;
if let Some(ref i) = index {
write!(writer, ",{}", *i)?;
}
}
FileFunction::Legend { ref pos, ref index } => {
write!(writer, "Legend,")?;
pos.serialize_partial(writer)?;
if let Some(ref i) = index {
write!(writer, ",{}", *i)?;
}
}
_ => unimplemented!(),
}
}
FileAttribute::GenerationSoftware(ref gs) => {
write!(writer, "GenerationSoftware,")?;
gs.serialize_partial(writer)?;
}
FileAttribute::FilePolarity(ref p) => {
write!(writer, "FilePolarity,")?;
p.serialize_partial(writer)?;
}
FileAttribute::Md5(ref hash) => write!(writer, "MD5,{}", hash)?,
_ => unimplemented!(),
};
Ok(())
}
}
// ApertureAttribute
#[derive(Debug, Clone, PartialEq)]
pub enum ApertureAttribute {
ApertureFunction(ApertureFunction),
DrillTolerance { plus: f64, minus: f64 },
}
// Part
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Part {
/// Single PCB
Single,
/// A.k.a. customer panel, assembly panel, shipping panel, biscuit
Array,
/// A.k.a. working panel, production panel
FabricationPanel,
/// A test coupon
Coupon,
/// None of the above
Other(String),
}
impl<W: Write> PartialGerberCode<W> for Part {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
Part::Single => write!(writer, "Single")?,
Part::Array => write!(writer, "Array")?,
Part::FabricationPanel => write!(writer, "FabricationPanel")?,
Part::Coupon => write!(writer, "Coupon")?,
Part::Other(ref description) => write!(writer, "Other,{}", description)?,
};
Ok(())
}
}
// Position
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Position {
Top,
Bottom,
}
impl<W: Write> PartialGerberCode<W> for Position {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
Position::Top => write!(writer, "Top")?,
Position::Bottom => write!(writer, "Bot")?,
};
Ok(())
}
}
// ExtendedPosition
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtendedPosition {
Top,
Inner,
Bottom,
}
impl<W: Write> PartialGerberCode<W> for ExtendedPosition {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
ExtendedPosition::Top => write!(writer, "Top")?,
ExtendedPosition::Inner => write!(writer, "Inr")?,
ExtendedPosition::Bottom => write!(writer, "Bot")?,
};
Ok(())
}
}
// CopperType
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CopperType {
Plane,
Signal,
Mixed,
Hatched,
}
impl<W: Write> PartialGerberCode<W> for CopperType {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
CopperType::Plane => write!(writer, "Plane")?,
CopperType::Signal => write!(writer, "Signal")?,
CopperType::Mixed => write!(writer, "Mixed")?,
CopperType::Hatched => write!(writer, "Hatched")?,
};
Ok(())
}
}
// Drill
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Drill {
ThroughHole,
Blind,
Buried,
}
// DrillRouteType
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DrillRouteType {
Drill,
Route,
Mixed,
}
// Profile
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Profile {
Plated,
NonPlated,
}
impl<W: Write> PartialGerberCode<W> for Profile {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
Profile::Plated => write!(writer, "P")?,
Profile::NonPlated => write!(writer, "NP")?,
};
Ok(())
}
}
// FileFunction
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileFunction {
Copper {
layer: i32,
pos: ExtendedPosition,
copper_type: Option<CopperType>,
},
Soldermask {
pos: Position,
index: Option<i32>,
},
Legend {
pos: Position,
index: Option<i32>,
},
Goldmask {
pos: Position,
index: Option<i32>,
},
Silvermask {
pos: Position,
index: Option<i32>,
},
Tinmask {
pos: Position,
index: Option<i32>,
},
Carbonmask {
pos: Position,
index: Option<i32>,
},
Peelablesoldermask {
pos: Position,
index: Option<i32>,
},
Glue {
pos: Position,
index: Option<i32>,
},
Viatenting(Position),
Viafill,
Heatsink(Position),
Paste(Position),
KeepOut(Position),
Pads(Position),
Scoring(Position),
Plated {
from_layer: i32,
to_layer: i32,
drill: Drill,
label: Option<DrillRouteType>,
},
NonPlated {
from_layer: i32,
to_layer: i32,
drill: Drill,
label: Option<DrillRouteType>,
},
Profile(Profile),
Drillmap,
FabricationDrawing,
ArrayDrawing,
AssemblyDrawing(Position),
Drawing(String),
Other(String),
}
// FilePolarity
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FilePolarity {
Positive,
Negative,
}
impl<W: Write> PartialGerberCode<W> for FilePolarity {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
FilePolarity::Positive => write!(writer, "Positive")?,
FilePolarity::Negative => write!(writer, "Negative")?,
};
Ok(())
}
}
// GenerationSoftware
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GenerationSoftware {
pub vendor: String,
pub application: String,
pub version: Option<String>,
}
impl GenerationSoftware {
pub fn new<S: Into<String>>(vendor: S, application: S, version: Option<S>) -> Self {
GenerationSoftware {
vendor: vendor.into(),
application: application.into(),
version: version.map(|s| s.into()),
}
}
}
impl<W: Write> PartialGerberCode<W> for GenerationSoftware {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match self.version {
Some(ref v) => write!(writer, "{},{},{}", self.vendor, self.application, v)?,
None => write!(writer, "{},{}", self.vendor, self.application)?,
};
Ok(())
}
}
// ApertureFunction
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApertureFunction {
// Only valid for layers with file function plated or non-plated
ViaDrill,
BackDrill,
ComponentDrill {
press_fit: Option<bool>, // TODO is this bool?
},
CastellatedDrill,
MechanicalDrill {
function: Option<DrillFunction>,
},
Slot,
CutOut,
Cavity,
OtherDrill(String),
// Only valid for layers with file function copper
ComponentPad {
press_fit: Option<bool>, // TODO is this bool?
},
SmdPad(SmdPadType),
BgaPad(SmdPadType),
ConnectorPad,
HeatsinkPad,
ViaPad,
TestPad,
CastellatedPad,
FiducialPad(FiducialScope),
ThermalReliefPad,
WasherPad,
AntiPad,
OtherPad(String),
Conductor,
NonConductor,
CopperBalancing,
Border,
OtherCopper(String),
// All layers
Profile,
NonMaterial,
Material,
Other(String),
}
// DrillFunction
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DrillFunction {
BreakOut,
Tooling,
Other,
}
// SmdPadType
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SmdPadType {
CopperDefined,
SoldermaskDefined,
}
// FiducialScope
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FiducialScope {
Global,
Local,
}

View File

@ -0,0 +1,106 @@
//! Generic code generation, e.g. implementations of `PartialGerberCode` for
//! bool or Vec<G: GerberCode>.
use std::io::Write;
use crate::errors::GerberResult;
use crate::traits::{GerberCode, PartialGerberCode};
use crate::types::*;
/// Implement `PartialGerberCode` for booleans
impl<W: Write> PartialGerberCode<W> for bool {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
if *self {
write!(writer, "1")?;
} else {
write!(writer, "0")?;
};
Ok(())
}
}
/// Implement `GerberCode` for Vectors of types that are `GerberCode`.
impl<W: Write, G: GerberCode<W>> GerberCode<W> for Vec<G> {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
for item in self.iter() {
item.serialize(writer)?;
}
Ok(())
}
}
/// Implement `PartialGerberCode` for `Option<T: PartialGerberCode>`
impl<T: PartialGerberCode<W>, W: Write> PartialGerberCode<W> for Option<T> {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
if let Some(ref val) = *self {
val.serialize_partial(writer)?;
}
Ok(())
}
}
impl<W: Write> GerberCode<W> for Command {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
Command::FunctionCode(ref code) => code.serialize(writer)?,
Command::ExtendedCode(ref code) => code.serialize(writer)?,
};
Ok(())
}
}
impl<W: Write> GerberCode<W> for FunctionCode {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
FunctionCode::DCode(ref code) => code.serialize(writer)?,
FunctionCode::GCode(ref code) => code.serialize(writer)?,
FunctionCode::MCode(ref code) => code.serialize(writer)?,
};
Ok(())
}
}
impl<W: Write> GerberCode<W> for ExtendedCode {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
ExtendedCode::CoordinateFormat(ref cf) => {
writeln!(writer, "%FSLAX{0}{1}Y{0}{1}*%", cf.integer, cf.decimal)?;
}
ExtendedCode::Unit(ref unit) => {
write!(writer, "%MO")?;
unit.serialize_partial(writer)?;
writeln!(writer, "*%")?;
}
ExtendedCode::ApertureDefinition(ref def) => {
write!(writer, "%ADD")?;
def.serialize_partial(writer)?;
writeln!(writer, "*%")?;
}
ExtendedCode::ApertureMacro(ref am) => {
write!(writer, "%")?;
am.serialize_partial(writer)?;
writeln!(writer, "%")?;
}
ExtendedCode::LoadPolarity(ref polarity) => {
write!(writer, "%LP")?;
polarity.serialize_partial(writer)?;
writeln!(writer, "*%")?;
}
ExtendedCode::StepAndRepeat(ref sar) => {
write!(writer, "%SR")?;
sar.serialize_partial(writer)?;
writeln!(writer, "*%")?;
}
ExtendedCode::FileAttribute(ref attr) => {
write!(writer, "%TF.")?;
attr.serialize_partial(writer)?;
writeln!(writer, "*%")?;
}
ExtendedCode::DeleteAttribute(ref attr) => {
writeln!(writer, "%TD{}*%", attr)?;
}
_ => unimplemented!(),
};
Ok(())
}
}

View File

@ -0,0 +1,479 @@
//! Types for Gerber code generation related to coordinates.
use std::convert::{From, Into};
use std::i64;
use std::io::Write;
use std::num::FpCategory;
use conv::TryFrom;
use num_rational::Ratio;
use crate::errors::{GerberError, GerberResult};
use crate::traits::PartialGerberCode;
// Helper macros
/// Automatically implement `PartialGerberCode` trait for struct types
/// that are based on `x` and `y` attributes.
macro_rules! impl_xy_partial_gerbercode {
($class:ty, $x:expr, $y: expr) => {
impl<W: Write> PartialGerberCode<W> for $class {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
if let Some(x) = self.x {
write!(writer, "{}{}", $x, x.gerber(&self.format)?)?;
}
if let Some(y) = self.y {
write!(writer, "{}{}", $y, y.gerber(&self.format)?)?;
}
Ok(())
}
}
};
}
// Types
/// The coordinate format specifies the number of integer and decimal places in
/// a coordinate number. For example, the `24` format specifies 2 integer and 4
/// decimal places. The number of decimal places must be 4, 5 or 6. The number
/// of integer places must be not more than 6. Thus the longest representable
/// coordinate number is `nnnnnn.nnnnnn`.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CoordinateFormat {
pub integer: u8,
pub decimal: u8,
}
impl CoordinateFormat {
pub fn new(integer: u8, decimal: u8) -> Self {
CoordinateFormat { integer, decimal }
}
}
/// Coordinate numbers are integers conforming to the rules set by the FS
/// command.
///
/// Coordinate numbers are integers. Explicit decimal points are not allowed.
///
/// A coordinate number must have at least one character. Zero therefore must
/// be encoded as `0`.
///
/// The value is stored as a 64 bit integer with 6 decimal places.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CoordinateNumber {
nano: i64,
}
impl CoordinateNumber {
pub fn new(nano: i64) -> Self {
CoordinateNumber { nano }
}
}
const DECIMAL_PLACES_CHARS: u8 = 6;
const DECIMAL_PLACES_FACTOR: i64 = 1_000_000;
impl TryFrom<f64> for CoordinateNumber {
type Err = GerberError;
fn try_from(val: f64) -> Result<Self, Self::Err> {
match val.classify() {
FpCategory::Nan => Err(GerberError::ConversionError("Value is NaN".into())),
FpCategory::Infinite => Err(GerberError::ConversionError("Value is infinite".into())),
FpCategory::Zero | FpCategory::Subnormal => Ok(CoordinateNumber { nano: 0 }),
FpCategory::Normal => {
let multiplied = val * DECIMAL_PLACES_FACTOR as f64;
if (multiplied > i64::MAX as f64) || (multiplied < i64::MIN as f64) {
Err(GerberError::ConversionError(
"Value is out of bounds".into(),
))
} else {
Ok(CoordinateNumber {
nano: multiplied as i64,
})
}
}
}
}
}
impl Into<f64> for CoordinateNumber {
fn into(self) -> f64 {
(self.nano as f64) / DECIMAL_PLACES_FACTOR as f64
}
}
macro_rules! impl_from_integer {
($class:ty) => {
impl From<$class> for CoordinateNumber {
fn from(val: $class) -> Self {
CoordinateNumber {
nano: val as i64 * DECIMAL_PLACES_FACTOR,
}
}
}
};
}
// These are the types we can safely multiply with DECIMAL_PLACES_FACTOR
// without the risk of an overflow.
impl_from_integer!(i8);
impl_from_integer!(i16);
impl_from_integer!(i32);
impl_from_integer!(u8);
impl_from_integer!(u16);
impl CoordinateNumber {
pub fn gerber(&self, format: &CoordinateFormat) -> Result<String, GerberError> {
if format.decimal > DECIMAL_PLACES_CHARS {
return Err(GerberError::CoordinateFormatError(
"Invalid precision: Too high!".into(),
));
}
if self.nano.abs() >= 10_i64.pow((format.integer + DECIMAL_PLACES_CHARS) as u32) {
return Err(GerberError::CoordinateFormatError(
"Number is too large for chosen format!".into(),
));
}
let divisor: i64 = 10_i64.pow((DECIMAL_PLACES_CHARS - format.decimal) as u32);
let number: i64 = Ratio::new(self.nano, divisor).round().to_integer();
Ok(number.to_string())
}
}
/// Coordinates are part of an operation.
///
/// Coordinates are modal. If an X is omitted, the X coordinate of the
/// current point is used. Similar for Y.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Coordinates {
pub x: Option<CoordinateNumber>,
pub y: Option<CoordinateNumber>,
pub format: CoordinateFormat,
}
impl Coordinates {
pub fn new<T, U>(x: T, y: U, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
U: Into<CoordinateNumber>,
{
Coordinates {
x: Some(x.into()),
y: Some(y.into()),
format,
}
}
pub fn at_x<T>(x: T, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
{
Coordinates {
x: Some(x.into()),
y: None,
format,
}
}
pub fn at_y<T>(y: T, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
{
Coordinates {
x: None,
y: Some(y.into()),
format,
}
}
}
impl_xy_partial_gerbercode!(Coordinates, "X", "Y");
/// Coordinate offsets can be used for interpolate operations in circular
/// interpolation mode.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoordinateOffset {
pub x: Option<CoordinateNumber>,
pub y: Option<CoordinateNumber>,
pub format: CoordinateFormat,
}
impl CoordinateOffset {
pub fn new<T, U>(x: T, y: U, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
U: Into<CoordinateNumber>,
{
CoordinateOffset {
x: Some(x.into()),
y: Some(y.into()),
format,
}
}
pub fn at_x<T>(x: T, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
{
CoordinateOffset {
x: Some(x.into()),
y: None,
format,
}
}
pub fn at_y<T>(y: T, format: CoordinateFormat) -> Self
where
T: Into<CoordinateNumber>,
{
CoordinateOffset {
x: None,
y: Some(y.into()),
format,
}
}
}
impl_xy_partial_gerbercode!(CoordinateOffset, "I", "J");
#[cfg(test)]
mod test {
use super::*;
use std::f64;
use std::io::BufWriter;
use conv::TryFrom;
use crate::traits::PartialGerberCode;
#[test]
/// Test integer to coordinate number conversion
fn test_from_i8() {
let a = CoordinateNumber { nano: 13000000 };
let b = CoordinateNumber::from(13i8);
assert_eq!(a, b);
let c = CoordinateNumber { nano: -99000000 };
let d = CoordinateNumber::from(-99i8);
assert_eq!(c, d);
}
#[test]
/// Test integer to coordinate number conversion
fn test_from_i32() {
let a = CoordinateNumber { nano: 13000000 };
let b = CoordinateNumber::from(13);
assert_eq!(a, b);
let c = CoordinateNumber { nano: -998000000 };
let d = CoordinateNumber::from(-998);
assert_eq!(c, d);
}
#[test]
/// Test float to coordinate number conversion
fn test_try_from_f64_success() {
let a = CoordinateNumber { nano: 1375000i64 };
let b = CoordinateNumber::try_from(1.375f64).unwrap();
assert_eq!(a, b);
let c = CoordinateNumber {
nano: 123456888888i64,
};
let d = CoordinateNumber::try_from(123456.888888f64).unwrap();
assert_eq!(c, d);
let e = CoordinateNumber { nano: 0i64 };
let f = CoordinateNumber::try_from(0f64).unwrap();
assert_eq!(e, f);
let g = CoordinateNumber { nano: -12345678 };
let h = CoordinateNumber::try_from(-12.345678).unwrap();
assert_eq!(g, h);
}
#[test]
/// Test failing float to coordinate number conversion
fn test_try_from_f64_fail() {
let cn1 = CoordinateNumber::try_from(f64::NAN);
assert!(cn1.is_err());
let cn2 = CoordinateNumber::try_from(f64::INFINITY);
assert!(cn2.is_err());
let cn3 = CoordinateNumber::try_from(f64::MAX - 1.0);
assert!(cn3.is_err());
let cn4 = CoordinateNumber::try_from(f64::MIN + 1.0);
assert!(cn4.is_err());
}
#[test]
/// Test coordinate number to float conversion
fn test_into_f64() {
let a: f64 = CoordinateNumber { nano: 1375000i64 }.into();
let b = 1.375f64;
assert_eq!(a, b);
let c: f64 = CoordinateNumber {
nano: 123456888888i64,
}
.into();
let d = 123456.888888f64;
assert_eq!(c, d);
let e: f64 = CoordinateNumber { nano: 0i64 }.into();
let f = 0f64;
assert_eq!(e, f);
}
#[test]
/// Test the coordinate number constructor creates correct
/// coordinate numbers.
fn test_coordinate_number_new() {
let nano = 5;
let cn1 = CoordinateNumber::new(nano);
assert_eq!(cn1.nano, nano);
}
#[test]
/// Test coordinate number to string conversion when it's 0
fn test_formatted_zero() {
let cf1 = CoordinateFormat::new(6, 6);
let cf2 = CoordinateFormat::new(2, 4);
let a = CoordinateNumber { nano: 0 }.gerber(&cf1).unwrap();
let b = CoordinateNumber { nano: 0 }.gerber(&cf2).unwrap();
assert_eq!(a, "0".to_string());
assert_eq!(b, "0".to_string());
}
#[test]
/// Test coordinate number to string conversion when the decimal part is 0
fn test_formatted_decimal_zero() {
let cf1 = CoordinateFormat::new(6, 6);
let cf2 = CoordinateFormat::new(2, 4);
let a = CoordinateNumber { nano: 10000000 }.gerber(&cf1).unwrap();
let b = CoordinateNumber { nano: 20000000 }.gerber(&cf2).unwrap();
assert_eq!(a, "10000000".to_string());
assert_eq!(b, "200000".to_string());
}
#[test]
/// Test coordinate number to string conversion
fn test_formatted_65() {
let cf = CoordinateFormat::new(6, 5);
let d = CoordinateNumber { nano: 123456789012 }.gerber(&cf).unwrap();
assert_eq!(d, "12345678901".to_string());
}
#[test]
/// Test coordinate number to string conversion
fn test_formatted_54() {
let cf = CoordinateFormat::new(5, 4);
let d = CoordinateNumber { nano: 12345678901 }.gerber(&cf).unwrap();
assert_eq!(d, "123456789".to_string());
}
#[test]
/// Test coordinate number to string conversion failure
fn test_formatted_number_too_large() {
let cf = CoordinateFormat::new(4, 5);
let d = CoordinateNumber { nano: 12345000000 }.gerber(&cf);
assert!(d.is_err());
}
#[test]
/// Test coordinate number to string conversion failure
fn test_formatted_negative_number_too_large() {
let cf = CoordinateFormat::new(4, 5);
let d = CoordinateNumber { nano: -12345000000 }.gerber(&cf);
assert!(d.is_err());
}
#[test]
/// Test coordinate number to string conversion (rounding of decimal part)
fn test_formatted_44_rounding() {
let cf = CoordinateFormat::new(4, 4);
let d = CoordinateNumber { nano: 1234432199 }.gerber(&cf).unwrap();
assert_eq!(d, "12344322".to_string());
}
#[test]
/// Test negative coordinate number to string conversion
fn test_formatted_negative_rounding() {
let cf = CoordinateFormat::new(6, 4);
let d = CoordinateNumber {
nano: -123456789099,
}
.gerber(&cf)
.unwrap();
assert_eq!(d, "-1234567891".to_string());
}
#[test]
fn test_coordinates_into() {
let cf = CoordinateFormat::new(2, 4);
let c1 = Coordinates::new(CoordinateNumber::from(1), CoordinateNumber::from(2), cf);
let c2 = Coordinates::new(1, 2, cf);
assert_eq!(c1, c2);
}
#[test]
fn test_coordinates_into_mixed() {
let cf = CoordinateFormat::new(2, 4);
let c1 = Coordinates::new(CoordinateNumber::from(1), 2, cf);
let c2 = Coordinates::new(1, 2, cf);
assert_eq!(c1, c2);
}
#[test]
fn test_coordinates() {
macro_rules! assert_coords {
($coords:expr, $result:expr) => {{
assert_partial_code!($coords, $result);
}};
}
let cf44 = CoordinateFormat::new(4, 4);
let cf46 = CoordinateFormat::new(4, 6);
assert_coords!(Coordinates::new(10, 20, cf44), "X100000Y200000");
assert_coords!(
Coordinates {
x: None,
y: None,
format: cf44
},
""
); // TODO should we catch this?
assert_coords!(Coordinates::at_x(10, cf44), "X100000");
assert_coords!(Coordinates::at_y(20, cf46), "Y20000000");
assert_coords!(Coordinates::new(0, -400, cf44), "X0Y-4000000");
}
#[test]
fn test_offset() {
macro_rules! assert_coords {
($coords:expr, $result:expr) => {{
assert_partial_code!($coords, $result);
}};
}
let cf44 = CoordinateFormat::new(4, 4);
let cf55 = CoordinateFormat::new(5, 5);
let cf66 = CoordinateFormat::new(6, 6);
assert_coords!(CoordinateOffset::new(10, 20, cf44), "I100000J200000");
assert_coords!(
CoordinateOffset {
x: None,
y: None,
format: cf44
},
""
); // TODO should we catch this?
assert_coords!(CoordinateOffset::at_x(10, cf66), "I10000000");
assert_coords!(CoordinateOffset::at_y(20, cf55), "J2000000");
assert_coords!(CoordinateOffset::new(0, -400, cf44), "I0J-4000000");
}
}

View File

@ -0,0 +1,39 @@
//! Error types used in the gerber-types library.
use std::io::Error as IoError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GerberError {
#[error("Conversion between two types failed: {0}")]
ConversionError(String),
#[error("Bad coordinate format: {0}")]
CoordinateFormatError(String),
#[error("A value is out of range: {0}")]
RangeError(String),
#[error("Required data is missing: {0}")]
MissingDataError(String),
#[error("I/O error during code generation")]
IoError(#[from] IoError),
}
pub type GerberResult<T> = Result<T, GerberError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_msg() {
let err = GerberError::CoordinateFormatError("Something went wrong".into());
assert_eq!(
err.to_string(),
"Bad coordinate format: Something went wrong"
);
}
}

View File

@ -0,0 +1,315 @@
//! Extended code types.
use std::io::Write;
use crate::errors::GerberResult;
use crate::traits::PartialGerberCode;
// Unit
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Unit {
Inches,
Millimeters,
}
impl<W: Write> PartialGerberCode<W> for Unit {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
Unit::Millimeters => write!(writer, "MM")?,
Unit::Inches => write!(writer, "IN")?,
};
Ok(())
}
}
// ApertureDefinition
#[derive(Debug, Clone, PartialEq)]
pub struct ApertureDefinition {
pub code: i32,
pub aperture: Aperture,
}
impl ApertureDefinition {
pub fn new(code: i32, aperture: Aperture) -> Self {
ApertureDefinition { code, aperture }
}
}
impl<W: Write> PartialGerberCode<W> for ApertureDefinition {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
write!(writer, "{}", self.code)?;
self.aperture.serialize_partial(writer)?;
Ok(())
}
}
// Aperture
#[derive(Debug, Clone, PartialEq)]
pub enum Aperture {
Circle(Circle),
Rectangle(Rectangular),
Obround(Rectangular),
Polygon(Polygon),
Other(String),
}
impl<W: Write> PartialGerberCode<W> for Aperture {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
Aperture::Circle(ref circle) => {
write!(writer, "C,")?;
circle.serialize_partial(writer)?;
}
Aperture::Rectangle(ref rectangular) => {
write!(writer, "R,")?;
rectangular.serialize_partial(writer)?;
}
Aperture::Obround(ref rectangular) => {
write!(writer, "O,")?;
rectangular.serialize_partial(writer)?;
}
Aperture::Polygon(ref polygon) => {
write!(writer, "P,")?;
polygon.serialize_partial(writer)?;
}
Aperture::Other(ref string) => write!(writer, "{}", string)?,
};
Ok(())
}
}
// Circle
#[derive(Debug, Clone, PartialEq)]
pub struct Circle {
pub diameter: f64,
pub hole_diameter: Option<f64>,
}
impl Circle {
pub fn new(diameter: f64) -> Self {
Circle {
diameter,
hole_diameter: None,
}
}
pub fn with_hole(diameter: f64, hole_diameter: f64) -> Self {
Circle {
diameter,
hole_diameter: Some(hole_diameter),
}
}
}
impl<W: Write> PartialGerberCode<W> for Circle {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match self.hole_diameter {
Some(hole_diameter) => {
write!(writer, "{}X{}", self.diameter, hole_diameter)?;
}
None => write!(writer, "{}", self.diameter)?,
};
Ok(())
}
}
// Rectangular
#[derive(Debug, Clone, PartialEq)]
pub struct Rectangular {
pub x: f64,
pub y: f64,
pub hole_diameter: Option<f64>,
}
impl Rectangular {
pub fn new(x: f64, y: f64) -> Self {
Rectangular {
x,
y,
hole_diameter: None,
}
}
pub fn with_hole(x: f64, y: f64, hole_diameter: f64) -> Self {
Rectangular {
x,
y,
hole_diameter: Some(hole_diameter),
}
}
}
impl<W: Write> PartialGerberCode<W> for Rectangular {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match self.hole_diameter {
Some(hole_diameter) => write!(writer, "{}X{}X{}", self.x, self.y, hole_diameter)?,
None => write!(writer, "{}X{}", self.x, self.y)?,
};
Ok(())
}
}
// Polygon
#[derive(Debug, Clone, PartialEq)]
pub struct Polygon {
pub diameter: f64,
pub vertices: u8, // 3--12
pub rotation: Option<f64>,
pub hole_diameter: Option<f64>,
}
impl Polygon {
pub fn new(diameter: f64, vertices: u8) -> Self {
Polygon {
diameter,
vertices,
rotation: None,
hole_diameter: None,
}
}
pub fn with_rotation(mut self, angle: f64) -> Self {
self.rotation = Some(angle);
self
}
pub fn with_diameter(mut self, diameter: f64) -> Self {
self.diameter = diameter;
self
}
}
impl<W: Write> PartialGerberCode<W> for Polygon {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match (self.rotation, self.hole_diameter) {
(Some(rot), Some(hd)) => {
write!(writer, "{}X{}X{}X{}", self.diameter, self.vertices, rot, hd)?
}
(Some(rot), None) => write!(writer, "{}X{}X{}", self.diameter, self.vertices, rot)?,
(None, Some(hd)) => write!(writer, "{}X{}X0X{}", self.diameter, self.vertices, hd)?,
(None, None) => write!(writer, "{}X{}", self.diameter, self.vertices)?,
};
Ok(())
}
}
// Polarity
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Polarity {
Clear,
Dark,
}
impl<W: Write> PartialGerberCode<W> for Polarity {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
Polarity::Clear => write!(writer, "C")?,
Polarity::Dark => write!(writer, "D")?,
};
Ok(())
}
}
// StepAndRepeat
#[derive(Debug, Clone, PartialEq)]
pub enum StepAndRepeat {
Open {
repeat_x: u32,
repeat_y: u32,
distance_x: f64,
distance_y: f64,
},
Close,
}
impl<W: Write> PartialGerberCode<W> for StepAndRepeat {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
match *self {
StepAndRepeat::Open {
repeat_x: rx,
repeat_y: ry,
distance_x: dx,
distance_y: dy,
} => write!(writer, "X{}Y{}I{}J{}", rx, ry, dx, dy)?,
StepAndRepeat::Close => {}
};
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_aperture_definition_new() {
let ad1 = ApertureDefinition::new(10, Aperture::Circle(Circle::new(3.0)));
let ad2 = ApertureDefinition {
code: 10,
aperture: Aperture::Circle(Circle::new(3.0)),
};
assert_eq!(ad1, ad2);
}
#[test]
fn test_rectangular_new() {
let r1 = Rectangular::new(2.0, 3.0);
let r2 = Rectangular {
x: 2.0,
y: 3.0,
hole_diameter: None,
};
assert_eq!(r1, r2);
}
#[test]
fn test_rectangular_with_hole() {
let r1 = Rectangular::with_hole(3.0, 2.0, 1.0);
let r2 = Rectangular {
x: 3.0,
y: 2.0,
hole_diameter: Some(1.0),
};
assert_eq!(r1, r2);
}
#[test]
fn test_circle_new() {
let c1 = Circle::new(3.0);
let c2 = Circle {
diameter: 3.0,
hole_diameter: None,
};
assert_eq!(c1, c2);
}
#[test]
fn test_circle_with_hole() {
let c1 = Circle::with_hole(3.0, 1.0);
let c2 = Circle {
diameter: 3.0,
hole_diameter: Some(1.0),
};
assert_eq!(c1, c2);
}
#[test]
fn test_polygon_new() {
let p1 = Polygon::new(3.0, 4).with_rotation(45.0);
let p2 = Polygon {
diameter: 3.0,
vertices: 4,
rotation: Some(45.0),
hole_diameter: None,
};
assert_eq!(p1, p2);
}
}

View File

@ -0,0 +1,143 @@
//! Function code types.
use std::io::Write;
use crate::coordinates::{CoordinateOffset, Coordinates};
use crate::errors::GerberResult;
use crate::traits::{GerberCode, PartialGerberCode};
// DCode
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DCode {
Operation(Operation),
SelectAperture(i32),
}
impl<W: Write> GerberCode<W> for DCode {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
DCode::Operation(ref operation) => operation.serialize(writer)?,
DCode::SelectAperture(code) => writeln!(writer, "D{}*", code)?,
};
Ok(())
}
}
// GCode
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GCode {
InterpolationMode(InterpolationMode),
RegionMode(bool),
QuadrantMode(QuadrantMode),
Comment(String),
}
impl<W: Write> GerberCode<W> for GCode {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
GCode::InterpolationMode(ref mode) => mode.serialize(writer)?,
GCode::RegionMode(enabled) => {
if enabled {
writeln!(writer, "G36*")?;
} else {
writeln!(writer, "G37*")?;
}
}
GCode::QuadrantMode(ref mode) => mode.serialize(writer)?,
GCode::Comment(ref comment) => writeln!(writer, "G04 {}*", comment)?,
};
Ok(())
}
}
// MCode
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MCode {
EndOfFile,
}
impl<W: Write> GerberCode<W> for MCode {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
MCode::EndOfFile => writeln!(writer, "M02*")?,
};
Ok(())
}
}
// Operation
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Operation {
/// D01 Command
Interpolate(Coordinates, Option<CoordinateOffset>),
/// D02 Command
Move(Coordinates),
/// D03 Command
Flash(Coordinates),
}
impl<W: Write> GerberCode<W> for Operation {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
Operation::Interpolate(ref coords, ref offset) => {
coords.serialize_partial(writer)?;
offset.serialize_partial(writer)?;
writeln!(writer, "D01*")?;
}
Operation::Move(ref coords) => {
coords.serialize_partial(writer)?;
writeln!(writer, "D02*")?;
}
Operation::Flash(ref coords) => {
coords.serialize_partial(writer)?;
writeln!(writer, "D03*")?;
}
};
Ok(())
}
}
// InterpolationMode
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterpolationMode {
Linear,
ClockwiseCircular,
CounterclockwiseCircular,
}
impl<W: Write> GerberCode<W> for InterpolationMode {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
InterpolationMode::Linear => writeln!(writer, "G01*")?,
InterpolationMode::ClockwiseCircular => writeln!(writer, "G02*")?,
InterpolationMode::CounterclockwiseCircular => writeln!(writer, "G03*")?,
};
Ok(())
}
}
// QuadrantMode
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuadrantMode {
Single,
Multi,
}
impl<W: Write> GerberCode<W> for QuadrantMode {
fn serialize(&self, writer: &mut W) -> GerberResult<()> {
match *self {
QuadrantMode::Single => writeln!(writer, "G74*")?,
QuadrantMode::Multi => writeln!(writer, "G75*")?,
};
Ok(())
}
}
#[cfg(test)]
mod test {}

289
gerber-types-rs/src/lib.rs Normal file
View File

@ -0,0 +1,289 @@
//! # Gerber commands
//!
//! This crate implements the basic building blocks of Gerber (RS-274X, aka
//! Extended Gerber version 2) code. It focusses on the low level types and does
//! not do any semantic checking.
//!
//! For example, you can use an aperture without defining it. This will
//! generate syntactically valid but semantially invalid Gerber code, but this
//! module won't complain.
//!
//! ## Traits: GerberCode and PartialGerberCode
//!
//! There are two main traits that are used for code generation:
//!
//! - [`GerberCode`](trait.GerberCode.html) generates a full Gerber code line,
//! terminated with a newline character.
//! - `PartialGerberCode` (internal only) generates Gerber representation of a
//! value, but does not represent a full line of code.
#![allow(clippy::new_without_default)]
#[cfg(test)]
#[macro_use]
mod test_macros;
mod attributes;
mod codegen;
mod coordinates;
mod errors;
mod extended_codes;
mod function_codes;
mod macros;
mod traits;
mod types;
pub use crate::attributes::*;
#[allow(unused)]
pub use crate::codegen::*;
pub use crate::coordinates::*;
pub use crate::errors::*;
pub use crate::extended_codes::*;
pub use crate::function_codes::*;
pub use crate::macros::*;
pub use crate::traits::GerberCode;
pub use crate::types::*;
#[cfg(test)]
mod test {
use std::io::BufWriter;
use super::traits::PartialGerberCode;
use super::*;
#[test]
fn test_serialize() {
//! The serialize method of the GerberCode trait should generate strings.
let comment = GCode::Comment("testcomment".to_string());
assert_code!(comment, "G04 testcomment*\n");
}
#[test]
fn test_vec_serialize() {
//! A `Vec<T: GerberCode>` should also implement `GerberCode`.
let mut v = Vec::new();
v.push(GCode::Comment("comment 1".to_string()));
v.push(GCode::Comment("another one".to_string()));
assert_code!(v, "G04 comment 1*\nG04 another one*\n");
}
#[test]
fn test_command_serialize() {
//! A `Command` should implement `GerberCode`
let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment("comment".to_string())));
assert_code!(c, "G04 comment*\n");
}
#[test]
fn test_interpolation_mode() {
let mut commands = Vec::new();
let c1 = GCode::InterpolationMode(InterpolationMode::Linear);
let c2 = GCode::InterpolationMode(InterpolationMode::ClockwiseCircular);
let c3 = GCode::InterpolationMode(InterpolationMode::CounterclockwiseCircular);
commands.push(c1);
commands.push(c2);
commands.push(c3);
assert_code!(commands, "G01*\nG02*\nG03*\n");
}
#[test]
fn test_region_mode() {
let mut commands = Vec::new();
commands.push(GCode::RegionMode(true));
commands.push(GCode::RegionMode(false));
assert_code!(commands, "G36*\nG37*\n");
}
#[test]
fn test_quadrant_mode() {
let mut commands = Vec::new();
commands.push(GCode::QuadrantMode(QuadrantMode::Single));
commands.push(GCode::QuadrantMode(QuadrantMode::Multi));
assert_code!(commands, "G74*\nG75*\n");
}
#[test]
fn test_end_of_file() {
let c = MCode::EndOfFile;
assert_code!(c, "M02*\n");
}
#[test]
fn test_operation_interpolate() {
let cf = CoordinateFormat::new(2, 5);
let c1 = Operation::Interpolate(
Coordinates::new(1, 2, cf),
Some(CoordinateOffset::new(5, 10, cf)),
);
assert_code!(c1, "X100000Y200000I500000J1000000D01*\n");
let c2 = Operation::Interpolate(Coordinates::at_y(-2, CoordinateFormat::new(4, 4)), None);
assert_code!(c2, "Y-20000D01*\n");
let cf = CoordinateFormat::new(4, 4);
let c3 = Operation::Interpolate(
Coordinates::at_x(1, cf),
Some(CoordinateOffset::at_y(2, cf)),
);
assert_code!(c3, "X10000J20000D01*\n");
}
#[test]
fn test_operation_move() {
let c = Operation::Move(Coordinates::new(23, 42, CoordinateFormat::new(6, 4)));
assert_code!(c, "X230000Y420000D02*\n");
}
#[test]
fn test_operation_flash() {
let c = Operation::Flash(Coordinates::new(23, 42, CoordinateFormat::new(4, 4)));
assert_code!(c, "X230000Y420000D03*\n");
}
#[test]
fn test_select_aperture() {
let c1 = DCode::SelectAperture(10);
assert_code!(c1, "D10*\n");
let c2 = DCode::SelectAperture(2147483647);
assert_code!(c2, "D2147483647*\n");
}
#[test]
fn test_coordinate_format() {
let c = ExtendedCode::CoordinateFormat(CoordinateFormat::new(2, 5));
assert_code!(c, "%FSLAX25Y25*%\n");
}
#[test]
fn test_unit() {
let c1 = ExtendedCode::Unit(Unit::Millimeters);
let c2 = ExtendedCode::Unit(Unit::Inches);
assert_code!(c1, "%MOMM*%\n");
assert_code!(c2, "%MOIN*%\n");
}
#[test]
fn test_aperture_circle_definition() {
let ad1 = ApertureDefinition {
code: 10,
aperture: Aperture::Circle(Circle {
diameter: 4.0,
hole_diameter: Some(2.0),
}),
};
let ad2 = ApertureDefinition {
code: 11,
aperture: Aperture::Circle(Circle {
diameter: 4.5,
hole_diameter: None,
}),
};
assert_partial_code!(ad1, "10C,4X2");
assert_partial_code!(ad2, "11C,4.5");
}
#[test]
fn test_aperture_rectangular_definition() {
let ad1 = ApertureDefinition {
code: 12,
aperture: Aperture::Rectangle(Rectangular {
x: 1.5,
y: 2.25,
hole_diameter: Some(3.8),
}),
};
let ad2 = ApertureDefinition {
code: 13,
aperture: Aperture::Rectangle(Rectangular {
x: 1.0,
y: 1.0,
hole_diameter: None,
}),
};
let ad3 = ApertureDefinition {
code: 14,
aperture: Aperture::Obround(Rectangular {
x: 2.0,
y: 4.5,
hole_diameter: None,
}),
};
assert_partial_code!(ad1, "12R,1.5X2.25X3.8");
assert_partial_code!(ad2, "13R,1X1");
assert_partial_code!(ad3, "14O,2X4.5");
}
#[test]
fn test_aperture_polygon_definition() {
let ad1 = ApertureDefinition {
code: 15,
aperture: Aperture::Polygon(Polygon {
diameter: 4.5,
vertices: 3,
rotation: None,
hole_diameter: None,
}),
};
let ad2 = ApertureDefinition {
code: 16,
aperture: Aperture::Polygon(Polygon {
diameter: 5.0,
vertices: 4,
rotation: Some(30.6),
hole_diameter: None,
}),
};
let ad3 = ApertureDefinition {
code: 17,
aperture: Aperture::Polygon(Polygon {
diameter: 5.5,
vertices: 5,
rotation: None,
hole_diameter: Some(1.8),
}),
};
assert_partial_code!(ad1, "15P,4.5X3");
assert_partial_code!(ad2, "16P,5X4X30.6");
assert_partial_code!(ad3, "17P,5.5X5X0X1.8");
}
#[test]
fn test_polarity_serialize() {
let d = ExtendedCode::LoadPolarity(Polarity::Dark);
let c = ExtendedCode::LoadPolarity(Polarity::Clear);
assert_code!(d, "%LPD*%\n");
assert_code!(c, "%LPC*%\n");
}
#[test]
fn test_step_and_repeat_serialize() {
let o = ExtendedCode::StepAndRepeat(StepAndRepeat::Open {
repeat_x: 2,
repeat_y: 3,
distance_x: 2.0,
distance_y: 3.0,
});
let c = ExtendedCode::StepAndRepeat(StepAndRepeat::Close);
assert_code!(o, "%SRX2Y3I2J3*%\n");
assert_code!(c, "%SR*%\n");
}
#[test]
fn test_delete_attribute_serialize() {
let d = ExtendedCode::DeleteAttribute("foo".into());
assert_code!(d, "%TDfoo*%\n");
}
#[test]
fn test_file_attribute_serialize() {
let part = ExtendedCode::FileAttribute(FileAttribute::Part(Part::Other("foo".into())));
assert_code!(part, "%TF.Part,Other,foo*%\n");
let gensw1 = ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(
GenerationSoftware::new("Vend0r", "superpcb", None),
));
assert_code!(gensw1, "%TF.GenerationSoftware,Vend0r,superpcb*%\n");
let gensw2 = ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(
GenerationSoftware::new("Vend0r", "superpcb", Some("1.2.3")),
));
assert_code!(gensw2, "%TF.GenerationSoftware,Vend0r,superpcb,1.2.3*%\n");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
/// Assert that serializing the object generates
/// the specified gerber code.
macro_rules! assert_code {
($obj:expr, $expected:expr) => {
let mut buf = BufWriter::new(Vec::new());
$obj.serialize(&mut buf)
.expect("Could not generate Gerber code");
let bytes = buf.into_inner().unwrap();
let code = String::from_utf8(bytes).unwrap();
assert_eq!(&code, $expected);
};
}
/// Assert that partially serializing the object generates
/// the specified gerber code.
macro_rules! assert_partial_code {
($obj:expr, $expected:expr) => {
let mut buf = BufWriter::new(Vec::new());
$obj.serialize_partial(&mut buf)
.expect("Could not generate Gerber code");
let bytes = buf.into_inner().unwrap();
let code = String::from_utf8(bytes).unwrap();
assert_eq!(&code, $expected);
};
}

View File

@ -0,0 +1,19 @@
//! Traits used in gerber-types.
use std::io::Write;
use crate::GerberResult;
/// All types that implement this trait can be converted to a complete Gerber
/// Code line. Generated code should end with a newline.
pub trait GerberCode<W: Write> {
fn serialize(&self, writer: &mut W) -> GerberResult<()>;
}
/// All types that implement this trait can be converted to a Gerber Code
/// representation.
///
/// This is a crate-internal trait.
pub trait PartialGerberCode<W: Write> {
fn serialize_partial(&self, writer: &mut W) -> GerberResult<()>;
}

View File

@ -0,0 +1,188 @@
//! Types for Gerber code generation.
//!
//! All types are stateless, meaning that they contain all information in order
//! to render themselves. This means for example that each `Coordinates`
//! instance contains a reference to the coordinate format to be used.
use std::convert::From;
use crate::attributes;
use crate::coordinates;
use crate::extended_codes;
use crate::function_codes;
use crate::macros;
// Helper macros
macro_rules! impl_from {
($from:ty, $target:ty, $variant:expr) => {
impl From<$from> for $target {
fn from(val: $from) -> Self {
$variant(val)
}
}
};
}
// Root type
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
FunctionCode(FunctionCode),
ExtendedCode(ExtendedCode),
}
impl_from!(FunctionCode, Command, Command::FunctionCode);
impl_from!(ExtendedCode, Command, Command::ExtendedCode);
macro_rules! impl_command_fromfrom {
($from:ty, $inner:path) => {
impl From<$from> for Command {
fn from(val: $from) -> Self {
Command::from($inner(val))
}
}
};
}
// Main categories
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FunctionCode {
DCode(function_codes::DCode),
GCode(function_codes::GCode),
MCode(function_codes::MCode),
}
impl_from!(function_codes::DCode, FunctionCode, FunctionCode::DCode);
impl_from!(function_codes::GCode, FunctionCode, FunctionCode::GCode);
impl_from!(function_codes::MCode, FunctionCode, FunctionCode::MCode);
impl_command_fromfrom!(function_codes::DCode, FunctionCode::from);
impl_command_fromfrom!(function_codes::GCode, FunctionCode::from);
impl_command_fromfrom!(function_codes::MCode, FunctionCode::from);
#[derive(Debug, Clone, PartialEq)]
pub enum ExtendedCode {
/// FS
CoordinateFormat(coordinates::CoordinateFormat),
/// MO
Unit(extended_codes::Unit),
/// AD
ApertureDefinition(extended_codes::ApertureDefinition),
/// AM
ApertureMacro(macros::ApertureMacro),
/// LP
LoadPolarity(extended_codes::Polarity),
/// SR
StepAndRepeat(extended_codes::StepAndRepeat),
/// TF
FileAttribute(attributes::FileAttribute),
/// TA
ApertureAttribute(attributes::ApertureAttribute),
/// TD
DeleteAttribute(String),
}
impl_from!(
coordinates::CoordinateFormat,
ExtendedCode,
ExtendedCode::CoordinateFormat
);
impl_from!(extended_codes::Unit, ExtendedCode, ExtendedCode::Unit);
impl_from!(
extended_codes::ApertureDefinition,
ExtendedCode,
ExtendedCode::ApertureDefinition
);
impl_from!(
macros::ApertureMacro,
ExtendedCode,
ExtendedCode::ApertureMacro
);
impl_from!(
extended_codes::Polarity,
ExtendedCode,
ExtendedCode::LoadPolarity
);
impl_from!(
extended_codes::StepAndRepeat,
ExtendedCode,
ExtendedCode::StepAndRepeat
);
impl_from!(
attributes::FileAttribute,
ExtendedCode,
ExtendedCode::FileAttribute
);
impl_from!(
attributes::ApertureAttribute,
ExtendedCode,
ExtendedCode::ApertureAttribute
);
impl_command_fromfrom!(coordinates::CoordinateFormat, ExtendedCode::from);
impl_command_fromfrom!(extended_codes::Unit, ExtendedCode::from);
impl_command_fromfrom!(extended_codes::ApertureDefinition, ExtendedCode::from);
impl_command_fromfrom!(macros::ApertureMacro, ExtendedCode::from);
impl_command_fromfrom!(extended_codes::Polarity, ExtendedCode::from);
impl_command_fromfrom!(extended_codes::StepAndRepeat, ExtendedCode::from);
impl_command_fromfrom!(attributes::FileAttribute, ExtendedCode::from);
impl_command_fromfrom!(attributes::ApertureAttribute, ExtendedCode::from);
#[cfg(test)]
mod test {
use super::*;
use std::io::BufWriter;
use crate::extended_codes::Polarity;
use crate::function_codes::GCode;
use crate::traits::GerberCode;
#[test]
fn test_debug() {
//! The debug representation should work properly.
let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment("test".to_string())));
let debug = format!("{:?}", c);
assert_eq!(debug, "FunctionCode(GCode(Comment(\"test\")))");
}
#[test]
fn test_function_code_serialize() {
//! A `FunctionCode` should implement `GerberCode`
let c = FunctionCode::GCode(GCode::Comment("comment".to_string()));
assert_code!(c, "G04 comment*\n");
}
#[test]
fn test_function_code_from_gcode() {
let comment = GCode::Comment("hello".into());
let f1: FunctionCode = FunctionCode::GCode(comment.clone());
let f2: FunctionCode = comment.into();
assert_eq!(f1, f2);
}
#[test]
fn test_command_from_function_code() {
let comment = FunctionCode::GCode(GCode::Comment("hello".into()));
let c1: Command = Command::FunctionCode(comment.clone());
let c2: Command = comment.into();
assert_eq!(c1, c2);
}
#[test]
fn test_command_from_extended_code() {
let delete_attr = ExtendedCode::DeleteAttribute("hello".into());
let c1: Command = Command::ExtendedCode(delete_attr.clone());
let c2: Command = delete_attr.into();
assert_eq!(c1, c2);
}
#[test]
fn test_extended_code_from_polarity() {
let e1: ExtendedCode = ExtendedCode::LoadPolarity(Polarity::Dark);
let e2: ExtendedCode = Polarity::Dark.into();
assert_eq!(e1, e2);
}
}