move gcode parse/emit to sameer/g-code, use structopt

master
Sameer Puri 4 years ago
parent 08762f432c
commit 7c6c790378

518
Cargo.lock generated

@ -18,12 +18,27 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.2" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "ascii-canvas"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff8eb72df928aafb99fe5d37b383f2fe25bd2a765e3e5f7c365916b6f2463a29"
dependencies = [
"term",
]
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -41,12 +56,50 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bit-set"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blake2b_simd"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]]
name = "byteorder"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -68,11 +121,101 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "codespan"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991d34632921756cbe9e3e1736b2e1f12f16166a9c9cd91979d96d4c0a086b63"
dependencies = [
"codespan-reporting",
]
[[package]]
name = "codespan-reporting"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6ce42b8998a383572e0a802d859b1f00c79b7b7474e62fff88ee5c2845d9c13"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "crossbeam-utils"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
dependencies = [
"autocfg",
"cfg-if",
"lazy_static",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "ctor"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "diff"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
[[package]]
name = "difference"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
name = "dirs"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "ena"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
dependencies = [
"log",
]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
dependencies = [ dependencies = [
"atty", "atty",
"humantime", "humantime",
@ -102,12 +245,48 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e"
[[package]]
name = "g-code"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f365da003f4befd25377840dbb6d49a3eca29e033ae2687e6d6d7460471aacdd"
dependencies = [
"codespan",
"codespan-reporting",
"lalrpop",
"lalrpop-util",
"lazy_static",
"num",
"num-rational",
"paste",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.9.1" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "heck"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.18" version = "0.1.18"
@ -133,11 +312,54 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]]
name = "lalrpop"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a71d75b267b3299da9ccff4dd80d73325b5d8adcd76fe97cf92725eb7c6f122"
dependencies = [
"ascii-canvas",
"atty",
"bit-set",
"diff",
"ena",
"itertools",
"lalrpop-util",
"petgraph",
"regex",
"regex-syntax",
"string_cache",
"term",
"tiny-keccak",
"unicode-xid",
]
[[package]]
name = "lalrpop-util"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ebbd90154472db6267a7d28ca08fea7788e5619fef10f2398155cb74c08f77a"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.84" version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
[[package]] [[package]]
name = "log" name = "log"
@ -165,6 +387,66 @@ version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "num"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
dependencies = [
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"
@ -181,23 +463,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
[[package]] [[package]]
name = "paste" name = "output_vt100"
version = "0.1.18" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
dependencies = [ dependencies = [
"paste-impl", "winapi",
"proc-macro-hack",
] ]
[[package]] [[package]]
name = "paste-impl" name = "paste"
version = "0.1.18" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" checksum = "c5d65c4d95931acda4498f675e332fcbdc9a06705cd07086c510e9b6009cd1c1"
dependencies = [
"proc-macro-hack",
]
[[package]] [[package]]
name = "petgraph" name = "petgraph"
@ -210,10 +488,90 @@ dependencies = [
] ]
[[package]] [[package]]
name = "proc-macro-hack" name = "phf_shared"
version = "0.5.19" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher 0.3.3",
]
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "pretty_assertions"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
dependencies = [
"ansi_term",
"ctor",
"difference",
"output_vt100",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_users"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [
"getrandom",
"redox_syscall",
"rust-argon2",
]
[[package]] [[package]]
name = "regex" name = "regex"
@ -242,12 +600,42 @@ dependencies = [
"xmlparser", "xmlparser",
] ]
[[package]]
name = "rust-argon2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
dependencies = [
"base64",
"blake2b_simd",
"constant_time_eq",
"crossbeam-utils",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.2.3" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
[[package]]
name = "siphasher"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
[[package]]
name = "string_cache"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"phf_shared",
"precomputed-hash",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.8.0"
@ -255,16 +643,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "svg2gcode" name = "structopt"
version = "0.0.1" version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
dependencies = [ dependencies = [
"clap", "clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "svg2gcode"
version = "0.0.2"
dependencies = [
"codespan-reporting",
"env_logger", "env_logger",
"euclid",
"g-code",
"log", "log",
"lyon_geom", "lyon_geom",
"paste", "paste",
"petgraph", "pretty_assertions",
"roxmltree", "roxmltree",
"structopt",
"svgtypes", "svgtypes",
"uom", "uom",
] ]
@ -276,7 +691,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff"
dependencies = [ dependencies = [
"float-cmp", "float-cmp",
"siphasher", "siphasher 0.2.3",
]
[[package]]
name = "syn"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "term"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42"
dependencies = [
"byteorder",
"dirs",
"winapi",
] ]
[[package]] [[package]]
@ -299,25 +736,46 @@ dependencies = [
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915" checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.12.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.8" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]] [[package]]
name = "uom" name = "uom"
version = "0.31.0" version = "0.31.0"
@ -334,6 +792,18 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

@ -1,17 +1,22 @@
[package] [package]
name = "svg2gcode" name = "svg2gcode"
version = "0.0.1" version = "0.0.2"
authors = ["Sameer Puri <purisame@spuri.io>"] authors = ["Sameer Puri <purisame@spuri.io>"]
edition = "2018" edition = "2018"
description = "Convert paths in SVG files to GCode for a pen plotter or laser engraver" description = "Convert paths in SVG files to GCode for a pen plotter, laser engraver, or other machine."
[dependencies] [dependencies]
g-code = "0.0.1"
lyon_geom = "0" lyon_geom = "0"
clap = "2" euclid = "0.22"
structopt = "0.3"
log = "0" log = "0"
env_logger = "0" env_logger = "0"
uom = "0.31.0" uom = "0.31.0"
paste = "0"
roxmltree = "0" roxmltree = "0"
svgtypes = "0" svgtypes = "0"
petgraph = "0" codespan-reporting = "0.11"
paste = "1"
[dev-dependencies]
pretty_assertions = "0.6"

@ -1,5 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use g_code::{command, emit::Token};
use lyon_geom::{ use lyon_geom::{
euclid::{default::Transform2D, Angle, Transform3D}, euclid::{default::Transform2D, Angle, Transform3D},
vector, vector,
@ -9,9 +10,6 @@ use svgtypes::{
LengthListParser, PathParser, PathSegment, TransformListParser, TransformListToken, ViewBox, LengthListParser, PathParser, PathSegment, TransformListParser, TransformListToken, ViewBox,
}; };
#[macro_use]
use crate::*;
use crate::gcode::*;
use crate::machine::*; use crate::machine::*;
use crate::turtle::*; use crate::turtle::*;
@ -36,15 +34,17 @@ impl Default for ProgramOptions {
} }
} }
pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Vec<Command> { pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Vec<Token> {
let mut turtle = Turtle::new(mach); let mut turtle = Turtle::new(mach);
let mut program = vec![ let mut program = command!(UnitsMillimeters {})
command!(CommandWord::UnitsMillimeters, {}), .as_token_vec()
command!(CommandWord::FeedRateUnitsPerMinute, {}), .drain(..)
]; .chain(command!(FeedRateUnitsPerMinute {}).as_token_vec())
program.append(&mut turtle.machine.program_begin()); .collect::<Vec<_>>();
program.append(&mut turtle.machine.absolute());
program.extend(turtle.machine.program_begin());
program.extend(turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0)); program.append(&mut turtle.move_to(true, 0.0, 0.0));
// Depth-first SVG DOM traversal // Depth-first SVG DOM traversal
@ -121,8 +121,11 @@ pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Ve
comment += " > "; comment += " > ";
}); });
comment += &node_name(&node); comment += &node_name(&node);
program.push(command!(CommandWord::Comment(Box::new(comment)), {})); program.push(Token::Comment {
program.append(&mut apply_path(&mut turtle, &options, d)); is_inline: false,
inner: comment,
});
program.extend(apply_path(&mut turtle, &options, d));
} else { } else {
warn!("There is a path node containing no actual path: {:?}", node); warn!("There is a path node containing no actual path: {:?}", node);
} }
@ -139,11 +142,11 @@ pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Ve
// Critical step for actually moving the machine back to the origin, just in case SVG is malformed // Critical step for actually moving the machine back to the origin, just in case SVG is malformed
turtle.pop_all_transforms(); turtle.pop_all_transforms();
program.append(&mut turtle.machine.tool_off()); program.extend(turtle.machine.tool_off());
program.append(&mut turtle.machine.absolute()); program.extend(turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0)); program.append(&mut turtle.move_to(true, 0.0, 0.0));
program.append(&mut turtle.machine.program_end()); program.extend(turtle.machine.program_end());
program.push(command!(CommandWord::ProgramEnd, {})); program.append(&mut command!(ProgramEnd {}).as_token_vec());
program program
} }
@ -185,11 +188,11 @@ fn width_and_height_into_transform(
} }
} }
fn apply_path<'a>(turtle: &mut Turtle, options: &ProgramOptions, path: &'a str) -> Vec<Command> { fn apply_path(turtle: &mut Turtle, options: &ProgramOptions, path: &str) -> Vec<Token> {
use PathSegment::*; use PathSegment::*;
PathParser::from(path) PathParser::from(path)
.map(|segment| segment.expect("could not parse path segment")) .map(|segment| segment.expect("could not parse path segment"))
.map(|segment| { .flat_map(|segment| {
match segment { match segment {
MoveTo { abs, x, y } => turtle.move_to(abs, x, y), MoveTo { abs, x, y } => turtle.move_to(abs, x, y),
ClosePath { abs: _ } => { ClosePath { abs: _ } => {
@ -271,7 +274,6 @@ fn apply_path<'a>(turtle: &mut Turtle, options: &ProgramOptions, path: &'a str)
), ),
} }
}) })
.flatten()
.collect() .collect()
} }

@ -1,107 +0,0 @@
use core::convert::TryFrom;
use std::io::{self, Write};
#[macro_use]
mod spec;
pub use spec::*;
/// Collapses GCode words into higher-level commands
pub struct CommandVecIntoIterator {
vec: Vec<Word>,
index: usize,
}
impl Iterator for CommandVecIntoIterator {
type Item = Command;
fn next(&mut self) -> Option<Self::Item> {
if self.vec.len() <= self.index {
return None;
}
let mut i = self.index + 1;
while i < self.vec.len() {
if CommandWord::is_command(&self.vec[i]) {
break;
}
i += 1;
}
let command = Command::try_from(&self.vec[self.index..i]).ok();
self.index = i;
command
}
}
impl From<Vec<Word>> for CommandVecIntoIterator {
fn from(vec: Vec<Word>) -> Self {
Self { vec, index: 0 }
}
}
pub fn parse_gcode(gcode: &str) -> Vec<Word> {
let mut vec = vec![];
let mut in_string = false;
let mut letter: Option<char> = None;
let mut value_range = 0..0;
gcode.char_indices().for_each(|(i, c)| {
if (c.is_alphabetic() || c.is_ascii_whitespace()) && !in_string {
if let Some(l) = letter {
vec.push(Word {
letter: l,
value: parse_value(&gcode[value_range.clone()]),
});
letter = None;
}
if c.is_alphabetic() {
letter = Some(c);
}
value_range = (i + 1)..(i + 1);
} else if in_string {
value_range = value_range.start..(i + 1);
} else {
if c == '"' {
in_string = !in_string;
}
value_range = value_range.start..(i + 1);
}
});
if let Some(l) = letter {
vec.push(Word {
letter: l,
value: parse_value(&gcode[value_range]),
});
}
vec
}
fn parse_value(word: &str) -> Value {
if word.starts_with('"') && word.ends_with('"') {
Value::String(Box::new(word.to_string()))
} else {
let index_of_dot = word.find('.');
Value::Fractional(
word[..index_of_dot.unwrap_or_else(|| word.len())]
.parse::<u32>()
.unwrap(),
index_of_dot.map(|j| word[j + 1..].parse::<u32>().unwrap()),
)
}
}
/// Writes a GCode program or sequence to a Writer
/// Each command is placed on a separate line
pub fn program2gcode<W: Write>(program: Vec<Command>, mut w: W) -> io::Result<()> {
for command in program.into_iter() {
let words: Vec<Word> = command.into();
let mut it = words.iter();
if let Some(command_word) = it.next() {
write!(w, "{}{}", command_word.letter, command_word.value)?;
for (i, word) in it.enumerate() {
write!(w, " {}{}", word.letter, word.value)?;
if i != words.len() - 2 {
write!(w, " ")?;
}
}
writeln!(w, "")?;
}
}
Ok(())
}

@ -1,300 +0,0 @@
use std::convert::TryFrom;
/// Fundamental unit of GCode: a value preceded by a descriptive letter.
#[derive(Clone, PartialEq, Debug)]
pub struct Word {
pub letter: char,
pub value: Value,
}
/// All the possible variations of a word's value.
/// Fractional is needed to support commands like G91.1 which would be changed by float arithmetic.
/// Some flavors of GCode also allow for strings.
#[derive(Clone, PartialEq, Debug)]
pub enum Value {
Fractional(u32, Option<u32>),
Float(f64),
String(Box<String>),
}
impl Into<f64> for &Value {
fn into(self) -> f64 {
match self {
Value::Float(f) => *f,
_ => panic!("Unwrapping a non-float"),
}
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fractional(number, Some(fraction)) => write!(f, "{}.{}", number, fraction),
Self::Fractional(number, None) => write!(f, "{}", number),
Self::Float(float) => write!(f, "{}", float),
Self::String(string) => write!(f, "{}", string),
}
}
}
/// A macro for quickly instantiating a float-valued command
#[macro_export]
macro_rules! command {
($commandWord: expr, {$($argument: ident : $value: expr,)*}) => {
paste::expr! (Command::new($commandWord, vec![$(Word {
letter: stringify!([<$argument:upper>]).chars().next().unwrap(),
value: Value::Float($value),
},)*]))
};
}
macro_rules! commands {
($($(#[$outer:meta])* $commandName: ident {$letter: expr, $number: expr, $fraction: path, {$($(#[$inner:meta])* $argument: ident), *} },)*) => {
/// Commands are the operational unit of GCode
/// They consist of an identifying word followed by arguments
#[derive(Clone, PartialEq, Debug)]
pub struct Command {
command_word: CommandWord,
arguments: Vec<Word>
}
impl Command {
pub fn new(command_word: CommandWord, mut arguments: Vec<Word>) -> Self {
Self {
command_word: command_word.clone(),
arguments: arguments.drain(..).filter(|w| {
match command_word {
$(CommandWord::$commandName => match w.letter.to_lowercase() {
$($argument => true,)*
_ => false
},)*
_ => false
}
}).collect()
}
}
pub fn push(&mut self, argument: Word) {
match self.command_word {
$(CommandWord::$commandName => match argument.letter.to_lowercase() {
$($argument => {
self.arguments.push(argument);
})*
_ => {}
},)*
_ => {}
}
}
pub fn word(&'_ self) -> &'_ CommandWord {
&self.command_word
}
pub fn get(&'_ self, letter: char) -> Option<&'_ Word> {
let letter = letter.to_ascii_uppercase();
self.arguments.iter().find(|arg| arg.letter == letter)
}
pub fn set(&mut self, letter: char, value: Value) {
let letter = letter.to_ascii_uppercase();
for i in 0..self.arguments.len() {
if self.arguments[i].letter == letter {
self.arguments[i].value = value;
break;
}
}
}
}
impl Into<Vec<Word>> for Command {
fn into(self) -> Vec<Word> {
let mut args = self.arguments;
args.insert(0, self.command_word.into());
args
}
}
impl TryFrom<&[Word]> for Command {
type Error = ();
fn try_from(words: &[Word]) -> Result<Self, ()> {
if words.len() == 0 {
return Err(());
}
let command_word = CommandWord::try_from(&words[0])?;
let mut arguments = Vec::with_capacity(words.len() - 1);
for i in 1..words.len() {
match command_word {
$(CommandWord::$commandName => match words[i].letter.to_lowercase() {
$($argument => {
arguments.push(words[i].clone());
})*
_ => {}
},)*
_ => {}
}
}
Ok(Self {
command_word,
arguments
})
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum CommandWord {
$(
$(#[$outer])*
$commandName,
)*
/// A comment is a special command: it is a semicolon followed by text until the end of the line
Comment(Box<String>),
/// Letter N followed by an integer (with no sign) between 0 and 99999 written with no more than five digits
LineNumber(u16),
/// Byte-sized checksums are used by some GCode generators at the end of each line
Checksum(u8),
}
impl CommandWord {
pub fn is_command(word: &Word) -> bool {
let (number, fraction) = match &word.value {
Value::Fractional(number, fraction) => (number, fraction),
_other => return false
};
match (word.letter, number, fraction) {
$(($letter, $number, $fraction) => true,)*
('*', _checksum, None) => true,
('N', _line_number, None) => true,
(_, _, _) => false
}
}
}
impl TryFrom<&Word> for CommandWord {
type Error = ();
fn try_from(word: &Word) -> Result<Self, ()> {
let (number, fraction) = match &word.value {
Value::Fractional(number, fraction) => (number, fraction),
_other => return Err(())
};
match (word.letter, number, fraction) {
$(($letter, $number, $fraction) => Ok(Self::$commandName),)*
('*', checksum, None) => Ok(Self::Checksum(*checksum as u8)),
('N', line_number, None) => Ok(Self::LineNumber(*line_number as u16)),
(_, _, _) => Err(())
}
}
}
impl Into<Word> for CommandWord {
fn into(self) -> Word {
match self {
$(
Self::$commandName {} => Word {
letter: $letter,
// TODO: fix fraction
value: Value::Fractional($number, $fraction)
},
)*
Self::Checksum(value) => Word {
letter: '*',
value: Value::Fractional(value as u32, None)
},
Self::LineNumber(value) => Word {
letter: 'N',
value: Value::Fractional(value as u32, None)
},
Self::Comment(string) => Word {
letter: ';',
value: Value::String(string)
}
}
}
}
};
}
commands!(
/// Moves the head at the fastest possible speed to the desired speed
/// Never enter a cut with rapid positioning
/// Some older machines may "dog leg" rapid positioning, moving one axis at a time
RapidPositioning {
'G', 0, None, {
x,
y,
z,
e,
f,
h,
r,
s,
a,
b,
c
}
},
/// Typically used for "cutting" motion
LinearInterpolation {
'G', 1, None, {
x,
y,
z,
e,
f,
h,
r,
s,
a,
b,
c
}
},
/// This will keep the axes unmoving for the period of time in seconds specified by the P number
Dwell {
'G', 4, None, {
/// Time in seconds
p
}
},
/// Use inches for length units
UnitsInches {
'G', 20, None, {}
},
/// Use millimeters for length units
UnitsMillimeters {
'G', 21, None, {}
},
/// In absolute distance mode, axis numbers usually represent positions in terms of the currently active coordinate system.
AbsoluteDistanceMode {
'G', 90, None, {}
},
/// In relative distance mode, axis numbers usually represent increments from the current values of the numbers
RelativeDistanceMode {
'G', 91, None, {}
},
FeedRateUnitsPerMinute {
'G', 94, None, {}
},
/// Start spinning the spindle clockwise with speed `p`
StartSpindleClockwise {
'M', 3, None, {
/// Speed
p
}
},
/// Start spinning the spindle counterclockwise with speed `p`
StartSpindleCounterclockwise {
'M', 4, None, {
/// Speed
p
}
},
/// Stop spinning the spindle
StopSpindle {
'M', 5, None, {}
},
/// Signals the end of a program
ProgramEnd {
'M', 20, None, {}
},
);

@ -1,11 +1,8 @@
use crate::gcode::*; use g_code::{
command,
//// Direction of the machine spindle emit::Token,
#[derive(Clone, PartialEq, Eq)] parse::{ast::Snippet, token::Field},
pub enum Direction { };
Clockwise,
Counterclockwise,
}
/// Whether the tool is active (i.e. cutting) /// Whether the tool is active (i.e. cutting)
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
@ -14,6 +11,16 @@ pub enum Tool {
On, On,
} }
impl std::ops::Not for Tool {
type Output = Self;
fn not(self) -> Self {
match self {
Self::Off => Self::On,
Self::On => Self::Off,
}
}
}
/// The distance mode for movement commands /// The distance mode for movement commands
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Distance { pub enum Distance {
@ -21,80 +28,87 @@ pub enum Distance {
Relative, Relative,
} }
/// Generic machine state simulation, assuming nothing is known about the machine when initialized. impl std::ops::Not for Distance {
/// This is used to reduce output GCode verbosity and run repetitive actions. type Output = Self;
#[derive(Debug, Default)] fn not(self) -> Self {
pub struct Machine { match self {
tool_state: Option<Tool>, Self::Absolute => Self::Relative,
distance_mode: Option<Distance>, Self::Relative => Self::Absolute,
tool_on_action: Vec<Command>,
tool_off_action: Vec<Command>,
program_begin_sequence: Vec<Command>,
program_end_sequence: Vec<Command>,
}
impl Machine {
/// Create a generic machine, given a tool on/off GCode sequence.
pub fn new(
tool_on_action: Vec<Word>,
tool_off_action: Vec<Word>,
program_begin_sequence: Vec<Word>,
program_end_sequence: Vec<Word>,
) -> Self {
Self {
tool_state: None,
distance_mode: None,
tool_on_action: CommandVecIntoIterator::from(tool_on_action).collect(),
tool_off_action: CommandVecIntoIterator::from(tool_off_action).collect(),
program_begin_sequence: CommandVecIntoIterator::from(program_begin_sequence).collect(),
program_end_sequence: CommandVecIntoIterator::from(program_end_sequence).collect(),
} }
} }
} }
impl Machine { /// Generic machine state simulation, assuming nothing is known about the machine when initialized.
/// This is used to reduce output GCode verbosity and run repetitive actions.
#[derive(Debug)]
pub struct Machine<'input> {
pub(crate) tool_state: Option<Tool>,
pub(crate) distance_mode: Option<Distance>,
pub(crate) tool_on_action: Option<Snippet<'input>>,
pub(crate) tool_off_action: Option<Snippet<'input>>,
pub(crate) program_begin_sequence: Option<Snippet<'input>>,
pub(crate) program_end_sequence: Option<Snippet<'input>>,
}
impl<'input> Machine<'input> {
/// Output gcode to turn the tool on. /// Output gcode to turn the tool on.
pub fn tool_on(&mut self) -> Vec<Command> { pub fn tool_on<'a>(&'a mut self) -> Vec<Token> {
if self.tool_state == Some(Tool::Off) || self.tool_state == None { if self.tool_state == Some(Tool::Off) || self.tool_state == None {
self.tool_state = Some(Tool::On); self.tool_state = Some(Tool::On);
self.tool_on_action.clone() self.tool_on_action
.iter()
.flat_map(|s| s.iter_fields())
.map(|f: &Field| Token::from(f))
.collect()
} else { } else {
vec![] vec![]
} }
} }
/// Output gcode to turn the tool off. /// Output gcode to turn the tool off.
pub fn tool_off(&mut self) -> Vec<Command> { pub fn tool_off<'a>(&'a mut self) -> Vec<Token> {
if self.tool_state == Some(Tool::On) || self.tool_state == None { if self.tool_state == Some(Tool::On) || self.tool_state == None {
self.tool_state = Some(Tool::Off); self.tool_state = Some(Tool::Off);
self.tool_off_action.clone() self.tool_on_action
.iter()
.flat_map(|s| s.iter_fields())
.map(|f: &Field| Token::from(f))
.collect()
} else { } else {
vec![] vec![]
} }
} }
pub fn program_begin(&self) -> Vec<Command> { pub fn program_begin<'a>(&'a self) -> Vec<Token> {
self.program_begin_sequence.clone() self.program_begin_sequence
.iter()
.flat_map(|s| s.iter_fields())
.map(|f: &Field| Token::from(f))
.collect()
} }
pub fn program_end(&self) -> Vec<Command> { pub fn program_end<'a>(&'a self) -> Vec<Token> {
self.program_end_sequence.clone() self.program_end_sequence
.iter()
.flat_map(|s| s.iter_fields())
.map(|f: &Field| Token::from(f))
.collect()
} }
/// Output relative distance field if mode was absolute or unknown. /// Output absolute distance field if mode was relative or unknown.
pub fn absolute(&mut self) -> Vec<Command> { pub fn absolute(&mut self) -> Vec<Token> {
if self.distance_mode == Some(Distance::Relative) || self.distance_mode == None { if self.distance_mode == Some(Distance::Relative) || self.distance_mode == None {
self.distance_mode = Some(Distance::Absolute); self.distance_mode = Some(Distance::Absolute);
vec![command!(CommandWord::AbsoluteDistanceMode, {})] command!(AbsoluteDistanceMode {}).as_token_vec()
} else { } else {
vec![] vec![]
} }
} }
/// Output absolute distance field if mode was relative or unknown. /// Output relative distance field if mode was absolute or unknown.
pub fn relative(&mut self) -> Vec<Command> { pub fn relative(&mut self) -> Vec<Token> {
if self.distance_mode == Some(Distance::Absolute) || self.distance_mode == None { if self.distance_mode == Some(Distance::Absolute) || self.distance_mode == None {
self.distance_mode = Some(Distance::Relative); self.distance_mode = Some(Distance::Relative);
vec![command!(CommandWord::RelativeDistanceMode, {})] command!(RelativeDistanceMode {}).as_token_vec()
} else { } else {
vec![] vec![]
} }

@ -1,17 +1,16 @@
#[macro_use] #[macro_use]
extern crate clap;
#[macro_use]
extern crate log; extern crate log;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::{self, Read}; use std::io::{self, Read};
use std::path::PathBuf;
use g_code::parse::{ast::Snippet, lexer::Lexer, ParseError, SnippetParser};
use structopt::StructOpt;
/// Converts an SVG to GCode in an internal representation /// Converts an SVG to GCode in an internal representation
mod converter; mod converter;
/// Defines an internal GCode representation
#[macro_use]
mod gcode;
/// Emulates the state of an arbitrary machine that can run GCode /// Emulates the state of an arbitrary machine that can run GCode
mod machine; mod machine;
/// Operations that are easier to implement after GCode is generated, or would /// Operations that are easier to implement after GCode is generated, or would
@ -24,29 +23,49 @@ mod turtle;
use converter::ProgramOptions; use converter::ProgramOptions;
use machine::Machine; use machine::Machine;
#[derive(Debug, StructOpt)]
#[structopt(name = "svg2gcode", author, about)]
struct Opt {
/// Curve interpolation tolerance
#[structopt(long, default_value = "0.002")]
tolerance: f64,
/// Machine feed rate in mm/min
#[structopt(long, default_value = "300")]
feedrate: f64,
/// Dots per inch (DPI) for pixels, points, picas, etc.
#[structopt(long, default_value = "96")]
dpi: f64,
#[structopt(short = "on", long)]
/// Tool on GCode sequence
tool_on_sequence: Option<String>,
#[structopt(short = "off", long)]
/// Tool off GCode sequence
tool_off_sequence: Option<String>,
/// Optional GCode begin sequence (i.e. change to a cutter tool)
#[structopt(short = "begin", long)]
begin_sequence: Option<String>,
/// Optional GCode end sequence, prior to program end (i.e. put away a cutter tool)
#[structopt(short = "end", long)]
end_sequence: Option<String>,
/// A file path for an SVG, else reads from stdin
file: Option<PathBuf>,
/// Output file path (overwrites old files), else writes to stdout
#[structopt(short, long)]
out: Option<PathBuf>,
/// Set where the bottom left corner of the SVG will be placed
#[structopt(long, default_value = "0,0")]
origin: String,
}
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
if env::var("RUST_LOG").is_err() { if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", "svg2gcode=info") env::set_var("RUST_LOG", "svg2gcode=info")
} }
env_logger::init(); env_logger::init();
let matches = clap_app!(svg2gcode =>
(version: crate_version!()) let opt = Opt::from_args();
(author: crate_authors!())
(about: crate_description!()) let input = match opt.file {
(@arg FILE: "A file path for an SVG, else reads from stdin")
(@arg tolerance: --tolerance +takes_value "Curve interpolation tolerance (default: 0.002mm)")
(@arg feedrate: --feedrate +takes_value "Machine feed rate in mm/min (default: 300mm/min)")
(@arg dpi: --dpi +takes_value "Dots per inch (DPI) for pixels, points, picas, etc. (default: 96dpi)")
(@arg tool_on_sequence: --on +takes_value +required "Tool on GCode sequence")
(@arg tool_off_sequence: --off +takes_value +required "Tool off GCode sequence")
(@arg begin_sequence: --begin +takes_value "Optional GCode begin sequence (i.e. change to a tool)")
(@arg end_sequence: --end +takes_value "Optional GCode end sequence, prior to program end (i.e. change to a tool)")
(@arg out: --out -o +takes_value "Output file path (overwrites old files), else writes to stdout")
(@arg origin: --origin +takes_value "Set where the bottom left corner of the SVG will be placed (default: 0,0)")
)
.get_matches();
let input = match matches.value_of("FILE") {
Some(filename) => { Some(filename) => {
let mut f = File::open(filename)?; let mut f = File::open(filename)?;
let len = f.metadata()?.len(); let len = f.metadata()?.len();
@ -63,79 +82,148 @@ fn main() -> io::Result<()> {
}; };
let mut options = ProgramOptions::default(); let mut options = ProgramOptions::default();
options.tolerance = opt.tolerance;
if let Some(tolerance) = matches options.feedrate = opt.feedrate;
.value_of("tolerance") options.dpi = opt.dpi;
.map(|tolerance| tolerance.parse().expect("could not parse tolerance"))
{ let snippets = [
options.tolerance = tolerance; opt.tool_on_sequence.as_ref().map(parse_snippet).transpose(),
} opt.tool_off_sequence
if let Some(feedrate) = matches .as_ref()
.value_of("feedrate") .map(parse_snippet)
.map(|feedrate| feedrate.parse().expect("could not parse tolerance")) .transpose(),
opt.begin_sequence.as_ref().map(parse_snippet).transpose(),
opt.end_sequence.as_ref().map(parse_snippet).transpose(),
];
let machine = if let [Ok(tool_on_action), Ok(tool_off_action), Ok(program_begin_sequence), Ok(program_end_sequence)] =
snippets
{ {
options.feedrate = feedrate; Machine {
} tool_on_action,
if let Some(dpi) = matches tool_off_action,
.value_of("dpi") program_begin_sequence,
.map(|dpi| dpi.parse().expect("could not parse tolerance")) program_end_sequence,
{ tool_state: None,
options.dpi = dpi; distance_mode: None,
} }
} else {
let machine = machine::Machine::new( use codespan_reporting::term::{
matches emit,
.value_of("tool_on_sequence") termcolor::{ColorChoice, StandardStream},
.map(gcode::parse_gcode) };
.unwrap_or_default(), let mut writer = StandardStream::stderr(ColorChoice::Auto);
matches let config = codespan_reporting::term::Config::default();
.value_of("tool_off_sequence")
.map(gcode::parse_gcode) for (i, (filename, gcode)) in [
.unwrap_or_default(), ("tool_on_sequence", &opt.tool_on_sequence),
matches ("tool_off_sequence", &opt.tool_off_sequence),
.value_of("begin_sequence") ("begin_sequence", &opt.begin_sequence),
.map(gcode::parse_gcode) ("end_sequence", &opt.end_sequence),
.unwrap_or_default(), ]
matches .iter()
.value_of("end_sequence") .enumerate()
.map(gcode::parse_gcode) {
.unwrap_or_default(), if let Err(err) = &snippets[i] {
); emit(
&mut writer,
&config,
&codespan_reporting::files::SimpleFile::new(filename, gcode.as_ref().unwrap()),
&g_code::parse::into_diagnostic(&err),
)
.unwrap();
}
}
std::process::exit(1)
};
let document = roxmltree::Document::parse(&input).expect("Invalid or unsupported SVG file"); let document = roxmltree::Document::parse(&input).expect("Invalid or unsupported SVG file");
let mut program = converter::svg2program(&document, options, machine); let mut program = converter::svg2program(&document, options, machine);
let origin = matches let origin = opt
.value_of("origin") .origin
.map(|coords| coords.split(',')) .split(',')
.map(|coords| coords.map(|point| point.parse().expect("could not parse coordinate"))) .map(|point| point.parse().expect("could not parse coordinate"))
.map(|coords| coords.collect::<Vec<f64>>()) .collect::<Vec<f64>>();
.map(|coords| (coords[0], coords[1])) postprocess::set_origin(&mut program, lyon_geom::point(origin[0], origin[1]));
.unwrap_or((0., 0.));
postprocess::set_origin(&mut program, lyon_geom::point(origin.0, origin.1)); if let Some(out_path) = opt.out {
tokens_into_gcode(program, File::create(out_path)?)
if let Some(out_path) = matches.value_of("out") {
gcode::program2gcode(program, File::create(out_path)?)
} else { } else {
gcode::program2gcode(program, std::io::stdout()) tokens_into_gcode(program, std::io::stdout())
}
}
fn parse_snippet<'input>(gcode: &'input String) -> Result<Snippet<'input>, ParseError<'input>> {
SnippetParser::new().parse(gcode, Lexer::new(gcode))
}
fn tokens_into_gcode<W: std::io::Write>(
program: Vec<g_code::emit::Token>,
mut w: W,
) -> io::Result<()> {
use g_code::emit::Token::*;
let mut preceded_by_newline = true;
for token in program {
match token {
Field(f) => {
if !preceded_by_newline {
if matches!(f.letters.as_str(), "G" | "M") {
writeln!(w, "")?;
} else {
write!(w, " ")?;
}
}
write!(w, "{}", f)?;
preceded_by_newline = false;
}
Comment {
is_inline: true,
inner,
} => {
write!(w, "({})", inner)?;
preceded_by_newline = false;
}
Comment {
is_inline: false,
inner,
} => {
writeln!(w, ";{}", inner)?;
preceded_by_newline = true;
}
_ => {}
}
}
// Ensure presence of trailing newline
if !preceded_by_newline {
writeln!(w, "")?;
} }
Ok(())
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use pretty_assertions::assert_eq;
fn get_actual(input: &str) -> String { fn get_actual(input: &str) -> String {
let options = ProgramOptions::default(); let options = ProgramOptions::default();
let machine = Machine::default(); let machine = Machine {
tool_state: None,
distance_mode: None,
tool_on_action: None,
tool_off_action: None,
program_begin_sequence: None,
program_end_sequence: None,
};
let document = roxmltree::Document::parse(input).unwrap(); let document = roxmltree::Document::parse(input).unwrap();
let mut program = converter::svg2program(&document, options, machine); let mut program = converter::svg2program(&document, options, machine);
postprocess::set_origin(&mut program, lyon_geom::point(0., 0.)); postprocess::set_origin(&mut program, lyon_geom::point(0., 0.));
let mut actual = vec![]; let mut actual = vec![];
assert!(gcode::program2gcode(program, &mut actual).is_ok()); assert!(tokens_into_gcode(program, &mut actual).is_ok());
String::from_utf8(actual).unwrap() String::from_utf8(actual).unwrap()
} }

@ -1,65 +1,86 @@
use crate::gcode::CommandWord::*; use euclid::default::Box2D;
use crate::gcode::*; use g_code::emit::{
Field, Token, Value, ABSOLUTE_DISTANCE_MODE_FIELD, RELATIVE_DISTANCE_MODE_FIELD,
};
use lyon_geom::{point, vector, Point}; use lyon_geom::{point, vector, Point};
type F64Point = Point<f64>; type F64Point = Point<f64>;
/// Moves all the commands so that they are beyond a specified position /// Moves all the commands so that they are beyond a specified position
pub fn set_origin(commands: &mut [Command], origin: F64Point) { pub fn set_origin(tokens: &mut [Token], origin: F64Point) {
let offset = -get_bounding_box(commands).0.to_vector() + origin.to_vector(); let offset = -get_bounding_box(tokens.iter()).min.to_vector() + origin.to_vector();
let mut is_relative = false; let mut is_relative = false;
let mut current_position = point(0f64, 0f64); let mut current_position = point(0f64, 0f64);
let x = "X".to_string();
for command in commands { let y = "Y".to_string();
match command.word() { let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD.clone());
RapidPositioning | LinearInterpolation => { let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD.clone());
let x: f64 = (&command.get('X').unwrap().value).into(); for token in tokens {
let y: f64 = (&command.get('Y').unwrap().value).into(); match token {
if is_relative { abs if *abs == abs_tok => is_relative = false,
current_position += vector(x, y); rel if *rel == rel_tok => is_relative = true,
} else { Token::Field(Field { letters, value }) if *letters == x => {
current_position = point(x, y); if let Some(float) = value.as_f64() {
command.set('X', Value::Float((current_position + offset).x)); if is_relative {
command.set('Y', Value::Float((current_position + offset).y)); current_position += vector(float, 0.)
} else {
current_position = point(float, 0.);
}
*value = Value::Float(current_position.x + offset.x)
} }
} }
AbsoluteDistanceMode => { Token::Field(Field { letters, value }) if *letters == y => {
is_relative = false; if let Some(float) = value.as_f64() {
} if is_relative {
RelativeDistanceMode => { current_position += vector(0., float)
is_relative = true; } else {
current_position = point(0., float);
}
*value = Value::Float(current_position.y + offset.y)
}
} }
_ => {} _ => {}
} }
} }
} }
fn get_bounding_box(commands: &[Command]) -> (F64Point, F64Point) { fn get_bounding_box<'a, I: Iterator<Item = &'a Token>>(tokens: I) -> Box2D<f64> {
let (mut minimum, mut maximum) = (point(0f64, 0f64), point(0f64, 0f64)); let (mut minimum, mut maximum) = (point(0f64, 0f64), point(0f64, 0f64));
let mut is_relative = false; let mut is_relative = false;
let mut current_position = point(0f64, 0f64); let mut current_position = point(0f64, 0f64);
for command in commands { let x = "X".to_string();
match command.word() { let y = "Y".to_string();
AbsoluteDistanceMode => { let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD.clone());
is_relative = false; let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD.clone());
} for token in tokens {
RelativeDistanceMode => { match token {
is_relative = true; abs if *abs == abs_tok => is_relative = false,
rel if *rel == rel_tok => is_relative = true,
Token::Field(Field { letters, value }) if *letters == x => {
if let Some(value) = value.as_f64() {
if is_relative {
current_position += vector(value, 0.)
} else {
current_position = point(value, 0.);
}
minimum = minimum.min(current_position);
maximum = maximum.max(current_position);
}
} }
LinearInterpolation | RapidPositioning => { Token::Field(Field { letters, value }) if *letters == y => {
let x: f64 = (&command.get('X').unwrap().value).into(); if let Some(value) = value.as_f64() {
let y: f64 = (&command.get('Y').unwrap().value).into(); if is_relative {
if is_relative { current_position += vector(0., value)
current_position += vector(x, y) } else {
} else { current_position = point(0., value);
current_position = point(x, y); }
minimum = minimum.min(current_position);
maximum = maximum.max(current_position);
} }
minimum = minimum.min(current_position);
maximum = maximum.max(current_position);
} }
_ => (), _ => {}
} }
} }
(minimum, maximum) Box2D::new(minimum, maximum)
} }

@ -1,5 +1,8 @@
use crate::gcode::*;
use crate::machine::Machine; use crate::machine::Machine;
use g_code::{
command,
emit::{Field, Token, Value},
};
use lyon_geom::euclid::{default::Transform2D, Angle}; use lyon_geom::euclid::{default::Transform2D, Angle};
use lyon_geom::{point, vector, Point}; use lyon_geom::{point, vector, Point};
use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc}; use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc};
@ -9,18 +12,18 @@ type F64Point = Point<f64>;
/// Turtle graphics simulator for paths that outputs the gcode representation for each operation. /// Turtle graphics simulator for paths that outputs the gcode representation for each operation.
/// Handles transforms, position, offsets, etc. See https://www.w3.org/TR/SVG/paths.html /// Handles transforms, position, offsets, etc. See https://www.w3.org/TR/SVG/paths.html
#[derive(Debug)] #[derive(Debug)]
pub struct Turtle { pub struct Turtle<'input> {
current_position: F64Point, current_position: F64Point,
initial_position: F64Point, initial_position: F64Point,
current_transform: Transform2D<f64>, current_transform: Transform2D<f64>,
transform_stack: Vec<Transform2D<f64>>, transform_stack: Vec<Transform2D<f64>>,
pub machine: Machine, pub machine: Machine<'input>,
previous_control: Option<F64Point>, previous_control: Option<F64Point>,
} }
impl Turtle { impl<'input> Turtle<'input> {
/// Create a turtle at the origin with no transform /// Create a turtle at the origin with no transform
pub fn new(machine: Machine) -> Self { pub fn new(machine: Machine<'input>) -> Self {
Self { Self {
current_position: point(0.0, 0.0), current_position: point(0.0, 0.0),
initial_position: point(0.0, 0.0), initial_position: point(0.0, 0.0),
@ -30,12 +33,10 @@ impl Turtle {
previous_control: None, previous_control: None,
} }
} }
}
impl Turtle {
/// Move the turtle to the given absolute/relative coordinates in the current transform /// Move the turtle to the given absolute/relative coordinates in the current transform
/// https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands /// https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
pub fn move_to<X, Y>(&mut self, abs: bool, x: X, y: Y) -> Vec<Command> pub fn move_to<X, Y>(&mut self, abs: bool, x: X, y: Y) -> Vec<Token>
where where
X: Into<Option<f64>>, X: Into<Option<f64>>,
Y: Into<Option<f64>>, Y: Into<Option<f64>>,
@ -71,39 +72,38 @@ impl Turtle {
self.machine self.machine
.tool_off() .tool_off()
.iter() .drain(..)
.chain(self.machine.absolute().iter()) .chain(self.machine.absolute().drain(..))
.chain(std::iter::once(&command!(CommandWord::RapidPositioning, { .chain(
x : to.x as f64, command!(RapidPositioning {
y : to.y as f64, X: to.x as f64,
}))) Y: to.y as f64,
.map(Clone::clone) })
.as_token_vec(),
)
.collect() .collect()
} }
fn linear_interpolation(x: f64, y: f64, z: Option<f64>, f: Option<f64>) -> Command { fn linear_interpolation(x: f64, y: f64, z: Option<f64>, f: Option<f64>) -> Vec<Token> {
let mut linear_interpolation = command!(CommandWord::LinearInterpolation, { let mut linear_interpolation = command! {LinearInterpolation { X: x, Y: y, }};
x: x,
y: y,
});
if let Some(z) = z { if let Some(z) = z {
linear_interpolation.push(Word { linear_interpolation.push(Field {
letter: 'Z', letters: "Z".to_string(),
value: Value::Float(z), value: Value::Float(z),
}); });
} }
if let Some(f) = f { if let Some(f) = f {
linear_interpolation.push(Word { linear_interpolation.push(Field {
letter: 'F', letters: "F".into(),
value: Value::Float(f), value: Value::Float(f),
}); });
} }
linear_interpolation linear_interpolation.as_token_vec()
} }
/// Close an SVG path, cutting back to its initial position /// Close an SVG path, cutting back to its initial position
/// https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand /// https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
pub fn close<Z, F>(&mut self, z: Z, f: F) -> Vec<Command> pub fn close<Z, F>(&mut self, z: Z, f: F) -> Vec<Token>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -121,21 +121,20 @@ impl Turtle {
self.machine self.machine
.tool_on() .tool_on()
.iter() .drain(..)
.chain(self.machine.absolute().iter()) .chain(self.machine.absolute())
.chain(std::iter::once(&Self::linear_interpolation( .chain(Self::linear_interpolation(
self.initial_position.x, self.initial_position.x,
self.initial_position.y, self.initial_position.y,
z.into(), z.into(),
f.into(), f.into(),
))) ))
.map(Clone::clone)
.collect() .collect()
} }
/// Draw a line from the current position in the current transform to the specified position /// Draw a line from the current position in the current transform to the specified position
/// https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands /// https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
pub fn line<X, Y, Z, F>(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Vec<Command> pub fn line<X, Y, Z, F>(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Vec<Token>
where where
X: Into<Option<f64>>, X: Into<Option<f64>>,
Y: Into<Option<f64>>, Y: Into<Option<f64>>,
@ -172,15 +171,9 @@ impl Turtle {
self.machine self.machine
.tool_on() .tool_on()
.iter() .drain(..)
.chain(self.machine.absolute().iter()) .chain(self.machine.absolute())
.chain(std::iter::once(&Self::linear_interpolation( .chain(Self::linear_interpolation(to.x, to.y, z.into(), f.into()))
to.x,
to.y,
z.into(),
f.into(),
)))
.map(Clone::clone)
.collect() .collect()
} }
@ -193,13 +186,13 @@ impl Turtle {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Command> { ) -> Vec<Token> {
let z = z.into(); let z = z.into();
let f = f.into(); let f = f.into();
let last_point = std::cell::Cell::new(self.current_position); let last_point = std::cell::Cell::new(self.current_position);
let cubic: Vec<Command> = cbs let cubic: Vec<Token> = cbs
.flattened(tolerance) .flattened(tolerance)
.map(|point| { .flat_map(|point| {
last_point.set(point); last_point.set(point);
Self::linear_interpolation(point.x, point.y, z, f) Self::linear_interpolation(point.x, point.y, z, f)
}) })
@ -214,10 +207,9 @@ impl Turtle {
self.machine self.machine
.tool_on() .tool_on()
.iter() .drain(..)
.chain(self.machine.absolute().iter()) .chain(self.machine.absolute())
.chain(cubic.iter()) .chain(cubic)
.map(Clone::clone)
.collect() .collect()
} }
@ -235,7 +227,7 @@ impl Turtle {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Command> ) -> Vec<Token>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -277,7 +269,7 @@ impl Turtle {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Command> ) -> Vec<Token>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -315,7 +307,7 @@ impl Turtle {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Command> ) -> Vec<Token>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -347,7 +339,7 @@ impl Turtle {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Command> ) -> Vec<Token>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -384,7 +376,7 @@ impl Turtle {
z: Z, z: Z,
f: F, f: F,
tolerance: f64, tolerance: f64,
) -> Vec<Command> ) -> Vec<Token>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -419,7 +411,7 @@ impl Turtle {
let mut ellipse = vec![]; let mut ellipse = vec![];
arc.for_each_flattened(tolerance, &mut |point: F64Point| { arc.for_each_flattened(tolerance, &mut |point: F64Point| {
ellipse.push(Self::linear_interpolation(point.x, point.y, z, f)); ellipse.append(&mut Self::linear_interpolation(point.x, point.y, z, f));
last_point.set(point); last_point.set(point);
}); });
self.current_position = last_point.get(); self.current_position = last_point.get();
@ -427,10 +419,9 @@ impl Turtle {
self.machine self.machine
.tool_on() .tool_on()
.iter() .drain(..)
.chain(self.machine.absolute().iter()) .chain(self.machine.absolute())
.chain(ellipse.iter()) .chain(ellipse)
.map(Clone::clone)
.collect() .collect()
} }

@ -1,12 +1,11 @@
G21 G21
G94 G94
G90 G90
G0 X0 Y0 G0 X0 Y0;svg#svg8 > g#layer1 > path#path838
;svg#svg8 > g#layer1 > path#path838 G0 X1 Y9
G0 X1 Y9 G1 X9 Y9 F300
G1 X9 Y9 F300 G1 X9 Y1 F300
G1 X9 Y1 F300 G1 X1 Y1 F300
G1 X1 Y1 F300 G1 X1 Y9 F300
G1 X1 Y9 F300 G0 X0 Y0
G0 X0 Y0
M20 M20

@ -1,12 +1,11 @@
G21 G21
G94 G94
G90 G90
G0 X0 Y19 G0 X0 Y19;svg#svg8 > g#layer1 > path#path838
;svg#svg8 > g#layer1 > path#path838 G0 X9 Y8
G0 X9 Y8 G1 X9 Y0 F300
G1 X9 Y0 F300 G1 X1 Y0 F300
G1 X1 Y0 F300 G1 X0.9999999999999982 Y8 F300
G1 X0.9999999999999982 Y8 F300 G1 X9 Y8 F300
G1 X9 Y8 F300 G0 X0 Y19
G0 X0 Y19
M20 M20

Loading…
Cancel
Save