Compare commits
No commits in common. "master" and "0.1" have entirely different histories.
|
@ -1,7 +1,5 @@
|
||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"centered",
|
|
||||||
"cmds",
|
|
||||||
"color",
|
"color",
|
||||||
"Color",
|
"Color",
|
||||||
"consts",
|
"consts",
|
||||||
|
@ -10,14 +8,11 @@
|
||||||
"egui",
|
"egui",
|
||||||
"emath",
|
"emath",
|
||||||
"epaint",
|
"epaint",
|
||||||
"evalexpr",
|
|
||||||
"excellon",
|
"excellon",
|
||||||
"Excellon",
|
"Excellon",
|
||||||
"excellons",
|
"excellons",
|
||||||
"gerbers",
|
"gerbers",
|
||||||
"Heatsink",
|
"Heatsink",
|
||||||
"itertools",
|
|
||||||
"Itertools",
|
|
||||||
"linepath",
|
"linepath",
|
||||||
"obround",
|
"obround",
|
||||||
"Obround",
|
"Obround",
|
||||||
|
@ -26,7 +21,6 @@
|
||||||
"rect",
|
"rect",
|
||||||
"Rect",
|
"Rect",
|
||||||
"regmatch",
|
"regmatch",
|
||||||
"Soldermask",
|
"Soldermask"
|
||||||
"winresource"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
|
@ -6,24 +6,16 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = "0.28.1"
|
eframe = "0.28.1"
|
||||||
egui_plot = { version = "0.28.1", features = ["serde"] }
|
egui_plot = { version = "0.28.1", features = ["serde"] }
|
||||||
egui_extras = { version = "0.28.1", features = ["all_loaders"] }
|
|
||||||
rfd = "0.14"
|
rfd = "0.14"
|
||||||
image = "0.25"
|
|
||||||
|
|
||||||
clipper2 = "0.4.1"
|
clipper2 = "0.4.1"
|
||||||
# gerber-types = "0.3"
|
gerber-types = "0.3"
|
||||||
gerber-types = { path = "./gerber-types-rs" }
|
|
||||||
|
|
||||||
svg = "0.17"
|
svg = "0.17"
|
||||||
dxf = "0.5"
|
dxf = "0.5"
|
||||||
|
|
||||||
lazy-regex = "3.1.0"
|
lazy-regex = "3.1.0"
|
||||||
itertools = "0.13"
|
|
||||||
evalexpr = "11.3.0"
|
|
||||||
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
error-stack = "0.5"
|
error-stack = "0.5"
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
winresource = "0.1"
|
|
||||||
|
|
14
build.rs
14
build.rs
|
@ -1,14 +0,0 @@
|
||||||
use {
|
|
||||||
std::{env, io},
|
|
||||||
winresource::WindowsResource,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
|
||||||
if env::var_os("CARGO_CFG_WINDOWS").is_some() {
|
|
||||||
WindowsResource::new()
|
|
||||||
// This path can be absolute, or relative to your crate root.
|
|
||||||
.set_icon("resources/icon.ico")
|
|
||||||
.compile()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
target
|
|
||||||
Cargo.lock
|
|
||||||
*.swp
|
|
|
@ -1,44 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,24 +0,0 @@
|
||||||
[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"
|
|
|
@ -1,176 +0,0 @@
|
||||||
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
|
|
|
@ -1,19 +0,0 @@
|
||||||
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.
|
|
|
@ -1,47 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,24 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,410 +0,0 @@
|
||||||
//! 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,
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
//! 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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,479 +0,0 @@
|
||||||
//! 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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
//! 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"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,315 +0,0 @@
|
||||||
//! 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
//! 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 {}
|
|
|
@ -1,289 +0,0 @@
|
||||||
//! # 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
|
@ -1,25 +0,0 @@
|
||||||
/// 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);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
//! 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<()>;
|
|
||||||
}
|
|
|
@ -1,188 +0,0 @@
|
||||||
//! 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);
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 147 KiB |
Binary file not shown.
Before Width: | Height: | Size: 187 KiB |
|
@ -1,8 +1,15 @@
|
||||||
|
use eframe::{
|
||||||
|
egui::{Color32, Ui},
|
||||||
|
epaint::CircleShape,
|
||||||
|
};
|
||||||
use egui_plot::PlotUi;
|
use egui_plot::PlotUi;
|
||||||
|
|
||||||
use crate::{application::Application, geometry::elements::Element};
|
use crate::{
|
||||||
|
application::Application,
|
||||||
|
geometry::{elements::Element, DrawableRaw},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{draw_on_plot_canvas, CanvasColour, PlotDrawer};
|
use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer};
|
||||||
|
|
||||||
pub fn draw_excellons_(ui: &mut PlotUi, app: &mut Application) {
|
pub fn draw_excellons_(ui: &mut PlotUi, app: &mut Application) {
|
||||||
for (file_name, (_, excellon)) in &app.excellons {
|
for (file_name, (_, excellon)) in &app.excellons {
|
||||||
|
@ -11,6 +18,7 @@ pub fn draw_excellons_(ui: &mut PlotUi, app: &mut Application) {
|
||||||
for circle in excellon.holes.iter() {
|
for circle in excellon.holes.iter() {
|
||||||
draw_on_plot_canvas(
|
draw_on_plot_canvas(
|
||||||
ui,
|
ui,
|
||||||
|
app,
|
||||||
PlotDrawer::Drawable((
|
PlotDrawer::Drawable((
|
||||||
&Element::Circle(circle.to_owned()),
|
&Element::Circle(circle.to_owned()),
|
||||||
CanvasColour::Excellon,
|
CanvasColour::Excellon,
|
||||||
|
@ -20,3 +28,29 @@ pub fn draw_excellons_(ui: &mut PlotUi, app: &mut Application) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_excellons(ui: &mut Ui, app: &mut Application) {
|
||||||
|
for (file_name, (ex_name, excellon)) in &app.excellons {
|
||||||
|
let selected = &app.selection == file_name;
|
||||||
|
|
||||||
|
for (i, circle) in excellon.holes.iter().enumerate() {
|
||||||
|
draw_floating_area_on_canvas(
|
||||||
|
ui,
|
||||||
|
app,
|
||||||
|
circle.canvas_pos(),
|
||||||
|
("ExcellonArea", ex_name, i),
|
||||||
|
Drawer::Closure(&|ui| {
|
||||||
|
ui.painter().add(CircleShape::filled(
|
||||||
|
circle.position.invert_y().into(),
|
||||||
|
circle.diameter as f32 / 2.,
|
||||||
|
if selected {
|
||||||
|
Color32::BROWN.gamma_multiply(0.5)
|
||||||
|
} else {
|
||||||
|
Color32::BROWN
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use eframe::egui::Pos2;
|
use eframe::{
|
||||||
|
egui::{Color32, Pos2, Ui},
|
||||||
|
epaint::{CircleShape, PathShape, PathStroke},
|
||||||
|
};
|
||||||
use egui_plot::{Line, PlotPoint, PlotPoints, PlotUi};
|
use egui_plot::{Line, PlotPoint, PlotPoints, PlotUi};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -6,7 +9,7 @@ use crate::{
|
||||||
geometry::{elements::circle::Circle, DrawableRaw},
|
geometry::{elements::circle::Circle, DrawableRaw},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{draw_on_plot_canvas, CanvasColour, PlotDrawer};
|
use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer};
|
||||||
|
|
||||||
pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) {
|
pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) {
|
||||||
for (file_name, geo) in &app.outlines {
|
for (file_name, geo) in &app.outlines {
|
||||||
|
@ -16,6 +19,7 @@ pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) {
|
||||||
for path in geo.paths().iter() {
|
for path in geo.paths().iter() {
|
||||||
draw_on_plot_canvas(
|
draw_on_plot_canvas(
|
||||||
ui,
|
ui,
|
||||||
|
app,
|
||||||
PlotDrawer::Closure(&|ui| {
|
PlotDrawer::Closure(&|ui| {
|
||||||
// draw outline path
|
// draw outline path
|
||||||
let mut points = path
|
let mut points = path
|
||||||
|
@ -36,7 +40,7 @@ pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) {
|
||||||
points.push(points[1]);
|
points.push(points[1]);
|
||||||
let line = Line::new(PlotPoints::from(points))
|
let line = Line::new(PlotPoints::from(points))
|
||||||
.width(width)
|
.width(width)
|
||||||
.color(CanvasColour::Outline.as_colour32(selected));
|
.color(CanvasColour::Outline.to_colour32(selected));
|
||||||
|
|
||||||
ui.line(line)
|
ui.line(line)
|
||||||
}),
|
}),
|
||||||
|
@ -47,11 +51,99 @@ pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) {
|
||||||
for point in geo.points().iter() {
|
for point in geo.points().iter() {
|
||||||
draw_on_plot_canvas(
|
draw_on_plot_canvas(
|
||||||
ui,
|
ui,
|
||||||
|
app,
|
||||||
PlotDrawer::Closure(&|ui| {
|
PlotDrawer::Closure(&|ui| {
|
||||||
let circle = Circle::new(*point, geo.stroke.into(), None);
|
let circle = Circle::new(*point, geo.stroke.into(), None);
|
||||||
circle.draw_egui_plot(ui, CanvasColour::Outline, selected);
|
circle.draw_egui_plot(ui, CanvasColour::Outline, selected);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
// draw_floating_area_on_canvas(
|
||||||
|
// ui,
|
||||||
|
// app,
|
||||||
|
// point,
|
||||||
|
// ("GeometryAreaPoint", name, i),
|
||||||
|
// Drawer::Closure(&|ui| {
|
||||||
|
// // draw point shape
|
||||||
|
// ui.painter().add(CircleShape::filled(
|
||||||
|
// point.invert_y().into(),
|
||||||
|
// geo.stroke,
|
||||||
|
// if selected {
|
||||||
|
// Color32::DARK_BLUE.gamma_multiply(0.5)
|
||||||
|
// } else {
|
||||||
|
// Color32::DARK_BLUE
|
||||||
|
// },
|
||||||
|
// ));
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for circle in excellon.holes.iter() {
|
||||||
|
// draw_on_plot_canvas(
|
||||||
|
// ui,
|
||||||
|
// app,
|
||||||
|
// PlotDrawer::Drawable((
|
||||||
|
// &Element::Circle(circle.to_owned()),
|
||||||
|
// CanvasColour::Excellon,
|
||||||
|
// selected,
|
||||||
|
// )),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_geometries(ui: &mut Ui, app: &mut Application) {
|
||||||
|
for (name, geo) in &app.outlines {
|
||||||
|
let selected = &app.selection == name;
|
||||||
|
|
||||||
|
// draw outline path
|
||||||
|
for (i, path) in geo.paths().iter().enumerate() {
|
||||||
|
draw_floating_area_on_canvas(
|
||||||
|
ui,
|
||||||
|
app,
|
||||||
|
{
|
||||||
|
let origin = path.iter().next().unwrap();
|
||||||
|
Pos2::new(origin.x() as f32, origin.y() as f32)
|
||||||
|
},
|
||||||
|
("GeometryAreaOutline", name, i),
|
||||||
|
Drawer::Closure(&|ui| {
|
||||||
|
// draw outline path
|
||||||
|
ui.painter().add(PathShape::closed_line(
|
||||||
|
path.iter()
|
||||||
|
.map(|v| Pos2::new(v.x() as f32, -v.y() as f32))
|
||||||
|
.collect::<Vec<Pos2>>(),
|
||||||
|
PathStroke::new(
|
||||||
|
geo.stroke,
|
||||||
|
if selected {
|
||||||
|
Color32::DARK_BLUE.gamma_multiply(0.5)
|
||||||
|
} else {
|
||||||
|
Color32::DARK_BLUE
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw point shapes
|
||||||
|
for (i, point) in geo.points().iter().enumerate() {
|
||||||
|
draw_floating_area_on_canvas(
|
||||||
|
ui,
|
||||||
|
app,
|
||||||
|
point,
|
||||||
|
("GeometryAreaPoint", name, i),
|
||||||
|
Drawer::Closure(&|ui| {
|
||||||
|
// draw point shape
|
||||||
|
ui.painter().add(CircleShape::filled(
|
||||||
|
point.invert_y().into(),
|
||||||
|
geo.stroke,
|
||||||
|
if selected {
|
||||||
|
Color32::DARK_BLUE.gamma_multiply(0.5)
|
||||||
|
} else {
|
||||||
|
Color32::DARK_BLUE
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,35 @@
|
||||||
|
use eframe::{
|
||||||
|
egui::{Color32, Pos2, Ui},
|
||||||
|
epaint::{PathShape, PathStroke},
|
||||||
|
};
|
||||||
use egui_plot::{Line, PlotPoints, PlotUi};
|
use egui_plot::{Line, PlotPoints, PlotUi};
|
||||||
|
|
||||||
use crate::{
|
use crate::{application::Application, geometry::DrawableRaw};
|
||||||
application::Application,
|
|
||||||
geometry::{ClipperPaths, DrawableRaw},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{draw_on_plot_canvas, CanvasColour, PlotDrawer};
|
use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer};
|
||||||
|
|
||||||
pub fn draw_gerbers_(ui: &mut PlotUi, app: &mut Application) {
|
pub fn draw_gerbers_(ui: &mut PlotUi, app: &mut Application) {
|
||||||
for (file_name, (_, geo)) in &app.gerbers {
|
for (file_name, (_, geo)) in &app.gerbers {
|
||||||
let selected = &app.selection == file_name;
|
let selected = &app.selection == file_name;
|
||||||
|
|
||||||
// draw apertures
|
|
||||||
for geometry in geo.apertures.iter() {
|
for geometry in geo.apertures.iter() {
|
||||||
draw_on_plot_canvas(
|
draw_on_plot_canvas(
|
||||||
ui,
|
ui,
|
||||||
|
app,
|
||||||
PlotDrawer::Drawable((geometry, CanvasColour::Copper, selected)),
|
PlotDrawer::Drawable((geometry, CanvasColour::Copper, selected)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw lines
|
|
||||||
for line in geo.paths.iter() {
|
for line in geo.paths.iter() {
|
||||||
draw_on_plot_canvas(
|
draw_on_plot_canvas(
|
||||||
ui,
|
ui,
|
||||||
|
app,
|
||||||
PlotDrawer::Closure(&|ui| line.draw_egui_plot(ui, CanvasColour::Copper, selected)),
|
PlotDrawer::Closure(&|ui| line.draw_egui_plot(ui, CanvasColour::Copper, selected)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw conductor outlines
|
// draw union path
|
||||||
for net in &geo.united_nets.conductors {
|
for path in geo.outline_union.iter() {
|
||||||
draw_paths_outline(ui, &net.outline, selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw isolated aperture outlines
|
|
||||||
for element in &geo.united_nets.isolated_apertures {
|
|
||||||
draw_paths_outline(ui, &element.to_paths(), selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_paths_outline(ui: &mut PlotUi, paths: &ClipperPaths, selected: bool) {
|
|
||||||
for path in paths.iter() {
|
|
||||||
if !path.is_empty() {
|
|
||||||
let mut points = path
|
let mut points = path
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| [p.x(), p.y()])
|
.map(|p| [p.x(), p.y()])
|
||||||
|
@ -49,9 +37,63 @@ fn draw_paths_outline(ui: &mut PlotUi, paths: &ClipperPaths, selected: bool) {
|
||||||
|
|
||||||
points.push(points[0]);
|
points.push(points[0]);
|
||||||
let line = Line::new(PlotPoints::from(points))
|
let line = Line::new(PlotPoints::from(points))
|
||||||
.color(CanvasColour::CopperOutline.as_colour32(selected));
|
.color(CanvasColour::CopperOutline.to_colour32(selected));
|
||||||
|
|
||||||
ui.line(line)
|
ui.line(line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_gerbers(ui: &mut Ui, app: &mut Application) {
|
||||||
|
for (file_name, (name, geo)) in &app.gerbers {
|
||||||
|
let selected = &app.selection == file_name;
|
||||||
|
|
||||||
|
for (i, geometry) in geo.apertures.iter().enumerate() {
|
||||||
|
draw_floating_area_on_canvas(
|
||||||
|
ui,
|
||||||
|
app,
|
||||||
|
geometry.canvas_pos(),
|
||||||
|
("GerberArea", name, i),
|
||||||
|
Drawer::Drawable((geometry, selected)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, line) in geo.paths.iter().enumerate() {
|
||||||
|
draw_floating_area_on_canvas(
|
||||||
|
ui,
|
||||||
|
app,
|
||||||
|
line.canvas_pos(),
|
||||||
|
("LinePath", name, i),
|
||||||
|
Drawer::Closure(&|ui| line.draw_egui(ui, selected)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw union path
|
||||||
|
for (i, path) in geo.outline_union.iter().enumerate() {
|
||||||
|
draw_floating_area_on_canvas(
|
||||||
|
ui,
|
||||||
|
app,
|
||||||
|
{
|
||||||
|
let origin = path.iter().next().unwrap();
|
||||||
|
Pos2::new(origin.x() as f32, origin.y() as f32)
|
||||||
|
},
|
||||||
|
("UnionOutline", name, i),
|
||||||
|
Drawer::Closure(&|ui| {
|
||||||
|
ui.painter().add(PathShape::closed_line(
|
||||||
|
path.iter()
|
||||||
|
.map(|v| Pos2::new(v.x() as f32, -v.y() as f32))
|
||||||
|
.collect::<Vec<Pos2>>(),
|
||||||
|
PathStroke::new(
|
||||||
|
0.1_f32,
|
||||||
|
if selected {
|
||||||
|
Color32::LIGHT_RED
|
||||||
|
} else {
|
||||||
|
Color32::BLACK
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
use eframe::egui::{self, Color32, Pos2, TextWrapMode, Ui, Vec2};
|
||||||
|
|
||||||
|
use crate::application::Application;
|
||||||
|
|
||||||
|
pub fn draw_live_position(ui: &mut Ui, app: &mut Application) {
|
||||||
|
if let Some(cursor) = cursor_canvas_position(app, ui) {
|
||||||
|
let _id = egui::Area::new(app.canvas.0.with("cursor_position_box"))
|
||||||
|
.fixed_pos(app.canvas.1.min)
|
||||||
|
// .order(egui::Order::Middle)
|
||||||
|
.default_size(Vec2::new(80., 80.))
|
||||||
|
.show(ui.ctx(), |ui| {
|
||||||
|
// let painter = ui.painter();
|
||||||
|
// painter.add(RectShape::filled(Rect::from_min_size(rect.min, Vec2::new(80., 80.)), Rounding::ZERO, Color32::LIGHT_BLUE));
|
||||||
|
egui::Frame::default()
|
||||||
|
.rounding(egui::Rounding::same(4.0))
|
||||||
|
.inner_margin(egui::Margin::same(8.0))
|
||||||
|
.stroke(ui.ctx().style().visuals.window_stroke)
|
||||||
|
.fill(Color32::from_rgba_premultiplied(0xAD, 0xD8, 0xE6, 200))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
|
||||||
|
let cursor_position = app.transform.inverse() * cursor; // + rect.min.to_vec2() - app.canvas_size.min;
|
||||||
|
let cursor_position2 = app.test_transform.inverse() * cursor; // + rect.min.to_vec2() - app.canvas_size.min;
|
||||||
|
|
||||||
|
ui.label(format!(
|
||||||
|
"x: {} {}",
|
||||||
|
(-app.transform.translation.x + cursor.x) / app.transform.scaling,
|
||||||
|
app.variables.units
|
||||||
|
));
|
||||||
|
ui.label(format!(
|
||||||
|
"y: {} {}",
|
||||||
|
// cursor.y / app.transform.scaling + app.test_transform.translation.y,
|
||||||
|
(-app.transform.translation.y + cursor.y) / app.transform.scaling,
|
||||||
|
app.variables.units
|
||||||
|
));
|
||||||
|
ui.label(format!(
|
||||||
|
"cursor: {:?} {}",
|
||||||
|
cursor_position, app.variables.units
|
||||||
|
));
|
||||||
|
ui.label(format!(
|
||||||
|
"cursor2: {:?} {}",
|
||||||
|
cursor_position2, app.variables.units
|
||||||
|
));
|
||||||
|
|
||||||
|
ui.label(format!("{:?} - {:?}", app.transform, app.test_transform))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_canvas_position(app: &Application, ui: &mut Ui) -> Option<Pos2> {
|
||||||
|
let pointer_pos = ui.ctx().input(|i| i.pointer.hover_pos());
|
||||||
|
|
||||||
|
if let Some(cursor) = pointer_pos {
|
||||||
|
let pos = cursor - app.canvas.1.min;
|
||||||
|
match (pos.x, pos.y) {
|
||||||
|
(x, y) if x > 0. && y > 0. => Some(pos.to_pos2()),
|
||||||
|
(_, _) => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
// pointer_pos.map(|pos| (pos - app.canvas_size.min).to_pos2())
|
||||||
|
}
|
|
@ -1,12 +1,19 @@
|
||||||
pub mod excellons;
|
pub mod excellons;
|
||||||
pub mod geometries;
|
pub mod geometries;
|
||||||
pub mod gerbers;
|
pub mod gerbers;
|
||||||
|
mod live_position;
|
||||||
|
|
||||||
use eframe::egui::{Color32, Ui};
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use eframe::{
|
||||||
|
egui::{self, Color32, Pos2, Rounding, Stroke, Ui},
|
||||||
|
epaint::RectShape,
|
||||||
|
};
|
||||||
use egui_plot::PlotUi;
|
use egui_plot::PlotUi;
|
||||||
use excellons::draw_excellons_;
|
use excellons::{draw_excellons, draw_excellons_};
|
||||||
use geometries::draw_geometries_;
|
use geometries::{draw_geometries, draw_geometries_};
|
||||||
use gerbers::draw_gerbers_;
|
use gerbers::{draw_gerbers, draw_gerbers_};
|
||||||
|
use live_position::draw_live_position;
|
||||||
|
|
||||||
use crate::geometry::elements::Element;
|
use crate::geometry::elements::Element;
|
||||||
|
|
||||||
|
@ -21,7 +28,6 @@ const EXCELLON_COLOR_SELECTED: Color32 = Color32::from_rgba_premultiplied(65, 42
|
||||||
const OUTLINE_COLOR: Color32 = Color32::DARK_BLUE;
|
const OUTLINE_COLOR: Color32 = Color32::DARK_BLUE;
|
||||||
const OUTLINE_COLOR_SELECTED: Color32 = Color32::from_rgba_premultiplied(0, 0, 139, 128);
|
const OUTLINE_COLOR_SELECTED: Color32 = Color32::from_rgba_premultiplied(0, 0, 139, 128);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum CanvasColour {
|
pub enum CanvasColour {
|
||||||
Copper,
|
Copper,
|
||||||
CopperOutline,
|
CopperOutline,
|
||||||
|
@ -30,7 +36,7 @@ pub enum CanvasColour {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CanvasColour {
|
impl CanvasColour {
|
||||||
pub fn as_colour32(&self, selected: bool) -> Color32 {
|
pub fn to_colour32(&self, selected: bool) -> Color32 {
|
||||||
match (self, selected) {
|
match (self, selected) {
|
||||||
(CanvasColour::Copper, true) => COPPER_COLOR_SELECTED,
|
(CanvasColour::Copper, true) => COPPER_COLOR_SELECTED,
|
||||||
(CanvasColour::Copper, false) => COPPER_COLOR,
|
(CanvasColour::Copper, false) => COPPER_COLOR,
|
||||||
|
@ -53,6 +59,48 @@ pub fn draw_canvas(ui: &mut Ui, app: &mut Application) {
|
||||||
draw_excellons_(plot_ui, app);
|
draw_excellons_(plot_ui, app);
|
||||||
draw_geometries_(plot_ui, app);
|
draw_geometries_(plot_ui, app);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// draw_live_position(ui, app);
|
||||||
|
|
||||||
|
// ui.painter().add(RectShape::stroke(
|
||||||
|
// app.canvas.1,
|
||||||
|
// Rounding::same(0.),
|
||||||
|
// Stroke::new(0.2, Color32::BLACK),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// draw_gerbers(ui, app);
|
||||||
|
// draw_excellons(ui, app);
|
||||||
|
// draw_geometries(ui, app);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Drawer<'a> {
|
||||||
|
Drawable((&'a Element, bool)),
|
||||||
|
Closure(&'a dyn Fn(&mut Ui)),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_floating_area_on_canvas(
|
||||||
|
ui: &mut Ui,
|
||||||
|
app: &Application,
|
||||||
|
pos: impl Into<Pos2>,
|
||||||
|
name: impl Hash,
|
||||||
|
drawer: Drawer,
|
||||||
|
) {
|
||||||
|
let window_layer = ui.layer_id();
|
||||||
|
let id = egui::Area::new(app.canvas.0.with(name))
|
||||||
|
.current_pos(pos)
|
||||||
|
// .order(egui::Order::Middle)
|
||||||
|
.show(ui.ctx(), |ui| {
|
||||||
|
ui.set_clip_rect(app.test_transform.inverse() * app.canvas.1);
|
||||||
|
match drawer {
|
||||||
|
Drawer::Drawable((t, selected)) => t.draw_egui(ui, selected),
|
||||||
|
Drawer::Closure(fun) => fun(ui),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.response
|
||||||
|
.layer_id;
|
||||||
|
|
||||||
|
ui.ctx().set_transform_layer(id, app.test_transform);
|
||||||
|
ui.ctx().set_sublayer(window_layer, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum PlotDrawer<'a> {
|
pub enum PlotDrawer<'a> {
|
||||||
|
@ -60,7 +108,7 @@ pub enum PlotDrawer<'a> {
|
||||||
Closure(&'a dyn Fn(&mut PlotUi)),
|
Closure(&'a dyn Fn(&mut PlotUi)),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_on_plot_canvas(ui: &mut PlotUi, drawer: PlotDrawer) {
|
fn draw_on_plot_canvas(ui: &mut PlotUi, app: &Application, drawer: PlotDrawer) {
|
||||||
match drawer {
|
match drawer {
|
||||||
PlotDrawer::Drawable((t, colour, selected)) => t.draw_egui_plot(ui, colour, selected),
|
PlotDrawer::Drawable((t, colour, selected)) => t.draw_egui_plot(ui, colour, selected),
|
||||||
PlotDrawer::Closure(fun) => fun(ui),
|
PlotDrawer::Closure(fun) => fun(ui),
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use eframe::egui;
|
use eframe::{
|
||||||
|
egui::{self, Vec2},
|
||||||
|
emath::TSTransform,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
canvas::draw_canvas,
|
canvas::draw_canvas,
|
||||||
|
@ -6,17 +9,230 @@ use super::{
|
||||||
Application,
|
Application,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<'a> eframe::App for Application<'a> {
|
impl eframe::App for Application {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
// let window_size = ctx.screen_rect().size();
|
||||||
|
|
||||||
draw_header(ctx, self);
|
draw_header(ctx, self);
|
||||||
|
|
||||||
draw_sidebar(ctx, self);
|
draw_sidebar(ctx, self);
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
// ui.horizontal(|ui| {
|
||||||
|
// let name_label = ui.label("Your name: ");
|
||||||
|
// ui.text_edit_singleline(&mut self.name)
|
||||||
|
// .labelled_by(name_label.id);
|
||||||
|
// });
|
||||||
|
// ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
|
||||||
|
// if ui.button("Increment").clicked() {
|
||||||
|
// self.age += 1;
|
||||||
|
// }
|
||||||
|
// ui.label(format!("Hello '{}', age {}", self.name, self.age));
|
||||||
|
|
||||||
|
// // ui.image(egui::include_image!(
|
||||||
|
// // "../../../crates/egui/assets/ferris.png"
|
||||||
|
// // ));
|
||||||
|
|
||||||
|
let pointer_pos = ui.ctx().input(|i| i.pointer.hover_pos());
|
||||||
|
|
||||||
draw_action_panel(ui, self);
|
draw_action_panel(ui, self);
|
||||||
|
|
||||||
|
// ui.label(
|
||||||
|
// "Pan, zoom in, and zoom out with scrolling (see the plot demo for more instructions). \
|
||||||
|
// Double click on the background to reset.",
|
||||||
|
// );
|
||||||
|
// ui.vertical_centered(|ui| {
|
||||||
|
// ui.add(crate::egui_github_link_file!());
|
||||||
|
// });
|
||||||
|
// if let Some(pos) = pointer_pos {
|
||||||
|
// ui.label(format!("Translation: {:?}", self.transform.translation));
|
||||||
|
// ui.label(format!("ScLING: {}", self.transform.scaling));
|
||||||
|
// ui.label(format!("Canvas start: {}", self.canvas.1.min));
|
||||||
|
// }
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
// ui.allocate_ui(ui.available_size(), |ui| {
|
||||||
|
// egui::TopBottomPanel::bottom("BottomCanvas")
|
||||||
|
// .show_separator_line(false)
|
||||||
|
// .exact_height(15.)
|
||||||
|
// .show_inside(ui, |ui| {});
|
||||||
|
// egui::SidePanel::left("LeftCanvas")
|
||||||
|
// .show_separator_line(false)
|
||||||
|
// .exact_width(15.)
|
||||||
|
// .show_inside(ui, |ui| {});
|
||||||
|
|
||||||
|
// egui::CentralPanel::default()
|
||||||
|
// .frame(egui::Frame::none().inner_margin(0.).outer_margin(0.))
|
||||||
|
// .show_inside(ui, |ui| {
|
||||||
|
// let sin: egui_plot::PlotPoints = (0..1000)
|
||||||
|
// .map(|i| {
|
||||||
|
// let x = i as f64 * 0.01;
|
||||||
|
// [x, x.sin()]
|
||||||
|
// })
|
||||||
|
// .collect();
|
||||||
|
// let line = egui_plot::Line::new(sin);
|
||||||
|
// egui_plot::Plot::new("my_plot")
|
||||||
|
// .view_aspect(2.0)
|
||||||
|
// .data_aspect(1.0)
|
||||||
|
// .show(ui, |plot_ui| {
|
||||||
|
// super::canvas::gerbers::draw_gerbers_(plot_ui, self);
|
||||||
|
// super::canvas::excellons::draw_excellons_(plot_ui, self);
|
||||||
|
// super::canvas::geometries::draw_geometries_(plot_ui, self)
|
||||||
|
// // plot_ui.line(line);
|
||||||
|
// // for (_, (name, geo)) in &self.gerbers {
|
||||||
|
// // let path = geo.outline_union.iter().map(|path| {
|
||||||
|
// // let mut points = path
|
||||||
|
// // .iter()
|
||||||
|
// // .map(|p| [p.x(), p.y()])
|
||||||
|
// // .collect::<Vec<[f64; 2]>>();
|
||||||
|
|
||||||
|
// // points.push(points[0]);
|
||||||
|
|
||||||
|
// // (
|
||||||
|
// // egui_plot::Line::new(egui_plot::PlotPoints::from(
|
||||||
|
// // points.clone(),
|
||||||
|
// // ))
|
||||||
|
// // .color(egui::Color32::DARK_BLUE),
|
||||||
|
// // egui_plot::Polygon::new(egui_plot::PlotPoints::from(
|
||||||
|
// // points,
|
||||||
|
// // ))
|
||||||
|
// // .fill_color(egui::Color32::LIGHT_GREEN),
|
||||||
|
// // )
|
||||||
|
// // });
|
||||||
|
|
||||||
|
// // for line in path {
|
||||||
|
// // plot_ui.line(line.0);
|
||||||
|
// // plot_ui.polygon(line.1);
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // let circle_points: egui_plot::PlotPoints = (0..=400)
|
||||||
|
// // .map(|i| {
|
||||||
|
// // let t = egui::remap(
|
||||||
|
// // i as f64,
|
||||||
|
// // 0.0..=(400 as f64),
|
||||||
|
// // 0.0..=std::f64::consts::TAU,
|
||||||
|
// // );
|
||||||
|
// // let r = 10.;
|
||||||
|
// // [r * t.cos() + 20. as f64, r * t.sin() + 20. as f64]
|
||||||
|
// // })
|
||||||
|
// // .collect();
|
||||||
|
|
||||||
|
// // let poly = egui_plot::Polygon::new(
|
||||||
|
// // egui_plot::PlotPoints::from(circle_points),
|
||||||
|
// // )
|
||||||
|
// // .fill_color(egui::Color32::LIGHT_GREEN);
|
||||||
|
// // plot_ui.polygon(poly);
|
||||||
|
// // }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // let (id, rect) = ui.allocate_space(ui.available_size());
|
||||||
|
// // let response = ui.interact(rect, id, egui::Sense::click_and_drag());
|
||||||
|
// // self.canvas = (id, rect);
|
||||||
|
// // // Allow dragging the background as well.
|
||||||
|
// // if response.dragged() {
|
||||||
|
// // self.transform.translation += response.drag_delta();
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // // Plot-like reset
|
||||||
|
// // if response.double_clicked() {
|
||||||
|
// // self.transform = TSTransform::default();
|
||||||
|
// // self.transform = TSTransform::from_translation(Vec2::new(
|
||||||
|
// // rect.width() / 2.,
|
||||||
|
// // rect.height() / 2.,
|
||||||
|
// // ));
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // let transform =
|
||||||
|
// // TSTransform::from_translation(ui.min_rect().left_top().to_vec2())
|
||||||
|
// // * self.transform;
|
||||||
|
|
||||||
|
// // if let Some(pointer) = ui.ctx().input(|i| i.pointer.hover_pos()) {
|
||||||
|
// // // Note: doesn't catch zooming / panning if a button in this PanZoom container is hovered.
|
||||||
|
// // if response.hovered() {
|
||||||
|
// // let pointer_in_layer = transform.inverse() * pointer;
|
||||||
|
// // let zoom_delta = ui.ctx().input(|i| i.zoom_delta());
|
||||||
|
// // let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta);
|
||||||
|
|
||||||
|
// // // Zoom in on pointer:
|
||||||
|
// // self.transform = self.transform
|
||||||
|
// // * TSTransform::from_translation(pointer_in_layer.to_vec2())
|
||||||
|
// // * TSTransform::from_scaling(zoom_delta)
|
||||||
|
// // * TSTransform::from_translation(-pointer_in_layer.to_vec2());
|
||||||
|
|
||||||
|
// // // Pan:
|
||||||
|
// // self.transform =
|
||||||
|
// // TSTransform::from_translation(pan_delta) * self.transform;
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // self.test_transform = transform;
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
|
||||||
|
// let (id, rect) = ui.allocate_space(Vec2::new(ui.available_width(), 15.));
|
||||||
|
// ui.painter().add(RectShape::stroke(
|
||||||
|
// rect,
|
||||||
|
// Rounding::same(0.),
|
||||||
|
// egui::Stroke::new(0.2, Color32::BLACK),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// let (id, rect) = ui.allocate_space(Vec2::new(10., ui.available_height()));
|
||||||
|
// ui.painter().add(RectShape::stroke(
|
||||||
|
// rect,
|
||||||
|
// Rounding::same(0.),
|
||||||
|
// egui::Stroke::new(0.2, Color32::BLACK),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// let (id, rect) = ui.allocate_space(ui.available_size());
|
||||||
|
// let response = ui.interact(rect, id, egui::Sense::click_and_drag());
|
||||||
|
// self.canvas = (id, rect);
|
||||||
|
// // Allow dragging the background as well.
|
||||||
|
// if response.dragged() {
|
||||||
|
// self.transform.translation += response.drag_delta();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Plot-like reset
|
||||||
|
// if response.double_clicked() {
|
||||||
|
// self.transform = TSTransform::default();
|
||||||
|
// self.transform =
|
||||||
|
// TSTransform::from_translation(Vec2::new(rect.width() / 2., rect.height() / 2.));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let transform =
|
||||||
|
// TSTransform::from_translation(ui.min_rect().left_top().to_vec2()) * self.transform;
|
||||||
|
|
||||||
|
// if let Some(pointer) = ui.ctx().input(|i| i.pointer.hover_pos()) {
|
||||||
|
// // Note: doesn't catch zooming / panning if a button in this PanZoom container is hovered.
|
||||||
|
// if response.hovered() {
|
||||||
|
// let pointer_in_layer = transform.inverse() * pointer;
|
||||||
|
// let zoom_delta = ui.ctx().input(|i| i.zoom_delta());
|
||||||
|
// let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta);
|
||||||
|
|
||||||
|
// // Zoom in on pointer:
|
||||||
|
// self.transform = self.transform
|
||||||
|
// * TSTransform::from_translation(pointer_in_layer.to_vec2())
|
||||||
|
// * TSTransform::from_scaling(zoom_delta)
|
||||||
|
// * TSTransform::from_translation(-pointer_in_layer.to_vec2());
|
||||||
|
|
||||||
|
// // Pan:
|
||||||
|
// self.transform = TSTransform::from_translation(pan_delta) * self.transform;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// self.test_transform = transform;
|
||||||
|
|
||||||
|
// let p = ui.painter_at(rect);
|
||||||
|
// p.add(RectShape::filled(
|
||||||
|
// Rect::from_center_size(rect.center(), Vec2::new(rect.width(), 1.)),
|
||||||
|
// Rounding::ZERO,
|
||||||
|
// Color32::LIGHT_RED,
|
||||||
|
// ));
|
||||||
|
// p.add(RectShape::filled(
|
||||||
|
// Rect::from_center_size(rect.center(), Vec2::new(1., rect.height())),
|
||||||
|
// Rounding::ZERO,
|
||||||
|
// Color32::LIGHT_RED,
|
||||||
|
// ));
|
||||||
|
|
||||||
draw_canvas(ui, self);
|
draw_canvas(ui, self);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use error_stack::{Context, Report};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FileError;
|
|
||||||
|
|
||||||
impl FileError {
|
|
||||||
pub fn new(text: &str) -> Report<Self> {
|
|
||||||
Report::new(Self).attach_printable(text.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FileError {
|
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
fmt.write_str("Error opening file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context for FileError {}
|
|
|
@ -1,24 +1,30 @@
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod egui;
|
mod egui;
|
||||||
mod errors;
|
|
||||||
pub mod panels;
|
pub mod panels;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use eframe::{
|
||||||
|
egui::{Id, Pos2, Rect, Vec2},
|
||||||
|
emath::TSTransform,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
excellon::drills::Drills,
|
excellon::drills::Drills,
|
||||||
geometry::{Geometry, Unit},
|
geometry::{Geometry, Unit},
|
||||||
outline_geometry::OutlineGeometry,
|
outline_geometry::OutlineGeometry,
|
||||||
resources::ResourceLoader,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use canvas::CanvasColour;
|
pub use canvas::CanvasColour;
|
||||||
|
|
||||||
pub struct Application<'a> {
|
pub struct Application {
|
||||||
resources: ResourceLoader<'a>,
|
|
||||||
gerbers: HashMap<String, (String, Geometry)>,
|
gerbers: HashMap<String, (String, Geometry)>,
|
||||||
outlines: HashMap<String, OutlineGeometry>,
|
outlines: HashMap<String, OutlineGeometry>,
|
||||||
excellons: HashMap<String, (String, Drills)>,
|
excellons: HashMap<String, (String, Drills)>,
|
||||||
|
// geometry: Geometry,
|
||||||
|
transform: TSTransform,
|
||||||
|
test_transform: TSTransform,
|
||||||
|
canvas: (Id, Rect),
|
||||||
selection: String,
|
selection: String,
|
||||||
variables: Variables,
|
variables: Variables,
|
||||||
}
|
}
|
||||||
|
@ -29,13 +35,19 @@ pub struct Variables {
|
||||||
units: Unit,
|
units: Unit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Application<'a> {
|
impl Application {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
resources: ResourceLoader::new(),
|
|
||||||
gerbers: HashMap::new(),
|
gerbers: HashMap::new(),
|
||||||
outlines: HashMap::new(),
|
outlines: HashMap::new(),
|
||||||
excellons: HashMap::new(),
|
excellons: HashMap::new(),
|
||||||
|
// geometry,
|
||||||
|
transform: TSTransform::default(),
|
||||||
|
test_transform: TSTransform::default(),
|
||||||
|
canvas: (
|
||||||
|
Id::new("0"),
|
||||||
|
Rect::from_center_size(Pos2::new(0., 0.), Vec2::default()),
|
||||||
|
),
|
||||||
selection: "".into(),
|
selection: "".into(),
|
||||||
variables: Variables::default(),
|
variables: Variables::default(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
|
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
|
|
||||||
use crate::{application::Application, geometry::ClipperPath, outline_geometry::OutlineGeometry};
|
use crate::{
|
||||||
|
application::Application,
|
||||||
|
geometry::{ClipperPath, ClipperPaths, DrawableRaw},
|
||||||
|
outline_geometry::OutlineGeometry,
|
||||||
|
};
|
||||||
|
|
||||||
use super::HOLE_MARK_DIAMETER;
|
use super::HOLE_MARK_DIAMETER;
|
||||||
|
|
||||||
pub fn show_excellon_actions(ui: &mut Ui, app: &mut Application) {
|
pub fn show_excellon_actions(ui: &mut Ui, app: &mut Application) {
|
||||||
if app.excellons.contains_key(&app.selection) {
|
if let Some((name, drill)) = app.excellons.get(&app.selection) {
|
||||||
match app.excellons.entry(app.selection.to_string()) {
|
|
||||||
Entry::Occupied(c) => {
|
|
||||||
let mut deletion_request = false;
|
|
||||||
let (name, drill) = c.get();
|
|
||||||
if ui.button("Delete").clicked() {
|
|
||||||
deletion_request = true;
|
|
||||||
}
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("Generate cut out").clicked() {
|
if ui.button("Generate cut out").clicked() {
|
||||||
let path = drill
|
let path = drill
|
||||||
.holes
|
.holes
|
||||||
|
@ -25,27 +19,33 @@ pub fn show_excellon_actions(ui: &mut Ui, app: &mut Application) {
|
||||||
.into();
|
.into();
|
||||||
app.outlines.insert(
|
app.outlines.insert(
|
||||||
format!("{name}-CutOut"),
|
format!("{name}-CutOut"),
|
||||||
OutlineGeometry::new_no_inflate(&path, 0.1, app.variables.units, name),
|
OutlineGeometry::new_no_inflate(
|
||||||
|
&path,
|
||||||
|
0.1,
|
||||||
|
app.variables.units,
|
||||||
|
name,
|
||||||
|
path.bounds(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if ui.button("Generate hole mark").clicked() {
|
if ui.button("Generate hole mark").clicked() {
|
||||||
app.outlines.insert(
|
app.outlines.insert(
|
||||||
format!("{name}-HoleMark"),
|
format!("{name}-HoleMark"),
|
||||||
OutlineGeometry::drill_marker(
|
OutlineGeometry::point_marker(
|
||||||
drill,
|
drill.holes.iter().map(|c| c.canvas_pos()).collect(),
|
||||||
HOLE_MARK_DIAMETER,
|
HOLE_MARK_DIAMETER,
|
||||||
app.variables.units,
|
app.variables.units,
|
||||||
name,
|
name,
|
||||||
|
ClipperPaths::from(
|
||||||
|
drill
|
||||||
|
.holes
|
||||||
|
.iter()
|
||||||
|
.map(|hole| hole.outline.clone())
|
||||||
|
.collect::<Vec<ClipperPath>>(),
|
||||||
|
)
|
||||||
|
.bounds(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if deletion_request {
|
|
||||||
c.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Entry::Vacant(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,20 @@
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
|
|
||||||
use eframe::egui::{ComboBox, Ui};
|
use eframe::egui::{ComboBox, Ui};
|
||||||
|
|
||||||
use crate::{application::Application, export::svg::SVGConverter};
|
use crate::{application::Application, export::svg::SVGConverter};
|
||||||
|
|
||||||
pub fn show_geometry_actions(ui: &mut Ui, app: &mut Application) {
|
pub fn show_geometry_actions(ui: &mut Ui, app: &mut Application) {
|
||||||
if app.outlines.contains_key(&app.selection) {
|
if let Some(outline) = app.outlines.get_mut(&app.selection) {
|
||||||
match app.outlines.entry(app.selection.to_string()) {
|
|
||||||
Entry::Occupied(mut c) => {
|
|
||||||
let mut deletion_request = false;
|
|
||||||
let isolated_tracks = c.get_mut();
|
|
||||||
|
|
||||||
if ui.button("Delete").clicked() {
|
|
||||||
deletion_request = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("BoundingBox");
|
ui.label("BoundingBox");
|
||||||
let id = ui.make_persistent_id("BoundingBoxSelection");
|
ComboBox::from_label("Select one!")
|
||||||
ComboBox::new(id, "")
|
.selected_text(format!("{:?}", outline.bounds_from))
|
||||||
.selected_text(format!("{:?}", isolated_tracks.bounds_from))
|
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
for (key, (name, _)) in app.gerbers.iter() {
|
for (key, (name, _)) in app.gerbers.iter() {
|
||||||
if ui
|
if ui
|
||||||
.selectable_value(
|
.selectable_value(&mut outline.bounds_from, name.into(), name)
|
||||||
&mut isolated_tracks.bounds_from,
|
|
||||||
name.into(),
|
|
||||||
name,
|
|
||||||
)
|
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
if let Some((_, box_geo)) = app.gerbers.get(key) {
|
if let Some((_, box_geo)) = app.gerbers.get(key) {
|
||||||
isolated_tracks.bounding_box = box_geo.united_nets.bounds();
|
outline.bounding_box = box_geo.outline_union.bounds();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,16 +27,8 @@ pub fn show_geometry_actions(ui: &mut Ui, app: &mut Application) {
|
||||||
.add_filter("SVG", &["svg", "SVG"])
|
.add_filter("SVG", &["svg", "SVG"])
|
||||||
.save_file()
|
.save_file()
|
||||||
{
|
{
|
||||||
SVGConverter::export(isolated_tracks, &path);
|
SVGConverter::export(outline, &path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if deletion_request {
|
|
||||||
c.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Entry::Vacant(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,28 @@
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
|
|
||||||
use eframe::egui::{DragValue, Ui};
|
use eframe::egui::{DragValue, Ui};
|
||||||
|
|
||||||
use crate::{application::Application, outline_geometry::OutlineGeometry};
|
use crate::{application::Application, outline_geometry::OutlineGeometry};
|
||||||
|
|
||||||
pub fn show_gerber_actions(ui: &mut Ui, app: &mut Application) {
|
pub fn show_gerber_actions(ui: &mut Ui, app: &mut Application) {
|
||||||
if app.gerbers.contains_key(&app.selection) {
|
if let Some((name, geo)) = app.gerbers.get(&app.selection) {
|
||||||
match app.gerbers.entry(app.selection.to_string()) {
|
|
||||||
Entry::Occupied(c) => {
|
|
||||||
let mut deletion_request = false;
|
|
||||||
let (name, gerber) = c.get();
|
|
||||||
|
|
||||||
if ui.button("Delete").clicked() {
|
|
||||||
deletion_request = true;
|
|
||||||
}
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(
|
ui.add(
|
||||||
DragValue::new(&mut app.variables.laser_line_width)
|
DragValue::new(&mut app.variables.laser_line_width)
|
||||||
.range(0.1..=10.)
|
.range(0.1..=10.)
|
||||||
.speed(0.1)
|
.suffix(geo.units),
|
||||||
.suffix(gerber.units),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if ui.button("Generate Isolation").clicked() {
|
if ui.button("Generate Isolation").clicked() {
|
||||||
app.outlines.insert(
|
app.outlines.insert(
|
||||||
format!("{name}-Iso"),
|
format!("{name}-Iso"),
|
||||||
OutlineGeometry::new(
|
OutlineGeometry::new(
|
||||||
&gerber.united_nets,
|
&geo.outline_union,
|
||||||
app.variables.laser_line_width,
|
app.variables.laser_line_width,
|
||||||
gerber.units,
|
geo.units,
|
||||||
name,
|
name,
|
||||||
|
geo.outline_union.bounds(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if deletion_request {
|
|
||||||
c.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Entry::Vacant(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,192 +1,62 @@
|
||||||
use std::{
|
use std::{fs::File, io::BufReader};
|
||||||
fs::File,
|
|
||||||
io::{BufReader, Read},
|
|
||||||
};
|
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use error_stack::{Report, ResultExt};
|
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
application::{errors::FileError, Application},
|
application::Application,
|
||||||
excellon::{doc::ExcellonDoc, drills::Drills, parse_excellon},
|
excellon::{drills::Drills, parse_excellon},
|
||||||
geometry::Geometry,
|
geometry::Geometry,
|
||||||
gerber::{doc::GerberDoc, parse_gerber},
|
gerber::parse_gerber,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GERBER_EXTENSIONS: &[&str] = &["GBR ", "gbr", "GB", "geb"];
|
|
||||||
const EXCELLON_EXTENSIONS: &[&str] = &["DRL ", "drl"];
|
|
||||||
|
|
||||||
pub fn draw_header(ctx: &egui::Context, app: &mut Application) {
|
pub fn draw_header(ctx: &egui::Context, app: &mut Application) {
|
||||||
egui::TopBottomPanel::top("top_panel")
|
egui::TopBottomPanel::top("top_panel")
|
||||||
.exact_height(40.)
|
.exact_height(40.)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.horizontal_centered(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if let Some(opened_files) = ui
|
if ui.button("Open Gerber").clicked() {
|
||||||
.menu_button("📝 Open File", open_file_menu)
|
|
||||||
.inner
|
|
||||||
.flatten()
|
|
||||||
{
|
|
||||||
match opened_files {
|
|
||||||
Ok(files) => {
|
|
||||||
for (path, name, content) in files {
|
|
||||||
match content {
|
|
||||||
AppFileContent::Gerber(gerber) => {
|
|
||||||
app.gerbers.insert(
|
|
||||||
path,
|
|
||||||
(
|
|
||||||
name,
|
|
||||||
Geometry::from(gerber)
|
|
||||||
.into_unit(app.variables.units),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
AppFileContent::Excellon(excellon) => {
|
|
||||||
let drills: Drills = excellon.into();
|
|
||||||
app.excellons.insert(
|
|
||||||
path,
|
|
||||||
(name, drills.into_unit(app.variables.units)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// TODO Show error
|
|
||||||
error!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileOpenResponse = Result<Vec<(String, String, AppFileContent)>, Report<FileError>>;
|
|
||||||
|
|
||||||
fn open_file_menu(ui: &mut egui::Ui) -> Option<FileOpenResponse> {
|
|
||||||
let mut response = None;
|
|
||||||
ui.set_max_width(200.0); // To make sure we wrap long text
|
|
||||||
|
|
||||||
if ui.button("Gerber").clicked() {
|
|
||||||
response = Some(open_file(AppFileType::Gerber));
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.button("Excellon").clicked() {
|
|
||||||
ui.close_menu();
|
|
||||||
response = Some(open_file(AppFileType::Excellon));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ui.menu_button("SubMenu", |ui| {
|
|
||||||
// ui.menu_button("SubMenu", |ui| {
|
|
||||||
// if ui.button("Open…").clicked() {
|
|
||||||
// ui.close_menu();
|
|
||||||
// }
|
|
||||||
// let _ = ui.button("Item");
|
|
||||||
// });
|
|
||||||
// ui.menu_button("SubMenu", |ui| {
|
|
||||||
// if ui.button("Open…").clicked() {
|
|
||||||
// ui.close_menu();
|
|
||||||
// }
|
|
||||||
// let _ = ui.button("Item");
|
|
||||||
// });
|
|
||||||
// let _ = ui.button("Item");
|
|
||||||
// if ui.button("Open…").clicked() {
|
|
||||||
// ui.close_menu();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// ui.menu_button("SubMenu", |ui| {
|
|
||||||
// let _ = ui.button("Item1");
|
|
||||||
// let _ = ui.button("Item2");
|
|
||||||
// let _ = ui.button("Item3");
|
|
||||||
// let _ = ui.button("Item4");
|
|
||||||
// if ui.button("Open…").clicked() {
|
|
||||||
// ui.close_menu();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// let _ = ui.button("Very long text for this item that should be wrapped");
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum AppFileType {
|
|
||||||
Gerber,
|
|
||||||
Excellon,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for AppFileType {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
|
||||||
AppFileType::Gerber => "Gerber",
|
|
||||||
AppFileType::Excellon => "Excellon",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppFileType {
|
|
||||||
pub fn extensions(&self) -> &[&str] {
|
|
||||||
match self {
|
|
||||||
AppFileType::Gerber => GERBER_EXTENSIONS,
|
|
||||||
AppFileType::Excellon => EXCELLON_EXTENSIONS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum AppFileContent {
|
|
||||||
Gerber(GerberDoc),
|
|
||||||
Excellon(ExcellonDoc),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppFileContent {
|
|
||||||
fn from_file<T: Read>(
|
|
||||||
file_type: AppFileType,
|
|
||||||
file: BufReader<T>,
|
|
||||||
) -> Result<Self, Report<FileError>> {
|
|
||||||
Ok(match file_type {
|
|
||||||
AppFileType::Gerber => Self::Gerber(parse_gerber(file)),
|
|
||||||
AppFileType::Excellon => Self::Excellon(
|
|
||||||
parse_excellon(file)
|
|
||||||
.change_context(FileError)
|
|
||||||
.attach_printable("Could not parse Excellon file")?,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_file(
|
|
||||||
file_type: AppFileType,
|
|
||||||
) -> Result<Vec<(String, String, AppFileContent)>, Report<FileError>> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
if let Some(paths) = rfd::FileDialog::new()
|
if let Some(paths) = rfd::FileDialog::new()
|
||||||
.add_filter(file_type.to_string(), file_type.extensions())
|
.add_filter("Gerber", &["GBR ", "gbr", "GB", "geb"])
|
||||||
|
.pick_files()
|
||||||
|
{
|
||||||
|
// self.picked_path = Some(path.display().to_string());
|
||||||
|
for path in paths {
|
||||||
|
// TODO remove all unwraps
|
||||||
|
if let Ok(file) = File::open(&path) {
|
||||||
|
let gerber = parse_gerber(BufReader::new(file));
|
||||||
|
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
|
app.gerbers.insert(
|
||||||
|
path.to_str().unwrap().into(),
|
||||||
|
(name, Geometry::from(gerber).to_unit(app.variables.units)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// TODO show error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Open Excellon").clicked() {
|
||||||
|
if let Some(paths) = rfd::FileDialog::new()
|
||||||
|
.add_filter("Excellon", &["DRL ", "drl"])
|
||||||
.pick_files()
|
.pick_files()
|
||||||
{
|
{
|
||||||
for path in paths {
|
for path in paths {
|
||||||
// get file content
|
// TODO remove all unwraps
|
||||||
let file = File::open(&path).change_context(FileError)?;
|
if let Ok(file) = File::open(&path) {
|
||||||
// parse file format
|
let excellon = parse_excellon(BufReader::new(file)).unwrap();
|
||||||
let content = AppFileContent::from_file(file_type, BufReader::new(file))?;
|
let drills: Drills = excellon.into();
|
||||||
// get file path
|
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
let path_str = path
|
app.excellons.insert(
|
||||||
.to_str()
|
path.to_str().unwrap().into(),
|
||||||
.ok_or(FileError::new("Could not get file path string"))?;
|
(name, drills.to_unit(app.variables.units)),
|
||||||
// get file name
|
);
|
||||||
let name_str = path
|
} else {
|
||||||
.file_name()
|
// TODO show error
|
||||||
.ok_or(FileError::new(
|
};
|
||||||
"Could not determine file name from selection",
|
|
||||||
))?
|
|
||||||
.to_str()
|
|
||||||
.ok_or(FileError::new("Could not convert OS String"))?;
|
|
||||||
|
|
||||||
result.push((path_str.into(), name_str.into(), content));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(result)
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use eframe::egui::{self, RichText};
|
use eframe::egui::{self, CollapsingHeader};
|
||||||
|
|
||||||
use crate::{application::Application, APP_NAME};
|
use crate::{application::Application, APP_NAME};
|
||||||
|
|
||||||
|
@ -6,74 +6,30 @@ pub fn draw_sidebar(ctx: &egui::Context, app: &mut Application) {
|
||||||
egui::SidePanel::left("left_panel")
|
egui::SidePanel::left("left_panel")
|
||||||
.exact_width(230.)
|
.exact_width(230.)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.add_space(6.0);
|
|
||||||
|
|
||||||
ui.heading(APP_NAME);
|
ui.heading(APP_NAME);
|
||||||
|
CollapsingHeader::new("Gerber")
|
||||||
ui.add_space(12.0);
|
.default_open(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
let id = ui.make_persistent_id("GerberContainer");
|
|
||||||
egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true)
|
|
||||||
.show_header(ui, |ui| {
|
|
||||||
ui.horizontal_centered(|ui| {
|
|
||||||
ui.image(app.resources.icons().gerber_file.clone());
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Gerber")
|
|
||||||
.extra_letter_spacing(1.2)
|
|
||||||
.size(17.)
|
|
||||||
.strong(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.body(|ui| {
|
|
||||||
for (key, (name, _)) in app.gerbers.iter() {
|
for (key, (name, _)) in app.gerbers.iter() {
|
||||||
ui.selectable_value(
|
ui.selectable_value(&mut app.selection, key.to_string(), name);
|
||||||
&mut app.selection,
|
|
||||||
key.to_string(),
|
|
||||||
RichText::new(name).size(11.),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let id = ui.make_persistent_id("ExcellonContainer");
|
CollapsingHeader::new("Excellon")
|
||||||
egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true)
|
.default_open(true)
|
||||||
.show_header(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.horizontal_centered(|ui| {
|
|
||||||
ui.image(app.resources.icons().excellon_file.clone());
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Excellon")
|
|
||||||
.extra_letter_spacing(1.2)
|
|
||||||
.size(17.)
|
|
||||||
.strong(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.body(|ui| {
|
|
||||||
for (key, (name, _)) in app.excellons.iter() {
|
for (key, (name, _)) in app.excellons.iter() {
|
||||||
ui.selectable_value(&mut app.selection, key.to_string(), name);
|
ui.selectable_value(&mut app.selection, key.to_string(), name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let id = ui.make_persistent_id("GeometryContainer");
|
CollapsingHeader::new("Geometry")
|
||||||
egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true)
|
.default_open(true)
|
||||||
.show_header(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.horizontal_centered(|ui| {
|
|
||||||
ui.image(app.resources.icons().geometry_file.clone());
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Geometry")
|
|
||||||
.extra_letter_spacing(1.2)
|
|
||||||
.size(17.)
|
|
||||||
.strong(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.body(|ui| {
|
|
||||||
for (key, _) in app.outlines.iter() {
|
for (key, _) in app.outlines.iter() {
|
||||||
ui.selectable_value(&mut app.selection, key.to_string(), key);
|
ui.selectable_value(&mut app.selection, key.to_string(), key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// allocate rest of the container
|
|
||||||
let (id, rect) = ui.allocate_space(ui.available_size());
|
let (id, rect) = ui.allocate_space(ui.available_size());
|
||||||
let response = ui.interact(rect, id, egui::Sense::click_and_drag());
|
let response = ui.interact(rect, id, egui::Sense::click_and_drag());
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
|
|
|
@ -41,13 +41,13 @@ impl From<ExcellonDoc> for Drills {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drills {
|
impl Drills {
|
||||||
pub fn into_unit(self, unit: Unit) -> Self {
|
pub fn to_unit(&self, unit: Unit) -> Self {
|
||||||
Self {
|
Self {
|
||||||
units: unit,
|
units: unit,
|
||||||
holes: self
|
holes: self
|
||||||
.holes
|
.holes
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|hole| hole.into_unit(self.units, unit))
|
.map(|hole| hole.to_unit(self.units, unit))
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod doc;
|
mod doc;
|
||||||
pub mod drills;
|
pub mod drills;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
|
||||||
|
@ -271,6 +271,8 @@ mod tests {
|
||||||
let file = File::open("./FirstPCB-PTH.drl").unwrap();
|
let file = File::open("./FirstPCB-PTH.drl").unwrap();
|
||||||
let _reader = BufReader::new(file);
|
let _reader = BufReader::new(file);
|
||||||
|
|
||||||
let _excellon = parse_excellon(BufReader::new(excellon.as_bytes()));
|
let excellon = parse_excellon(BufReader::new(excellon.as_bytes()));
|
||||||
|
// let excellon = parse_excellon(reader);
|
||||||
|
println!("{:#?}", excellon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ use crate::geometry::Geometry;
|
||||||
pub struct DXFConverter;
|
pub struct DXFConverter;
|
||||||
|
|
||||||
impl DXFConverter {
|
impl DXFConverter {
|
||||||
pub fn export(_geometry: &Geometry, _file: &str) {
|
pub fn export(geometry: &Geometry, file: &str) {
|
||||||
let mut drawing = Drawing::new();
|
let mut drawing = Drawing::new();
|
||||||
let _added_entity_ref = drawing.add_entity(Entity::new(EntityType::Line(Line::default())));
|
let added_entity_ref = drawing.add_entity(Entity::new(EntityType::Line(Line::default())));
|
||||||
// `added_entity_ref` is a reference to the newly added entity
|
// `added_entity_ref` is a reference to the newly added entity
|
||||||
drawing.save_file("./file.dxf").unwrap();
|
drawing.save_file("./file.dxf").unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,8 @@ impl SVGConverter {
|
||||||
let points = geo.points().into_iter().map(|p| {
|
let points = geo.points().into_iter().map(|p| {
|
||||||
Circle::new()
|
Circle::new()
|
||||||
.set("r", HOLE_MARK_DIAMETER)
|
.set("r", HOLE_MARK_DIAMETER)
|
||||||
.set("cx", p.x())
|
.set("cx", p.x)
|
||||||
.set("cy", p.invert_y().y())
|
.set("cy", p.invert_y().y)
|
||||||
.set("fill", "black")
|
.set("fill", "black")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
use std::f64::consts::{PI, TAU};
|
use std::f64::consts::{PI, TAU};
|
||||||
|
|
||||||
use eframe::egui::{remap, Stroke};
|
use eframe::{
|
||||||
|
egui::{remap, Stroke, Ui},
|
||||||
|
epaint::CircleShape,
|
||||||
|
};
|
||||||
use egui_plot::{PlotPoints, PlotUi, Polygon};
|
use egui_plot::{PlotPoints, PlotUi, Polygon};
|
||||||
use gerber_types::CirclePrimitive;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
application::CanvasColour,
|
application::CanvasColour,
|
||||||
geometry::{ClipperPath, ClipperPaths},
|
geometry::{ClipperPath, ClipperPaths},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::super::{
|
||||||
super::{
|
|
||||||
helpers::{create_circular_path, CircleSegment},
|
helpers::{create_circular_path, CircleSegment},
|
||||||
point::{convert_to_unit, Point},
|
point::{convert_to_unit, Point},
|
||||||
DrawableRaw, Unit,
|
DrawableRaw, Unit,
|
||||||
},
|
|
||||||
macro_decimal_to_f64,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -30,7 +29,7 @@ impl Circle {
|
||||||
pub fn new(position: impl Into<Point>, diameter: f64, hole_diameter: Option<f64>) -> Self {
|
pub fn new(position: impl Into<Point>, diameter: f64, hole_diameter: Option<f64>) -> Self {
|
||||||
let position = position.into();
|
let position = position.into();
|
||||||
Self {
|
Self {
|
||||||
position,
|
position: position,
|
||||||
diameter,
|
diameter,
|
||||||
hole_diameter,
|
hole_diameter,
|
||||||
outline: create_circular_path(&position, diameter, CircleSegment::Full).into(),
|
outline: create_circular_path(&position, diameter, CircleSegment::Full).into(),
|
||||||
|
@ -40,19 +39,8 @@ impl Circle {
|
||||||
Self::new(position, aperture.diameter, aperture.hole_diameter)
|
Self::new(position, aperture.diameter, aperture.hole_diameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_macro(ap_macro: &CirclePrimitive, variables: &[f64], target: Point) -> Self {
|
pub fn to_unit(&self, origin: Unit, to: Unit) -> Self {
|
||||||
Self::new(
|
let position = self.position.to_unit(origin, to);
|
||||||
Point::new(
|
|
||||||
macro_decimal_to_f64(&ap_macro.center.0, variables) + target.x(),
|
|
||||||
macro_decimal_to_f64(&ap_macro.center.1, variables) + target.y(),
|
|
||||||
),
|
|
||||||
macro_decimal_to_f64(&ap_macro.diameter, variables),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
|
||||||
let position = self.position.into_unit(origin, to);
|
|
||||||
let diameter = convert_to_unit(self.diameter, origin, to);
|
let diameter = convert_to_unit(self.diameter, origin, to);
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
|
@ -63,6 +51,17 @@ impl Circle {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_circle(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool) {
|
fn draw_circle(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool) {
|
||||||
|
// let n = 512;
|
||||||
|
// let circle_points: PlotPoints = (0..=n)
|
||||||
|
// .map(|i| {
|
||||||
|
// let t = remap(i as f64, 0.0..=(n as f64), 0.0..=TAU);
|
||||||
|
// let r = self.diameter / 2.;
|
||||||
|
// [
|
||||||
|
// r * t.cos() + self.position.x as f64,
|
||||||
|
// r * t.sin() + self.position.y as f64,
|
||||||
|
// ]
|
||||||
|
// })
|
||||||
|
// .collect();
|
||||||
let circle_points = PlotPoints::from(Self::circle_segment_points(
|
let circle_points = PlotPoints::from(Self::circle_segment_points(
|
||||||
self.position,
|
self.position,
|
||||||
self.diameter,
|
self.diameter,
|
||||||
|
@ -72,7 +71,7 @@ impl Circle {
|
||||||
|
|
||||||
ui.polygon(
|
ui.polygon(
|
||||||
Polygon::new(circle_points)
|
Polygon::new(circle_points)
|
||||||
.fill_color(colour.as_colour32(selected))
|
.fill_color(colour.to_colour32(selected))
|
||||||
.stroke(Stroke::NONE),
|
.stroke(Stroke::NONE),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -85,15 +84,16 @@ impl Circle {
|
||||||
) -> Vec<[f64; 2]> {
|
) -> Vec<[f64; 2]> {
|
||||||
let segment_width = segment_width.clamp(0.0, 1.0);
|
let segment_width = segment_width.clamp(0.0, 1.0);
|
||||||
let n = (512. * segment_width) as i32;
|
let n = (512. * segment_width) as i32;
|
||||||
|
let circle_points = (0..=n)
|
||||||
(0..=n)
|
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let t = remap(i as f64, 0.0..=(n as f64), 0.0..=TAU * segment_width)
|
let t = remap(i as f64, 0.0..=(n as f64), 0.0..=TAU * segment_width)
|
||||||
+ rotation * (PI / 180.);
|
+ rotation * (PI / 180.);
|
||||||
let r = diameter / 2.;
|
let r = diameter / 2.;
|
||||||
[r * t.cos() + position.x(), r * t.sin() + position.y()]
|
[r * t.cos() + position.x, r * t.sin() + position.y]
|
||||||
})
|
})
|
||||||
.collect()
|
.collect();
|
||||||
|
|
||||||
|
circle_points
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,31 @@ impl DrawableRaw for Circle {
|
||||||
fn canvas_pos(&self) -> Point {
|
fn canvas_pos(&self) -> Point {
|
||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_egui(&self, ui: &mut Ui, selected: bool) {
|
||||||
|
ui.painter().add(CircleShape::filled(
|
||||||
|
self.position.invert_y().into(),
|
||||||
|
self.diameter as f32 / 2.,
|
||||||
|
CanvasColour::Copper.to_colour32(selected),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) {
|
fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) {
|
||||||
|
// let circle_points: Vec<[f64; 2]> =
|
||||||
|
// create_circular_path(&self.position, self.diameter, CircleSegment::Full)
|
||||||
|
// .iter()
|
||||||
|
// .map(|(x, y)| [*x, *y])
|
||||||
|
// .collect();
|
||||||
|
|
||||||
|
// let polygon = Polygon::new(PlotPoints::from(circle_points))
|
||||||
|
// .fill_color(if selected {
|
||||||
|
// COPPER_COLOR_SELECTED
|
||||||
|
// } else {
|
||||||
|
// COPPER_COLOR
|
||||||
|
// })
|
||||||
|
// .stroke(Stroke::new(0., Color32::TRANSPARENT));
|
||||||
|
|
||||||
|
// ui.polygon(polygon);
|
||||||
self.draw_circle(ui, colour, selected);
|
self.draw_circle(ui, colour, selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use clipper2::PointInPolygonResult;
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
application::CanvasColour,
|
|
||||||
geometry::{point::Point, union::union_function, ClipperPath, ClipperPaths, DrawableRaw, Unit},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::Element;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Composite {
|
|
||||||
elements: Vec<Element>,
|
|
||||||
outline: ClipperPaths,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Composite {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
elements: Vec::new(),
|
|
||||||
outline: ClipperPaths::new(vec![]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&mut self, element: Element) {
|
|
||||||
self.elements.push(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finalize(&mut self) {
|
|
||||||
let mut intersection_map: HashMap<usize, Vec<usize>> = HashMap::new();
|
|
||||||
|
|
||||||
for part in self.elements.iter().enumerate().combinations(2) {
|
|
||||||
let (index1, element1) = part[0];
|
|
||||||
let (index2, element2) = part[1];
|
|
||||||
|
|
||||||
// check if element1 and element2 intersect
|
|
||||||
if element1
|
|
||||||
.outline()
|
|
||||||
.iter()
|
|
||||||
.any(|p| element2.outline().is_point_inside(*p) != PointInPolygonResult::IsOutside)
|
|
||||||
{
|
|
||||||
// add intersection result for both indices
|
|
||||||
let entry1 = intersection_map.entry(index1).or_default();
|
|
||||||
entry1.push(index2);
|
|
||||||
let entry2 = intersection_map.entry(index2).or_default();
|
|
||||||
entry2.push(index1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go through all aperture components
|
|
||||||
for i in 0..intersection_map.len() {
|
|
||||||
// remove component from map
|
|
||||||
if let Some(mut intersections) = intersection_map.remove(&i) {
|
|
||||||
// take outline of element as base
|
|
||||||
let mut geo = self.elements[i].to_paths();
|
|
||||||
|
|
||||||
// union with intersecting aperture components until done
|
|
||||||
while let Some(other) = intersections.pop() {
|
|
||||||
// union with intersecting aperture component
|
|
||||||
let intersecting_element = &self.elements[other];
|
|
||||||
if let Some(union) = union_function(&geo, &intersecting_element.to_paths()) {
|
|
||||||
geo = union;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get intersections of united aperture component and add them if not already in own
|
|
||||||
if let Some(new_intersections) = intersection_map.remove(&other) {
|
|
||||||
for o in new_intersections {
|
|
||||||
// add to original line if not self and not already inside
|
|
||||||
if o != i // ensure to not intersect with itself
|
|
||||||
&& !intersections.contains(&o) // do not add if already in intersection list
|
|
||||||
&& intersection_map.contains_key(&o)
|
|
||||||
// check if intersection was already performed
|
|
||||||
{
|
|
||||||
intersections.push(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.outline.push(geo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
|
||||||
Self {
|
|
||||||
elements: self
|
|
||||||
.elements
|
|
||||||
.into_iter()
|
|
||||||
.map(|el| el.into_unit(origin, to))
|
|
||||||
.collect(),
|
|
||||||
outline: self.outline,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DrawableRaw for Composite {
|
|
||||||
fn canvas_pos(&self) -> Point {
|
|
||||||
// self.position
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) {
|
|
||||||
for element in &self.elements {
|
|
||||||
element.draw_egui_plot(ui, colour, selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_paths(&self) -> ClipperPaths {
|
|
||||||
self.outline.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn outline(&self) -> ClipperPath {
|
|
||||||
self.outline.first().unwrap().clone()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,14 @@
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::PI;
|
||||||
|
|
||||||
use clipper2::PointInPolygonResult;
|
use eframe::{
|
||||||
use eframe::egui::Stroke;
|
egui::{Pos2, Stroke, Ui},
|
||||||
|
epaint::{CircleShape, PathShape, PathStroke},
|
||||||
|
};
|
||||||
use egui_plot::{PlotPoints, Polygon};
|
use egui_plot::{PlotPoints, Polygon};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
application::CanvasColour,
|
application::CanvasColour,
|
||||||
geometry::{helpers::semi_circle, ClipperPath, ClipperPaths, ClipperPoint},
|
geometry::{helpers::semi_circle, ClipperPath, ClipperPaths},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::{
|
use super::super::{
|
||||||
|
@ -45,16 +47,11 @@ impl LinePath {
|
||||||
pub fn finalize(&mut self, stroke_width: f64) {
|
pub fn finalize(&mut self, stroke_width: f64) {
|
||||||
self.diameter = stroke_width;
|
self.diameter = stroke_width;
|
||||||
self.outline = self.create_outline();
|
self.outline = self.create_outline();
|
||||||
println!("Line with diameter: {stroke_width}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
pub fn to_unit(&self, origin: Unit, to: Unit) -> Self {
|
||||||
let mut converted = Self {
|
let mut converted = Self {
|
||||||
points: self
|
points: self.points.iter().map(|p| p.to_unit(origin, to)).collect(),
|
||||||
.points
|
|
||||||
.iter()
|
|
||||||
.map(|p| p.into_unit(origin, to))
|
|
||||||
.collect(),
|
|
||||||
diameter: convert_to_unit(self.diameter, origin, to),
|
diameter: convert_to_unit(self.diameter, origin, to),
|
||||||
outline: ClipperPaths::new(vec![]),
|
outline: ClipperPaths::new(vec![]),
|
||||||
};
|
};
|
||||||
|
@ -63,14 +60,6 @@ impl LinePath {
|
||||||
converted
|
converted
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outline_includes_points(&self, points: &[Point]) -> bool {
|
|
||||||
self.outline.iter().any(|path| {
|
|
||||||
points.iter().any(|point| {
|
|
||||||
path.is_point_inside(ClipperPoint::from(point)) != PointInPolygonResult::IsOutside
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_outline(&self) -> ClipperPaths {
|
fn create_outline(&self) -> ClipperPaths {
|
||||||
let mut paths: Vec<ClipperPath> = Vec::new();
|
let mut paths: Vec<ClipperPath> = Vec::new();
|
||||||
|
|
||||||
|
@ -102,12 +91,33 @@ impl DrawableRaw for &LinePath {
|
||||||
points.push(points[0]);
|
points.push(points[0]);
|
||||||
|
|
||||||
let poly = Polygon::new(PlotPoints::from(points))
|
let poly = Polygon::new(PlotPoints::from(points))
|
||||||
.fill_color(colour.as_colour32(selected))
|
.fill_color(colour.to_colour32(selected))
|
||||||
.stroke(Stroke::NONE);
|
.stroke(Stroke::NONE);
|
||||||
ui.polygon(poly);
|
ui.polygon(poly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_egui(&self, ui: &mut Ui, selected: bool) {
|
||||||
|
ui.painter().add(PathShape::line(
|
||||||
|
self.points
|
||||||
|
.iter()
|
||||||
|
.map(|v| (*v).invert_y().into())
|
||||||
|
.collect::<Vec<Pos2>>(),
|
||||||
|
PathStroke::new(
|
||||||
|
self.diameter as f32,
|
||||||
|
CanvasColour::Copper.to_colour32(selected),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
for point in &self.points {
|
||||||
|
ui.painter().add(CircleShape::filled(
|
||||||
|
point.invert_y().into(),
|
||||||
|
self.diameter as f32 / 2.,
|
||||||
|
CanvasColour::Copper.to_colour32(selected),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_paths(&self) -> ClipperPaths {
|
fn to_paths(&self) -> ClipperPaths {
|
||||||
self.outline.clone()
|
self.outline.clone()
|
||||||
}
|
}
|
||||||
|
@ -122,7 +132,7 @@ fn create_outline_between_points(point1: Point, point2: Point, width: f64) -> Ve
|
||||||
let normalized = line_vec.normalize();
|
let normalized = line_vec.normalize();
|
||||||
|
|
||||||
let angle =
|
let angle =
|
||||||
(normalized.x()).acos() * (180. / PI) * if normalized.y() < 0. { -1. } else { 1. } + 90.;
|
(normalized.x).acos() * (180. / PI) * if normalized.y < 0. { -1. } else { 1. } + 90.;
|
||||||
|
|
||||||
let mut outline: Vec<(f64, f64)> = Vec::new();
|
let mut outline: Vec<(f64, f64)> = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -1,126 +1,79 @@
|
||||||
use circle::Circle;
|
use circle::Circle;
|
||||||
use composite::Composite;
|
use eframe::egui::Ui;
|
||||||
use egui_plot::PlotUi;
|
use egui_plot::PlotUi;
|
||||||
use evalexpr::eval_number;
|
|
||||||
use gerber_types::MacroDecimal;
|
|
||||||
use lazy_regex::regex;
|
|
||||||
use linepath::LinePath;
|
use linepath::LinePath;
|
||||||
use obround::Obround;
|
use obround::Obround;
|
||||||
use polygon::Polygon;
|
|
||||||
use rectangle::Rectangle;
|
use rectangle::Rectangle;
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
use crate::application::CanvasColour;
|
use crate::application::CanvasColour;
|
||||||
|
|
||||||
use super::{point::Point, ClipperPath, ClipperPaths, DrawableRaw, Unit};
|
use super::{point::Point, ClipperPath, ClipperPaths, DrawableRaw, Unit};
|
||||||
|
|
||||||
pub mod circle;
|
pub mod circle;
|
||||||
pub mod composite;
|
|
||||||
pub mod linepath;
|
pub mod linepath;
|
||||||
pub mod obround;
|
pub mod obround;
|
||||||
pub mod polygon;
|
|
||||||
pub mod rectangle;
|
pub mod rectangle;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub enum Element {
|
pub enum Element {
|
||||||
Circle(Circle),
|
Circle(Circle),
|
||||||
Composite(Composite),
|
|
||||||
Rectangle(Rectangle),
|
Rectangle(Rectangle),
|
||||||
_Line(LinePath),
|
Line(LinePath),
|
||||||
Obround(Obround),
|
Obround(Obround),
|
||||||
Polygon(Polygon),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element {
|
impl Element {
|
||||||
pub fn draw_egui_plot(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool) {
|
pub fn draw_egui_plot(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool) {
|
||||||
match self {
|
match self {
|
||||||
Element::Circle(c) => c.draw_egui_plot(ui, colour, selected),
|
Element::Circle(c) => c.draw_egui_plot(ui, colour, selected),
|
||||||
Element::Composite(co) => co.draw_egui_plot(ui, colour, selected),
|
|
||||||
Element::Rectangle(r) => r.draw_egui_plot(ui, colour, selected),
|
Element::Rectangle(r) => r.draw_egui_plot(ui, colour, selected),
|
||||||
Element::_Line(l) => l.draw_egui_plot(ui, colour, selected),
|
Element::Line(l) => l.draw_egui_plot(ui, colour, selected),
|
||||||
Element::Obround(o) => o.draw_egui_plot(ui, colour, selected),
|
Element::Obround(o) => o.draw_egui_plot(ui, colour, selected),
|
||||||
Element::Polygon(p) => p.draw_egui_plot(ui, colour, selected),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_egui(&self, ui: &mut Ui, selected: bool) {
|
||||||
|
match self {
|
||||||
|
Element::Circle(c) => c.draw_egui(ui, selected),
|
||||||
|
Element::Rectangle(r) => r.draw_egui(ui, selected),
|
||||||
|
Element::Line(l) => l.draw_egui(ui, selected),
|
||||||
|
Element::Obround(o) => o.draw_egui(ui, selected),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canvas_pos(&self) -> Point {
|
pub fn canvas_pos(&self) -> Point {
|
||||||
match self {
|
match self {
|
||||||
Element::Circle(c) => c.canvas_pos(),
|
Element::Circle(c) => c.canvas_pos(),
|
||||||
Element::Composite(c) => c.canvas_pos(),
|
|
||||||
Element::Rectangle(r) => r.canvas_pos(),
|
Element::Rectangle(r) => r.canvas_pos(),
|
||||||
Element::_Line(l) => l.canvas_pos(),
|
Element::Line(l) => l.canvas_pos(),
|
||||||
Element::Obround(o) => o.canvas_pos(),
|
Element::Obround(o) => o.canvas_pos(),
|
||||||
Element::Polygon(p) => p.canvas_pos(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_paths(&self) -> ClipperPaths {
|
pub fn to_paths(&self) -> ClipperPaths {
|
||||||
match self {
|
match self {
|
||||||
Element::Circle(c) => c.to_paths(),
|
Element::Circle(c) => c.to_paths(),
|
||||||
Element::Composite(c) => c.to_paths(),
|
|
||||||
Element::Rectangle(r) => r.to_paths(),
|
Element::Rectangle(r) => r.to_paths(),
|
||||||
Element::_Line(l) => l.to_paths(),
|
Element::Line(l) => l.to_paths(),
|
||||||
Element::Obround(o) => o.to_paths(),
|
Element::Obround(o) => o.to_paths(),
|
||||||
Element::Polygon(p) => p.to_paths(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outline(&self) -> ClipperPath {
|
pub fn outline(&self) -> ClipperPath {
|
||||||
match self {
|
match self {
|
||||||
Element::Circle(c) => c.outline(),
|
Element::Circle(c) => c.outline(),
|
||||||
Element::Composite(c) => c.outline(),
|
|
||||||
Element::Rectangle(r) => r.outline(),
|
Element::Rectangle(r) => r.outline(),
|
||||||
Element::_Line(l) => l.outline(),
|
Element::Line(l) => l.outline(),
|
||||||
Element::Obround(o) => o.outline(),
|
Element::Obround(o) => o.outline(),
|
||||||
Element::Polygon(p) => p.outline(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
pub fn to_unit(&self, origin: Unit, to: Unit) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Element::Circle(c) => Element::Circle(c.into_unit(origin, to)),
|
Element::Circle(c) => Element::Circle(c.to_unit(origin, to)),
|
||||||
Element::Composite(c) => Element::Composite(c.into_unit(origin, to)),
|
Element::Rectangle(r) => Element::Rectangle(r.to_unit(origin, to)),
|
||||||
Element::Rectangle(r) => Element::Rectangle(r.into_unit(origin, to)),
|
Element::Line(l) => Element::Line(l.to_unit(origin, to)),
|
||||||
Element::_Line(l) => Element::_Line(l.into_unit(origin, to)),
|
Element::Obround(o) => Element::Obround(o.to_unit(origin, to)),
|
||||||
Element::Obround(o) => Element::Obround(o.into_unit(origin, to)),
|
|
||||||
Element::Polygon(p) => Element::Polygon(p.into_unit(origin, to)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn macro_decimal_to_f64(macro_decimal: &MacroDecimal, variables: &[f64]) -> f64 {
|
|
||||||
let re_units = regex!(r#"(\$\d+)"#);
|
|
||||||
|
|
||||||
match macro_decimal {
|
|
||||||
MacroDecimal::Value(v) => *v,
|
|
||||||
MacroDecimal::Variable(v) => *variables.get(*v as usize).unwrap_or(&0.),
|
|
||||||
MacroDecimal::Expression(ex) => {
|
|
||||||
// search for variables (eg. $1)
|
|
||||||
if let Some(captures) = re_units.captures(ex) {
|
|
||||||
// copy expression
|
|
||||||
let mut expr = ex.clone();
|
|
||||||
// go through all found variable names
|
|
||||||
for c in captures.iter().skip(1).flatten() {
|
|
||||||
// get variable name
|
|
||||||
let matched = c.as_str();
|
|
||||||
// create index out of variable
|
|
||||||
let index = matched.replace('$', "").parse::<usize>().unwrap();
|
|
||||||
// replace variable name with value
|
|
||||||
expr = expr.replace(
|
|
||||||
matched,
|
|
||||||
&variables.get(index - 1).unwrap_or(&0.).to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
match eval_number(&expr) {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(e) => {
|
|
||||||
error!("{e:?}");
|
|
||||||
0.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use eframe::egui::Stroke;
|
use eframe::{
|
||||||
|
egui::{Rect, Rounding, Stroke, Ui, Vec2},
|
||||||
|
epaint::RectShape,
|
||||||
|
};
|
||||||
use egui_plot::{PlotPoints, Polygon};
|
use egui_plot::{PlotPoints, Polygon};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -6,21 +9,24 @@ use crate::{
|
||||||
geometry::{ClipperPath, ClipperPaths},
|
geometry::{ClipperPath, ClipperPaths},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::{
|
use super::rectangle::Rectangle;
|
||||||
|
use super::{
|
||||||
|
super::{
|
||||||
helpers::{create_circular_path, semi_circle, CircleSegment},
|
helpers::{create_circular_path, semi_circle, CircleSegment},
|
||||||
point::{convert_to_unit, Point},
|
point::{convert_to_unit, Point},
|
||||||
DrawableRaw, Unit,
|
DrawableRaw, Unit,
|
||||||
|
},
|
||||||
|
circle::Circle,
|
||||||
};
|
};
|
||||||
use super::rectangle::Rectangle;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct Obround {
|
pub struct Obround {
|
||||||
position: Point,
|
position: Point,
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
_rounding: f64,
|
rounding: f64,
|
||||||
outline: ClipperPath,
|
outline: ClipperPath,
|
||||||
_rectangle: Rectangle,
|
rectangle: Rectangle,
|
||||||
hole_diameter: Option<f64>,
|
hole_diameter: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,27 +40,12 @@ impl Obround {
|
||||||
// check if obround is round to x or y
|
// check if obround is round to x or y
|
||||||
if x < y {
|
if x < y {
|
||||||
// round on y axis
|
// round on y axis
|
||||||
path.append(&mut semi_circle(
|
path.append(&mut semi_circle(position - Point::new(0., y / 4.), x, 180.));
|
||||||
position - Point::new(0., y / 2. - x / 2.),
|
path.append(&mut semi_circle(position + Point::new(0., y / 4.), x, 0.));
|
||||||
x,
|
|
||||||
180.,
|
|
||||||
));
|
|
||||||
path.append(&mut semi_circle(
|
|
||||||
position + Point::new(0., y / 2. - x / 2.),
|
|
||||||
x,
|
|
||||||
0.,
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
path.append(&mut semi_circle(
|
// TODO round on x axis -> check for correctness!!!!!!!
|
||||||
position + Point::new(x / 2. - y / 2., 0.),
|
path.append(&mut semi_circle(position - Point::new(0., x / 4.), y, 270.));
|
||||||
y,
|
path.append(&mut semi_circle(position + Point::new(0., x / 4.), y, 90.));
|
||||||
270.,
|
|
||||||
));
|
|
||||||
path.append(&mut semi_circle(
|
|
||||||
position - Point::new(x / 2. - y / 2., 0.),
|
|
||||||
y,
|
|
||||||
90.,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path
|
path
|
||||||
|
@ -62,12 +53,12 @@ impl Obround {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
x,
|
x: x,
|
||||||
y,
|
y: y,
|
||||||
_rounding: diameter,
|
rounding: diameter,
|
||||||
outline: outline.into(),
|
outline: outline.into(),
|
||||||
_rectangle: Rectangle::new(position, x, y, hole_diameter),
|
rectangle: Rectangle::new(position, x, y, hole_diameter),
|
||||||
hole_diameter,
|
hole_diameter: hole_diameter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +66,9 @@ impl Obround {
|
||||||
Self::new(position, aperture.x, aperture.y, aperture.hole_diameter)
|
Self::new(position, aperture.x, aperture.y, aperture.hole_diameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
pub fn to_unit(&self, origin: Unit, to: Unit) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
self.position.into_unit(origin, to),
|
self.position.to_unit(origin, to),
|
||||||
convert_to_unit(self.x, origin, to),
|
convert_to_unit(self.x, origin, to),
|
||||||
convert_to_unit(self.y, origin, to),
|
convert_to_unit(self.y, origin, to),
|
||||||
self.hole_diameter.map(|d| convert_to_unit(d, origin, to)),
|
self.hole_diameter.map(|d| convert_to_unit(d, origin, to)),
|
||||||
|
@ -95,11 +86,22 @@ impl DrawableRaw for Obround {
|
||||||
points.push(points[0]);
|
points.push(points[0]);
|
||||||
|
|
||||||
let poly = Polygon::new(PlotPoints::from(points))
|
let poly = Polygon::new(PlotPoints::from(points))
|
||||||
.fill_color(colour.as_colour32(selected))
|
.fill_color(colour.to_colour32(selected))
|
||||||
.stroke(Stroke::NONE);
|
.stroke(Stroke::NONE);
|
||||||
ui.polygon(poly);
|
ui.polygon(poly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_egui(&self, ui: &mut Ui, selected: bool) {
|
||||||
|
ui.painter().add(RectShape::filled(
|
||||||
|
Rect::from_center_size(
|
||||||
|
self.position.invert_y().into(),
|
||||||
|
Vec2::new(self.rectangle.width as f32, self.rectangle.height as f32),
|
||||||
|
),
|
||||||
|
Rounding::same(self.rounding as f32 / 2.),
|
||||||
|
CanvasColour::Copper.to_colour32(selected),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
fn to_paths(&self) -> ClipperPaths {
|
fn to_paths(&self) -> ClipperPaths {
|
||||||
ClipperPaths::new(vec![self.outline.clone()])
|
ClipperPaths::new(vec![self.outline.clone()])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
use eframe::egui::Stroke;
|
|
||||||
use gerber_types::OutlinePrimitive;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
application::CanvasColour,
|
|
||||||
geometry::{
|
|
||||||
elements::macro_decimal_to_f64, point::Point, ClipperPath, ClipperPaths, DrawableRaw, Unit,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Polygon {
|
|
||||||
points: Vec<Point>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Polygon {
|
|
||||||
pub fn new(points: &[Point]) -> Self {
|
|
||||||
Self {
|
|
||||||
points: points.to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
|
||||||
Self {
|
|
||||||
points: self
|
|
||||||
.points
|
|
||||||
.iter()
|
|
||||||
.map(|p| p.into_unit(origin, to))
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_macro(ap_macro: &OutlinePrimitive, variables: &[f64], target: Point) -> Self {
|
|
||||||
Self {
|
|
||||||
points: ap_macro
|
|
||||||
.points
|
|
||||||
.iter()
|
|
||||||
.map(|(x, y)| {
|
|
||||||
Point::new(
|
|
||||||
macro_decimal_to_f64(x, variables),
|
|
||||||
macro_decimal_to_f64(y, variables),
|
|
||||||
) + target
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DrawableRaw for Polygon {
|
|
||||||
fn canvas_pos(&self) -> Point {
|
|
||||||
// self.position
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) {
|
|
||||||
let points: Vec<[f64; 2]> = self.points.iter().map(|p| [p.x(), p.y()]).collect();
|
|
||||||
|
|
||||||
ui.polygon(
|
|
||||||
egui_plot::Polygon::new(points)
|
|
||||||
.fill_color(colour.as_colour32(selected))
|
|
||||||
.stroke(Stroke::NONE),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_paths(&self) -> ClipperPaths {
|
|
||||||
ClipperPaths::new(vec![self.outline()])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn outline(&self) -> ClipperPath {
|
|
||||||
ClipperPath::new(self.points.iter().map(|p| p.into()).collect())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +1,20 @@
|
||||||
use eframe::egui::Stroke;
|
use eframe::{
|
||||||
|
egui::{Rect, Rounding, Stroke, Ui, Vec2},
|
||||||
|
epaint::RectShape,
|
||||||
|
};
|
||||||
use egui_plot::{PlotPoints, Polygon};
|
use egui_plot::{PlotPoints, Polygon};
|
||||||
use gerber_types::{CenterLinePrimitive, VectorLinePrimitive};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
application::CanvasColour,
|
application::CanvasColour,
|
||||||
geometry::{ClipperPath, ClipperPaths},
|
geometry::{ClipperPath, ClipperPaths},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::super::{
|
||||||
super::{
|
|
||||||
point::{convert_to_unit, Point},
|
point::{convert_to_unit, Point},
|
||||||
DrawableRaw, Unit,
|
DrawableRaw, Unit,
|
||||||
},
|
|
||||||
macro_decimal_to_f64,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct Rectangle {
|
pub struct Rectangle {
|
||||||
position: Point,
|
position: Point,
|
||||||
pub width: f64,
|
pub width: f64,
|
||||||
|
@ -35,26 +34,11 @@ impl Rectangle {
|
||||||
height: aperture.y,
|
height: aperture.y,
|
||||||
hole_diameter: aperture.hole_diameter,
|
hole_diameter: aperture.hole_diameter,
|
||||||
outline: vec![
|
outline: vec![
|
||||||
(
|
(position.x - aperture.x / 2., position.y - aperture.y / 2.),
|
||||||
position.x() - aperture.x / 2.,
|
(position.x - aperture.x / 2., position.y + aperture.y / 2.),
|
||||||
position.y() - aperture.y / 2.,
|
(position.x + aperture.x / 2., position.y + aperture.y / 2.),
|
||||||
),
|
(position.x + aperture.x / 2., position.y - aperture.y / 2.),
|
||||||
(
|
(position.x - aperture.x / 2., position.y - aperture.y / 2.),
|
||||||
position.x() - aperture.x / 2.,
|
|
||||||
position.y() + aperture.y / 2.,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
position.x() + aperture.x / 2.,
|
|
||||||
position.y() + aperture.y / 2.,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
position.x() + aperture.x / 2.,
|
|
||||||
position.y() - aperture.y / 2.,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
position.x() - aperture.x / 2.,
|
|
||||||
position.y() - aperture.y / 2.,
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
|
@ -67,66 +51,18 @@ impl Rectangle {
|
||||||
height,
|
height,
|
||||||
hole_diameter,
|
hole_diameter,
|
||||||
outline: vec![
|
outline: vec![
|
||||||
(position.x() - width / 2., position.y() - height / 2.),
|
(position.x - width / 2., position.y - height / 2.),
|
||||||
(position.x() - width / 2., position.y() + height / 2.),
|
(position.x - width / 2., position.y + height / 2.),
|
||||||
(position.x() + width / 2., position.y() + height / 2.),
|
(position.x + width / 2., position.y + height / 2.),
|
||||||
(position.x() + width / 2., position.y() - height / 2.),
|
(position.x + width / 2., position.y - height / 2.),
|
||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_macro_vector_line(
|
pub fn to_unit(&self, origin: Unit, to: Unit) -> Self {
|
||||||
ap_macro: &VectorLinePrimitive,
|
|
||||||
variables: &[f64],
|
|
||||||
target: Point,
|
|
||||||
) -> Self {
|
|
||||||
// TODO angle
|
|
||||||
let x_start = macro_decimal_to_f64(&ap_macro.start.0, variables);
|
|
||||||
let x_end = macro_decimal_to_f64(&ap_macro.end.0, variables);
|
|
||||||
let y_start = macro_decimal_to_f64(&ap_macro.start.1, variables);
|
|
||||||
let y_end = macro_decimal_to_f64(&ap_macro.end.1, variables);
|
|
||||||
|
|
||||||
let height = if x_end - x_start == 0. {
|
|
||||||
macro_decimal_to_f64(&ap_macro.width, variables)
|
|
||||||
} else {
|
|
||||||
x_end - x_start
|
|
||||||
};
|
|
||||||
let width = if y_end - y_start == 0. {
|
|
||||||
macro_decimal_to_f64(&ap_macro.width, variables)
|
|
||||||
} else {
|
|
||||||
y_end - y_start
|
|
||||||
};
|
|
||||||
|
|
||||||
Self::new(
|
Self::new(
|
||||||
Point::new(x_start, y_start)
|
self.position.to_unit(origin, to),
|
||||||
+ (Point::new(x_end, y_end) - Point::new(x_start, y_start)).div(2.)
|
|
||||||
+ target,
|
|
||||||
height,
|
|
||||||
width,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pub fn from_macro_center_line(
|
|
||||||
ap_macro: &CenterLinePrimitive,
|
|
||||||
variables: &[f64],
|
|
||||||
target: Point,
|
|
||||||
) -> Self {
|
|
||||||
// TODO angle
|
|
||||||
Self::new(
|
|
||||||
Point::new(
|
|
||||||
macro_decimal_to_f64(&ap_macro.center.0, variables),
|
|
||||||
macro_decimal_to_f64(&ap_macro.center.1, variables),
|
|
||||||
) + target,
|
|
||||||
macro_decimal_to_f64(&ap_macro.dimensions.0, variables),
|
|
||||||
macro_decimal_to_f64(&ap_macro.dimensions.1, variables),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
|
||||||
Self::new(
|
|
||||||
self.position.into_unit(origin, to),
|
|
||||||
convert_to_unit(self.width, origin, to),
|
convert_to_unit(self.width, origin, to),
|
||||||
convert_to_unit(self.height, origin, to),
|
convert_to_unit(self.height, origin, to),
|
||||||
self.hole_diameter.map(|d| convert_to_unit(d, origin, to)),
|
self.hole_diameter.map(|d| convert_to_unit(d, origin, to)),
|
||||||
|
@ -137,8 +73,8 @@ impl Rectangle {
|
||||||
impl DrawableRaw for Rectangle {
|
impl DrawableRaw for Rectangle {
|
||||||
fn canvas_pos(&self) -> Point {
|
fn canvas_pos(&self) -> Point {
|
||||||
self.position
|
self.position
|
||||||
.shift_x(self.position.x() / 2.)
|
.shift_x(self.position.x / 2.)
|
||||||
.shift_y(self.position.y() / 2.)
|
.shift_y(self.position.y / 2.)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) {
|
fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) {
|
||||||
|
@ -146,11 +82,22 @@ impl DrawableRaw for Rectangle {
|
||||||
points.push(points[0]);
|
points.push(points[0]);
|
||||||
|
|
||||||
let poly = Polygon::new(PlotPoints::from(points))
|
let poly = Polygon::new(PlotPoints::from(points))
|
||||||
.fill_color(colour.as_colour32(selected))
|
.fill_color(colour.to_colour32(selected))
|
||||||
.stroke(Stroke::NONE);
|
.stroke(Stroke::NONE);
|
||||||
ui.polygon(poly);
|
ui.polygon(poly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_egui(&self, ui: &mut Ui, selected: bool) {
|
||||||
|
ui.painter().add(RectShape::filled(
|
||||||
|
Rect::from_center_size(
|
||||||
|
self.position.invert_y().into(),
|
||||||
|
Vec2::new(self.width as f32, self.height as f32),
|
||||||
|
),
|
||||||
|
Rounding::ZERO,
|
||||||
|
CanvasColour::Copper.to_colour32(selected),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
fn to_paths(&self) -> ClipperPaths {
|
fn to_paths(&self) -> ClipperPaths {
|
||||||
ClipperPaths::new(vec![self.outline.clone()])
|
ClipperPaths::new(vec![self.outline.clone()])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use clipper2::Paths;
|
||||||
|
|
||||||
use gerber_types::{
|
use gerber_types::{
|
||||||
Aperture, ApertureMacro, Command, Coordinates, DCode, FunctionCode, GCode, InterpolationMode,
|
Aperture, Command, Coordinates, DCode, FunctionCode, GCode, InterpolationMode, MCode,
|
||||||
MCode, MacroContent, Operation, Unit,
|
Operation, Unit,
|
||||||
};
|
};
|
||||||
use tracing::{error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{
|
geometry::{
|
||||||
|
@ -17,7 +16,6 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
elements::{composite::Composite, polygon::Polygon},
|
|
||||||
union::{union_lines, union_with_apertures},
|
union::{union_lines, union_with_apertures},
|
||||||
Geometry,
|
Geometry,
|
||||||
};
|
};
|
||||||
|
@ -32,8 +30,8 @@ impl From<GerberDoc> for Geometry {
|
||||||
let mut path_container: Vec<LinePath> = Vec::new();
|
let mut path_container: Vec<LinePath> = Vec::new();
|
||||||
let mut added_apertures: Vec<Element> = Vec::new();
|
let mut added_apertures: Vec<Element> = Vec::new();
|
||||||
|
|
||||||
// create geometries by applying all gerber commands
|
|
||||||
for command in gerber.commands {
|
for command in gerber.commands {
|
||||||
|
println!("{command:?}");
|
||||||
match command {
|
match command {
|
||||||
Command::FunctionCode(f) => {
|
Command::FunctionCode(f) => {
|
||||||
match f {
|
match f {
|
||||||
|
@ -41,15 +39,14 @@ impl From<GerberDoc> for Geometry {
|
||||||
match code {
|
match code {
|
||||||
DCode::Operation(op) => match op {
|
DCode::Operation(op) => match op {
|
||||||
Operation::Interpolate(coordinates, offset) => {
|
Operation::Interpolate(coordinates, offset) => {
|
||||||
// create line by interpolating from current position to new position
|
|
||||||
if selected_interpolation_mode == InterpolationMode::Linear
|
if selected_interpolation_mode == InterpolationMode::Linear
|
||||||
{
|
{
|
||||||
// add current position as starting position if active path is empty (=> start new one)
|
// self.add_draw_segment(coord);
|
||||||
|
let point = Point::try_from(&coordinates);
|
||||||
if active_path.is_empty() {
|
if active_path.is_empty() {
|
||||||
active_path.add(current_position);
|
active_path.add(current_position);
|
||||||
}
|
}
|
||||||
// add point (from gerber coordinates) to active path
|
match point {
|
||||||
match Point::try_from(&coordinates) {
|
|
||||||
Ok(point) => {
|
Ok(point) => {
|
||||||
active_path.add(point);
|
active_path.add(point);
|
||||||
}
|
}
|
||||||
|
@ -59,32 +56,27 @@ impl From<GerberDoc> for Geometry {
|
||||||
// TODO
|
// TODO
|
||||||
// self.add_arc_segment(coord, offset.as_ref().expect(format!("No offset coord with 'Circular' state\r\n{:#?}", c).as_str()))
|
// self.add_arc_segment(coord, offset.as_ref().expect(format!("No offset coord with 'Circular' state\r\n{:#?}", c).as_str()))
|
||||||
}
|
}
|
||||||
// move current coordinates to new position
|
|
||||||
Self::move_position(&coordinates, &mut current_position);
|
Self::move_position(&coordinates, &mut current_position);
|
||||||
}
|
}
|
||||||
// move current coordinates to new position
|
|
||||||
Operation::Move(m) => {
|
Operation::Move(m) => {
|
||||||
// check if a aperture is selected and if it's circular
|
debug!("Move to {:?}, create path.", &m);
|
||||||
|
// self.create_path_from_data();
|
||||||
if let Some(Aperture::Circle(c)) =
|
if let Some(Aperture::Circle(c)) =
|
||||||
selected_aperture.as_ref()
|
selected_aperture.as_ref()
|
||||||
{
|
{
|
||||||
// check if a path is currently active
|
|
||||||
if !active_path.is_empty() {
|
if !active_path.is_empty() {
|
||||||
// finish active path if there is an active one
|
|
||||||
active_path.finalize(c.diameter);
|
active_path.finalize(c.diameter);
|
||||||
path_container.push(active_path);
|
path_container.push(active_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new active path and move position tp current position
|
|
||||||
active_path = LinePath::new();
|
active_path = LinePath::new();
|
||||||
Self::move_position(&m, &mut current_position);
|
Geometry::move_position(&m, &mut current_position);
|
||||||
}
|
}
|
||||||
// add selected Aperture
|
|
||||||
Operation::Flash(f) => {
|
Operation::Flash(f) => {
|
||||||
|
// self.create_path_from_data();
|
||||||
Self::add_geometry(
|
Self::add_geometry(
|
||||||
&mut added_apertures,
|
&mut added_apertures,
|
||||||
&gerber.aperture_macros,
|
|
||||||
¤t_position,
|
¤t_position,
|
||||||
&f,
|
&f,
|
||||||
&selected_aperture,
|
&selected_aperture,
|
||||||
|
@ -93,21 +85,8 @@ impl From<GerberDoc> for Geometry {
|
||||||
Self::move_position(&f, &mut current_position);
|
Self::move_position(&f, &mut current_position);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// select an aperture
|
|
||||||
DCode::SelectAperture(ap) => {
|
DCode::SelectAperture(ap) => {
|
||||||
// check if a aperture is selected and if it's circular
|
// self.create_path_from_data();
|
||||||
if let Some(Aperture::Circle(c)) = selected_aperture.as_ref() {
|
|
||||||
// check if a path is currently active
|
|
||||||
if !active_path.is_empty() {
|
|
||||||
// finish active path if there is an active one before selecting new aperture
|
|
||||||
active_path.finalize(c.diameter);
|
|
||||||
path_container.push(active_path);
|
|
||||||
|
|
||||||
// create new active path
|
|
||||||
active_path = LinePath::new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selected_aperture = Some(
|
selected_aperture = Some(
|
||||||
gerber
|
gerber
|
||||||
.apertures
|
.apertures
|
||||||
|
@ -142,13 +121,50 @@ impl From<GerberDoc> for Geometry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// union all drawn lines into nets of conductors
|
let mut result = clipper2::Paths::new(vec![]);
|
||||||
let conductor_net = union_lines(&path_container);
|
// if path_container.len() > 1 {
|
||||||
// union conductors with apertures
|
// let mut clipper = path_container[1]
|
||||||
let united_nets = union_with_apertures(&added_apertures, conductor_net).unwrap();
|
// .outline
|
||||||
|
// // .to_paths()
|
||||||
|
// .to_clipper_subject()
|
||||||
|
// .add_clip(path_container[2].outline.clone());
|
||||||
|
// // .add_clip(path_container[3].outline.clone())
|
||||||
|
// // .add_clip(path_container[4].outline.clone());
|
||||||
|
|
||||||
|
// // for clip in added_apertures.iter().skip(2) {
|
||||||
|
// // clipper = clipper.add_clip(clip.to_paths());
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // for line in path_container.iter().skip(2) {
|
||||||
|
// // clipper = clipper.add_clip(line.to_paths())
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// result = clipper.union(clipper2::FillRule::default()).unwrap();
|
||||||
|
|
||||||
|
// result = result
|
||||||
|
// .to_clipper_subject()
|
||||||
|
// .add_clip(path_container[3].outline.clone())
|
||||||
|
// .add_clip(path_container[4].outline.clone())
|
||||||
|
// .union(clipper2::FillRule::default())
|
||||||
|
// .unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut geo = Paths::new(vec![]);
|
||||||
|
let conductor_net = union_lines(&path_container);
|
||||||
|
|
||||||
|
for outline in &conductor_net {
|
||||||
|
println!("{:?}", outline.included_points);
|
||||||
|
geo.push(outline.outline.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Number of conductor net paths: {}", geo.len());
|
||||||
|
|
||||||
|
if let Some(geo) = union_with_apertures(&added_apertures, conductor_net) {
|
||||||
|
println!("Number of finalized net paths: {}", geo.len());
|
||||||
|
result = geo;
|
||||||
|
}
|
||||||
Self {
|
Self {
|
||||||
united_nets,
|
outline_union: result,
|
||||||
apertures: added_apertures,
|
apertures: added_apertures,
|
||||||
paths: path_container,
|
paths: path_container,
|
||||||
units: gerber.units.unwrap_or(Unit::Millimeters).into(),
|
units: gerber.units.unwrap_or(Unit::Millimeters).into(),
|
||||||
|
@ -157,15 +173,15 @@ impl From<GerberDoc> for Geometry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Geometry {
|
impl Geometry {
|
||||||
fn move_position(coord: &Coordinates, position: &mut Point) {
|
fn move_position(coord: &Coordinates, position: &mut Point) -> () {
|
||||||
if let Ok(pos) = Point::try_from(coord) {
|
if let Ok(pos) = Point::try_from(coord) {
|
||||||
|
debug!("Moved position to {pos:?}");
|
||||||
*position = pos;
|
*position = pos;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_geometry(
|
fn add_geometry(
|
||||||
geometries: &mut Vec<Element>,
|
geometries: &mut Vec<Element>,
|
||||||
aperture_macros: &HashMap<String, ApertureMacro>,
|
|
||||||
position: &Point,
|
position: &Point,
|
||||||
coordinates: &Coordinates,
|
coordinates: &Coordinates,
|
||||||
aperture: &Option<Aperture>,
|
aperture: &Option<Aperture>,
|
||||||
|
@ -185,61 +201,15 @@ impl Geometry {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Aperture::Obround(o) => {
|
Aperture::Obround(o) => {
|
||||||
|
// error!("Unsupported Obround aperture:\r\n{:#?}", o);
|
||||||
geometries.push(Element::Obround(Obround::from_aperture_obround(o, target)));
|
geometries.push(Element::Obround(Obround::from_aperture_obround(o, target)));
|
||||||
}
|
}
|
||||||
Aperture::Polygon(p) => {
|
Aperture::Polygon(p) => {
|
||||||
// TODO add polygon
|
|
||||||
error!("Unsupported Polygon aperture:\r\n{:#?}", p);
|
error!("Unsupported Polygon aperture:\r\n{:#?}", p);
|
||||||
}
|
}
|
||||||
Aperture::Other(o) => {
|
Aperture::Other(o) => {
|
||||||
// split at '/' -> name/arguments
|
|
||||||
if let Some((name, args)) = o.split_once("/") {
|
|
||||||
// parse variables from args
|
|
||||||
let variables: Vec<f64> = args
|
|
||||||
.split(",")
|
|
||||||
.map(|s| s.parse::<f64>().unwrap_or(0.))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if let Some(ap_macro) = aperture_macros.get(name) {
|
|
||||||
let mut composite = Composite::new();
|
|
||||||
// add elements from macro
|
|
||||||
for aperture in &ap_macro.content {
|
|
||||||
match aperture {
|
|
||||||
MacroContent::Circle(c) => composite.add(Element::Circle(
|
|
||||||
Circle::from_macro(c, &variables, target),
|
|
||||||
)),
|
|
||||||
MacroContent::VectorLine(vl) => composite.add(Element::Rectangle(
|
|
||||||
Rectangle::from_macro_vector_line(vl, &variables, target),
|
|
||||||
)),
|
|
||||||
MacroContent::CenterLine(cl) => composite.add(Element::Rectangle(
|
|
||||||
Rectangle::from_macro_center_line(cl, &variables, target),
|
|
||||||
)),
|
|
||||||
MacroContent::Outline(ol) => composite.add(Element::Polygon(
|
|
||||||
Polygon::from_macro(ol, &variables, target),
|
|
||||||
)),
|
|
||||||
MacroContent::Polygon(p) => {
|
|
||||||
error!("Polygon Not implemented yet")
|
|
||||||
}
|
|
||||||
MacroContent::Moire(m) => {
|
|
||||||
error!("Moire Not implemented yet")
|
|
||||||
}
|
|
||||||
MacroContent::Thermal(t) => {
|
|
||||||
error!("Thermal Not implemented yet")
|
|
||||||
}
|
|
||||||
MacroContent::VariableDefinition(_) => {}
|
|
||||||
MacroContent::Comment(_) => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
composite.finalize();
|
|
||||||
geometries.push(Element::Composite(composite));
|
|
||||||
} else {
|
|
||||||
error!("Unsupported Other aperture:\r\n{:#?}", o);
|
error!("Unsupported Other aperture:\r\n{:#?}", o);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
error!("Unsupported Other aperture:\r\n{:#?}", o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,13 @@ pub fn semi_circle(center: Point, diameter: f64, tilt: f64) -> Vec<(f64, f64)> {
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let angle = (i as f64 / (CIRCLE_SEGMENTS / 2) as f64) * PI + tilt * (PI / 180.);
|
let angle = (i as f64 / (CIRCLE_SEGMENTS / 2) as f64) * PI + tilt * (PI / 180.);
|
||||||
(
|
(
|
||||||
angle.cos() * diameter / 2. + center.x(),
|
angle.cos() * diameter / 2. + center.x,
|
||||||
angle.sin() * diameter / 2. + center.y(),
|
angle.sin() * diameter / 2. + center.y,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub enum CircleSegment {
|
pub enum CircleSegment {
|
||||||
North,
|
North,
|
||||||
East,
|
East,
|
||||||
|
@ -50,106 +49,106 @@ pub fn create_circular_path(
|
||||||
.map(|&i| {
|
.map(|&i| {
|
||||||
let angle = (i as f64 / CIRCLE_SEGMENTS as f64) * 2.0 * PI;
|
let angle = (i as f64 / CIRCLE_SEGMENTS as f64) * 2.0 * PI;
|
||||||
(
|
(
|
||||||
angle.sin() * diameter / 2. + position.x(),
|
angle.sin() * diameter / 2. + position.x,
|
||||||
angle.cos() * diameter / 2. + position.y(),
|
angle.cos() * diameter / 2. + position.y,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
// pub enum Orientation {
|
pub enum Orientation {
|
||||||
// Clockwise,
|
Clockwise,
|
||||||
// CounterClockwise,
|
CounterClockwise,
|
||||||
// CoLinear,
|
CoLinear,
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // To find orientation of ordered triplet (p, q, r).
|
// To find orientation of ordered triplet (p, q, r).
|
||||||
// // https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
// https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
||||||
// pub fn orientation(p: impl Into<Point>, q: impl Into<Point>, r: impl Into<Point>) -> Orientation {
|
pub fn orientation(p: impl Into<Point>, q: impl Into<Point>, r: impl Into<Point>) -> Orientation {
|
||||||
// let (p, q, r) = (p.into(), q.into(), r.into());
|
let (p, q, r) = (p.into(), q.into(), r.into());
|
||||||
|
|
||||||
// // See https://www.geeksforgeeks.org/orientation-3-ordered-points/
|
// See https://www.geeksforgeeks.org/orientation-3-ordered-points/
|
||||||
// // for details of below formula.
|
// for details of below formula.
|
||||||
// let val = (q.y() - p.y()) * (r.x() - q.x()) - (q.x() - p.x()) * (r.y() - q.y());
|
let val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
||||||
|
|
||||||
// match val {
|
match val {
|
||||||
// 0. => Orientation::CoLinear,
|
0. => Orientation::CoLinear,
|
||||||
// x if x > 0. => Orientation::Clockwise,
|
x if x > 0. => Orientation::Clockwise,
|
||||||
// _ => Orientation::CounterClockwise,
|
_ => Orientation::CounterClockwise,
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // Given three collinear points p, q, r, the function checks if
|
// Given three collinear points p, q, r, the function checks if
|
||||||
// // point q lies on line segment 'pr'
|
// point q lies on line segment 'pr'
|
||||||
// pub fn on_segment(p: impl Into<Point>, q: impl Into<Point>, r: impl Into<Point>) -> bool {
|
pub fn on_segment(p: impl Into<Point>, q: impl Into<Point>, r: impl Into<Point>) -> bool {
|
||||||
// let (p, q, r) = (p.into(), q.into(), r.into());
|
let (p, q, r) = (p.into(), q.into(), r.into());
|
||||||
|
|
||||||
// q.x() <= greater_val(p.x(), r.x())
|
q.x <= greater_val(p.x, r.x)
|
||||||
// && q.x() >= lower_val(p.x(), r.x())
|
&& q.x >= lower_val(p.x, r.x)
|
||||||
// && q.y() <= greater_val(p.y(), r.y())
|
&& q.y <= greater_val(p.y, r.y)
|
||||||
// && q.y() >= lower_val(p.y(), r.y())
|
&& q.y >= lower_val(p.y, r.y)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fn greater_val(a: f64, b: f64) -> f64 {
|
fn greater_val(a: f64, b: f64) -> f64 {
|
||||||
// if a > b {
|
if a > b {
|
||||||
// a
|
a
|
||||||
// } else {
|
} else {
|
||||||
// b
|
b
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fn lower_val(a: f64, b: f64) -> f64 {
|
fn lower_val(a: f64, b: f64) -> f64 {
|
||||||
// if a < b {
|
if a < b {
|
||||||
// a
|
a
|
||||||
// } else {
|
} else {
|
||||||
// b
|
b
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // The main function that returns true if line segment 'p1q1'
|
// The main function that returns true if line segment 'p1q1'
|
||||||
// // and 'p2q2' intersect.
|
// and 'p2q2' intersect.
|
||||||
// // https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
// https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
||||||
// pub fn do_intersect(
|
pub fn do_intersect(
|
||||||
// p1: impl Into<Point>,
|
p1: impl Into<Point>,
|
||||||
// q1: impl Into<Point>,
|
q1: impl Into<Point>,
|
||||||
// p2: impl Into<Point>,
|
p2: impl Into<Point>,
|
||||||
// q2: impl Into<Point>,
|
q2: impl Into<Point>,
|
||||||
// ) -> bool {
|
) -> bool {
|
||||||
// let (p1, q1, p2, q2) = (p1.into(), q1.into(), p2.into(), q2.into());
|
let (p1, q1, p2, q2) = (p1.into(), q1.into(), p2.into(), q2.into());
|
||||||
|
|
||||||
// // Find the four orientations needed for general and
|
// Find the four orientations needed for general and
|
||||||
// // special cases
|
// special cases
|
||||||
// let o1 = orientation(p1, q1, p2);
|
let o1 = orientation(p1, q1, p2);
|
||||||
// let o2 = orientation(p1, q1, q2);
|
let o2 = orientation(p1, q1, q2);
|
||||||
// let o3 = orientation(p2, q2, p1);
|
let o3 = orientation(p2, q2, p1);
|
||||||
// let o4 = orientation(p2, q2, q1);
|
let o4 = orientation(p2, q2, q1);
|
||||||
|
|
||||||
// // General case
|
// General case
|
||||||
// if o1 != o2 && o3 != o4 {
|
if o1 != o2 && o3 != o4 {
|
||||||
// return true;
|
return true;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // Special Cases
|
// Special Cases
|
||||||
// // p1, q1 and p2 are collinear and p2 lies on segment p1q1
|
// p1, q1 and p2 are collinear and p2 lies on segment p1q1
|
||||||
// if o1 == Orientation::CoLinear && on_segment(p1, p2, q1) {
|
if o1 == Orientation::CoLinear && on_segment(p1, p2, q1) {
|
||||||
// return true;
|
return true;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // p1, q1 and q2 are collinear and q2 lies on segment p1q1
|
// p1, q1 and q2 are collinear and q2 lies on segment p1q1
|
||||||
// if o2 == Orientation::CoLinear && on_segment(p1, q2, q1) {
|
if o2 == Orientation::CoLinear && on_segment(p1, q2, q1) {
|
||||||
// return true;
|
return true;
|
||||||
// };
|
};
|
||||||
|
|
||||||
// // p2, q2 and p1 are collinear and p1 lies on segment p2q2
|
// p2, q2 and p1 are collinear and p1 lies on segment p2q2
|
||||||
// if o3 == Orientation::CoLinear && on_segment(p2, p1, q2) {
|
if o3 == Orientation::CoLinear && on_segment(p2, p1, q2) {
|
||||||
// return true;
|
return true;
|
||||||
// };
|
};
|
||||||
|
|
||||||
// // p2, q2 and q1 are collinear and q1 lies on segment p2q2
|
// p2, q2 and q1 are collinear and q1 lies on segment p2q2
|
||||||
// if o4 == Orientation::CoLinear && on_segment(p2, q1, q2) {
|
if o4 == Orientation::CoLinear && on_segment(p2, q1, q2) {
|
||||||
// return true;
|
return true;
|
||||||
// };
|
};
|
||||||
|
|
||||||
// false // Doesn't fall in any of the above cases
|
false // Doesn't fall in any of the above cases
|
||||||
// }
|
}
|
||||||
|
|
|
@ -2,39 +2,47 @@ pub mod elements;
|
||||||
pub mod gerber;
|
pub mod gerber;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
pub mod point;
|
pub mod point;
|
||||||
pub mod union;
|
mod union;
|
||||||
|
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
use clipper2::{Bounds, Path, Paths, PointScaler};
|
use clipper2::{Bounds, Path, Paths, PointScaler};
|
||||||
|
use eframe::egui::Ui;
|
||||||
use egui_plot::PlotUi;
|
use egui_plot::PlotUi;
|
||||||
use elements::{linepath::LinePath, Element};
|
use elements::{linepath::LinePath, Element};
|
||||||
|
|
||||||
use point::Point;
|
use point::Point;
|
||||||
use union::UnitedNets;
|
|
||||||
|
|
||||||
use crate::application::CanvasColour;
|
use crate::application::CanvasColour;
|
||||||
|
|
||||||
pub struct Geometry {
|
pub struct Geometry {
|
||||||
pub united_nets: UnitedNets,
|
pub outline_union: ClipperPaths,
|
||||||
pub apertures: Vec<Element>,
|
pub apertures: Vec<Element>,
|
||||||
pub paths: Vec<LinePath>,
|
pub paths: Vec<LinePath>,
|
||||||
pub units: Unit,
|
pub units: Unit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Geometry {
|
impl Geometry {
|
||||||
pub fn into_unit(self, to: Unit) -> Self {
|
pub fn to_unit(self, to: Unit) -> Self {
|
||||||
Self {
|
Self {
|
||||||
united_nets: self.united_nets.into_unit(self.units, to),
|
outline_union: self
|
||||||
|
.outline_union
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
p.iter()
|
||||||
|
.map(|p| (&Point::from(p).to_unit(self.units, to)).into())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
apertures: self
|
apertures: self
|
||||||
.apertures
|
.apertures
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|a| a.into_unit(self.units, to))
|
.map(|a| a.to_unit(self.units, to))
|
||||||
.collect(),
|
.collect(),
|
||||||
paths: self
|
paths: self
|
||||||
.paths
|
.paths
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|l| l.into_unit(self.units, to))
|
.map(|l| l.to_unit(self.units, to))
|
||||||
.collect(),
|
.collect(),
|
||||||
units: to,
|
units: to,
|
||||||
}
|
}
|
||||||
|
@ -90,7 +98,12 @@ pub type ClipperBounds = Bounds<Micro>;
|
||||||
|
|
||||||
pub trait DrawableRaw {
|
pub trait DrawableRaw {
|
||||||
fn canvas_pos(&self) -> Point;
|
fn canvas_pos(&self) -> Point;
|
||||||
|
fn draw_egui(&self, ui: &mut Ui, selected: bool);
|
||||||
fn draw_egui_plot(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool);
|
fn draw_egui_plot(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool);
|
||||||
fn to_paths(&self) -> ClipperPaths;
|
fn to_paths(&self) -> ClipperPaths;
|
||||||
fn outline(&self) -> ClipperPath;
|
fn outline(&self) -> ClipperPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canvas_position_from_gerber(gerber_position: &Point, offset: Point) -> Point {
|
||||||
|
gerber_position.shift_x(offset.x).shift_y(offset.y)
|
||||||
|
}
|
||||||
|
|
|
@ -1,115 +1,80 @@
|
||||||
use std::{
|
use std::ops::{Add, Sub};
|
||||||
fmt,
|
|
||||||
marker::PhantomData,
|
|
||||||
ops::{Add, Sub},
|
|
||||||
};
|
|
||||||
|
|
||||||
use clipper2::PointScaler;
|
|
||||||
use eframe::egui::{Pos2, Vec2};
|
use eframe::egui::{Pos2, Vec2};
|
||||||
use egui_plot::PlotPoint;
|
use egui_plot::PlotPoint;
|
||||||
use gerber_types::Coordinates;
|
use gerber_types::Coordinates;
|
||||||
|
|
||||||
use super::{ClipperPoint, Micro, Unit};
|
use super::{ClipperPoint, Unit};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Point<P: PointScaler = Micro> {
|
pub struct Point {
|
||||||
x: i64,
|
pub x: f64,
|
||||||
y: i64,
|
pub y: f64,
|
||||||
phantom_data: PhantomData<P>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Point {
|
impl Point {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("Point")
|
|
||||||
.field("x", &self.x())
|
|
||||||
.field("y", &self.y())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: PointScaler> Point<P> {
|
|
||||||
pub fn new(x: f64, y: f64) -> Self {
|
pub fn new(x: f64, y: f64) -> Self {
|
||||||
// Point { x, y }
|
Point { x, y }
|
||||||
Self {
|
|
||||||
x: P::scale(x) as i64,
|
|
||||||
y: P::scale(y) as i64,
|
|
||||||
phantom_data: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shift_x(&self, shift: f64) -> Self {
|
pub fn shift_x(&self, shift: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x + P::scale(shift) as i64,
|
x: self.x + shift,
|
||||||
y: self.y,
|
y: self.y,
|
||||||
phantom_data: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shift_y(&self, shift: f64) -> Self {
|
pub fn shift_y(&self, shift: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x,
|
x: self.x,
|
||||||
y: self.y + P::scale(shift) as i64,
|
y: self.y + shift,
|
||||||
phantom_data: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn div(&self, divisor: f64) -> Self {
|
|
||||||
Self {
|
|
||||||
x: (self.x as f64 / divisor) as i64,
|
|
||||||
y: (self.y as f64 / divisor) as i64,
|
|
||||||
phantom_data: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> f64 {
|
pub fn len(&self) -> f64 {
|
||||||
P::descale(((self.x.pow(2) + self.y.pow(2)) as f64).sqrt())
|
(self.x.powi(2) + self.y.powi(2)).sqrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn normalize(&self) -> Self {
|
pub fn normalize(&self) -> Self {
|
||||||
Self::new(self.x() / self.len(), self.y() / self.len())
|
Self {
|
||||||
|
x: self.x / self.len(),
|
||||||
|
y: self.y / self.len(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invert_y(&self) -> Self {
|
pub fn invert_y(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x,
|
x: self.x,
|
||||||
y: -self.y,
|
y: -self.y,
|
||||||
phantom_data: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
pub fn to_unit(&self, origin: Unit, to: Unit) -> Self {
|
||||||
Self::new(
|
Self {
|
||||||
convert_to_unit(self.x(), origin, to),
|
x: convert_to_unit(self.x, origin, to),
|
||||||
convert_to_unit(self.y(), origin, to),
|
y: convert_to_unit(self.y, origin, to),
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the x coordinate of the point.
|
|
||||||
pub fn x(&self) -> f64 {
|
|
||||||
P::descale(self.x as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the y coordinate of the point.
|
|
||||||
pub fn y(&self) -> f64 {
|
|
||||||
P::descale(self.y as f64)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Point> for (f64, f64) {
|
impl From<Point> for (f64, f64) {
|
||||||
fn from(value: Point) -> Self {
|
fn from(value: Point) -> Self {
|
||||||
(value.x(), value.y())
|
(value.x, value.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Point> for [f64; 2] {
|
impl From<Point> for [f64; 2] {
|
||||||
fn from(value: Point) -> Self {
|
fn from(value: Point) -> Self {
|
||||||
[value.x(), value.y()]
|
[value.x, value.y]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[f64; 2]> for Point {
|
impl From<[f64; 2]> for Point {
|
||||||
fn from(value: [f64; 2]) -> Self {
|
fn from(value: [f64; 2]) -> Self {
|
||||||
Self::new(value[0], value[1])
|
Point {
|
||||||
|
x: value[0],
|
||||||
|
y: value[1],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +85,6 @@ impl Add for Point {
|
||||||
Self {
|
Self {
|
||||||
x: self.x + other.x,
|
x: self.x + other.x,
|
||||||
y: self.y + other.y,
|
y: self.y + other.y,
|
||||||
phantom_data: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +96,6 @@ impl Sub for Point {
|
||||||
Self {
|
Self {
|
||||||
x: self.x - other.x,
|
x: self.x - other.x,
|
||||||
y: self.y - other.y,
|
y: self.y - other.y,
|
||||||
phantom_data: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,8 +103,8 @@ impl Sub for Point {
|
||||||
impl From<Point> for Pos2 {
|
impl From<Point> for Pos2 {
|
||||||
fn from(value: Point) -> Self {
|
fn from(value: Point) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: value.x() as f32,
|
x: value.x as f32,
|
||||||
y: value.y() as f32,
|
y: value.y as f32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,30 +112,33 @@ impl From<Point> for Pos2 {
|
||||||
impl From<&Point> for Pos2 {
|
impl From<&Point> for Pos2 {
|
||||||
fn from(value: &Point) -> Self {
|
fn from(value: &Point) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: value.x() as f32,
|
x: value.x as f32,
|
||||||
y: value.y() as f32,
|
y: value.y as f32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Pos2> for Point {
|
impl From<Pos2> for Point {
|
||||||
fn from(value: Pos2) -> Self {
|
fn from(value: Pos2) -> Self {
|
||||||
Self::new(value.x.into(), value.y.into())
|
Self {
|
||||||
|
x: value.x.into(),
|
||||||
|
y: value.y.into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Point> for Vec2 {
|
impl From<Point> for Vec2 {
|
||||||
fn from(value: Point) -> Self {
|
fn from(value: Point) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: value.x() as f32,
|
x: value.x as f32,
|
||||||
y: value.y() as f32,
|
y: value.y as f32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Point> for ClipperPoint {
|
impl From<&Point> for ClipperPoint {
|
||||||
fn from(value: &Point) -> Self {
|
fn from(value: &Point) -> Self {
|
||||||
Self::from_scaled(value.x, value.y)
|
Self::new(value.x, value.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +150,7 @@ impl From<&ClipperPoint> for Point {
|
||||||
|
|
||||||
impl From<Point> for PlotPoint {
|
impl From<Point> for PlotPoint {
|
||||||
fn from(value: Point) -> Self {
|
fn from(value: Point) -> Self {
|
||||||
Self::new(value.x(), value.y())
|
Self::new(value.x, value.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,143 +1,50 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use clipper2::{FillRule, PointInPolygonResult};
|
use clipper2::{FillRule, One, Paths, PointInPolygonResult};
|
||||||
use itertools::Itertools;
|
|
||||||
|
use crate::geometry::helpers::do_intersect;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
elements::{linepath::LinePath, Element},
|
elements::{linepath::LinePath, Element},
|
||||||
point::Point,
|
point::Point,
|
||||||
ClipperBounds, ClipperPaths, ClipperPoint, Unit,
|
ClipperPaths,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct UnitedNets {
|
|
||||||
pub conductors: Vec<ConductorNet>,
|
|
||||||
pub isolated_apertures: Vec<Element>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnitedNets {
|
|
||||||
pub fn into_unit(self, origin: Unit, to: Unit) -> Self {
|
|
||||||
Self {
|
|
||||||
conductors: self
|
|
||||||
.conductors
|
|
||||||
.iter()
|
|
||||||
.map(|net| net.to_unit(origin, to))
|
|
||||||
.collect(),
|
|
||||||
isolated_apertures: self
|
|
||||||
.isolated_apertures
|
|
||||||
.into_iter()
|
|
||||||
.map(|el| el.into_unit(origin, to))
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bounds(&self) -> ClipperBounds {
|
|
||||||
let conductor_net = self
|
|
||||||
.conductors
|
|
||||||
.iter()
|
|
||||||
.map(|net| net.outline.clone())
|
|
||||||
.reduce(|mut acc, v| {
|
|
||||||
acc.push(v);
|
|
||||||
acc
|
|
||||||
});
|
|
||||||
let isolated_apertures_bound = ClipperPaths::new(
|
|
||||||
self.isolated_apertures
|
|
||||||
.iter()
|
|
||||||
.map(|net| net.outline())
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.bounds();
|
|
||||||
|
|
||||||
if let Some(net) = conductor_net {
|
|
||||||
let net_bounds = net.bounds();
|
|
||||||
|
|
||||||
ClipperBounds {
|
|
||||||
min: ClipperPoint::new(
|
|
||||||
net_bounds.min.x().min(isolated_apertures_bound.min.x()),
|
|
||||||
net_bounds.min.y().min(isolated_apertures_bound.min.y()),
|
|
||||||
),
|
|
||||||
max: ClipperPoint::new(
|
|
||||||
net_bounds.max.x().max(isolated_apertures_bound.max.x()),
|
|
||||||
net_bounds.max.y().max(isolated_apertures_bound.max.y()),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isolated_apertures_bound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConductorNet {
|
pub struct ConductorNet {
|
||||||
pub outline: ClipperPaths,
|
pub outline: ClipperPaths,
|
||||||
pub included_points: Vec<Point>,
|
pub included_points: Vec<Point>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConductorNet {
|
|
||||||
pub fn to_unit(&self, origin: Unit, to: Unit) -> Self {
|
|
||||||
Self {
|
|
||||||
outline: self
|
|
||||||
.outline
|
|
||||||
.iter()
|
|
||||||
.map(|p| {
|
|
||||||
p.iter()
|
|
||||||
.map(|p| (&Point::from(p).into_unit(origin, to)).into())
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
included_points: self
|
|
||||||
.included_points
|
|
||||||
.iter()
|
|
||||||
.map(|el| el.into_unit(origin, to))
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn union_lines(lines: &[LinePath]) -> Vec<ConductorNet> {
|
pub fn union_lines(lines: &[LinePath]) -> Vec<ConductorNet> {
|
||||||
let mut intersection_map = HashMap::new();
|
let mut intersection_map = HashMap::new();
|
||||||
|
println!(
|
||||||
// TODO prevent double checking same combination
|
"START LINE UNION of {:?}",
|
||||||
let combinations = lines.iter().enumerate().combinations(2);
|
lines
|
||||||
|
.iter()
|
||||||
for co in combinations {
|
.map(|l| l.points.clone())
|
||||||
let (index, line) = co[0];
|
.collect::<Vec<Vec<Point>>>()
|
||||||
// print!("{} mit {};", combi[0].0, combi[1].0);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
for (index, line) in lines.iter().enumerate() {
|
for (index, line) in lines.iter().enumerate() {
|
||||||
if !line.is_empty() {
|
if !line.is_empty() {
|
||||||
// create list of intersecting lines
|
// create list of intersecting lines
|
||||||
let mut intersections = Vec::new();
|
let mut intersections = Vec::new();
|
||||||
let (p1, q1) = (line.points[0], line.points[1]);
|
let (p1, q1) = (line.points[0], line.points[1]);
|
||||||
// println!("LINE 1 {p1:?}, {q1:?}");
|
println!("LINE 1 {p1:?}, {q1:?}");
|
||||||
for (i, l) in lines.iter().enumerate() {
|
for (i, l) in lines.iter().enumerate() {
|
||||||
if !l.is_empty() {
|
if !l.is_empty() {
|
||||||
// do not check for intersection with itself
|
// do not check for intersection with itself
|
||||||
if index != i {
|
if index != i {
|
||||||
// check for all lines in path
|
// check for all lines in path
|
||||||
// let intersect = l.points.windows(2).any(|w| {
|
let intersect = l.points.windows(2).any(|w| {
|
||||||
// let (p2, q2) = (w[0], w[1]);
|
let (p2, q2) = (w[0], w[1]);
|
||||||
// // println!("LINE 2 {p2:?}, {q2:?}");
|
println!("LINE 2 {p2:?}, {q2:?}");
|
||||||
// // do_intersect(p1, q1, p2, q2)
|
do_intersect(p1, q1, p2, q2)
|
||||||
// line.outline
|
});
|
||||||
// .get(0)
|
println!("INTERSECTING: {intersect}");
|
||||||
// .unwrap()
|
|
||||||
// .is_point_inside(ClipperPoint::from(&p2))
|
|
||||||
// != PointInPolygonResult::IsOutside
|
|
||||||
// || line
|
|
||||||
// .outline
|
|
||||||
// .get(0)
|
|
||||||
// .unwrap()
|
|
||||||
// .is_point_inside(ClipperPoint::from(&q2))
|
|
||||||
// != PointInPolygonResult::IsOutside
|
|
||||||
// });
|
|
||||||
let intersect = line.outline_includes_points(&l.points);
|
|
||||||
// println!("INTERSECTING: {intersect}");
|
|
||||||
if intersect {
|
if intersect {
|
||||||
// let entry = intersection_map.entry(index).or_insert(Vec::new());
|
// let entry = intersection_map.entry(index).or_insert(Vec::new());
|
||||||
// entry.push(i);
|
// entry.push(i);
|
||||||
// println!("INTERSECTING {:?} and {:?}", line.points, l.points);
|
|
||||||
intersections.push(i);
|
intersections.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,20 +55,34 @@ pub fn union_lines(lines: &[LinePath]) -> Vec<ConductorNet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("{intersection_map:?}");
|
||||||
let mut final_geo = Vec::new();
|
let mut final_geo = Vec::new();
|
||||||
|
|
||||||
// go through all line segments
|
// go through all line segments
|
||||||
for i in 0..intersection_map.len() {
|
for i in 0..intersection_map.len() {
|
||||||
if let Some(mut intersections) = intersection_map.remove(&i) {
|
if let Some(mut intersections) = intersection_map.remove(&i) {
|
||||||
|
// if no intersections are given, add line as own conductor net
|
||||||
|
// if intersections.is_empty() {
|
||||||
|
// if let Some(line) = lines.get(i) {
|
||||||
|
// final_geo.push(ConductorNet {
|
||||||
|
// outline: line.outline.clone(),
|
||||||
|
// included_points: line.points.clone(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
println!("--- Taking line segment {i}");
|
||||||
// get current line path
|
// get current line path
|
||||||
let mut geo = lines[i].outline.clone();
|
let mut geo = lines[i].outline.clone();
|
||||||
let mut included_points = lines[i].points.clone();
|
let mut included_points = lines[i].points.clone();
|
||||||
|
|
||||||
// union with intersecting lines until done
|
// union with intersecting lines until done
|
||||||
|
println!("Intersection points: {intersections:?}");
|
||||||
while let Some(other) = intersections.pop() {
|
while let Some(other) = intersections.pop() {
|
||||||
|
println!("Intersecting with line # {other:?}");
|
||||||
// union with intersecting line
|
// union with intersecting line
|
||||||
let intersecting_line = &lines[other];
|
let intersecting_line = &lines[other];
|
||||||
if let Some(union) = union_function(&geo, &intersecting_line.outline) {
|
if let Some(union) = union(&geo, &intersecting_line.outline) {
|
||||||
geo = union;
|
geo = union;
|
||||||
}
|
}
|
||||||
// add points of added line to included points
|
// add points of added line to included points
|
||||||
|
@ -193,16 +114,23 @@ pub fn union_lines(lines: &[LinePath]) -> Vec<ConductorNet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if final_geo.is_empty() {
|
||||||
|
// if let Some(line) = lines.get(0) {
|
||||||
|
// final_geo.push(ConductorNet {
|
||||||
|
// outline: line.outline.clone(),
|
||||||
|
// included_points: line.points.clone(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
final_geo
|
final_geo
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn union_with_apertures(
|
pub fn union_with_apertures(
|
||||||
apertures: &Vec<Element>,
|
apertures: &Vec<Element>,
|
||||||
conductors: Vec<ConductorNet>,
|
conductors: Vec<ConductorNet>,
|
||||||
) -> Option<UnitedNets> {
|
) -> Option<ClipperPaths> {
|
||||||
let mut isolated_apertures = Vec::new();
|
let mut finalized_paths = Vec::new(); // handle apertures without connection
|
||||||
|
|
||||||
// let mut finalized_paths = Vec::new(); // handle apertures without connection
|
|
||||||
let mut current_conductors = conductors;
|
let mut current_conductors = conductors;
|
||||||
|
|
||||||
// go through all apertures
|
// go through all apertures
|
||||||
|
@ -223,7 +151,7 @@ pub fn union_with_apertures(
|
||||||
.any(|c| ap.outline().is_point_inside(c.into()) != PointInPolygonResult::IsOutside)
|
.any(|c| ap.outline().is_point_inside(c.into()) != PointInPolygonResult::IsOutside)
|
||||||
{
|
{
|
||||||
// union aperture with conductor net
|
// union aperture with conductor net
|
||||||
let geo = union_function(&geo, &conductor.outline)?;
|
let geo = union(&geo, &conductor.outline)?;
|
||||||
let mut cond = conductor;
|
let mut cond = conductor;
|
||||||
cond.outline = geo;
|
cond.outline = geo;
|
||||||
isolated = false;
|
isolated = false;
|
||||||
|
@ -235,21 +163,24 @@ pub fn union_with_apertures(
|
||||||
|
|
||||||
// add aperture to extra container if isolated
|
// add aperture to extra container if isolated
|
||||||
if isolated {
|
if isolated {
|
||||||
// finalized_paths.push(geo);
|
finalized_paths.push(geo);
|
||||||
isolated_apertures.push(ap.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update current conductors
|
// update current conductors
|
||||||
current_conductors = new_conductors;
|
current_conductors = new_conductors;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(UnitedNets {
|
for conductor in current_conductors {
|
||||||
conductors: current_conductors,
|
finalized_paths.push(conductor.outline);
|
||||||
isolated_apertures,
|
}
|
||||||
|
|
||||||
|
finalized_paths.into_iter().reduce(|mut all, paths| {
|
||||||
|
all.push(paths);
|
||||||
|
all
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn union_function(path1: &ClipperPaths, path2: &ClipperPaths) -> Option<ClipperPaths> {
|
fn union(path1: &ClipperPaths, path2: &ClipperPaths) -> Option<ClipperPaths> {
|
||||||
path1
|
path1
|
||||||
.to_clipper_subject()
|
.to_clipper_subject()
|
||||||
.add_clip(path2.clone())
|
.add_clip(path2.clone())
|
||||||
|
|
|
@ -1,170 +1,7 @@
|
||||||
use std::str::Chars;
|
use gerber_types::MacroContent;
|
||||||
|
|
||||||
use gerber_types::{
|
pub fn parse(data: &str) -> Option<MacroContent> {
|
||||||
ApertureMacro, CenterLinePrimitive, CirclePrimitive, MacroContent, MacroDecimal,
|
todo!()
|
||||||
OutlinePrimitive, PolygonPrimitive, VectorLinePrimitive,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::doc::GerberDoc;
|
|
||||||
|
|
||||||
pub fn start_macro(doc: &mut GerberDoc, macro_lines: &mut Option<Vec<String>>, line: &str) {
|
|
||||||
*macro_lines = Some(vec![line.replacen("%AM", "", 1)]);
|
|
||||||
if line.ends_with("*%") {
|
|
||||||
if let Some(aperture_macro) = parse(macro_lines.as_ref()) {
|
|
||||||
doc.aperture_macros
|
|
||||||
.insert(aperture_macro.name.to_string(), aperture_macro);
|
|
||||||
};
|
|
||||||
*macro_lines = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn continue_macro(doc: &mut GerberDoc, macro_lines: &mut Option<Vec<String>>, line: &str) {
|
|
||||||
if let Some(lines) = macro_lines {
|
|
||||||
lines.push(line.to_string());
|
|
||||||
if line.ends_with("*%") {
|
|
||||||
if let Some(aperture_macro) = parse(macro_lines.as_ref()) {
|
|
||||||
doc.aperture_macros
|
|
||||||
.insert(aperture_macro.name.to_string(), aperture_macro);
|
|
||||||
};
|
|
||||||
*macro_lines = None;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
start_macro(doc, macro_lines, line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(data: Option<&Vec<String>>) -> Option<ApertureMacro> {
|
|
||||||
let mut name = "";
|
|
||||||
let mut macro_content = Vec::new();
|
|
||||||
let lines = data?;
|
|
||||||
|
|
||||||
// Go through every very part in the macro
|
|
||||||
for (index, line) in lines.iter().enumerate() {
|
|
||||||
// get name
|
|
||||||
if index == 0 {
|
|
||||||
name = line.strip_suffix("*")?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut chars = line.chars();
|
|
||||||
// remove % char of last line
|
|
||||||
if index == lines.len() - 1 {
|
|
||||||
chars.next_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
match chars.next()? {
|
|
||||||
// Comment
|
|
||||||
'0' => macro_content.push(MacroContent::Comment(line.to_string())),
|
|
||||||
// Circle
|
|
||||||
'1' => {
|
|
||||||
let args = get_attr_args(chars)?;
|
|
||||||
|
|
||||||
macro_content.push(MacroContent::Circle(CirclePrimitive {
|
|
||||||
exposure: args.first()? == &MacroDecimal::Value(1.0),
|
|
||||||
diameter: args.get(1)?.clone(),
|
|
||||||
center: (args.get(2)?.clone(), args.get(3)?.clone()),
|
|
||||||
angle: match args.get(4) {
|
|
||||||
Some(arg) => Some(arg.clone()),
|
|
||||||
None => Some(MacroDecimal::Value(0.)),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
'2' => {
|
|
||||||
match chars.next()? {
|
|
||||||
// Vector Line
|
|
||||||
'0' => {
|
|
||||||
let args = get_attr_args(chars)?;
|
|
||||||
|
|
||||||
macro_content.push(MacroContent::VectorLine(VectorLinePrimitive {
|
|
||||||
exposure: args.first()? == &MacroDecimal::Value(1.0),
|
|
||||||
end: (args.get(4)?.clone(), args.get(5)?.clone()),
|
|
||||||
width: args.get(1)?.clone(),
|
|
||||||
start: (args.get(2)?.clone(), args.get(3)?.clone()),
|
|
||||||
angle: match args.get(6) {
|
|
||||||
Some(arg) => arg.clone(),
|
|
||||||
None => MacroDecimal::Value(0.),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
// Center Line
|
|
||||||
'1' => {
|
|
||||||
let args = get_attr_args(chars)?;
|
|
||||||
|
|
||||||
macro_content.push(MacroContent::CenterLine(CenterLinePrimitive {
|
|
||||||
exposure: args.first()? == &MacroDecimal::Value(1.0),
|
|
||||||
dimensions: (args.get(1)?.clone(), args.get(2)?.clone()),
|
|
||||||
center: (args.get(3)?.clone(), args.get(4)?.clone()),
|
|
||||||
angle: match args.get(6) {
|
|
||||||
Some(arg) => arg.clone(),
|
|
||||||
None => MacroDecimal::Value(0.),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
_ => panic!("Unknown command in macro line:\n{} | {}", index, line),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Outline
|
|
||||||
'4' => {
|
|
||||||
let args = get_attr_args(chars)?;
|
|
||||||
|
|
||||||
macro_content.push(MacroContent::Outline(OutlinePrimitive {
|
|
||||||
exposure: args.first()? == &MacroDecimal::Value(1.0),
|
|
||||||
points: (args)
|
|
||||||
.chunks_exact(2)
|
|
||||||
.map(|c| (c[0].clone(), c[1].clone()))
|
|
||||||
.collect(),
|
|
||||||
angle: args.last()?.clone(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
// Polygon
|
|
||||||
'5' => {
|
|
||||||
let args = get_attr_args(chars)?;
|
|
||||||
|
|
||||||
macro_content.push(MacroContent::Polygon(PolygonPrimitive {
|
|
||||||
exposure: args.first()? == &MacroDecimal::Value(1.0),
|
|
||||||
vertices: match args.get(1)? {
|
|
||||||
MacroDecimal::Value(v) => *v as u8,
|
|
||||||
_ => 0,
|
|
||||||
},
|
|
||||||
center: (args.get(2)?.clone(), args.get(3)?.clone()),
|
|
||||||
diameter: args.get(4)?.clone(),
|
|
||||||
angle: match args.get(5) {
|
|
||||||
Some(arg) => arg.clone(),
|
|
||||||
None => MacroDecimal::Value(0.),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
'6' => todo!(), // Moiré
|
|
||||||
'7' => todo!(), // Thermal
|
|
||||||
'$' => todo!(), // Define variable
|
|
||||||
_ => panic!("Unknown command in macro line:\n{} | {}", index, line),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(ApertureMacro {
|
|
||||||
name: name.to_string(),
|
|
||||||
content: macro_content,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_attr_args(mut attribute_chars: Chars) -> Option<Vec<MacroDecimal>> {
|
|
||||||
attribute_chars.next_back()?;
|
|
||||||
attribute_chars.next()?;
|
|
||||||
|
|
||||||
Some(
|
|
||||||
attribute_chars
|
|
||||||
.as_str()
|
|
||||||
.split(",")
|
|
||||||
.map(|el| el.trim())
|
|
||||||
.map(|el| {
|
|
||||||
if el.starts_with("$") {
|
|
||||||
MacroDecimal::Expression(el.to_string())
|
|
||||||
} else {
|
|
||||||
MacroDecimal::Value(el.parse::<f64>().unwrap())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// def parse_content(self):
|
// def parse_content(self):
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use ::std::collections::HashMap;
|
use ::std::collections::HashMap;
|
||||||
use gerber_types::{
|
use gerber_types::{Aperture, ApertureDefinition, Command, CoordinateFormat, ExtendedCode, Unit};
|
||||||
Aperture, ApertureDefinition, ApertureMacro, Command, CoordinateFormat, ExtendedCode, Unit,
|
|
||||||
};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::iter::repeat;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
// Representation of Gerber document
|
// Representation of Gerber document
|
||||||
|
@ -13,8 +12,6 @@ pub struct GerberDoc {
|
||||||
pub format_specification: Option<CoordinateFormat>,
|
pub format_specification: Option<CoordinateFormat>,
|
||||||
/// map of apertures which can be used in draw commands later on in the document.
|
/// map of apertures which can be used in draw commands later on in the document.
|
||||||
pub apertures: HashMap<i32, Aperture>,
|
pub apertures: HashMap<i32, Aperture>,
|
||||||
/// map of aperture macro which can be used to create aperture definitions
|
|
||||||
pub aperture_macros: HashMap<String, ApertureMacro>,
|
|
||||||
// Anything else, draw commands, comments, attributes
|
// Anything else, draw commands, comments, attributes
|
||||||
pub commands: Vec<Command>,
|
pub commands: Vec<Command>,
|
||||||
}
|
}
|
||||||
|
@ -26,7 +23,6 @@ impl GerberDoc {
|
||||||
units: None,
|
units: None,
|
||||||
format_specification: None,
|
format_specification: None,
|
||||||
apertures: HashMap::new(),
|
apertures: HashMap::new(),
|
||||||
aperture_macros: HashMap::new(),
|
|
||||||
commands: Vec::new(),
|
commands: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,24 +33,26 @@ impl GerberDoc {
|
||||||
/// in the gerber-types rust crate. Note that aperture definitions will be sorted by code number
|
/// in the gerber-types rust crate. Note that aperture definitions will be sorted by code number
|
||||||
/// with lower codes being at the top of the command. This is independent of their order during
|
/// with lower codes being at the top of the command. This is independent of their order during
|
||||||
/// parsing.
|
/// parsing.
|
||||||
pub fn into_commands(self) -> Vec<Command> {
|
pub fn to_commands(mut self) -> Vec<Command> {
|
||||||
let mut gerber_doc = self;
|
|
||||||
let mut gerber_cmds: Vec<Command> = Vec::new();
|
let mut gerber_cmds: Vec<Command> = Vec::new();
|
||||||
gerber_cmds
|
gerber_cmds.push(ExtendedCode::CoordinateFormat(self.format_specification.unwrap()).into());
|
||||||
.push(ExtendedCode::CoordinateFormat(gerber_doc.format_specification.unwrap()).into());
|
gerber_cmds.push(ExtendedCode::Unit(self.units.unwrap()).into());
|
||||||
gerber_cmds.push(ExtendedCode::Unit(gerber_doc.units.unwrap()).into());
|
|
||||||
|
|
||||||
// we add the apertures to the list, but we sort by code. This means the order of the output
|
// we add the apertures to the list, but we sort by code. This means the order of the output
|
||||||
// is reproducible every time.
|
// is reproducible every time.
|
||||||
let mut apertures = gerber_doc.apertures.into_iter().collect::<Vec<_>>();
|
let mut apertures = self.apertures.into_iter().collect::<Vec<_>>();
|
||||||
apertures.sort_by_key(|tup| tup.0);
|
apertures.sort_by_key(|tup| tup.0);
|
||||||
for (code, aperture) in apertures {
|
for (code, aperture) in apertures {
|
||||||
gerber_cmds.push(
|
gerber_cmds.push(
|
||||||
ExtendedCode::ApertureDefinition(ApertureDefinition { code, aperture }).into(),
|
ExtendedCode::ApertureDefinition(ApertureDefinition {
|
||||||
|
code: code,
|
||||||
|
aperture: aperture,
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
gerber_cmds.append(&mut gerber_doc.commands);
|
gerber_cmds.append(&mut self.commands);
|
||||||
// TODO implement for units
|
// TODO implement for units
|
||||||
gerber_cmds
|
gerber_cmds
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
mod aperture_macros;
|
|
||||||
pub mod doc;
|
pub mod doc;
|
||||||
|
|
||||||
use aperture_macros::{continue_macro, start_macro};
|
|
||||||
use doc::GerberDoc;
|
use doc::GerberDoc;
|
||||||
use gerber_types::{
|
use gerber_types::{
|
||||||
Aperture, ApertureAttribute, ApertureFunction, Circle, Command, CoordinateFormat,
|
Aperture, ApertureAttribute, ApertureFunction, Circle, Command, CoordinateFormat,
|
||||||
|
@ -27,14 +25,12 @@ pub fn parse_gerber<T: Read>(reader: BufReader<T>) -> GerberDoc {
|
||||||
// By default the 'last coordinate' can be taken to be (0,0)
|
// By default the 'last coordinate' can be taken to be (0,0)
|
||||||
let mut last_coords = (0i64, 0i64);
|
let mut last_coords = (0i64, 0i64);
|
||||||
|
|
||||||
let mut aperture_macro: Option<Vec<String>> = None;
|
|
||||||
|
|
||||||
// naively define some regex terms
|
// naively define some regex terms
|
||||||
// TODO see which ones can be done without regex for better performance?
|
// TODO see which ones can be done without regex for better performance?
|
||||||
let re_units = regex!(r#"%MO(.*)\*%"#);
|
let re_units = regex!(r#"%MO(.*)\*%"#);
|
||||||
let re_comment = regex!(r#"G04 (.*)\*"#);
|
let re_comment = regex!(r#"G04 (.*)\*"#);
|
||||||
let re_formatspec = regex!(r#"%FSLAX(.*)Y(.*)\*%"#);
|
let re_formatspec = regex!(r#"%FSLAX(.*)Y(.*)\*%"#);
|
||||||
let re_aperture = regex!(r#"%ADD([0-9]+)(\w*),(.*)\*%"#);
|
let re_aperture = regex!(r#"%ADD([0-9]+)([A-Z]),(.*)\*%"#);
|
||||||
let re_interpolation = regex!(r#"X?(-?[0-9]+)?Y?(-?[0-9]+)?I?(-?[0-9]+)?J?(-?[0-9]+)?D01\*"#);
|
let re_interpolation = regex!(r#"X?(-?[0-9]+)?Y?(-?[0-9]+)?I?(-?[0-9]+)?J?(-?[0-9]+)?D01\*"#);
|
||||||
let re_move_or_flash = regex!(r#"X?(-?[0-9]+)?Y?(-?[0-9]+)?D0[2-3]*"#);
|
let re_move_or_flash = regex!(r#"X?(-?[0-9]+)?Y?(-?[0-9]+)?D0[2-3]*"#);
|
||||||
// TODO: handle escaped characters for attributes
|
// TODO: handle escaped characters for attributes
|
||||||
|
@ -50,12 +46,6 @@ pub fn parse_gerber<T: Read>(reader: BufReader<T>) -> GerberDoc {
|
||||||
debug!("{}. {}", index + 1, &line);
|
debug!("{}. {}", index + 1, &line);
|
||||||
|
|
||||||
if !line.is_empty() {
|
if !line.is_empty() {
|
||||||
// continue aperture macro if one is active
|
|
||||||
if aperture_macro.is_some() {
|
|
||||||
continue_macro(&mut gerber_doc, &mut aperture_macro, line);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut linechars = line.chars();
|
let mut linechars = line.chars();
|
||||||
|
|
||||||
match linechars.next().unwrap() {
|
match linechars.next().unwrap() {
|
||||||
|
@ -124,8 +114,7 @@ pub fn parse_gerber<T: Read>(reader: BufReader<T>) -> GerberDoc {
|
||||||
'A' => match linechars.next().unwrap() {
|
'A' => match linechars.next().unwrap() {
|
||||||
'D' => parse_aperture_defs(line, re_aperture, &mut gerber_doc), // AD
|
'D' => parse_aperture_defs(line, re_aperture, &mut gerber_doc), // AD
|
||||||
'M' => {
|
'M' => {
|
||||||
start_macro(&mut gerber_doc, &mut aperture_macro, line);
|
panic!("Aperture Macros (AM) are not supported yet.")
|
||||||
// panic!("Aperture Macros (AM) are not supported yet.")
|
|
||||||
} // AM
|
} // AM
|
||||||
_ => line_parse_failure(line, index),
|
_ => line_parse_failure(line, index),
|
||||||
},
|
},
|
||||||
|
@ -359,15 +348,8 @@ fn parse_aperture_defs(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(ap_macro) = gerber_doc.aperture_macros.get(aperture_type) {
|
|
||||||
gerber_doc.apertures.insert(
|
|
||||||
code,
|
|
||||||
Aperture::Other(format!("{}/{}", ap_macro.name, aperture_args.join(","))),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
panic!("Encountered unknown aperture definition statement")
|
panic!("Encountered unknown aperture definition statement")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// the insert state will be None if the key (i.e. aperture code) was not present yet,
|
// the insert state will be None if the key (i.e. aperture code) was not present yet,
|
||||||
|
@ -529,7 +511,7 @@ fn parse_move_or_flash(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_load_mirroring(mut _linechars: Chars, _gerber_doc: &mut GerberDoc) {
|
fn parse_load_mirroring(mut linechars: Chars, gerber_doc: &mut GerberDoc) {
|
||||||
// match linechars.next().unwrap() {
|
// match linechars.next().unwrap() {
|
||||||
// 'N' => gerber_doc.commands.push(value), //LMN
|
// 'N' => gerber_doc.commands.push(value), //LMN
|
||||||
// 'Y' => gerber_doc.commands.push(value), // LMY
|
// 'Y' => gerber_doc.commands.push(value), // LMY
|
||||||
|
@ -546,7 +528,7 @@ fn parse_load_mirroring(mut _linechars: Chars, _gerber_doc: &mut GerberDoc) {
|
||||||
// a step and repeat open statement has four (required) parameters that we need to extract
|
// a step and repeat open statement has four (required) parameters that we need to extract
|
||||||
// X (pos int) Y (pos int), I (decimal), J (decimal)
|
// X (pos int) Y (pos int), I (decimal), J (decimal)
|
||||||
fn parse_step_repeat_open(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) {
|
fn parse_step_repeat_open(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) {
|
||||||
// println!("SR line: {}", &line);
|
println!("SR line: {}", &line);
|
||||||
if let Some(regmatch) = re.captures(line) {
|
if let Some(regmatch) = re.captures(line) {
|
||||||
gerber_doc.commands.push(
|
gerber_doc.commands.push(
|
||||||
ExtendedCode::StepAndRepeat(StepAndRepeat::Open {
|
ExtendedCode::StepAndRepeat(StepAndRepeat::Open {
|
||||||
|
@ -595,7 +577,7 @@ fn parse_step_repeat_open(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) {
|
||||||
/// ⚠️ Any other Attributes (which seem to be valid within the gerber spec) we will **fail** to parse!
|
/// ⚠️ Any other Attributes (which seem to be valid within the gerber spec) we will **fail** to parse!
|
||||||
///
|
///
|
||||||
/// ⚠️ This parsing statement needs a lot of tests and validation at the current stage!
|
/// ⚠️ This parsing statement needs a lot of tests and validation at the current stage!
|
||||||
fn _parse_file_attribute(line: Chars, _re: &Regex, gerber_doc: &mut GerberDoc) {
|
fn parse_file_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) {
|
||||||
let attr_args = get_attr_args(line);
|
let attr_args = get_attr_args(line);
|
||||||
if attr_args.len() >= 2 {
|
if attr_args.len() >= 2 {
|
||||||
// we must have at least 1 field
|
// we must have at least 1 field
|
||||||
|
@ -644,7 +626,7 @@ fn _parse_file_attribute(line: Chars, _re: &Regex, gerber_doc: &mut GerberDoc) {
|
||||||
/// ⚠️ Any other Attributes (which seem to be valid within the gerber spec) we will **fail** to parse!
|
/// ⚠️ Any other Attributes (which seem to be valid within the gerber spec) we will **fail** to parse!
|
||||||
///
|
///
|
||||||
/// ⚠️ This parsing statement needs a lot of tests and validation at the current stage!
|
/// ⚠️ This parsing statement needs a lot of tests and validation at the current stage!
|
||||||
fn parse_aperture_attribute(line: Chars, _re: &Regex, gerber_doc: &mut GerberDoc) {
|
fn parse_aperture_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) {
|
||||||
let attr_args = get_attr_args(line);
|
let attr_args = get_attr_args(line);
|
||||||
if attr_args.len() >= 2 {
|
if attr_args.len() >= 2 {
|
||||||
// we must have at least 1 field
|
// we must have at least 1 field
|
||||||
|
@ -726,7 +708,7 @@ fn parse_aperture_attribute(line: Chars, _re: &Regex, gerber_doc: &mut GerberDoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_object_attribute(line: Chars, _re: &Regex, _gerber_doc: &mut GerberDoc) {
|
fn parse_object_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) {
|
||||||
let attr_args = get_attr_args(line);
|
let attr_args = get_attr_args(line);
|
||||||
if attr_args.len() >= 2 {
|
if attr_args.len() >= 2 {
|
||||||
// gerber_doc.commands.push(
|
// gerber_doc.commands.push(
|
||||||
|
@ -746,7 +728,7 @@ fn parse_object_attribute(line: Chars, _re: &Regex, _gerber_doc: &mut GerberDoc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_delete_attribute(line: Chars, _re: &Regex, gerber_doc: &mut GerberDoc) {
|
fn parse_delete_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) {
|
||||||
let attr_args = get_attr_args(line);
|
let attr_args = get_attr_args(line);
|
||||||
match attr_args.len() {
|
match attr_args.len() {
|
||||||
1 => gerber_doc
|
1 => gerber_doc
|
||||||
|
|
29
src/main.rs
29
src/main.rs
|
@ -6,15 +6,13 @@ mod export;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
mod gerber;
|
mod gerber;
|
||||||
mod outline_geometry;
|
mod outline_geometry;
|
||||||
mod resources;
|
|
||||||
|
|
||||||
use application::Application;
|
use application::Application;
|
||||||
|
|
||||||
use eframe::egui::{self, IconData};
|
use eframe::egui;
|
||||||
use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer};
|
use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer};
|
||||||
|
|
||||||
const APP_NAME: &str = "Outlinify";
|
const APP_NAME: &str = "Outlinify";
|
||||||
const ICON: &[u8] = include_bytes!("../resources/icon.png");
|
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
let stdout_log = tracing_subscriber::fmt::layer()
|
let stdout_log = tracing_subscriber::fmt::layer()
|
||||||
|
@ -30,38 +28,17 @@ fn main() -> eframe::Result {
|
||||||
let application = Application::new();
|
let application = Application::new();
|
||||||
|
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default()
|
viewport: egui::ViewportBuilder::default().with_inner_size([900.0, 700.0]),
|
||||||
.with_inner_size([900.0, 700.0])
|
|
||||||
.with_icon(load_icon()),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
options,
|
options,
|
||||||
Box::new(|cc| {
|
Box::new(|cc| {
|
||||||
// This gives us image support:
|
// This gives us image support:
|
||||||
egui_extras::install_image_loaders(&cc.egui_ctx);
|
// egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||||
|
|
||||||
Ok(Box::new(application))
|
Ok(Box::new(application))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_icon() -> IconData {
|
|
||||||
let (icon_rgba, icon_width, icon_height) = {
|
|
||||||
let icon = ICON;
|
|
||||||
let image = image::load_from_memory(icon)
|
|
||||||
.expect("Failed to open icon path")
|
|
||||||
.into_rgba8();
|
|
||||||
let (width, height) = image.dimensions();
|
|
||||||
let rgba = image.into_raw();
|
|
||||||
(rgba, width, height)
|
|
||||||
};
|
|
||||||
|
|
||||||
IconData {
|
|
||||||
rgba: icon_rgba,
|
|
||||||
width: icon_width,
|
|
||||||
height: icon_height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use clipper2::{EndType, JoinType};
|
use clipper2::{Bounds, EndType, JoinType, One, Paths};
|
||||||
|
use eframe::{
|
||||||
|
egui::{Rect, Shape, Stroke},
|
||||||
|
epaint::{PathShape, PathStroke},
|
||||||
|
};
|
||||||
|
use egui_plot::{PlotBounds, PlotItem, PlotPoint, Polygon};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
excellon::drills::Drills,
|
application::CanvasColour,
|
||||||
geometry::{
|
geometry::{elements::circle::Circle, point::Point, ClipperBounds, ClipperPaths, Unit},
|
||||||
point::Point, union::UnitedNets, ClipperBounds, ClipperPath, ClipperPaths, DrawableRaw,
|
|
||||||
Unit,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -14,8 +16,10 @@ pub enum GeometryType {
|
||||||
Points(Vec<Point>),
|
Points(Vec<Point>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct OutlineGeometry {
|
pub struct OutlineGeometry {
|
||||||
|
// pub path: ClipperPaths,
|
||||||
|
// pub points: Vec<Point>,
|
||||||
items: GeometryType,
|
items: GeometryType,
|
||||||
pub stroke: f32,
|
pub stroke: f32,
|
||||||
pub unit: Unit,
|
pub unit: Unit,
|
||||||
|
@ -24,31 +28,26 @@ pub struct OutlineGeometry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutlineGeometry {
|
impl OutlineGeometry {
|
||||||
pub fn new(united_nets: &UnitedNets, stroke: f32, unit: Unit, bounds_from: &str) -> Self {
|
pub fn new(
|
||||||
let mut outline_paths = ClipperPaths::new(vec![]);
|
outline: &ClipperPaths,
|
||||||
// inflate conductor net paths
|
stroke: f32,
|
||||||
for net in &united_nets.conductors {
|
unit: Unit,
|
||||||
outline_paths.push(
|
bounds_from: &str,
|
||||||
net.outline
|
bounds: ClipperBounds,
|
||||||
.inflate((stroke / 2.).into(), JoinType::Round, EndType::Polygon, 2.)
|
) -> Self {
|
||||||
.simplify(0.001, false),
|
// inflate given path
|
||||||
)
|
let outline = outline
|
||||||
}
|
.clone()
|
||||||
// inflate isolated apertures
|
.inflate((stroke / 2.).into(), JoinType::Round, EndType::Polygon, 2.0)
|
||||||
for ap in &united_nets.isolated_apertures {
|
.simplify(0.01, false);
|
||||||
outline_paths.push(
|
|
||||||
ap.to_paths()
|
|
||||||
.inflate((stroke / 2.).into(), JoinType::Round, EndType::Polygon, 2.)
|
|
||||||
.simplify(0.001, false),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
items: GeometryType::Paths(outline_paths),
|
// path: outline,
|
||||||
|
// points: Vec::new(),
|
||||||
|
items: GeometryType::Paths(outline),
|
||||||
stroke,
|
stroke,
|
||||||
unit,
|
unit,
|
||||||
bounds_from: bounds_from.into(),
|
bounds_from: bounds_from.into(),
|
||||||
bounding_box: united_nets.bounds(),
|
bounding_box: bounds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,30 +56,34 @@ impl OutlineGeometry {
|
||||||
stroke: f32,
|
stroke: f32,
|
||||||
unit: Unit,
|
unit: Unit,
|
||||||
bounds_from: &str,
|
bounds_from: &str,
|
||||||
|
bounds: ClipperBounds,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
// path: outline.clone(),
|
||||||
|
// points: Vec::new(),
|
||||||
items: GeometryType::Paths(outline.clone()),
|
items: GeometryType::Paths(outline.clone()),
|
||||||
stroke,
|
stroke,
|
||||||
unit,
|
unit,
|
||||||
bounds_from: bounds_from.into(),
|
bounds_from: bounds_from.into(),
|
||||||
bounding_box: outline.bounds(),
|
bounding_box: bounds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drill_marker(drills: &Drills, stroke: f32, unit: Unit, bounds_from: &str) -> Self {
|
pub fn point_marker(
|
||||||
|
points: Vec<Point>,
|
||||||
|
stroke: f32,
|
||||||
|
unit: Unit,
|
||||||
|
bounds_from: &str,
|
||||||
|
bounds: ClipperBounds,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: GeometryType::Points(drills.holes.iter().map(|c| c.canvas_pos()).collect()),
|
// path: Paths::new(vec![]),
|
||||||
|
// points,
|
||||||
|
items: GeometryType::Points(points),
|
||||||
stroke,
|
stroke,
|
||||||
unit,
|
unit,
|
||||||
bounds_from: bounds_from.into(),
|
bounds_from: bounds_from.into(),
|
||||||
bounding_box: ClipperPaths::from(
|
bounding_box: bounds,
|
||||||
drills
|
|
||||||
.holes
|
|
||||||
.iter()
|
|
||||||
.map(|hole| hole.outline.clone())
|
|
||||||
.collect::<Vec<ClipperPath>>(),
|
|
||||||
)
|
|
||||||
.bounds(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,3 +101,114 @@ impl OutlineGeometry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct OutlineShape {
|
||||||
|
stroke: f32,
|
||||||
|
selected: bool,
|
||||||
|
colour: CanvasColour,
|
||||||
|
items: GeometryType,
|
||||||
|
bounds: ClipperBounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl OutlineShape {
|
||||||
|
// pub fn new_from_geometry(
|
||||||
|
// geometry: &OutlineGeometry,
|
||||||
|
// colour: CanvasColour,
|
||||||
|
// selected: bool,
|
||||||
|
// ) -> Self {
|
||||||
|
// Self {
|
||||||
|
// stroke: geometry.stroke,
|
||||||
|
// selected,
|
||||||
|
// items: geometry.items.clone(),
|
||||||
|
// bounds: geometry.bounding_box,
|
||||||
|
// colour,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl PlotItem for OutlineShape {
|
||||||
|
// fn shapes(
|
||||||
|
// &self,
|
||||||
|
// _ui: &eframe::egui::Ui,
|
||||||
|
// transform: &egui_plot::PlotTransform,
|
||||||
|
// shapes: &mut Vec<eframe::egui::Shape>,
|
||||||
|
// ) {
|
||||||
|
// match &self.items {
|
||||||
|
// GeometryType::Paths(paths) => {
|
||||||
|
// for path in paths.iter() {
|
||||||
|
// let values_tf: Vec<_> = path
|
||||||
|
// .iter()
|
||||||
|
// .map(|v| transform.position_from_point(&PlotPoint::from(Point::from(v))))
|
||||||
|
// .collect();
|
||||||
|
|
||||||
|
// let shape = PathShape::closed_line(
|
||||||
|
// values_tf.clone(),
|
||||||
|
// PathStroke::new(20., self.colour.to_colour32(self.selected)),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// shapes.push(shape.into());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// GeometryType::Points(points) => {
|
||||||
|
// for point in points {
|
||||||
|
// // draw outline of hole
|
||||||
|
// let points = Circle::circle_segment_points(
|
||||||
|
// Point::from(transform.position_from_point(&PlotPoint::from(*point))),
|
||||||
|
// self.stroke.into(),
|
||||||
|
// 1.,
|
||||||
|
// 0.,
|
||||||
|
// )
|
||||||
|
// .into_iter()
|
||||||
|
// .map(|p| Point::from(p).into())
|
||||||
|
// .collect();
|
||||||
|
|
||||||
|
// let polygon = Shape::convex_polygon(
|
||||||
|
// points,
|
||||||
|
// self.colour.to_colour32(self.selected),
|
||||||
|
// Stroke::NONE,
|
||||||
|
// );
|
||||||
|
// shapes.push(polygon);
|
||||||
|
// }
|
||||||
|
// // TODO draw point circles
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // todo!()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn initialize(&mut self, x_range: std::ops::RangeInclusive<f64>) {
|
||||||
|
// {}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn name(&self) -> &str {
|
||||||
|
// "Test"
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn color(&self) -> eframe::egui::Color32 {
|
||||||
|
// self.colour.to_colour32(self.selected)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn highlight(&mut self) {
|
||||||
|
// {}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn highlighted(&self) -> bool {
|
||||||
|
// false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn allow_hover(&self) -> bool {
|
||||||
|
// false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn geometry(&self) -> egui_plot::PlotGeometry<'_> {
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn bounds(&self) -> PlotBounds {
|
||||||
|
// PlotBounds::from_min_max(self.bounds.min.into(), self.bounds.max.into())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn id(&self) -> Option<eframe::egui::Id> {
|
||||||
|
// None
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use error_stack::{Context, Report};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ResourceError;
|
|
||||||
|
|
||||||
impl ResourceError {
|
|
||||||
pub fn new(text: &str) -> Report<Self> {
|
|
||||||
Report::new(Self).attach_printable(text.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ResourceError {
|
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
fmt.write_str("Error loading resource")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context for ResourceError {}
|
|
|
@ -1,37 +0,0 @@
|
||||||
use eframe::egui::{FontData, FontDefinitions};
|
|
||||||
// const FONT_ROBOTO: &[u8] = include_bytes!("../../resources/Roboto-Light.ttf");
|
|
||||||
// const FONT_ROBOTO_MONO: &[u8] = include_bytes!("../../resources/RobotoMono-Light.ttf");
|
|
||||||
const FONT_OPEN_SANS: &[u8] = include_bytes!("../../resources/OpenSans-Light.ttf");
|
|
||||||
|
|
||||||
// pub fn register_fonts(style: &mut Style) {
|
|
||||||
// use eframe::epaint::FontFamily::{Monospace, Proportional};
|
|
||||||
|
|
||||||
// style.text_styles = [
|
|
||||||
// (TextStyle::Body, FontId::new(21.0, Proportional)),
|
|
||||||
// (TextStyle::Button, FontId::new(18.0, Monospace)),
|
|
||||||
// (TextStyle::Monospace, FontId::new(18.0, Monospace)),
|
|
||||||
// (TextStyle::Small, FontId::new(16.0, Proportional)),
|
|
||||||
// (TextStyle::Heading, FontId::new(48.0, Proportional)),
|
|
||||||
// ]
|
|
||||||
// .into();
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn load_fonts() -> FontDefinitions {
|
|
||||||
use eframe::epaint::FontFamily::{Monospace, Proportional};
|
|
||||||
|
|
||||||
let mut fonts = FontDefinitions::default();
|
|
||||||
// let roboto = FontData::from_static(FONT_ROBOTO);
|
|
||||||
// let roboto_mono = FontData::from_static(FONT_ROBOTO_MONO);
|
|
||||||
let open_sans = FontData::from_static(FONT_OPEN_SANS);
|
|
||||||
|
|
||||||
// fonts.font_data.insert("roboto".into(), roboto);
|
|
||||||
// fonts.font_data.insert("roboto_mono".into(), roboto_mono);
|
|
||||||
fonts.font_data.insert("OpenSans".into(), open_sans);
|
|
||||||
|
|
||||||
// fonts.families.insert(Monospace, vec!["roboto_mono".into()]);
|
|
||||||
// fonts.families.insert(Monospace, vec!["roboto".into()]);
|
|
||||||
fonts.families.insert(Proportional, vec!["OpenSans".into()]);
|
|
||||||
fonts.families.insert(Monospace, vec!["OpenSans".into()]);
|
|
||||||
|
|
||||||
fonts
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
use eframe::egui::{include_image, ImageSource};
|
|
||||||
|
|
||||||
pub struct Icons<'a> {
|
|
||||||
pub gerber_file: ImageSource<'a>,
|
|
||||||
pub excellon_file: ImageSource<'a>,
|
|
||||||
pub geometry_file: ImageSource<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Icons<'a> {
|
|
||||||
pub fn preload() -> Self {
|
|
||||||
Self {
|
|
||||||
gerber_file: include_image!("../../resources/gerber_file.png"),
|
|
||||||
excellon_file: include_image!("../../resources/excellon_file.png"),
|
|
||||||
geometry_file: include_image!("../../resources/geometry_file.png"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// mod fonts;
|
|
||||||
mod errors;
|
|
||||||
mod icons;
|
|
||||||
|
|
||||||
pub use self::icons::Icons;
|
|
||||||
|
|
||||||
pub struct ResourceLoader<'a> {
|
|
||||||
icons: Icons<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ResourceLoader<'a> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
icons: Icons::preload(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn fonts(&self) -> FontDefinitions {
|
|
||||||
// fonts::load_fonts()
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn icons(&self) -> &Icons {
|
|
||||||
&self.icons
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue