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(),
} }
} }
/// 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 Machine { 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()
pub fn program_end(&self) -> Vec<Command> { .flat_map(|s| s.iter_fields())
self.program_end_sequence.clone() .map(|f: &Field| Token::from(f))
.collect()
}
pub fn program_end<'a>(&'a self) -> Vec<Token> {
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!())
(author: crate_authors!())
(about: crate_description!())
(@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") { let opt = Opt::from_args();
let input = match opt.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 = [
opt.tool_on_sequence.as_ref().map(parse_snippet).transpose(),
opt.tool_off_sequence
.as_ref()
.map(parse_snippet)
.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.tolerance = tolerance; Machine {
tool_on_action,
tool_off_action,
program_begin_sequence,
program_end_sequence,
tool_state: None,
distance_mode: None,
} }
if let Some(feedrate) = matches } else {
.value_of("feedrate") use codespan_reporting::term::{
.map(|feedrate| feedrate.parse().expect("could not parse tolerance")) emit,
termcolor::{ColorChoice, StandardStream},
};
let mut writer = StandardStream::stderr(ColorChoice::Auto);
let config = codespan_reporting::term::Config::default();
for (i, (filename, gcode)) in [
("tool_on_sequence", &opt.tool_on_sequence),
("tool_off_sequence", &opt.tool_off_sequence),
("begin_sequence", &opt.begin_sequence),
("end_sequence", &opt.end_sequence),
]
.iter()
.enumerate()
{ {
options.feedrate = feedrate; 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();
} }
if let Some(dpi) = matches }
.value_of("dpi") std::process::exit(1)
.map(|dpi| dpi.parse().expect("could not parse tolerance")) };
{
options.dpi = dpi;
}
let machine = machine::Machine::new(
matches
.value_of("tool_on_sequence")
.map(gcode::parse_gcode)
.unwrap_or_default(),
matches
.value_of("tool_off_sequence")
.map(gcode::parse_gcode)
.unwrap_or_default(),
matches
.value_of("begin_sequence")
.map(gcode::parse_gcode)
.unwrap_or_default(),
matches
.value_of("end_sequence")
.map(gcode::parse_gcode)
.unwrap_or_default(),
);
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 {
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(float) = value.as_f64() {
if is_relative { if is_relative {
current_position += vector(x, y); current_position += vector(float, 0.)
} else { } else {
current_position = point(x, y); current_position = point(float, 0.);
command.set('X', Value::Float((current_position + offset).x)); }
command.set('Y', Value::Float((current_position + offset).y)); *value = Value::Float(current_position.x + offset.x)
}
} }
Token::Field(Field { letters, value }) if *letters == y => {
if let Some(float) = value.as_f64() {
if is_relative {
current_position += vector(0., float)
} else {
current_position = point(0., float);
} }
AbsoluteDistanceMode => { *value = Value::Float(current_position.y + offset.y)
is_relative = false;
} }
RelativeDistanceMode => {
is_relative = true;
} }
_ => {} _ => {}
} }
} }
} }
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 {
match token {
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);
} }
RelativeDistanceMode => {
is_relative = true;
} }
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(x, y) current_position += vector(0., value)
} else { } else {
current_position = point(x, y); current_position = point(0., value);
} }
minimum = minimum.min(current_position); minimum = minimum.min(current_position);
maximum = maximum.max(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,8 +1,7 @@
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

@ -1,8 +1,7 @@
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

Loading…
Cancel
Save