added local fork of gerber-types-rs
This commit is contained in:
parent
7913541282
commit
ff45b5ef03
3
gerber-types-rs/.gitignore
vendored
Normal file
3
gerber-types-rs/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
|
*.swp
|
44
gerber-types-rs/CHANGELOG.md
Normal file
44
gerber-types-rs/CHANGELOG.md
Normal 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
|
24
gerber-types-rs/Cargo.toml
Normal file
24
gerber-types-rs/Cargo.toml
Normal 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"
|
176
gerber-types-rs/LICENSE-APACHE
Normal file
176
gerber-types-rs/LICENSE-APACHE
Normal 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
|
19
gerber-types-rs/LICENSE-MIT
Normal file
19
gerber-types-rs/LICENSE-MIT
Normal 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
47
gerber-types-rs/README.md
Normal 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
|
24
gerber-types-rs/RELEASING.md
Normal file
24
gerber-types-rs/RELEASING.md
Normal 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
|
410
gerber-types-rs/src/attributes.rs
Normal file
410
gerber-types-rs/src/attributes.rs
Normal 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,
|
||||||
|
}
|
106
gerber-types-rs/src/codegen.rs
Normal file
106
gerber-types-rs/src/codegen.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
479
gerber-types-rs/src/coordinates.rs
Normal file
479
gerber-types-rs/src/coordinates.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
39
gerber-types-rs/src/errors.rs
Normal file
39
gerber-types-rs/src/errors.rs
Normal 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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
315
gerber-types-rs/src/extended_codes.rs
Normal file
315
gerber-types-rs/src/extended_codes.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
143
gerber-types-rs/src/function_codes.rs
Normal file
143
gerber-types-rs/src/function_codes.rs
Normal 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
289
gerber-types-rs/src/lib.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
1089
gerber-types-rs/src/macros.rs
Normal file
1089
gerber-types-rs/src/macros.rs
Normal file
File diff suppressed because it is too large
Load Diff
25
gerber-types-rs/src/test_macros.rs
Normal file
25
gerber-types-rs/src/test_macros.rs
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
19
gerber-types-rs/src/traits.rs
Normal file
19
gerber-types-rs/src/traits.rs
Normal 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<()>;
|
||||||
|
}
|
188
gerber-types-rs/src/types.rs
Normal file
188
gerber-types-rs/src/types.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user