added local fork of gerber-types-rs
This commit is contained in:
parent
7913541282
commit
ff45b5ef03
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
Cargo.lock
|
||||
*.swp
|
|
@ -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
|
|
@ -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"
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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,
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -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
|
@ -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);
|
||||
};
|
||||
}
|
|
@ -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<()>;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue