added local fork of gerber-types-rs
This commit is contained in:
		
							parent
							
								
									7913541282
								
							
						
					
					
						commit
						ff45b5ef03
					
				
							
								
								
									
										3
									
								
								gerber-types-rs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								gerber-types-rs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					target
 | 
				
			||||||
 | 
					Cargo.lock
 | 
				
			||||||
 | 
					*.swp
 | 
				
			||||||
							
								
								
									
										44
									
								
								gerber-types-rs/CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								gerber-types-rs/CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					# Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This project follows semantic versioning.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Possible log types:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `[added]` for new features.
 | 
				
			||||||
 | 
					- `[changed]` for changes in existing functionality.
 | 
				
			||||||
 | 
					- `[deprecated]` for once-stable features removed in upcoming releases.
 | 
				
			||||||
 | 
					- `[removed]` for deprecated features removed in this release.
 | 
				
			||||||
 | 
					- `[fixed]` for any bug fixes.
 | 
				
			||||||
 | 
					- `[security]` to invite users to upgrade in case of vulnerabilities.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### v0.3.0 (2022-07-05)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [fixed] Fix whitespace in G04 comment serialization (#33)
 | 
				
			||||||
 | 
					- [changed] Updated dependencies
 | 
				
			||||||
 | 
					- [changed] A fixed MSRV was dropped
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Thanks @NemoAndrea for contributions!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### v0.2.0 (2021-01-06)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This release requires at least Rust 1.31 (2018 edition).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [added] Implement constructors for `Circle` and `Rectangular`
 | 
				
			||||||
 | 
					- [added] Derive Clone for all structs and enums (#16)
 | 
				
			||||||
 | 
					- [added] Derive PartialEq and Eq where possible
 | 
				
			||||||
 | 
					- [added] Implement `From<FunctionCode>` and `From<ExtendedCode>` for Command
 | 
				
			||||||
 | 
					- [added] Impl `From<>` for Command, FunctionCode and ExtendedCode
 | 
				
			||||||
 | 
					- [added] New builder-style constructors (#22)
 | 
				
			||||||
 | 
					- [added] Support for more FileFunction and FileAttribute variants (#26, #30)
 | 
				
			||||||
 | 
					- [changed] Derive Copy for some trivial enums
 | 
				
			||||||
 | 
					- [changed] Create new internal `PartialGerberCode` trait (#18)
 | 
				
			||||||
 | 
					- [changed] Split up code into more modules
 | 
				
			||||||
 | 
					- [changed] Upgraded all dependencies
 | 
				
			||||||
 | 
					- [changed] Require Rust 1.31+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Thanks @connorkuehl and @twitchyliquid64 for contributions!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### v0.1.1 (2017-06-10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- First crates.io release
 | 
				
			||||||
							
								
								
									
										24
									
								
								gerber-types-rs/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								gerber-types-rs/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "gerber-types"
 | 
				
			||||||
 | 
					version = "0.3.0"
 | 
				
			||||||
 | 
					documentation = "https://docs.rs/gerber-types/"
 | 
				
			||||||
 | 
					repository = "https://github.com/dbrgn/gerber-types-rs"
 | 
				
			||||||
 | 
					license = "MIT OR Apache-2.0"
 | 
				
			||||||
 | 
					authors = ["Danilo Bargen <mail@dbrgn.ch>"]
 | 
				
			||||||
 | 
					description = "Types and code generation for Gerber files (RS-274X)."
 | 
				
			||||||
 | 
					readme = "README.md"
 | 
				
			||||||
 | 
					keywords = ["gerber", "pcb", "rs274x", "cad", "cam"]
 | 
				
			||||||
 | 
					include = [
 | 
				
			||||||
 | 
					    "**/*.rs",
 | 
				
			||||||
 | 
					    "Cargo.toml",
 | 
				
			||||||
 | 
					    "README.md",
 | 
				
			||||||
 | 
					    "LICENSE-*",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					edition = "2018"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					chrono = "0.4"
 | 
				
			||||||
 | 
					conv = "0.3"
 | 
				
			||||||
 | 
					num-rational = "0.4"
 | 
				
			||||||
 | 
					thiserror = "1"
 | 
				
			||||||
 | 
					uuid = "1"
 | 
				
			||||||
							
								
								
									
										176
									
								
								gerber-types-rs/LICENSE-APACHE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								gerber-types-rs/LICENSE-APACHE
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,176 @@
 | 
				
			|||||||
 | 
					                                 Apache License
 | 
				
			||||||
 | 
					                           Version 2.0, January 2004
 | 
				
			||||||
 | 
					                        http://www.apache.org/licenses/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   1. Definitions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "License" shall mean the terms and conditions for use, reproduction,
 | 
				
			||||||
 | 
					      and distribution as defined by Sections 1 through 9 of this document.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Licensor" shall mean the copyright owner or entity authorized by
 | 
				
			||||||
 | 
					      the copyright owner that is granting the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Legal Entity" shall mean the union of the acting entity and all
 | 
				
			||||||
 | 
					      other entities that control, are controlled by, or are under common
 | 
				
			||||||
 | 
					      control with that entity. For the purposes of this definition,
 | 
				
			||||||
 | 
					      "control" means (i) the power, direct or indirect, to cause the
 | 
				
			||||||
 | 
					      direction or management of such entity, whether by contract or
 | 
				
			||||||
 | 
					      otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
				
			||||||
 | 
					      outstanding shares, or (iii) beneficial ownership of such entity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "You" (or "Your") shall mean an individual or Legal Entity
 | 
				
			||||||
 | 
					      exercising permissions granted by this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Source" form shall mean the preferred form for making modifications,
 | 
				
			||||||
 | 
					      including but not limited to software source code, documentation
 | 
				
			||||||
 | 
					      source, and configuration files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Object" form shall mean any form resulting from mechanical
 | 
				
			||||||
 | 
					      transformation or translation of a Source form, including but
 | 
				
			||||||
 | 
					      not limited to compiled object code, generated documentation,
 | 
				
			||||||
 | 
					      and conversions to other media types.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Work" shall mean the work of authorship, whether in Source or
 | 
				
			||||||
 | 
					      Object form, made available under the License, as indicated by a
 | 
				
			||||||
 | 
					      copyright notice that is included in or attached to the work
 | 
				
			||||||
 | 
					      (an example is provided in the Appendix below).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Derivative Works" shall mean any work, whether in Source or Object
 | 
				
			||||||
 | 
					      form, that is based on (or derived from) the Work and for which the
 | 
				
			||||||
 | 
					      editorial revisions, annotations, elaborations, or other modifications
 | 
				
			||||||
 | 
					      represent, as a whole, an original work of authorship. For the purposes
 | 
				
			||||||
 | 
					      of this License, Derivative Works shall not include works that remain
 | 
				
			||||||
 | 
					      separable from, or merely link (or bind by name) to the interfaces of,
 | 
				
			||||||
 | 
					      the Work and Derivative Works thereof.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Contribution" shall mean any work of authorship, including
 | 
				
			||||||
 | 
					      the original version of the Work and any modifications or additions
 | 
				
			||||||
 | 
					      to that Work or Derivative Works thereof, that is intentionally
 | 
				
			||||||
 | 
					      submitted to Licensor for inclusion in the Work by the copyright owner
 | 
				
			||||||
 | 
					      or by an individual or Legal Entity authorized to submit on behalf of
 | 
				
			||||||
 | 
					      the copyright owner. For the purposes of this definition, "submitted"
 | 
				
			||||||
 | 
					      means any form of electronic, verbal, or written communication sent
 | 
				
			||||||
 | 
					      to the Licensor or its representatives, including but not limited to
 | 
				
			||||||
 | 
					      communication on electronic mailing lists, source code control systems,
 | 
				
			||||||
 | 
					      and issue tracking systems that are managed by, or on behalf of, the
 | 
				
			||||||
 | 
					      Licensor for the purpose of discussing and improving the Work, but
 | 
				
			||||||
 | 
					      excluding communication that is conspicuously marked or otherwise
 | 
				
			||||||
 | 
					      designated in writing by the copyright owner as "Not a Contribution."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
				
			||||||
 | 
					      on behalf of whom a Contribution has been received by Licensor and
 | 
				
			||||||
 | 
					      subsequently incorporated within the Work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   2. Grant of Copyright License. Subject to the terms and conditions of
 | 
				
			||||||
 | 
					      this License, each Contributor hereby grants to You a perpetual,
 | 
				
			||||||
 | 
					      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
				
			||||||
 | 
					      copyright license to reproduce, prepare Derivative Works of,
 | 
				
			||||||
 | 
					      publicly display, publicly perform, sublicense, and distribute the
 | 
				
			||||||
 | 
					      Work and such Derivative Works in Source or Object form.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   3. Grant of Patent License. Subject to the terms and conditions of
 | 
				
			||||||
 | 
					      this License, each Contributor hereby grants to You a perpetual,
 | 
				
			||||||
 | 
					      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
				
			||||||
 | 
					      (except as stated in this section) patent license to make, have made,
 | 
				
			||||||
 | 
					      use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
				
			||||||
 | 
					      where such license applies only to those patent claims licensable
 | 
				
			||||||
 | 
					      by such Contributor that are necessarily infringed by their
 | 
				
			||||||
 | 
					      Contribution(s) alone or by combination of their Contribution(s)
 | 
				
			||||||
 | 
					      with the Work to which such Contribution(s) was submitted. If You
 | 
				
			||||||
 | 
					      institute patent litigation against any entity (including a
 | 
				
			||||||
 | 
					      cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
				
			||||||
 | 
					      or a Contribution incorporated within the Work constitutes direct
 | 
				
			||||||
 | 
					      or contributory patent infringement, then any patent licenses
 | 
				
			||||||
 | 
					      granted to You under this License for that Work shall terminate
 | 
				
			||||||
 | 
					      as of the date such litigation is filed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   4. Redistribution. You may reproduce and distribute copies of the
 | 
				
			||||||
 | 
					      Work or Derivative Works thereof in any medium, with or without
 | 
				
			||||||
 | 
					      modifications, and in Source or Object form, provided that You
 | 
				
			||||||
 | 
					      meet the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (a) You must give any other recipients of the Work or
 | 
				
			||||||
 | 
					          Derivative Works a copy of this License; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (b) You must cause any modified files to carry prominent notices
 | 
				
			||||||
 | 
					          stating that You changed the files; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (c) You must retain, in the Source form of any Derivative Works
 | 
				
			||||||
 | 
					          that You distribute, all copyright, patent, trademark, and
 | 
				
			||||||
 | 
					          attribution notices from the Source form of the Work,
 | 
				
			||||||
 | 
					          excluding those notices that do not pertain to any part of
 | 
				
			||||||
 | 
					          the Derivative Works; and
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      (d) If the Work includes a "NOTICE" text file as part of its
 | 
				
			||||||
 | 
					          distribution, then any Derivative Works that You distribute must
 | 
				
			||||||
 | 
					          include a readable copy of the attribution notices contained
 | 
				
			||||||
 | 
					          within such NOTICE file, excluding those notices that do not
 | 
				
			||||||
 | 
					          pertain to any part of the Derivative Works, in at least one
 | 
				
			||||||
 | 
					          of the following places: within a NOTICE text file distributed
 | 
				
			||||||
 | 
					          as part of the Derivative Works; within the Source form or
 | 
				
			||||||
 | 
					          documentation, if provided along with the Derivative Works; or,
 | 
				
			||||||
 | 
					          within a display generated by the Derivative Works, if and
 | 
				
			||||||
 | 
					          wherever such third-party notices normally appear. The contents
 | 
				
			||||||
 | 
					          of the NOTICE file are for informational purposes only and
 | 
				
			||||||
 | 
					          do not modify the License. You may add Your own attribution
 | 
				
			||||||
 | 
					          notices within Derivative Works that You distribute, alongside
 | 
				
			||||||
 | 
					          or as an addendum to the NOTICE text from the Work, provided
 | 
				
			||||||
 | 
					          that such additional attribution notices cannot be construed
 | 
				
			||||||
 | 
					          as modifying the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      You may add Your own copyright statement to Your modifications and
 | 
				
			||||||
 | 
					      may provide additional or different license terms and conditions
 | 
				
			||||||
 | 
					      for use, reproduction, or distribution of Your modifications, or
 | 
				
			||||||
 | 
					      for any such Derivative Works as a whole, provided Your use,
 | 
				
			||||||
 | 
					      reproduction, and distribution of the Work otherwise complies with
 | 
				
			||||||
 | 
					      the conditions stated in this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
				
			||||||
 | 
					      any Contribution intentionally submitted for inclusion in the Work
 | 
				
			||||||
 | 
					      by You to the Licensor shall be under the terms and conditions of
 | 
				
			||||||
 | 
					      this License, without any additional terms or conditions.
 | 
				
			||||||
 | 
					      Notwithstanding the above, nothing herein shall supersede or modify
 | 
				
			||||||
 | 
					      the terms of any separate license agreement you may have executed
 | 
				
			||||||
 | 
					      with Licensor regarding such Contributions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   6. Trademarks. This License does not grant permission to use the trade
 | 
				
			||||||
 | 
					      names, trademarks, service marks, or product names of the Licensor,
 | 
				
			||||||
 | 
					      except as required for reasonable and customary use in describing the
 | 
				
			||||||
 | 
					      origin of the Work and reproducing the content of the NOTICE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   7. Disclaimer of Warranty. Unless required by applicable law or
 | 
				
			||||||
 | 
					      agreed to in writing, Licensor provides the Work (and each
 | 
				
			||||||
 | 
					      Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
				
			||||||
 | 
					      implied, including, without limitation, any warranties or conditions
 | 
				
			||||||
 | 
					      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
				
			||||||
 | 
					      PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
				
			||||||
 | 
					      appropriateness of using or redistributing the Work and assume any
 | 
				
			||||||
 | 
					      risks associated with Your exercise of permissions under this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   8. Limitation of Liability. In no event and under no legal theory,
 | 
				
			||||||
 | 
					      whether in tort (including negligence), contract, or otherwise,
 | 
				
			||||||
 | 
					      unless required by applicable law (such as deliberate and grossly
 | 
				
			||||||
 | 
					      negligent acts) or agreed to in writing, shall any Contributor be
 | 
				
			||||||
 | 
					      liable to You for damages, including any direct, indirect, special,
 | 
				
			||||||
 | 
					      incidental, or consequential damages of any character arising as a
 | 
				
			||||||
 | 
					      result of this License or out of the use or inability to use the
 | 
				
			||||||
 | 
					      Work (including but not limited to damages for loss of goodwill,
 | 
				
			||||||
 | 
					      work stoppage, computer failure or malfunction, or any and all
 | 
				
			||||||
 | 
					      other commercial damages or losses), even if such Contributor
 | 
				
			||||||
 | 
					      has been advised of the possibility of such damages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   9. Accepting Warranty or Additional Liability. While redistributing
 | 
				
			||||||
 | 
					      the Work or Derivative Works thereof, You may choose to offer,
 | 
				
			||||||
 | 
					      and charge a fee for, acceptance of support, warranty, indemnity,
 | 
				
			||||||
 | 
					      or other liability obligations and/or rights consistent with this
 | 
				
			||||||
 | 
					      License. However, in accepting such obligations, You may act only
 | 
				
			||||||
 | 
					      on Your own behalf and on Your sole responsibility, not on behalf
 | 
				
			||||||
 | 
					      of any other Contributor, and only if You agree to indemnify,
 | 
				
			||||||
 | 
					      defend, and hold each Contributor harmless for any liability
 | 
				
			||||||
 | 
					      incurred by, or claims asserted against, such Contributor by reason
 | 
				
			||||||
 | 
					      of your accepting any such warranty or additional liability.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   END OF TERMS AND CONDITIONS
 | 
				
			||||||
							
								
								
									
										19
									
								
								gerber-types-rs/LICENSE-MIT
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								gerber-types-rs/LICENSE-MIT
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					Copyright (c) 2016-2021 Danilo Bargen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
							
								
								
									
										47
									
								
								gerber-types-rs/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								gerber-types-rs/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					# Rust Gerber Library
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[![Build status][build-status-badge]][build-status]
 | 
				
			||||||
 | 
					[![Crates.io][crates-io-badge]][crates-io]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Docs (released)](https://docs.rs/gerber-types/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This crate implements the basic building blocks of Gerber X2 (compatible with
 | 
				
			||||||
 | 
					Gerber RS-274X) code. It focusses on the low level types (to be used like an
 | 
				
			||||||
 | 
					AST) and code generation and does not do any semantic checking.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For example, you can use an aperture without defining it. This will generate
 | 
				
			||||||
 | 
					syntactically valid but semantially invalid Gerber code, but this module won't
 | 
				
			||||||
 | 
					complain.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The plan is to write a high-level wrapper library on top of this. Early drafts
 | 
				
			||||||
 | 
					[are in progress](https://github.com/dbrgn/gerber-rs) but the design isn't
 | 
				
			||||||
 | 
					fixed yet.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Current Gerber X2 spec: https://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can find an example in the [`examples`
 | 
				
			||||||
 | 
					directory](https://github.com/dbrgn/gerber-types-rs/blob/main/examples/polarities-apertures.rs).
 | 
				
			||||||
 | 
					It's still quite verbose, the goal is to make the API a bit more ergonomic in
 | 
				
			||||||
 | 
					the future. (This library has a low-level focus though, so it will never get a
 | 
				
			||||||
 | 
					high-level API. That is the task of other libraries.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To generate Gerber code for that example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ cargo run --example polarities-apertures
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under either of
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
 | 
				
			||||||
 | 
					 * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					at your option.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Badges -->
 | 
				
			||||||
 | 
					[build-status]: https://github.com/dbrgn/gerber-types-rs/actions?query=workflow%3ACI
 | 
				
			||||||
 | 
					[build-status-badge]: https://img.shields.io/github/workflow/status/dbrgn/gerber-types-rs/CI/main
 | 
				
			||||||
 | 
					[crates-io]: https://crates.io/crates/gerber-types
 | 
				
			||||||
 | 
					[crates-io-badge]: https://img.shields.io/crates/v/gerber-types.svg
 | 
				
			||||||
							
								
								
									
										24
									
								
								gerber-types-rs/RELEASING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								gerber-types-rs/RELEASING.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					# Releasing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Set variables:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ export VERSION=X.Y.Z
 | 
				
			||||||
 | 
					    $ export GPG_KEY=EA456E8BAF0109429583EED83578F667F2F3A5FA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Update version numbers:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ vim -p Cargo.toml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Update changelog:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ vim CHANGELOG.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Commit & tag:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ git commit -S${GPG_KEY} -m "Release v${VERSION}"
 | 
				
			||||||
 | 
					    $ git tag -s -u ${GPG_KEY} v${VERSION} -m "Version ${VERSION}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Publish:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ cargo publish
 | 
				
			||||||
 | 
					    $ git push && git push --tags
 | 
				
			||||||
							
								
								
									
										410
									
								
								gerber-types-rs/src/attributes.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								gerber-types-rs/src/attributes.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,410 @@
 | 
				
			|||||||
 | 
					//! Attributes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::io::Write;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use chrono::{DateTime, Utc};
 | 
				
			||||||
 | 
					use uuid::Uuid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::errors::GerberResult;
 | 
				
			||||||
 | 
					use crate::traits::PartialGerberCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FileAttribute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum FileAttribute {
 | 
				
			||||||
 | 
					    Part(Part),
 | 
				
			||||||
 | 
					    FileFunction(FileFunction),
 | 
				
			||||||
 | 
					    FilePolarity(FilePolarity),
 | 
				
			||||||
 | 
					    GenerationSoftware(GenerationSoftware),
 | 
				
			||||||
 | 
					    CreationDate(DateTime<Utc>),
 | 
				
			||||||
 | 
					    ProjectId {
 | 
				
			||||||
 | 
					        id: String,
 | 
				
			||||||
 | 
					        guid: Uuid,
 | 
				
			||||||
 | 
					        revision: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Md5(String),
 | 
				
			||||||
 | 
					    UserDefined {
 | 
				
			||||||
 | 
					        name: String,
 | 
				
			||||||
 | 
					        value: Vec<String>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for FileAttribute {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            FileAttribute::Part(ref part) => {
 | 
				
			||||||
 | 
					                write!(writer, "Part,")?;
 | 
				
			||||||
 | 
					                part.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            FileAttribute::FileFunction(ref function) => {
 | 
				
			||||||
 | 
					                write!(writer, "FileFunction,")?;
 | 
				
			||||||
 | 
					                match function {
 | 
				
			||||||
 | 
					                    FileFunction::Copper {
 | 
				
			||||||
 | 
					                        ref layer,
 | 
				
			||||||
 | 
					                        ref pos,
 | 
				
			||||||
 | 
					                        ref copper_type,
 | 
				
			||||||
 | 
					                    } => {
 | 
				
			||||||
 | 
					                        write!(writer, "Copper,L{},", layer)?;
 | 
				
			||||||
 | 
					                        pos.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                        if let Some(ref t) = *copper_type {
 | 
				
			||||||
 | 
					                            write!(writer, ",")?;
 | 
				
			||||||
 | 
					                            t.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    FileFunction::Profile(ref plating) => {
 | 
				
			||||||
 | 
					                        write!(writer, "Profile,")?;
 | 
				
			||||||
 | 
					                        plating.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    FileFunction::Soldermask { ref pos, ref index } => {
 | 
				
			||||||
 | 
					                        write!(writer, "Soldermask,")?;
 | 
				
			||||||
 | 
					                        pos.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                        if let Some(ref i) = index {
 | 
				
			||||||
 | 
					                            write!(writer, ",{}", *i)?;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    FileFunction::Legend { ref pos, ref index } => {
 | 
				
			||||||
 | 
					                        write!(writer, "Legend,")?;
 | 
				
			||||||
 | 
					                        pos.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                        if let Some(ref i) = index {
 | 
				
			||||||
 | 
					                            write!(writer, ",{}", *i)?;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    _ => unimplemented!(),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            FileAttribute::GenerationSoftware(ref gs) => {
 | 
				
			||||||
 | 
					                write!(writer, "GenerationSoftware,")?;
 | 
				
			||||||
 | 
					                gs.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            FileAttribute::FilePolarity(ref p) => {
 | 
				
			||||||
 | 
					                write!(writer, "FilePolarity,")?;
 | 
				
			||||||
 | 
					                p.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            FileAttribute::Md5(ref hash) => write!(writer, "MD5,{}", hash)?,
 | 
				
			||||||
 | 
					            _ => unimplemented!(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApertureAttribute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub enum ApertureAttribute {
 | 
				
			||||||
 | 
					    ApertureFunction(ApertureFunction),
 | 
				
			||||||
 | 
					    DrillTolerance { plus: f64, minus: f64 },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Part
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum Part {
 | 
				
			||||||
 | 
					    /// Single PCB
 | 
				
			||||||
 | 
					    Single,
 | 
				
			||||||
 | 
					    /// A.k.a. customer panel, assembly panel, shipping panel, biscuit
 | 
				
			||||||
 | 
					    Array,
 | 
				
			||||||
 | 
					    /// A.k.a. working panel, production panel
 | 
				
			||||||
 | 
					    FabricationPanel,
 | 
				
			||||||
 | 
					    /// A test coupon
 | 
				
			||||||
 | 
					    Coupon,
 | 
				
			||||||
 | 
					    /// None of the above
 | 
				
			||||||
 | 
					    Other(String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Part {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            Part::Single => write!(writer, "Single")?,
 | 
				
			||||||
 | 
					            Part::Array => write!(writer, "Array")?,
 | 
				
			||||||
 | 
					            Part::FabricationPanel => write!(writer, "FabricationPanel")?,
 | 
				
			||||||
 | 
					            Part::Coupon => write!(writer, "Coupon")?,
 | 
				
			||||||
 | 
					            Part::Other(ref description) => write!(writer, "Other,{}", description)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Position
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum Position {
 | 
				
			||||||
 | 
					    Top,
 | 
				
			||||||
 | 
					    Bottom,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Position {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            Position::Top => write!(writer, "Top")?,
 | 
				
			||||||
 | 
					            Position::Bottom => write!(writer, "Bot")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ExtendedPosition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum ExtendedPosition {
 | 
				
			||||||
 | 
					    Top,
 | 
				
			||||||
 | 
					    Inner,
 | 
				
			||||||
 | 
					    Bottom,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for ExtendedPosition {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            ExtendedPosition::Top => write!(writer, "Top")?,
 | 
				
			||||||
 | 
					            ExtendedPosition::Inner => write!(writer, "Inr")?,
 | 
				
			||||||
 | 
					            ExtendedPosition::Bottom => write!(writer, "Bot")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CopperType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum CopperType {
 | 
				
			||||||
 | 
					    Plane,
 | 
				
			||||||
 | 
					    Signal,
 | 
				
			||||||
 | 
					    Mixed,
 | 
				
			||||||
 | 
					    Hatched,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for CopperType {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            CopperType::Plane => write!(writer, "Plane")?,
 | 
				
			||||||
 | 
					            CopperType::Signal => write!(writer, "Signal")?,
 | 
				
			||||||
 | 
					            CopperType::Mixed => write!(writer, "Mixed")?,
 | 
				
			||||||
 | 
					            CopperType::Hatched => write!(writer, "Hatched")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Drill
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum Drill {
 | 
				
			||||||
 | 
					    ThroughHole,
 | 
				
			||||||
 | 
					    Blind,
 | 
				
			||||||
 | 
					    Buried,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DrillRouteType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum DrillRouteType {
 | 
				
			||||||
 | 
					    Drill,
 | 
				
			||||||
 | 
					    Route,
 | 
				
			||||||
 | 
					    Mixed,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Profile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum Profile {
 | 
				
			||||||
 | 
					    Plated,
 | 
				
			||||||
 | 
					    NonPlated,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Profile {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            Profile::Plated => write!(writer, "P")?,
 | 
				
			||||||
 | 
					            Profile::NonPlated => write!(writer, "NP")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FileFunction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum FileFunction {
 | 
				
			||||||
 | 
					    Copper {
 | 
				
			||||||
 | 
					        layer: i32,
 | 
				
			||||||
 | 
					        pos: ExtendedPosition,
 | 
				
			||||||
 | 
					        copper_type: Option<CopperType>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Soldermask {
 | 
				
			||||||
 | 
					        pos: Position,
 | 
				
			||||||
 | 
					        index: Option<i32>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Legend {
 | 
				
			||||||
 | 
					        pos: Position,
 | 
				
			||||||
 | 
					        index: Option<i32>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Goldmask {
 | 
				
			||||||
 | 
					        pos: Position,
 | 
				
			||||||
 | 
					        index: Option<i32>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Silvermask {
 | 
				
			||||||
 | 
					        pos: Position,
 | 
				
			||||||
 | 
					        index: Option<i32>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Tinmask {
 | 
				
			||||||
 | 
					        pos: Position,
 | 
				
			||||||
 | 
					        index: Option<i32>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Carbonmask {
 | 
				
			||||||
 | 
					        pos: Position,
 | 
				
			||||||
 | 
					        index: Option<i32>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Peelablesoldermask {
 | 
				
			||||||
 | 
					        pos: Position,
 | 
				
			||||||
 | 
					        index: Option<i32>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Glue {
 | 
				
			||||||
 | 
					        pos: Position,
 | 
				
			||||||
 | 
					        index: Option<i32>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Viatenting(Position),
 | 
				
			||||||
 | 
					    Viafill,
 | 
				
			||||||
 | 
					    Heatsink(Position),
 | 
				
			||||||
 | 
					    Paste(Position),
 | 
				
			||||||
 | 
					    KeepOut(Position),
 | 
				
			||||||
 | 
					    Pads(Position),
 | 
				
			||||||
 | 
					    Scoring(Position),
 | 
				
			||||||
 | 
					    Plated {
 | 
				
			||||||
 | 
					        from_layer: i32,
 | 
				
			||||||
 | 
					        to_layer: i32,
 | 
				
			||||||
 | 
					        drill: Drill,
 | 
				
			||||||
 | 
					        label: Option<DrillRouteType>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    NonPlated {
 | 
				
			||||||
 | 
					        from_layer: i32,
 | 
				
			||||||
 | 
					        to_layer: i32,
 | 
				
			||||||
 | 
					        drill: Drill,
 | 
				
			||||||
 | 
					        label: Option<DrillRouteType>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Profile(Profile),
 | 
				
			||||||
 | 
					    Drillmap,
 | 
				
			||||||
 | 
					    FabricationDrawing,
 | 
				
			||||||
 | 
					    ArrayDrawing,
 | 
				
			||||||
 | 
					    AssemblyDrawing(Position),
 | 
				
			||||||
 | 
					    Drawing(String),
 | 
				
			||||||
 | 
					    Other(String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FilePolarity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum FilePolarity {
 | 
				
			||||||
 | 
					    Positive,
 | 
				
			||||||
 | 
					    Negative,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for FilePolarity {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            FilePolarity::Positive => write!(writer, "Positive")?,
 | 
				
			||||||
 | 
					            FilePolarity::Negative => write!(writer, "Negative")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GenerationSoftware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub struct GenerationSoftware {
 | 
				
			||||||
 | 
					    pub vendor: String,
 | 
				
			||||||
 | 
					    pub application: String,
 | 
				
			||||||
 | 
					    pub version: Option<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl GenerationSoftware {
 | 
				
			||||||
 | 
					    pub fn new<S: Into<String>>(vendor: S, application: S, version: Option<S>) -> Self {
 | 
				
			||||||
 | 
					        GenerationSoftware {
 | 
				
			||||||
 | 
					            vendor: vendor.into(),
 | 
				
			||||||
 | 
					            application: application.into(),
 | 
				
			||||||
 | 
					            version: version.map(|s| s.into()),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for GenerationSoftware {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match self.version {
 | 
				
			||||||
 | 
					            Some(ref v) => write!(writer, "{},{},{}", self.vendor, self.application, v)?,
 | 
				
			||||||
 | 
					            None => write!(writer, "{},{}", self.vendor, self.application)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApertureFunction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum ApertureFunction {
 | 
				
			||||||
 | 
					    // Only valid for layers with file function plated or non-plated
 | 
				
			||||||
 | 
					    ViaDrill,
 | 
				
			||||||
 | 
					    BackDrill,
 | 
				
			||||||
 | 
					    ComponentDrill {
 | 
				
			||||||
 | 
					        press_fit: Option<bool>, // TODO is this bool?
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    CastellatedDrill,
 | 
				
			||||||
 | 
					    MechanicalDrill {
 | 
				
			||||||
 | 
					        function: Option<DrillFunction>,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Slot,
 | 
				
			||||||
 | 
					    CutOut,
 | 
				
			||||||
 | 
					    Cavity,
 | 
				
			||||||
 | 
					    OtherDrill(String),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Only valid for layers with file function copper
 | 
				
			||||||
 | 
					    ComponentPad {
 | 
				
			||||||
 | 
					        press_fit: Option<bool>, // TODO is this bool?
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    SmdPad(SmdPadType),
 | 
				
			||||||
 | 
					    BgaPad(SmdPadType),
 | 
				
			||||||
 | 
					    ConnectorPad,
 | 
				
			||||||
 | 
					    HeatsinkPad,
 | 
				
			||||||
 | 
					    ViaPad,
 | 
				
			||||||
 | 
					    TestPad,
 | 
				
			||||||
 | 
					    CastellatedPad,
 | 
				
			||||||
 | 
					    FiducialPad(FiducialScope),
 | 
				
			||||||
 | 
					    ThermalReliefPad,
 | 
				
			||||||
 | 
					    WasherPad,
 | 
				
			||||||
 | 
					    AntiPad,
 | 
				
			||||||
 | 
					    OtherPad(String),
 | 
				
			||||||
 | 
					    Conductor,
 | 
				
			||||||
 | 
					    NonConductor,
 | 
				
			||||||
 | 
					    CopperBalancing,
 | 
				
			||||||
 | 
					    Border,
 | 
				
			||||||
 | 
					    OtherCopper(String),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // All layers
 | 
				
			||||||
 | 
					    Profile,
 | 
				
			||||||
 | 
					    NonMaterial,
 | 
				
			||||||
 | 
					    Material,
 | 
				
			||||||
 | 
					    Other(String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DrillFunction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum DrillFunction {
 | 
				
			||||||
 | 
					    BreakOut,
 | 
				
			||||||
 | 
					    Tooling,
 | 
				
			||||||
 | 
					    Other,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SmdPadType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum SmdPadType {
 | 
				
			||||||
 | 
					    CopperDefined,
 | 
				
			||||||
 | 
					    SoldermaskDefined,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FiducialScope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum FiducialScope {
 | 
				
			||||||
 | 
					    Global,
 | 
				
			||||||
 | 
					    Local,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										106
									
								
								gerber-types-rs/src/codegen.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								gerber-types-rs/src/codegen.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					//! Generic code generation, e.g. implementations of `PartialGerberCode` for
 | 
				
			||||||
 | 
					//! bool or Vec<G: GerberCode>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::io::Write;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::errors::GerberResult;
 | 
				
			||||||
 | 
					use crate::traits::{GerberCode, PartialGerberCode};
 | 
				
			||||||
 | 
					use crate::types::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Implement `PartialGerberCode` for booleans
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for bool {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        if *self {
 | 
				
			||||||
 | 
					            write!(writer, "1")?;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            write!(writer, "0")?;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Implement `GerberCode` for Vectors of types that are `GerberCode`.
 | 
				
			||||||
 | 
					impl<W: Write, G: GerberCode<W>> GerberCode<W> for Vec<G> {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        for item in self.iter() {
 | 
				
			||||||
 | 
					            item.serialize(writer)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Implement `PartialGerberCode` for `Option<T: PartialGerberCode>`
 | 
				
			||||||
 | 
					impl<T: PartialGerberCode<W>, W: Write> PartialGerberCode<W> for Option<T> {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        if let Some(ref val) = *self {
 | 
				
			||||||
 | 
					            val.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for Command {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            Command::FunctionCode(ref code) => code.serialize(writer)?,
 | 
				
			||||||
 | 
					            Command::ExtendedCode(ref code) => code.serialize(writer)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for FunctionCode {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            FunctionCode::DCode(ref code) => code.serialize(writer)?,
 | 
				
			||||||
 | 
					            FunctionCode::GCode(ref code) => code.serialize(writer)?,
 | 
				
			||||||
 | 
					            FunctionCode::MCode(ref code) => code.serialize(writer)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for ExtendedCode {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            ExtendedCode::CoordinateFormat(ref cf) => {
 | 
				
			||||||
 | 
					                writeln!(writer, "%FSLAX{0}{1}Y{0}{1}*%", cf.integer, cf.decimal)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ExtendedCode::Unit(ref unit) => {
 | 
				
			||||||
 | 
					                write!(writer, "%MO")?;
 | 
				
			||||||
 | 
					                unit.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "*%")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ExtendedCode::ApertureDefinition(ref def) => {
 | 
				
			||||||
 | 
					                write!(writer, "%ADD")?;
 | 
				
			||||||
 | 
					                def.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "*%")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ExtendedCode::ApertureMacro(ref am) => {
 | 
				
			||||||
 | 
					                write!(writer, "%")?;
 | 
				
			||||||
 | 
					                am.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "%")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ExtendedCode::LoadPolarity(ref polarity) => {
 | 
				
			||||||
 | 
					                write!(writer, "%LP")?;
 | 
				
			||||||
 | 
					                polarity.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "*%")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ExtendedCode::StepAndRepeat(ref sar) => {
 | 
				
			||||||
 | 
					                write!(writer, "%SR")?;
 | 
				
			||||||
 | 
					                sar.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "*%")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ExtendedCode::FileAttribute(ref attr) => {
 | 
				
			||||||
 | 
					                write!(writer, "%TF.")?;
 | 
				
			||||||
 | 
					                attr.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "*%")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ExtendedCode::DeleteAttribute(ref attr) => {
 | 
				
			||||||
 | 
					                writeln!(writer, "%TD{}*%", attr)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => unimplemented!(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										479
									
								
								gerber-types-rs/src/coordinates.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								gerber-types-rs/src/coordinates.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,479 @@
 | 
				
			|||||||
 | 
					//! Types for Gerber code generation related to coordinates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::convert::{From, Into};
 | 
				
			||||||
 | 
					use std::i64;
 | 
				
			||||||
 | 
					use std::io::Write;
 | 
				
			||||||
 | 
					use std::num::FpCategory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use conv::TryFrom;
 | 
				
			||||||
 | 
					use num_rational::Ratio;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::errors::{GerberError, GerberResult};
 | 
				
			||||||
 | 
					use crate::traits::PartialGerberCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper macros
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Automatically implement `PartialGerberCode` trait for struct types
 | 
				
			||||||
 | 
					/// that are based on `x` and `y` attributes.
 | 
				
			||||||
 | 
					macro_rules! impl_xy_partial_gerbercode {
 | 
				
			||||||
 | 
					    ($class:ty, $x:expr, $y: expr) => {
 | 
				
			||||||
 | 
					        impl<W: Write> PartialGerberCode<W> for $class {
 | 
				
			||||||
 | 
					            fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					                if let Some(x) = self.x {
 | 
				
			||||||
 | 
					                    write!(writer, "{}{}", $x, x.gerber(&self.format)?)?;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if let Some(y) = self.y {
 | 
				
			||||||
 | 
					                    write!(writer, "{}{}", $y, y.gerber(&self.format)?)?;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Ok(())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The coordinate format specifies the number of integer and decimal places in
 | 
				
			||||||
 | 
					/// a coordinate number. For example, the `24` format specifies 2 integer and 4
 | 
				
			||||||
 | 
					/// decimal places. The number of decimal places must be 4, 5 or 6. The number
 | 
				
			||||||
 | 
					/// of integer places must be not more than 6. Thus the longest representable
 | 
				
			||||||
 | 
					/// coordinate number is `nnnnnn.nnnnnn`.
 | 
				
			||||||
 | 
					#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub struct CoordinateFormat {
 | 
				
			||||||
 | 
					    pub integer: u8,
 | 
				
			||||||
 | 
					    pub decimal: u8,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl CoordinateFormat {
 | 
				
			||||||
 | 
					    pub fn new(integer: u8, decimal: u8) -> Self {
 | 
				
			||||||
 | 
					        CoordinateFormat { integer, decimal }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Coordinate numbers are integers conforming to the rules set by the FS
 | 
				
			||||||
 | 
					/// command.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Coordinate numbers are integers. Explicit decimal points are not allowed.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// A coordinate number must have at least one character. Zero therefore must
 | 
				
			||||||
 | 
					/// be encoded as `0`.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// The value is stored as a 64 bit integer with 6 decimal places.
 | 
				
			||||||
 | 
					#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub struct CoordinateNumber {
 | 
				
			||||||
 | 
					    nano: i64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl CoordinateNumber {
 | 
				
			||||||
 | 
					    pub fn new(nano: i64) -> Self {
 | 
				
			||||||
 | 
					        CoordinateNumber { nano }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DECIMAL_PLACES_CHARS: u8 = 6;
 | 
				
			||||||
 | 
					const DECIMAL_PLACES_FACTOR: i64 = 1_000_000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TryFrom<f64> for CoordinateNumber {
 | 
				
			||||||
 | 
					    type Err = GerberError;
 | 
				
			||||||
 | 
					    fn try_from(val: f64) -> Result<Self, Self::Err> {
 | 
				
			||||||
 | 
					        match val.classify() {
 | 
				
			||||||
 | 
					            FpCategory::Nan => Err(GerberError::ConversionError("Value is NaN".into())),
 | 
				
			||||||
 | 
					            FpCategory::Infinite => Err(GerberError::ConversionError("Value is infinite".into())),
 | 
				
			||||||
 | 
					            FpCategory::Zero | FpCategory::Subnormal => Ok(CoordinateNumber { nano: 0 }),
 | 
				
			||||||
 | 
					            FpCategory::Normal => {
 | 
				
			||||||
 | 
					                let multiplied = val * DECIMAL_PLACES_FACTOR as f64;
 | 
				
			||||||
 | 
					                if (multiplied > i64::MAX as f64) || (multiplied < i64::MIN as f64) {
 | 
				
			||||||
 | 
					                    Err(GerberError::ConversionError(
 | 
				
			||||||
 | 
					                        "Value is out of bounds".into(),
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Ok(CoordinateNumber {
 | 
				
			||||||
 | 
					                        nano: multiplied as i64,
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Into<f64> for CoordinateNumber {
 | 
				
			||||||
 | 
					    fn into(self) -> f64 {
 | 
				
			||||||
 | 
					        (self.nano as f64) / DECIMAL_PLACES_FACTOR as f64
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					macro_rules! impl_from_integer {
 | 
				
			||||||
 | 
					    ($class:ty) => {
 | 
				
			||||||
 | 
					        impl From<$class> for CoordinateNumber {
 | 
				
			||||||
 | 
					            fn from(val: $class) -> Self {
 | 
				
			||||||
 | 
					                CoordinateNumber {
 | 
				
			||||||
 | 
					                    nano: val as i64 * DECIMAL_PLACES_FACTOR,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// These are the types we can safely multiply with DECIMAL_PLACES_FACTOR
 | 
				
			||||||
 | 
					// without the risk of an overflow.
 | 
				
			||||||
 | 
					impl_from_integer!(i8);
 | 
				
			||||||
 | 
					impl_from_integer!(i16);
 | 
				
			||||||
 | 
					impl_from_integer!(i32);
 | 
				
			||||||
 | 
					impl_from_integer!(u8);
 | 
				
			||||||
 | 
					impl_from_integer!(u16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl CoordinateNumber {
 | 
				
			||||||
 | 
					    pub fn gerber(&self, format: &CoordinateFormat) -> Result<String, GerberError> {
 | 
				
			||||||
 | 
					        if format.decimal > DECIMAL_PLACES_CHARS {
 | 
				
			||||||
 | 
					            return Err(GerberError::CoordinateFormatError(
 | 
				
			||||||
 | 
					                "Invalid precision: Too high!".into(),
 | 
				
			||||||
 | 
					            ));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if self.nano.abs() >= 10_i64.pow((format.integer + DECIMAL_PLACES_CHARS) as u32) {
 | 
				
			||||||
 | 
					            return Err(GerberError::CoordinateFormatError(
 | 
				
			||||||
 | 
					                "Number is too large for chosen format!".into(),
 | 
				
			||||||
 | 
					            ));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let divisor: i64 = 10_i64.pow((DECIMAL_PLACES_CHARS - format.decimal) as u32);
 | 
				
			||||||
 | 
					        let number: i64 = Ratio::new(self.nano, divisor).round().to_integer();
 | 
				
			||||||
 | 
					        Ok(number.to_string())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Coordinates are part of an operation.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Coordinates are modal. If an X is omitted, the X coordinate of the
 | 
				
			||||||
 | 
					/// current point is used. Similar for Y.
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub struct Coordinates {
 | 
				
			||||||
 | 
					    pub x: Option<CoordinateNumber>,
 | 
				
			||||||
 | 
					    pub y: Option<CoordinateNumber>,
 | 
				
			||||||
 | 
					    pub format: CoordinateFormat,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Coordinates {
 | 
				
			||||||
 | 
					    pub fn new<T, U>(x: T, y: U, format: CoordinateFormat) -> Self
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: Into<CoordinateNumber>,
 | 
				
			||||||
 | 
					        U: Into<CoordinateNumber>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Coordinates {
 | 
				
			||||||
 | 
					            x: Some(x.into()),
 | 
				
			||||||
 | 
					            y: Some(y.into()),
 | 
				
			||||||
 | 
					            format,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn at_x<T>(x: T, format: CoordinateFormat) -> Self
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: Into<CoordinateNumber>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Coordinates {
 | 
				
			||||||
 | 
					            x: Some(x.into()),
 | 
				
			||||||
 | 
					            y: None,
 | 
				
			||||||
 | 
					            format,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn at_y<T>(y: T, format: CoordinateFormat) -> Self
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: Into<CoordinateNumber>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Coordinates {
 | 
				
			||||||
 | 
					            x: None,
 | 
				
			||||||
 | 
					            y: Some(y.into()),
 | 
				
			||||||
 | 
					            format,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl_xy_partial_gerbercode!(Coordinates, "X", "Y");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Coordinate offsets can be used for interpolate operations in circular
 | 
				
			||||||
 | 
					/// interpolation mode.
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub struct CoordinateOffset {
 | 
				
			||||||
 | 
					    pub x: Option<CoordinateNumber>,
 | 
				
			||||||
 | 
					    pub y: Option<CoordinateNumber>,
 | 
				
			||||||
 | 
					    pub format: CoordinateFormat,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl CoordinateOffset {
 | 
				
			||||||
 | 
					    pub fn new<T, U>(x: T, y: U, format: CoordinateFormat) -> Self
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: Into<CoordinateNumber>,
 | 
				
			||||||
 | 
					        U: Into<CoordinateNumber>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        CoordinateOffset {
 | 
				
			||||||
 | 
					            x: Some(x.into()),
 | 
				
			||||||
 | 
					            y: Some(y.into()),
 | 
				
			||||||
 | 
					            format,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn at_x<T>(x: T, format: CoordinateFormat) -> Self
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: Into<CoordinateNumber>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        CoordinateOffset {
 | 
				
			||||||
 | 
					            x: Some(x.into()),
 | 
				
			||||||
 | 
					            y: None,
 | 
				
			||||||
 | 
					            format,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn at_y<T>(y: T, format: CoordinateFormat) -> Self
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: Into<CoordinateNumber>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        CoordinateOffset {
 | 
				
			||||||
 | 
					            x: None,
 | 
				
			||||||
 | 
					            y: Some(y.into()),
 | 
				
			||||||
 | 
					            format,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl_xy_partial_gerbercode!(CoordinateOffset, "I", "J");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod test {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use std::f64;
 | 
				
			||||||
 | 
					    use std::io::BufWriter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use conv::TryFrom;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use crate::traits::PartialGerberCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test integer to coordinate number conversion
 | 
				
			||||||
 | 
					    fn test_from_i8() {
 | 
				
			||||||
 | 
					        let a = CoordinateNumber { nano: 13000000 };
 | 
				
			||||||
 | 
					        let b = CoordinateNumber::from(13i8);
 | 
				
			||||||
 | 
					        assert_eq!(a, b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let c = CoordinateNumber { nano: -99000000 };
 | 
				
			||||||
 | 
					        let d = CoordinateNumber::from(-99i8);
 | 
				
			||||||
 | 
					        assert_eq!(c, d);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test integer to coordinate number conversion
 | 
				
			||||||
 | 
					    fn test_from_i32() {
 | 
				
			||||||
 | 
					        let a = CoordinateNumber { nano: 13000000 };
 | 
				
			||||||
 | 
					        let b = CoordinateNumber::from(13);
 | 
				
			||||||
 | 
					        assert_eq!(a, b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let c = CoordinateNumber { nano: -998000000 };
 | 
				
			||||||
 | 
					        let d = CoordinateNumber::from(-998);
 | 
				
			||||||
 | 
					        assert_eq!(c, d);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test float to coordinate number conversion
 | 
				
			||||||
 | 
					    fn test_try_from_f64_success() {
 | 
				
			||||||
 | 
					        let a = CoordinateNumber { nano: 1375000i64 };
 | 
				
			||||||
 | 
					        let b = CoordinateNumber::try_from(1.375f64).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(a, b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let c = CoordinateNumber {
 | 
				
			||||||
 | 
					            nano: 123456888888i64,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let d = CoordinateNumber::try_from(123456.888888f64).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(c, d);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let e = CoordinateNumber { nano: 0i64 };
 | 
				
			||||||
 | 
					        let f = CoordinateNumber::try_from(0f64).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(e, f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let g = CoordinateNumber { nano: -12345678 };
 | 
				
			||||||
 | 
					        let h = CoordinateNumber::try_from(-12.345678).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(g, h);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test failing float to coordinate number conversion
 | 
				
			||||||
 | 
					    fn test_try_from_f64_fail() {
 | 
				
			||||||
 | 
					        let cn1 = CoordinateNumber::try_from(f64::NAN);
 | 
				
			||||||
 | 
					        assert!(cn1.is_err());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cn2 = CoordinateNumber::try_from(f64::INFINITY);
 | 
				
			||||||
 | 
					        assert!(cn2.is_err());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cn3 = CoordinateNumber::try_from(f64::MAX - 1.0);
 | 
				
			||||||
 | 
					        assert!(cn3.is_err());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cn4 = CoordinateNumber::try_from(f64::MIN + 1.0);
 | 
				
			||||||
 | 
					        assert!(cn4.is_err());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test coordinate number to float conversion
 | 
				
			||||||
 | 
					    fn test_into_f64() {
 | 
				
			||||||
 | 
					        let a: f64 = CoordinateNumber { nano: 1375000i64 }.into();
 | 
				
			||||||
 | 
					        let b = 1.375f64;
 | 
				
			||||||
 | 
					        assert_eq!(a, b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let c: f64 = CoordinateNumber {
 | 
				
			||||||
 | 
					            nano: 123456888888i64,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .into();
 | 
				
			||||||
 | 
					        let d = 123456.888888f64;
 | 
				
			||||||
 | 
					        assert_eq!(c, d);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let e: f64 = CoordinateNumber { nano: 0i64 }.into();
 | 
				
			||||||
 | 
					        let f = 0f64;
 | 
				
			||||||
 | 
					        assert_eq!(e, f);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test the coordinate number constructor creates correct
 | 
				
			||||||
 | 
					    /// coordinate numbers.
 | 
				
			||||||
 | 
					    fn test_coordinate_number_new() {
 | 
				
			||||||
 | 
					        let nano = 5;
 | 
				
			||||||
 | 
					        let cn1 = CoordinateNumber::new(nano);
 | 
				
			||||||
 | 
					        assert_eq!(cn1.nano, nano);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test coordinate number to string conversion when it's 0
 | 
				
			||||||
 | 
					    fn test_formatted_zero() {
 | 
				
			||||||
 | 
					        let cf1 = CoordinateFormat::new(6, 6);
 | 
				
			||||||
 | 
					        let cf2 = CoordinateFormat::new(2, 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let a = CoordinateNumber { nano: 0 }.gerber(&cf1).unwrap();
 | 
				
			||||||
 | 
					        let b = CoordinateNumber { nano: 0 }.gerber(&cf2).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(a, "0".to_string());
 | 
				
			||||||
 | 
					        assert_eq!(b, "0".to_string());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test coordinate number to string conversion when the decimal part is 0
 | 
				
			||||||
 | 
					    fn test_formatted_decimal_zero() {
 | 
				
			||||||
 | 
					        let cf1 = CoordinateFormat::new(6, 6);
 | 
				
			||||||
 | 
					        let cf2 = CoordinateFormat::new(2, 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let a = CoordinateNumber { nano: 10000000 }.gerber(&cf1).unwrap();
 | 
				
			||||||
 | 
					        let b = CoordinateNumber { nano: 20000000 }.gerber(&cf2).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(a, "10000000".to_string());
 | 
				
			||||||
 | 
					        assert_eq!(b, "200000".to_string());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test coordinate number to string conversion
 | 
				
			||||||
 | 
					    fn test_formatted_65() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(6, 5);
 | 
				
			||||||
 | 
					        let d = CoordinateNumber { nano: 123456789012 }.gerber(&cf).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(d, "12345678901".to_string());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test coordinate number to string conversion
 | 
				
			||||||
 | 
					    fn test_formatted_54() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(5, 4);
 | 
				
			||||||
 | 
					        let d = CoordinateNumber { nano: 12345678901 }.gerber(&cf).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(d, "123456789".to_string());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test coordinate number to string conversion failure
 | 
				
			||||||
 | 
					    fn test_formatted_number_too_large() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(4, 5);
 | 
				
			||||||
 | 
					        let d = CoordinateNumber { nano: 12345000000 }.gerber(&cf);
 | 
				
			||||||
 | 
					        assert!(d.is_err());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test coordinate number to string conversion failure
 | 
				
			||||||
 | 
					    fn test_formatted_negative_number_too_large() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(4, 5);
 | 
				
			||||||
 | 
					        let d = CoordinateNumber { nano: -12345000000 }.gerber(&cf);
 | 
				
			||||||
 | 
					        assert!(d.is_err());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test coordinate number to string conversion (rounding of decimal part)
 | 
				
			||||||
 | 
					    fn test_formatted_44_rounding() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(4, 4);
 | 
				
			||||||
 | 
					        let d = CoordinateNumber { nano: 1234432199 }.gerber(&cf).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(d, "12344322".to_string());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    /// Test negative coordinate number to string conversion
 | 
				
			||||||
 | 
					    fn test_formatted_negative_rounding() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(6, 4);
 | 
				
			||||||
 | 
					        let d = CoordinateNumber {
 | 
				
			||||||
 | 
					            nano: -123456789099,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .gerber(&cf)
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(d, "-1234567891".to_string());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_coordinates_into() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(2, 4);
 | 
				
			||||||
 | 
					        let c1 = Coordinates::new(CoordinateNumber::from(1), CoordinateNumber::from(2), cf);
 | 
				
			||||||
 | 
					        let c2 = Coordinates::new(1, 2, cf);
 | 
				
			||||||
 | 
					        assert_eq!(c1, c2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_coordinates_into_mixed() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(2, 4);
 | 
				
			||||||
 | 
					        let c1 = Coordinates::new(CoordinateNumber::from(1), 2, cf);
 | 
				
			||||||
 | 
					        let c2 = Coordinates::new(1, 2, cf);
 | 
				
			||||||
 | 
					        assert_eq!(c1, c2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_coordinates() {
 | 
				
			||||||
 | 
					        macro_rules! assert_coords {
 | 
				
			||||||
 | 
					            ($coords:expr, $result:expr) => {{
 | 
				
			||||||
 | 
					                assert_partial_code!($coords, $result);
 | 
				
			||||||
 | 
					            }};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let cf44 = CoordinateFormat::new(4, 4);
 | 
				
			||||||
 | 
					        let cf46 = CoordinateFormat::new(4, 6);
 | 
				
			||||||
 | 
					        assert_coords!(Coordinates::new(10, 20, cf44), "X100000Y200000");
 | 
				
			||||||
 | 
					        assert_coords!(
 | 
				
			||||||
 | 
					            Coordinates {
 | 
				
			||||||
 | 
					                x: None,
 | 
				
			||||||
 | 
					                y: None,
 | 
				
			||||||
 | 
					                format: cf44
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        ); // TODO should we catch this?
 | 
				
			||||||
 | 
					        assert_coords!(Coordinates::at_x(10, cf44), "X100000");
 | 
				
			||||||
 | 
					        assert_coords!(Coordinates::at_y(20, cf46), "Y20000000");
 | 
				
			||||||
 | 
					        assert_coords!(Coordinates::new(0, -400, cf44), "X0Y-4000000");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_offset() {
 | 
				
			||||||
 | 
					        macro_rules! assert_coords {
 | 
				
			||||||
 | 
					            ($coords:expr, $result:expr) => {{
 | 
				
			||||||
 | 
					                assert_partial_code!($coords, $result);
 | 
				
			||||||
 | 
					            }};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let cf44 = CoordinateFormat::new(4, 4);
 | 
				
			||||||
 | 
					        let cf55 = CoordinateFormat::new(5, 5);
 | 
				
			||||||
 | 
					        let cf66 = CoordinateFormat::new(6, 6);
 | 
				
			||||||
 | 
					        assert_coords!(CoordinateOffset::new(10, 20, cf44), "I100000J200000");
 | 
				
			||||||
 | 
					        assert_coords!(
 | 
				
			||||||
 | 
					            CoordinateOffset {
 | 
				
			||||||
 | 
					                x: None,
 | 
				
			||||||
 | 
					                y: None,
 | 
				
			||||||
 | 
					                format: cf44
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        ); // TODO should we catch this?
 | 
				
			||||||
 | 
					        assert_coords!(CoordinateOffset::at_x(10, cf66), "I10000000");
 | 
				
			||||||
 | 
					        assert_coords!(CoordinateOffset::at_y(20, cf55), "J2000000");
 | 
				
			||||||
 | 
					        assert_coords!(CoordinateOffset::new(0, -400, cf44), "I0J-4000000");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										39
									
								
								gerber-types-rs/src/errors.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								gerber-types-rs/src/errors.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					//! Error types used in the gerber-types library.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::io::Error as IoError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Error, Debug)]
 | 
				
			||||||
 | 
					pub enum GerberError {
 | 
				
			||||||
 | 
					    #[error("Conversion between two types failed: {0}")]
 | 
				
			||||||
 | 
					    ConversionError(String),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Bad coordinate format: {0}")]
 | 
				
			||||||
 | 
					    CoordinateFormatError(String),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("A value is out of range: {0}")]
 | 
				
			||||||
 | 
					    RangeError(String),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("Required data is missing: {0}")]
 | 
				
			||||||
 | 
					    MissingDataError(String),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("I/O error during code generation")]
 | 
				
			||||||
 | 
					    IoError(#[from] IoError),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type GerberResult<T> = Result<T, GerberError>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_error_msg() {
 | 
				
			||||||
 | 
					        let err = GerberError::CoordinateFormatError("Something went wrong".into());
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            err.to_string(),
 | 
				
			||||||
 | 
					            "Bad coordinate format: Something went wrong"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										315
									
								
								gerber-types-rs/src/extended_codes.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								gerber-types-rs/src/extended_codes.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,315 @@
 | 
				
			|||||||
 | 
					//! Extended code types.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::io::Write;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::errors::GerberResult;
 | 
				
			||||||
 | 
					use crate::traits::PartialGerberCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Unit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum Unit {
 | 
				
			||||||
 | 
					    Inches,
 | 
				
			||||||
 | 
					    Millimeters,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Unit {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            Unit::Millimeters => write!(writer, "MM")?,
 | 
				
			||||||
 | 
					            Unit::Inches => write!(writer, "IN")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApertureDefinition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub struct ApertureDefinition {
 | 
				
			||||||
 | 
					    pub code: i32,
 | 
				
			||||||
 | 
					    pub aperture: Aperture,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ApertureDefinition {
 | 
				
			||||||
 | 
					    pub fn new(code: i32, aperture: Aperture) -> Self {
 | 
				
			||||||
 | 
					        ApertureDefinition { code, aperture }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for ApertureDefinition {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        write!(writer, "{}", self.code)?;
 | 
				
			||||||
 | 
					        self.aperture.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Aperture
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub enum Aperture {
 | 
				
			||||||
 | 
					    Circle(Circle),
 | 
				
			||||||
 | 
					    Rectangle(Rectangular),
 | 
				
			||||||
 | 
					    Obround(Rectangular),
 | 
				
			||||||
 | 
					    Polygon(Polygon),
 | 
				
			||||||
 | 
					    Other(String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Aperture {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            Aperture::Circle(ref circle) => {
 | 
				
			||||||
 | 
					                write!(writer, "C,")?;
 | 
				
			||||||
 | 
					                circle.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Aperture::Rectangle(ref rectangular) => {
 | 
				
			||||||
 | 
					                write!(writer, "R,")?;
 | 
				
			||||||
 | 
					                rectangular.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Aperture::Obround(ref rectangular) => {
 | 
				
			||||||
 | 
					                write!(writer, "O,")?;
 | 
				
			||||||
 | 
					                rectangular.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Aperture::Polygon(ref polygon) => {
 | 
				
			||||||
 | 
					                write!(writer, "P,")?;
 | 
				
			||||||
 | 
					                polygon.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Aperture::Other(ref string) => write!(writer, "{}", string)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Circle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub struct Circle {
 | 
				
			||||||
 | 
					    pub diameter: f64,
 | 
				
			||||||
 | 
					    pub hole_diameter: Option<f64>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Circle {
 | 
				
			||||||
 | 
					    pub fn new(diameter: f64) -> Self {
 | 
				
			||||||
 | 
					        Circle {
 | 
				
			||||||
 | 
					            diameter,
 | 
				
			||||||
 | 
					            hole_diameter: None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn with_hole(diameter: f64, hole_diameter: f64) -> Self {
 | 
				
			||||||
 | 
					        Circle {
 | 
				
			||||||
 | 
					            diameter,
 | 
				
			||||||
 | 
					            hole_diameter: Some(hole_diameter),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Circle {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match self.hole_diameter {
 | 
				
			||||||
 | 
					            Some(hole_diameter) => {
 | 
				
			||||||
 | 
					                write!(writer, "{}X{}", self.diameter, hole_diameter)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            None => write!(writer, "{}", self.diameter)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Rectangular
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub struct Rectangular {
 | 
				
			||||||
 | 
					    pub x: f64,
 | 
				
			||||||
 | 
					    pub y: f64,
 | 
				
			||||||
 | 
					    pub hole_diameter: Option<f64>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Rectangular {
 | 
				
			||||||
 | 
					    pub fn new(x: f64, y: f64) -> Self {
 | 
				
			||||||
 | 
					        Rectangular {
 | 
				
			||||||
 | 
					            x,
 | 
				
			||||||
 | 
					            y,
 | 
				
			||||||
 | 
					            hole_diameter: None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn with_hole(x: f64, y: f64, hole_diameter: f64) -> Self {
 | 
				
			||||||
 | 
					        Rectangular {
 | 
				
			||||||
 | 
					            x,
 | 
				
			||||||
 | 
					            y,
 | 
				
			||||||
 | 
					            hole_diameter: Some(hole_diameter),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Rectangular {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match self.hole_diameter {
 | 
				
			||||||
 | 
					            Some(hole_diameter) => write!(writer, "{}X{}X{}", self.x, self.y, hole_diameter)?,
 | 
				
			||||||
 | 
					            None => write!(writer, "{}X{}", self.x, self.y)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Polygon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub struct Polygon {
 | 
				
			||||||
 | 
					    pub diameter: f64,
 | 
				
			||||||
 | 
					    pub vertices: u8, // 3--12
 | 
				
			||||||
 | 
					    pub rotation: Option<f64>,
 | 
				
			||||||
 | 
					    pub hole_diameter: Option<f64>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Polygon {
 | 
				
			||||||
 | 
					    pub fn new(diameter: f64, vertices: u8) -> Self {
 | 
				
			||||||
 | 
					        Polygon {
 | 
				
			||||||
 | 
					            diameter,
 | 
				
			||||||
 | 
					            vertices,
 | 
				
			||||||
 | 
					            rotation: None,
 | 
				
			||||||
 | 
					            hole_diameter: None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn with_rotation(mut self, angle: f64) -> Self {
 | 
				
			||||||
 | 
					        self.rotation = Some(angle);
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn with_diameter(mut self, diameter: f64) -> Self {
 | 
				
			||||||
 | 
					        self.diameter = diameter;
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Polygon {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match (self.rotation, self.hole_diameter) {
 | 
				
			||||||
 | 
					            (Some(rot), Some(hd)) => {
 | 
				
			||||||
 | 
					                write!(writer, "{}X{}X{}X{}", self.diameter, self.vertices, rot, hd)?
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            (Some(rot), None) => write!(writer, "{}X{}X{}", self.diameter, self.vertices, rot)?,
 | 
				
			||||||
 | 
					            (None, Some(hd)) => write!(writer, "{}X{}X0X{}", self.diameter, self.vertices, hd)?,
 | 
				
			||||||
 | 
					            (None, None) => write!(writer, "{}X{}", self.diameter, self.vertices)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Polarity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum Polarity {
 | 
				
			||||||
 | 
					    Clear,
 | 
				
			||||||
 | 
					    Dark,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for Polarity {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            Polarity::Clear => write!(writer, "C")?,
 | 
				
			||||||
 | 
					            Polarity::Dark => write!(writer, "D")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StepAndRepeat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub enum StepAndRepeat {
 | 
				
			||||||
 | 
					    Open {
 | 
				
			||||||
 | 
					        repeat_x: u32,
 | 
				
			||||||
 | 
					        repeat_y: u32,
 | 
				
			||||||
 | 
					        distance_x: f64,
 | 
				
			||||||
 | 
					        distance_y: f64,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Close,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> PartialGerberCode<W> for StepAndRepeat {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            StepAndRepeat::Open {
 | 
				
			||||||
 | 
					                repeat_x: rx,
 | 
				
			||||||
 | 
					                repeat_y: ry,
 | 
				
			||||||
 | 
					                distance_x: dx,
 | 
				
			||||||
 | 
					                distance_y: dy,
 | 
				
			||||||
 | 
					            } => write!(writer, "X{}Y{}I{}J{}", rx, ry, dx, dy)?,
 | 
				
			||||||
 | 
					            StepAndRepeat::Close => {}
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod test {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_aperture_definition_new() {
 | 
				
			||||||
 | 
					        let ad1 = ApertureDefinition::new(10, Aperture::Circle(Circle::new(3.0)));
 | 
				
			||||||
 | 
					        let ad2 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 10,
 | 
				
			||||||
 | 
					            aperture: Aperture::Circle(Circle::new(3.0)),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(ad1, ad2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_rectangular_new() {
 | 
				
			||||||
 | 
					        let r1 = Rectangular::new(2.0, 3.0);
 | 
				
			||||||
 | 
					        let r2 = Rectangular {
 | 
				
			||||||
 | 
					            x: 2.0,
 | 
				
			||||||
 | 
					            y: 3.0,
 | 
				
			||||||
 | 
					            hole_diameter: None,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(r1, r2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_rectangular_with_hole() {
 | 
				
			||||||
 | 
					        let r1 = Rectangular::with_hole(3.0, 2.0, 1.0);
 | 
				
			||||||
 | 
					        let r2 = Rectangular {
 | 
				
			||||||
 | 
					            x: 3.0,
 | 
				
			||||||
 | 
					            y: 2.0,
 | 
				
			||||||
 | 
					            hole_diameter: Some(1.0),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(r1, r2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_circle_new() {
 | 
				
			||||||
 | 
					        let c1 = Circle::new(3.0);
 | 
				
			||||||
 | 
					        let c2 = Circle {
 | 
				
			||||||
 | 
					            diameter: 3.0,
 | 
				
			||||||
 | 
					            hole_diameter: None,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(c1, c2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_circle_with_hole() {
 | 
				
			||||||
 | 
					        let c1 = Circle::with_hole(3.0, 1.0);
 | 
				
			||||||
 | 
					        let c2 = Circle {
 | 
				
			||||||
 | 
					            diameter: 3.0,
 | 
				
			||||||
 | 
					            hole_diameter: Some(1.0),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(c1, c2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_polygon_new() {
 | 
				
			||||||
 | 
					        let p1 = Polygon::new(3.0, 4).with_rotation(45.0);
 | 
				
			||||||
 | 
					        let p2 = Polygon {
 | 
				
			||||||
 | 
					            diameter: 3.0,
 | 
				
			||||||
 | 
					            vertices: 4,
 | 
				
			||||||
 | 
					            rotation: Some(45.0),
 | 
				
			||||||
 | 
					            hole_diameter: None,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_eq!(p1, p2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										143
									
								
								gerber-types-rs/src/function_codes.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								gerber-types-rs/src/function_codes.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					//! Function code types.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::io::Write;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::coordinates::{CoordinateOffset, Coordinates};
 | 
				
			||||||
 | 
					use crate::errors::GerberResult;
 | 
				
			||||||
 | 
					use crate::traits::{GerberCode, PartialGerberCode};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DCode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum DCode {
 | 
				
			||||||
 | 
					    Operation(Operation),
 | 
				
			||||||
 | 
					    SelectAperture(i32),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for DCode {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            DCode::Operation(ref operation) => operation.serialize(writer)?,
 | 
				
			||||||
 | 
					            DCode::SelectAperture(code) => writeln!(writer, "D{}*", code)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GCode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum GCode {
 | 
				
			||||||
 | 
					    InterpolationMode(InterpolationMode),
 | 
				
			||||||
 | 
					    RegionMode(bool),
 | 
				
			||||||
 | 
					    QuadrantMode(QuadrantMode),
 | 
				
			||||||
 | 
					    Comment(String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for GCode {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            GCode::InterpolationMode(ref mode) => mode.serialize(writer)?,
 | 
				
			||||||
 | 
					            GCode::RegionMode(enabled) => {
 | 
				
			||||||
 | 
					                if enabled {
 | 
				
			||||||
 | 
					                    writeln!(writer, "G36*")?;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    writeln!(writer, "G37*")?;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            GCode::QuadrantMode(ref mode) => mode.serialize(writer)?,
 | 
				
			||||||
 | 
					            GCode::Comment(ref comment) => writeln!(writer, "G04 {}*", comment)?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MCode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum MCode {
 | 
				
			||||||
 | 
					    EndOfFile,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for MCode {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            MCode::EndOfFile => writeln!(writer, "M02*")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Operation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum Operation {
 | 
				
			||||||
 | 
					    /// D01 Command
 | 
				
			||||||
 | 
					    Interpolate(Coordinates, Option<CoordinateOffset>),
 | 
				
			||||||
 | 
					    /// D02 Command
 | 
				
			||||||
 | 
					    Move(Coordinates),
 | 
				
			||||||
 | 
					    /// D03 Command
 | 
				
			||||||
 | 
					    Flash(Coordinates),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for Operation {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            Operation::Interpolate(ref coords, ref offset) => {
 | 
				
			||||||
 | 
					                coords.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                offset.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "D01*")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Operation::Move(ref coords) => {
 | 
				
			||||||
 | 
					                coords.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "D02*")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Operation::Flash(ref coords) => {
 | 
				
			||||||
 | 
					                coords.serialize_partial(writer)?;
 | 
				
			||||||
 | 
					                writeln!(writer, "D03*")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InterpolationMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum InterpolationMode {
 | 
				
			||||||
 | 
					    Linear,
 | 
				
			||||||
 | 
					    ClockwiseCircular,
 | 
				
			||||||
 | 
					    CounterclockwiseCircular,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for InterpolationMode {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            InterpolationMode::Linear => writeln!(writer, "G01*")?,
 | 
				
			||||||
 | 
					            InterpolationMode::ClockwiseCircular => writeln!(writer, "G02*")?,
 | 
				
			||||||
 | 
					            InterpolationMode::CounterclockwiseCircular => writeln!(writer, "G03*")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// QuadrantMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum QuadrantMode {
 | 
				
			||||||
 | 
					    Single,
 | 
				
			||||||
 | 
					    Multi,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<W: Write> GerberCode<W> for QuadrantMode {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()> {
 | 
				
			||||||
 | 
					        match *self {
 | 
				
			||||||
 | 
					            QuadrantMode::Single => writeln!(writer, "G74*")?,
 | 
				
			||||||
 | 
					            QuadrantMode::Multi => writeln!(writer, "G75*")?,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod test {}
 | 
				
			||||||
							
								
								
									
										289
									
								
								gerber-types-rs/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								gerber-types-rs/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,289 @@
 | 
				
			|||||||
 | 
					//! # Gerber commands
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! This crate implements the basic building blocks of Gerber (RS-274X, aka
 | 
				
			||||||
 | 
					//! Extended Gerber version 2) code. It focusses on the low level types and does
 | 
				
			||||||
 | 
					//! not do any semantic checking.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! For example, you can use an aperture without defining it. This will
 | 
				
			||||||
 | 
					//! generate syntactically valid but semantially invalid Gerber code, but this
 | 
				
			||||||
 | 
					//! module won't complain.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! ## Traits: GerberCode and PartialGerberCode
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! There are two main traits that are used for code generation:
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! - [`GerberCode`](trait.GerberCode.html) generates a full Gerber code line,
 | 
				
			||||||
 | 
					//!   terminated with a newline character.
 | 
				
			||||||
 | 
					//! - `PartialGerberCode` (internal only) generates Gerber representation of a
 | 
				
			||||||
 | 
					//!   value, but does not represent a full line of code.
 | 
				
			||||||
 | 
					#![allow(clippy::new_without_default)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					#[macro_use]
 | 
				
			||||||
 | 
					mod test_macros;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod attributes;
 | 
				
			||||||
 | 
					mod codegen;
 | 
				
			||||||
 | 
					mod coordinates;
 | 
				
			||||||
 | 
					mod errors;
 | 
				
			||||||
 | 
					mod extended_codes;
 | 
				
			||||||
 | 
					mod function_codes;
 | 
				
			||||||
 | 
					mod macros;
 | 
				
			||||||
 | 
					mod traits;
 | 
				
			||||||
 | 
					mod types;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use crate::attributes::*;
 | 
				
			||||||
 | 
					#[allow(unused)]
 | 
				
			||||||
 | 
					pub use crate::codegen::*;
 | 
				
			||||||
 | 
					pub use crate::coordinates::*;
 | 
				
			||||||
 | 
					pub use crate::errors::*;
 | 
				
			||||||
 | 
					pub use crate::extended_codes::*;
 | 
				
			||||||
 | 
					pub use crate::function_codes::*;
 | 
				
			||||||
 | 
					pub use crate::macros::*;
 | 
				
			||||||
 | 
					pub use crate::traits::GerberCode;
 | 
				
			||||||
 | 
					pub use crate::types::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod test {
 | 
				
			||||||
 | 
					    use std::io::BufWriter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use super::traits::PartialGerberCode;
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_serialize() {
 | 
				
			||||||
 | 
					        //! The serialize method of the GerberCode trait should generate strings.
 | 
				
			||||||
 | 
					        let comment = GCode::Comment("testcomment".to_string());
 | 
				
			||||||
 | 
					        assert_code!(comment, "G04 testcomment*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_vec_serialize() {
 | 
				
			||||||
 | 
					        //! A `Vec<T: GerberCode>` should also implement `GerberCode`.
 | 
				
			||||||
 | 
					        let mut v = Vec::new();
 | 
				
			||||||
 | 
					        v.push(GCode::Comment("comment 1".to_string()));
 | 
				
			||||||
 | 
					        v.push(GCode::Comment("another one".to_string()));
 | 
				
			||||||
 | 
					        assert_code!(v, "G04 comment 1*\nG04 another one*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_command_serialize() {
 | 
				
			||||||
 | 
					        //! A `Command` should implement `GerberCode`
 | 
				
			||||||
 | 
					        let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment("comment".to_string())));
 | 
				
			||||||
 | 
					        assert_code!(c, "G04 comment*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_interpolation_mode() {
 | 
				
			||||||
 | 
					        let mut commands = Vec::new();
 | 
				
			||||||
 | 
					        let c1 = GCode::InterpolationMode(InterpolationMode::Linear);
 | 
				
			||||||
 | 
					        let c2 = GCode::InterpolationMode(InterpolationMode::ClockwiseCircular);
 | 
				
			||||||
 | 
					        let c3 = GCode::InterpolationMode(InterpolationMode::CounterclockwiseCircular);
 | 
				
			||||||
 | 
					        commands.push(c1);
 | 
				
			||||||
 | 
					        commands.push(c2);
 | 
				
			||||||
 | 
					        commands.push(c3);
 | 
				
			||||||
 | 
					        assert_code!(commands, "G01*\nG02*\nG03*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_region_mode() {
 | 
				
			||||||
 | 
					        let mut commands = Vec::new();
 | 
				
			||||||
 | 
					        commands.push(GCode::RegionMode(true));
 | 
				
			||||||
 | 
					        commands.push(GCode::RegionMode(false));
 | 
				
			||||||
 | 
					        assert_code!(commands, "G36*\nG37*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_quadrant_mode() {
 | 
				
			||||||
 | 
					        let mut commands = Vec::new();
 | 
				
			||||||
 | 
					        commands.push(GCode::QuadrantMode(QuadrantMode::Single));
 | 
				
			||||||
 | 
					        commands.push(GCode::QuadrantMode(QuadrantMode::Multi));
 | 
				
			||||||
 | 
					        assert_code!(commands, "G74*\nG75*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_end_of_file() {
 | 
				
			||||||
 | 
					        let c = MCode::EndOfFile;
 | 
				
			||||||
 | 
					        assert_code!(c, "M02*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_operation_interpolate() {
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(2, 5);
 | 
				
			||||||
 | 
					        let c1 = Operation::Interpolate(
 | 
				
			||||||
 | 
					            Coordinates::new(1, 2, cf),
 | 
				
			||||||
 | 
					            Some(CoordinateOffset::new(5, 10, cf)),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        assert_code!(c1, "X100000Y200000I500000J1000000D01*\n");
 | 
				
			||||||
 | 
					        let c2 = Operation::Interpolate(Coordinates::at_y(-2, CoordinateFormat::new(4, 4)), None);
 | 
				
			||||||
 | 
					        assert_code!(c2, "Y-20000D01*\n");
 | 
				
			||||||
 | 
					        let cf = CoordinateFormat::new(4, 4);
 | 
				
			||||||
 | 
					        let c3 = Operation::Interpolate(
 | 
				
			||||||
 | 
					            Coordinates::at_x(1, cf),
 | 
				
			||||||
 | 
					            Some(CoordinateOffset::at_y(2, cf)),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        assert_code!(c3, "X10000J20000D01*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_operation_move() {
 | 
				
			||||||
 | 
					        let c = Operation::Move(Coordinates::new(23, 42, CoordinateFormat::new(6, 4)));
 | 
				
			||||||
 | 
					        assert_code!(c, "X230000Y420000D02*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_operation_flash() {
 | 
				
			||||||
 | 
					        let c = Operation::Flash(Coordinates::new(23, 42, CoordinateFormat::new(4, 4)));
 | 
				
			||||||
 | 
					        assert_code!(c, "X230000Y420000D03*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_select_aperture() {
 | 
				
			||||||
 | 
					        let c1 = DCode::SelectAperture(10);
 | 
				
			||||||
 | 
					        assert_code!(c1, "D10*\n");
 | 
				
			||||||
 | 
					        let c2 = DCode::SelectAperture(2147483647);
 | 
				
			||||||
 | 
					        assert_code!(c2, "D2147483647*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_coordinate_format() {
 | 
				
			||||||
 | 
					        let c = ExtendedCode::CoordinateFormat(CoordinateFormat::new(2, 5));
 | 
				
			||||||
 | 
					        assert_code!(c, "%FSLAX25Y25*%\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_unit() {
 | 
				
			||||||
 | 
					        let c1 = ExtendedCode::Unit(Unit::Millimeters);
 | 
				
			||||||
 | 
					        let c2 = ExtendedCode::Unit(Unit::Inches);
 | 
				
			||||||
 | 
					        assert_code!(c1, "%MOMM*%\n");
 | 
				
			||||||
 | 
					        assert_code!(c2, "%MOIN*%\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_aperture_circle_definition() {
 | 
				
			||||||
 | 
					        let ad1 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 10,
 | 
				
			||||||
 | 
					            aperture: Aperture::Circle(Circle {
 | 
				
			||||||
 | 
					                diameter: 4.0,
 | 
				
			||||||
 | 
					                hole_diameter: Some(2.0),
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let ad2 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 11,
 | 
				
			||||||
 | 
					            aperture: Aperture::Circle(Circle {
 | 
				
			||||||
 | 
					                diameter: 4.5,
 | 
				
			||||||
 | 
					                hole_diameter: None,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_partial_code!(ad1, "10C,4X2");
 | 
				
			||||||
 | 
					        assert_partial_code!(ad2, "11C,4.5");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_aperture_rectangular_definition() {
 | 
				
			||||||
 | 
					        let ad1 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 12,
 | 
				
			||||||
 | 
					            aperture: Aperture::Rectangle(Rectangular {
 | 
				
			||||||
 | 
					                x: 1.5,
 | 
				
			||||||
 | 
					                y: 2.25,
 | 
				
			||||||
 | 
					                hole_diameter: Some(3.8),
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let ad2 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 13,
 | 
				
			||||||
 | 
					            aperture: Aperture::Rectangle(Rectangular {
 | 
				
			||||||
 | 
					                x: 1.0,
 | 
				
			||||||
 | 
					                y: 1.0,
 | 
				
			||||||
 | 
					                hole_diameter: None,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let ad3 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 14,
 | 
				
			||||||
 | 
					            aperture: Aperture::Obround(Rectangular {
 | 
				
			||||||
 | 
					                x: 2.0,
 | 
				
			||||||
 | 
					                y: 4.5,
 | 
				
			||||||
 | 
					                hole_diameter: None,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_partial_code!(ad1, "12R,1.5X2.25X3.8");
 | 
				
			||||||
 | 
					        assert_partial_code!(ad2, "13R,1X1");
 | 
				
			||||||
 | 
					        assert_partial_code!(ad3, "14O,2X4.5");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_aperture_polygon_definition() {
 | 
				
			||||||
 | 
					        let ad1 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 15,
 | 
				
			||||||
 | 
					            aperture: Aperture::Polygon(Polygon {
 | 
				
			||||||
 | 
					                diameter: 4.5,
 | 
				
			||||||
 | 
					                vertices: 3,
 | 
				
			||||||
 | 
					                rotation: None,
 | 
				
			||||||
 | 
					                hole_diameter: None,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let ad2 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 16,
 | 
				
			||||||
 | 
					            aperture: Aperture::Polygon(Polygon {
 | 
				
			||||||
 | 
					                diameter: 5.0,
 | 
				
			||||||
 | 
					                vertices: 4,
 | 
				
			||||||
 | 
					                rotation: Some(30.6),
 | 
				
			||||||
 | 
					                hole_diameter: None,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let ad3 = ApertureDefinition {
 | 
				
			||||||
 | 
					            code: 17,
 | 
				
			||||||
 | 
					            aperture: Aperture::Polygon(Polygon {
 | 
				
			||||||
 | 
					                diameter: 5.5,
 | 
				
			||||||
 | 
					                vertices: 5,
 | 
				
			||||||
 | 
					                rotation: None,
 | 
				
			||||||
 | 
					                hole_diameter: Some(1.8),
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        assert_partial_code!(ad1, "15P,4.5X3");
 | 
				
			||||||
 | 
					        assert_partial_code!(ad2, "16P,5X4X30.6");
 | 
				
			||||||
 | 
					        assert_partial_code!(ad3, "17P,5.5X5X0X1.8");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_polarity_serialize() {
 | 
				
			||||||
 | 
					        let d = ExtendedCode::LoadPolarity(Polarity::Dark);
 | 
				
			||||||
 | 
					        let c = ExtendedCode::LoadPolarity(Polarity::Clear);
 | 
				
			||||||
 | 
					        assert_code!(d, "%LPD*%\n");
 | 
				
			||||||
 | 
					        assert_code!(c, "%LPC*%\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_step_and_repeat_serialize() {
 | 
				
			||||||
 | 
					        let o = ExtendedCode::StepAndRepeat(StepAndRepeat::Open {
 | 
				
			||||||
 | 
					            repeat_x: 2,
 | 
				
			||||||
 | 
					            repeat_y: 3,
 | 
				
			||||||
 | 
					            distance_x: 2.0,
 | 
				
			||||||
 | 
					            distance_y: 3.0,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        let c = ExtendedCode::StepAndRepeat(StepAndRepeat::Close);
 | 
				
			||||||
 | 
					        assert_code!(o, "%SRX2Y3I2J3*%\n");
 | 
				
			||||||
 | 
					        assert_code!(c, "%SR*%\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_delete_attribute_serialize() {
 | 
				
			||||||
 | 
					        let d = ExtendedCode::DeleteAttribute("foo".into());
 | 
				
			||||||
 | 
					        assert_code!(d, "%TDfoo*%\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_file_attribute_serialize() {
 | 
				
			||||||
 | 
					        let part = ExtendedCode::FileAttribute(FileAttribute::Part(Part::Other("foo".into())));
 | 
				
			||||||
 | 
					        assert_code!(part, "%TF.Part,Other,foo*%\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let gensw1 = ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(
 | 
				
			||||||
 | 
					            GenerationSoftware::new("Vend0r", "superpcb", None),
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert_code!(gensw1, "%TF.GenerationSoftware,Vend0r,superpcb*%\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let gensw2 = ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(
 | 
				
			||||||
 | 
					            GenerationSoftware::new("Vend0r", "superpcb", Some("1.2.3")),
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					        assert_code!(gensw2, "%TF.GenerationSoftware,Vend0r,superpcb,1.2.3*%\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1089
									
								
								gerber-types-rs/src/macros.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1089
									
								
								gerber-types-rs/src/macros.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										25
									
								
								gerber-types-rs/src/test_macros.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								gerber-types-rs/src/test_macros.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					/// Assert that serializing the object generates
 | 
				
			||||||
 | 
					/// the specified gerber code.
 | 
				
			||||||
 | 
					macro_rules! assert_code {
 | 
				
			||||||
 | 
					    ($obj:expr, $expected:expr) => {
 | 
				
			||||||
 | 
					        let mut buf = BufWriter::new(Vec::new());
 | 
				
			||||||
 | 
					        $obj.serialize(&mut buf)
 | 
				
			||||||
 | 
					            .expect("Could not generate Gerber code");
 | 
				
			||||||
 | 
					        let bytes = buf.into_inner().unwrap();
 | 
				
			||||||
 | 
					        let code = String::from_utf8(bytes).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(&code, $expected);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Assert that partially serializing the object generates
 | 
				
			||||||
 | 
					/// the specified gerber code.
 | 
				
			||||||
 | 
					macro_rules! assert_partial_code {
 | 
				
			||||||
 | 
					    ($obj:expr, $expected:expr) => {
 | 
				
			||||||
 | 
					        let mut buf = BufWriter::new(Vec::new());
 | 
				
			||||||
 | 
					        $obj.serialize_partial(&mut buf)
 | 
				
			||||||
 | 
					            .expect("Could not generate Gerber code");
 | 
				
			||||||
 | 
					        let bytes = buf.into_inner().unwrap();
 | 
				
			||||||
 | 
					        let code = String::from_utf8(bytes).unwrap();
 | 
				
			||||||
 | 
					        assert_eq!(&code, $expected);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								gerber-types-rs/src/traits.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								gerber-types-rs/src/traits.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					//! Traits used in gerber-types.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::io::Write;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::GerberResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// All types that implement this trait can be converted to a complete Gerber
 | 
				
			||||||
 | 
					/// Code line. Generated code should end with a newline.
 | 
				
			||||||
 | 
					pub trait GerberCode<W: Write> {
 | 
				
			||||||
 | 
					    fn serialize(&self, writer: &mut W) -> GerberResult<()>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// All types that implement this trait can be converted to a Gerber Code
 | 
				
			||||||
 | 
					/// representation.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This is a crate-internal trait.
 | 
				
			||||||
 | 
					pub trait PartialGerberCode<W: Write> {
 | 
				
			||||||
 | 
					    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										188
									
								
								gerber-types-rs/src/types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								gerber-types-rs/src/types.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,188 @@
 | 
				
			|||||||
 | 
					//! Types for Gerber code generation.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! All types are stateless, meaning that they contain all information in order
 | 
				
			||||||
 | 
					//! to render themselves. This means for example that each `Coordinates`
 | 
				
			||||||
 | 
					//! instance contains a reference to the coordinate format to be used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::convert::From;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::attributes;
 | 
				
			||||||
 | 
					use crate::coordinates;
 | 
				
			||||||
 | 
					use crate::extended_codes;
 | 
				
			||||||
 | 
					use crate::function_codes;
 | 
				
			||||||
 | 
					use crate::macros;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper macros
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					macro_rules! impl_from {
 | 
				
			||||||
 | 
					    ($from:ty, $target:ty, $variant:expr) => {
 | 
				
			||||||
 | 
					        impl From<$from> for $target {
 | 
				
			||||||
 | 
					            fn from(val: $from) -> Self {
 | 
				
			||||||
 | 
					                $variant(val)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Root type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub enum Command {
 | 
				
			||||||
 | 
					    FunctionCode(FunctionCode),
 | 
				
			||||||
 | 
					    ExtendedCode(ExtendedCode),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl_from!(FunctionCode, Command, Command::FunctionCode);
 | 
				
			||||||
 | 
					impl_from!(ExtendedCode, Command, Command::ExtendedCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					macro_rules! impl_command_fromfrom {
 | 
				
			||||||
 | 
					    ($from:ty, $inner:path) => {
 | 
				
			||||||
 | 
					        impl From<$from> for Command {
 | 
				
			||||||
 | 
					            fn from(val: $from) -> Self {
 | 
				
			||||||
 | 
					                Command::from($inner(val))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Main categories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum FunctionCode {
 | 
				
			||||||
 | 
					    DCode(function_codes::DCode),
 | 
				
			||||||
 | 
					    GCode(function_codes::GCode),
 | 
				
			||||||
 | 
					    MCode(function_codes::MCode),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl_from!(function_codes::DCode, FunctionCode, FunctionCode::DCode);
 | 
				
			||||||
 | 
					impl_from!(function_codes::GCode, FunctionCode, FunctionCode::GCode);
 | 
				
			||||||
 | 
					impl_from!(function_codes::MCode, FunctionCode, FunctionCode::MCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl_command_fromfrom!(function_codes::DCode, FunctionCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(function_codes::GCode, FunctionCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(function_codes::MCode, FunctionCode::from);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub enum ExtendedCode {
 | 
				
			||||||
 | 
					    /// FS
 | 
				
			||||||
 | 
					    CoordinateFormat(coordinates::CoordinateFormat),
 | 
				
			||||||
 | 
					    /// MO
 | 
				
			||||||
 | 
					    Unit(extended_codes::Unit),
 | 
				
			||||||
 | 
					    /// AD
 | 
				
			||||||
 | 
					    ApertureDefinition(extended_codes::ApertureDefinition),
 | 
				
			||||||
 | 
					    /// AM
 | 
				
			||||||
 | 
					    ApertureMacro(macros::ApertureMacro),
 | 
				
			||||||
 | 
					    /// LP
 | 
				
			||||||
 | 
					    LoadPolarity(extended_codes::Polarity),
 | 
				
			||||||
 | 
					    /// SR
 | 
				
			||||||
 | 
					    StepAndRepeat(extended_codes::StepAndRepeat),
 | 
				
			||||||
 | 
					    /// TF
 | 
				
			||||||
 | 
					    FileAttribute(attributes::FileAttribute),
 | 
				
			||||||
 | 
					    /// TA
 | 
				
			||||||
 | 
					    ApertureAttribute(attributes::ApertureAttribute),
 | 
				
			||||||
 | 
					    /// TD
 | 
				
			||||||
 | 
					    DeleteAttribute(String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl_from!(
 | 
				
			||||||
 | 
					    coordinates::CoordinateFormat,
 | 
				
			||||||
 | 
					    ExtendedCode,
 | 
				
			||||||
 | 
					    ExtendedCode::CoordinateFormat
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					impl_from!(extended_codes::Unit, ExtendedCode, ExtendedCode::Unit);
 | 
				
			||||||
 | 
					impl_from!(
 | 
				
			||||||
 | 
					    extended_codes::ApertureDefinition,
 | 
				
			||||||
 | 
					    ExtendedCode,
 | 
				
			||||||
 | 
					    ExtendedCode::ApertureDefinition
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					impl_from!(
 | 
				
			||||||
 | 
					    macros::ApertureMacro,
 | 
				
			||||||
 | 
					    ExtendedCode,
 | 
				
			||||||
 | 
					    ExtendedCode::ApertureMacro
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					impl_from!(
 | 
				
			||||||
 | 
					    extended_codes::Polarity,
 | 
				
			||||||
 | 
					    ExtendedCode,
 | 
				
			||||||
 | 
					    ExtendedCode::LoadPolarity
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					impl_from!(
 | 
				
			||||||
 | 
					    extended_codes::StepAndRepeat,
 | 
				
			||||||
 | 
					    ExtendedCode,
 | 
				
			||||||
 | 
					    ExtendedCode::StepAndRepeat
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					impl_from!(
 | 
				
			||||||
 | 
					    attributes::FileAttribute,
 | 
				
			||||||
 | 
					    ExtendedCode,
 | 
				
			||||||
 | 
					    ExtendedCode::FileAttribute
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					impl_from!(
 | 
				
			||||||
 | 
					    attributes::ApertureAttribute,
 | 
				
			||||||
 | 
					    ExtendedCode,
 | 
				
			||||||
 | 
					    ExtendedCode::ApertureAttribute
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl_command_fromfrom!(coordinates::CoordinateFormat, ExtendedCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(extended_codes::Unit, ExtendedCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(extended_codes::ApertureDefinition, ExtendedCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(macros::ApertureMacro, ExtendedCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(extended_codes::Polarity, ExtendedCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(extended_codes::StepAndRepeat, ExtendedCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(attributes::FileAttribute, ExtendedCode::from);
 | 
				
			||||||
 | 
					impl_command_fromfrom!(attributes::ApertureAttribute, ExtendedCode::from);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod test {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use std::io::BufWriter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use crate::extended_codes::Polarity;
 | 
				
			||||||
 | 
					    use crate::function_codes::GCode;
 | 
				
			||||||
 | 
					    use crate::traits::GerberCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_debug() {
 | 
				
			||||||
 | 
					        //! The debug representation should work properly.
 | 
				
			||||||
 | 
					        let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment("test".to_string())));
 | 
				
			||||||
 | 
					        let debug = format!("{:?}", c);
 | 
				
			||||||
 | 
					        assert_eq!(debug, "FunctionCode(GCode(Comment(\"test\")))");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_function_code_serialize() {
 | 
				
			||||||
 | 
					        //! A `FunctionCode` should implement `GerberCode`
 | 
				
			||||||
 | 
					        let c = FunctionCode::GCode(GCode::Comment("comment".to_string()));
 | 
				
			||||||
 | 
					        assert_code!(c, "G04 comment*\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_function_code_from_gcode() {
 | 
				
			||||||
 | 
					        let comment = GCode::Comment("hello".into());
 | 
				
			||||||
 | 
					        let f1: FunctionCode = FunctionCode::GCode(comment.clone());
 | 
				
			||||||
 | 
					        let f2: FunctionCode = comment.into();
 | 
				
			||||||
 | 
					        assert_eq!(f1, f2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_command_from_function_code() {
 | 
				
			||||||
 | 
					        let comment = FunctionCode::GCode(GCode::Comment("hello".into()));
 | 
				
			||||||
 | 
					        let c1: Command = Command::FunctionCode(comment.clone());
 | 
				
			||||||
 | 
					        let c2: Command = comment.into();
 | 
				
			||||||
 | 
					        assert_eq!(c1, c2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_command_from_extended_code() {
 | 
				
			||||||
 | 
					        let delete_attr = ExtendedCode::DeleteAttribute("hello".into());
 | 
				
			||||||
 | 
					        let c1: Command = Command::ExtendedCode(delete_attr.clone());
 | 
				
			||||||
 | 
					        let c2: Command = delete_attr.into();
 | 
				
			||||||
 | 
					        assert_eq!(c1, c2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_extended_code_from_polarity() {
 | 
				
			||||||
 | 
					        let e1: ExtendedCode = ExtendedCode::LoadPolarity(Polarity::Dark);
 | 
				
			||||||
 | 
					        let e2: ExtendedCode = Polarity::Dark.into();
 | 
				
			||||||
 | 
					        assert_eq!(e1, e2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user