Comparar commits
1143 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 1730923083 | |||
| 8e5a936522 | |||
| d29da8f7f8 | |||
| ad56ce8866 | |||
| 565b09e37d | |||
| edc9767912 | |||
| ce6f58f136 | |||
| 5b941d07ff | |||
| 350b42052a | |||
| 0ba152d081 | |||
| 2c70c2ecb7 | |||
| 4da8c6d8e7 | |||
| c5ce425924 | |||
| c74d4f7550 | |||
| 19f093e0cc | |||
| 2a4cca0402 | |||
| 32804c102c | |||
| 776f56fc37 | |||
| a92cd23e62 | |||
| 4725001564 | |||
| a18595877f | |||
| 40e7c450d5 | |||
| 13a0b11d68 | |||
| 4fc69f608a | |||
| deb577d559 | |||
| c8161da3eb | |||
| 87733834d1 | |||
| b9aeea425f | |||
| 44b3039db6 | |||
| 6476270cf6 | |||
| e532f3ddda | |||
| 7aaab151e7 | |||
| 0353433919 | |||
| 40a6c495c7 | |||
| cfa6758911 | |||
| f6b29b1d8a | |||
| bd79a8468e | |||
| 35dcc6906f | |||
| db7bc279ee | |||
| a7ea623266 | |||
| 724589c13a | |||
| db1fa098ad | |||
| be99db30c0 | |||
| 9456592dd7 | |||
| 20cd4275fc | |||
| 67697d5258 | |||
| 7d8e4de7ba | |||
| 2486120a38 | |||
| 43c8f2a913 | |||
| dd8b57b666 | |||
| 72ed492a61 | |||
| c02482590f | |||
| f32e2d9554 | |||
| bf61814849 | |||
| 5741e79ade | |||
| 09419fd6be | |||
| a002f4b3b2 | |||
| 25c7ccb0d3 | |||
| 58a99a403b | |||
| dd8594b200 | |||
| 6df5368cb9 | |||
| 3a038a2cd2 | |||
| 9613e186a3 | |||
| 82b2362b2d | |||
| a6ee34602c | |||
| 05d29eabcd | |||
| f7e3534a79 | |||
| bd4972e466 | |||
| 0acf3beec2 | |||
| 081055be46 | |||
| 560d23d3f2 | |||
| 44e7b0e0be | |||
| 8c48d1ce82 | |||
| 8432e9ed9f | |||
| 1b014199c9 | |||
| 7e0c5c6323 | |||
| 9ada0901a6 | |||
| d60d32a5d8 | |||
| 667c00aa5a | |||
| cf0ca05843 | |||
| 3bcabacef4 | |||
| b5801ea847 | |||
| 1bb90a2f03 | |||
| cf55aef52b | |||
| c0f7538fdb | |||
| 395ab9b7cd | |||
| 80ecabb618 | |||
| ff291cc4d1 | |||
| 4183835d2b | |||
| 0e24d8db47 | |||
| f7bde54869 | |||
| bf8f027a97 | |||
| 60625ea4bf | |||
| a9f6ae1943 | |||
| cd01d7051b | |||
| bd126bb841 | |||
| f1b3fb3cd6 | |||
| d48f06db0e | |||
| 5567b06ed4 | |||
| e87b11179f | |||
| a17fef9d4b | |||
| 4c49bf7894 | |||
| bcb0ded784 | |||
| 54ba25e7f5 | |||
| 84a7f401a2 | |||
| 6afdf19ce4 | |||
| 4aa1cc47f4 | |||
| 166473596a | |||
| feb21b67d1 | |||
| dae7ec184f | |||
| cd170cae99 | |||
| d47d0a2e46 | |||
| f0d59db203 | |||
| f34a4e566b | |||
| 9c1ef177d1 | |||
| e1c7e86b66 | |||
| 79d1074d47 | |||
| 348ca93dcb | |||
| e70b3c099f | |||
| 8c9858a14c | |||
| 6e56cb3416 | |||
| 8761ae1e53 | |||
| d32be27b50 | |||
| 11b33215a2 | |||
| e8a31f1380 | |||
| 7cbfb22f1f | |||
| 40e6aac22b | |||
| 33cc601a1b | |||
| d50b0c9c2f | |||
| 77f415be61 | |||
| 1aa784e521 | |||
| ef8258da2f | |||
| 79692200b0 | |||
| 966912ccc5 | |||
| 1513dcad5c | |||
| 41050ac835 | |||
| fb8625756f | |||
| 886bab29f7 | |||
| 5bdf538d35 | |||
| 7d5040e283 | |||
| a49ed9a90e | |||
| 7a4e732f3b | |||
| 653ce82255 | |||
| 0b1df81945 | |||
| f65993c5b6 | |||
| df91b3d8aa | |||
| 4b58ab0ad9 | |||
| 9cbdfd2152 | |||
| 4a0a0088fe | |||
| dc37384ffd | |||
| 2a21e6c348 | |||
| 30011a5285 | |||
| 965b28e009 | |||
| 1b37007003 | |||
| 5cf0bcd399 | |||
| 0930d61dad | |||
| a181977ce9 | |||
| 3ab19f2f29 | |||
| c4a3830838 | |||
| d546c27462 | |||
| 7664e81aa2 | |||
| aa2ce7c770 | |||
| f63fc1ed74 | |||
| ac183f4e4b | |||
| 4f3c337cc7 | |||
| 1e24769061 | |||
| 02134eb822 | |||
| b436da841a | |||
| fa95e56b76 | |||
| 6bed19c295 | |||
| 2a20853b92 | |||
| 09ad6301e7 | |||
| d8fa29e979 | |||
| ab91cc2def | |||
| cd4e2e29a3 | |||
| 0f454e897b | |||
| c1d53811f3 | |||
| dbe6269262 | |||
| 0c395c5bfa | |||
| f312f15813 | |||
| 406ea7155c | |||
| f0f23ed732 | |||
| 0a59a059f4 | |||
| e902fb3fc9 | |||
| c7be93e850 | |||
| fe8207fb1b | |||
| d1fbe96d6f | |||
| ccc89e6263 | |||
| d235de6ca2 | |||
| 4d98e07eb4 | |||
| 7c3f69a5bf | |||
| 846bb1109a | |||
| 8c5ba49068 | |||
| 8e18139ae9 | |||
| 410d42ccb3 | |||
| 2c35ba595c | |||
| 8f04afbff9 | |||
| 1fd5355290 | |||
| 65b6c48391 | |||
| 34c0960cc2 | |||
| d6175e8269 | |||
| 47984afb5f | |||
| c86823033d | |||
| 6c157b4e1f | |||
| 97b8d63547 | |||
| f0536be401 | |||
| 2c983ce39c | |||
| a3eb8ebb2d | |||
| bb98cb983e | |||
| a67f7465c1 | |||
| 9b8ef3981a | |||
| 24a22eec82 | |||
| 4d34cbc66b | |||
| 405a2b2a5f | |||
| 17260b0272 | |||
| 2785baf7be | |||
| c4bc6b8d19 | |||
| a29af7f791 | |||
| f304c09db4 | |||
| d50f67e583 | |||
| b2d837efde | |||
| 5c680b312b | |||
| 0a683ff05e | |||
| 0dc6dcbb34 | |||
| dbb48c82bf | |||
| 2a1e4da930 | |||
| dd481bcc2e | |||
| f68d1a4e2d | |||
| 9d56baba41 | |||
| 4e96d9cbe6 | |||
| 00041da2df | |||
| 478bd31d02 | |||
| faab907bab | |||
| dff554863c | |||
| f2222f97fd | |||
| aa18e7bd08 | |||
| 07dc53765a | |||
| 559deb8914 | |||
| 421ff6d818 | |||
| e85a1004cd | |||
| 627848bc41 | |||
| eccc62cf1d | |||
| 1d251fef70 | |||
| 16a0cf0fd5 | |||
| 3c7c352bfc | |||
| 66e6375942 | |||
| 8d3f66c2cb | |||
| 1fd540c231 | |||
| ac799dd00e | |||
| 699406b044 | |||
| 2f3333e87c | |||
| 58e2e1a4e1 | |||
| 4ebbceb3e3 | |||
| c0495edf48 | |||
| ace0045109 | |||
| 1574292945 | |||
| ed3f9f107c | |||
| 7a4a7fe46a | |||
| 18bf08a1b5 | |||
| 977d02a9a2 | |||
| f30c309249 | |||
| af3536b307 | |||
| 8aed4c9139 | |||
| 03fa1c2d37 | |||
| 7582100fba | |||
| edd1d5c8dd | |||
| 5971604805 | |||
| a8ea6511bd | |||
| 5c1083db0b | |||
| f9b917f9d1 | |||
| eb065c1a07 | |||
| 29978b8654 | |||
| 9563aeb1c0 | |||
| fb7cd6a572 | |||
| 3f02cf1f33 | |||
| 7bdd5690f7 | |||
| 1de4107ea6 | |||
| 6306321007 | |||
| 7281e2187e | |||
| 5f99281478 | |||
| 3d3c12a9d2 | |||
| 739aedf8ad | |||
| 1bb79f850a | |||
| 3d77a426dd | |||
| 3aa3e4ef92 | |||
| eb5231e7a8 | |||
| 17a9764181 | |||
| 86e7dbf2b1 | |||
| ccaf8538d8 | |||
| a248662077 | |||
| fc28698479 | |||
| 263bb3099b | |||
| 25b83c9dda | |||
| 7025e5cea4 | |||
| 183fc38ca9 | |||
| f4461ca41a | |||
| 5ba9026632 | |||
| 01ec0b847f | |||
| 9fe8796844 | |||
| c554ec4178 | |||
| dce8a4629f | |||
| 10af83137b | |||
| d45d00b441 | |||
| b2abcbd7b4 | |||
| 9c15d18cfb | |||
| 7122335a55 | |||
| b5b05c1894 | |||
| 8d6dd5ae8b | |||
| 433c90617e | |||
| 2980b20346 | |||
| 5993b5f606 | |||
| f7940498f0 | |||
| 03acc7629c | |||
| f903d438f2 | |||
| 04dc8ac68b | |||
| 1a2ba63c74 | |||
| d28d86c208 | |||
| afd91bfd2e | |||
| 4cd55f0907 | |||
| f20320031f | |||
| 44f7b92958 | |||
| 0b4eda9592 | |||
| c674a84f38 | |||
| 2660bd1bc1 | |||
| 618660c16f | |||
| 252a797f36 | |||
| 2db70b4b5c | |||
| 379a3d54f1 | |||
| 92fca17411 | |||
| 87637c9ef7 | |||
| d2bbe41ff1 | |||
| a70fdfb917 | |||
| ceff626d87 | |||
| 53c4552de7 | |||
| a576dd8929 | |||
| dcec2d2584 | |||
| 6c2d7b72cb | |||
| 228b13c96c | |||
| 4607d0a8ad | |||
| cec7d2bef4 | |||
| 1eee21a16c | |||
| e52beb8239 | |||
| 72f09d3fa0 | |||
| 9ba3e301d6 | |||
| 4bea7d8ba2 | |||
| b03a7bd9ee | |||
| 1f5071c739 | |||
| a165265c0a | |||
| 7ba5b5c0c8 | |||
| adcd061f00 | |||
| f6df79a134 | |||
| 955399765c | |||
| b79043f772 | |||
| 1380d76075 | |||
| a2c5a73f09 | |||
| 1bdf07ae88 | |||
| ca1d6082e6 | |||
| 33eabda16f | |||
| da659759cf | |||
| 5b99143761 | |||
| dc92c1376c | |||
| beaf71dfe8 | |||
| 3062c07763 | |||
| 134b37da2e | |||
| af10ef571d | |||
| 92af4b76b7 | |||
| d97cfa4152 | |||
| 9988c67397 | |||
| b7f77e9dca | |||
| ca25ce076c | |||
| 71bfafbc0f | |||
| ec44e711fb | |||
| 8fd5c6d404 | |||
| 9da278795f | |||
| 2050e71dda | |||
| df003d7649 | |||
| 281e647b82 | |||
| 0771eafc60 | |||
| 0743994375 | |||
| 7b9e571474 | |||
| 73506345ac | |||
| ff57c578db | |||
| 0436916f4a | |||
| 4a392d1e79 | |||
| eb277a1181 | |||
| 7b972305b5 | |||
| 103b2ef7a8 | |||
| a4bb8ed287 | |||
| 8edce9d470 | |||
| 54152aded7 | |||
| 771e840d07 | |||
| 1057b1e277 | |||
| 26b78d68f4 | |||
| 2583c2e594 | |||
| 9c2fb686e6 | |||
| 7396deb65b | |||
| 32f234eb87 | |||
| adbf0354e6 | |||
| 5e7acc8b62 | |||
| b1044cbe24 | |||
| 91a5589413 | |||
| dc97b5f312 | |||
| b494b7b86d | |||
| 970789fb4b | |||
| f4e19dd217 | |||
| 700ce8ef3b | |||
| df3426e8bb | |||
| fd73470580 | |||
| 959664c080 | |||
| 3e9b42a2a2 | |||
| eb74c5ce88 | |||
| 51b840caee | |||
| d695344f1b | |||
| 0e9a7154ca | |||
| 72e4bf8069 | |||
| acf26ba2f8 | |||
| 347f5c6238 | |||
| aebe1ef46c | |||
| 4f073ab23f | |||
| 62dd1c1575 | |||
| 04df283704 | |||
| 695e5dd982 | |||
| 26757ef802 | |||
| baf8f49360 | |||
| 4b20528a34 | |||
| 487b990f46 | |||
| 27d940191e | |||
| 7c48706e80 | |||
| 09a3ec4790 | |||
| b1e602f9f2 | |||
| f98836c279 | |||
| b54a0d3b91 | |||
| a3f2d08f13 | |||
| 2b6c70d19c | |||
| b79f362196 | |||
| 535bb8cf66 | |||
| eb2d4d3ad1 | |||
| a8152bb727 | |||
| 3e0a2ca48a | |||
| 2dc4411702 | |||
| 0d38105a1a | |||
| c179d16a9e | |||
| 76ac083eb3 | |||
| da998384f0 | |||
| 13f7e0d7e4 | |||
| 1f721a2513 | |||
| 4df0a27cb2 | |||
| 0c6aca19aa | |||
| bd8c501dc3 | |||
| b36acb07c5 | |||
| 492bf72ffd | |||
| f7ef517f01 | |||
| 7a015ea146 | |||
| 48aaebc20d | |||
| 209f770cf4 | |||
| ec6c1216f2 | |||
| c638ca822b | |||
| 9007e3ff50 | |||
| 32ce5e5a44 | |||
| 825882100f | |||
| a47aa1e4fd | |||
| f5f1886175 | |||
| 26d05dc5e6 | |||
| 2715599791 | |||
| 3c6b153e60 | |||
| 9bfdeb4ccd | |||
| 3aaddb08ba | |||
| 752985c9b2 | |||
| dcb8a03a73 | |||
| 028c408be3 | |||
| 822a2d55f2 | |||
| 3e283a375e | |||
| 635ef25030 | |||
| 9a9855c0a5 | |||
| 83e52f6e53 | |||
| 9d59dde760 | |||
| 9b40d6053c | |||
| ba05f42c91 | |||
| 5684a0cbb4 | |||
| 0647fa21fe | |||
| cb5467d2b3 | |||
| 24ec8bb254 | |||
| 8069aba0b5 | |||
| 591cd105b5 | |||
| 13e5e786bc | |||
| 6f0ef96c5e | |||
| ed636f9a44 | |||
| 8f50db989c | |||
| f4e22ae7a0 | |||
| cbc6e6f270 | |||
| 07b8165d47 | |||
| e2409148cc | |||
| a3129bf1f6 | |||
| f191497234 | |||
| ef9c477ee2 | |||
| bdcaa6c9ac | |||
| 046310efcb | |||
| 74133ae7d7 | |||
| 20dff1fe8b | |||
| c7be6e147b | |||
| 574739e193 | |||
| b3c0f0d2da | |||
| 33be01596c | |||
| 2cbd6d1c8a | |||
| 510c328127 | |||
| 9413aceaf2 | |||
| aee1887b57 | |||
| b72917305c | |||
| 7d61f00b3a | |||
| 247f5d3cde | |||
| 21202c2e8c | |||
| 6973e99cd1 | |||
| 8cf1f1646b | |||
| 2a1fc8245a | |||
| a1a3931f68 | |||
| e3dd8f0bdf | |||
| 3a06f49fba | |||
| 65e90d5de0 | |||
| e33c441f6d | |||
| 418de5d822 | |||
| a6b45b683e | |||
| eda2c987ff | |||
| a71c18a146 | |||
| 424e3edb4d | |||
| ad1c442c9a | |||
| 65ced4a4bb | |||
| 242e8118e3 | |||
| f720cc1f0b | |||
| 721d7d799f | |||
| 26c3a35150 | |||
| c1d097f7fe | |||
| fedcfe582e | |||
| 1ffafd4b17 | |||
| 45c4e2e0a1 | |||
| 6ae7fdac5c | |||
| bf2123b82c | |||
| 81881a876d | |||
| 34859670ef | |||
| 4038d39e85 | |||
| 999a131ecf | |||
| 2bcffaa0ab | |||
| 7f584c2e4f | |||
| ba045e803b | |||
| 1fd2c66ec7 | |||
| be7ea53325 | |||
| 8045fb49ff | |||
| 1504e38c8c | |||
| b646e94880 | |||
| 929719ebdd | |||
| 905d788c3c | |||
| 0dba144c37 | |||
| f7ba6979da | |||
| ae832cba68 | |||
| 6bdc8dae51 | |||
| 9a23163564 | |||
| fcc207ad0d | |||
| 44df11bc2c | |||
| 2458a9a580 | |||
| ba92bf9693 | |||
| 8c1ecf1200 | |||
| 4fdcf4ed29 | |||
| cde315109a | |||
| 50ca8d7473 | |||
| 9b8b3d150c | |||
| f4fd6ab0f4 | |||
| b593331c17 | |||
| afb6237cbf | |||
| a716b88621 | |||
| 0bc384c807 | |||
| a2e4f2a1cb | |||
| d3c62049eb | |||
| e773029266 | |||
| c42b3322cd | |||
| b652db33c1 | |||
| 596082dc7c | |||
| 411300e0e1 | |||
| b44116c4e5 | |||
| bf0cd02928 | |||
| 3b4c54839d | |||
| b6cb7cd271 | |||
| ee447a9191 | |||
| 9d755211cd | |||
| db83fcd57e | |||
| 2ccaef10b9 | |||
| d004c77106 | |||
| 2bddfcd42a | |||
| 4cd1b5066c | |||
| e0959e4aa0 | |||
| a29dcb33da | |||
| 152cf5835f | |||
| eb065aa415 | |||
| 9430432af7 | |||
| 252c2cae28 | |||
| 9ee30bba12 | |||
| 3afef5d29f | |||
| 056240d55c | |||
| 10b43fc9f8 | |||
| f83fbfe143 | |||
| bcfe400c18 | |||
| b67b51ce76 | |||
| 83aeb36bcf | |||
| e75cdf3f7c | |||
| ecd11212f4 | |||
| 5763b1704f | |||
| f14d67934d | |||
| 9ef35e2b14 | |||
| fadfaa5a80 | |||
| fc962709b0 | |||
| a848b8c71d | |||
| dc05c2749a | |||
| cea312a23a | |||
| deee23d1f8 | |||
| d097cfa78f | |||
| 09c1df7ea6 | |||
| 7f9e034dbb | |||
| 05b5a0b45c | |||
| ca1a076db3 | |||
| 36c2bd81d0 | |||
| d44c8eb067 | |||
| 2b81ce1dd5 | |||
| 73df32881a | |||
| 01357eacc4 | |||
| fc7c55768a | |||
| fea300cfc3 | |||
| 4f4a5dd1e7 | |||
| 02fe987035 | |||
| 9e7b6212b0 | |||
| 94a3a6b642 | |||
| d7eef238ce | |||
| 3cbfe3b037 | |||
| 9b8917e763 | |||
| a5b7b474a6 | |||
| 839fc30c91 | |||
| b8a6888d94 | |||
| 14cb8eeabd | |||
| f9f79fa0e8 | |||
| 691efb3b6e | |||
| 93be9431b7 | |||
| 6c757f3fcf | |||
| ae66df7b0a | |||
| d447d9d711 | |||
| b30f1c1a2e | |||
| 2ff1c876b3 | |||
| 6db1097faa | |||
| 86cbe1797b | |||
| 65f0ba9673 | |||
| 7d6df9746e | |||
| 2d2196648f | |||
| 6992bb6e03 | |||
| dee2f82d87 | |||
| bec685e004 | |||
| d38ccb7691 | |||
| e5036763fe | |||
| 865f97c5d7 | |||
| 0b4b04f31a | |||
| 39fea42b92 | |||
| f810356bdb | |||
| 4c41acd946 | |||
| af135fff49 | |||
| ed4457a3f4 | |||
| 7ea9b14a33 | |||
| 957c56c6b2 | |||
| 0a689a0da8 | |||
| 39efcf5680 | |||
| 0bc373e935 | |||
| 0c82c00e15 | |||
| 1859de69d4 | |||
| e2fc79889b | |||
| b31646f10d | |||
| 10a8265674 | |||
| 1445472530 | |||
| 37f7e18269 | |||
| a854dc692d | |||
| 9cbecf5782 | |||
| b5575093d0 | |||
| 368747b213 | |||
| 64d3ee9728 | |||
| 0ba0188bcd | |||
| b8691c776e | |||
| afde126052 | |||
| 5c22649d7b | |||
| 1e0f2ec7c1 | |||
| c0d59627eb | |||
| 836686b90c | |||
| 4d7dd3adce | |||
| 14013e657a | |||
| 2a1cb236c8 | |||
| 0d17a056a2 | |||
| 722456ba64 | |||
| ce1fe33b2e | |||
| df3c77d0ce | |||
| 39e1c5ffd9 | |||
| af31f141d8 | |||
| 5d7687228c | |||
| ffb4cdaacd | |||
| ce10cdecc7 | |||
| d7806994be | |||
| 6d5762bc59 | |||
| f96f189d0a | |||
| d9605a4a53 | |||
| 0238d1822c | |||
| d1f38c2be0 | |||
| 0df427bc7a | |||
| 6328aa7210 | |||
| 2d761096b8 | |||
| 1a8b4c6655 | |||
| cdb451b679 | |||
| 7aa157c955 | |||
| 52f71823f4 | |||
| 7522ca3ddc | |||
| b502a74a28 | |||
| 3f131e40b9 | |||
| 9b1a8fab73 | |||
| 7e5538b65e | |||
| 1f60e35bd2 | |||
| 70b3da26b9 | |||
| ecdfbd89ed | |||
| 134e52b15f | |||
| 5722a3e086 | |||
| e6adbd2f1d | |||
| 533d00cc3a | |||
| 586e0031d5 | |||
| 3ab6beb239 | |||
| 1da383d0d0 | |||
| 479742cea3 | |||
| e2c206e8ee | |||
| f58c506c6a | |||
| da2a329db4 | |||
| ecb7ba8904 | |||
| a84bb21d10 | |||
| d41c2c6ac2 | |||
| 73f9b09951 | |||
| ac0b15eb93 | |||
| 27ab0658df | |||
| 759eb0ab90 | |||
| 2099302a3b | |||
| 8365cb070d | |||
| 4121b3da1e | |||
| 931df895c6 | |||
| 5b20d02c11 | |||
| a702fa711d | |||
| eeb741bfb6 | |||
| 5942bb3d4c | |||
| dd3007a61e | |||
| 0e0119a3e6 | |||
| ef38ed3688 | |||
| 142464ebeb | |||
| 1d54240c32 | |||
| 25384faeed | |||
| 610685e13b | |||
| ae59b2b8d8 | |||
| fe184f9177 | |||
| bce51f4807 | |||
| e371c43395 | |||
| b3f80cec83 | |||
| e34f2ffc67 | |||
| 0f0bd3c32c | |||
| 71beb1cc98 | |||
| bc56e73e36 | |||
| ea364c4cd8 | |||
| d581957308 | |||
| 11c611c9e4 | |||
| 5ab937ba03 | |||
| aa5c9d37fd | |||
| 9de3edee39 | |||
| 21fa313feb | |||
| 6fb61a3c79 | |||
| bc2efcfc39 | |||
| cb40a72eda | |||
| 096e8f7675 | |||
| 87e02722e7 | |||
| d15387905b | |||
| 5280c9df10 | |||
| 021c7ac0bf | |||
| 3825e200a6 | |||
| 6022e44dee | |||
| b0054e1b39 | |||
| 7a1ee0429e | |||
| fddaa58d67 | |||
| 0ff87b785f | |||
| 71df0e2586 | |||
| 53c8cc5e78 | |||
| aa87a720c0 | |||
| 6e171f795c | |||
| e76cda3ea8 | |||
| 8794688c48 | |||
| 957c82715d | |||
| c0813b2c77 | |||
| 3042483f73 | |||
| 3abc718240 | |||
| a52208b15e | |||
| edd44de9e3 | |||
| d4b1c70790 | |||
| 534e265fd2 | |||
| 120de96bb5 | |||
| a611739ff7 | |||
| 791e436222 | |||
| d16f52e776 | |||
| a99020e995 | |||
| 5237b1bb01 | |||
| 7e0d44cfcb | |||
| 453b3ebbc5 | |||
| 473b925814 | |||
| 4199fcf8ac | |||
| 203f04571c | |||
| 186472c26e | |||
| dc2b505274 | |||
| 9adc0a128c | |||
| e6a5eccf03 | |||
| 71ea91b61b | |||
| 00d6a55836 | |||
| 4308c74451 | |||
| 757df8c7c8 | |||
| ff5cc9399d | |||
| ddf7bbe06e | |||
| e34608442a | |||
| c0446f81e0 | |||
| 27ac98caa5 | |||
| ebcfad8200 | |||
| 69665a0d4a | |||
| a50d6fe5a9 | |||
| bed556a5e6 | |||
| a86c5b1105 | |||
| 3db1c2580f | |||
| 2925af497c | |||
| 0bd5ae9e01 | |||
| eff7f0e326 | |||
| a6ad786443 | |||
| ce85280971 | |||
| a7bc153db5 | |||
| 3d98bb7e74 | |||
| cc632cd201 | |||
| b0ee1ca57d | |||
| e7992fc17a | |||
| 3f2162d76c | |||
| 019116cd33 | |||
| b0f725c1e5 | |||
| 17c125d395 | |||
| d0d0532cb8 | |||
| 563be4980d | |||
| 4a91663bb4 | |||
| f486d6be6f | |||
| 945711f10a | |||
| 0ae670ce0c | |||
| 2db4730f00 | |||
| 09b36ecdd8 | |||
| 5e83d4252d | |||
| a69270e212 | |||
| d197c7f3d7 | |||
| c82a8c5a47 | |||
| 288f053abd | |||
| f73586190e | |||
| 0fd0dd6869 | |||
| dee0ae12d6 | |||
| c2571d41af | |||
| c8fd58376b | |||
| bce0a8a3a8 | |||
| 25c2f7086d | |||
| 941f4e3a66 | |||
| 6d8d453df9 | |||
| 90c212b52e | |||
| db79eb7ece | |||
| 9afcbddf29 | |||
| c4c221145e | |||
| 1956fc00d3 | |||
| 8ac6c3b597 | |||
| 76b3c8757d | |||
| c6b6688170 | |||
| d2c80127ef | |||
| 3ada6f266c | |||
| 4b5553019e | |||
| 4fd499f7a1 | |||
| 28aedf57c5 | |||
| 28e6a54961 | |||
| b1b49f16e0 | |||
| 6b72d52c65 | |||
| 07d416691a | |||
| 960c56a9fa | |||
| a18b05f347 | |||
| 4d46fbea8f | |||
| 5620abfd95 | |||
| 3d307112de | |||
| 2c5639097e | |||
| cbf5d962b7 | |||
| 744264481a | |||
| 2cd22bdfd5 | |||
| c259d90def | |||
| a7b3d53872 | |||
| 57219beb98 | |||
| 8f0c3c9a49 | |||
| 6adfdd7ad4 | |||
| 59b516702e | |||
| 78c155b1ab | |||
| f39a3e62e7 | |||
| 00bcbe7192 | |||
| ab850577b5 | |||
| 307551b1db | |||
| 9a09cdc18a | |||
| dfcc993034 | |||
| 65df6439fa | |||
| 43a51f5757 | |||
| 96a2ac56c5 | |||
| 63cd06ca02 | |||
| 39da22630f | |||
| 3528b9eca7 | |||
| 8d7827aa87 | |||
| c73084bbd1 | |||
| bf372cb3aa | |||
| 0b8c30616a | |||
| a7720bcfbf | |||
| bf19300def | |||
| 9a34e09806 | |||
| 62a90f86c6 | |||
| f3b6042243 | |||
| 795d68d142 | |||
| c8e9091764 | |||
| 4b43c96cbc | |||
| 4ba0ae104e | |||
| f99c8b86cc | |||
| 5c7862287f | |||
| a2c8311c68 | |||
| 018f952a67 | |||
| b05928fb52 | |||
| 8b5f2f8ff8 | |||
| 50d92ff91e | |||
| e3c0439575 | |||
| a0f624992d | |||
| a41a0285f0 | |||
| dbeab1ec05 | |||
| b81b956965 | |||
| db2462882c | |||
| cc0f0918d3 | |||
| 3d93407b61 | |||
| ead062757a | |||
| 35b1a42cf3 | |||
| 420508099a | |||
| 7d1586ec4c | |||
| cc40088b3e | |||
| e28f64d36d | |||
| 0357102399 | |||
| bdd1a5c4ed | |||
| d9b47a8c96 | |||
| 507aa1c9d9 | |||
| c2a0dd952c | |||
| edc4e3e57b | |||
| cbf7960e08 | |||
| 74e52455fa | |||
| fc7a60b34a | |||
| 6fa82e044b | |||
| 8dbfb70c67 | |||
| 513d1347ca | |||
| 97d895b0a3 | |||
| ac1087dda3 | |||
| f23a91f0aa | |||
| dd7fb1941f | |||
| 54f7a45f65 | |||
| 7d7ca8ae4a | |||
| 9ab0839b4a | |||
| 844bfd34ab | |||
| 7a87848562 | |||
| 69a62a74ca | |||
| 162f39e3fb | |||
| 7fa436de51 | |||
| 1ef7ae46b2 | |||
| 8b9695b913 | |||
| 5879a3cdaa | |||
| 2236b3afeb | |||
| ed283a978b | |||
| 9f44d8362a | |||
| 7e12817974 | |||
| 93bf338ff2 | |||
| 48c6b2a058 | |||
| e132db6142 | |||
| a0536af848 | |||
| 8e8c7fcfc9 | |||
| 6d672cc840 | |||
| 9bf8dbbc96 | |||
| c1d4ba89d2 | |||
| 58269b363c | |||
| a2f6e62795 | |||
| 1e1a7e4f95 | |||
| b035b47526 | |||
| 53eda52041 | |||
| 52331d0c83 | |||
| 5cc85d3bf6 | |||
| 7cf21e9926 | |||
| 26d506a0f4 | |||
| 607c21ee46 | |||
| 402ede10d4 | |||
| bdd86ab9aa | |||
| ab3b1c2320 | |||
| 85e2ecc87e | |||
| 82846c0cc2 | |||
| 09015e188a | |||
| 36dbcfc667 | |||
| 5db83799d2 | |||
| 9cb04d9e7a | |||
| 1c966cb6a5 | |||
| f4d7695700 | |||
| 2288d2da49 | |||
| 5d5a02739c | |||
| 9248c17ace | |||
| 7417e7d674 | |||
| 58228ff9a6 | |||
| 329199af15 | |||
| 2aa67efa20 | |||
| 7b79debf9a | |||
| 1314f00713 | |||
| a407ea6b61 | |||
| 02e22b9442 | |||
| 5062a985fe | |||
| 5294ea508c | |||
| 286a6edaa4 | |||
| 6af8a8eb85 | |||
| ee38f51af8 | |||
| 7428a47344 | |||
| b859327bbd | |||
| 0a02a140fb | |||
| 5d2d1fe6f3 | |||
| ed2cdfa67a | |||
| 6d22cceac5 | |||
| 8a9f7dbf2b | |||
| aa5bc6385b | |||
| 764d9868c7 | |||
| 5153022d28 | |||
| 7d87d2baea | |||
| 14518007a4 | |||
| 64f69d7629 | |||
| c93facc43c | |||
| c2144cfe1c | |||
| ca2b4af6f7 | |||
| 4d429eb719 | |||
| e76109697e | |||
| 213b67528a | |||
| 162d9aa0f7 | |||
| 1c8f859885 | |||
| db4c9b63f8 | |||
| cc349cd046 | |||
| 6a925b73d6 | |||
| 2290a0927d | |||
| 297a22280d | |||
| cd98c620af | |||
| e9647413f8 | |||
| 998ed5a76c | |||
| 2d62e67a24 | |||
| 69d2ab837f | |||
| ab8f84aa2d | |||
| 72c0a8391b | |||
| 4b9a79df4d | |||
| f7decf86a2 | |||
| 61b735ce89 | |||
| c4b78fdfd1 | |||
| 96769cb90c | |||
| 0ee60d2d3a | |||
| 9cbbdad56f | |||
| 4a18f5d03d | |||
| 9b76a2e820 | |||
| 5a1897d631 | |||
| 9d83fe08d8 | |||
| 428198b4f9 | |||
| e92bf03293 | |||
| 9fe77a5761 | |||
| fa07addba2 | |||
| ba4e4cc38f | |||
| e2a47e1619 | |||
| e107626026 | |||
| 527b1f9b13 | |||
| 6abc896bab | |||
| f2de0fd5df | |||
| c202d1b47a | |||
| d4349818b7 | |||
| ce80ceb165 | |||
| d19310ee27 | |||
| 63e6a249e3 | |||
| 0b05457fac | |||
| 63356dca41 | |||
| 4bce36550f | |||
| 3ca1e6673f | |||
| b2a4ee1647 | |||
| 81ef194164 | |||
| 2c698c8ba0 | |||
| 54e63de3a8 | |||
| 0186d35372 | |||
| c018a44c38 | |||
| 0c62d1ab65 | |||
| ff1fca976e | |||
| a7ad42eb1f | |||
| fb526ffc6d | |||
| d806c23d59 | |||
| 28b8b41475 | |||
| 457e609091 | |||
| ffcd076f3e | |||
| 01951ac513 | |||
| a8debfde40 | |||
| 3194e30772 | |||
| 223313c5ef | |||
| 5a8f580edd | |||
| 394fbda0ff | |||
| e778cc6bf0 | |||
| af6b581167 | |||
| 2f026d006c | |||
| 86daf598e5 | |||
| e77ddee5de | |||
| 162fac057d | |||
| 5c3c038c43 | |||
| 78432b49fe | |||
| 4395f9a465 | |||
| 0288f08cc3 | |||
| beb14b3e15 | |||
| c436a108ec | |||
| 5f9bfb6ac5 | |||
| 43a69544fb | |||
| a53141089c | |||
| 57a58afc7a | |||
| 2e9f712eae | |||
| 075b3e6581 | |||
| 8c4c2bae67 | |||
| 6808e25cd8 | |||
| cdc2d17455 | |||
| 31dbc4ef24 | |||
| 01c78d555c | |||
| 33d2594b20 | |||
| f6cf3356f8 | |||
| c041ca5ea1 | |||
| 92cb2f82ab | |||
| fa7ff61bf5 | |||
| 255c62782e | |||
| 9cbf760c80 | |||
| 506560e9e0 | |||
| d634e310fd | |||
| 255dd4ffcd | |||
| 5fd3277919 | |||
| a0aa69f8b0 | |||
| f63d78357b | |||
| fb2dac0f96 | |||
| e2e9cf8ea8 | |||
| 57ec92a645 | |||
| 6a26649fe4 | |||
| 5334fb881e | |||
| bc9d7aced8 | |||
| 5bb794dc91 | |||
| 7d928703a5 | |||
| 8961df4342 |
@@ -11,11 +11,26 @@ NUL
|
||||
build/*
|
||||
*.pyc
|
||||
*.exe
|
||||
*.a
|
||||
*.o
|
||||
CuraEngine
|
||||
_bin
|
||||
_obj
|
||||
|
||||
## CMake files
|
||||
cmake_install.cmake
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
CPackSourceConfig.cmake
|
||||
|
||||
# Visual Studio files generated by CMake
|
||||
*.vcxproj
|
||||
*.vcxproj.filters
|
||||
CuraEngine.sln
|
||||
|
||||
# Makefile generated by CMake
|
||||
Makefile
|
||||
|
||||
## IDE project files.
|
||||
CuraEngine.layout
|
||||
CuraEngine.cbp
|
||||
@@ -32,3 +47,4 @@ documentation/latex/*
|
||||
|
||||
## Test results.
|
||||
tests/output.xml
|
||||
callgrind.out.*
|
||||
|
||||
+18
-5
@@ -28,9 +28,11 @@ set(CURA_ENGINE_VERSION "master" CACHE STRING "Version name of Cura")
|
||||
option(BUILD_TESTS OFF)
|
||||
|
||||
# Add a compiler flag to check the output for insane values if we are in debug mode.
|
||||
if(CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||
if(CMAKE_BUILD_TYPE MATCHES DEBUG OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
|
||||
message(STATUS "Building debug release of CuraEngine.")
|
||||
add_definitions(-DASSERT_INSANE_OUTPUT)
|
||||
add_definitions(-DUSE_CPU_TIME)
|
||||
add_definitions(-DDEBUG)
|
||||
endif()
|
||||
|
||||
# Add warnings
|
||||
@@ -46,12 +48,14 @@ add_library(clipper STATIC libs/clipper/clipper.cpp)
|
||||
|
||||
set(engine_SRCS # Except main.cpp.
|
||||
src/bridge.cpp
|
||||
src/comb.cpp
|
||||
src/commandSocket.cpp
|
||||
src/ConicalOverhang.cpp
|
||||
src/ExtruderTrain.cpp
|
||||
src/FffGcodeWriter.cpp
|
||||
src/FffPolygonGenerator.cpp
|
||||
src/FffProcessor.cpp
|
||||
src/gcodeExport.cpp
|
||||
src/GCodePathConfig.cpp
|
||||
src/gcodePlanner.cpp
|
||||
src/infill.cpp
|
||||
src/WallsComputation.cpp
|
||||
@@ -62,10 +66,11 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/MeshGroup.cpp
|
||||
src/multiVolumes.cpp
|
||||
src/pathOrderOptimizer.cpp
|
||||
src/Preheat.cpp
|
||||
src/PrimeTower.cpp
|
||||
src/raft.cpp
|
||||
src/skin.cpp
|
||||
src/skirt.cpp
|
||||
src/SkirtBrim.cpp
|
||||
src/sliceDataStorage.cpp
|
||||
src/slicer.cpp
|
||||
src/support.cpp
|
||||
@@ -81,6 +86,9 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/infill/ZigzagConnectorProcessorEndPieces.cpp
|
||||
src/infill/ZigzagConnectorProcessorNoEndPieces.cpp
|
||||
|
||||
src/pathPlanning/Comb.cpp
|
||||
src/pathPlanning/LinePolygonsCrossings.cpp
|
||||
|
||||
src/progress/Progress.cpp
|
||||
src/progress/ProgressStageEstimator.cpp
|
||||
|
||||
@@ -94,9 +102,12 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/utils/Date.cpp
|
||||
src/utils/gettime.cpp
|
||||
src/utils/LinearAlg2D.cpp
|
||||
src/utils/ListPolyIt.cpp
|
||||
src/utils/logoutput.cpp
|
||||
src/utils/PolygonProximityLinker.cpp
|
||||
src/utils/polygonUtils.cpp
|
||||
src/utils/polygon.cpp
|
||||
src/utils/ProximityPointLink.cpp
|
||||
)
|
||||
|
||||
# List of tests. For each test there must be a file tests/${NAME}.cpp and a file tests/${NAME}.h.
|
||||
@@ -106,9 +117,11 @@ set(engine_TEST
|
||||
set(engine_TEST_INFILL
|
||||
)
|
||||
set(engine_TEST_UTILS
|
||||
BucketGrid2DTest
|
||||
SparseGridTest
|
||||
LinearAlg2DTest
|
||||
PolygonUtilsTest
|
||||
PolygonTest
|
||||
StringTest
|
||||
)
|
||||
|
||||
# Generating ProtoBuf protocol
|
||||
@@ -160,4 +173,4 @@ add_custom_command(TARGET CuraEngine POST_BUILD
|
||||
# Installing CuraEngine.
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS CuraEngine DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
include(CPackConfig.cmake)
|
||||
include(CPackConfig.cmake)
|
||||
|
||||
+57
-14
@@ -5,12 +5,21 @@ package cura.proto;
|
||||
message ObjectList
|
||||
{
|
||||
repeated Object objects = 1;
|
||||
repeated Setting settings = 2;
|
||||
repeated Setting settings = 2; // meshgroup settings (for one-at-a-time printing)
|
||||
}
|
||||
|
||||
message Slice
|
||||
{
|
||||
repeated ObjectList object_lists = 1;
|
||||
repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another
|
||||
SettingList global_settings = 2; // The global settings used for the whole print job
|
||||
repeated Extruder extruders = 3; // The settings sent to each extruder object
|
||||
repeated SettingExtruder limit_to_extruder = 4; // From which stack the setting would inherit if not defined per object
|
||||
}
|
||||
|
||||
message Extruder
|
||||
{
|
||||
int32 id = 1;
|
||||
SettingList settings = 2;
|
||||
}
|
||||
|
||||
message Object
|
||||
@@ -29,10 +38,10 @@ message Progress
|
||||
|
||||
message Layer {
|
||||
int32 id = 1;
|
||||
float height = 2;
|
||||
float thickness = 3;
|
||||
float height = 2; // Z position
|
||||
float thickness = 3; // height of a single layer
|
||||
|
||||
repeated Polygon polygons = 4;
|
||||
repeated Polygon polygons = 4; // layer data
|
||||
}
|
||||
|
||||
message Polygon {
|
||||
@@ -47,20 +56,48 @@ message Polygon {
|
||||
SupportInfillType = 7;
|
||||
MoveCombingType = 8;
|
||||
MoveRetractionType = 9;
|
||||
SupportInterfaceType = 10;
|
||||
}
|
||||
Type type = 1;
|
||||
bytes points = 2;
|
||||
float line_width = 3;
|
||||
Type type = 1; // Type of move
|
||||
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
|
||||
float line_width = 3; // The width of the line being laid down
|
||||
}
|
||||
|
||||
message LayerOptimized {
|
||||
int32 id = 1;
|
||||
float height = 2; // Z position
|
||||
float thickness = 3; // height of a single layer
|
||||
|
||||
repeated PathSegment path_segment = 4; // layer data
|
||||
}
|
||||
|
||||
|
||||
message PathSegment {
|
||||
int32 extruder = 1; // The extruder used for this path segment
|
||||
enum PointType {
|
||||
Point2D = 0;
|
||||
Point3D = 1;
|
||||
}
|
||||
PointType point_type = 2;
|
||||
bytes points = 3; // The points defining the line segments, bytes of float[2/3] array of length N+1
|
||||
bytes line_type = 4; // Type of line segment as an unsigned char array of length 1 or N, where N is the number of line segments in this path
|
||||
bytes line_width = 5; // The widths of the line segments as bytes of a float array of length 1 or N
|
||||
}
|
||||
|
||||
|
||||
message GCodeLayer {
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message ObjectPrintTime {
|
||||
|
||||
message PrintTimeMaterialEstimates { // The print time for the whole print and material estimates for the extruder
|
||||
float time = 1; // Total time estimate
|
||||
repeated MaterialEstimates materialEstimates = 2; // materialEstimates data
|
||||
}
|
||||
|
||||
message MaterialEstimates {
|
||||
int64 id = 1;
|
||||
float time = 2;
|
||||
float material_amount = 3;
|
||||
float material_amount = 2; // material used in the extruder
|
||||
}
|
||||
|
||||
message SettingList {
|
||||
@@ -68,13 +105,19 @@ message SettingList {
|
||||
}
|
||||
|
||||
message Setting {
|
||||
string name = 1;
|
||||
string name = 1; // Internal key to signify a setting
|
||||
|
||||
bytes value = 2;
|
||||
bytes value = 2; // The value of the setting
|
||||
}
|
||||
|
||||
message SettingExtruder {
|
||||
string name = 1; //The setting key.
|
||||
|
||||
int32 extruder = 2; //From which extruder stack the setting should inherit.
|
||||
}
|
||||
|
||||
message GCodePrefix {
|
||||
bytes data = 2;
|
||||
bytes data = 2; //Header string to be prepended before the rest of the g-code sent from the engine.
|
||||
}
|
||||
|
||||
message SlicingFinished {
|
||||
|
||||
+1
-1
@@ -832,7 +832,7 @@ EXAMPLE_RECURSIVE = NO
|
||||
# that contain images that are to be included in the documentation (see the
|
||||
# \image command).
|
||||
|
||||
IMAGE_PATH = documentation/assets
|
||||
IMAGE_PATH = docs/assets
|
||||
|
||||
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
||||
# invoke to filter for each input file. Doxygen will invoke the filter program
|
||||
|
||||
+16
-13
@@ -19,38 +19,41 @@ But in general it boils down to: You need to share the source of any CuraEngine
|
||||
How to Install
|
||||
==============
|
||||
1. Clone the repository from https://github.com/Ultimaker/CuraEngine.git (the URL at the right hand side of this page).
|
||||
2. Install Protobuf (see below)
|
||||
2. Install Protobuf >= 3.0.0 (see below)
|
||||
3. Install libArcus (see https://github.com/Ultimaker/libArcus)
|
||||
|
||||
In order to compile CuraEngine, either use CMake or start a project in your preferred IDE.
|
||||
CMake compilation:
|
||||
|
||||
1. Navigate to the CuraEngine directory and execute the following commands
|
||||
2. $ mkdir build && cd build
|
||||
3. $ cmake ..
|
||||
4. $ make
|
||||
2. ```$ mkdir build && cd build```
|
||||
3. ```$ cmake ..```
|
||||
4. ```$ make```
|
||||
|
||||
Project files generation:
|
||||
|
||||
1. Navigate to the CuraEngine directory and execute the following commands
|
||||
2. cmake . -G "CodeBlocks - Unix Makefiles"
|
||||
2. ```cmake . -G "CodeBlocks - Unix Makefiles"```
|
||||
3. (for a list of supported IDE's see http://www.cmake.org/Wiki/CMake_Generator_Specific_Information#Code::Blocks_Generator)
|
||||
|
||||
Installing Protobuf
|
||||
-------------------
|
||||
1. Be sure to have libtool installed.
|
||||
2. Download protobuf from https://github.com/google/protobuf/ (download ZIP and unZIP at desired location, or clone the repo) The protocol buffer is used for communication between the CuraEngine and the GUI.
|
||||
3. Before installing protobuf, change autogen.sh : comment line 18 to line 38 using '#'s. This removes the dependency on gtest-1.7.0.
|
||||
4. Run autogen.sh from the protobuf directory:
|
||||
$ ./autogen.sh
|
||||
5. $ ./configure
|
||||
6. $ make
|
||||
7. $ make install # Requires superused priviliges.
|
||||
8. (In case the shared library cannot be loaded, you can try "sudo ldconfig" on Linux systems)
|
||||
2. Download protobuf from https://github.com/google/protobuf/releases (download ZIP and unZIP at desired location, or clone the repo). The protocol buffer is used for communication between the CuraEngine and the GUI.
|
||||
3. Run ```autogen.sh``` from the protobuf directory:
|
||||
```$ ./autogen.sh```
|
||||
4. ```$ ./configure```
|
||||
5. ```$ make```
|
||||
6. ```# make install```
|
||||
(Please note the ```#```. It indicates the need of superuser, as known as root, priviliges.)
|
||||
7. (In case the shared library cannot be loaded, you can try ```sudo ldconfig``` on Linux systems)
|
||||
|
||||
Running
|
||||
=======
|
||||
Other than running CuraEngine from a frontend, such as Ultimaker/Cura, one can run CuraEngine from the command line.
|
||||
For that one needs a settings JSON file, which can be found in the Ultimaker/Cura repository.
|
||||
Note that the structure of the json files has changed since 2.1. In the corresponding branch of the Cura repository you can find how the json files used to be structured.
|
||||
|
||||
An example run for an UM2 machine looks as follows:
|
||||
* Navigate to the CuraEngine directory and execute the following
|
||||
```
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 18 KiB Depois Largura: | Altura: | Tamanho: 18 KiB |
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 70 KiB |
|
Antes Largura: | Altura: | Tamanho: 20 KiB Depois Largura: | Altura: | Tamanho: 20 KiB |
@@ -7,4 +7,4 @@ This is the documentation for CuraEngine, the back-end slicer of Cura.
|
||||
|
||||
[Glossary](documentation/glossary.md)
|
||||
|
||||
[Code Conventions](documentation/code_conventions.md)
|
||||
[Code Conventions](https://github.com/Ultimaker/Meta/blob/master/code_conventions.md)
|
||||
@@ -1 +0,0 @@
|
||||
html/index.html
|
||||
+19
-21
@@ -1,26 +1,24 @@
|
||||
The Clipper Library (including Delphi, C++ & C# source code, other accompanying
|
||||
code, examples and documentation), hereafter called "the Software", has been
|
||||
released under the following license, terms and conditions:
|
||||
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
http://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the Software covered by this license to use, reproduce,
|
||||
display, distribute, execute, and transmit the Software, and to prepare
|
||||
derivative works of the Software, and to permit third-parties to whom the
|
||||
Software is furnished to do so, all subject to the following:
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including the
|
||||
above license grant, this restriction and the following disclaimer, must be
|
||||
included in all copies of the Software, in whole or in part, and all derivative
|
||||
works of the Software, unless such copies or derivative works are solely in the
|
||||
form of machine-executable object code generated by a source language processor.
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
||||
THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
+34
-3
@@ -1,8 +1,39 @@
|
||||
=====================================================================
|
||||
Clipper Change Log
|
||||
=====================================================================
|
||||
v6.2.1 (31 October 2014) Rev 482
|
||||
* Bugfix in ClipperOffset.Execute where the Polytree.IsHole property
|
||||
was returning incorrect values with negative offsets
|
||||
* Very minor improvement to join rounding in ClipperOffset
|
||||
* Fixed CPP OpenGL demo.
|
||||
|
||||
v6.1.3 (19 January 2014)
|
||||
v6.2.0 (17 October 2014) Rev 477
|
||||
* Numerous minor bugfixes, too many to list.
|
||||
(See revisions 454-475 in Sourceforge Repository)
|
||||
* The ZFillFunction (custom callback function) has had its parameters
|
||||
changed.
|
||||
* Curves demo removed (temporarily).
|
||||
* Deprecated functions have been removed.
|
||||
|
||||
v6.1.5 (26 February 2014) Rev 460
|
||||
* Improved the joining of output polygons sharing a common edge
|
||||
when those common edges are horizontal.
|
||||
* Fixed a bug in ClipperOffset.AddPath() which would produce
|
||||
incorrect solutions when open paths were added before closed paths.
|
||||
* Minor code tidy and performance improvement
|
||||
|
||||
v6.1.4 (6 February 2014)
|
||||
* Fixed bugs in MinkowskiSum
|
||||
* Fixed minor bug when using Clipper.ForceSimplify.
|
||||
* Modified use_xyz callback so that all 4 vertices around an
|
||||
intersection point are now passed to the callback function.
|
||||
|
||||
v6.1.3a (22 January 2014) Rev 453
|
||||
* Fixed buggy PointInPolygon function (C++ and C# only).
|
||||
Note this bug only affected the newly exported function, the
|
||||
internal PointInPolygon function used by Clipper was OK.
|
||||
|
||||
v6.1.3 (19 January 2014) Rev 452
|
||||
* Fixed potential endless loop condition when adding open
|
||||
paths to Clipper.
|
||||
* Fixed missing implementation of SimplifyPolygon function
|
||||
@@ -13,11 +44,11 @@ v6.1.3 (19 January 2014)
|
||||
* Overloaded MinkowskiSum function to accommodate multi-contour
|
||||
paths.
|
||||
|
||||
v6.1.2 (15 December 2013)
|
||||
v6.1.2 (15 December 2013) Rev 444
|
||||
* Fixed broken C++ header file.
|
||||
* Minor improvement to joining polygons.
|
||||
|
||||
v6.1.1 (13 December 2013)
|
||||
v6.1.1 (13 December 2013) Rev 441
|
||||
* Fixed a couple of bugs affecting open paths that could
|
||||
raise unhandled exceptions.
|
||||
|
||||
|
||||
+388
-534
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+32
-35
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Author : Angus Johnson *
|
||||
* Version : 6.1.3a *
|
||||
* Date : 22 January 2014 *
|
||||
* Version : 6.2.1 *
|
||||
* Date : 31 October 2014 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2014 *
|
||||
* *
|
||||
@@ -34,7 +34,7 @@
|
||||
#ifndef clipper_hpp
|
||||
#define clipper_hpp
|
||||
|
||||
#define CLIPPER_VERSION "6.1.3"
|
||||
#define CLIPPER_VERSION "6.2.0"
|
||||
|
||||
//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
|
||||
//improve performance but coordinate values are limited to the range +/- 46340
|
||||
@@ -46,9 +46,8 @@
|
||||
//use_lines: Enables line clipping. Adds a very minor cost to performance.
|
||||
//#define use_lines
|
||||
|
||||
//use_deprecated: Enables support for the obsolete OffsetPaths() function
|
||||
//which has been replace with the ClipperOffset class.
|
||||
#define use_deprecated
|
||||
//use_deprecated: Enables temporary support for the obsolete functions
|
||||
//#define use_deprecated
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
@@ -57,6 +56,7 @@
|
||||
#include <cstdlib>
|
||||
#include <ostream>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
|
||||
namespace ClipperLib {
|
||||
|
||||
@@ -69,11 +69,16 @@ enum PolyType { ptSubject, ptClip };
|
||||
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
|
||||
|
||||
#ifdef use_int32
|
||||
typedef int cInt;
|
||||
typedef unsigned int cUInt;
|
||||
typedef int cInt;
|
||||
static cInt const loRange = 0x7FFF;
|
||||
static cInt const hiRange = 0x7FFF;
|
||||
#else
|
||||
typedef signed long long cInt;
|
||||
typedef unsigned long long cUInt;
|
||||
typedef signed long long cInt;
|
||||
static cInt const loRange = 0x3FFFFFFF;
|
||||
static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
|
||||
typedef signed long long long64; //used by Int128 class
|
||||
typedef unsigned long long ulong64;
|
||||
|
||||
#endif
|
||||
|
||||
struct IntPoint {
|
||||
@@ -117,15 +122,12 @@ struct DoublePoint
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#ifdef use_xyz
|
||||
typedef void (*TZFillCallback)(IntPoint& z1, IntPoint& z2, IntPoint& pt);
|
||||
typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
|
||||
#endif
|
||||
|
||||
enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
|
||||
enum JoinType {jtSquare, jtRound, jtMiter};
|
||||
enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
|
||||
#ifdef use_deprecated
|
||||
enum EndType_ {etClosed, etButt = 2, etSquare, etRound};
|
||||
#endif
|
||||
|
||||
class PolyNode;
|
||||
typedef std::vector< PolyNode* > PolyNodes;
|
||||
@@ -134,6 +136,7 @@ class PolyNode
|
||||
{
|
||||
public:
|
||||
PolyNode();
|
||||
virtual ~PolyNode(){};
|
||||
Path Contour;
|
||||
PolyNodes Childs;
|
||||
PolyNode* Parent;
|
||||
@@ -168,11 +171,6 @@ bool Orientation(const Path &poly);
|
||||
double Area(const Path &poly);
|
||||
int PointInPolygon(const IntPoint &pt, const Path &path);
|
||||
|
||||
#ifdef use_deprecated
|
||||
void OffsetPaths(const Paths &in_polys, Paths &out_polys,
|
||||
double delta, JoinType jointype, EndType_ endtype, double limit = 0);
|
||||
#endif
|
||||
|
||||
void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
|
||||
void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
|
||||
void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
|
||||
@@ -183,8 +181,7 @@ void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.
|
||||
void CleanPolygons(Paths& polys, double distance = 1.415);
|
||||
|
||||
void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
|
||||
void MinkowskiSum(const Path& pattern, const Paths& paths,
|
||||
Paths& solution, PolyFillType pathFillType, bool pathIsClosed);
|
||||
void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
|
||||
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
|
||||
|
||||
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
|
||||
@@ -202,7 +199,7 @@ enum EdgeSide { esLeft = 1, esRight = 2};
|
||||
//forward declarations (for stuff used internally) ...
|
||||
struct TEdge;
|
||||
struct IntersectNode;
|
||||
struct LocalMinima;
|
||||
struct LocalMinimum;
|
||||
struct Scanbeam;
|
||||
struct OutPt;
|
||||
struct OutRec;
|
||||
@@ -213,7 +210,6 @@ typedef std::vector < TEdge* > EdgeList;
|
||||
typedef std::vector < Join* > JoinList;
|
||||
typedef std::vector < IntersectNode* > IntersectList;
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//ClipperBase is the ancestor to the Clipper class. It should not be
|
||||
@@ -236,12 +232,14 @@ protected:
|
||||
void PopLocalMinima();
|
||||
virtual void Reset();
|
||||
TEdge* ProcessBound(TEdge* E, bool IsClockwise);
|
||||
void InsertLocalMinima(LocalMinima *newLm);
|
||||
void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed);
|
||||
TEdge* DescendToMin(TEdge *&E);
|
||||
void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
|
||||
LocalMinima *m_CurrentLM;
|
||||
LocalMinima *m_MinimaList;
|
||||
|
||||
typedef std::vector<LocalMinimum> MinimaList;
|
||||
MinimaList::iterator m_CurrentLM;
|
||||
MinimaList m_MinimaList;
|
||||
|
||||
bool m_UseFullRange;
|
||||
EdgeList m_edges;
|
||||
bool m_PreserveCollinear;
|
||||
@@ -268,7 +266,7 @@ public:
|
||||
void StrictlySimple(bool value) {m_StrictSimple = value;};
|
||||
//set the callback function for z value filling on intersections (otherwise Z is 0)
|
||||
#ifdef use_xyz
|
||||
void ZFillFunction(TZFillCallback zFillFunc);
|
||||
void ZFillFunction(ZFillCallback zFillFunc);
|
||||
#endif
|
||||
protected:
|
||||
void Reset();
|
||||
@@ -279,7 +277,8 @@ private:
|
||||
JoinList m_GhostJoins;
|
||||
IntersectList m_IntersectList;
|
||||
ClipType m_ClipType;
|
||||
std::set< cInt, std::greater<cInt> > m_Scanbeam;
|
||||
typedef std::priority_queue<cInt> ScanbeamList;
|
||||
ScanbeamList m_Scanbeam;
|
||||
TEdge *m_ActiveEdges;
|
||||
TEdge *m_SortedEdges;
|
||||
bool m_ExecuteLocked;
|
||||
@@ -289,7 +288,7 @@ private:
|
||||
bool m_UsingPolyTree;
|
||||
bool m_StrictSimple;
|
||||
#ifdef use_xyz
|
||||
TZFillCallback m_ZFill; //custom callback
|
||||
ZFillCallback m_ZFill; //custom callback
|
||||
#endif
|
||||
void SetWindingCount(TEdge& edge);
|
||||
bool IsEvenOddFillType(const TEdge& edge) const;
|
||||
@@ -308,21 +307,19 @@ private:
|
||||
bool IsTopHorz(const cInt XPos);
|
||||
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
|
||||
void DoMaxima(TEdge *e);
|
||||
void PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam);
|
||||
void ProcessHorizontals(bool IsTopOfScanbeam);
|
||||
void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam);
|
||||
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutRec* GetOutRec(int idx);
|
||||
void AppendPolygon(TEdge *e1, TEdge *e2);
|
||||
void IntersectEdges(TEdge *e1, TEdge *e2,
|
||||
const IntPoint &pt, bool protect = false);
|
||||
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
|
||||
OutRec* CreateOutRec();
|
||||
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
|
||||
void DisposeAllOutRecs();
|
||||
void DisposeOutRec(PolyOutList::size_type index);
|
||||
bool ProcessIntersections(const cInt botY, const cInt topY);
|
||||
void BuildIntersectList(const cInt botY, const cInt topY);
|
||||
bool ProcessIntersections(const cInt topY);
|
||||
void BuildIntersectList(const cInt topY);
|
||||
void ProcessIntersectList();
|
||||
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
|
||||
void BuildResult(Paths& polys);
|
||||
@@ -344,7 +341,7 @@ private:
|
||||
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
#ifdef use_xyz
|
||||
void SetZ(IntPoint& pt, TEdge& e);
|
||||
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
|
||||
#endif
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ cd ~/Development/CuraEngine/output/reflection/
|
||||
run setting inheritance reflection
|
||||
|
||||
cd ~/Development/CuraEngine
|
||||
./build/CuraEngine analyse ../Cura/resources/machines/fdmprinter.json meta/refl_ff.gv output/reflection/engineSettingLiterals.txt -piew
|
||||
./build/CuraEngine analyse ../Cura/resources/definitions/fdmprinter.def.json meta/refl_ff.gv output/reflection/engineSettingLiterals.txt -piew
|
||||
|
||||
dot meta/refl_ff.gv -Tpng > meta/rafl_ff_dotted.png
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Command line setting defaults CuraEngine",
|
||||
"author": "Ultimaker B.V.",
|
||||
"categories": {
|
||||
"command_line_settings": {
|
||||
"label": "Command Line Settings",
|
||||
"settings": {
|
||||
"center_object": {
|
||||
"description": "Whether to center the object on the middle of the build platform (0,0), instead of using the coordinate system in which the object was saved.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"machine_print_temp_wait": {
|
||||
"description": "Whether to wait for the nozzle temperature to be reached when preheating the nozzles at the start of the gcode.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"mesh_position_x": {
|
||||
"description": "Offset applied to the object in the x direction.",
|
||||
"type": "float",
|
||||
"default": 0
|
||||
},
|
||||
"mesh_position_y": {
|
||||
"description": "Offset applied to the object in the y direction.",
|
||||
"type": "float",
|
||||
"default": 0
|
||||
},
|
||||
"mesh_position_z": {
|
||||
"description": "Offset applied to the object in the z direction. With this you can perform what was used to call 'Object Sink'.",
|
||||
"type": "float",
|
||||
"default": 0
|
||||
},
|
||||
"prime_tower_dir_outward": {
|
||||
"description": "Whether to start printing in the middle of the prime tower and end up at the perimeter, or the other way around. This is only used for certain types of prime tower.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#include "ConicalOverhang.h"
|
||||
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
void ConicalOverhang::apply(Slicer* slicer, double angle, int layer_thickness)
|
||||
{
|
||||
double tanAngle = tan(angle); // the XY-component of the angle
|
||||
int max_dist_from_lower_layer = tanAngle * layer_thickness; // max dist which can be bridged
|
||||
|
||||
for (unsigned int layer_nr = slicer->layers.size() - 2; static_cast<int>(layer_nr) >= 0; layer_nr--)
|
||||
{
|
||||
SlicerLayer& layer = slicer->layers[layer_nr];
|
||||
SlicerLayer& layer_above = slicer->layers[layer_nr + 1];
|
||||
if (std::abs(max_dist_from_lower_layer) < 5)
|
||||
{ // magically nothing happens when max_dist_from_lower_layer == 0
|
||||
// below magic code solves that
|
||||
int safe_dist = 20;
|
||||
Polygons diff = layer_above.polygons.difference(layer.polygons.offset(-safe_dist));
|
||||
layer.polygons = layer.polygons.unionPolygons(diff);
|
||||
layer.polygons = layer.polygons.smooth(safe_dist);
|
||||
layer.polygons.simplify(safe_dist, safe_dist * safe_dist / 4);
|
||||
// somehow layer.polygons get really jagged lines with a lot of vertices
|
||||
// without the above steps slicing goes really slow
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.polygons = layer.polygons.unionPolygons(layer_above.polygons.offset(-max_dist_from_lower_layer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,30 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef CONICAL_OVERHANG_H
|
||||
#define CONICAL_OVERHANG_H
|
||||
|
||||
#include "slicer.h"
|
||||
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
/*!
|
||||
* A class for changing the geometry of a model such that it is printable without support -
|
||||
* Or at least with at least support as possible
|
||||
*/
|
||||
class ConicalOverhang
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Change the slice data such that the model becomes more printable
|
||||
*
|
||||
* \param[in,out] slicer The slice data
|
||||
* \param angle The maximum angle which can be printed without generating support (or at least generating least support)
|
||||
* \param layer_thickness The general layer thickness
|
||||
*/
|
||||
static void apply(Slicer* slicer, double angle, int layer_thickness);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif // CONICAL_OVERHANG_H
|
||||
@@ -0,0 +1,27 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "ExtruderTrain.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
int ExtruderTrain::getExtruderNr()
|
||||
{
|
||||
return extruder_nr;
|
||||
}
|
||||
ExtruderTrain::ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr)
|
||||
: SettingsBase(settings)
|
||||
, extruder_nr(extruder_nr)
|
||||
{
|
||||
}
|
||||
|
||||
bool ExtruderTrain::getIsUsed() const
|
||||
{
|
||||
return is_used;
|
||||
}
|
||||
|
||||
void ExtruderTrain::setIsUsed(bool used)
|
||||
{
|
||||
is_used = used;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef EXTRUDER_TRAIN_H
|
||||
#define EXTRUDER_TRAIN_H
|
||||
|
||||
@@ -9,14 +10,15 @@ namespace cura
|
||||
class ExtruderTrain : public SettingsBase
|
||||
{
|
||||
int extruder_nr;
|
||||
bool is_used = false; //!< whether this extruder train is (probably) used during printing the current meshgroup
|
||||
public:
|
||||
int getExtruderNr() { return extruder_nr; }
|
||||
|
||||
ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr)
|
||||
: SettingsBase(settings)
|
||||
, extruder_nr(extruder_nr)
|
||||
{ }
|
||||
|
||||
int getExtruderNr();
|
||||
|
||||
bool getIsUsed() const; //!< return whether this extruder train is (probably) used during printing the current meshgroup
|
||||
void setIsUsed(bool used); //!< set whether this extruder train is (probably) used during printing the current meshgroup
|
||||
|
||||
ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr);
|
||||
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -11,6 +11,7 @@ struct FanSpeedLayerTimeSettings
|
||||
public:
|
||||
double cool_min_layer_time;
|
||||
double cool_min_layer_time_fan_speed_max;
|
||||
double cool_fan_speed_0;
|
||||
double cool_fan_speed_min;
|
||||
double cool_fan_speed_max;
|
||||
double cool_min_speed;
|
||||
|
||||
+567
-304
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+61
-48
@@ -60,32 +60,23 @@ private:
|
||||
std::ofstream output_file;
|
||||
|
||||
/*!
|
||||
* Layer number of the last layer in which a prime tower has been printed per extruder train.
|
||||
*
|
||||
* This is recorded per extruder to account for a prime tower per extruder, instead of the mixed prime tower.
|
||||
* Whether the skirt or brim polygons have been processed into planned paths
|
||||
* for each extruder train.
|
||||
*/
|
||||
int last_prime_tower_poly_printed[MAX_EXTRUDERS];
|
||||
bool skirt_brim_is_processed[MAX_EXTRUDERS];
|
||||
|
||||
bool skirt_is_processed[MAX_EXTRUDERS]; //!< Whether the skirt polygons have been processed into planned paths for each extruder train
|
||||
|
||||
FanSpeedLayerTimeSettings fan_speed_layer_time_settings; //!< The settings used relating to minimal layer time and fan speeds.
|
||||
std::vector<FanSpeedLayerTimeSettings> fan_speed_layer_time_settings_per_extruder; //!< The settings used relating to minimal layer time and fan speeds. Configured for each extruder.
|
||||
|
||||
Point last_position_planned; //!< The position of the head before planning the next layer
|
||||
int current_extruder_planned; //!< The extruder train in use before planning the next layer
|
||||
bool is_inside_mesh_layer_part; //!< Whether the last position was inside a layer part (used in combing)
|
||||
public:
|
||||
FffGcodeWriter(SettingsBase* settings_)
|
||||
: SettingsMessenger(settings_)
|
||||
, layer_plan_buffer(this, gcode)
|
||||
, last_position_planned(no_point)
|
||||
, current_extruder_planned(0) // TODO: make configurable
|
||||
, is_inside_mesh_layer_part(false)
|
||||
, current_extruder_planned(0) // changed somewhere early in FffGcodeWriter::writeGCode
|
||||
{
|
||||
max_object_height = 0;
|
||||
for (unsigned int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++)
|
||||
{
|
||||
skirt_is_processed[extruder_nr] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -153,8 +144,10 @@ public:
|
||||
private:
|
||||
/*!
|
||||
* Set the FffGcodeWriter::fan_speed_layer_time_settings by retrieving all settings from the global/per-meshgroup settings.
|
||||
*
|
||||
* \param[out] storage The data storage to which to save the configuration
|
||||
*/
|
||||
void setConfigFanSpeedLayerTime();
|
||||
void setConfigFanSpeedLayerTime(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Create and set the SliceDataStorage::coasting_config for each extruder.
|
||||
@@ -171,11 +164,13 @@ private:
|
||||
void setConfigRetraction(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Initialize the GcodePathConfig config parameters which don't change over all layers, for each feature.
|
||||
* Initialize the GcodePathConfig config parameters which don't change over
|
||||
* all layers, for each feature.
|
||||
*
|
||||
* The features are: skirt, support and for each mesh: outer wall, inner walls, skin, infill (and combined infill)
|
||||
* The features are: skirt or brim, support and for each mesh: outer wall,
|
||||
* inner walls, skin, infill (and combined infill).
|
||||
*
|
||||
* \param[out] storage The data storage to which to save the configurations
|
||||
* \param[out] storage The data storage to which to save the configurations.
|
||||
*/
|
||||
void initConfigs(SliceDataStorage& storage);
|
||||
|
||||
@@ -198,29 +193,40 @@ private:
|
||||
/*!
|
||||
* Add raft layer plans onto the FffGcodeWriter::layer_plan_buffer
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param[in,out] storage where the slice data is stored.
|
||||
* \param total_layers The total number of layers.
|
||||
*/
|
||||
void processRaft(SliceDataStorage& storage, unsigned int total_layers);
|
||||
|
||||
|
||||
/*!
|
||||
* Convert the polygon data of a layer into a layer plan on the FffGcodeWriter::layer_plan_buffer
|
||||
*
|
||||
* In case of negative layer numbers, create layers only containing the data from
|
||||
* the helper parts (support etc) to fill up the gap between the raft and the model.
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \param total_layers The total number of layers.
|
||||
* \param has_raft Whether a raft is used for this print.
|
||||
*/
|
||||
void processLayer(SliceDataStorage& storage, unsigned int layer_nr, unsigned int total_layers, bool has_raft);
|
||||
|
||||
void processLayer(SliceDataStorage& storage, int layer_nr, unsigned int total_layers);
|
||||
|
||||
/*!
|
||||
* Add the skirt to the layer plan \p gcodeLayer.
|
||||
*
|
||||
* Plan priming of all used extruders which haven't been primed yet
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param extruder_nr The extrudewr train for which to process the skirt
|
||||
* \param layer_plan The initial planning of the g-code of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*/
|
||||
void processSkirt(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int extruder_nr);
|
||||
void ensureAllExtrudersArePrimed(SliceDataStorage& storage, GCodePlanner& layer_plan, const int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add the skirt or the brim to the layer plan \p gcodeLayer.
|
||||
*
|
||||
* \param Storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the g-code of the layer.
|
||||
* \param extruder_nr The extruder train for which to process the skirt or
|
||||
* brim.
|
||||
*/
|
||||
void processSkirtBrim(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int extruder_nr);
|
||||
|
||||
/*!
|
||||
* Adds the ooze shield to the layer plan \p gcodeLayer.
|
||||
@@ -239,16 +245,25 @@ private:
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr);
|
||||
|
||||
|
||||
/*!
|
||||
* Calculate in which order to print the meshes.
|
||||
* Calculate in which order to plan the extruders
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param current_extruder The current extruder with which we last printed
|
||||
* \return A vector of mesh indices ordered on print order.
|
||||
* \return A vector of pairs of extruder numbers coupled with the mesh indices ordered on print order for that extruder.
|
||||
*/
|
||||
std::vector<unsigned int> calculateMeshOrder(SliceDataStorage& storage, int current_extruder);
|
||||
|
||||
std::vector<int> calculateExtruderOrder(SliceDataStorage& storage, int current_extruder);
|
||||
|
||||
/*!
|
||||
* Calculate in which order to plan the meshes of a specific extruder
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param extruder_nr The extruder for which to determine the order
|
||||
* \return A vector of pairs of extruder numbers coupled with the mesh indices ordered on print order for that extruder.
|
||||
*/
|
||||
std::vector<unsigned int> calculateMeshOrder(SliceDataStorage& storage, int extruder_nr);
|
||||
|
||||
/*!
|
||||
* Add a single layer from a single mesh-volume to the layer plan \p gcodeLayer in mesh surface mode.
|
||||
*
|
||||
@@ -292,9 +307,8 @@ private:
|
||||
* \param infill_line_distance The distance between the infill lines
|
||||
* \param infill_overlap The distance by which the infill overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
void processMultiLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int fillAngle, int extrusionWidth);
|
||||
void processMultiLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int fillAngle);
|
||||
|
||||
/*!
|
||||
* Add normal sparse infill for a given part in a layer.
|
||||
@@ -305,9 +319,8 @@ private:
|
||||
* \param infill_line_distance The distance between the infill lines
|
||||
* \param infill_overlap The distance by which the infill overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
void processSingleLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int fillAngle, int extrusionWidth);
|
||||
void processSingleLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int fillAngle);
|
||||
|
||||
/*!
|
||||
* Generate the insets for the walls of a given layer part.
|
||||
@@ -326,35 +339,35 @@ private:
|
||||
* \param mesh The mesh for which to add to the layer plan \p gcodeLayer.
|
||||
* \param part The part for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param skin_overlap The distance by which the skinfill overlaps with the wall insets.
|
||||
* \param skin_overlap The distance by which the skin overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int infill_angle, int extrusion_width);
|
||||
|
||||
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int infill_angle);
|
||||
|
||||
/*!
|
||||
* Add the support to the layer plan \p gcodeLayer of the current layer.
|
||||
* Add the support to the layer plan \p gcodeLayer of the current layer for all support parts with the given \p extruder_nr.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \param extruder_nr_before The extruder number at the start of the layer (before other print parts aka the rest)
|
||||
* \param before_rest Whether the function has been called before adding the rest to the layer plan \p gcodeLayer, or after.
|
||||
* \return whether any support was added to the layer plan
|
||||
*/
|
||||
void addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int extruder_nr_before, bool before_rest);
|
||||
bool addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int extruder_nr);
|
||||
/*!
|
||||
* Add the support lines/walls to the layer plan \p gcodeLayer of the current layer.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \return whether any support infill was added to the layer plan
|
||||
*/
|
||||
void addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
bool addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
/*!
|
||||
* Add the support roofs to the layer plan \p gcodeLayer of the current layer.
|
||||
* Add the support skins to the layer plan \p gcodeLayer of the current layer.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \return whether any support skin was added to the layer plan
|
||||
*/
|
||||
void addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
bool addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
|
||||
/*!
|
||||
* Change to a new extruder, and add the prime tower instructions if the new extruder is different from the last.
|
||||
|
||||
+311
-142
@@ -3,6 +3,8 @@
|
||||
#include <algorithm>
|
||||
#include <map> // multimap (ordered map allowing duplicate keys)
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "utils/algorithm.h"
|
||||
#include "slicer.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
@@ -11,13 +13,13 @@
|
||||
#include "multiVolumes.h"
|
||||
#include "layerPart.h"
|
||||
#include "WallsComputation.h"
|
||||
#include "skirt.h"
|
||||
#include "SkirtBrim.h"
|
||||
#include "skin.h"
|
||||
#include "infill.h"
|
||||
#include "raft.h"
|
||||
#include "debug.h"
|
||||
#include "progress/Progress.h"
|
||||
#include "PrintFeature.h"
|
||||
#include "ConicalOverhang.h"
|
||||
#include "progress/ProgressEstimator.h"
|
||||
#include "progress/ProgressStageEstimator.h"
|
||||
#include "progress/ProgressEstimatorLinear.h"
|
||||
@@ -26,7 +28,7 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
|
||||
bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper)
|
||||
{
|
||||
if (!sliceModel(meshgroup, timeKeeper, storage))
|
||||
@@ -39,6 +41,22 @@ bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* me
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int FffPolygonGenerator::getDraftShieldLayerCount(const unsigned int total_layers) const
|
||||
{
|
||||
if (!getSettingBoolean("draft_shield_enabled"))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
switch (getSettingAsDraftShieldHeightLimitation("draft_shield_height_limitation"))
|
||||
{
|
||||
default:
|
||||
case DraftShieldHeightLimitation::FULL:
|
||||
return total_layers;
|
||||
case DraftShieldHeightLimitation::LIMITED:
|
||||
return std::max(0, (getSettingInMicrons("draft_shield_height") - getSettingInMicrons("layer_height_0")) / getSettingInMicrons("layer_height") + 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeeper, SliceDataStorage& storage) /// slices the model
|
||||
{
|
||||
Progress::messageProgressStage(Progress::Stage::SLICING, &timeKeeper);
|
||||
@@ -61,8 +79,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
return false;
|
||||
}
|
||||
int initial_slice_z = initial_layer_thickness - layer_thickness / 2;
|
||||
int layer_count = (storage.model_max.z - initial_slice_z) / layer_thickness + 1;
|
||||
if(layer_count <= 0) //Model is shallower than layer_height_0, so not even the first layer is sliced. Return an empty model then.
|
||||
int slice_layer_count = (storage.model_max.z - initial_slice_z) / layer_thickness + 1;
|
||||
if (slice_layer_count <= 0) //Model is shallower than layer_height_0, so not even the first layer is sliced. Return an empty model then.
|
||||
{
|
||||
return true; //This is NOT an error state!
|
||||
}
|
||||
@@ -71,7 +89,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < meshgroup->meshes.size(); mesh_idx++)
|
||||
{
|
||||
Mesh& mesh = meshgroup->meshes[mesh_idx];
|
||||
Slicer* slicer = new Slicer(&mesh, initial_slice_z, layer_thickness, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
|
||||
Slicer* slicer = new Slicer(&mesh, initial_slice_z, layer_thickness, slice_layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
|
||||
slicerList.push_back(slicer);
|
||||
/*
|
||||
for(SlicerLayer& layer : slicer->layers)
|
||||
@@ -83,25 +101,71 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
*/
|
||||
Progress::messageProgress(Progress::Stage::SLICING, mesh_idx + 1, meshgroup->meshes.size());
|
||||
}
|
||||
|
||||
log("Layer count: %i\n", layer_count);
|
||||
|
||||
meshgroup->clear();///Clear the mesh face and vertex data, it is no longer needed after this point, and it saves a lot of memory.
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
|
||||
//carveMultipleVolumes(storage.meshes);
|
||||
generateMultipleVolumesOverlap(slicerList);
|
||||
// TODO!!! dont generate multi volume overlap with infill meshes!
|
||||
|
||||
storage.meshes.reserve(slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated.
|
||||
|
||||
for(unsigned int meshIdx=0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
storage.meshes.emplace_back(&meshgroup->meshes[meshIdx]); // new mesh in storage had settings from the Mesh
|
||||
SliceMeshStorage& meshStorage = storage.meshes.back();
|
||||
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
|
||||
|
||||
|
||||
createLayerParts(meshStorage, slicerList[meshIdx], mesh.getSettingBoolean("meshfix_union_all"), mesh.getSettingBoolean("meshfix_union_all_remove_holes"));
|
||||
if (mesh.getSettingBoolean("conical_overhang_enabled") && !mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
ConicalOverhang::apply(slicerList[meshIdx], mesh.getSettingInAngleRadians("conical_overhang_angle"), layer_thickness);
|
||||
}
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
|
||||
|
||||
if (storage.getSettingBoolean("carve_multiple_volumes"))
|
||||
{
|
||||
carveMultipleVolumes(slicerList, storage.getSettingBoolean("alternate_carve_order"));
|
||||
}
|
||||
generateMultipleVolumesOverlap(slicerList);
|
||||
|
||||
storage.print_layer_count = 0;
|
||||
for (unsigned int meshIdx = 0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
|
||||
Slicer* slicer = slicerList[meshIdx];
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh") && !mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
storage.print_layer_count = std::max(storage.print_layer_count, (unsigned int)slicer->layers.size());
|
||||
}
|
||||
}
|
||||
storage.support.supportLayers.resize(storage.print_layer_count);
|
||||
|
||||
storage.meshes.reserve(slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated.
|
||||
for (unsigned int meshIdx = 0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
Slicer* slicer = slicerList[meshIdx];
|
||||
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
|
||||
|
||||
// always make a new SliceMeshStorage, so that they have the same ordering / indexing as meshgroup.meshes
|
||||
storage.meshes.emplace_back(&meshgroup->meshes[meshIdx], slicer->layers.size()); // new mesh in storage had settings from the Mesh
|
||||
SliceMeshStorage& meshStorage = storage.meshes.back();
|
||||
|
||||
if (mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
for (unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
|
||||
SlicerLayer& slicer_layer = slicer->layers[layer_nr];
|
||||
support_layer.anti_overhang = support_layer.anti_overhang.unionPolygons(slicer_layer.polygons);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (mesh.getSettingBoolean("support_mesh"))
|
||||
{
|
||||
for (unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
|
||||
SlicerLayer& slicer_layer = slicer->layers[layer_nr];
|
||||
support_layer.support_mesh.add(slicer_layer.polygons);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
createLayerParts(meshStorage, slicer, mesh.getSettingBoolean("meshfix_union_all"), mesh.getSettingBoolean("meshfix_union_all_remove_holes"));
|
||||
delete slicerList[meshIdx];
|
||||
|
||||
bool has_raft = getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT;
|
||||
@@ -116,9 +180,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
{
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("adhesion_extruder_nr"));
|
||||
layer.printZ +=
|
||||
train->getSettingInMicrons("raft_base_thickness")
|
||||
+ train->getSettingInMicrons("raft_interface_thickness")
|
||||
+ train->getSettingAsCount("raft_surface_layers") * train->getSettingInMicrons("raft_surface_thickness")
|
||||
Raft::getTotalThickness(storage)
|
||||
+ train->getSettingInMicrons("raft_airgap")
|
||||
- train->getSettingInMicrons("layer_0_z_overlap"); // shift all layers (except 0) down
|
||||
if (layer_nr == 0)
|
||||
@@ -126,19 +188,13 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
layer.printZ += train->getSettingInMicrons("layer_0_z_overlap"); // undo shifting down of first layer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) )
|
||||
{
|
||||
meshStorage.layer_nr_max_filled_layer = layer_nr; // last set by the highest non-empty layer
|
||||
}
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, layer.printZ, layer_nr == 0? getSettingInMicrons("layer_height_0") : getSettingInMicrons("layer_height"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Progress::messageProgress(Progress::Stage::PARTS, meshIdx + 1, slicerList.size());
|
||||
}
|
||||
return true;
|
||||
@@ -148,12 +204,12 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
{
|
||||
// compute layer count and remove first empty layers
|
||||
// there is no separate progress stage for removeEmptyFisrtLayer (TODO)
|
||||
unsigned int total_layers = 0;
|
||||
unsigned int slice_layer_count = 0;
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (!mesh.getSettingBoolean("infill_mesh"))
|
||||
if (!mesh.getSettingBoolean("infill_mesh") && !mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
total_layers = std::max<unsigned int>(total_layers, mesh.layers.size());
|
||||
slice_layer_count = std::max<unsigned int>(slice_layer_count, mesh.layers.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,65 +234,94 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
mesh_order.push_back(order_and_mesh_idx.second);
|
||||
}
|
||||
}
|
||||
for (unsigned int mesh_idx : mesh_order)
|
||||
for (unsigned int mesh_order_idx(0); mesh_order_idx < mesh_order.size(); ++mesh_order_idx)
|
||||
{
|
||||
processBasicWallsSkinInfill(storage, mesh_idx, mesh_order, total_layers, inset_skin_progress_estimate);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, mesh_idx + 1, storage.meshes.size());
|
||||
processBasicWallsSkinInfill(storage, mesh_order_idx, mesh_order, inset_skin_progress_estimate);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, mesh_order_idx + 1, storage.meshes.size());
|
||||
}
|
||||
|
||||
for (unsigned int layer_nr = 0; layer_nr < slice_layer_count; layer_nr++)
|
||||
{
|
||||
SliceLayer* layer = nullptr;
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
{ // find first mesh which has this layer
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
if (int(layer_nr) <= mesh.layer_nr_max_filled_layer)
|
||||
{
|
||||
layer = &mesh.layers[layer_nr];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (layer != nullptr)
|
||||
{
|
||||
if (CommandSocket::isInstantiated())
|
||||
{ // send layer info
|
||||
CommandSocket::getInstance()->sendOptimizedLayerInfo(layer_nr, layer->printZ, layer_nr == 0? getSettingInMicrons("layer_height_0") : getSettingInMicrons("layer_height"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("Layer count: %i\n", storage.print_layer_count);
|
||||
|
||||
//layerparts2HTML(storage, "output/output.html");
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
|
||||
|
||||
AreaSupport::generateSupportAreas(storage, storage.print_layer_count);
|
||||
|
||||
// we need to remove empty layers after we have procesed the insets
|
||||
// processInsets might throw away parts if they have no wall at all (cause it doesn't fit)
|
||||
// brim depends on the first layer not being empty
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), total_layers); // changes total_layers!
|
||||
if (total_layers == 0)
|
||||
// only remove empty layers if we haven't generate support, because then support was added underneath the model.
|
||||
// for some materials it's better to print on support than on the buildplate.
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), storage.print_layer_count); // changes storage.print_layer_count!
|
||||
if (storage.print_layer_count == 0)
|
||||
{
|
||||
log("Stopping process because there are no non-empty layers.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
|
||||
|
||||
AreaSupport::generateSupportAreas(storage, total_layers);
|
||||
|
||||
/*
|
||||
if (storage.support.generated)
|
||||
{
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
for (unsigned int layer_idx = 0; layer_idx < storage.print_layer_count; layer_idx++)
|
||||
{
|
||||
Polygons& support = storage.support.supportLayers[layer_idx].supportAreas;
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::Infill, layer_idx, support, 100); //getSettingInMicrons("support_line_width"));
|
||||
}
|
||||
ExtruderTrain* infill_extr = storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("support_infill_extruder_nr"));
|
||||
CommandSocket::sendPolygons(PrintFeatureType::Infill, support, 100); // infill_extr->getSettingInMicrons("support_line_width"));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
computePrintHeightStatistics(storage);
|
||||
|
||||
// handle helpers
|
||||
storage.primeTower.computePrimeTowerMax(storage);
|
||||
storage.primeTower.generatePaths(storage, total_layers);
|
||||
storage.primeTower.generatePaths(storage);
|
||||
|
||||
logDebug("Processing ooze shield\n");
|
||||
processOozeShield(storage);
|
||||
|
||||
processOozeShield(storage, total_layers);
|
||||
|
||||
processDraftShield(storage, total_layers);
|
||||
logDebug("Processing draft shield\n");
|
||||
processDraftShield(storage);
|
||||
|
||||
logDebug("Processing platform adhesion\n");
|
||||
processPlatformAdhesion(storage);
|
||||
|
||||
// meshes post processing
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
processDerivedWallsSkinInfill(mesh, total_layers);
|
||||
processDerivedWallsSkinInfill(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate)
|
||||
void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, ProgressStageEstimator& inset_skin_progress_estimate)
|
||||
{
|
||||
|
||||
unsigned int mesh_idx = mesh_order[mesh_order_idx];
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
size_t mesh_layer_count = mesh.layers.size();
|
||||
if (mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
processInfillMesh(storage, mesh_idx, mesh_order, total_layers);
|
||||
processInfillMesh(storage, mesh_order_idx, mesh_order);
|
||||
}
|
||||
|
||||
// TODO: make progress more accurate!!
|
||||
@@ -246,21 +331,40 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
|
||||
inset_skin_progress_estimate.nextStage(mesh_inset_skin_progress_estimator); // the stage of this function call
|
||||
|
||||
ProgressEstimatorLinear* inset_estimator = new ProgressEstimatorLinear(total_layers);
|
||||
ProgressEstimatorLinear* inset_estimator = new ProgressEstimatorLinear(mesh_layer_count);
|
||||
mesh_inset_skin_progress_estimator->nextStage(inset_estimator);
|
||||
|
||||
|
||||
// walls
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
for (unsigned int layer_number = 0; layer_number < mesh.layers.size(); layer_number++)
|
||||
{
|
||||
logDebug("Processing insets for layer %i of %i\n", layer_number, mesh_layer_count);
|
||||
processInsets(mesh, layer_number);
|
||||
double progress = inset_skin_progress_estimate.progress(layer_number);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100);
|
||||
}
|
||||
|
||||
ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(total_layers);
|
||||
ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(mesh_layer_count);
|
||||
mesh_inset_skin_progress_estimator->nextStage(skin_estimator);
|
||||
|
||||
|
||||
bool process_infill = mesh.getSettingInMicrons("infill_line_distance") > 0;
|
||||
if (!process_infill)
|
||||
{ // do process infill anyway if it's modified by modifier meshes
|
||||
for (unsigned int other_mesh_order_idx(mesh_order_idx + 1); other_mesh_order_idx < mesh_order.size(); ++other_mesh_order_idx)
|
||||
{
|
||||
unsigned int other_mesh_idx = mesh_order[other_mesh_order_idx];
|
||||
SliceMeshStorage& other_mesh = storage.meshes[other_mesh_idx];
|
||||
if (other_mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
AABB3D aabb = storage.meshgroup->meshes[mesh_idx].getAABB();
|
||||
AABB3D other_aabb = storage.meshgroup->meshes[other_mesh_idx].getAABB();
|
||||
if (aabb.hit(other_aabb))
|
||||
{
|
||||
process_infill = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// skin & infill
|
||||
// Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper);
|
||||
int mesh_max_bottom_layer_count = 0;
|
||||
@@ -268,34 +372,37 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
{
|
||||
mesh_max_bottom_layer_count = std::max(mesh_max_bottom_layer_count, mesh.getSettingAsCount("bottom_layers"));
|
||||
}
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
for (unsigned int layer_number = 0; layer_number < mesh.layers.size(); layer_number++)
|
||||
{
|
||||
logDebug("Processing skins and infill layer %i of %i\n", layer_number, mesh_layer_count);
|
||||
if (!mesh.getSettingBoolean("magic_spiralize") || static_cast<int>(layer_number) < mesh_max_bottom_layer_count) //Only generate up/downskin and infill for the first X layers when spiralize is choosen.
|
||||
{
|
||||
processSkinsAndInfill(mesh, layer_number);
|
||||
processSkinsAndInfill(mesh, layer_number, process_infill);
|
||||
}
|
||||
double progress = inset_skin_progress_estimate.progress(layer_number);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers)
|
||||
void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order)
|
||||
{
|
||||
unsigned int mesh_idx = mesh_order[mesh_order_idx];
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
mesh.layer_nr_max_filled_layer = -1;
|
||||
for (unsigned int layer_idx = 0; layer_idx < mesh.layers.size(); layer_idx++)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
std::vector<PolygonsPart> new_parts;
|
||||
|
||||
for (unsigned int other_mesh_idx : mesh_order)
|
||||
{
|
||||
{ // limit the infill mesh's outline to within the infill of all meshes with lower order
|
||||
if (other_mesh_idx == mesh_idx)
|
||||
{
|
||||
break; // all previous meshes have been processed
|
||||
}
|
||||
SliceMeshStorage& other_mesh = storage.meshes[other_mesh_idx];
|
||||
if (layer_idx >= other_mesh.layers.size())
|
||||
{
|
||||
{ // there can be no interaction between the infill mesh and this other non-infill mesh
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -304,29 +411,30 @@ void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
for (SliceLayerPart& other_part : other_layer.parts)
|
||||
{
|
||||
{ // limit the outline of each part of this infill mesh to the infill of parts of the other mesh with lower infill mesh order
|
||||
if (!part.boundaryBox.hit(other_part.boundaryBox))
|
||||
{
|
||||
{ // early out
|
||||
continue;
|
||||
}
|
||||
Polygons& infill = other_part.infill_area;
|
||||
Polygons new_outline = part.outline.intersection(infill);
|
||||
Polygons new_outline = part.outline.intersection(other_part.getOwnInfillArea());
|
||||
if (new_outline.size() == 1)
|
||||
{
|
||||
{ // we don't have to call splitIntoParts, because a single polygon can only be a single part
|
||||
PolygonsPart outline_part_here;
|
||||
outline_part_here.add(new_outline[0]);
|
||||
new_parts.push_back(outline_part_here);
|
||||
}
|
||||
else if (new_outline.size() > 1)
|
||||
{
|
||||
{ // we don't know whether it's a multitude of parts because of newly introduced holes, or because the polygon has been split up
|
||||
std::vector<PolygonsPart> new_parts_here = new_outline.splitIntoParts();
|
||||
for (PolygonsPart& new_part_here : new_parts_here)
|
||||
{
|
||||
new_parts.push_back(new_part_here);
|
||||
}
|
||||
}
|
||||
infill = infill.difference(part.outline);
|
||||
other_part.infill_area_per_combine.back() = infill;
|
||||
// change the infill area of the non-infill mesh which is to be filled with e.g. lines
|
||||
other_part.infill_area_own = other_part.getOwnInfillArea().difference(part.outline);
|
||||
// note: don't change the part.infill_area, because we change the structure of that area, while the basic area in which infill is printed remains the same
|
||||
// the infill area remains the same for combing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,14 +446,22 @@ void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned
|
||||
layer.parts.back().outline = part;
|
||||
layer.parts.back().boundaryBox.calculate(part);
|
||||
}
|
||||
|
||||
if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) )
|
||||
{
|
||||
mesh.layer_nr_max_filled_layer = layer_idx; // last set by the highest non-empty layer
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers)
|
||||
void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh)
|
||||
{
|
||||
// create gradual infill areas
|
||||
SkinInfillAreaComputation::generateGradualInfill(mesh, mesh.getSettingInMicrons("gradual_infill_step_height"), mesh.getSettingAsCount("gradual_infill_steps"));
|
||||
|
||||
// combine infill
|
||||
unsigned int combined_infill_layers = mesh.getSettingInMicrons("infill_sparse_thickness") / std::max(getSettingInMicrons("layer_height"), 1); //How many infill layers to combine to obtain the requested sparse thickness.
|
||||
unsigned int combined_infill_layers = std::max(1U, round_divide(mesh.getSettingInMicrons("infill_sparse_thickness"), std::max(getSettingInMicrons("layer_height"), 1))); //How many infill layers to combine to obtain the requested sparse thickness.
|
||||
combineInfillLayers(mesh,combined_infill_layers);
|
||||
|
||||
// fuzzy skin
|
||||
@@ -361,37 +477,35 @@ void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int lay
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::SURFACE)
|
||||
{
|
||||
int inset_count = mesh.getSettingAsCount("wall_line_count");
|
||||
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
|
||||
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && ((layer_nr % 2) + 2) % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
|
||||
inset_count += 5;
|
||||
int line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
|
||||
int line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
|
||||
if (mesh.getSettingBoolean("alternate_extra_perimeter"))
|
||||
inset_count += layer_nr % 2;
|
||||
{
|
||||
inset_count += ((layer_nr % 2) + 2) % 2;
|
||||
}
|
||||
bool recompute_outline_based_on_outer_wall = mesh.getSettingBoolean("support_enable");
|
||||
WallsComputation walls_computation(mesh.getSettingInMicrons("wall_0_inset"), line_width_0, line_width_x, inset_count, recompute_outline_based_on_outer_wall);
|
||||
walls_computation.generateInsets(layer);
|
||||
}
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
{
|
||||
for (PolygonRef polyline : layer->openPolyLines)
|
||||
{
|
||||
Polygons segments;
|
||||
for (unsigned int point_idx = 1; point_idx < polyline.size(); point_idx++)
|
||||
{
|
||||
PolygonRef segment = segments.newPoly();
|
||||
segment.add(polyline[point_idx-1]);
|
||||
segment.add(polyline[point_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, const int layer_height, unsigned int& total_layers)
|
||||
{
|
||||
{
|
||||
int n_empty_first_layers = 0;
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
{
|
||||
bool layer_is_empty = true;
|
||||
if (storage.support.generated && layer_idx < storage.support.supportLayers.size())
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_idx];
|
||||
if (support_layer.supportAreas.size() > 0 || support_layer.skin.size() > 0)
|
||||
{
|
||||
layer_is_empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
@@ -422,87 +536,144 @@ void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, cons
|
||||
{
|
||||
layer.printZ -= n_empty_first_layers * layer_height;
|
||||
}
|
||||
mesh.layer_nr_max_filled_layer -= n_empty_first_layers;
|
||||
}
|
||||
total_layers -= n_empty_first_layers;
|
||||
storage.support.layer_nr_max_filled_layer -= n_empty_first_layers;
|
||||
std::vector<SupportLayer>& support_layers = storage.support.supportLayers;
|
||||
support_layers.erase(support_layers.begin(), support_layers.begin() + n_empty_first_layers);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr)
|
||||
void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr, bool process_infill)
|
||||
{
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int wall_line_count = mesh.getSettingAsCount("wall_line_count");
|
||||
int skin_extrusion_width = mesh.getSettingInMicrons("skin_line_width");
|
||||
int innermost_wall_extrusion_width = (wall_line_count == 1)? mesh.getSettingInMicrons("wall_line_width_0") : mesh.getSettingInMicrons("wall_line_width_x");
|
||||
generateSkins(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), wall_line_count, innermost_wall_extrusion_width, mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("skin_no_small_gaps_heuristic"));
|
||||
if (mesh.getSettingInMicrons("infill_line_distance") > 0)
|
||||
{
|
||||
|
||||
const int wall_line_count = mesh.getSettingAsCount("wall_line_count");
|
||||
const int innermost_wall_line_width = (wall_line_count == 1) ? mesh.getSettingInMicrons("wall_line_width_0") : mesh.getSettingInMicrons("wall_line_width_x");
|
||||
generateSkins(layer_nr, mesh, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), wall_line_count, innermost_wall_line_width, mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("skin_no_small_gaps_heuristic"));
|
||||
|
||||
if (process_infill)
|
||||
{ // process infill when infill density > 0
|
||||
// or when other infill meshes want to modify this infill
|
||||
int infill_skin_overlap = 0;
|
||||
bool infill_is_dense = mesh.getSettingInMicrons("infill_line_distance") < mesh.getSettingInMicrons("infill_line_width") + 10;
|
||||
if (!infill_is_dense && mesh.getSettingAsFillMethod("infill_pattern") != EFillMethod::CONCENTRIC)
|
||||
{
|
||||
infill_skin_overlap = skin_extrusion_width / 2;
|
||||
infill_skin_overlap = innermost_wall_line_width / 2;
|
||||
}
|
||||
generateInfill(layer_nr, mesh, innermost_wall_extrusion_width, infill_skin_overlap, wall_line_count);
|
||||
generateInfill(layer_nr, mesh, innermost_wall_line_width, infill_skin_overlap, wall_line_count);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage, unsigned int total_layers)
|
||||
void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage)
|
||||
{
|
||||
int extruder_count = storage.meshgroup->getExtruderCount();
|
||||
|
||||
std::vector<int>& max_print_height_per_extruder = storage.max_print_height_per_extruder;
|
||||
assert(max_print_height_per_extruder.size() == 0 && "storage.max_print_height_per_extruder shouldn't have been initialized yet!");
|
||||
max_print_height_per_extruder.resize(extruder_count, -1); //Initialize all as -1.
|
||||
{ // compute max_object_height_per_extruder
|
||||
//Height of the meshes themselves.
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (mesh.getSettingBoolean("anti_overhang_mesh") || mesh.getSettingBoolean("support_mesh"))
|
||||
{
|
||||
continue; //Special type of mesh that doesn't get printed.
|
||||
}
|
||||
const unsigned int extr_nr = mesh.getSettingAsIndex("extruder_nr");
|
||||
max_print_height_per_extruder[extr_nr] = std::max(max_print_height_per_extruder[extr_nr], mesh.layer_nr_max_filled_layer);
|
||||
}
|
||||
|
||||
//Height of where the support reaches.
|
||||
const unsigned int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); // TODO: support extruder should be configurable per object
|
||||
max_print_height_per_extruder[support_infill_extruder_nr] =
|
||||
std::max(max_print_height_per_extruder[support_infill_extruder_nr],
|
||||
storage.support.layer_nr_max_filled_layer);
|
||||
const unsigned int support_skin_extruder_nr = storage.getSettingAsIndex("support_interface_extruder_nr"); // TODO: support skin extruder should be configurable per object
|
||||
max_print_height_per_extruder[support_skin_extruder_nr] =
|
||||
std::max(max_print_height_per_extruder[support_skin_extruder_nr],
|
||||
storage.support.layer_nr_max_filled_layer);
|
||||
|
||||
//Height of where the platform adhesion reaches.
|
||||
if (storage.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
const unsigned int adhesion_extruder_nr = storage.getSettingAsIndex("adhesion_extruder_nr");
|
||||
max_print_height_per_extruder[adhesion_extruder_nr] =
|
||||
std::max(0, max_print_height_per_extruder[adhesion_extruder_nr]);
|
||||
}
|
||||
}
|
||||
|
||||
storage.max_print_height_order = order(max_print_height_per_extruder);
|
||||
if (extruder_count >= 2)
|
||||
{
|
||||
int second_highest_extruder = storage.max_print_height_order[extruder_count - 2];
|
||||
storage.max_print_height_second_to_last_extruder = max_print_height_per_extruder[second_highest_extruder];
|
||||
}
|
||||
else
|
||||
{
|
||||
storage.max_print_height_second_to_last_extruder = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage)
|
||||
{
|
||||
if (!getSettingBoolean("ooze_shield_enabled"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int ooze_shield_dist = getSettingInMicrons("ooze_shield_dist");
|
||||
|
||||
for(unsigned int layer_nr=0; layer_nr<total_layers; layer_nr++)
|
||||
|
||||
const int ooze_shield_dist = getSettingInMicrons("ooze_shield_dist");
|
||||
|
||||
for (int layer_nr = 0; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++)
|
||||
{
|
||||
storage.oozeShield.push_back(storage.getLayerOutlines(layer_nr, true).offset(ooze_shield_dist));
|
||||
storage.oozeShield.push_back(storage.getLayerOutlines(layer_nr, true).offset(ooze_shield_dist, ClipperLib::jtRound));
|
||||
}
|
||||
|
||||
int largest_printed_radius = MM2INT(1.0); // TODO: make var a parameter, and perhaps even a setting?
|
||||
for(unsigned int layer_nr=0; layer_nr<total_layers; layer_nr++)
|
||||
|
||||
double angle = getSettingInAngleDegrees("ooze_shield_angle");
|
||||
if (angle <= 89)
|
||||
{
|
||||
storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].offset(-largest_printed_radius).offset(largest_printed_radius);
|
||||
int allowed_angle_offset = tan(getSettingInAngleRadians("ooze_shield_angle")) * getSettingInMicrons("layer_height"); // Allow for a 60deg angle in the oozeShield.
|
||||
for (int layer_nr = 1; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++)
|
||||
{
|
||||
storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].unionPolygons(storage.oozeShield[layer_nr - 1].offset(-allowed_angle_offset));
|
||||
}
|
||||
for (int layer_nr = storage.max_print_height_second_to_last_extruder; layer_nr > 0; layer_nr--)
|
||||
{
|
||||
storage.oozeShield[layer_nr - 1] = storage.oozeShield[layer_nr - 1].unionPolygons(storage.oozeShield[layer_nr].offset(-allowed_angle_offset));
|
||||
}
|
||||
}
|
||||
int allowed_angle_offset = tan(getSettingInAngleRadians("ooze_shield_angle")) * getSettingInMicrons("layer_height");//Allow for a 60deg angle in the oozeShield.
|
||||
for(unsigned int layer_nr=1; layer_nr<total_layers; layer_nr++)
|
||||
|
||||
const float largest_printed_area = 1.0; // TODO: make var a parameter, and perhaps even a setting?
|
||||
for (int layer_nr = 0; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++)
|
||||
{
|
||||
storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].unionPolygons(storage.oozeShield[layer_nr-1].offset(-allowed_angle_offset));
|
||||
}
|
||||
for(unsigned int layer_nr=total_layers-1; layer_nr>0; layer_nr--)
|
||||
{
|
||||
storage.oozeShield[layer_nr-1] = storage.oozeShield[layer_nr-1].unionPolygons(storage.oozeShield[layer_nr].offset(-allowed_angle_offset));
|
||||
storage.oozeShield[layer_nr].removeSmallAreas(largest_printed_area);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned int total_layers)
|
||||
void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage)
|
||||
{
|
||||
int draft_shield_height = getSettingInMicrons("draft_shield_height");
|
||||
int draft_shield_dist = getSettingInMicrons("draft_shield_dist");
|
||||
int layer_height_0 = getSettingInMicrons("layer_height_0");
|
||||
int layer_height = getSettingInMicrons("layer_height");
|
||||
|
||||
if (draft_shield_height < layer_height_0)
|
||||
const unsigned int draft_shield_layers = getDraftShieldLayerCount(storage.print_layer_count);
|
||||
if (draft_shield_layers <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int max_screen_layer = (draft_shield_height - layer_height_0) / layer_height + 1;
|
||||
|
||||
int layer_skip = 500 / layer_height + 1;
|
||||
|
||||
const int layer_height = getSettingInMicrons("layer_height");
|
||||
|
||||
const unsigned int layer_skip = 500 / layer_height + 1;
|
||||
|
||||
Polygons& draft_shield = storage.draft_protection_shield;
|
||||
for (unsigned int layer_nr = 0; layer_nr < total_layers && layer_nr < max_screen_layer; layer_nr += layer_skip)
|
||||
for (unsigned int layer_nr = 0; layer_nr < storage.print_layer_count && layer_nr < draft_shield_layers; layer_nr += layer_skip)
|
||||
{
|
||||
draft_shield = draft_shield.unionPolygons(storage.getLayerOutlines(layer_nr, true));
|
||||
}
|
||||
|
||||
storage.draft_protection_shield = draft_shield.convexHull(draft_shield_dist);
|
||||
|
||||
const int draft_shield_dist = getSettingInMicrons("draft_shield_dist");
|
||||
storage.draft_protection_shield = draft_shield.approxConvexHull(draft_shield_dist);
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
|
||||
@@ -511,22 +682,20 @@ void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
|
||||
switch(getSettingAsPlatformAdhesion("adhesion_type"))
|
||||
{
|
||||
case EPlatformAdhesion::SKIRT:
|
||||
if (train->getSettingInMicrons("draft_shield_height") == 0)
|
||||
{ // draft screen replaces skirt
|
||||
generateSkirt(storage, train->getSettingInMicrons("skirt_gap"), train->getSettingAsCount("skirt_line_count"), train->getSettingInMicrons("skirt_minimal_length"));
|
||||
{
|
||||
constexpr bool outside_polygons_only = true;
|
||||
SkirtBrim::generate(storage, train->getSettingInMicrons("skirt_gap"), train->getSettingAsCount("skirt_line_count"), outside_polygons_only);
|
||||
}
|
||||
break;
|
||||
case EPlatformAdhesion::BRIM:
|
||||
generateSkirt(storage, 0, train->getSettingAsCount("brim_line_count"), train->getSettingInMicrons("skirt_minimal_length"));
|
||||
SkirtBrim::generate(storage, 0, train->getSettingAsCount("brim_line_count"), train->getSettingBoolean("brim_outside_only"));
|
||||
break;
|
||||
case EPlatformAdhesion::RAFT:
|
||||
generateRaft(storage, train->getSettingInMicrons("raft_margin"));
|
||||
Raft::generate(storage, train->getSettingInMicrons("raft_margin"));
|
||||
break;
|
||||
case EPlatformAdhesion::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
Polygons skirt_sent = storage.skirt[0];
|
||||
for (int extruder = 1; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
skirt_sent.add(storage.skirt[extruder]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+39
-21
@@ -46,7 +46,19 @@ public:
|
||||
bool generateAreas(SliceDataStorage& storage, MeshGroup* object, TimeKeeper& timeKeeper);
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \brief Helper function to get the actual height of the draft shield.
|
||||
*
|
||||
* The draft shield is the height of the print if we've set the draft shield
|
||||
* limitation to FULL. Otherwise the height is set to the height limit
|
||||
* setting. If the draft shield is disabled, the height is always 0.
|
||||
*
|
||||
* \param total_layers The total number of layers in the print (the height
|
||||
* of the draft shield if the limit is FULL.
|
||||
* \return The actual height of the draft shield.
|
||||
*/
|
||||
unsigned int getDraftShieldLayerCount(unsigned int total_layers) const;
|
||||
|
||||
/*!
|
||||
* Slice the \p object and store the outlines in the \p storage.
|
||||
*
|
||||
@@ -70,31 +82,28 @@ private:
|
||||
* Processes the outline information as stored in the \p storage: generates inset perimeter polygons, skin and infill
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh_idx The index of the mesh to process in the vector of meshes in \p storage
|
||||
* \param mesh_order_idx The index of the mesh_idx in \p mesh_order to process in the vector of meshes in \p storage
|
||||
* \param mesh_order The order in which the meshes are processed (used for infill meshes)
|
||||
* \param total_layers The total number of layers over all objects
|
||||
* \param inset_skin_progress_estimate The progress stage estimate calculator
|
||||
*/
|
||||
void processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate);
|
||||
void processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, ProgressStageEstimator& inset_skin_progress_estimate);
|
||||
|
||||
/*!
|
||||
* Process the mesh to be an infill mesh: limit all outlines to within the infill of normal meshes and subtract their volume from the infill of those meshes
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh_idx The index of the mesh to process in the vector of meshes in \p storage
|
||||
* \param mesh_order_idx The index of the mesh_idx in \p mesh_order to process in the vector of meshes in \p storage
|
||||
* \param mesh_order The order in which the meshes are processed
|
||||
* \param total_layers The total number of layers over all objects
|
||||
*/
|
||||
void processInfillMesh(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers);
|
||||
void processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order);
|
||||
|
||||
/*!
|
||||
* Process features which are derived from the basic walls, skin, and infill:
|
||||
* fuzzy skin, infill combine
|
||||
*
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param total_layers The total number of layers over all objects
|
||||
*/
|
||||
void processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers);
|
||||
void processDerivedWallsSkinInfill(SliceMeshStorage& mesh);
|
||||
|
||||
/*!
|
||||
* Remove all bottom layers which are empty.
|
||||
@@ -106,7 +115,14 @@ private:
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void removeEmptyFirstLayers(SliceDataStorage& storage, const int layer_height, unsigned int& total_layers);
|
||||
|
||||
|
||||
/*!
|
||||
* Set \ref SliceDataStorage::max_print_height_per_extruder and \ref SliceDataStorage::max_print_height_order and \ref SliceDataStorage::max_print_height_second_to_last_extruder
|
||||
*
|
||||
* \param[in,out] storage Where to retrieve mesh and support etc settings from and where the print height statistics are saved.
|
||||
*/
|
||||
void computePrintHeightStatistics(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Generate the inset polygons which form the walls.
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
@@ -117,40 +133,42 @@ private:
|
||||
/*!
|
||||
* Generate the outline of the ooze shield.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void processOozeShield(SliceDataStorage& storage, unsigned int total_layers);
|
||||
void processOozeShield(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Generate the skin areas.
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param layer_nr The layer for which to generate the skin areas.
|
||||
* \param process_infill Generate infill areas
|
||||
*/
|
||||
void processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr);
|
||||
void processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr, bool process_infill);
|
||||
|
||||
/*!
|
||||
* Generate the polygons where the draft screen should be.
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, unsigned int total_layers);
|
||||
void processDraftShield(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Generate the skirt/brim/raft areas/insets.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
*/
|
||||
void processPlatformAdhesion(SliceDataStorage& storage);
|
||||
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* Make the outer wall 'fuzzy'
|
||||
*
|
||||
* Introduce new vertices and move existing vertices in or out by a random distance, based on the fuzzy skin settings.
|
||||
*
|
||||
* This only changes the outer wall.
|
||||
*
|
||||
* \param[in,out] mesh where the outer wall is retrieved and stored in.
|
||||
*/
|
||||
void processFuzzyWalls(SliceMeshStorage& mesh);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
}//namespace cura
|
||||
#endif // FFF_AREA_GENERATOR_H
|
||||
|
||||
+2
-25
@@ -45,29 +45,6 @@ std::string FffProcessor::getAllSettingsString(MeshGroup& meshgroup, bool first_
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
bool FffProcessor::processFiles(const std::vector< std::string >& files)
|
||||
{
|
||||
time_keeper.restart();
|
||||
MeshGroup* meshgroup = new MeshGroup(this);
|
||||
|
||||
for(std::string filename : files)
|
||||
{
|
||||
log("Loading %s from disk...\n", filename.c_str());
|
||||
|
||||
FMatrix3x3 matrix;
|
||||
if (!loadMeshIntoMeshGroup(meshgroup, filename.c_str(), matrix))
|
||||
{
|
||||
logError("Failed to load model: %s\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
meshgroup->finalize();
|
||||
|
||||
log("Loaded from disk in %5.3fs\n", time_keeper.restart());
|
||||
return processMeshGroup(meshgroup);
|
||||
}
|
||||
|
||||
bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
{
|
||||
if (SHOW_ALL_SETTINGS) { logWarning(getAllSettingsString(*meshgroup, meshgroup_number == 0).c_str()); }
|
||||
@@ -83,7 +60,7 @@ bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
bool empty = true;
|
||||
for (Mesh& mesh : meshgroup->meshes)
|
||||
{
|
||||
if (!mesh.getSettingBoolean("infill_mesh"))
|
||||
if (!mesh.getSettingBoolean("infill_mesh") && !mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
empty = false;
|
||||
}
|
||||
@@ -126,7 +103,7 @@ bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->flushGcode();
|
||||
CommandSocket::getInstance()->sendLayerData();
|
||||
CommandSocket::getInstance()->sendOptimizedLayerData();
|
||||
}
|
||||
log("Total time elapsed %5.2fs.\n", time_keeper_total.restart());
|
||||
|
||||
|
||||
@@ -151,13 +151,6 @@ public:
|
||||
gcode_writer.finalize();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Process all files into one meshgroup
|
||||
*
|
||||
* \warning Unused!
|
||||
*/
|
||||
bool processFiles(const std::vector<std::string> &files);
|
||||
|
||||
/*!
|
||||
* Generate gcode for a given \p meshgroup
|
||||
* The primary function of this class.
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "utils/intpoint.h" // INT2MM
|
||||
#include "GCodePathConfig.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
GCodePathConfig::BasicConfig::BasicConfig()
|
||||
: speed(0)
|
||||
, acceleration(0)
|
||||
, jerk(0)
|
||||
, line_width(0)
|
||||
, flow(100)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
GCodePathConfig::BasicConfig::BasicConfig(double speed, double acceleration, double jerk, int line_width, double flow)
|
||||
: speed(speed)
|
||||
, acceleration(acceleration)
|
||||
, jerk(jerk)
|
||||
, line_width(line_width)
|
||||
, flow(flow)
|
||||
{
|
||||
}
|
||||
|
||||
void GCodePathConfig::BasicConfig::set(double speed, double acceleration, double jerk, int line_width, double flow)
|
||||
{
|
||||
this->speed = speed;
|
||||
this->acceleration = acceleration;
|
||||
this->jerk = jerk;
|
||||
this->line_width = line_width;
|
||||
this->flow = flow;
|
||||
}
|
||||
|
||||
|
||||
GCodePathConfig::GCodePathConfig(PrintFeatureType type)
|
||||
: extrusion_mm3_per_mm(0.0)
|
||||
, type(type)
|
||||
{
|
||||
}
|
||||
|
||||
void GCodePathConfig::init(double speed, double acceleration, double jerk, int line_width, double flow)
|
||||
{
|
||||
iconic_config.set(speed, acceleration, jerk, line_width, flow);
|
||||
current_config = iconic_config;
|
||||
}
|
||||
|
||||
void GCodePathConfig::setLayerHeight(int layer_height)
|
||||
{
|
||||
this->layer_thickness = layer_height;
|
||||
calculateExtrusion();
|
||||
}
|
||||
|
||||
void GCodePathConfig::smoothSpeed(GCodePathConfig::BasicConfig first_layer_config, int layer_nr, double max_speed_layer)
|
||||
{
|
||||
current_config.speed = (iconic_config.speed * layer_nr) / max_speed_layer + (first_layer_config.speed * (max_speed_layer - layer_nr) / max_speed_layer);
|
||||
current_config.acceleration = (iconic_config.acceleration * layer_nr) / max_speed_layer + (first_layer_config.acceleration * (max_speed_layer - layer_nr) / max_speed_layer);
|
||||
current_config.jerk = (iconic_config.jerk * layer_nr) / max_speed_layer + (first_layer_config.jerk * (max_speed_layer - layer_nr) / max_speed_layer);
|
||||
}
|
||||
|
||||
void GCodePathConfig::setSpeedIconic()
|
||||
{
|
||||
current_config.speed = iconic_config.speed;
|
||||
current_config.acceleration = iconic_config.acceleration;
|
||||
current_config.jerk = iconic_config.jerk;
|
||||
}
|
||||
|
||||
double GCodePathConfig::getExtrusionMM3perMM()
|
||||
{
|
||||
return extrusion_mm3_per_mm;
|
||||
}
|
||||
|
||||
double GCodePathConfig::getSpeed()
|
||||
{
|
||||
return current_config.speed;
|
||||
}
|
||||
|
||||
double GCodePathConfig::getAcceleration()
|
||||
{
|
||||
return current_config.acceleration;
|
||||
}
|
||||
|
||||
double GCodePathConfig::getJerk()
|
||||
{
|
||||
return current_config.jerk;
|
||||
}
|
||||
|
||||
int GCodePathConfig::getLineWidth()
|
||||
{
|
||||
return current_config.line_width;
|
||||
}
|
||||
|
||||
bool GCodePathConfig::isTravelPath()
|
||||
{
|
||||
return current_config.line_width == 0;
|
||||
}
|
||||
|
||||
double GCodePathConfig::getFlowPercentage()
|
||||
{
|
||||
return current_config.flow;
|
||||
}
|
||||
|
||||
void GCodePathConfig::calculateExtrusion()
|
||||
{
|
||||
extrusion_mm3_per_mm = INT2MM(current_config.line_width) * INT2MM(layer_thickness) * double(current_config.flow) / 100.0;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,112 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef G_CODE_PATH_CONFIG_H
|
||||
#define G_CODE_PATH_CONFIG_H
|
||||
|
||||
#include "RetractionConfig.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* The GCodePathConfig is the configuration for moves/extrusion actions. This defines at which width the line is printed and at which speed.
|
||||
*/
|
||||
class GCodePathConfig
|
||||
{
|
||||
friend class GCodePlannerTest;
|
||||
public:
|
||||
/*!
|
||||
* The path config settings which may change from layer to layer
|
||||
*/
|
||||
struct BasicConfig
|
||||
{
|
||||
double speed; //!< movement speed (mm/s)
|
||||
double acceleration; //!< acceleration of head movement (mm/s^2)
|
||||
double jerk; //!< jerk of the head movement (around stand still) (mm/s^3)
|
||||
int line_width; //!< width of the line extruded
|
||||
double flow; //!< extrusion flow modifier in %
|
||||
BasicConfig(); //!< basic contructor initializing with inaccurate values
|
||||
BasicConfig(double speed, double acceleration, double jerk, int line_width, double flow); //!< basic contructor initializing all values
|
||||
void set(double speed, double acceleration, double jerk, int line_width, double flow); //!< Set all config values
|
||||
};
|
||||
private:
|
||||
BasicConfig iconic_config; //!< The basic path configuration iconic to this print feature type
|
||||
BasicConfig current_config; //!< The current path configuration for the current layer
|
||||
int layer_thickness; //!< current layer height in micron
|
||||
double extrusion_mm3_per_mm;//!< current mm^3 filament moved per mm line traversed
|
||||
public:
|
||||
const PrintFeatureType type; //!< name of the feature type
|
||||
|
||||
/*!
|
||||
* Basic constructor.
|
||||
*/
|
||||
GCodePathConfig(PrintFeatureType type);
|
||||
|
||||
/*!
|
||||
* Initialize some of the member variables.
|
||||
*
|
||||
* \warning GCodePathConfig::setLayerHeight still has to be called before this object can be used.
|
||||
*
|
||||
* \param speed The regular speed with which to print this feature
|
||||
* \param line_width The line width for this feature
|
||||
* \param flow The flow modifier to apply to the extruded filament when printing this feature
|
||||
*/
|
||||
void init(double speed, double acceleration, double jerk, int line_width, double flow);
|
||||
|
||||
/*!
|
||||
* Set the layer height and (re)compute the extrusion_per_mm
|
||||
*/
|
||||
void setLayerHeight(int layer_height);
|
||||
|
||||
/*!
|
||||
* Set the speed to somewhere between the speed of @p first_layer_config and the iconic speed.
|
||||
*
|
||||
* \warning This functions should not be called with @p layer_nr > @p max_speed_layer !
|
||||
*
|
||||
* \param first_layer_config The speed settings at layer zero
|
||||
* \param layer_nr The layer number
|
||||
* \param max_speed_layer The layer number for which the speed_iconic should be used.
|
||||
*/
|
||||
void smoothSpeed(BasicConfig first_layer_config, int layer_nr, double max_speed_layer);
|
||||
|
||||
/*!
|
||||
* Set the speed config to the iconic speed config, i.e. the normal speed of the feature type for which this is a config.
|
||||
*
|
||||
* Does the same for acceleration and jerk.
|
||||
*/
|
||||
void setSpeedIconic();
|
||||
|
||||
/*!
|
||||
* Can only be called after the layer height has been set (which is done while writing the gcode!)
|
||||
*/
|
||||
double getExtrusionMM3perMM();
|
||||
|
||||
/*!
|
||||
* Get the movement speed in mm/s
|
||||
*/
|
||||
double getSpeed();
|
||||
|
||||
/*!
|
||||
* Get the current acceleration of this config
|
||||
*/
|
||||
double getAcceleration();
|
||||
|
||||
/*!
|
||||
* Get the current jerk of this config
|
||||
*/
|
||||
double getJerk();
|
||||
|
||||
int getLineWidth();
|
||||
|
||||
bool isTravelPath();
|
||||
|
||||
double getFlowPercentage();
|
||||
|
||||
private:
|
||||
void calculateExtrusion();
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif // G_CODE_PATH_CONFIG_H
|
||||
+310
-146
@@ -13,7 +13,7 @@ void LayerPlanBuffer::flush()
|
||||
{
|
||||
if (buffer.size() > 0)
|
||||
{
|
||||
insertPreheatCommands(); // insert preheat commands of the very last layer
|
||||
insertTempCommands(); // insert preheat commands of the very last layer
|
||||
}
|
||||
while (!buffer.empty())
|
||||
{
|
||||
@@ -30,120 +30,348 @@ void LayerPlanBuffer::flush()
|
||||
void LayerPlanBuffer::insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_after_extruder_plan_start, int extruder, double temp)
|
||||
{
|
||||
double acc_time = 0.0;
|
||||
for (unsigned int path_idx = 0; path_idx < extruder_plan_before.paths.size(); path_idx++)
|
||||
for (unsigned int path_idx = extruder_plan_before.paths.size() - 1; int(path_idx) != -1 ; path_idx--)
|
||||
{
|
||||
GCodePath& path = extruder_plan_before.paths[path_idx];
|
||||
acc_time += path.estimates.getTotalTime();
|
||||
const double time_this_path = path.estimates.getTotalTime();
|
||||
acc_time += time_this_path;
|
||||
if (acc_time > time_after_extruder_plan_start)
|
||||
{
|
||||
// logError("Inserting %f\t seconds too early!\n", acc_time - time_after_extruder_plan_start);
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, false, acc_time - time_after_extruder_plan_start);
|
||||
const double time_before_path_end = acc_time - time_after_extruder_plan_start;
|
||||
bool wait = false;
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, wait, time_this_path - time_before_path_end);
|
||||
return;
|
||||
}
|
||||
}
|
||||
extruder_plan_before.insertCommand(extruder_plan_before.paths.size(), extruder, temp, false); // insert at end of extruder plan if time_after_extruder_plan_start > extruder_plan.time
|
||||
// = special insert after all extruder plans
|
||||
bool wait = false;
|
||||
unsigned int path_idx = 0;
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, wait); // insert at start of extruder plan if time_after_extruder_plan_start > extruder_plan.time
|
||||
}
|
||||
|
||||
double LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx)
|
||||
Preheat::WarmUpResult LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = layers[layer_plan_idx]->extruder_plans[extruder_plan_idx];
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
|
||||
unsigned int extruder_plan_before_idx = extruder_plan_idx - 1;
|
||||
bool first_it = true;
|
||||
double initial_print_temp = extruder_plan.initial_printing_temperature;
|
||||
|
||||
double in_between_time = 0.0;
|
||||
for (unsigned int layer_idx = layer_plan_idx; int(layer_idx) >= 0; layer_idx--)
|
||||
{
|
||||
GCodePlanner& layer = *layers[layer_idx];
|
||||
if (!first_it)
|
||||
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{ // find a previous extruder plan where the same extruder is used to see what time this extruder wasn't used
|
||||
ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
|
||||
if (extruder_plan_before.extruder == extruder)
|
||||
{
|
||||
extruder_plan_before_idx = layer.extruder_plans.size() - 1;
|
||||
}
|
||||
for ( ; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = layer.extruder_plans[extruder_plan_before_idx];
|
||||
if (extruder_plan.extruder == extruder)
|
||||
double temp_before = preheat_config.getFinalPrintTemp(extruder);
|
||||
if (temp_before == 0)
|
||||
{
|
||||
return preheat_config.timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(in_between_time, extruder, required_temp);
|
||||
temp_before = extruder_plan_before.printing_temperature;
|
||||
}
|
||||
in_between_time += extruder_plan.estimates.getTotalTime();
|
||||
constexpr bool during_printing = false;
|
||||
Preheat::WarmUpResult warm_up = preheat_config.getWarmUpPointAfterCoolDown(in_between_time, extruder, temp_before, preheat_config.getStandbyTemp(extruder), initial_print_temp, during_printing);
|
||||
warm_up.heating_time = std::min(in_between_time, warm_up.heating_time + extra_preheat_time);
|
||||
return warm_up;
|
||||
}
|
||||
first_it = false;
|
||||
in_between_time += extruder_plan_before.estimates.getTotalTime();
|
||||
}
|
||||
// The last extruder plan with the same extruder falls outside of the buffer
|
||||
// assume the nozzle has cooled down to strandby temperature already.
|
||||
return preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(preheat_config.getStandbyTemp(extruder), extruder, required_temp, false);
|
||||
Preheat::WarmUpResult warm_up;
|
||||
warm_up.total_time_window = in_between_time;
|
||||
warm_up.lowest_temperature = preheat_config.getStandbyTemp(extruder);
|
||||
constexpr bool during_printing = false;
|
||||
warm_up.heating_time = preheat_config.getTimeToGoFromTempToTemp(extruder, warm_up.lowest_temperature, initial_print_temp, during_printing);
|
||||
if (warm_up.heating_time > in_between_time)
|
||||
{
|
||||
warm_up.heating_time = in_between_time;
|
||||
warm_up.lowest_temperature = in_between_time / preheat_config.getTimeToHeatup1Degree(extruder, during_printing);
|
||||
}
|
||||
warm_up.heating_time = warm_up.heating_time + extra_preheat_time;
|
||||
return warm_up;
|
||||
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommand_singleExtrusion(ExtruderPlan& prev_extruder_plan, int extruder, double required_temp)
|
||||
{
|
||||
// time_before_extruder_plan_end is halved, so that at the layer change the temperature will be half way betewen the two requested temperatures
|
||||
double time_before_extruder_plan_end = 0.5 * preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(prev_extruder_plan.required_temp, extruder, required_temp, true);
|
||||
double time_after_extruder_plan_start = prev_extruder_plan.estimates.getTotalTime() - time_before_extruder_plan_end;
|
||||
if (time_after_extruder_plan_start < 0)
|
||||
{
|
||||
time_after_extruder_plan_start = 0; // don't override the extruder plan with same extruder of the previous layer
|
||||
}
|
||||
|
||||
insertPreheatCommand(prev_extruder_plan, time_after_extruder_plan_start, extruder, required_temp);
|
||||
constexpr bool during_printing = true;
|
||||
double time_before_extruder_plan_end = 0.5 * preheat_config.getTimeToGoFromTempToTemp(extruder, prev_extruder_plan.printing_temperature, required_temp, during_printing);
|
||||
time_before_extruder_plan_end = std::min(prev_extruder_plan.estimates.getTotalTime(), time_before_extruder_plan_end);
|
||||
|
||||
insertPreheatCommand(prev_extruder_plan, time_before_extruder_plan_end, extruder, required_temp);
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx)
|
||||
|
||||
void LayerPlanBuffer::handleStandbyTemp(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx, double standby_temp)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = layers[layer_plan_idx]->extruder_plans[extruder_plan_idx];
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
|
||||
extruder_plan.insertCommand(0, extruder, required_temp, true); // just after the extruder switch, wait for the destination temperature to be reached
|
||||
|
||||
double time_before_extruder_plan_to_insert = timeBeforeExtruderPlanToInsert(layers, layer_plan_idx, extruder_plan_idx);
|
||||
|
||||
unsigned int extruder_plan_before_idx = extruder_plan_idx - 1;
|
||||
bool first_it = true; // Whether it's the first iteration of the for loop below
|
||||
for (unsigned int layer_idx = layer_plan_idx; int(layer_idx) >= 0; layer_idx--)
|
||||
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 2; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{
|
||||
GCodePlanner& layer = *layers[layer_idx];
|
||||
if (!first_it)
|
||||
if (extruder_plans[extruder_plan_before_idx]->extruder == extruder)
|
||||
{
|
||||
extruder_plan_before_idx = layer.extruder_plans.size() - 1;
|
||||
extruder_plans[extruder_plan_before_idx + 1]->prev_extruder_standby_temp = standby_temp;
|
||||
return;
|
||||
}
|
||||
for ( ; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{
|
||||
ExtruderPlan& extruder_plan_before = layer.extruder_plans[extruder_plan_before_idx];
|
||||
assert (extruder_plan_before.extruder != extruder);
|
||||
|
||||
double time_here = extruder_plan_before.estimates.getTotalTime();
|
||||
if (time_here >= time_before_extruder_plan_to_insert)
|
||||
{
|
||||
insertPreheatCommand(extruder_plan_before, time_here - time_before_extruder_plan_to_insert, extruder, required_temp);
|
||||
return;
|
||||
}
|
||||
time_before_extruder_plan_to_insert -= time_here;
|
||||
|
||||
}
|
||||
first_it = false;
|
||||
}
|
||||
|
||||
// time_before_extruder_plan_to_insert falls before all plans in the buffer
|
||||
ExtruderPlan& first_extruder_plan = layers[0]->extruder_plans[0];
|
||||
first_extruder_plan.insertCommand(0, extruder, required_temp, false); // insert preheat command at verfy beginning of buffer
|
||||
logWarning("Warning: Couldn't find previous extruder plan so as to set the standby temperature. Inserting temp command in earliest available layer.\n");
|
||||
ExtruderPlan& earliest_extruder_plan = *extruder_plans[0];
|
||||
constexpr bool wait = false;
|
||||
earliest_extruder_plan.insertCommand(0, extruder, standby_temp, wait);
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommand(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = layers[layer_plan_idx]->extruder_plans[extruder_plan_idx];
|
||||
void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
double initial_print_temp = extruder_plan.initial_printing_temperature;
|
||||
|
||||
|
||||
ExtruderPlan* prev_extruder_plan = nullptr;
|
||||
if (extruder_plan_idx == 0)
|
||||
Preheat::WarmUpResult heating_time_and_from_temp = timeBeforeExtruderPlanToInsert(extruder_plans, extruder_plan_idx);
|
||||
|
||||
if (heating_time_and_from_temp.total_time_window < preheat_config.getMinimalTimeWindow(extruder))
|
||||
{
|
||||
if (layer_plan_idx == 0)
|
||||
handleStandbyTemp(extruder_plans, extruder_plan_idx, initial_print_temp);
|
||||
return; // don't insert preheat command and just stay on printing temperature
|
||||
}
|
||||
else
|
||||
{
|
||||
handleStandbyTemp(extruder_plans, extruder_plan_idx, heating_time_and_from_temp.lowest_temperature);
|
||||
}
|
||||
|
||||
// handle preheat command
|
||||
double time_before_extruder_plan_to_insert = heating_time_and_from_temp.heating_time;
|
||||
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{
|
||||
ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
|
||||
assert (extruder_plan_before.extruder != extruder);
|
||||
|
||||
double time_here = extruder_plan_before.estimates.getTotalTime();
|
||||
if (time_here >= time_before_extruder_plan_to_insert)
|
||||
{
|
||||
insertPreheatCommand(extruder_plan_before, time_before_extruder_plan_to_insert, extruder, initial_print_temp);
|
||||
return;
|
||||
}
|
||||
time_before_extruder_plan_to_insert -= time_here;
|
||||
}
|
||||
|
||||
// time_before_extruder_plan_to_insert falls before all plans in the buffer
|
||||
bool wait = false;
|
||||
unsigned int path_idx = 0;
|
||||
extruder_plans[0]->insertCommand(path_idx, extruder, initial_print_temp, wait); // insert preheat command at verfy beginning of buffer
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertTempCommands(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
|
||||
|
||||
ExtruderPlan* prev_extruder_plan = extruder_plans[extruder_plan_idx - 1];
|
||||
|
||||
int prev_extruder = prev_extruder_plan->extruder;
|
||||
|
||||
if (prev_extruder != extruder)
|
||||
{ // set previous extruder to standby temperature
|
||||
extruder_plan.prev_extruder_standby_temp = preheat_config.getStandbyTemp(prev_extruder);
|
||||
}
|
||||
|
||||
if (prev_extruder == extruder)
|
||||
{
|
||||
insertPreheatCommand_singleExtrusion(*prev_extruder_plan, extruder, extruder_plan.printing_temperature);
|
||||
prev_extruder_plan->printing_temperature_command = --prev_extruder_plan->inserts.end();
|
||||
}
|
||||
else
|
||||
{
|
||||
insertPreheatCommand_multiExtrusion(extruder_plans, extruder_plan_idx);
|
||||
insertFinalPrintTempCommand(extruder_plans, extruder_plan_idx - 1);
|
||||
insertPrintTempCommand(extruder_plan);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPrintTempCommand(ExtruderPlan& extruder_plan)
|
||||
{
|
||||
unsigned int extruder = extruder_plan.extruder;
|
||||
double print_temp = extruder_plan.printing_temperature;
|
||||
|
||||
double heated_pre_travel_time = 0;
|
||||
if (preheat_config.getInitialPrintTemp(extruder) != 0)
|
||||
{ // handle heating from initial_print_temperature to printing_tempreature
|
||||
unsigned int path_idx;
|
||||
for (path_idx = 0; path_idx < extruder_plan.paths.size(); path_idx++)
|
||||
{
|
||||
GCodePath& path = extruder_plan.paths[path_idx];
|
||||
heated_pre_travel_time += path.estimates.getTotalTime();
|
||||
if (!path.isTravelPath())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool wait = false;
|
||||
extruder_plan.insertCommand(path_idx, extruder, print_temp, wait);
|
||||
}
|
||||
extruder_plan.heated_pre_travel_time = heated_pre_travel_time;
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int last_extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& last_extruder_plan = *extruder_plans[last_extruder_plan_idx];
|
||||
int extruder = last_extruder_plan.extruder;
|
||||
|
||||
double final_print_temp = preheat_config.getFinalPrintTemp(extruder);
|
||||
if (final_print_temp == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double heated_post_travel_time = 0; // The time after the last extrude move toward the end of the extruder plan during which the nozzle is stable at the final print temperature
|
||||
{ // compute heated_post_travel_time
|
||||
unsigned int path_idx;
|
||||
for (path_idx = last_extruder_plan.paths.size() - 1; int(path_idx) >= 0; path_idx--)
|
||||
{
|
||||
GCodePath& path = last_extruder_plan.paths[path_idx];
|
||||
if (!path.isTravelPath())
|
||||
{
|
||||
break;
|
||||
}
|
||||
heated_post_travel_time += path.estimates.getTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
double time_window = 0; // The time window within which the nozzle needs to heat from the initial print temp to the printing temperature and then back to the final print temp; i.e. from the first to the last extrusion move with this extruder
|
||||
double weighted_average_print_temp = 0; // The average of the normal printing temperatures of the extruder plans (which might be different due to flow dependent temp or due to initial layer temp) Weighted by time
|
||||
double initial_print_temp = -1; // The initial print temp of the first extruder plan with this extruder
|
||||
{ // compute time window and print temp statistics
|
||||
double heated_pre_travel_time = -1; // The time before the first extrude move from the start of the extruder plan during which the nozzle is stable at the initial print temperature
|
||||
for (unsigned int prev_extruder_plan_idx = last_extruder_plan_idx; (int)prev_extruder_plan_idx >= 0; prev_extruder_plan_idx--)
|
||||
{
|
||||
ExtruderPlan& prev_extruder_plan = *extruder_plans[prev_extruder_plan_idx];
|
||||
if (prev_extruder_plan.extruder != extruder)
|
||||
{
|
||||
break;
|
||||
}
|
||||
double prev_extruder_plan_time = prev_extruder_plan.estimates.getTotalTime();
|
||||
time_window += prev_extruder_plan_time;
|
||||
heated_pre_travel_time = prev_extruder_plan.heated_pre_travel_time;
|
||||
|
||||
if (prev_extruder_plan.estimates.getTotalUnretractedTime() > 0 && prev_extruder_plan.estimates.getMaterial() > 0)
|
||||
{ // handle temp statistics
|
||||
assert(prev_extruder_plan.printing_temperature != -1 && "Previous extruder plan should already have a temperature planned");
|
||||
weighted_average_print_temp += prev_extruder_plan.printing_temperature * prev_extruder_plan_time;
|
||||
initial_print_temp = prev_extruder_plan.initial_printing_temperature;
|
||||
}
|
||||
}
|
||||
weighted_average_print_temp /= time_window;
|
||||
time_window -= heated_pre_travel_time + heated_post_travel_time;
|
||||
assert(heated_pre_travel_time != -1 && "heated_pre_travel_time must have been computed; there must have been an extruder plan!");
|
||||
}
|
||||
|
||||
assert((time_window >= 0 || last_extruder_plan.estimates.getMaterial() == 0) && "Time window should always be positive if we actually extrude");
|
||||
|
||||
// ,layer change .
|
||||
// : ,precool command ,layer change .
|
||||
// : ____: : ,precool command .
|
||||
// :/ \ _____:_____: .
|
||||
// _____/ \ / \ .
|
||||
// / \ / \ .
|
||||
// / / .
|
||||
// / / .
|
||||
// .
|
||||
// approximate ^ by ^ .
|
||||
// This approximation is quite ok since it only determines where to insert the precool temp command,
|
||||
// which means the stable temperature of the previous extruder plan and the stable temperature of the next extruder plan couldn't be reached
|
||||
constexpr bool during_printing = true;
|
||||
Preheat::CoolDownResult warm_cool_result = preheat_config.getCoolDownPointAfterWarmUp(time_window, extruder, initial_print_temp, weighted_average_print_temp, final_print_temp, during_printing);
|
||||
double cool_down_time = warm_cool_result.cooling_time;
|
||||
assert(cool_down_time >= 0);
|
||||
|
||||
// find extruder plan in which to insert cooling command
|
||||
ExtruderPlan* precool_extruder_plan = &last_extruder_plan;
|
||||
{
|
||||
for (unsigned int precool_extruder_plan_idx = last_extruder_plan_idx; (int)precool_extruder_plan_idx >= 0; precool_extruder_plan_idx--)
|
||||
{
|
||||
precool_extruder_plan = extruder_plans[precool_extruder_plan_idx];
|
||||
if (precool_extruder_plan->printing_temperature_command)
|
||||
{ // the precool command ends up before the command to go to the print temperature of the next extruder plan, so remove that print temp command
|
||||
precool_extruder_plan->inserts.erase(*precool_extruder_plan->printing_temperature_command);
|
||||
}
|
||||
double time_here = precool_extruder_plan->estimates.getTotalTime();
|
||||
if (cool_down_time < time_here)
|
||||
{
|
||||
break;
|
||||
}
|
||||
cool_down_time -= time_here;
|
||||
}
|
||||
}
|
||||
|
||||
// at this point cool_down_time is what time is left if cool down time of extruder plans after precool_extruder_plan (up until last_extruder_plan) are already taken into account
|
||||
|
||||
{ // insert temp command in precool_extruder_plan
|
||||
double extrusion_time_seen = 0;
|
||||
unsigned int path_idx;
|
||||
for (path_idx = precool_extruder_plan->paths.size() - 1; int(path_idx) >= 0; path_idx--)
|
||||
{
|
||||
GCodePath& path = precool_extruder_plan->paths[path_idx];
|
||||
extrusion_time_seen += path.estimates.getTotalTime();
|
||||
if (extrusion_time_seen >= cool_down_time)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool wait = false;
|
||||
double time_after_path_start = extrusion_time_seen - cool_down_time;
|
||||
precool_extruder_plan->insertCommand(path_idx, extruder, final_print_temp, wait, time_after_path_start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LayerPlanBuffer::insertTempCommands()
|
||||
{
|
||||
if (buffer.back().extruder_plans.size() == 0 || (buffer.back().extruder_plans.size() == 1 && buffer.back().extruder_plans[0].paths.size() == 0))
|
||||
{ // disregard empty layer
|
||||
buffer.pop_back();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ExtruderPlan*> extruder_plans;
|
||||
extruder_plans.reserve(buffer.size() * 2);
|
||||
for (GCodePlanner& layer_plan : buffer)
|
||||
{
|
||||
for (ExtruderPlan& extr_plan : layer_plan.extruder_plans)
|
||||
{
|
||||
extruder_plans.push_back(&extr_plan);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// insert commands for all extruder plans on this layer
|
||||
GCodePlanner& layer_plan = buffer.back();
|
||||
for (unsigned int extruder_plan_idx = 0; extruder_plan_idx < layer_plan.extruder_plans.size(); extruder_plan_idx++)
|
||||
{
|
||||
unsigned int overall_extruder_plan_idx = extruder_plans.size() - layer_plan.extruder_plans.size() + extruder_plan_idx;
|
||||
ExtruderPlan& extruder_plan = layer_plan.extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double time = extruder_plan.estimates.getTotalUnretractedTime();
|
||||
if (time <= 0.0
|
||||
|| extruder_plan.estimates.getMaterial() == 0.0 // extruder plan only consists of moves (when an extruder switch occurs at the beginning of a layer)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double avg_flow = extruder_plan.estimates.getMaterial() / time;
|
||||
extruder_plan.printing_temperature = preheat_config.getTemp(extruder, avg_flow, extruder_plan.is_initial_layer);
|
||||
extruder_plan.initial_printing_temperature = preheat_config.getInitialPrintTemp(extruder);
|
||||
if (extruder_plan.initial_printing_temperature == 0
|
||||
|| !extruder_used_in_meshgroup[extruder]
|
||||
|| (overall_extruder_plan_idx > 0 && extruder_plans[overall_extruder_plan_idx - 1]->extruder == extruder)
|
||||
)
|
||||
{
|
||||
extruder_plan.initial_printing_temperature = extruder_plan.printing_temperature;
|
||||
extruder_used_in_meshgroup[extruder] = true;
|
||||
}
|
||||
assert(extruder_plan.printing_temperature != -1 && "extruder_plan.printing_temperature should now have been set");
|
||||
|
||||
if (buffer.size() == 1 && extruder_plan_idx == 0)
|
||||
{ // the very first extruder plan of the current meshgroup
|
||||
int extruder = extruder_plan.extruder;
|
||||
for (int extruder_idx = 0; extruder_idx < getSettingAsCount("machine_extruder_count"); extruder_idx++)
|
||||
{ // set temperature of the first nozzle, turn other nozzles down
|
||||
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
|
||||
@@ -153,7 +381,7 @@ void LayerPlanBuffer::insertPreheatCommand(std::vector<GCodePlanner*>& layers, u
|
||||
// see FffGcodeWriter::processStartingCode
|
||||
if (extruder_idx == extruder)
|
||||
{
|
||||
gcode.setInitialTemp(extruder_idx, required_temp);
|
||||
gcode.setInitialTemp(extruder_idx, extruder_plan.printing_temperature);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -162,80 +390,16 @@ void LayerPlanBuffer::insertPreheatCommand(std::vector<GCodePlanner*>& layers, u
|
||||
}
|
||||
else
|
||||
{
|
||||
if (extruder_idx == extruder)
|
||||
{
|
||||
extruder_plan.insertCommand(0, extruder, required_temp, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
extruder_plan.insertCommand(0, extruder_idx, preheat_config.getStandbyTemp(extruder_idx), false);
|
||||
if (extruder_idx != extruder)
|
||||
{ // TODO: do we need to do this?
|
||||
extruder_plan.prev_extruder_standby_temp = preheat_config.getStandbyTemp(extruder_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
prev_extruder_plan = &layers[layer_plan_idx - 1]->extruder_plans.back();
|
||||
}
|
||||
else
|
||||
{
|
||||
prev_extruder_plan = &layers[layer_plan_idx]->extruder_plans[extruder_plan_idx - 1];
|
||||
}
|
||||
assert(prev_extruder_plan != nullptr);
|
||||
|
||||
int prev_extruder = prev_extruder_plan->extruder;
|
||||
|
||||
if (prev_extruder != extruder)
|
||||
{ // set previous extruder to standby temperature
|
||||
prev_extruder_plan->insertCommand(prev_extruder_plan->paths.size(), prev_extruder, preheat_config.getStandbyTemp(prev_extruder), false);
|
||||
}
|
||||
|
||||
if (prev_extruder == extruder)
|
||||
{
|
||||
if (preheat_config.usesFlowDependentTemp(extruder))
|
||||
{
|
||||
insertPreheatCommand_singleExtrusion(*prev_extruder_plan, extruder, required_temp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
insertPreheatCommand_multiExtrusion(layers, layer_plan_idx, extruder_plan_idx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommands()
|
||||
{
|
||||
if (buffer.back().extruder_plans.size() == 0 || (buffer.back().extruder_plans.size() == 1 && buffer.back().extruder_plans[0].paths.size() == 0))
|
||||
{ // disregard empty layer
|
||||
buffer.pop_back();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<GCodePlanner*> layers;
|
||||
layers.reserve(buffer.size());
|
||||
for (GCodePlanner& layer_plan : buffer)
|
||||
{
|
||||
layers.push_back(&layer_plan);
|
||||
}
|
||||
|
||||
unsigned int layer_idx = layers.size() - 1;
|
||||
|
||||
// insert commands for all extruder plans on this layer
|
||||
GCodePlanner& layer_plan = *layers[layer_idx];
|
||||
for (unsigned int extruder_plan_idx = 0; extruder_plan_idx < layer_plan.extruder_plans.size(); extruder_plan_idx++)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = layer_plan.extruder_plans[extruder_plan_idx];
|
||||
double time = extruder_plan.estimates.getTotalUnretractedTime();
|
||||
if (time <= 0.0
|
||||
|| extruder_plan.estimates.getMaterial() == 0.0 // extruder plan only consists of moves (when an extruder switch occurs at the beginning of a layer)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
double avg_flow = extruder_plan.estimates.getMaterial() / time; // TODO: subtract retracted travel time
|
||||
extruder_plan.required_temp = preheat_config.getTemp(extruder_plan.extruder, avg_flow);
|
||||
|
||||
insertPreheatCommand(layers, layer_idx, extruder_plan_idx);
|
||||
|
||||
insertTempCommands(extruder_plans, overall_extruder_plan_idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+67
-19
@@ -1,3 +1,4 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef LAYER_PLAN_BUFFER_H
|
||||
#define LAYER_PLAN_BUFFER_H
|
||||
|
||||
@@ -15,6 +16,19 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for buffering multiple layer plans (\ref GCodePlanner) / extruder plans within those layer plans, so that temperature commands can be inserted in earlier layer plans.
|
||||
*
|
||||
* This class handles where to insert temperature commands for:
|
||||
* - initial layer temperature
|
||||
* - flow dependent temperature
|
||||
* - starting to heat up from the standby temperature
|
||||
* - initial printing temperature | printing temperature | final printing temperature
|
||||
*
|
||||
* \image html assets/precool.png "Temperature Regulation" width=10cm
|
||||
* \image latex assets/precool.png "Temperature Regulation" width=10cm
|
||||
*
|
||||
*/
|
||||
class LayerPlanBuffer : SettingsMessenger
|
||||
{
|
||||
GCodeExport& gcode;
|
||||
@@ -23,13 +37,17 @@ class LayerPlanBuffer : SettingsMessenger
|
||||
|
||||
static constexpr unsigned int buffer_size = 5; // should be as low as possible while still allowing enough time in the buffer to heat up from standby temp to printing temp // TODO: hardcoded value
|
||||
// this value should be higher than 1, cause otherwise each layer is viewed as the first layer and no temp commands are inserted.
|
||||
|
||||
|
||||
static constexpr const double extra_preheat_time = 1.0; //!< Time to start heating earlier than computed to avoid accummulative discrepancy between actual heating times and computed ones.
|
||||
|
||||
std::vector<bool> extruder_used_in_meshgroup; //!< For each extruder whether it has already been planned once in this meshgroup. This is used to see whether we should heat to the initial_print_temp or to the printing_temperature
|
||||
public:
|
||||
std::list<GCodePlanner> buffer; //!< The buffer containing several layer plans (GCodePlanner) before writing them to gcode.
|
||||
|
||||
LayerPlanBuffer(SettingsBaseVirtual* settings, GCodeExport& gcode)
|
||||
: SettingsMessenger(settings)
|
||||
, gcode(gcode)
|
||||
, extruder_used_in_meshgroup(MAX_EXTRUDERS, false)
|
||||
{ }
|
||||
|
||||
void setPreheatConfig(MeshGroup& settings)
|
||||
@@ -46,7 +64,7 @@ public:
|
||||
{
|
||||
if (buffer.size() > 0)
|
||||
{
|
||||
insertPreheatCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
|
||||
insertTempCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
|
||||
}
|
||||
buffer.emplace_back(constructor_args...);
|
||||
if (buffer.size() > buffer_size)
|
||||
@@ -65,27 +83,27 @@ public:
|
||||
* Write all remaining layer plans (GCodePlanner) to gcode and empty the buffer.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Insert the preheat command for @p extruder into @p extruder_plan_before
|
||||
*
|
||||
* \param extruder_plan_before An extruder plan before the extruder plan for which the temperature is computed, in which to insert the preheat command
|
||||
* \param time_after_extruder_plan_start The time after the start of the extruder plan, before which to insert the preheat command
|
||||
* \param time_before_extruder_plan_end The time before the end of the extruder plan, before which to insert the preheat command
|
||||
* \param extruder The extruder for which to set the temperature
|
||||
* \param temp The temperature of the preheat command
|
||||
*/
|
||||
void insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_after_extruder_plan_start, int extruder, double temp);
|
||||
void insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_before_extruder_plan_end, int extruder, double temp);
|
||||
|
||||
/*!
|
||||
* Compute the time needed to preheat, based either on the time the extruder has been on standby
|
||||
* or based on the temp of the previous extruder plan which has the same extruder nr.
|
||||
*
|
||||
* \param layers The layers in the buffer, moved to a vector
|
||||
* \param layer_plan_idx The index into @p layers in which to find the extruder plan
|
||||
* \param extruder_plan_idx The index of the extruder plan in the layer corresponding to @p layer_plan_idx for which to find the preheat time needed
|
||||
* \return the time needed to preheat
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to find the preheat time needed
|
||||
* \return the time needed to preheat and the temperature from which heating starts
|
||||
*/
|
||||
double timeBeforeExtruderPlanToInsert(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx);
|
||||
Preheat::WarmUpResult timeBeforeExtruderPlanToInsert(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
|
||||
|
||||
/*!
|
||||
* For two consecutive extruder plans of the same extruder (so on different layers),
|
||||
@@ -105,25 +123,55 @@ public:
|
||||
* and compute at what time the preheat command needs to be inserted.
|
||||
* Then insert the preheat command in the right extruder plan.
|
||||
*
|
||||
* \param layers The layers in the buffer, moved to a vector
|
||||
* \param layer_plan_idx The index into @p layers in which to find the extruder plan
|
||||
* \param extruder_plan_idx The index of the extruder plan in the layer corresponding to @p layer_plan_idx for which to find the preheat time needed
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to find the preheat time needed
|
||||
*/
|
||||
void insertPreheatCommand_multiExtrusion(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx);
|
||||
void insertPreheatCommand_multiExtrusion(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
|
||||
|
||||
/*!
|
||||
* Insert the preheat command for the extruder plan corersponding to @p extruder_plan_idx of the layer corresponding to @p layer_plan_idx.
|
||||
*
|
||||
* \param layers The layers of the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param layer_plan_idx The index of the layer plan for which to generate a preheat command
|
||||
* \param extruder_plan_idx The index of the extruder plan in the layer corresponding to @p layer_plan_idx for which to generate the preheat command
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to generate the preheat command
|
||||
*/
|
||||
void insertPreheatCommand(std::vector<GCodePlanner*>& layers, unsigned int layer_plan_idx, unsigned int extruder_plan_idx);
|
||||
void insertTempCommands(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
|
||||
|
||||
/*!
|
||||
* Insert the temperature command to heat from the initial print temperature to the printing temperature
|
||||
*
|
||||
* The temperature command is insert at the start of the very first extrusion move
|
||||
*
|
||||
* \param extruder_plan The extruder plan in which to insert the heat up command
|
||||
*/
|
||||
void insertPrintTempCommand(ExtruderPlan& extruder_plan);
|
||||
|
||||
/*!
|
||||
* Insert the temp command to start cooling from the printing temperature to the final print temp
|
||||
*
|
||||
* The print temp is inserted before the last extrusion move of the extruder plan corresponding to \p last_extruder_plan_idx
|
||||
*
|
||||
* The command is inserted at a timed offset before the end of the last extrusion move
|
||||
*
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param last_extruder_plan_idx The index of the last extruder plan in \p extruder_plans with the same extruder as previous extruder plans
|
||||
*/
|
||||
void insertFinalPrintTempCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int last_extruder_plan_idx);
|
||||
|
||||
/*!
|
||||
* Insert the preheat commands for the last added layer (unless that layer was empty)
|
||||
*/
|
||||
void insertPreheatCommands();
|
||||
void insertTempCommands();
|
||||
|
||||
/*!
|
||||
* Reconfigure the standby temperature during which we didn't print with this extruder.
|
||||
* Find the previous extruder plan with the same extruder as layers[layer_plan_idx].extruder_plans[extruder_plan_idx]
|
||||
* Set the prev_extruder_standby_temp in the next extruder plan
|
||||
*
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans before which to reconfigure the standby temperature
|
||||
* \param standby_temp The temperature to which to cool down when the extruder is in standby mode.
|
||||
*/
|
||||
void handleStandbyTemp(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx, double standby_temp);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -11,14 +11,18 @@ void MergeInfillLines::writeCompensatedMove(Point& to, double speed, GCodePath&
|
||||
{
|
||||
double old_line_width = INT2MM(last_path.config->getLineWidth());
|
||||
double new_line_width_mm = INT2MM(new_line_width);
|
||||
double speed_mod = old_line_width / new_line_width_mm;
|
||||
double extrusion_mod = new_line_width_mm / old_line_width;
|
||||
double new_speed = std::min(speed * speed_mod, 150.0); // TODO: hardcoded value: max extrusion speed is 150 mm/s = 9000 mm/min
|
||||
sendPolygon(last_path.config->type, gcode.getPositionXY(), to, last_path.getLineWidth());
|
||||
double new_speed = speed;
|
||||
if (speed_equalize_flow_enabled)
|
||||
{
|
||||
double speed_mod = old_line_width / new_line_width_mm;
|
||||
new_speed = std::min(speed * speed_mod, speed_equalize_flow_max);
|
||||
}
|
||||
sendLineTo(last_path.config->type, to, last_path.getLineWidth());
|
||||
gcode.writeMove(to, new_speed, last_path.getExtrusionMM3perMM() * extrusion_mod);
|
||||
}
|
||||
|
||||
bool MergeInfillLines::mergeInfillLines(double speed, unsigned int& path_idx)
|
||||
bool MergeInfillLines::mergeInfillLines(unsigned int& path_idx)
|
||||
{ //Check for lots of small moves and combine them into one large line
|
||||
Point prev_middle;
|
||||
Point last_middle;
|
||||
@@ -31,12 +35,12 @@ bool MergeInfillLines::mergeInfillLines(double speed, unsigned int& path_idx)
|
||||
GCodePath& move_path = paths[path_idx];
|
||||
for(unsigned int point_idx = 0; point_idx < move_path.points.size() - 1; point_idx++)
|
||||
{
|
||||
gcode.writeMove(move_path.points[point_idx], speed, move_path.getExtrusionMM3perMM());
|
||||
gcode.writeMove(move_path.points[point_idx], move_path.config->getSpeed() * extruder_plan.getTravelSpeedFactor(), move_path.getExtrusionMM3perMM());
|
||||
}
|
||||
gcode.writeMove(prev_middle, travelConfig.getSpeed(), 0);
|
||||
GCodePath& last_path = paths[path_idx + 3];
|
||||
|
||||
writeCompensatedMove(last_middle, speed, last_path, line_width);
|
||||
writeCompensatedMove(last_middle, last_path.config->getSpeed() * extruder_plan.getExtrudeSpeedFactor(), last_path, line_width);
|
||||
}
|
||||
|
||||
path_idx += 2;
|
||||
@@ -45,7 +49,7 @@ bool MergeInfillLines::mergeInfillLines(double speed, unsigned int& path_idx)
|
||||
{
|
||||
extruder_plan.handleInserts(path_idx, gcode);
|
||||
GCodePath& last_path = paths[path_idx + 3];
|
||||
writeCompensatedMove(last_middle, speed, last_path, line_width);
|
||||
writeCompensatedMove(last_middle, last_path.config->getSpeed() * extruder_plan.getExtrudeSpeedFactor(), last_path, line_width);
|
||||
}
|
||||
path_idx = path_idx + 1; // means that the next path considered is the travel path after the converted extrusion path corresponding to the updated path_idx
|
||||
extruder_plan.handleInserts(path_idx, gcode);
|
||||
@@ -227,4 +231,4 @@ void MergeInfillLines::merge(Point& from, Point& p0, Point& p1)
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
}//namespace cura
|
||||
|
||||
+10
-16
@@ -4,6 +4,7 @@
|
||||
#include "utils/intpoint.h"
|
||||
#include "gcodeExport.h"
|
||||
#include "gcodePlanner.h"
|
||||
#include "GCodePathConfig.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -18,6 +19,8 @@ class MergeInfillLines
|
||||
|
||||
GCodePathConfig& travelConfig; //!< The travel settings used to see whether a path is a travel path or an extrusion path
|
||||
int64_t nozzle_size; //!< The diameter of the hole in the nozzle
|
||||
bool speed_equalize_flow_enabled; //!< Should the speed be varied with extrusion width
|
||||
double speed_equalize_flow_max; //!< Maximum speed when adjusting speed for flow
|
||||
|
||||
/*!
|
||||
* Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
|
||||
@@ -61,8 +64,8 @@ public:
|
||||
/*!
|
||||
* Simple constructor only used by MergeInfillLines::isConvertible to easily convey the environment
|
||||
*/
|
||||
MergeInfillLines(GCodeExport& gcode, int layer_nr, std::vector<GCodePath>& paths, ExtruderPlan& extruder_plan, GCodePathConfig& travelConfig, int64_t nozzle_size)
|
||||
: gcode(gcode), layer_nr(layer_nr), paths(paths), extruder_plan(extruder_plan), travelConfig(travelConfig), nozzle_size(nozzle_size) { }
|
||||
MergeInfillLines(GCodeExport& gcode, int layer_nr, std::vector<GCodePath>& paths, ExtruderPlan& extruder_plan, GCodePathConfig& travelConfig, int64_t nozzle_size, bool speed_equalize_flow_enabled, double speed_equalize_flow_max)
|
||||
: gcode(gcode), layer_nr(layer_nr), paths(paths), extruder_plan(extruder_plan), travelConfig(travelConfig), nozzle_size(nozzle_size), speed_equalize_flow_enabled(speed_equalize_flow_enabled), speed_equalize_flow_max(speed_equalize_flow_max) { }
|
||||
|
||||
/*!
|
||||
* Check for lots of small moves and combine them into one large line.
|
||||
@@ -72,28 +75,19 @@ public:
|
||||
* \param paths The paths currently under consideration
|
||||
* \param travelConfig The travel settings used to see whether a path is a travel path or an extrusion path
|
||||
* \param nozzle_size The diameter of the hole in the nozzle
|
||||
* \param speed A factor used to scale the movement speed
|
||||
* \param path_idx Input/Output parameter: The current index in \p paths where to start combining and the current index after combining as output parameter.
|
||||
* \return Whether lines have been merged and normal path-to-gcode generation can be skipped for the current resulting \p path_idx .
|
||||
*/
|
||||
bool mergeInfillLines(double speed, unsigned int& path_idx);
|
||||
bool mergeInfillLines(unsigned int& path_idx);
|
||||
|
||||
/*!
|
||||
* send a polygon through the command socket from the previous point to the given point
|
||||
* send a line segment through the command socket from the previous point to the given point \p to
|
||||
*/
|
||||
void sendPolygon(PrintFeatureType print_feature_type, Point from, Point to, int line_width)
|
||||
void sendLineTo(PrintFeatureType print_feature_type, Point to, int line_width)
|
||||
{
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
// we should send this travel as a non-retraction move
|
||||
cura::Polygons pathPoly;
|
||||
PolygonRef path = pathPoly.newPoly();
|
||||
path.add(from);
|
||||
path.add(to);
|
||||
CommandSocket::getInstance()->sendPolygons(print_feature_type, layer_nr, pathPoly, line_width);
|
||||
}
|
||||
CommandSocket::sendLineTo(print_feature_type, to, line_width);
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif // MERGE_INFILL_LINES_H
|
||||
#endif // MERGE_INFILL_LINES_H
|
||||
|
||||
+54
-4
@@ -4,9 +4,12 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "MeshGroup.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/string.h"
|
||||
|
||||
#include "settings/SettingRegistry.h" // loadExtruderJSONsettings
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
@@ -44,7 +47,7 @@ MeshGroup::~MeshGroup()
|
||||
}
|
||||
}
|
||||
|
||||
int MeshGroup::getExtruderCount()
|
||||
int MeshGroup::getExtruderCount() const
|
||||
{
|
||||
if (extruder_count == -1)
|
||||
{
|
||||
@@ -54,13 +57,19 @@ int MeshGroup::getExtruderCount()
|
||||
}
|
||||
|
||||
ExtruderTrain* MeshGroup::createExtruderTrain(unsigned int extruder_nr)
|
||||
{
|
||||
if (!extruders[extruder_nr])
|
||||
{
|
||||
if (!extruders[extruder_nr])
|
||||
extruders[extruder_nr] = new ExtruderTrain(this, extruder_nr);
|
||||
int err = SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_nr, extruders[extruder_nr]);
|
||||
if (err)
|
||||
{
|
||||
extruders[extruder_nr] = new ExtruderTrain(this, extruder_nr);
|
||||
logError("Couldn't load extruder.def.json for extruder %i\n", extruder_nr);
|
||||
std::exit(1);
|
||||
}
|
||||
return extruders[extruder_nr];
|
||||
}
|
||||
return extruders[extruder_nr];
|
||||
}
|
||||
|
||||
ExtruderTrain* MeshGroup::getExtruderTrain(unsigned int extruder_nr)
|
||||
{
|
||||
@@ -118,6 +127,44 @@ void MeshGroup::clear()
|
||||
|
||||
void MeshGroup::finalize()
|
||||
{
|
||||
extruder_count = getSettingAsCount("machine_extruder_count");
|
||||
|
||||
for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
createExtruderTrain(extruder_nr); // create it if it didn't exist yet
|
||||
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") == extruder_nr && getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
getExtruderTrain(extruder_nr)->setIsUsed(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const Mesh& mesh : meshes)
|
||||
{
|
||||
if (mesh.getSettingBoolean("support_enable")
|
||||
&& (
|
||||
getSettingAsIndex("support_infill_extruder_nr") == extruder_nr
|
||||
|| getSettingAsIndex("support_extruder_nr_layer_0") == extruder_nr
|
||||
|| (getSettingBoolean("support_interface_enable") && getSettingAsIndex("support_interface_extruder_nr") == extruder_nr)
|
||||
)
|
||||
)
|
||||
{
|
||||
getExtruderTrain(extruder_nr)->setIsUsed(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const Mesh& mesh : meshes)
|
||||
{
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh.getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
getExtruderTrain(mesh.getSettingAsIndex("extruder_nr"))->setIsUsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
//If the machine settings have been supplied, offset the given position vertices to the center of vertices (0,0,0) is at the bed center.
|
||||
Point3 meshgroup_offset(0, 0, 0);
|
||||
if (!getSettingBoolean("machine_center_is_zero"))
|
||||
@@ -283,6 +330,8 @@ bool loadMeshSTL(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
|
||||
|
||||
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings)
|
||||
{
|
||||
TimeKeeper load_timer;
|
||||
|
||||
const char* ext = strrchr(filename, '.');
|
||||
if (ext && (strcmp(ext, ".stl") == 0 || strcmp(ext, ".STL") == 0))
|
||||
{
|
||||
@@ -290,6 +339,7 @@ bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMa
|
||||
if(loadMeshSTL(&mesh,filename,transformation)) //Load it! If successful...
|
||||
{
|
||||
meshgroup->meshes.push_back(mesh);
|
||||
log("loading '%s' took %.3f seconds\n",filename,load_timer.restart());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -18,9 +18,9 @@ namespace cura
|
||||
class MeshGroup : public SettingsBase, NoCopy
|
||||
{
|
||||
ExtruderTrain* extruders[MAX_EXTRUDERS] = {nullptr};
|
||||
int extruder_count;
|
||||
mutable int extruder_count; //!< The number of extruders. (mutable because of lazy evaluation)
|
||||
public:
|
||||
int getExtruderCount();
|
||||
int getExtruderCount() const;
|
||||
|
||||
MeshGroup(SettingsBaseVirtual* settings_base);
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
#include "Preheat.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void Preheat::setConfig(const MeshGroup& meshgroup)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < meshgroup.getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
assert(meshgroup.getExtruderTrain(extruder_nr) != nullptr);
|
||||
const ExtruderTrain& extruder_train = *meshgroup.getExtruderTrain(extruder_nr);
|
||||
config_per_extruder.emplace_back();
|
||||
Config& config = config_per_extruder.back();
|
||||
double machine_nozzle_cool_down_speed = extruder_train.getSettingInSeconds("machine_nozzle_cool_down_speed");
|
||||
double machine_nozzle_heat_up_speed = extruder_train.getSettingInSeconds("machine_nozzle_heat_up_speed");
|
||||
double material_extrusion_cool_down_speed = extruder_train.getSettingInSeconds("material_extrusion_cool_down_speed");
|
||||
assert(material_extrusion_cool_down_speed < machine_nozzle_heat_up_speed && "The extrusion cooldown speed must be smaller than the heat up speed; otherwise the printing temperature cannot be reached!");
|
||||
config.time_to_cooldown_1_degree[0] = 1.0 / machine_nozzle_cool_down_speed;
|
||||
config.time_to_heatup_1_degree[0] = 1.0 / machine_nozzle_heat_up_speed;
|
||||
config.time_to_cooldown_1_degree[1] = 1.0 / (machine_nozzle_cool_down_speed + material_extrusion_cool_down_speed);
|
||||
config.time_to_heatup_1_degree[1] = 1.0 / (machine_nozzle_heat_up_speed - material_extrusion_cool_down_speed);
|
||||
config.standby_temp = extruder_train.getSettingInSeconds("material_standby_temperature");
|
||||
|
||||
config.min_time_window = extruder_train.getSettingInSeconds("machine_min_cool_heat_time_window");
|
||||
|
||||
config.material_print_temperature = extruder_train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
config.material_print_temperature_layer_0 = extruder_train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
config.material_initial_print_temperature = extruder_train.getSettingInDegreeCelsius("material_initial_print_temperature");
|
||||
config.material_final_print_temperature = extruder_train.getSettingInDegreeCelsius("material_final_print_temperature");
|
||||
|
||||
config.flow_dependent_temperature = extruder_train.getSettingBoolean("material_flow_dependent_temperature");
|
||||
|
||||
config.flow_temp_graph = extruder_train.getSettingAsFlowTempGraph("material_flow_temp_graph"); // [[0.1,180],[20,230]]
|
||||
}
|
||||
}
|
||||
|
||||
double Preheat::getTimeToGoFromTempToTemp(int extruder, double temp_before, double temp_after, bool during_printing)
|
||||
{
|
||||
Config& config = config_per_extruder[extruder];
|
||||
double time;
|
||||
if (temp_after > temp_before)
|
||||
{
|
||||
time = (temp_after - temp_before) * config.time_to_heatup_1_degree[during_printing];
|
||||
}
|
||||
else
|
||||
{
|
||||
time = (temp_before - temp_after) * config.time_to_cooldown_1_degree[during_printing];
|
||||
}
|
||||
return std::max(0.0, time);
|
||||
}
|
||||
|
||||
double Preheat::getTemp(unsigned int extruder, double flow, bool is_initial_layer)
|
||||
{
|
||||
if (is_initial_layer && config_per_extruder[extruder].material_print_temperature_layer_0 != 0)
|
||||
{
|
||||
return config_per_extruder[extruder].material_print_temperature_layer_0;
|
||||
}
|
||||
return config_per_extruder[extruder].flow_temp_graph.getTemp(flow, config_per_extruder[extruder].material_print_temperature, config_per_extruder[extruder].flow_dependent_temperature);
|
||||
}
|
||||
|
||||
Preheat::WarmUpResult Preheat::getWarmUpPointAfterCoolDown(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing)
|
||||
{
|
||||
WarmUpResult result;
|
||||
const Config& config = config_per_extruder[extruder];
|
||||
double time_to_cooldown_1_degree = config.time_to_cooldown_1_degree[during_printing];
|
||||
double time_to_heatup_1_degree = config.time_to_heatup_1_degree[during_printing];
|
||||
result.total_time_window = time_window;
|
||||
|
||||
// ,temp_end
|
||||
// / .
|
||||
// ,temp_start / .
|
||||
// \ ' ' ' ' '/ ' ' '> outer_temp .
|
||||
// \________/ .
|
||||
// "-> temp_mid
|
||||
// ^^^^^^^^^^
|
||||
// limited_time_window
|
||||
double outer_temp;
|
||||
double limited_time_window;
|
||||
if (temp_start < temp_end)
|
||||
{ // extra time needed during heating
|
||||
double extra_heatup_time = (temp_end - temp_start) * time_to_heatup_1_degree;
|
||||
result.heating_time = extra_heatup_time;
|
||||
limited_time_window = time_window - extra_heatup_time;
|
||||
outer_temp = temp_start;
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.heating_time = 0.0;
|
||||
result.lowest_temperature = temp_start;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double extra_cooldown_time = (temp_start - temp_end) * time_to_cooldown_1_degree;
|
||||
result.heating_time = 0;
|
||||
limited_time_window = time_window - extra_cooldown_time;
|
||||
outer_temp = temp_end;
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.heating_time = 0.0;
|
||||
result.lowest_temperature = temp_end;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
double time_ratio_cooldown_heatup = time_to_cooldown_1_degree / time_to_heatup_1_degree;
|
||||
double time_to_heat_from_standby_to_print_temp = getTimeToGoFromTempToTemp(extruder, temp_mid, outer_temp, during_printing);
|
||||
double time_needed_to_reach_standby_temp = time_to_heat_from_standby_to_print_temp * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_standby_temp < limited_time_window)
|
||||
{
|
||||
result.heating_time += time_to_heat_from_standby_to_print_temp;
|
||||
result.lowest_temperature = temp_mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.heating_time += limited_time_window * time_to_heatup_1_degree / (time_to_cooldown_1_degree + time_to_heatup_1_degree);
|
||||
result.lowest_temperature = std::max(temp_mid, temp_end - result.heating_time / time_to_heatup_1_degree);
|
||||
}
|
||||
|
||||
if (result.heating_time > time_window || result.heating_time < 0.0)
|
||||
{
|
||||
logWarning("getWarmUpPointAfterCoolDown returns result outside of the time window!");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Preheat::CoolDownResult Preheat::getCoolDownPointAfterWarmUp(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing)
|
||||
{
|
||||
CoolDownResult result;
|
||||
const Config& config = config_per_extruder[extruder];
|
||||
double time_to_cooldown_1_degree = config.time_to_cooldown_1_degree[during_printing];
|
||||
double time_to_heatup_1_degree = config.time_to_heatup_1_degree[during_printing];
|
||||
|
||||
assert(temp_start != -1 && temp_mid != -1 && temp_end != -1 && "temperatures must be initialized!");
|
||||
|
||||
result.total_time_window = time_window;
|
||||
|
||||
// limited_time_window
|
||||
// :^^^^^^^^^^^^:
|
||||
// : ________. : . . .> temp_mid
|
||||
// : / \ : .
|
||||
// :/ . . . . .\:. . .> outer_temp .
|
||||
// ^temp_start \ .
|
||||
// \ .
|
||||
// ^temp_end
|
||||
double outer_temp;
|
||||
double limited_time_window;
|
||||
if (temp_start < temp_end)
|
||||
{ // extra time needed during heating
|
||||
double extra_heatup_time = (temp_end - temp_start) * time_to_heatup_1_degree;
|
||||
result.cooling_time = 0;
|
||||
limited_time_window = time_window - extra_heatup_time;
|
||||
outer_temp = temp_end;
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.cooling_time = 0.0;
|
||||
result.highest_temperature = temp_end;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double extra_cooldown_time = (temp_start - temp_end) * time_to_cooldown_1_degree;
|
||||
result.cooling_time = extra_cooldown_time;
|
||||
limited_time_window = time_window - extra_cooldown_time;
|
||||
outer_temp = temp_start;
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.cooling_time = 0.0;
|
||||
result.highest_temperature = temp_start;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
double time_ratio_cooldown_heatup = time_to_cooldown_1_degree / time_to_heatup_1_degree;
|
||||
double cool_down_time = getTimeToGoFromTempToTemp(extruder, temp_mid, outer_temp, during_printing);
|
||||
double time_needed_to_reach_temp1 = cool_down_time * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_temp1 < limited_time_window)
|
||||
{
|
||||
result.cooling_time += cool_down_time;
|
||||
result.highest_temperature = temp_mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.cooling_time += limited_time_window * time_to_heatup_1_degree / (time_to_cooldown_1_degree + time_to_heatup_1_degree);
|
||||
result.highest_temperature = std::min(temp_mid, temp_end + result.cooling_time / time_to_cooldown_1_degree);
|
||||
}
|
||||
|
||||
if (result.cooling_time > time_window || result.cooling_time < 0.0)
|
||||
{
|
||||
logWarning("getCoolDownPointAfterWarmUp returns result outside of the time window!");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
+123
-98
@@ -26,15 +26,21 @@ class Preheat
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
double time_to_heatup_1_degree; //!< average time it takes to heat up one degree (in the range of normal print temperatures and standby temperature)
|
||||
double time_to_cooldown_1_degree; //!< average time it takes to cool down one degree (in the range of normal print temperatures and standby temperature)
|
||||
|
||||
double heatup_cooldown_time_mod_while_printing; //!< The time to be added to Preheat::time_to_heatup_1_degree and subtracted from Preheat::time_to_cooldown_1_degree to get the timings while printing
|
||||
double time_to_heatup_1_degree[2]; //!< average time it takes to heat up one degree (in the range of normal print temperatures and standby temperature), while not-printing and while printing
|
||||
double time_to_cooldown_1_degree[2]; //!< average time it takes to cool down one degree (in the range of normal print temperatures and standby temperature), while not-printing and while printing
|
||||
|
||||
double standby_temp; //!< The temperature at which the nozzle rests when it is not printing.
|
||||
|
||||
double min_time_window; //!< Minimal time (in seconds) to allow an extruder to cool down and then warm up again.
|
||||
|
||||
double material_print_temperature; //!< default print temp (backward compatilibily)
|
||||
|
||||
|
||||
double material_print_temperature_layer_0; //!< initial layer print temp
|
||||
|
||||
double material_initial_print_temperature; //!< print temp when first starting to extrude after a layer switch
|
||||
|
||||
double material_final_print_temperature; //!< print temp at the end of all extrusion moves of an extruder to which it's cooled down just before - during the extrusion
|
||||
|
||||
bool flow_dependent_temperature; //!< Whether to make the temperature dependent on flow
|
||||
|
||||
FlowTempGraph flow_temp_graph; //!< The graph linking flows to corresponding temperatures
|
||||
@@ -42,6 +48,26 @@ class Preheat
|
||||
|
||||
std::vector<Config> config_per_extruder;//!< the nozzle and material temperature settings for each extruder train.
|
||||
public:
|
||||
/*!
|
||||
* The type of result when computing when to start heating up a nozzle before it's going to be used again.
|
||||
*/
|
||||
struct WarmUpResult
|
||||
{
|
||||
double total_time_window; //!< The total time in which cooling and heating takes place.
|
||||
double heating_time; //!< The total time needed to heat to the required temperature.
|
||||
double lowest_temperature; //!< The lower temperature from which heating starts.
|
||||
};
|
||||
|
||||
/*!
|
||||
* The type of result when computing when to start cooling down a nozzle before it's not going to be used again.
|
||||
*/
|
||||
struct CoolDownResult
|
||||
{
|
||||
double total_time_window; //!< The total time in which heating and cooling takes place.
|
||||
double cooling_time; //!< The total time needed to cool down to the required temperature.
|
||||
double highest_temperature; //!< The upper temperature from which cooling starts.
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the standby temperature of an extruder train
|
||||
* \param extruder the extruder train for which to get the standby tmep
|
||||
@@ -51,126 +77,125 @@ public:
|
||||
{
|
||||
return config_per_extruder[extruder].standby_temp;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Get the time it takes to heat up one degree celsius
|
||||
*
|
||||
* \param extruder the extruder train for which to get time it takes to heat up one degree celsius
|
||||
* \param during_printing whether the heating takes time during printing or when idle
|
||||
* \return the time it takes to heat up one degree celsius
|
||||
*/
|
||||
double getTimeToHeatup1Degree(int extruder, bool during_printing)
|
||||
{
|
||||
return config_per_extruder[extruder].time_to_heatup_1_degree[during_printing];
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the initial print temperature when starting to extrude.
|
||||
* \param during_printing whether the heating takes time during printing or when idle
|
||||
*/
|
||||
double getInitialPrintTemp(int extruder)
|
||||
{
|
||||
return config_per_extruder[extruder].material_initial_print_temperature;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the final print temperature at the end of all extrusion moves with the current extruder
|
||||
*/
|
||||
double getFinalPrintTemp(int extruder)
|
||||
{
|
||||
return config_per_extruder[extruder].material_final_print_temperature;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the nozzle and material temperature settings for each extruder train.
|
||||
* \param meshgroup Where to get settings from
|
||||
*/
|
||||
void setConfig(MeshGroup& settings)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < settings.getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
assert(settings.getExtruderTrain(extruder_nr) != nullptr);
|
||||
ExtruderTrain& extruder_train = *settings.getExtruderTrain(extruder_nr);
|
||||
config_per_extruder.emplace_back();
|
||||
Config& config = config_per_extruder.back();
|
||||
config.time_to_cooldown_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_cool_down_speed"); // 0.5
|
||||
config.time_to_heatup_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_heat_up_speed"); // 0.5
|
||||
config.heatup_cooldown_time_mod_while_printing = 1.0 / extruder_train.getSettingInSeconds("material_extrusion_cool_down_speed"); // 0.1
|
||||
config.standby_temp = extruder_train.getSettingInSeconds("material_standby_temperature"); // 150
|
||||
|
||||
config.material_print_temperature = extruder_train.getSettingInDegreeCelsius("material_print_temperature"); // 220
|
||||
|
||||
config.flow_dependent_temperature = extruder_train.getSettingBoolean("material_flow_dependent_temperature");
|
||||
|
||||
config.flow_temp_graph = extruder_train.getSettingAsFlowTempGraph("material_flow_temp_graph"); // [[0.1,180],[20,230]]
|
||||
}
|
||||
}
|
||||
|
||||
void setConfig(const MeshGroup& meshgroup);
|
||||
|
||||
bool usesFlowDependentTemp(int extruder_nr)
|
||||
{
|
||||
return config_per_extruder[extruder_nr].flow_dependent_temperature;
|
||||
}
|
||||
private:
|
||||
/*!
|
||||
* Calculate time to heat up from standby temperature to a given temperature.
|
||||
* Assumes @p temp is higher than the standby temperature.
|
||||
*
|
||||
* \param extruder The extruder for which to get the time
|
||||
* \param temp The temperature to be reached
|
||||
*/
|
||||
double timeToHeatFromStandbyToPrintTemp(unsigned int extruder, double temp)
|
||||
{
|
||||
return (temp - config_per_extruder[extruder].standby_temp) * config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Get the optimal temperature corresponding to a given average flow.
|
||||
* Get the optimal temperature corresponding to a given average flow,
|
||||
* or the initial layer temperature.
|
||||
*
|
||||
* \param extruder The extruder train
|
||||
* \param flow The flow for which to get the optimal temperature
|
||||
* \param is_initial_layer Whether the initial layer temperature should be returned instead of flow-based temperature
|
||||
* \return The corresponding optimal temperature
|
||||
*/
|
||||
double getTemp(unsigned int extruder, double flow)
|
||||
{
|
||||
return config_per_extruder[extruder].flow_temp_graph.getTemp(flow, config_per_extruder[extruder].material_print_temperature, config_per_extruder[extruder].flow_dependent_temperature);
|
||||
}
|
||||
|
||||
double getTemp(unsigned int extruder, double flow, bool is_initial_layer);
|
||||
|
||||
/*!
|
||||
* Decide when to start warming up again after starting to cool down towards the standby temperature.
|
||||
* Return the minimal time window of a specific extruder for letting an unused extruder cool down and warm up again
|
||||
* \param extruder The extruder for which to get the minimal time window
|
||||
* \return the minimal time window of a specific extruder for letting an unused extruder cool down and warm up again
|
||||
*/
|
||||
double getMinimalTimeWindow(unsigned int extruder)
|
||||
{
|
||||
return config_per_extruder[extruder].min_time_window;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Decide when to start warming up again after starting to cool down towards \p temp_mid.
|
||||
* Two cases are considered:
|
||||
* the case where the standby temperature is reached \__/ .
|
||||
* and the case where it isn't \/ .
|
||||
*
|
||||
* IT is assumed that the printer is not printing during this cool down and warm up time.
|
||||
*
|
||||
* Assumes from_temp is approximately the same as @p temp
|
||||
* \warning it is assumed that \p temp_mid is lower than both \p temp_start and \p temp_end. If not somewhat weird results may follow.
|
||||
*
|
||||
// ,temp_end
|
||||
// / .
|
||||
// ,temp_start / .
|
||||
// \ / .
|
||||
// \________/ .
|
||||
// "-> temp_mid
|
||||
* \param window_time The time window within which the cooldown and heat up must take place.
|
||||
* \param extruder The extruder used
|
||||
* \param temp The temperature to which to heat
|
||||
* \return The time before the end of the @p time_window to insert the preheat command
|
||||
* \param temp_start The temperature from which to start cooling down
|
||||
* \param temp_mid The temeprature to which we try to cool down
|
||||
* \param temp_end The temperature to which we need to have heated up at the end of the \p time_window
|
||||
* \param during_printing Whether the warming up and cooling down is performed during printing
|
||||
* \return The time before the end of the @p time_window to insert the preheat command and the temperature from which the heating starts
|
||||
*/
|
||||
double timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(double time_window, unsigned int extruder, double temp)
|
||||
{
|
||||
double time_ratio_cooldown_heatup = config_per_extruder[extruder].time_to_cooldown_1_degree / config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
double time_to_heat_from_standby_to_print_temp = timeToHeatFromStandbyToPrintTemp(extruder, temp);
|
||||
double time_needed_to_reach_standby_temp = time_to_heat_from_standby_to_print_temp * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_standby_temp < time_window)
|
||||
{
|
||||
return time_to_heat_from_standby_to_print_temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return time_window * config_per_extruder[extruder].time_to_heatup_1_degree / (config_per_extruder[extruder].time_to_cooldown_1_degree + config_per_extruder[extruder].time_to_heatup_1_degree);
|
||||
}
|
||||
}
|
||||
WarmUpResult getWarmUpPointAfterCoolDown(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing);
|
||||
|
||||
/*!
|
||||
* Calculate time needed to warm up the nozzle from a given temp to a given temp.
|
||||
* If the printer is printing in the mean time the warming up will take longer.
|
||||
* Decide when to start cooling down again after starting to warm up towards the \p temp_mid
|
||||
* Two cases are considered:
|
||||
* the case where the temperature is reached /"""\ .
|
||||
* and the case where it isn't /\ .
|
||||
*
|
||||
* \warning it is assumed that \p temp_mid is higher than both \p temp_start and \p temp_end. If not somewhat weird results may follow.
|
||||
*
|
||||
* \param from_temp The temperature at which the nozzle was before
|
||||
// _> temp_mid
|
||||
// /""""""""\ .
|
||||
// / \ .
|
||||
// ^temp_start \ .
|
||||
// \ .
|
||||
// ^temp_end
|
||||
* \param window_time The time window within which the cooldown and heat up must take place.
|
||||
* \param extruder The extruder used
|
||||
* \param temp The temperature to which to heat
|
||||
* \param printing Whether the printer is printing in the time to heat up the nozzle
|
||||
* \return The time needed to reach the desired temperature (@p temp)
|
||||
* \param temp_start The temperature from which to start heating up
|
||||
* \param temp_mid The temeprature to which we try to heat up
|
||||
* \param temp_end The temperature to which we need to have cooled down after \p time_window time
|
||||
* \param during_printing Whether the warming up and cooling down is performed during printing
|
||||
* \return The time before the end of the \p time_window to insert the preheat command and the temperature from which the cooling starts
|
||||
*/
|
||||
double timeBeforeEndToInsertPreheatCommand_warmUp(double from_temp, unsigned int extruder, double temp, bool printing)
|
||||
{
|
||||
if (temp > from_temp)
|
||||
{
|
||||
if (printing)
|
||||
{
|
||||
return (temp - from_temp) * (config_per_extruder[extruder].time_to_heatup_1_degree + config_per_extruder[extruder].heatup_cooldown_time_mod_while_printing);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (temp - from_temp) * config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (printing)
|
||||
{
|
||||
return (from_temp - temp) * config_per_extruder[extruder].time_to_cooldown_1_degree;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (from_temp - temp) * std::max(0.0, config_per_extruder[extruder].time_to_cooldown_1_degree - config_per_extruder[extruder].heatup_cooldown_time_mod_while_printing);
|
||||
}
|
||||
}
|
||||
}
|
||||
CoolDownResult getCoolDownPointAfterWarmUp(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing);
|
||||
|
||||
/*!
|
||||
* Get the time to go from one temperature to another temperature
|
||||
* \param extruder The extruder number for which to perform the heatup / cooldown
|
||||
* \param temp_before The before temperature
|
||||
* \param temp_after The after temperature
|
||||
* \param during_printing Whether the planned cooldown / warmup occurs during printing or while in standby mode
|
||||
* \return The time needed
|
||||
*/
|
||||
double getTimeToGoFromTempToTemp(int extruder, double temp_before, double temp_after, bool during_printing);
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
+206
-212
@@ -1,5 +1,7 @@
|
||||
#include "PrimeTower.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "ExtruderTrain.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "gcodeExport.h"
|
||||
@@ -9,34 +11,39 @@
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
|
||||
PrimeTower::PrimeTower()
|
||||
: is_hollow(false)
|
||||
, wipe_from_middle(false)
|
||||
, current_pre_wipe_location_idx(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrimeTower::initConfigs(MeshGroup* meshgroup, std::vector<RetractionConfig>& retraction_config_per_extruder)
|
||||
{
|
||||
extruder_count = meshgroup->getSettingAsCount("machine_extruder_count");
|
||||
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
for (int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++)
|
||||
{
|
||||
config_per_extruder.emplace_back(&retraction_config_per_extruder[extr], PrintFeatureType::Support);// so that visualization in the old Cura still works (TODO)
|
||||
}
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
{
|
||||
ExtruderTrain* train = meshgroup->getExtruderTrain(extr);
|
||||
config_per_extruder[extr].init(train->getSettingInMillimetersPerSecond("speed_prime_tower"), train->getSettingInMicrons("prime_tower_line_width"), train->getSettingInPercentage("prime_tower_flow"));
|
||||
last_prime_tower_poly_printed[extruder_nr] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void PrimeTower::setConfigs(MeshGroup* meshgroup, int layer_thickness)
|
||||
|
||||
|
||||
void PrimeTower::initConfigs(const MeshGroup* meshgroup)
|
||||
{
|
||||
|
||||
extruder_count = meshgroup->getSettingAsCount("machine_extruder_count");
|
||||
|
||||
extruder_count = meshgroup->getExtruderCount();
|
||||
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
{
|
||||
config_per_extruder.emplace_back(PrintFeatureType::Support);// so that visualization in the old Cura still works (TODO)
|
||||
}
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
{
|
||||
const ExtruderTrain* train = meshgroup->getExtruderTrain(extr);
|
||||
config_per_extruder[extr].init(train->getSettingInMillimetersPerSecond("speed_prime_tower"), train->getSettingInMillimetersPerSecond("acceleration_prime_tower"), train->getSettingInMillimetersPerSecond("jerk_prime_tower"), train->getSettingInMicrons("prime_tower_line_width"), train->getSettingInPercentage("prime_tower_flow"));
|
||||
}
|
||||
}
|
||||
|
||||
void PrimeTower::setConfigs(const MeshGroup* meshgroup, const int layer_thickness)
|
||||
{
|
||||
extruder_count = meshgroup->getExtruderCount();
|
||||
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
{
|
||||
GCodePathConfig& conf = config_per_extruder[extr];
|
||||
@@ -44,66 +51,19 @@ void PrimeTower::setConfigs(MeshGroup* meshgroup, int layer_thickness)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrimeTower::computePrimeTowerMax(SliceDataStorage& storage)
|
||||
{ // compute storage.max_object_height_second_to_last_extruder, which is used to determine the highest point in the prime tower
|
||||
|
||||
extruder_count = storage.getSettingAsCount("machine_extruder_count");
|
||||
|
||||
int max_object_height_per_extruder[extruder_count];
|
||||
std::fill_n(max_object_height_per_extruder, extruder_count, -1); // unitialize all as -1
|
||||
{ // compute max_object_height_per_extruder
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
unsigned int extr_nr = mesh.getSettingAsIndex("extruder_nr");
|
||||
max_object_height_per_extruder[extr_nr] =
|
||||
std::max( max_object_height_per_extruder[extr_nr]
|
||||
, mesh.layer_nr_max_filled_layer );
|
||||
}
|
||||
int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); // TODO: support extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_infill_extruder_nr] =
|
||||
std::max( max_object_height_per_extruder[support_infill_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
int support_roof_extruder_nr = storage.getSettingAsIndex("support_roof_extruder_nr"); // TODO: support roof extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_roof_extruder_nr] =
|
||||
std::max( max_object_height_per_extruder[support_roof_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
}
|
||||
{ // // compute max_object_height_second_to_last_extruder
|
||||
int extruder_max_object_height = 0;
|
||||
for (int extruder_nr = 1; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
if (max_object_height_per_extruder[extruder_nr] > max_object_height_per_extruder[extruder_max_object_height])
|
||||
{
|
||||
extruder_max_object_height = extruder_nr;
|
||||
}
|
||||
}
|
||||
int extruder_second_max_object_height = -1;
|
||||
for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
if (extruder_nr == extruder_max_object_height) { continue; }
|
||||
if (extruder_second_max_object_height == -1 || max_object_height_per_extruder[extruder_nr] > max_object_height_per_extruder[extruder_second_max_object_height])
|
||||
{
|
||||
extruder_second_max_object_height = extruder_nr;
|
||||
}
|
||||
}
|
||||
if (extruder_second_max_object_height < 0)
|
||||
{
|
||||
storage.max_object_height_second_to_last_extruder = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
storage.max_object_height_second_to_last_extruder = max_object_height_per_extruder[extruder_second_max_object_height];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PrimeTower::generateGroundpoly(SliceDataStorage& storage)
|
||||
void PrimeTower::generateGroundpoly(const SliceDataStorage& storage)
|
||||
{
|
||||
PolygonRef p = storage.primeTower.ground_poly.newPoly();
|
||||
int tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
extruder_count = storage.meshgroup->getExtruderCount();
|
||||
|
||||
int64_t prime_tower_wall_thickness = storage.getSettingInMicrons("prime_tower_wall_thickness");
|
||||
int64_t tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
|
||||
if (prime_tower_wall_thickness * 2 < tower_size)
|
||||
{
|
||||
is_hollow = true;
|
||||
}
|
||||
|
||||
PolygonRef p = ground_poly.newPoly();
|
||||
int tower_distance = 0;
|
||||
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
|
||||
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
|
||||
@@ -111,90 +71,63 @@ void PrimeTower::generateGroundpoly(SliceDataStorage& storage)
|
||||
p.add(Point(x + tower_distance, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance));
|
||||
middle = Point(x - tower_size / 2, y + tower_size / 2);
|
||||
|
||||
storage.wipePoint = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
|
||||
if (is_hollow)
|
||||
{
|
||||
ground_poly = ground_poly.difference(ground_poly.offset(-prime_tower_wall_thickness));
|
||||
}
|
||||
|
||||
post_wipe_point = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
|
||||
}
|
||||
|
||||
void PrimeTower::generatePaths(SliceDataStorage& storage, unsigned int total_layers)
|
||||
void PrimeTower::generatePaths(const SliceDataStorage& storage)
|
||||
{
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingBoolean("prime_tower_enable"))
|
||||
enabled = storage.max_print_height_second_to_last_extruder >= 0
|
||||
&& storage.getSettingBoolean("prime_tower_enable")
|
||||
&& storage.getSettingInMicrons("prime_tower_wall_thickness") > 10
|
||||
&& storage.getSettingInMicrons("prime_tower_size") > 10;
|
||||
if (enabled)
|
||||
{
|
||||
generatePaths3(storage);
|
||||
generatePaths_denseInfill(storage);
|
||||
generateWipeLocations(storage);
|
||||
}
|
||||
}
|
||||
void PrimeTower::generatePaths_OLD(SliceDataStorage& storage, unsigned int total_layers)
|
||||
{
|
||||
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingBoolean("prime_tower_enable"))
|
||||
{
|
||||
PolygonRef p = storage.primeTower.ground_poly.newPoly();
|
||||
int tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
int tower_distance = 0;
|
||||
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
|
||||
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
|
||||
p.add(Point(x + tower_distance, y + tower_distance));
|
||||
p.add(Point(x + tower_distance, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance));
|
||||
|
||||
storage.wipePoint = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PrimeTower::generatePaths2(SliceDataStorage& storage) // half baked attempt at spiral shaped prime tower pattern
|
||||
void PrimeTower::generatePaths_denseInfill(const SliceDataStorage& storage)
|
||||
{
|
||||
// extruder_count = storage.getSettingAsCount("machine_extruder_count");
|
||||
//
|
||||
// int64_t line_dists[extruder_count + 1]; // distance between the lines of different extruders, and half the line dist for beginning and ending
|
||||
// int64_t total_width = 0;
|
||||
// {
|
||||
// int64_t last_line_width = 0;
|
||||
// for (int extr = 0; extr < extruder_count; extr++)
|
||||
// {
|
||||
// int64_t line_width = config_per_extruder[extr].getLineWidth();
|
||||
// line_dists[extr] = (line_width + last_line_width) / 2;
|
||||
// total_width += line_width;
|
||||
// last_line_width = line_width;
|
||||
// }
|
||||
// line_dists[extruder_count] = last_line_width / 2;
|
||||
// }
|
||||
//
|
||||
|
||||
|
||||
}
|
||||
|
||||
void PrimeTower::generatePaths3(SliceDataStorage& storage)
|
||||
{
|
||||
|
||||
int n_patterns = 2; // alternating patterns between layers
|
||||
int infill_overlap = 60; // so that it can't be zero; EDIT: wtf?
|
||||
|
||||
int extra_infill_shift = 0;
|
||||
|
||||
generateGroundpoly(storage);
|
||||
|
||||
|
||||
int64_t z = 0; // (TODO) because the prime tower stores the paths for each extruder for once instead of generating each layer, we don't know the z position
|
||||
|
||||
for (int extruder = 0; extruder < extruder_count; extruder++)
|
||||
{
|
||||
int line_width = storage.meshgroup->getExtruderTrain(extruder)->getSettingInMicrons("prime_tower_line_width");
|
||||
patterns_per_extruder.emplace_back(n_patterns);
|
||||
std::vector<Polygons>& patterns = patterns_per_extruder.back();
|
||||
std::vector<ExtrusionMoves>& patterns = patterns_per_extruder.back();
|
||||
patterns.resize(n_patterns);
|
||||
for (int pattern_idx = 0; pattern_idx < n_patterns; pattern_idx++)
|
||||
{
|
||||
Polygons result_polygons; // should remain empty, since we generate lines pattern!
|
||||
int outline_offset = -line_width/2;
|
||||
patterns[pattern_idx].polygons = ground_poly.offset(-line_width / 2);
|
||||
Polygons& result_lines = patterns[pattern_idx].lines;
|
||||
int outline_offset = -line_width;
|
||||
int line_distance = line_width;
|
||||
double fill_angle = 45 + pattern_idx * 90;
|
||||
Polygons& result_lines = patterns[pattern_idx];
|
||||
Infill infill_comp(EFillMethod::LINES, ground_poly, outline_offset, line_width, line_distance, infill_overlap, fill_angle);
|
||||
Polygons result_polygons; // should remain empty, since we generate lines pattern!
|
||||
Infill infill_comp(EFillMethod::LINES, ground_poly, outline_offset, line_width, line_distance, infill_overlap, fill_angle, z, extra_infill_shift);
|
||||
infill_comp.generate(result_polygons, result_lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrimeTower::addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
void PrimeTower::addToGcode(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder)
|
||||
{
|
||||
if (!( storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0) )
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -207,99 +140,160 @@ void PrimeTower::addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer,
|
||||
{ // don't print the prime tower if it has been printed already
|
||||
return;
|
||||
}
|
||||
|
||||
if (prev_extruder == gcodeLayer.getExtruder())
|
||||
{
|
||||
wipe = false;
|
||||
}
|
||||
addToGcode3(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed);
|
||||
}
|
||||
|
||||
void PrimeTower::addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
{
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
if (layer_nr > storage.max_print_height_second_to_last_extruder + 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
|
||||
|
||||
Polygons& pattern = patterns_per_extruder[new_extruder][layer_nr % 2];
|
||||
bool pre_wipe = storage.meshgroup->getExtruderTrain(new_extruder)->getSettingBoolean("dual_pre_wipe");
|
||||
bool post_wipe = storage.meshgroup->getExtruderTrain(prev_extruder)->getSettingBoolean("prime_tower_wipe_enabled");
|
||||
|
||||
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
int start_idx = 0; // TODO: figure out which idx is closest to the far right corner
|
||||
gcodeLayer.addPolygon(ground_poly.back(), start_idx, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern, &config, SpaceFillType::Lines);
|
||||
|
||||
last_prime_tower_poly_printed[new_extruder] = layer_nr;
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
if (prev_extruder == new_extruder)
|
||||
{
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::Support, layer_nr, pattern, config.getLineWidth());
|
||||
pre_wipe = false;
|
||||
post_wipe = false;
|
||||
}
|
||||
// pre-wipe:
|
||||
if (pre_wipe)
|
||||
{
|
||||
preWipe(storage, gcodeLayer, new_extruder);
|
||||
}
|
||||
|
||||
if (wipe)
|
||||
addToGcode_denseInfill(storage, gcodeLayer, gcode, layer_nr, prev_extruder, new_extruder);
|
||||
|
||||
// post-wipe:
|
||||
if (post_wipe)
|
||||
{ //Make sure we wipe the old extruder on the prime tower.
|
||||
gcodeLayer.addTravel(storage.wipePoint - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
|
||||
gcodeLayer.addTravel(post_wipe_point - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
|
||||
}
|
||||
}
|
||||
|
||||
void PrimeTower::addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
void PrimeTower::addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder)
|
||||
{
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
ExtrusionMoves& pattern = patterns_per_extruder[new_extruder][((layer_nr % 2) + 2) % 2]; // +2) %2 to handle negative layer numbers
|
||||
|
||||
int64_t offset = -config_per_extruder[new_extruder].getLineWidth();
|
||||
if (layer_nr > 0)
|
||||
offset *= 2;
|
||||
|
||||
//If we changed extruder, print the wipe/prime tower for this nozzle;
|
||||
std::vector<Polygons> insets;
|
||||
{ // generate polygons
|
||||
if ((layer_nr % 2) == 1)
|
||||
insets.push_back(storage.primeTower.ground_poly.offset(offset / 2));
|
||||
else
|
||||
insets.push_back(storage.primeTower.ground_poly);
|
||||
while(true)
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
|
||||
gcodeLayer.addPolygonsByOptimizer(pattern.polygons, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern.lines, &config, SpaceFillType::Lines);
|
||||
|
||||
last_prime_tower_poly_printed[new_extruder] = layer_nr;
|
||||
}
|
||||
|
||||
Point PrimeTower::getLocationBeforePrimeTower(const SliceDataStorage& storage)
|
||||
{
|
||||
Point ret(0, 0);
|
||||
int absolute_starting_points = 0;
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(0);
|
||||
if (train.getSettingBoolean("machine_extruder_start_pos_abs"))
|
||||
{
|
||||
Polygons new_inset = insets[insets.size() - 1].offset(offset);
|
||||
if (new_inset.size() < 1)
|
||||
break;
|
||||
insets.push_back(new_inset);
|
||||
ret += Point(train.getSettingInMicrons("machine_extruder_start_pos_x"), train.getSettingInMicrons("machine_extruder_start_pos_y"));
|
||||
absolute_starting_points++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for(unsigned int n=0; n<insets.size(); n++)
|
||||
if (absolute_starting_points > 0)
|
||||
{ // take the average over all absolute starting positions
|
||||
ret /= absolute_starting_points;
|
||||
}
|
||||
else
|
||||
{ // use the middle of the bed
|
||||
if (!storage.getSettingBoolean("machine_center_is_zero"))
|
||||
{
|
||||
ret = Point(storage.getSettingInMicrons("machine_width"), storage.getSettingInMicrons("machine_depth")) / 2;
|
||||
}
|
||||
// otherwise keep (0, 0)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PrimeTower::generateWipeLocations(const SliceDataStorage& storage)
|
||||
{
|
||||
wipe_from_middle = is_hollow;
|
||||
// only wipe from the middle of the prime tower if we have a z hop already on the first move after the layer switch
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
gcodeLayer.addPolygonsByOptimizer(insets[(prime_tower_dir_outward)? insets.size() - 1 - n : n], &config);
|
||||
const ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
wipe_from_middle &= train.getSettingBoolean("retraction_hop_enabled")
|
||||
&& (!train.getSettingBoolean("retraction_hop_only_when_collides") || train.getSettingBoolean("retraction_hop_after_extruder_switch"));
|
||||
}
|
||||
last_prime_tower_poly_printed[new_extruder] = layer_nr;
|
||||
|
||||
if (wipe)
|
||||
{ //Make sure we wipe the old extruder on the prime tower.
|
||||
gcodeLayer.addTravel(storage.wipePoint - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
|
||||
|
||||
PolygonsPointIndex segment_start; // from where to start the sequence of wipe points
|
||||
PolygonsPointIndex segment_end; // where to end the sequence of wipe points
|
||||
|
||||
if (wipe_from_middle)
|
||||
{
|
||||
// take the same start as end point so that the whole poly os covered.
|
||||
// find the inner polygon.
|
||||
segment_start = segment_end = PolygonUtils::findNearestVert(middle, ground_poly);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
else
|
||||
{
|
||||
// take the closer corner of the wipe tower and generate wipe locations on that side only:
|
||||
//
|
||||
// |
|
||||
// |
|
||||
// +-----
|
||||
// .
|
||||
// ^ nozzle switch location
|
||||
Point from = getLocationBeforePrimeTower(storage);
|
||||
|
||||
// find the single line segment closest to [from] pointing most toward [from]
|
||||
PolygonsPointIndex closest_vert = PolygonUtils::findNearestVert(from, ground_poly);
|
||||
PolygonsPointIndex prev = closest_vert.prev();
|
||||
PolygonsPointIndex next = closest_vert.next();
|
||||
int64_t prev_dot_score = dot(from - closest_vert.p(), turn90CCW(prev.p() - closest_vert.p()));
|
||||
int64_t next_dot_score = dot(from - closest_vert.p(), turn90CCW(closest_vert.p() - next.p()));
|
||||
if (prev_dot_score > next_dot_score)
|
||||
{
|
||||
segment_start = prev;
|
||||
segment_end = closest_vert;
|
||||
}
|
||||
else
|
||||
{
|
||||
segment_start = closest_vert;
|
||||
segment_end = next;
|
||||
}
|
||||
}
|
||||
|
||||
PolygonUtils::spreadDots(segment_start, segment_end, number_of_pre_wipe_locations, pre_wipe_locations);
|
||||
}
|
||||
|
||||
void PrimeTower::preWipe(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const int extruder_nr)
|
||||
{
|
||||
const ClosestPolygonPoint wipe_location = pre_wipe_locations[current_pre_wipe_location_idx];
|
||||
current_pre_wipe_location_idx = (current_pre_wipe_location_idx + pre_wipe_location_skip) % number_of_pre_wipe_locations;
|
||||
|
||||
ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
const int inward_dist = train.getSettingInMicrons("machine_nozzle_size") * 3 / 2 ;
|
||||
const int start_dist = train.getSettingInMicrons("machine_nozzle_size") * 2;
|
||||
const Point end = PolygonUtils::moveInsideDiagonally(wipe_location, inward_dist);
|
||||
const Point outward_dir = wipe_location.location - end;
|
||||
const Point start = wipe_location.location + normal(outward_dir, start_dist);
|
||||
if (wipe_from_middle)
|
||||
{
|
||||
// for hollow wipe tower:
|
||||
// start from above
|
||||
// go to wipe start
|
||||
// go to the Z height of the previous/current layer
|
||||
// wipe
|
||||
// go to normal layer height (automatically on the next extrusion move)...
|
||||
GCodePath& toward_middle = gcode_layer.addTravel(middle);
|
||||
toward_middle.perform_z_hop = true;
|
||||
gcode_layer.forceNewPathStart();
|
||||
GCodePath& toward_wipe_start = gcode_layer.addTravel_simple(start);
|
||||
toward_wipe_start.perform_z_hop = false;
|
||||
toward_wipe_start.retract = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.addTravel(start);
|
||||
}
|
||||
float flow = 0.0001; // force this path being interpreted as an extrusion path, so that no Z hop will occur (TODO: really separately handle travel and extrusion moves)
|
||||
gcode_layer.addExtrusionMove(end, &config_per_extruder[extruder_nr], SpaceFillType::None, flow);
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+127
-32
@@ -1,9 +1,12 @@
|
||||
#ifndef PRIME_TOWER_H
|
||||
#define PRIME_TOWER_H
|
||||
|
||||
#include "gcodeExport.h" // GCodePathConfig
|
||||
#include <vector>
|
||||
|
||||
#include "GCodePathConfig.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "utils/polygon.h" // Polygons
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -13,51 +16,143 @@ class SliceDataStorage;
|
||||
class GCodePlanner;
|
||||
class GCodeExport;
|
||||
|
||||
typedef std::vector<IntPoint> PolyLine;
|
||||
|
||||
/*!
|
||||
* Class for everything to do with the prime tower:
|
||||
* - generating the areas
|
||||
* - checking up till which height the prime tower has to be printed
|
||||
* - generating the paths and adding them to the layer plan
|
||||
*/
|
||||
class PrimeTower
|
||||
{
|
||||
private:
|
||||
int extruder_count;
|
||||
std::vector<GCodePathConfig> config_per_extruder;
|
||||
|
||||
class WallInfill
|
||||
struct ExtrusionMoves
|
||||
{
|
||||
|
||||
Polygons polygons;
|
||||
Polygons lines;
|
||||
};
|
||||
public:
|
||||
void initConfigs(MeshGroup* meshgroup, std::vector<RetractionConfig>& retraction_config_per_extruder);
|
||||
void setConfigs(MeshGroup* configs, int layer_thickness);
|
||||
|
||||
Polygons ground_poly;
|
||||
|
||||
std::vector<PolyLine> extruder_paths;
|
||||
|
||||
|
||||
void generateGroundpoly(SliceDataStorage& storage);
|
||||
bool enabled; //!< Whether the prime tower is enabled
|
||||
|
||||
int extruder_count; //!< number of extruders
|
||||
std::vector<GCodePathConfig> config_per_extruder; //!< Path config for prime tower for each extruder
|
||||
|
||||
bool is_hollow; //!< Whether the prime tower is hollow
|
||||
|
||||
bool wipe_from_middle; //!< Whether to wipe on the inside of the hollow prime tower
|
||||
Point middle; //!< The middle of the prime tower
|
||||
|
||||
Point post_wipe_point; //!< location to post-wipe the unused nozzle off on
|
||||
|
||||
std::vector<ClosestPolygonPoint> pre_wipe_locations; //!< The differernt locations where to pre-wipe the active nozzle
|
||||
const unsigned int pre_wipe_location_skip = 13; //!< How big the steps are when stepping through \ref PrimeTower::wipe_locations
|
||||
const unsigned int number_of_pre_wipe_locations = 21; //!< The required size of \ref PrimeTower::wipe_locations
|
||||
// note that the above are two consecutive numbers in the Fibonacci sequence
|
||||
int current_pre_wipe_location_idx; //!< Index into \ref PrimeTower::wipe_locations of where to pre-wipe the nozzle
|
||||
|
||||
public:
|
||||
Polygons ground_poly; //!< The outline of the prime tower to be used for each layer
|
||||
|
||||
std::vector<std::vector<ExtrusionMoves>> patterns_per_extruder; //!< for each extruder a vector of patterns to alternate between, over the layers
|
||||
|
||||
/*!
|
||||
* Initialize \ref PrimeTower::config_per_extruder with speed and line width settings.
|
||||
*
|
||||
* \param meshgroup Where to retrieve the setttings for each extruder
|
||||
*/
|
||||
void initConfigs(const MeshGroup* meshgroup);
|
||||
|
||||
/*!
|
||||
* Complete the \ref PrimeTower::config_per_extruder by settings the layer height.
|
||||
*
|
||||
* \param meshgroup Where to retrieve the setttings for each extruder
|
||||
* \param layer_thickness The current layer thickness
|
||||
*/
|
||||
void setConfigs(const MeshGroup* meshgroup, const int layer_thickness);
|
||||
|
||||
/*!
|
||||
* Generate the prime tower area to be used on each layer
|
||||
*
|
||||
* Fills \ref PrimeTower::ground_poly and sets \ref PrimeTower::middle
|
||||
*
|
||||
* \param storage Where to retrieve prime tower settings from
|
||||
*/
|
||||
void generateGroundpoly(const SliceDataStorage& storage);
|
||||
|
||||
std::vector<std::vector<Polygons>> patterns_per_extruder; //!< for each extruder a vector of patterns to alternate between, over the layers
|
||||
|
||||
void generatePaths3(SliceDataStorage& storage);
|
||||
|
||||
void generatePaths2(SliceDataStorage& storage);
|
||||
/*!
|
||||
* Generate the area where the prime tower should be.
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param storage where to get settings from
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void generatePaths(SliceDataStorage& storage, unsigned int total_layers);
|
||||
void generatePaths_OLD(SliceDataStorage& storage, unsigned int total_layers);
|
||||
void generatePaths(const SliceDataStorage& storage);
|
||||
|
||||
void computePrimeTowerMax(SliceDataStorage& storage);
|
||||
|
||||
PrimeTower();
|
||||
PrimeTower(); //!< basic constructor
|
||||
|
||||
void addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
void addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
void addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
/*!
|
||||
* Add path plans for the prime tower to the \p gcode_layer
|
||||
*
|
||||
* \param storage where to get settings from; where to get the maximum height of the prime tower from
|
||||
* \param[in,out] gcode_layer Where to get the current extruder from; where to store the generated layer paths
|
||||
* \param layer_nr The layer for which to generate the prime tower paths
|
||||
* \param prev_extruder The previous extruder with which paths were planned; from which extruder a switch was made
|
||||
* \param new_extruder The switched to extruder with which the prime tower paths should be generated.
|
||||
*/
|
||||
void addToGcode(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder);
|
||||
private:
|
||||
/*!
|
||||
* Layer number of the last layer in which a prime tower has been printed per extruder train.
|
||||
*
|
||||
* This is recorded per extruder to account for a prime tower per extruder, instead of the mixed prime tower.
|
||||
*/
|
||||
int last_prime_tower_poly_printed[MAX_EXTRUDERS];
|
||||
|
||||
/*!
|
||||
* Find an approriate representation for the point representing the location before going to the prime tower
|
||||
*
|
||||
* \warning This is not the actual position each time before the wipe tower
|
||||
*
|
||||
* \param storage where to get settings from
|
||||
* \return that location
|
||||
*/
|
||||
Point getLocationBeforePrimeTower(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* \param storage where to get settings from
|
||||
* Depends on ground_poly being generated
|
||||
*/
|
||||
void generateWipeLocations(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* \see WipeTower::generatePaths
|
||||
*
|
||||
* Generate the extrude paths for each extruder on even and odd layers
|
||||
* Fill the ground poly with dense infill.
|
||||
*
|
||||
* \param storage where to get settings from
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void generatePaths_denseInfill(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* \see PrimeTower::addToGcode
|
||||
*
|
||||
* Add path plans for the prime tower to the \p gcode_layer
|
||||
*
|
||||
* \param storage where to get settings from; where to get the maximum height of the prime tower from
|
||||
* \param[in,out] gcode_layer Where to get the current extruder from; where to store the generated layer paths
|
||||
* \param layer_nr The layer for which to generate the prime tower paths
|
||||
* \param prev_extruder The previous extruder with which paths were planned; from which extruder a switch was made
|
||||
* \param new_extruder The switched to extruder with which the prime tower paths should be generated.
|
||||
*/
|
||||
void addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder);
|
||||
|
||||
/*!
|
||||
* Plan the moves for wiping the current nozzles oozed material before starting to print the prime tower.
|
||||
*
|
||||
* \param storage where to get settings from
|
||||
* \param[out] gcode_layer where to add the planned paths for wiping
|
||||
* \param extruder_nr The current extruder
|
||||
*/
|
||||
void preWipe(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const int extruder_nr);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
enum class PrintFeatureType
|
||||
enum class PrintFeatureType: unsigned char
|
||||
{
|
||||
NoneType, // unused, but libArcus depends on it
|
||||
NoneType, // used to mark unspecified jumps in polygons. libArcus depends on it
|
||||
OuterWall,
|
||||
InnerWall,
|
||||
Skin,
|
||||
Support,
|
||||
Skirt,
|
||||
SkirtBrim,
|
||||
Infill,
|
||||
SupportInfill,
|
||||
MoveCombing,
|
||||
MoveRetraction
|
||||
MoveRetraction,
|
||||
SupportInterface
|
||||
};
|
||||
|
||||
|
||||
@@ -23,4 +24,4 @@ enum class PrintFeatureType
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // PRINT_FEATURE
|
||||
#endif // PRINT_FEATURE
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef RETRACTION_CONFIG_H
|
||||
#define RETRACTION_CONFIG_H
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* The retraction configuration used in the GCodePathConfig of each feature (and the travel config)
|
||||
*/
|
||||
class RetractionConfig
|
||||
{
|
||||
public:
|
||||
double distance; //!< The distance retracted (in mm)
|
||||
double speed; //!< The speed with which to retract (in mm/s)
|
||||
double primeSpeed; //!< the speed with which to unretract (in mm/s)
|
||||
double prime_volume; //!< the amount of material primed after unretracting (in mm^3)
|
||||
int zHop; //!< the amount with which to lift the head during a retraction-travel
|
||||
int retraction_min_travel_distance; //!< Minimal distance traversed to even consider retracting (in micron)
|
||||
double retraction_extrusion_window; //!< Window of mm extruded filament in which to limit the amount of retractions
|
||||
int retraction_count_max; //!< The maximum amount of retractions allowed to occur in the RetractionConfig::retraction_extrusion_window
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif // RETRACTION_CONFIG_H
|
||||
@@ -0,0 +1,187 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "SkirtBrim.h"
|
||||
#include "support.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const unsigned int primary_line_count, const int primary_extruder_skirt_brim_line_width, const bool is_skirt, const bool outside_only, Polygons& first_layer_outline)
|
||||
{
|
||||
bool external_only = is_skirt; // whether to include holes or not
|
||||
const int layer_nr = 0;
|
||||
if (is_skirt)
|
||||
{
|
||||
const bool include_helper_parts = true;
|
||||
first_layer_outline = storage.getLayerOutlines(layer_nr, include_helper_parts, external_only);
|
||||
first_layer_outline = first_layer_outline.approxConvexHull();
|
||||
}
|
||||
else
|
||||
{ // add brim underneath support by removing support where there's brim around the model
|
||||
const bool include_helper_parts = false; // include manually below
|
||||
first_layer_outline = storage.getLayerOutlines(layer_nr, include_helper_parts, external_only);
|
||||
Polygons first_layer_empty_holes;
|
||||
if (outside_only)
|
||||
{
|
||||
first_layer_empty_holes = first_layer_outline.getEmptyHoles();
|
||||
first_layer_outline = first_layer_outline.removeEmptyHoles();
|
||||
}
|
||||
if (storage.support.generated && primary_line_count > 0)
|
||||
{ // remove model-brim from support
|
||||
// avoid gap in the middle
|
||||
// V
|
||||
// +---+ +----+
|
||||
// |+-+| |+--+|
|
||||
// || || ||[]|| > expand to fit an extra brim line
|
||||
// |+-+| |+--+|
|
||||
// +---+ +----+
|
||||
Polygons model_brim_covered_area = first_layer_outline.offset(primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2), ClipperLib::jtRound); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides
|
||||
if (outside_only)
|
||||
{ // don't remove support within empty holes where no brim is generated.
|
||||
model_brim_covered_area.add(first_layer_empty_holes);
|
||||
}
|
||||
SupportLayer& support_layer = storage.support.supportLayers[0];
|
||||
support_layer.supportAreas = support_layer.supportAreas.difference(model_brim_covered_area);
|
||||
first_layer_outline.add(support_layer.supportAreas);
|
||||
first_layer_outline.add(support_layer.skin);
|
||||
}
|
||||
first_layer_outline.add(storage.primeTower.ground_poly); // don't remove parts of the prime tower, but make a brim for it
|
||||
}
|
||||
constexpr int join_distance = 20;
|
||||
first_layer_outline = first_layer_outline.offset(join_distance).offset(-join_distance); // merge adjacent models into single polygon
|
||||
constexpr int smallest_line_length = 200;
|
||||
constexpr int largest_error_of_removed_point = 50;
|
||||
first_layer_outline.simplify(smallest_line_length, largest_error_of_removed_point); // simplify for faster processing of the brim lines
|
||||
}
|
||||
|
||||
int SkirtBrim::generatePrimarySkirtBrimLines(SliceDataStorage& storage, int start_distance, unsigned int primary_line_count, const int primary_extruder_skirt_brim_line_width, const int64_t primary_extruder_minimal_length, const Polygons& first_layer_outline, Polygons& skirt_brim_primary_extruder)
|
||||
{
|
||||
|
||||
int offset_distance = start_distance - primary_extruder_skirt_brim_line_width / 2;
|
||||
for (unsigned int skirt_brim_number = 0; skirt_brim_number < primary_line_count; skirt_brim_number++)
|
||||
{
|
||||
offset_distance += primary_extruder_skirt_brim_line_width;
|
||||
|
||||
Polygons outer_skirt_brim_line = first_layer_outline.offset(offset_distance, ClipperLib::jtRound);
|
||||
|
||||
//Remove small inner skirt and brim holes. Holes have a negative area, remove anything smaller then 100x extrusion "area"
|
||||
for (unsigned int n = 0; n < outer_skirt_brim_line.size(); n++)
|
||||
{
|
||||
double area = outer_skirt_brim_line[n].area();
|
||||
if (area < 0 && area > -primary_extruder_skirt_brim_line_width * primary_extruder_skirt_brim_line_width * 100)
|
||||
{
|
||||
outer_skirt_brim_line.remove(n--);
|
||||
}
|
||||
}
|
||||
|
||||
skirt_brim_primary_extruder.add(outer_skirt_brim_line);
|
||||
|
||||
int length = skirt_brim_primary_extruder.polygonLength();
|
||||
if (skirt_brim_number + 1 >= primary_line_count && length > 0 && length < primary_extruder_minimal_length) //Make brim or skirt have more lines when total length is too small.
|
||||
{
|
||||
primary_line_count++;
|
||||
}
|
||||
}
|
||||
return offset_distance;
|
||||
}
|
||||
|
||||
void SkirtBrim::generate(SliceDataStorage& storage, int start_distance, unsigned int primary_line_count, bool outside_only)
|
||||
{
|
||||
const bool is_skirt = start_distance > 0;
|
||||
|
||||
const int adhesion_extruder_nr = storage.getSettingAsIndex("adhesion_extruder_nr");
|
||||
const ExtruderTrain* adhesion_extruder = storage.meshgroup->getExtruderTrain(adhesion_extruder_nr);
|
||||
const int primary_extruder_skirt_brim_line_width = adhesion_extruder->getSettingInMicrons("skirt_brim_line_width");
|
||||
const int64_t primary_extruder_minimal_length = adhesion_extruder->getSettingInMicrons("skirt_brim_minimal_length");
|
||||
|
||||
Polygons& skirt_brim_primary_extruder = storage.skirt_brim[adhesion_extruder_nr];
|
||||
|
||||
Polygons first_layer_outline;
|
||||
getFirstLayerOutline(storage, primary_line_count, primary_extruder_skirt_brim_line_width, is_skirt, outside_only, first_layer_outline);
|
||||
|
||||
const bool has_ooze_shield = storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0;
|
||||
const bool has_draft_shield = storage.draft_protection_shield.size() > 0;
|
||||
|
||||
if (is_skirt && (has_ooze_shield || has_draft_shield))
|
||||
{ // make sure we don't generate skirt through draft / ooze shield
|
||||
first_layer_outline = first_layer_outline.offset(start_distance - primary_extruder_skirt_brim_line_width / 2, ClipperLib::jtRound).unionPolygons(storage.draft_protection_shield);
|
||||
if (has_ooze_shield)
|
||||
{
|
||||
first_layer_outline = first_layer_outline.unionPolygons(storage.oozeShield[0]);
|
||||
}
|
||||
first_layer_outline = first_layer_outline.approxConvexHull();
|
||||
start_distance = primary_extruder_skirt_brim_line_width / 2;
|
||||
}
|
||||
|
||||
int offset_distance = generatePrimarySkirtBrimLines(storage, start_distance, primary_line_count, primary_extruder_skirt_brim_line_width, primary_extruder_minimal_length, first_layer_outline, skirt_brim_primary_extruder);
|
||||
|
||||
|
||||
// generate brim for ooze shield and draft shield
|
||||
if (!is_skirt && (has_ooze_shield || has_draft_shield))
|
||||
{
|
||||
// generate areas where to make extra brim for the shields
|
||||
// avoid gap in the middle
|
||||
// V
|
||||
// +---+ +----+
|
||||
// |+-+| |+--+|
|
||||
// || || ||[]|| > expand to fit an extra brim line
|
||||
// |+-+| |+--+|
|
||||
// +---+ +----+
|
||||
const int64_t primary_skirt_brim_width = (primary_line_count + primary_line_count % 2) * primary_extruder_skirt_brim_line_width; // always use an even number, because we will fil the area from both sides
|
||||
|
||||
Polygons shield_brim;
|
||||
if (has_ooze_shield)
|
||||
{
|
||||
shield_brim = storage.oozeShield[0].difference(storage.oozeShield[0].offset(-primary_skirt_brim_width - primary_extruder_skirt_brim_line_width));
|
||||
}
|
||||
if (has_draft_shield)
|
||||
{
|
||||
shield_brim = shield_brim.unionPolygons(storage.draft_protection_shield.difference(storage.draft_protection_shield.offset(-primary_skirt_brim_width - primary_extruder_skirt_brim_line_width)));
|
||||
}
|
||||
const Polygons outer_primary_brim = first_layer_outline.offset(offset_distance, ClipperLib::jtRound);
|
||||
shield_brim = shield_brim.difference(outer_primary_brim.offset(primary_extruder_skirt_brim_line_width));
|
||||
|
||||
// generate brim within shield_brim
|
||||
skirt_brim_primary_extruder.add(shield_brim);
|
||||
while (shield_brim.size() > 0)
|
||||
{
|
||||
shield_brim = shield_brim.offset(-primary_extruder_skirt_brim_line_width);
|
||||
skirt_brim_primary_extruder.add(shield_brim);
|
||||
}
|
||||
|
||||
// update parameters to generate secondary skirt around
|
||||
first_layer_outline = outer_primary_brim;
|
||||
if (has_draft_shield)
|
||||
{
|
||||
first_layer_outline = first_layer_outline.unionPolygons(storage.draft_protection_shield);
|
||||
}
|
||||
if (has_ooze_shield)
|
||||
{
|
||||
first_layer_outline = first_layer_outline.unionPolygons(storage.oozeShield[0]);
|
||||
}
|
||||
|
||||
offset_distance = 0;
|
||||
}
|
||||
|
||||
{ // process other extruders' brim/skirt (as one brim line around the old brim)
|
||||
int last_width = primary_extruder_skirt_brim_line_width;
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
if (extruder == adhesion_extruder_nr || !storage.meshgroup->getExtruderTrain(extruder)->getIsUsed())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
const int width = train->getSettingInMicrons("skirt_brim_line_width");
|
||||
const int64_t minimal_length = train->getSettingInMicrons("skirt_brim_minimal_length");
|
||||
offset_distance += last_width / 2 + width/2;
|
||||
last_width = width;
|
||||
while (storage.skirt_brim[extruder].polygonLength() < minimal_length)
|
||||
{
|
||||
storage.skirt_brim[extruder].add(first_layer_outline.offset(offset_distance, ClipperLib::jtRound));
|
||||
offset_distance += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,59 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef SKIRT_BRIM_H
|
||||
#define SKIRT_BRIM_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class SkirtBrim
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Generate skirt or brim (depending on parameters).
|
||||
*
|
||||
* When \p distance > 0 and \p count == 1 a skirt is generated, which has
|
||||
* slightly different configuration. Otherwise, a brim is generated.
|
||||
*
|
||||
* \param storage Storage containing the parts at the first layer.
|
||||
* \param distance The distance of the first outset from the parts at the first
|
||||
* layer.
|
||||
* \param primary_line_count Number of outsets / brim lines of the primary extruder.
|
||||
* \param outside_only Whether to only generate a brim on the outside, rather than also in holes
|
||||
*/
|
||||
static void generate(SliceDataStorage& storage, int distance, unsigned int primary_line_count, bool outside_only);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Get the reference outline of the first layer around which to generate the first brim/skirt line.
|
||||
*
|
||||
* This function may change the support polygons in the first layer
|
||||
* in order to meet criteria for putting brim around the model as well as around the support.
|
||||
*
|
||||
* \param storage Storage containing the parts at the first layer.
|
||||
* \param primary_line_count Number of outsets / brim lines of the primary extruder.
|
||||
* \param primary_extruder_skirt_brim_line_width Line widths of the initial skirt/brim lines
|
||||
* \param is_skirt Whether a skirt is being generated vs a brim
|
||||
* \param outside_only Whether to only generate a brim on the outside, rather than also in holes
|
||||
* \param[out] first_layer_outline The resulting reference polygons
|
||||
*/
|
||||
static void getFirstLayerOutline(SliceDataStorage& storage, const unsigned int primary_line_count, const int primary_extruder_skirt_brim_line_width, const bool is_skirt, const bool outside_only, Polygons& first_layer_outline);
|
||||
|
||||
/*!
|
||||
* Generate the skirt/brim lines around the model
|
||||
*
|
||||
* \param storage Storage containing the parts at the first layer.
|
||||
* \param start_distance The distance of the first outset from the parts at the first
|
||||
* \param primary_line_count Number of outsets / brim lines of the primary extruder.
|
||||
* \param primary_extruder_skirt_brim_line_width Line widths of the initial skirt/brim lines
|
||||
* \param primary_extruder_minimal_length The minimal total length of the skirt/brim lines of the primary extruder
|
||||
* \param first_layer_outline The reference polygons from which to offset outward to generate skirt/brim lines
|
||||
* \param[out] skirt_brim_primary_extruder Where to store the resulting brim/skirt lines in
|
||||
* \return The offset of the last brim/skirt line from the reference polygon \p first_layer_outline
|
||||
*/
|
||||
static int generatePrimarySkirtBrimLines(SliceDataStorage& storage, int start_distance, unsigned int primary_line_count, const int primary_extruder_skirt_brim_line_width, const int64_t primary_extruder_minimal_length, const Polygons& first_layer_outline, Polygons& skirt_brim_primary_extruder);
|
||||
};
|
||||
}//namespace cura
|
||||
|
||||
#endif //SKIRT_BRIM_H
|
||||
@@ -38,6 +38,7 @@ void WallsComputation::generateInsets(SliceLayerPart* part)
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
part->insets[i].simplify();
|
||||
part->insets[i].removeDegenerateVerts();
|
||||
if (i == 0)
|
||||
{
|
||||
if (recompute_outline_based_on_outer_wall)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef INSET_H
|
||||
#define INSET_H
|
||||
#ifndef WALLS_COMPUTATION_H
|
||||
#define WALLS_COMPUTATION_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
@@ -66,4 +66,4 @@ private:
|
||||
};
|
||||
}//namespace cura
|
||||
|
||||
#endif//INSET_H
|
||||
#endif//WALLS_COMPUTATION_H
|
||||
|
||||
+12
-27
@@ -19,7 +19,7 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
|
||||
int layer_count = (maxz - initial_layer_thickness) / connectionHeight + 1;
|
||||
|
||||
DEBUG_SHOW(layer_count);
|
||||
std::cerr << "Layer count: " << layer_count << "\n";
|
||||
|
||||
std::vector<cura::Slicer*> slicerList;
|
||||
|
||||
@@ -42,7 +42,7 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
}
|
||||
if (starting_layer_idx > 0)
|
||||
{
|
||||
logError("First %i layers are empty!\n", starting_layer_idx);
|
||||
logWarning("First %i layers are empty!\n", starting_layer_idx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,7 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
for (cura::Slicer* slicer : slicerList)
|
||||
wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygons);
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::OuterWall, 0, wireFrame.bottom_outline, 1);
|
||||
CommandSocket::sendPolygons(PrintFeatureType::OuterWall, /*0,*/ wireFrame.bottom_outline, 1);
|
||||
|
||||
if (slicerList.empty()) //Wait, there is nothing to slice.
|
||||
{
|
||||
@@ -85,10 +84,8 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
|
||||
chainify_polygons(parts1, starting_point_in_layer, chainified, false);
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::OuterWall, layer_idx - starting_layer_idx, chainified, 1);
|
||||
}
|
||||
CommandSocket::sendPolygons(PrintFeatureType::OuterWall, /*layer_idx - starting_layer_idx,*/ chainified, 1);
|
||||
|
||||
if (chainified.size() > 0)
|
||||
{
|
||||
if (starting_z == -1) starting_z = slicerList[0]->layers[layer_idx-1].z;
|
||||
@@ -327,7 +324,7 @@ void Weaver::connections2moves(WeaveRoofPart& inset)
|
||||
WeaveConnectionSegment& segment = segments[idx];
|
||||
assert(segment.segmentType == WeaveSegmentType::UP);
|
||||
Point3 from = (idx == 0)? part.connection.from : segments[idx-1].to;
|
||||
bool skipped = (segment.to - from).vSize2() < extrusionWidth * extrusionWidth;
|
||||
bool skipped = (segment.to - from).vSize2() < line_width * line_width;
|
||||
if (skipped)
|
||||
{
|
||||
unsigned int begin = idx;
|
||||
@@ -336,9 +333,11 @@ void Weaver::connections2moves(WeaveRoofPart& inset)
|
||||
WeaveConnectionSegment& segment = segments[idx];
|
||||
assert(segments[idx].segmentType == WeaveSegmentType::UP);
|
||||
Point3 from = (idx == 0)? part.connection.from : segments[idx-1].to;
|
||||
bool skipped = (segment.to - from).vSize2() < extrusionWidth * extrusionWidth;
|
||||
bool skipped = (segment.to - from).vSize2() < line_width * line_width;
|
||||
if (!skipped)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
int end = idx - ((include_half_of_last_down)? 2 : 1);
|
||||
if (idx >= segments.size())
|
||||
@@ -388,8 +387,6 @@ void Weaver::connect(Polygons& parts0, int z0, Polygons& parts1, int z1, WeaveCo
|
||||
|
||||
void Weaver::chainify_polygons(Polygons& parts1, Point start_close_to, Polygons& result, bool include_last)
|
||||
{
|
||||
|
||||
|
||||
for (unsigned int prt = 0 ; prt < parts1.size(); prt++)
|
||||
{
|
||||
const PolygonRef upperPart = parts1[prt];
|
||||
@@ -403,9 +400,9 @@ void Weaver::chainify_polygons(Polygons& parts1, Point start_close_to, Polygons&
|
||||
bool found = true;
|
||||
int idx = 0;
|
||||
|
||||
for (Point upper_point = upperPart[closestInPoly.pos]; found; upper_point = next_upper.location)
|
||||
for (Point upper_point = upperPart[closestInPoly.point_idx]; found; upper_point = next_upper.location)
|
||||
{
|
||||
found = PolygonUtils::getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.pos, next_upper);
|
||||
found = PolygonUtils::getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.point_idx, next_upper);
|
||||
|
||||
|
||||
if (!found)
|
||||
@@ -430,7 +427,7 @@ void Weaver::connect_polygons(Polygons& supporting, int z0, Polygons& supported,
|
||||
|
||||
if (supporting.size() < 1)
|
||||
{
|
||||
DEBUG_PRINTLN("lower layer has zero parts!");
|
||||
std::cerr << "lower layer has zero parts!\n";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -476,16 +473,4 @@ void Weaver::connect_polygons(Polygons& supporting, int z0, Polygons& supported,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
+2
-4
@@ -12,8 +12,6 @@
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
@@ -30,7 +28,7 @@ private:
|
||||
|
||||
int initial_layer_thickness;
|
||||
int connectionHeight;
|
||||
int extrusionWidth;
|
||||
int line_width;
|
||||
|
||||
int roof_inset;
|
||||
|
||||
@@ -47,7 +45,7 @@ public:
|
||||
initial_layer_thickness = getSettingInMicrons("layer_height_0");
|
||||
connectionHeight = getSettingInMicrons("wireframe_height");
|
||||
|
||||
extrusionWidth = getSettingInMicrons("wall_line_width_x");
|
||||
line_width = getSettingInMicrons("wall_line_width_x");
|
||||
|
||||
roof_inset = getSettingInMicrons("wireframe_roof_inset");
|
||||
nozzle_outer_diameter = getSettingInMicrons("machine_nozzle_tip_outer_diameter"); // ___ ___ .
|
||||
|
||||
+71
-51
@@ -3,10 +3,12 @@
|
||||
#include <cmath> // sqrt
|
||||
#include <fstream> // debug IO
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "weaveDataStorage.h"
|
||||
#include "progress/Progress.h"
|
||||
|
||||
#include "pathOrderOptimizer.h" // for skirt
|
||||
#include "pathOrderOptimizer.h" //For skirt/brim.
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -17,7 +19,7 @@ void Wireframe2gcode::writeGCode()
|
||||
|
||||
gcode.preSetup(wireFrame.meshgroup);
|
||||
|
||||
gcode.setInitialTemps(wireFrame.meshgroup);
|
||||
gcode.setInitialTemps(*wireFrame.meshgroup);
|
||||
|
||||
if (CommandSocket::getInstance())
|
||||
CommandSocket::getInstance()->beginGCode();
|
||||
@@ -33,23 +35,22 @@ void Wireframe2gcode::writeGCode()
|
||||
{
|
||||
maxObjectHeight = wireFrame.layers.back().z1;
|
||||
}
|
||||
|
||||
processSkirt();
|
||||
|
||||
|
||||
unsigned int total_layers = wireFrame.layers.size();
|
||||
gcode.writeLayerComment(0);
|
||||
gcode.writeTypeComment("SKIRT");
|
||||
|
||||
gcode.setZ(initial_layer_thickness);
|
||||
|
||||
|
||||
processSkirt();
|
||||
|
||||
unsigned int total_layers = wireFrame.layers.size();
|
||||
gcode.writeLayerComment(0);
|
||||
gcode.writeTypeComment(PrintFeatureType::SkirtBrim);
|
||||
|
||||
for (PolygonRef bottom_part : wireFrame.bottom_infill.roof_outlines)
|
||||
{
|
||||
if (bottom_part.size() == 0) continue;
|
||||
writeMoveWithRetract(bottom_part[bottom_part.size()-1]);
|
||||
for (Point& segment_to : bottom_part)
|
||||
{
|
||||
gcode.writeMove(segment_to, speedBottom, extrusion_per_mm_flat);
|
||||
gcode.writeMove(segment_to, speedBottom, extrusion_mm3_per_mm_flat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ void Wireframe2gcode::writeGCode()
|
||||
writeMoveWithRetract(segment.to);
|
||||
} else
|
||||
{
|
||||
gcode.writeMove(segment.to, speedBottom, extrusion_per_mm_connection);
|
||||
gcode.writeMove(segment.to, speedBottom, extrusion_mm3_per_mm_connection);
|
||||
}
|
||||
}
|
||||
,
|
||||
@@ -75,7 +76,7 @@ void Wireframe2gcode::writeGCode()
|
||||
else if (segment.segmentType == WeaveSegmentType::DOWN_AND_FLAT)
|
||||
return; // do nothing
|
||||
else
|
||||
gcode.writeMove(segment.to, speedBottom, extrusion_per_mm_flat);
|
||||
gcode.writeMove(segment.to, speedBottom, extrusion_mm3_per_mm_flat);
|
||||
}
|
||||
);
|
||||
Progress::messageProgressStage(Progress::Stage::EXPORT, nullptr);
|
||||
@@ -98,7 +99,7 @@ void Wireframe2gcode::writeGCode()
|
||||
|
||||
if (part.connection.segments.size() == 0) continue;
|
||||
|
||||
gcode.writeTypeComment("SUPPORT"); // connection
|
||||
gcode.writeTypeComment(PrintFeatureType::Support); // connection
|
||||
{
|
||||
if (vSize2(gcode.getPositionXY() - part.connection.from) > connectionHeight)
|
||||
{
|
||||
@@ -114,7 +115,7 @@ void Wireframe2gcode::writeGCode()
|
||||
|
||||
|
||||
|
||||
gcode.writeTypeComment("WALL-OUTER"); // top
|
||||
gcode.writeTypeComment(PrintFeatureType::OuterWall); // top
|
||||
{
|
||||
for (unsigned int segment_idx = 0; segment_idx < part.connection.segments.size(); segment_idx++)
|
||||
{
|
||||
@@ -125,7 +126,7 @@ void Wireframe2gcode::writeGCode()
|
||||
writeMoveWithRetract(segment.to);
|
||||
} else
|
||||
{
|
||||
gcode.writeMove(segment.to, speedFlat, extrusion_per_mm_flat);
|
||||
gcode.writeMove(segment.to, speedFlat, extrusion_mm3_per_mm_flat);
|
||||
gcode.writeDelay(flat_delay);
|
||||
}
|
||||
}
|
||||
@@ -147,7 +148,7 @@ void Wireframe2gcode::writeGCode()
|
||||
// do nothing
|
||||
} else
|
||||
{
|
||||
gcode.writeMove(segment.to, speedFlat, extrusion_per_mm_flat);
|
||||
gcode.writeMove(segment.to, speedFlat, extrusion_mm3_per_mm_flat);
|
||||
gcode.writeDelay(flat_delay);
|
||||
}
|
||||
});
|
||||
@@ -179,7 +180,7 @@ void Wireframe2gcode::go_down(WeaveLayer& layer, WeaveConnectionPart& part, unsi
|
||||
gcode.writeMove(from, speedDown, 0);
|
||||
if (straight_first_when_going_down <= 0)
|
||||
{
|
||||
gcode.writeMove(segment.to, speedDown, extrusion_per_mm_connection);
|
||||
gcode.writeMove(segment.to, speedDown, extrusion_mm3_per_mm_connection);
|
||||
} else
|
||||
{
|
||||
Point3& to = segment.to;
|
||||
@@ -191,14 +192,14 @@ void Wireframe2gcode::go_down(WeaveLayer& layer, WeaveConnectionPart& part, unsi
|
||||
int64_t new_length = (up - from).vSize() + (to - up).vSize() + 5;
|
||||
int64_t orr_length = vec.vSize();
|
||||
double enlargement = new_length / orr_length;
|
||||
gcode.writeMove(up, speedDown*enlargement, extrusion_per_mm_connection / enlargement);
|
||||
gcode.writeMove(to, speedDown*enlargement, extrusion_per_mm_connection / enlargement);
|
||||
gcode.writeMove(up, speedDown*enlargement, extrusion_mm3_per_mm_connection / enlargement);
|
||||
gcode.writeMove(to, speedDown*enlargement, extrusion_mm3_per_mm_connection / enlargement);
|
||||
}
|
||||
gcode.writeDelay(bottom_delay);
|
||||
if (up_dist_half_speed > 0)
|
||||
{
|
||||
|
||||
gcode.writeMove(Point3(0,0,up_dist_half_speed) + gcode.getPosition(), speedUp / 2, extrusion_per_mm_connection * 2);
|
||||
gcode.writeMove(Point3(0,0,up_dist_half_speed) + gcode.getPosition(), speedUp / 2, extrusion_mm3_per_mm_connection * 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +208,7 @@ void Wireframe2gcode::go_down(WeaveLayer& layer, WeaveConnectionPart& part, unsi
|
||||
void Wireframe2gcode::strategy_knot(WeaveLayer& layer, WeaveConnectionPart& part, unsigned int segment_idx)
|
||||
{
|
||||
WeaveConnectionSegment& segment = part.connection.segments[segment_idx];
|
||||
gcode.writeMove(segment.to, speedUp, extrusion_per_mm_connection);
|
||||
gcode.writeMove(segment.to, speedUp, extrusion_mm3_per_mm_connection);
|
||||
Point3 next_vector;
|
||||
if (segment_idx + 1 < part.connection.segments.size())
|
||||
{
|
||||
@@ -257,7 +258,7 @@ void Wireframe2gcode::strategy_retract(WeaveLayer& layer, WeaveConnectionPart& p
|
||||
Point3 vec = to - from;
|
||||
Point3 lowering = vec * retract_hop_dist / 2 / vec.vSize();
|
||||
Point3 lower = to - lowering;
|
||||
gcode.writeMove(lower, speedUp, extrusion_per_mm_connection);
|
||||
gcode.writeMove(lower, speedUp, extrusion_mm3_per_mm_connection);
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
gcode.writeMove(to + lowering, speedUp, 0);
|
||||
gcode.writeDelay(top_retract_pause);
|
||||
@@ -266,7 +267,7 @@ void Wireframe2gcode::strategy_retract(WeaveLayer& layer, WeaveConnectionPart& p
|
||||
|
||||
} else
|
||||
{
|
||||
gcode.writeMove(to, speedUp, extrusion_per_mm_connection);
|
||||
gcode.writeMove(to, speedUp, extrusion_mm3_per_mm_connection);
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
gcode.writeMove(to + Point3(0, 0, retract_hop_dist), speedFlat, 0);
|
||||
gcode.writeDelay(top_retract_pause);
|
||||
@@ -304,7 +305,7 @@ void Wireframe2gcode::strategy_compensate(WeaveLayer& layer, WeaveConnectionPart
|
||||
int64_t orrLength = (segment.to - from).vSize() + next_vector.vSize() + 1; // + 1 in order to avoid division by zero
|
||||
int64_t newLength = (newTop - from).vSize() + (next_point - newTop).vSize() + 1; // + 1 in order to avoid division by zero
|
||||
|
||||
gcode.writeMove(newTop, speedUp * newLength / orrLength, extrusion_per_mm_connection * orrLength / newLength);
|
||||
gcode.writeMove(newTop, speedUp * newLength / orrLength, extrusion_mm3_per_mm_connection * orrLength / newLength);
|
||||
}
|
||||
void Wireframe2gcode::handle_segment(WeaveLayer& layer, WeaveConnectionPart& part, unsigned int segment_idx)
|
||||
{
|
||||
@@ -319,7 +320,7 @@ void Wireframe2gcode::handle_segment(WeaveLayer& layer, WeaveConnectionPart& par
|
||||
go_down(layer, part, segment_idx);
|
||||
break;
|
||||
case WeaveSegmentType::FLAT:
|
||||
DEBUG_SHOW("flat piece in connection?!!?!");
|
||||
logWarning("Warning: flat piece in wire print connection.\n");
|
||||
break;
|
||||
case WeaveSegmentType::UP:
|
||||
if (strategy == STRATEGY_KNOT)
|
||||
@@ -383,12 +384,12 @@ void Wireframe2gcode::handle_roof_segment(WeaveRoofPart& inset, WeaveConnectionP
|
||||
detoured -= next_dir;
|
||||
}
|
||||
|
||||
gcode.writeMove(detoured, speedUp, extrusion_per_mm_connection);
|
||||
gcode.writeMove(detoured, speedUp, extrusion_mm3_per_mm_connection);
|
||||
|
||||
}
|
||||
break;
|
||||
case WeaveSegmentType::DOWN:
|
||||
gcode.writeMove(segment.to, speedDown, extrusion_per_mm_connection);
|
||||
gcode.writeMove(segment.to, speedDown, extrusion_mm3_per_mm_connection);
|
||||
gcode.writeDelay(roof_outer_delay);
|
||||
break;
|
||||
case WeaveSegmentType::FLAT:
|
||||
@@ -406,7 +407,7 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
|
||||
{
|
||||
|
||||
// bottom:
|
||||
gcode.writeTypeComment("FILL");
|
||||
gcode.writeTypeComment(PrintFeatureType::Infill);
|
||||
for (unsigned int inset_idx = 0; inset_idx < infill_insets.size(); inset_idx++)
|
||||
{
|
||||
WeaveRoofPart& inset = infill_insets[inset_idx];
|
||||
@@ -417,7 +418,7 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
|
||||
WeaveConnectionPart& inset_part = inset.connections[inset_part_nr];
|
||||
std::vector<WeaveConnectionSegment>& segments = inset_part.connection.segments;
|
||||
|
||||
gcode.writeTypeComment("SUPPORT"); // connection
|
||||
gcode.writeTypeComment(PrintFeatureType::Support); // connection
|
||||
if (segments.size() == 0) continue;
|
||||
Point3 first_extrusion_from = inset_part.connection.from;
|
||||
unsigned int first_segment_idx;
|
||||
@@ -433,7 +434,7 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
|
||||
connectionHandler(*this, inset, inset_part, segment_idx);
|
||||
}
|
||||
|
||||
gcode.writeTypeComment("WALL-INNER"); // top
|
||||
gcode.writeTypeComment(PrintFeatureType::InnerWall); // top
|
||||
for (unsigned int segment_idx = 0; segment_idx < segments.size(); segment_idx++)
|
||||
{
|
||||
WeaveConnectionSegment& segment = segments[segment_idx];
|
||||
@@ -447,7 +448,7 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
|
||||
|
||||
}
|
||||
|
||||
gcode.writeTypeComment("WALL-OUTER"); // outer perimeter of the flat parts
|
||||
gcode.writeTypeComment(PrintFeatureType::OuterWall); // outer perimeter of the flat parts
|
||||
for (PolygonRef poly : roof_outlines)
|
||||
{
|
||||
writeMoveWithRetract(poly[poly.size() - 1]);
|
||||
@@ -487,16 +488,15 @@ Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBas
|
||||
roof_inset = getSettingInMicrons("wireframe_roof_inset");
|
||||
|
||||
filament_diameter = getSettingInMicrons("material_diameter");
|
||||
extrusionWidth = getSettingInMicrons("wall_line_width_x");
|
||||
line_width = getSettingInMicrons("wall_line_width_x");
|
||||
|
||||
flowConnection = getSettingInPercentage("wireframe_flow_connection");
|
||||
flowFlat = getSettingInPercentage("wireframe_flow_flat");
|
||||
|
||||
double filament_area = /* M_PI * */ (INT2MM(filament_diameter) / 2.0) * (INT2MM(filament_diameter) / 2.0);
|
||||
double lineArea = /* M_PI * */ (INT2MM(extrusionWidth) / 2.0) * (INT2MM(extrusionWidth) / 2.0);
|
||||
extrusion_per_mm_connection = lineArea / filament_area * flowConnection / 100.0;
|
||||
extrusion_per_mm_flat = lineArea / filament_area * flowFlat / 100.0;
|
||||
|
||||
|
||||
const double line_area = M_PI * square(INT2MM(line_width) / 2.0);
|
||||
extrusion_mm3_per_mm_connection = line_area * flowConnection / 100.0;
|
||||
extrusion_mm3_per_mm_flat = line_area * flowFlat / 100.0;
|
||||
|
||||
nozzle_outer_diameter = getSettingInMicrons("machine_nozzle_tip_outer_diameter"); // ___ ___ .
|
||||
nozzle_head_distance = getSettingInMicrons("machine_nozzle_head_distance"); // | | .
|
||||
nozzle_expansion_angle = getSettingInAngleRadians("machine_nozzle_expansion_angle"); // \_U_/ .
|
||||
@@ -550,9 +550,15 @@ void Wireframe2gcode::processStartingCode()
|
||||
{
|
||||
if (!CommandSocket::isInstantiated())
|
||||
{
|
||||
gcode.writeCode(gcode.getFileHeader().c_str());
|
||||
std::string prefix = gcode.getFileHeader();
|
||||
gcode.writeCode(prefix.c_str());
|
||||
}
|
||||
else
|
||||
|
||||
int start_extruder_nr = getSettingAsIndex("adhesion_extruder_nr");
|
||||
|
||||
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
|
||||
|
||||
if (gcode.getFlavor() != EGCodeFlavor::ULTIGCODE && gcode.getFlavor() != EGCodeFlavor::GRIFFIN)
|
||||
{
|
||||
if (getSettingBoolean("material_bed_temp_prepend"))
|
||||
{
|
||||
@@ -561,23 +567,27 @@ void Wireframe2gcode::processStartingCode()
|
||||
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature"), getSettingBoolean("material_bed_temp_wait"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (getSettingBoolean("material_print_temp_prepend"))
|
||||
{
|
||||
if (getSettingInDegreeCelsius("material_print_temperature") > 0)
|
||||
for (int extruder_nr = 0; extruder_nr < getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{
|
||||
gcode.writeTemperatureCommand(getSettingAsIndex("extruder_nr"), getSettingInDegreeCelsius("material_print_temperature"));
|
||||
if (getSettingBoolean("machine_print_temp_wait"))
|
||||
double print_temp = getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(extruder_nr, print_temp);
|
||||
}
|
||||
if (getSettingBoolean("material_print_temp_wait"))
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{
|
||||
gcode.writeTemperatureCommand(getSettingAsIndex("extruder_nr"), getSettingInDegreeCelsius("material_print_temperature"), true);
|
||||
double print_temp = getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(extruder_nr, print_temp, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
gcode.writeCode(getSettingString("machine_start_gcode").c_str());
|
||||
|
||||
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
|
||||
|
||||
if (gcode.getFlavor() == EGCodeFlavor::BFB)
|
||||
{
|
||||
gcode.writeComment("enable auto-retraction");
|
||||
@@ -585,6 +595,16 @@ void Wireframe2gcode::processStartingCode()
|
||||
tmp << "M227 S" << (getSettingInMicrons("retraction_amount") * 2560 / 1000) << " P" << (getSettingInMicrons("retraction_amount") * 2560 / 1000);
|
||||
gcode.writeLine(tmp.str().c_str());
|
||||
}
|
||||
else if (gcode.getFlavor() == EGCodeFlavor::GRIFFIN)
|
||||
{ // initialize extruder trains
|
||||
gcode.writeCode("T0"); // Toolhead already assumed to be at T0, but writing it just to be safe...
|
||||
CommandSocket::setSendCurrentPosition(gcode.getPositionXY());
|
||||
gcode.startExtruder(start_extruder_nr);
|
||||
constexpr bool wait = true;
|
||||
gcode.writeTemperatureCommand(start_extruder_nr, getSettingInDegreeCelsius("material_print_temperature"), wait);
|
||||
gcode.writePrimeTrain(getSettingInMillimetersPerSecond("speed_travel"));
|
||||
gcode.writeRetraction(&standard_retraction_config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -598,7 +618,7 @@ void Wireframe2gcode::processSkirt()
|
||||
PathOrderOptimizer order(Point(INT32_MIN, INT32_MIN));
|
||||
order.addPolygons(skirt);
|
||||
order.optimize();
|
||||
|
||||
|
||||
for (unsigned int poly_order_idx = 0; poly_order_idx < skirt.size(); poly_order_idx++)
|
||||
{
|
||||
unsigned int poly_idx = order.polyOrder[poly_order_idx];
|
||||
@@ -607,7 +627,7 @@ void Wireframe2gcode::processSkirt()
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
Point& p = poly[(point_idx + order.polyStart[poly_idx] + 1) % poly.size()];
|
||||
gcode.writeMove(p, getSettingInMillimetersPerSecond("skirt_speed"), getSettingInMillimetersPerSecond("skirt_line_width"));
|
||||
gcode.writeMove(p, getSettingInMillimetersPerSecond("skirt_brim_speed"), getSettingInMillimeters("skirt_brim_line_width") * INT2MM(initial_layer_thickness));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
#include "utils/polygon.h"
|
||||
#include "Weaver.h"
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
@@ -33,11 +31,11 @@ private:
|
||||
|
||||
int initial_layer_thickness;
|
||||
int filament_diameter;
|
||||
int extrusionWidth;
|
||||
int line_width;
|
||||
double flowConnection;
|
||||
double flowFlat;
|
||||
double extrusion_per_mm_connection;
|
||||
double extrusion_per_mm_flat;
|
||||
double extrusion_mm3_per_mm_connection;
|
||||
double extrusion_mm3_per_mm_flat;
|
||||
int nozzle_outer_diameter;
|
||||
int nozzle_head_distance;
|
||||
double nozzle_expansion_angle;
|
||||
|
||||
-483
@@ -1,483 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "comb.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "utils/SVG.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
// boundary_outside is only computed when it's needed!
|
||||
Polygons& Comb::getBoundaryOutside()
|
||||
{
|
||||
if (!boundary_outside)
|
||||
{
|
||||
boundary_outside = new Polygons();
|
||||
*boundary_outside = storage.getLayerOutlines(layer_nr, false).offset(offset_from_outlines_outside);
|
||||
}
|
||||
return *boundary_outside;
|
||||
}
|
||||
|
||||
BucketGrid2D<PolygonsPointIndex>& Comb::getOutsideLocToLine()
|
||||
{
|
||||
Polygons& outside = getBoundaryOutside();
|
||||
if (!outside_loc_to_line)
|
||||
{
|
||||
outside_loc_to_line = PolygonUtils::createLocToLineGrid(outside, offset_from_outlines_outside * 3 / 2);
|
||||
}
|
||||
return *outside_loc_to_line;
|
||||
}
|
||||
|
||||
|
||||
Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
, layer_nr(layer_nr)
|
||||
, offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls
|
||||
, max_moveInside_distance2(offset_from_outlines * 2 * offset_from_outlines * 2)
|
||||
, offset_from_outlines_outside(travel_avoid_distance)
|
||||
, max_crossing_dist2(offset_from_outlines_outside * offset_from_outlines_outside * 3) // so max_crossing_dist = offset_from_outlines_outside * sqrt(3), which is a bit more than sqrt(2) which is necesary for 90* corners
|
||||
, avoid_other_parts(travel_avoid_other_parts)
|
||||
// , boundary_inside( boundary.offset(-offset_from_outlines) ) // TODO: make inside boundary configurable?
|
||||
, boundary_inside( comb_boundary_inside )
|
||||
, boundary_outside(nullptr)
|
||||
, outside_loc_to_line(nullptr)
|
||||
, partsView_inside( boundary_inside.splitIntoPartsView() ) // !! changes the order of boundary_inside !!
|
||||
{
|
||||
}
|
||||
|
||||
Comb::~Comb()
|
||||
{
|
||||
if (boundary_outside)
|
||||
{
|
||||
delete boundary_outside;
|
||||
}
|
||||
if (outside_loc_to_line)
|
||||
{
|
||||
delete outside_loc_to_line;
|
||||
}
|
||||
}
|
||||
|
||||
bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside, bool endInside, int64_t max_comb_distance_ignored)
|
||||
{
|
||||
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Move start and end point inside the comb boundary
|
||||
unsigned int start_inside_poly = NO_INDEX;
|
||||
if (startInside)
|
||||
{
|
||||
start_inside_poly = PolygonUtils::moveInside(boundary_inside, startPoint, offset_extra_start_end, max_moveInside_distance2);
|
||||
if (!boundary_inside.inside(start_inside_poly) || start_inside_poly == NO_INDEX)
|
||||
{
|
||||
if (start_inside_poly != NO_INDEX)
|
||||
{ // if not yet inside because of overshoot, try again
|
||||
start_inside_poly = PolygonUtils::moveInside(boundary_inside, startPoint, offset_extra_start_end, max_moveInside_distance2);
|
||||
}
|
||||
if (start_inside_poly == NO_INDEX) //If we fail to move the point inside the comb boundary we need to retract.
|
||||
{
|
||||
startInside = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
unsigned int end_inside_poly = NO_INDEX;
|
||||
if (endInside)
|
||||
{
|
||||
end_inside_poly = PolygonUtils::moveInside(boundary_inside, endPoint, offset_extra_start_end, max_moveInside_distance2);
|
||||
if (!boundary_inside.inside(endPoint) || end_inside_poly == NO_INDEX)
|
||||
{
|
||||
if (end_inside_poly != NO_INDEX)
|
||||
{ // if not yet inside because of overshoot, try again
|
||||
end_inside_poly = PolygonUtils::moveInside(boundary_inside, endPoint, offset_extra_start_end, max_moveInside_distance2);
|
||||
}
|
||||
if (end_inside_poly == NO_INDEX) //If we fail to move the point inside the comb boundary we need to retract.
|
||||
{
|
||||
endInside = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned int start_part_boundary_poly_idx;
|
||||
unsigned int end_part_boundary_poly_idx;
|
||||
unsigned int start_part_idx = (start_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(start_inside_poly, &start_part_boundary_poly_idx);
|
||||
unsigned int end_part_idx = (end_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(end_inside_poly, &end_part_boundary_poly_idx);
|
||||
|
||||
if (startInside && endInside && start_part_idx == end_part_idx)
|
||||
{ // normal combing within part
|
||||
PolygonsPart part = partsView_inside.assemblePart(start_part_idx);
|
||||
combPaths.emplace_back();
|
||||
LinePolygonsCrossings::comb(part, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{ // comb inside part to edge (if needed) >> move through air avoiding other parts >> comb inside end part upto the endpoint (if needed)
|
||||
// INSIDE | in_between | OUTSIDE | in_between | INSIDE
|
||||
// ^crossing_1_in ^crossing_1_mid ^crossing_1_out ^crossing_2_out ^crossing_2_mid ^crossing_2_in
|
||||
//
|
||||
// when startPoint is inside crossing_1_in is of interest
|
||||
// when it is in between inside and outside it is equal to crossing_1_mid
|
||||
Point crossing_1_in_or_mid; // the point inside the starting polygon if startPoint is inside or the startPoint itself if it is not inside
|
||||
Point crossing_1_out;
|
||||
Point crossing_2_in_or_mid; // the point inside the ending polygon if endPoint is inside or the endPoint itself if it is not inside
|
||||
Point crossing_2_out;
|
||||
|
||||
{ // find crossing over the in-between area between inside and outside
|
||||
if (startInside)
|
||||
{
|
||||
ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::findClosest(endPoint, boundary_inside[start_part_boundary_poly_idx]);
|
||||
crossing_1_in_or_mid = PolygonUtils::moveInside(crossing_1_in_cp, offset_dist_to_get_from_on_the_polygon_to_outside); // in-case
|
||||
}
|
||||
else
|
||||
{
|
||||
crossing_1_in_or_mid = startPoint; // mid-case
|
||||
}
|
||||
|
||||
if (endInside)
|
||||
{
|
||||
ClosestPolygonPoint crossing_2_in_cp = PolygonUtils::findClosest(crossing_1_in_or_mid, boundary_inside[end_part_boundary_poly_idx]);
|
||||
crossing_2_in_or_mid = PolygonUtils::moveInside(crossing_2_in_cp, offset_dist_to_get_from_on_the_polygon_to_outside); // in-case
|
||||
}
|
||||
else
|
||||
{
|
||||
crossing_2_in_or_mid = endPoint; // mid-case
|
||||
}
|
||||
}
|
||||
|
||||
bool avoid_other_parts_now = avoid_other_parts;
|
||||
if (avoid_other_parts_now && vSize2(crossing_1_in_or_mid - crossing_2_in_or_mid) < offset_from_outlines_outside * offset_from_outlines_outside * 4)
|
||||
{ // parts are next to eachother, i.e. the direct crossing will always be smaller than two crossings via outside
|
||||
avoid_other_parts_now = false;
|
||||
}
|
||||
|
||||
if (avoid_other_parts_now)
|
||||
{ // compute the crossing points when moving through air
|
||||
Polygons& outside = getBoundaryOutside(); // comb through all air, since generally the outside consists of a single part
|
||||
|
||||
|
||||
crossing_1_out = crossing_1_in_or_mid;
|
||||
if (startInside || outside.inside(crossing_1_in_or_mid, true)) // start in_between
|
||||
{ // move outside
|
||||
ClosestPolygonPoint* crossing_1_out_cpp = PolygonUtils::findClose(crossing_1_in_or_mid, outside, getOutsideLocToLine());
|
||||
if (crossing_1_out_cpp)
|
||||
{
|
||||
crossing_1_out = PolygonUtils::moveOutside(*crossing_1_out_cpp, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
else
|
||||
{
|
||||
PolygonUtils::moveOutside(outside, crossing_1_out, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
int64_t in_out_dist2_1 = vSize2(crossing_1_out - crossing_1_in_or_mid);
|
||||
if (startInside && in_out_dist2_1 > max_crossing_dist2) // moveInside moved too far
|
||||
{ // if move is to far over in_between
|
||||
// find crossing closer by
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(boundary_inside[start_part_boundary_poly_idx], startPoint, endPoint);
|
||||
if (best)
|
||||
{
|
||||
crossing_1_in_or_mid = PolygonUtils::moveInside(best->first, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
crossing_1_out = PolygonUtils::moveOutside(best->second, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
crossing_2_out = crossing_2_in_or_mid;
|
||||
if (endInside || outside.inside(crossing_2_in_or_mid, true))
|
||||
{ // move outside
|
||||
ClosestPolygonPoint* crossing_2_out_cpp = PolygonUtils::findClose(crossing_2_in_or_mid, outside, getOutsideLocToLine());
|
||||
if (crossing_2_out_cpp)
|
||||
{
|
||||
crossing_2_out = PolygonUtils::moveOutside(*crossing_2_out_cpp, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
else
|
||||
{
|
||||
PolygonUtils::moveOutside(outside, crossing_2_out, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
int64_t in_out_dist2_2 = vSize2(crossing_2_out - crossing_2_in_or_mid);
|
||||
if (endInside && in_out_dist2_2 > max_crossing_dist2) // moveInside moved too far
|
||||
{ // if move is to far over in_between
|
||||
// find crossing closer by
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(boundary_inside[end_part_boundary_poly_idx], endPoint, crossing_1_out);
|
||||
if (best)
|
||||
{
|
||||
crossing_2_in_or_mid = PolygonUtils::moveInside(best->first, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
crossing_2_out = PolygonUtils::moveOutside(best->second, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startInside)
|
||||
{
|
||||
// start to boundary
|
||||
PolygonsPart part_begin = partsView_inside.assemblePart(start_part_idx); // comb through the starting part only
|
||||
combPaths.emplace_back();
|
||||
LinePolygonsCrossings::comb(part_begin, startPoint, crossing_1_in_or_mid, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
}
|
||||
|
||||
// throught air from boundary to boundary
|
||||
if (avoid_other_parts_now)
|
||||
{
|
||||
combPaths.emplace_back();
|
||||
combPaths.throughAir = true;
|
||||
if ( vSize(crossing_1_in_or_mid - crossing_2_in_or_mid) < vSize(crossing_1_in_or_mid - crossing_1_out) + vSize(crossing_2_in_or_mid - crossing_2_out) )
|
||||
{ // via outside is moving more over the in-between zone
|
||||
combPaths.back().push_back(crossing_1_in_or_mid);
|
||||
combPaths.back().push_back(crossing_2_in_or_mid);
|
||||
}
|
||||
else
|
||||
{
|
||||
LinePolygonsCrossings::comb(getBoundaryOutside(), crossing_1_out, crossing_2_out, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // directly through air (not avoiding other parts)
|
||||
combPaths.emplace_back();
|
||||
combPaths.throughAir = true;
|
||||
combPaths.back().cross_boundary = true; // TODO: calculate whether we cross a boundary!
|
||||
combPaths.back().push_back(crossing_1_in_or_mid);
|
||||
combPaths.back().push_back(crossing_2_in_or_mid);
|
||||
}
|
||||
|
||||
if (endInside)
|
||||
{
|
||||
// boundary to end
|
||||
PolygonsPart part_end = partsView_inside.assemblePart(end_part_idx); // comb through end part only
|
||||
combPaths.emplace_back();
|
||||
LinePolygonsCrossings::comb(part_end, crossing_2_in_or_mid, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> Comb::findBestCrossing(PolygonRef from, Point estimated_start, Point estimated_end)
|
||||
{
|
||||
ClosestPolygonPoint* best_in = nullptr;
|
||||
ClosestPolygonPoint* best_out = nullptr;
|
||||
int64_t best_detour_dist = std::numeric_limits<int64_t>::max();
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> crossing_out_candidates = PolygonUtils::findClose(from, getBoundaryOutside(), getOutsideLocToLine());
|
||||
for (std::pair<ClosestPolygonPoint, ClosestPolygonPoint>& crossing_candidate : crossing_out_candidates)
|
||||
{
|
||||
int64_t crossing_dist2 = vSize2(crossing_candidate.first.location - crossing_candidate.second.location);
|
||||
if (crossing_dist2 > max_crossing_dist2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t dist_to_start = vSize(crossing_candidate.second.location - estimated_start); // use outside location, so that the crossing direction is taken into account
|
||||
int64_t dist_to_end = vSize(crossing_candidate.second.location - estimated_end);
|
||||
int64_t detour_dist = dist_to_start + dist_to_end;
|
||||
if (detour_dist < best_detour_dist)
|
||||
{
|
||||
best_in = &crossing_candidate.first;
|
||||
best_out = &crossing_candidate.second;
|
||||
best_detour_dist = detour_dist;
|
||||
}
|
||||
}
|
||||
if (best_detour_dist == std::numeric_limits<int64_t>::max())
|
||||
{
|
||||
return std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>();
|
||||
}
|
||||
return std::make_shared<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>(*best_in, *best_out);
|
||||
}
|
||||
|
||||
|
||||
void LinePolygonsCrossings::calcScanlineCrossings()
|
||||
{
|
||||
|
||||
min_crossing_idx = NO_INDEX;
|
||||
max_crossing_idx = NO_INDEX;
|
||||
|
||||
for(unsigned int poly_idx = 0; poly_idx < boundary.size(); poly_idx++)
|
||||
{
|
||||
PolyCrossings minMax(poly_idx);
|
||||
PolygonRef poly = boundary[poly_idx];
|
||||
Point p0 = transformation_matrix.apply(poly[poly.size() - 1]);
|
||||
for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
Point p1 = transformation_matrix.apply(poly[point_idx]);
|
||||
if((p0.Y >= transformed_startPoint.Y && p1.Y <= transformed_startPoint.Y) || (p1.Y >= transformed_startPoint.Y && p0.Y <= transformed_startPoint.Y))
|
||||
{
|
||||
if(p1.Y == p0.Y) //Line segment is parallel with the scanline. That means that both endpoints lie on the scanline, so they will have intersected with the adjacent line.
|
||||
{
|
||||
p0 = p1;
|
||||
continue;
|
||||
}
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
|
||||
|
||||
if (x >= transformed_startPoint.X && x <= transformed_endPoint.X)
|
||||
{
|
||||
if(x < minMax.min.x) //For the leftmost intersection, move x left to stay outside of the border.
|
||||
//Note: The actual distance from the intersection to the border is almost always less than dist_to_move_boundary_point_outside, since it only moves along the direction of the scanline.
|
||||
{
|
||||
minMax.min.x = x;
|
||||
minMax.min.point_idx = point_idx;
|
||||
}
|
||||
if(x > minMax.max.x) //For the rightmost intersection, move x right to stay outside of the border.
|
||||
{
|
||||
minMax.max.x = x;
|
||||
minMax.max.point_idx = point_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
|
||||
if (minMax.min.point_idx != NO_INDEX)
|
||||
{ // then also max.point_idx != -1
|
||||
if (min_crossing_idx == NO_INDEX || minMax.min.x < crossings[min_crossing_idx].min.x) { min_crossing_idx = crossings.size(); }
|
||||
if (max_crossing_idx == NO_INDEX || minMax.max.x > crossings[max_crossing_idx].max.x) { max_crossing_idx = crossings.size(); }
|
||||
crossings.push_back(minMax);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool LinePolygonsCrossings::lineSegmentCollidesWithBoundary()
|
||||
{
|
||||
Point diff = endPoint - startPoint;
|
||||
|
||||
transformation_matrix = PointMatrix(diff);
|
||||
transformed_startPoint = transformation_matrix.apply(startPoint);
|
||||
transformed_endPoint = transformation_matrix.apply(endPoint);
|
||||
|
||||
for(PolygonRef poly : boundary)
|
||||
{
|
||||
Point p0 = transformation_matrix.apply(poly.back());
|
||||
for(Point p1_ : poly)
|
||||
{
|
||||
Point p1 = transformation_matrix.apply(p1_);
|
||||
if ((p0.Y > transformed_startPoint.Y && p1.Y < transformed_startPoint.Y) || (p1.Y > transformed_startPoint.Y && p0.Y < transformed_startPoint.Y))
|
||||
{
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
|
||||
|
||||
if (x > transformed_startPoint.X && x < transformed_endPoint.X)
|
||||
return true;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void LinePolygonsCrossings::getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored)
|
||||
{
|
||||
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored) || !lineSegmentCollidesWithBoundary())
|
||||
{
|
||||
//We're not crossing any boundaries. So skip the comb generation.
|
||||
combPath.push_back(startPoint);
|
||||
combPath.push_back(endPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
calcScanlineCrossings();
|
||||
|
||||
CombPath basicPath;
|
||||
getBasicCombingPath(basicPath);
|
||||
optimizePath(basicPath, combPath);
|
||||
// combPath = basicPath; // uncomment to disable comb path optimization
|
||||
}
|
||||
|
||||
|
||||
void LinePolygonsCrossings::getBasicCombingPath(CombPath& combPath)
|
||||
{
|
||||
for (PolyCrossings* crossing = getNextPolygonAlongScanline(transformed_startPoint.X)
|
||||
; crossing != nullptr
|
||||
; crossing = getNextPolygonAlongScanline(crossing->max.x))
|
||||
{
|
||||
getBasicCombingPath(*crossing, combPath);
|
||||
}
|
||||
combPath.push_back(endPoint);
|
||||
}
|
||||
|
||||
void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, CombPath& combPath)
|
||||
{
|
||||
PolygonRef poly = boundary[polyCrossings.poly_idx];
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x - dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
if ( ( polyCrossings.max.point_idx - polyCrossings.min.point_idx + poly.size() ) % poly.size()
|
||||
< poly.size() / 2 )
|
||||
{ // follow the path in the same direction as the winding order of the boundary polygon
|
||||
for(unsigned int point_idx = polyCrossings.min.point_idx
|
||||
; point_idx != polyCrossings.max.point_idx
|
||||
; point_idx = (point_idx < poly.size() - 1) ? (point_idx + 1) : (0))
|
||||
{
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // follow the path in the opposite direction of the winding order of the boundary polygon
|
||||
unsigned int min_idx = (polyCrossings.min.point_idx == 0)? poly.size() - 1: polyCrossings.min.point_idx - 1;
|
||||
unsigned int max_idx = (polyCrossings.max.point_idx == 0)? poly.size() - 1: polyCrossings.max.point_idx - 1;
|
||||
|
||||
for(unsigned int point_idx = min_idx; point_idx != max_idx; point_idx = (point_idx > 0) ? (point_idx - 1) : (poly.size() - 1))
|
||||
{
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x + dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
LinePolygonsCrossings::PolyCrossings* LinePolygonsCrossings::getNextPolygonAlongScanline(int64_t x)
|
||||
{
|
||||
PolyCrossings* ret = nullptr;
|
||||
for(PolyCrossings& crossing : crossings)
|
||||
{
|
||||
if (crossing.min.x > x && (ret == nullptr || crossing.min.x < ret->min.x) )
|
||||
{
|
||||
ret = &crossing;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimized_comb_path)
|
||||
{
|
||||
optimized_comb_path.push_back(startPoint);
|
||||
for(unsigned int point_idx = 1; point_idx<comb_path.size(); point_idx++)
|
||||
{
|
||||
if(comb_path[point_idx] == comb_path[point_idx - 1]) //Two points are the same. Skip the second.
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Point& current_point = optimized_comb_path.back();
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx]))
|
||||
{
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx - 1]))
|
||||
{
|
||||
comb_path.cross_boundary = true;
|
||||
}
|
||||
optimized_comb_path.push_back(comb_path[point_idx - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// : dont add the newest point
|
||||
|
||||
// TODO: add the below extra optimization? (+/- 7% extra computation time, +/- 2% faster print for Dual_extrusion_support_generation.stl)
|
||||
while (optimized_comb_path.size() > 1)
|
||||
{
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
optimized_comb_path.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
optimized_comb_path.push_back(comb_path.back());
|
||||
return true;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
+450
-73
@@ -18,8 +18,6 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "settings/SettingRegistry.h" // loadExtruderJSONsettings
|
||||
|
||||
#define DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(x)
|
||||
|
||||
// std::cerr << x;
|
||||
@@ -57,45 +55,193 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* A template structure used to store data to be sent to the front end.
|
||||
*/
|
||||
template <typename T>
|
||||
class SliceDataStruct
|
||||
{
|
||||
SliceDataStruct(const SliceDataStruct&) = delete;
|
||||
SliceDataStruct& operator=(const SliceDataStruct&) = delete;
|
||||
public:
|
||||
|
||||
SliceDataStruct()
|
||||
: sliced_objects(0)
|
||||
, current_layer_count(0)
|
||||
, current_layer_offset(0)
|
||||
{ }
|
||||
|
||||
//! The number of sliced objects for this sliced object list
|
||||
int sliced_objects;
|
||||
|
||||
int current_layer_count;//!< Number of layers for which data has been buffered in slice_data so far.
|
||||
int current_layer_offset;//!< Offset to add to layer number for the current slice object when slicing one at a time.
|
||||
|
||||
std::unordered_map<int, std::shared_ptr<T>> slice_data;
|
||||
};
|
||||
|
||||
class CommandSocket::Private
|
||||
{
|
||||
public:
|
||||
Private()
|
||||
: socket(nullptr)
|
||||
, object_count(0)
|
||||
, sliced_objects(0)
|
||||
, current_layer_count(0)
|
||||
, current_layer_offset(0)
|
||||
{ }
|
||||
|
||||
std::shared_ptr<cura::proto::Layer> getLayerById(int id);
|
||||
|
||||
std::shared_ptr<cura::proto::LayerOptimized> getOptimizedLayerById(int id);
|
||||
|
||||
Arcus::Socket* socket;
|
||||
|
||||
// Number of objects that need to be sliced
|
||||
int object_count;
|
||||
|
||||
// Number of sliced objects for this sliced object list
|
||||
int sliced_objects;
|
||||
|
||||
// Number of layers sent to the front end so far
|
||||
// Used for incrementing the current layer in one at a time mode
|
||||
int current_layer_count;
|
||||
int current_layer_offset;
|
||||
|
||||
std::string temp_gcode_file;
|
||||
std::ostringstream gcode_output_stream;
|
||||
|
||||
// Print object that olds one or more meshes that need to be sliced.
|
||||
std::vector< std::shared_ptr<MeshGroup> > objects_to_slice;
|
||||
|
||||
std::unordered_map<int, std::shared_ptr<cura::proto::Layer>> sliced_layers;
|
||||
SliceDataStruct<cura::proto::Layer> sliced_layers;
|
||||
SliceDataStruct<cura::proto::LayerOptimized> optimized_layers;
|
||||
};
|
||||
|
||||
/*!
|
||||
* PathCompiler buffers and prepares the sliced data to be sent to the front end and saves them in
|
||||
* appropriate buffers
|
||||
*/
|
||||
class CommandSocket::PathCompiler
|
||||
{
|
||||
typedef cura::proto::PathSegment::PointType PointType;
|
||||
static_assert(sizeof(PrintFeatureType) == 1, "To be compatible with the Cura frontend code PrintFeatureType needs to be of size 1");
|
||||
//! Reference to the private data of the CommandSocket used to send the data to the front end.
|
||||
CommandSocket::Private& _cs_private_data;
|
||||
//! Keeps track of the current layer number being processed. If layer number is set to a different value, the current data is flushed to CommandSocket.
|
||||
int _layer_nr;
|
||||
int extruder;
|
||||
PointType data_point_type;
|
||||
|
||||
std::vector<PrintFeatureType> line_types; //!< Line types for the line segments stored, the size of this vector is N.
|
||||
std::vector<float> line_widths; //!< Line widths for the line segments stored, the size of this vector is N.
|
||||
std::vector<float> points; //!< The points used to define the line segments, the size of this vector is D*(N+1) as each line segment is defined from one point to the next. D is the dimensionality of the point.
|
||||
|
||||
Point last_point;
|
||||
|
||||
PathCompiler(const PathCompiler&) = delete;
|
||||
PathCompiler& operator=(const PathCompiler&) = delete;
|
||||
public:
|
||||
PathCompiler(CommandSocket::Private& cs_private_data):
|
||||
_cs_private_data(cs_private_data),
|
||||
_layer_nr(0),
|
||||
extruder(0),
|
||||
data_point_type(cura::proto::PathSegment::Point2D),
|
||||
line_types(),
|
||||
line_widths(),
|
||||
points(),
|
||||
last_point{0,0}
|
||||
{}
|
||||
~PathCompiler()
|
||||
{
|
||||
if (line_types.size())
|
||||
{
|
||||
flushPathSegments();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Used to select which layer the following layer data is intended for.
|
||||
*/
|
||||
void setLayer(int new_layer_nr)
|
||||
{
|
||||
if (_layer_nr != new_layer_nr)
|
||||
{
|
||||
flushPathSegments();
|
||||
_layer_nr = new_layer_nr;
|
||||
}
|
||||
}
|
||||
/*!
|
||||
* Returns the current layer which data is written to.
|
||||
*/
|
||||
int getLayer() const
|
||||
{
|
||||
return _layer_nr;
|
||||
}
|
||||
/*!
|
||||
* Used to set which extruder will be used for printing the following layer data is intended for.
|
||||
*/
|
||||
void setExtruder(int new_extruder)
|
||||
{
|
||||
if (extruder != new_extruder)
|
||||
{
|
||||
flushPathSegments();
|
||||
extruder = new_extruder;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Special handling of the first point in an added line sequence.
|
||||
* If the new sequence of lines does not start at the current end point
|
||||
* of the path this jump is marked as PrintFeatureType::NoneType
|
||||
*/
|
||||
void handleInitialPoint(Point from)
|
||||
{
|
||||
if (points.size() == 0)
|
||||
{
|
||||
addPoint2D(from);
|
||||
}
|
||||
else if (from != last_point)
|
||||
{
|
||||
addLineSegment(PrintFeatureType::NoneType, from, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Transfers the currently buffered line segments to the
|
||||
* CommandSocket layer message storage.
|
||||
*/
|
||||
void flushPathSegments();
|
||||
/*!
|
||||
* Move the current point of this path to \position.
|
||||
*/
|
||||
void setCurrentPosition(Point position)
|
||||
{
|
||||
handleInitialPoint(position);
|
||||
}
|
||||
/*!
|
||||
* Adds a single line segment to the current path. The line segment added is from the current last point to point \p to
|
||||
*/
|
||||
void sendLineTo(PrintFeatureType print_feature_type, Point to, int width);
|
||||
/*!
|
||||
* Adds closed polygon to the current path
|
||||
*/
|
||||
void sendPolygon(PrintFeatureType print_feature_type, Polygon poly, int width);
|
||||
private:
|
||||
/*!
|
||||
* Convert and add a point to the points buffer, each point being represented as two consecutive floats. All members adding a 2D point to the data should use this function.
|
||||
*/
|
||||
void addPoint2D(Point point)
|
||||
{
|
||||
points.push_back(INT2MM(point.X));
|
||||
points.push_back(INT2MM(point.Y));
|
||||
last_point = point;
|
||||
}
|
||||
/*!
|
||||
* Implements the functionality of adding a single 2D line segment to the path data. All member functions adding a 2D line segment should use this functions.
|
||||
*/
|
||||
void addLineSegment(PrintFeatureType print_feature_type, Point point, int line_width)
|
||||
{
|
||||
addPoint2D(point);
|
||||
line_types.push_back(print_feature_type);
|
||||
line_widths.push_back(INT2MM(line_width));
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
CommandSocket::CommandSocket()
|
||||
#ifdef ARCUS
|
||||
: private_data(new Private)
|
||||
, path_comp(new PathCompiler(*private_data))
|
||||
#endif
|
||||
{
|
||||
#ifdef ARCUS
|
||||
@@ -127,12 +273,14 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
//private_data->socket->registerMessageType(1, &Cura::ObjectList::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::Slice::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::Layer::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::LayerOptimized::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::Progress::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::GCodeLayer::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::ObjectPrintTime::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::PrintTimeMaterialEstimates::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::SettingList::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::GCodePrefix::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::SlicingFinished::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::SettingExtruder::default_instance());
|
||||
|
||||
private_data->socket->connect(ip, port);
|
||||
|
||||
@@ -152,44 +300,83 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
{
|
||||
// Actually start handling messages.
|
||||
Arcus::MessagePtr message = private_data->socket->takeNextMessage();
|
||||
|
||||
/*
|
||||
* handle a message which consists purely of a SettingList
|
||||
cura::proto::SettingList* setting_list = dynamic_cast<cura::proto::SettingList*>(message.get());
|
||||
if (setting_list)
|
||||
{
|
||||
handleSettingList(setting_list);
|
||||
}
|
||||
*/
|
||||
|
||||
/*cura::proto::ObjectList* object_list = dynamic_cast<cura::proto::ObjectList*>(message.get());
|
||||
/*
|
||||
* handle a message which consists purely of an ObjectList
|
||||
cura::proto::ObjectList* object_list = dynamic_cast<cura::proto::ObjectList*>(message.get());
|
||||
if (object_list)
|
||||
{
|
||||
handleObjectList(object_list);
|
||||
}*/
|
||||
|
||||
cura::proto::Slice* slice = dynamic_cast<cura::proto::Slice*>(message.get());
|
||||
}
|
||||
*/
|
||||
|
||||
// Handle the main Slice message
|
||||
cura::proto::Slice* slice = dynamic_cast<cura::proto::Slice*>(message.get()); // See if the message is of the message type Slice; returns nullptr otherwise
|
||||
if (slice)
|
||||
{
|
||||
logDebug("Received a Slice message\n");
|
||||
const cura::proto::SettingList& global_settings = slice->global_settings();
|
||||
for (auto setting : global_settings.settings())
|
||||
{
|
||||
FffProcessor::getInstance()->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
// Reset object counts
|
||||
private_data->object_count = 0;
|
||||
for (auto object : slice->object_lists())
|
||||
{
|
||||
handleObjectList(&object);
|
||||
handleObjectList(&object, slice->extruders());
|
||||
}
|
||||
//For every object, set the extruder fallbacks from the limit_to_extruder.
|
||||
for (const cura::proto::SettingExtruder setting_extruder : slice->limit_to_extruder())
|
||||
{
|
||||
const int32_t extruder_nr = setting_extruder.extruder(); //Implicit cast from Protobuf's int32 to normal int32.
|
||||
for (std::shared_ptr<MeshGroup> meshgroup : private_data->objects_to_slice)
|
||||
{
|
||||
if (extruder_nr < 0 || extruder_nr >= meshgroup->getExtruderCount()) //We obtained an invalid value from the front-end. Ignore.
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const ExtruderTrain* settings_base = meshgroup->getExtruderTrain(extruder_nr); //The extruder train that the setting should fall back to.
|
||||
for (Mesh& mesh : meshgroup->meshes)
|
||||
{
|
||||
mesh.setSettingInheritBase(setting_extruder.name(), *settings_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
logDebug("Done reading Slice message\n");
|
||||
}
|
||||
|
||||
//If there is an object to slice, do so.
|
||||
if (private_data->objects_to_slice.size())
|
||||
{
|
||||
int object_count = private_data->objects_to_slice.size();
|
||||
logDebug("Slicing %i objects\n", object_count);
|
||||
FffProcessor::getInstance()->resetMeshGroupNumber();
|
||||
int i = 1;
|
||||
for (auto object : private_data->objects_to_slice)
|
||||
{
|
||||
logDebug("Slicing object %i of %i\n", i, object_count);
|
||||
if (!FffProcessor::getInstance()->processMeshGroup(object.get()))
|
||||
{
|
||||
logError("Slicing mesh group failed!");
|
||||
}
|
||||
i++;
|
||||
}
|
||||
logDebug("Done slicing objects\n");
|
||||
|
||||
private_data->objects_to_slice.clear();
|
||||
FffProcessor::getInstance()->finalize();
|
||||
flushGcode();
|
||||
sendPrintTime();
|
||||
sendPrintTimeMaterialEstimates();
|
||||
sendFinishedSlicing();
|
||||
slice_another_time = false; // TODO: remove this when multiple slicing with CuraEngine is safe
|
||||
//TODO: Support all-at-once/one-at-a-time printing
|
||||
@@ -197,7 +384,7 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
//private_data->object_to_slice.reset();
|
||||
//private_data->processor->resetFileNumber();
|
||||
|
||||
//sendPrintTime();
|
||||
//sendPrintTimeMaterialEstimates();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
@@ -208,7 +395,7 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
}
|
||||
|
||||
#ifdef ARCUS
|
||||
void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
void CommandSocket::handleObjectList(cura::proto::ObjectList* list, const google::protobuf::RepeatedPtrField<cura::proto::Extruder> settings_per_extruder_train)
|
||||
{
|
||||
if (list->objects_size() <= 0)
|
||||
{
|
||||
@@ -220,18 +407,30 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
//private_data->object_ids.clear();
|
||||
private_data->objects_to_slice.push_back(std::make_shared<MeshGroup>(FffProcessor::getInstance()));
|
||||
MeshGroup* meshgroup = private_data->objects_to_slice.back().get();
|
||||
|
||||
|
||||
// load meshgroup settings
|
||||
for (auto setting : list->settings())
|
||||
{
|
||||
meshgroup->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
|
||||
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_nr, train);
|
||||
|
||||
{ // load extruder settings
|
||||
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
}
|
||||
|
||||
for (auto extruder : settings_per_extruder_train)
|
||||
{
|
||||
int extruder_nr = extruder.id();
|
||||
ExtruderTrain* train = meshgroup->getExtruderTrain(extruder_nr);
|
||||
for (auto setting : extruder.settings().settings())
|
||||
{
|
||||
train->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (auto object : list->objects())
|
||||
{
|
||||
int bytes_per_face = BYTES_PER_FLOAT * FLOATS_PER_VECTOR * VECTORS_PER_FACE;
|
||||
@@ -243,7 +442,9 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
continue;
|
||||
}
|
||||
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR("solid Cura_out\n");
|
||||
int extruder_train_nr = 0; // TODO: make primary extruder configurable!
|
||||
|
||||
// Check to which extruder train this object belongs
|
||||
int extruder_train_nr = 0; // assume extruder 0 if setting wasn't supplied
|
||||
for (auto setting : object.settings())
|
||||
{
|
||||
if (setting.name() == "extruder_nr")
|
||||
@@ -278,6 +479,7 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" endfacet\n");
|
||||
}
|
||||
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR("endsolid Cura_out\n");
|
||||
|
||||
for (auto setting : object.settings())
|
||||
{
|
||||
mesh.setSetting(setting.name(), setting.value());
|
||||
@@ -289,51 +491,101 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
private_data->object_count++;
|
||||
meshgroup->finalize();
|
||||
}
|
||||
|
||||
void CommandSocket::handleSettingList(cura::proto::SettingList* list)
|
||||
{
|
||||
for (auto setting : list->settings())
|
||||
{
|
||||
FffProcessor::getInstance()->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void CommandSocket::sendLayerInfo(int layer_nr, int32_t z, int32_t height)
|
||||
void CommandSocket::sendOptimizedLayerInfo(int layer_nr, int32_t z, int32_t height)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
std::shared_ptr<cura::proto::Layer> layer = private_data->getLayerById(layer_nr);
|
||||
std::shared_ptr<cura::proto::LayerOptimized> layer = private_data->getOptimizedLayerById(layer_nr);
|
||||
layer->set_height(z);
|
||||
layer->set_thickness(height);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::sendPolygons(PrintFeatureType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
void CommandSocket::sendPolygons(PrintFeatureType type, const Polygons& polygons, int line_width)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if (polygons.size() == 0)
|
||||
return;
|
||||
|
||||
std::shared_ptr<cura::proto::Layer> proto_layer = private_data->getLayerById(layer_nr);
|
||||
|
||||
for (unsigned int i = 0; i < polygons.size(); ++i)
|
||||
{
|
||||
cura::proto::Polygon* p = proto_layer->add_polygons();
|
||||
p->set_type(static_cast<cura::proto::Polygon_Type>(type));
|
||||
std::string polydata;
|
||||
polydata.append(reinterpret_cast<const char*>(polygons[i].data()), polygons[i].size() * sizeof(Point));
|
||||
p->set_points(polydata);
|
||||
p->set_line_width(line_width);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
auto& path_comp = CommandSocket::getInstance()->path_comp;
|
||||
|
||||
for (unsigned int i = 0; i < polygons.size(); ++i)
|
||||
{
|
||||
path_comp->sendPolygon(type, polygons[i], line_width);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::sendPolygon(PrintFeatureType type, Polygon& polygon, int line_width)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
auto& path_comp = CommandSocket::getInstance()->path_comp;
|
||||
|
||||
path_comp->sendPolygon(type, polygon, line_width);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::sendLineTo(cura::PrintFeatureType type, Point to, int line_width)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
auto& path_comp = CommandSocket::getInstance()->path_comp;
|
||||
|
||||
path_comp->sendLineTo(type, to, line_width);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::setSendCurrentPosition(Point position)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
auto& path_comp = CommandSocket::getInstance()->path_comp;
|
||||
path_comp->setCurrentPosition(position);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::setLayerForSend(int layer_nr)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
auto& path_comp = CommandSocket::getInstance()->path_comp;
|
||||
path_comp->setLayer(layer_nr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::setExtruderForSend(int extruder)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
auto& path_comp = CommandSocket::getInstance()->path_comp;
|
||||
path_comp->setExtruder(extruder);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void CommandSocket::sendProgress(float amount)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
auto message = std::make_shared<cura::proto::Progress>();
|
||||
amount /= private_data->object_count;
|
||||
amount += private_data->sliced_objects * (1. / private_data->object_count);
|
||||
amount += private_data->optimized_layers.sliced_objects * (1. / private_data->object_count);
|
||||
message->set_amount(amount);
|
||||
private_data->socket->sendMessage(message);
|
||||
#endif
|
||||
@@ -344,13 +596,24 @@ void CommandSocket::sendProgressStage(Progress::Stage stage)
|
||||
// TODO
|
||||
}
|
||||
|
||||
void CommandSocket::sendPrintTime()
|
||||
void CommandSocket::sendPrintTimeMaterialEstimates()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
auto message = std::make_shared<cura::proto::ObjectPrintTime>();
|
||||
logDebug("Sending print time and material estimates.\n");
|
||||
auto message = std::make_shared<cura::proto::PrintTimeMaterialEstimates>();
|
||||
|
||||
message->set_time(FffProcessor::getInstance()->getTotalPrintTime());
|
||||
message->set_material_amount(FffProcessor::getInstance()->getTotalFilamentUsed(0));
|
||||
int num_extruders = FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count");
|
||||
for (int extruder_nr (0); extruder_nr < num_extruders; ++extruder_nr)
|
||||
{
|
||||
cura::proto::MaterialEstimates* material_message = message->add_materialestimates();
|
||||
|
||||
material_message->set_id(extruder_nr);
|
||||
material_message->set_material_amount(FffProcessor::getInstance()->getTotalFilamentUsed(extruder_nr));
|
||||
}
|
||||
|
||||
private_data->socket->sendMessage(message);
|
||||
logDebug("Done sending print time and material estimates.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -368,22 +631,48 @@ void CommandSocket::sendLayerData()
|
||||
#ifdef ARCUS
|
||||
#endif
|
||||
#ifdef ARCUS
|
||||
private_data->sliced_objects++;
|
||||
private_data->current_layer_offset = private_data->current_layer_count;
|
||||
log("End sliced object called. Sending ", private_data->current_layer_count, " layers.");
|
||||
auto& data = private_data->sliced_layers;
|
||||
|
||||
if (private_data->sliced_objects >= private_data->object_count)
|
||||
data.sliced_objects++;
|
||||
data.current_layer_offset = data.current_layer_count;
|
||||
// log("End sliced object called. Sending %d layers.", data.current_layer_count);
|
||||
|
||||
// Only send the data to the front end when all mesh groups have been processed.
|
||||
if (data.sliced_objects >= private_data->object_count)
|
||||
{
|
||||
for (std::pair<const int, std::shared_ptr<cura::proto::Layer>> entry : private_data->sliced_layers) //Note: This is in no particular order!
|
||||
for (std::pair<const int, std::shared_ptr<cura::proto::Layer>> entry : data.slice_data) //Note: This is in no particular order!
|
||||
{
|
||||
private_data->socket->sendMessage(entry.second); //Send the actual layers.
|
||||
}
|
||||
private_data->sliced_objects = 0;
|
||||
private_data->current_layer_count = 0;
|
||||
private_data->current_layer_offset = 0;
|
||||
private_data->sliced_layers.clear();
|
||||
auto done_message = std::make_shared<cura::proto::SlicingFinished>();
|
||||
private_data->socket->sendMessage(done_message);
|
||||
data.sliced_objects = 0;
|
||||
data.current_layer_count = 0;
|
||||
data.current_layer_offset = 0;
|
||||
data.slice_data.clear();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::sendOptimizedLayerData()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
path_comp->flushPathSegments(); // make sure the last path segment has been flushed from the compiler
|
||||
|
||||
auto& data = private_data->optimized_layers;
|
||||
|
||||
data.sliced_objects++;
|
||||
data.current_layer_offset = data.current_layer_count;
|
||||
log("End sliced object called. Sending %d layers.", data.current_layer_count);
|
||||
|
||||
if (data.sliced_objects >= private_data->object_count)
|
||||
{
|
||||
for (std::pair<const int, std::shared_ptr<cura::proto::LayerOptimized>> entry : data.slice_data) //Note: This is in no particular order!
|
||||
{
|
||||
private_data->socket->sendMessage(entry.second); //Send the actual layers.
|
||||
}
|
||||
data.sliced_objects = 0;
|
||||
data.current_layer_count = 0;
|
||||
data.current_layer_offset = 0;
|
||||
data.slice_data.clear();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -391,8 +680,10 @@ void CommandSocket::sendLayerData()
|
||||
void CommandSocket::sendFinishedSlicing()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
logDebug("Sending Slicing Finished message.\n");
|
||||
std::shared_ptr<cura::proto::SlicingFinished> done_message = std::make_shared<cura::proto::SlicingFinished>();
|
||||
private_data->socket->sendMessage(done_message);
|
||||
logDebug("Done sending Slicing Finished message.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -426,12 +717,12 @@ void CommandSocket::sendGCodePrefix(std::string prefix)
|
||||
#ifdef ARCUS
|
||||
std::shared_ptr<cura::proto::Layer> CommandSocket::Private::getLayerById(int id)
|
||||
{
|
||||
id += current_layer_offset;
|
||||
id += sliced_layers.current_layer_offset;
|
||||
|
||||
auto itr = sliced_layers.find(id);
|
||||
auto itr = sliced_layers.slice_data.find(id);
|
||||
|
||||
std::shared_ptr<cura::proto::Layer> layer;
|
||||
if (itr != sliced_layers.end())
|
||||
if (itr != sliced_layers.slice_data.end())
|
||||
{
|
||||
layer = itr->second;
|
||||
}
|
||||
@@ -439,12 +730,98 @@ std::shared_ptr<cura::proto::Layer> CommandSocket::Private::getLayerById(int id)
|
||||
{
|
||||
layer = std::make_shared<cura::proto::Layer>();
|
||||
layer->set_id(id);
|
||||
current_layer_count++;
|
||||
sliced_layers[id] = layer;
|
||||
sliced_layers.current_layer_count++;
|
||||
sliced_layers.slice_data[id] = layer;
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ARCUS
|
||||
std::shared_ptr<cura::proto::LayerOptimized> CommandSocket::Private::getOptimizedLayerById(int id)
|
||||
{
|
||||
id += optimized_layers.current_layer_offset;
|
||||
|
||||
auto itr = optimized_layers.slice_data.find(id);
|
||||
|
||||
std::shared_ptr<cura::proto::LayerOptimized> layer;
|
||||
if (itr != optimized_layers.slice_data.end())
|
||||
{
|
||||
layer = itr->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
layer = std::make_shared<cura::proto::LayerOptimized>();
|
||||
layer->set_id(id);
|
||||
optimized_layers.current_layer_count++;
|
||||
optimized_layers.slice_data[id] = layer;
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ARCUS
|
||||
void CommandSocket::PathCompiler::flushPathSegments()
|
||||
{
|
||||
if (line_types.size() > 0 && CommandSocket::isInstantiated())
|
||||
{
|
||||
std::shared_ptr<cura::proto::LayerOptimized> proto_layer = _cs_private_data.getOptimizedLayerById(_layer_nr);
|
||||
|
||||
cura::proto::PathSegment* p = proto_layer->add_path_segment();
|
||||
p->set_extruder(extruder);
|
||||
p->set_point_type(data_point_type);
|
||||
std::string line_type_data;
|
||||
line_type_data.append(reinterpret_cast<const char*>(line_types.data()), line_types.size()*sizeof(PrintFeatureType));
|
||||
p->set_line_type(line_type_data);
|
||||
std::string polydata;
|
||||
polydata.append(reinterpret_cast<const char*>(points.data()), points.size() * sizeof(float));
|
||||
p->set_points(polydata);
|
||||
std::string line_width_data;
|
||||
line_width_data.append(reinterpret_cast<const char*>(line_widths.data()), line_widths.size()*sizeof(float));
|
||||
p->set_line_width(line_width_data);
|
||||
}
|
||||
points.clear();
|
||||
line_widths.clear();
|
||||
line_types.clear();
|
||||
}
|
||||
|
||||
void CommandSocket::PathCompiler::sendLineTo(PrintFeatureType print_feature_type, Point to, int width)
|
||||
{
|
||||
assert(points.size() > 0 && "A point must already be in the buffer for sendLineTo(.) to function properly");
|
||||
|
||||
if (to != last_point)
|
||||
{
|
||||
addLineSegment(print_feature_type, to, width);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandSocket::PathCompiler::sendPolygon(PrintFeatureType print_feature_type, Polygon polygon, int width)
|
||||
{
|
||||
if (polygon.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = polygon.begin();
|
||||
handleInitialPoint(*it);
|
||||
|
||||
const auto it_end = polygon.end();
|
||||
while (++it != it_end)
|
||||
{
|
||||
// Ignore zero-length segments.
|
||||
if (*it != last_point)
|
||||
{
|
||||
addLineSegment(print_feature_type, *it, width);
|
||||
}
|
||||
}
|
||||
// Make sure the polygon is closed
|
||||
if (*polygon.begin() != polygon.back())
|
||||
{
|
||||
addLineSegment(print_feature_type, *polygon.begin(), width);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+56
-21
@@ -42,30 +42,54 @@ public:
|
||||
/*!
|
||||
* Handler for ObjectList message.
|
||||
* Loads all objects from the message and starts the slicing process
|
||||
*
|
||||
* Also handles meshgroup settings and extruder settings.
|
||||
*
|
||||
* \param[in] list The list of objects to slice
|
||||
* \param[in] settings_per_extruder_train The extruder train settings to load into the meshgroup
|
||||
*/
|
||||
void handleObjectList(cura::proto::ObjectList* list);
|
||||
|
||||
/*!
|
||||
* Handler for SettingList message.
|
||||
* This simply sets all the settings by using key value pair
|
||||
*/
|
||||
void handleSettingList(cura::proto::SettingList* list);
|
||||
void handleObjectList(cura::proto::ObjectList* list, const google::protobuf::RepeatedPtrField<cura::proto::Extruder> settings_per_extruder_train);
|
||||
#endif
|
||||
|
||||
|
||||
/*!
|
||||
* Send info on a layer to be displayed by the forntend: set the z and the thickness of the layer.
|
||||
* Send info on an optimized layer to be displayed by the forntend: set the z and the thickness of the layer.
|
||||
*/
|
||||
void sendLayerInfo(int layer_nr, int32_t z, int32_t height);
|
||||
|
||||
/*!
|
||||
* Send a polygon to the engine. This is used for the layerview in the GUI
|
||||
*/
|
||||
void sendPolygons(cura::PrintFeatureType type, int layer_nr, cura::Polygons& polygons, int line_width);
|
||||
void sendOptimizedLayerInfo(int layer_nr, int32_t z, int32_t height);
|
||||
|
||||
/*!
|
||||
* Send a polygon to the engine if the command socket is instantiated. This is used for the layerview in the GUI
|
||||
* Send a polygon to the front-end. This is used for the layerview in the GUI
|
||||
*/
|
||||
static void sendPolygonsToCommandSocket(cura::PrintFeatureType type, int layer_nr, cura::Polygons& polygons, int line_width);
|
||||
static void sendPolygons(cura::PrintFeatureType type, const cura::Polygons& polygons, int line_width);
|
||||
|
||||
/*!
|
||||
* Send a polygon to the front-end. This is used for the layerview in the GUI
|
||||
*/
|
||||
static void sendPolygon(cura::PrintFeatureType type, Polygon& polygon, int line_width);
|
||||
|
||||
/*!
|
||||
* Send a line to the front-end. This is used for the layerview in the GUI
|
||||
*/
|
||||
static void sendLineTo(cura::PrintFeatureType type, Point to, int line_width);
|
||||
|
||||
/*!
|
||||
* Set the current position of the path compiler to \p position. This is used for the layerview in the GUI
|
||||
*/
|
||||
static void setSendCurrentPosition(Point position);
|
||||
|
||||
/*!
|
||||
* Set which layer is being used for the following calls to SendPolygons, SendPolygon and SendLineTo.
|
||||
*/
|
||||
static void setLayerForSend(int layer_nr);
|
||||
|
||||
/*!
|
||||
* Set which extruder is being used for the following calls to SendPolygons, SendPolygon and SendLineTo.
|
||||
*/
|
||||
static void setExtruderForSend(int extruder);
|
||||
|
||||
/*!
|
||||
* Send a polygon to the front-end if the command socket is instantiated. This is used for the layerview in the GUI
|
||||
*/
|
||||
static void sendPolygonsToCommandSocket(cura::PrintFeatureType type, int layer_nr, const cura::Polygons& polygons, int line_width);
|
||||
|
||||
/*!
|
||||
* Send progress to GUI
|
||||
@@ -80,7 +104,7 @@ public:
|
||||
/*!
|
||||
* Send time estimate of how long print would take.
|
||||
*/
|
||||
void sendPrintTime();
|
||||
void sendPrintTimeMaterialEstimates();
|
||||
|
||||
/*!
|
||||
* Does nothing at the moment
|
||||
@@ -88,13 +112,22 @@ public:
|
||||
void sendPrintMaterialForObject(int index, int extruder_nr, float material_amount);
|
||||
|
||||
/*!
|
||||
* Send the sliced layer data to the GUI.
|
||||
* Send the slices of the model as polygons to the GUI.
|
||||
*
|
||||
* The GUI may use this to visualise the g-code, so that the user can
|
||||
* inspect the result of slicing.
|
||||
* The GUI may use this to visualize the early result of the slicing
|
||||
* process.
|
||||
*/
|
||||
void sendLayerData();
|
||||
|
||||
/*!
|
||||
* Send the sliced layer data to the GUI after the optimization is done and
|
||||
* the actual order in which to print has been set.
|
||||
*
|
||||
* The GUI may use this to visualize the g-code, so that the user can
|
||||
* inspect the result of slicing.
|
||||
*/
|
||||
void sendOptimizedLayerData();
|
||||
|
||||
/*!
|
||||
* \brief Sends a message to indicate that all the slicing is done.
|
||||
*
|
||||
@@ -115,6 +148,8 @@ public:
|
||||
private:
|
||||
class Private;
|
||||
const std::unique_ptr<Private> private_data;
|
||||
class PathCompiler;
|
||||
const std::unique_ptr<PathCompiler> path_comp;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#ifndef DEBUG_H
|
||||
#define DEBUG_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define __FILE_NAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
|
||||
|
||||
|
||||
#define DEBUG_HERE std::cerr << __FILE_NAME__ << " : " << __LINE__ << std::endl
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
#define DEBUG_SHOW_LINE 1
|
||||
|
||||
#if DEBUG_SHOW_LINE == 1
|
||||
#define DEBUG_FILE_LINE __FILE_NAME__ << "." << __LINE__ << ": "
|
||||
#else
|
||||
#define DEBUG_FILE_LINE ""
|
||||
#endif
|
||||
|
||||
#if DEBUG == 1
|
||||
# define DEBUG_DO(x) do { x } while (0)
|
||||
# define DEBUG_SHOW(x) do { std::cerr << DEBUG_FILE_LINE << #x << " = " << x << std::endl; } while (0)
|
||||
# define DEBUG_PRINTLN(x) do { std::cerr << DEBUG_FILE_LINE << x << std::endl; } while (0)
|
||||
#else
|
||||
# define DEBUG_DO(x)
|
||||
# define DEBUG_SHOW(x)
|
||||
# define DEBUG_PRINTLN(x)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#if 0==1
|
||||
#define ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT};
|
||||
#endif
|
||||
#define ENUM(name, ...) enum class name { __VA_ARGS__}; \
|
||||
inline std::ostream& operator<<(std::ostream& os, name value) { \
|
||||
std::string enumName = #name; \
|
||||
std::string str = #__VA_ARGS__; \
|
||||
int len = str.length(); \
|
||||
std::vector<std::string> strings; \
|
||||
std::ostringstream temp; \
|
||||
for(int i = 0; i < len; i ++) { \
|
||||
if(isspace(str[i])) continue; \
|
||||
else if(str[i] == ',') { \
|
||||
strings.push_back(temp.str()); \
|
||||
temp.str(std::string());\
|
||||
} \
|
||||
else temp<< str[i]; \
|
||||
} \
|
||||
strings.push_back(temp.str()); \
|
||||
os << enumName << "::" << strings[static_cast<int>(value)]; \
|
||||
return os;}
|
||||
|
||||
#endif // DEBUG_H
|
||||
+251
-108
@@ -7,6 +7,7 @@
|
||||
#include "utils/logoutput.h"
|
||||
#include "PrintFeature.h"
|
||||
#include "utils/Date.h"
|
||||
#include "utils/string.h" // MMtoStream, PrecisionedDouble
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -15,6 +16,8 @@ GCodeExport::GCodeExport()
|
||||
, currentPosition(0,0,MM2INT(20))
|
||||
, layer_nr(0)
|
||||
{
|
||||
*output_stream << std::fixed;
|
||||
|
||||
current_e_value = 0;
|
||||
current_extruder = 0;
|
||||
currentFanSpeed = -1;
|
||||
@@ -22,51 +25,77 @@ GCodeExport::GCodeExport()
|
||||
totalPrintTime = 0.0;
|
||||
|
||||
currentSpeed = 1;
|
||||
current_acceleration = -1;
|
||||
current_jerk = -1;
|
||||
current_max_z_feedrate = -1;
|
||||
|
||||
isZHopped = 0;
|
||||
setFlavor(EGCodeFlavor::REPRAP);
|
||||
initial_bed_temp = 0;
|
||||
|
||||
extruder_count = 0;
|
||||
|
||||
total_bounding_box = AABB3D();
|
||||
}
|
||||
|
||||
GCodeExport::~GCodeExport()
|
||||
{
|
||||
}
|
||||
|
||||
void GCodeExport::preSetup(MeshGroup* settings)
|
||||
void GCodeExport::preSetup(const MeshGroup* meshgroup)
|
||||
{
|
||||
setFlavor(settings->getSettingAsGCodeFlavor("machine_gcode_flavor"));
|
||||
use_extruder_offset_to_offset_coords = settings->getSettingBoolean("machine_use_extruder_offset_to_offset_coords");
|
||||
setFlavor(meshgroup->getSettingAsGCodeFlavor("machine_gcode_flavor"));
|
||||
use_extruder_offset_to_offset_coords = meshgroup->getSettingBoolean("machine_use_extruder_offset_to_offset_coords");
|
||||
|
||||
extruder_count = settings->getSettingAsCount("machine_extruder_count");
|
||||
extruder_count = meshgroup->getSettingAsCount("machine_extruder_count");
|
||||
|
||||
for (unsigned int n = 0; n < extruder_count; n++)
|
||||
for (const Mesh& mesh : meshgroup->meshes)
|
||||
{
|
||||
ExtruderTrain* train = settings->getExtruderTrain(n);
|
||||
setFilamentDiameter(n, train->getSettingInMicrons("material_diameter"));
|
||||
|
||||
extruder_attr[n].nozzle_size = train->getSettingInMicrons("machine_nozzle_size");
|
||||
extruder_attr[n].nozzle_offset = Point(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
||||
|
||||
extruder_attr[n].start_code = train->getSettingString("machine_extruder_start_code");
|
||||
extruder_attr[n].end_code = train->getSettingString("machine_extruder_end_code");
|
||||
|
||||
extruder_attr[n].extruder_switch_retraction_config.distance = train->getSettingInMillimeters("switch_extruder_retraction_amount");
|
||||
extruder_attr[n].extruder_switch_retraction_config.prime_volume = 0.0;
|
||||
extruder_attr[n].extruder_switch_retraction_config.speed = train->getSettingInMillimetersPerSecond("switch_extruder_retraction_speed");
|
||||
extruder_attr[n].extruder_switch_retraction_config.primeSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_prime_speed");
|
||||
extruder_attr[n].extruder_switch_retraction_config.zHop = train->getSettingInMicrons("switch_extruder_retraction_hop");
|
||||
extruder_attr[n].extruder_switch_retraction_config.retraction_count_max = 9999999; // extruder switch retraction is never limited
|
||||
extruder_attr[n].extruder_switch_retraction_config.retraction_extrusion_window = 99999.9; // so that extruder switch retractions won't affect the retraction buffer (extruded_volume_at_previous_n_retractions)
|
||||
extruder_attr[n].extruder_switch_retraction_config.retraction_min_travel_distance = 0; // no limitation on travel distance for an extruder switch retract
|
||||
|
||||
extruder_attr[n].last_retraction_prime_speed = train->getSettingInMillimetersPerSecond("retraction_prime_speed"); // the alternative would be switch_extruder_prime_speed, but dual extrusion might not even be configured...
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh.getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
extruder_attr[mesh.getSettingAsIndex("extruder_nr")].is_used = true;
|
||||
}
|
||||
}
|
||||
machine_dimensions.x = settings->getSettingInMicrons("machine_width");
|
||||
machine_dimensions.y = settings->getSettingInMicrons("machine_depth");
|
||||
machine_dimensions.z = settings->getSettingInMicrons("machine_height");
|
||||
|
||||
machine_name = settings->getSettingString("machine_name");
|
||||
for (unsigned int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
const ExtruderTrain* train = meshgroup->getExtruderTrain(extruder_nr);
|
||||
|
||||
if (meshgroup->getSettingAsIndex("adhesion_extruder_nr") == int(extruder_nr) && meshgroup->getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
extruder_attr[extruder_nr].is_used = true;
|
||||
}
|
||||
for (const Mesh& mesh : meshgroup->meshes)
|
||||
{
|
||||
if ((mesh.getSettingBoolean("support_enable") && mesh.getSettingBoolean("support_interface_enable") && meshgroup->getSettingAsIndex("support_interface_extruder_nr") == int(extruder_nr))
|
||||
|| (mesh.getSettingBoolean("support_enable") && meshgroup->getSettingAsIndex("support_infill_extruder_nr") == int(extruder_nr))
|
||||
|| (mesh.getSettingBoolean("support_enable") && meshgroup->getSettingAsIndex("support_extruder_nr_layer_0") == int(extruder_nr))
|
||||
)
|
||||
{
|
||||
extruder_attr[extruder_nr].is_used = true;
|
||||
}
|
||||
}
|
||||
setFilamentDiameter(extruder_nr, train->getSettingInMicrons("material_diameter"));
|
||||
|
||||
extruder_attr[extruder_nr].prime_pos = Point3(train->getSettingInMicrons("extruder_prime_pos_x"), train->getSettingInMicrons("extruder_prime_pos_y"), train->getSettingInMicrons("extruder_prime_pos_z"));
|
||||
extruder_attr[extruder_nr].prime_pos_is_abs = train->getSettingBoolean("extruder_prime_pos_abs");
|
||||
|
||||
extruder_attr[extruder_nr].nozzle_size = train->getSettingInMicrons("machine_nozzle_size");
|
||||
extruder_attr[extruder_nr].nozzle_offset = Point(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
||||
extruder_attr[extruder_nr].material_guid = train->getSettingString("material_guid");
|
||||
|
||||
extruder_attr[extruder_nr].start_code = train->getSettingString("machine_extruder_start_code");
|
||||
extruder_attr[extruder_nr].end_code = train->getSettingString("machine_extruder_end_code");
|
||||
|
||||
extruder_attr[extruder_nr].last_retraction_prime_speed = train->getSettingInMillimetersPerSecond("retraction_prime_speed"); // the alternative would be switch_extruder_prime_speed, but dual extrusion might not even be configured...
|
||||
}
|
||||
machine_dimensions.x = meshgroup->getSettingInMicrons("machine_width");
|
||||
machine_dimensions.y = meshgroup->getSettingInMicrons("machine_depth");
|
||||
machine_dimensions.z = meshgroup->getSettingInMicrons("machine_height");
|
||||
|
||||
machine_name = meshgroup->getSettingString("machine_name");
|
||||
|
||||
if (flavor == EGCodeFlavor::BFB)
|
||||
{
|
||||
@@ -76,19 +105,28 @@ void GCodeExport::preSetup(MeshGroup* settings)
|
||||
{
|
||||
new_line = "\n";
|
||||
}
|
||||
|
||||
estimateCalculator.setFirmwareDefaults(meshgroup);
|
||||
}
|
||||
|
||||
void GCodeExport::setInitialTemps(const MeshGroup& settings)
|
||||
{
|
||||
int start_extruder_nr = 0;
|
||||
if (settings.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
start_extruder_nr = settings.getSettingAsIndex("adhesion_extruder_nr");
|
||||
}
|
||||
for (unsigned int extr_nr = 0; extr_nr < extruder_count; extr_nr++)
|
||||
{
|
||||
const ExtruderTrain* extr_train = settings.getExtruderTrain(extr_nr);
|
||||
assert(extr_train);
|
||||
double temp = extr_train->getSettingInDegreeCelsius((extr_nr == 0)? "material_print_temperature" : "material_standby_temperature");
|
||||
const ExtruderTrain& train = *settings.getExtruderTrain(extr_nr);
|
||||
|
||||
double print_temp_0 = train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
double print_temp_here = (print_temp_0 != 0)? print_temp_0 : train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
double temp = ((int)extr_nr == start_extruder_nr)? print_temp_here : train.getSettingInDegreeCelsius("material_standby_temperature");
|
||||
setInitialTemp(extr_nr, temp);
|
||||
}
|
||||
|
||||
initial_bed_temp = settings.getSettingInDegreeCelsius("material_bed_temperature");
|
||||
initial_bed_temp = settings.getSettingInDegreeCelsius("material_bed_temperature_layer_0");
|
||||
}
|
||||
|
||||
void GCodeExport::setInitialTemp(int extruder_nr, double temp)
|
||||
@@ -101,7 +139,7 @@ void GCodeExport::setInitialTemp(int extruder_nr, double temp)
|
||||
}
|
||||
|
||||
|
||||
std::string GCodeExport::getFileHeader(const double* print_time, const std::vector<double>& filament_used, const std::vector<int16_t>& mat_ids)
|
||||
std::string GCodeExport::getFileHeader(const double* print_time, const std::vector<double>& filament_used, const std::vector<std::string>& mat_ids)
|
||||
{
|
||||
std::ostringstream prefix;
|
||||
switch (flavor)
|
||||
@@ -117,14 +155,18 @@ std::string GCodeExport::getFileHeader(const double* print_time, const std::vect
|
||||
|
||||
for (unsigned int extr_nr = 0; extr_nr < extruder_count; extr_nr++)
|
||||
{
|
||||
if (!extruder_attr[extr_nr].is_used)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".INITIAL_TEMPERATURE:" << extruder_attr[extr_nr].initial_temp << new_line;
|
||||
if (filament_used.size() == extruder_count)
|
||||
{
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".MATERIAL.VOLUME_USED:" << static_cast<int>(filament_used[extr_nr]) << new_line;
|
||||
}
|
||||
if (mat_ids.size() == extruder_count)
|
||||
if (mat_ids.size() == extruder_count && mat_ids[extr_nr] != "")
|
||||
{
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".MATERIAL.GUID:" << mat_ids[extr_nr] << new_line; // TODO: convert to hexadecimal format
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".MATERIAL.GUID:" << mat_ids[extr_nr] << new_line;
|
||||
}
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".NOZZLE.DIAMETER:" << float(INT2MM(getNozzleSize(extr_nr))) << new_line;
|
||||
}
|
||||
@@ -135,12 +177,12 @@ std::string GCodeExport::getFileHeader(const double* print_time, const std::vect
|
||||
prefix << ";PRINT.TIME:" << static_cast<int>(*print_time) << new_line;
|
||||
}
|
||||
|
||||
prefix << ";PRINT.SIZE.MIN.X:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Y:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Z:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.X:" << INT2MM(machine_dimensions.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Y:" << INT2MM(machine_dimensions.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Z:" << INT2MM(machine_dimensions.z) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.X:" << INT2MM(total_bounding_box.min.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Y:" << INT2MM(total_bounding_box.min.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Z:" << INT2MM(total_bounding_box.min.z) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.X:" << INT2MM(total_bounding_box.max.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Y:" << INT2MM(total_bounding_box.max.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Z:" << INT2MM(total_bounding_box.max.z) << new_line;
|
||||
prefix << ";END_OF_HEADER" << new_line;
|
||||
return prefix.str();
|
||||
default:
|
||||
@@ -169,17 +211,27 @@ void GCodeExport::setOutputStream(std::ostream* stream)
|
||||
*output_stream << std::fixed;
|
||||
}
|
||||
|
||||
int GCodeExport::getNozzleSize(int extruder_idx)
|
||||
bool GCodeExport::getExtruderIsUsed(const int extruder_nr) const
|
||||
{
|
||||
return extruder_attr[extruder_idx].nozzle_size;
|
||||
return extruder_attr[extruder_nr].is_used;
|
||||
}
|
||||
|
||||
Point GCodeExport::getExtruderOffset(int id)
|
||||
int GCodeExport::getNozzleSize(const int extruder_nr) const
|
||||
{
|
||||
return extruder_attr[extruder_nr].nozzle_size;
|
||||
}
|
||||
|
||||
Point GCodeExport::getExtruderOffset(const int id) const
|
||||
{
|
||||
return extruder_attr[id].nozzle_offset;
|
||||
}
|
||||
|
||||
Point GCodeExport::getGcodePos(int64_t x, int64_t y, int extruder_train)
|
||||
std::string GCodeExport::getMaterialGUID(const int extruder_nr) const
|
||||
{
|
||||
return extruder_attr[extruder_nr].material_guid;
|
||||
}
|
||||
|
||||
Point GCodeExport::getGcodePos(const int64_t x, const int64_t y, const int extruder_train) const
|
||||
{
|
||||
if (use_extruder_offset_to_offset_coords) { return Point(x,y) - getExtruderOffset(extruder_train); }
|
||||
else { return Point(x,y); }
|
||||
@@ -221,7 +273,7 @@ EGCodeFlavor GCodeExport::getFlavor()
|
||||
|
||||
void GCodeExport::setZ(int z)
|
||||
{
|
||||
this->zPos = z;
|
||||
this->current_layer_z = z;
|
||||
}
|
||||
|
||||
Point3 GCodeExport::getPosition()
|
||||
@@ -333,6 +385,7 @@ void GCodeExport::updateTotalPrintTime()
|
||||
{
|
||||
totalPrintTime += estimateCalculator.calculate();
|
||||
estimateCalculator.reset();
|
||||
writeTimeComment(totalPrintTime);
|
||||
}
|
||||
|
||||
void GCodeExport::writeComment(std::string comment)
|
||||
@@ -350,9 +403,9 @@ void GCodeExport::writeComment(std::string comment)
|
||||
*output_stream << new_line;
|
||||
}
|
||||
|
||||
void GCodeExport::writeTypeComment(const char* type)
|
||||
void GCodeExport::writeTimeComment(const double time)
|
||||
{
|
||||
*output_stream << ";TYPE:" << type << new_line;
|
||||
*output_stream << ";TIME_ELAPSED:" << time << new_line;
|
||||
}
|
||||
|
||||
void GCodeExport::writeTypeComment(PrintFeatureType type)
|
||||
@@ -371,7 +424,7 @@ void GCodeExport::writeTypeComment(PrintFeatureType type)
|
||||
case PrintFeatureType::Support:
|
||||
*output_stream << ";TYPE:SUPPORT" << new_line;
|
||||
break;
|
||||
case PrintFeatureType::Skirt:
|
||||
case PrintFeatureType::SkirtBrim:
|
||||
*output_stream << ";TYPE:SKIRT" << new_line;
|
||||
break;
|
||||
case PrintFeatureType::Infill:
|
||||
@@ -428,7 +481,7 @@ void GCodeExport::writeDelay(double timeAmount)
|
||||
|
||||
void GCodeExport::writeMove(Point p, double speed, double extrusion_mm3_per_mm)
|
||||
{
|
||||
writeMove(p.X, p.Y, zPos, speed, extrusion_mm3_per_mm);
|
||||
writeMove(p.X, p.Y, current_layer_z, speed, extrusion_mm3_per_mm);
|
||||
}
|
||||
|
||||
void GCodeExport::writeMove(Point3 p, double speed, double extrusion_mm3_per_mm)
|
||||
@@ -455,7 +508,7 @@ void GCodeExport::writeMoveBFB(int x, int y, int z, double speed, double extrusi
|
||||
{
|
||||
//fprintf(f, "; %f e-per-mm %d mm-width %d mm/s\n", extrusion_per_mm, lineWidth, speed);
|
||||
//fprintf(f, "M108 S%0.1f\r\n", rpm);
|
||||
*output_stream << "M108 S" << std::setprecision(1) << rpm << new_line;
|
||||
*output_stream << "M108 S" << PrecisionedDouble{1, rpm} << new_line;
|
||||
currentSpeed = double(rpm);
|
||||
}
|
||||
//Add M101 or M201 to enable the proper extruder.
|
||||
@@ -480,10 +533,8 @@ void GCodeExport::writeMoveBFB(int x, int y, int z, double speed, double extrusi
|
||||
extruder_attr[current_extruder].retraction_e_amount_current = 1.0; // 1.0 used as stub; BFB doesn't use the actual retraction amount; it performs retraction on the firmware automatically
|
||||
}
|
||||
}
|
||||
*output_stream << std::setprecision(3) <<
|
||||
"G1 X" << INT2MM(gcode_pos.X) <<
|
||||
" Y" << INT2MM(gcode_pos.Y) <<
|
||||
" Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << new_line;
|
||||
*output_stream << "G1 X" << MMtoStream{gcode_pos.X} << " Y" << MMtoStream{gcode_pos.Y} << " Z" << MMtoStream{z};
|
||||
*output_stream << " F" << PrecisionedDouble{1, fspeed} << new_line;
|
||||
|
||||
currentPosition = Point3(x, y, z);
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), speed);
|
||||
@@ -495,11 +546,14 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
return;
|
||||
|
||||
#ifdef ASSERT_INSANE_OUTPUT
|
||||
assert(speed < 200 && speed > 1); // normal F values occurring in UM2 gcode (this code should not be compiled for release)
|
||||
assert(speed < 400 && speed > 1); // normal F values occurring in UM2 gcode (this code should not be compiled for release)
|
||||
assert(currentPosition != no_point3);
|
||||
assert(Point3(x, y, z) != no_point3);
|
||||
assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(300)); // no crazy positions (this code should not be compiled for release)
|
||||
#endif //ASSERT_INSANE_OUTPUT
|
||||
|
||||
total_bounding_box.include(Point3(x, y, z));
|
||||
|
||||
if (extrusion_mm3_per_mm < 0)
|
||||
logWarning("Warning! Negative extrusion move!");
|
||||
|
||||
@@ -518,7 +572,7 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
Point3 diff = Point3(x,y,z) - getPosition();
|
||||
if (isZHopped > 0)
|
||||
{
|
||||
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z) << new_line;
|
||||
*output_stream << "G1 Z" << MMtoStream{currentPosition.z} << new_line;
|
||||
isZHopped = 0;
|
||||
}
|
||||
double prime_volume = extruder_attr[current_extruder].prime_volume;
|
||||
@@ -531,7 +585,7 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
//Assume default UM2 retraction settings.
|
||||
if (prime_volume > 0)
|
||||
{
|
||||
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << new_line;
|
||||
*output_stream << "G1 F" << PrecisionedDouble{1, extruder_attr[current_extruder].last_retraction_prime_speed * 60} << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{5, current_e_value} << new_line;
|
||||
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
|
||||
}
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), 25.0);
|
||||
@@ -539,7 +593,7 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
else
|
||||
{
|
||||
current_e_value += extruder_attr[current_extruder].retraction_e_amount_current;
|
||||
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << new_line;
|
||||
*output_stream << "G1 F" << PrecisionedDouble{1, extruder_attr[current_extruder].last_retraction_prime_speed * 60} << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{5, current_e_value} << new_line;
|
||||
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
|
||||
}
|
||||
@@ -551,7 +605,7 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
}
|
||||
else if (prime_volume > 0.0)
|
||||
{
|
||||
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << new_line;
|
||||
*output_stream << "G1 F" << PrecisionedDouble{1, extruder_attr[current_extruder].last_retraction_prime_speed * 60} << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{5, current_e_value} << new_line;
|
||||
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
|
||||
}
|
||||
@@ -563,30 +617,22 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
{
|
||||
*output_stream << "G0";
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
// we should send this travel as a non-retraction move
|
||||
cura::Polygons travelPoly;
|
||||
PolygonRef travel = travelPoly.newPoly();
|
||||
travel.add(Point(currentPosition.x, currentPosition.y));
|
||||
travel.add(Point(x, y));
|
||||
CommandSocket::getInstance()->sendPolygons(extruder_attr[current_extruder].retraction_e_amount_current ? PrintFeatureType::MoveRetraction : PrintFeatureType::MoveCombing, layer_nr, travelPoly, extruder_attr[current_extruder].retraction_e_amount_current ? MM2INT(0.2) : MM2INT(0.1));
|
||||
}
|
||||
CommandSocket::sendLineTo(extruder_attr[current_extruder].retraction_e_amount_current ? PrintFeatureType::MoveRetraction : PrintFeatureType::MoveCombing, Point(x, y), extruder_attr[current_extruder].retraction_e_amount_current ? MM2INT(0.2) : MM2INT(0.1));
|
||||
}
|
||||
|
||||
if (currentSpeed != speed)
|
||||
{
|
||||
*output_stream << " F" << (speed * 60);
|
||||
*output_stream << " F" << PrecisionedDouble{1, speed * 60};
|
||||
currentSpeed = speed;
|
||||
}
|
||||
|
||||
*output_stream << std::setprecision(3) <<
|
||||
" X" << INT2MM(gcode_pos.X) <<
|
||||
" Y" << INT2MM(gcode_pos.Y);
|
||||
*output_stream << " X" << MMtoStream{gcode_pos.X} << " Y" << MMtoStream{gcode_pos.Y};
|
||||
if (z != currentPosition.z + isZHopped)
|
||||
*output_stream << " Z" << INT2MM(z + isZHopped);
|
||||
{
|
||||
*output_stream << " Z" << MMtoStream{z + isZHopped};
|
||||
}
|
||||
if (extrusion_mm3_per_mm > 0.000001)
|
||||
*output_stream << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value;
|
||||
*output_stream << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{5, current_e_value};
|
||||
*output_stream << new_line;
|
||||
|
||||
currentPosition = Point3(x, y, z);
|
||||
@@ -661,8 +707,8 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool ext
|
||||
{
|
||||
double speed = ((retraction_diff_e_amount < 0.0)? config->speed : extr_attr.last_retraction_prime_speed) * 60;
|
||||
current_e_value += retraction_diff_e_amount;
|
||||
*output_stream << "G1 F" << speed << " "
|
||||
<< extr_attr.extruderCharacter << std::setprecision(5) << current_e_value << new_line;
|
||||
*output_stream << "G1 F" << PrecisionedDouble{1, speed} << " "
|
||||
<< extr_attr.extruderCharacter << PrecisionedDouble{5, current_e_value} << new_line;
|
||||
currentSpeed = speed;
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
|
||||
extr_attr.last_retraction_prime_speed = config->primeSpeed;
|
||||
@@ -671,49 +717,70 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool ext
|
||||
extr_attr.retraction_e_amount_current = new_retraction_e_amount; // suppose that for UM2 the retraction amount in the firmware is equal to the provided amount
|
||||
extr_attr.prime_volume += config->prime_volume;
|
||||
|
||||
if (config->zHop > 0)
|
||||
}
|
||||
|
||||
void GCodeExport::writeZhopStart(int hop_height)
|
||||
{
|
||||
if (hop_height > 0)
|
||||
{
|
||||
isZHopped = config->zHop;
|
||||
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z + isZHopped) << new_line;
|
||||
isZHopped = hop_height;
|
||||
*output_stream << "G1 Z" << MMtoStream{currentPosition.z + isZHopped} << new_line;
|
||||
total_bounding_box.include(currentPosition + Point3(0, 0, isZHopped));
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeExport::writeRetraction_extruderSwitch()
|
||||
void GCodeExport::writeZhopEnd()
|
||||
{
|
||||
ExtruderTrainAttributes& extr_attr = extruder_attr[current_extruder];
|
||||
RetractionConfig* config = &extr_attr.extruder_switch_retraction_config;
|
||||
|
||||
writeRetraction(config, true, true);
|
||||
if (isZHopped)
|
||||
{
|
||||
isZHopped = 0;
|
||||
*output_stream << "G1 Z" << MMtoStream{currentPosition.z} << new_line;
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeExport::switchExtruder(int new_extruder)
|
||||
void GCodeExport::startExtruder(int new_extruder)
|
||||
{
|
||||
if (new_extruder != current_extruder) // wouldn't be the case on the very first extruder start if it's extruder 0
|
||||
{
|
||||
if (flavor == EGCodeFlavor::MAKERBOT)
|
||||
{
|
||||
*output_stream << "M135 T" << new_extruder << new_line;
|
||||
}
|
||||
else
|
||||
{
|
||||
*output_stream << "T" << new_extruder << new_line;
|
||||
}
|
||||
}
|
||||
|
||||
current_extruder = new_extruder;
|
||||
|
||||
assert(getCurrentExtrudedVolume() == 0.0 && "Just after an extruder switch we haven't extruded anything yet!");
|
||||
resetExtrusionValue(); // zero the E value on the new extruder, just to be sure
|
||||
|
||||
writeCode(extruder_attr[new_extruder].start_code.c_str());
|
||||
CommandSocket::setExtruderForSend(new_extruder);
|
||||
CommandSocket::setSendCurrentPosition( getPositionXY() );
|
||||
|
||||
//Change the Z position so it gets re-writting again. We do not know if the switch code modified the Z position.
|
||||
currentPosition.z += 1;
|
||||
}
|
||||
|
||||
void GCodeExport::switchExtruder(int new_extruder, const RetractionConfig& retraction_config_old_extruder)
|
||||
{
|
||||
if (current_extruder == new_extruder)
|
||||
return;
|
||||
|
||||
writeRetraction_extruderSwitch();
|
||||
bool force = true;
|
||||
bool extruder_switch = true;
|
||||
writeRetraction(&const_cast<RetractionConfig&>(retraction_config_old_extruder), force, extruder_switch);
|
||||
|
||||
resetExtrusionValue(); // zero the E value on the old extruder, so that the current_e_value is registered on the old extruder
|
||||
|
||||
int old_extruder = current_extruder;
|
||||
current_extruder = new_extruder;
|
||||
|
||||
writeCode(extruder_attr[old_extruder].end_code.c_str());
|
||||
if (flavor == EGCodeFlavor::MAKERBOT)
|
||||
{
|
||||
*output_stream << "M135 T" << current_extruder << new_line;
|
||||
}
|
||||
else
|
||||
{
|
||||
*output_stream << "T" << current_extruder << new_line;
|
||||
}
|
||||
|
||||
resetExtrusionValue(); // zero the E value on the new extruder, because a firmware bug in Griffin adjusted the E-value when performing a toolswitch (should be fixed as of 9 may 2016)
|
||||
|
||||
writeCode(extruder_attr[new_extruder].start_code.c_str());
|
||||
|
||||
//Change the Z position so it gets re-writting again. We do not know if the switch code modified the Z position.
|
||||
currentPosition.z += 1;
|
||||
startExtruder(new_extruder);
|
||||
}
|
||||
|
||||
void GCodeExport::writeCode(const char* str)
|
||||
@@ -721,9 +788,29 @@ void GCodeExport::writeCode(const char* str)
|
||||
*output_stream << str << new_line;
|
||||
}
|
||||
|
||||
void GCodeExport::writePrimeTrain()
|
||||
void GCodeExport::writePrimeTrain(double travel_speed)
|
||||
{
|
||||
*output_stream << "G280" << new_line;
|
||||
if (extruder_attr[current_extruder].is_primed)
|
||||
{ // extruder is already primed once!
|
||||
return;
|
||||
}
|
||||
Point3 prime_pos = extruder_attr[current_extruder].prime_pos;
|
||||
if (!extruder_attr[current_extruder].prime_pos_is_abs)
|
||||
{
|
||||
prime_pos += currentPosition;
|
||||
}
|
||||
writeMove(prime_pos, travel_speed, 0.0);
|
||||
|
||||
if (flavor == EGCodeFlavor::GRIFFIN)
|
||||
{
|
||||
*output_stream << "G280" << new_line;
|
||||
}
|
||||
else
|
||||
{
|
||||
// there is no prime gcode for other firmware versions...
|
||||
}
|
||||
|
||||
extruder_attr[current_extruder].is_primed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -736,7 +823,7 @@ void GCodeExport::writeFanCommand(double speed)
|
||||
if (flavor == EGCodeFlavor::MAKERBOT)
|
||||
*output_stream << "M126 T0" << new_line; //value = speed * 255 / 100 // Makerbot cannot set fan speed...;
|
||||
else
|
||||
*output_stream << "M106 S" << (speed * 255 / 100) << new_line;
|
||||
*output_stream << "M106 S" << PrecisionedDouble{1, speed * 255 / 100} << new_line;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -752,24 +839,80 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
|
||||
{
|
||||
if (!wait && extruder_attr[extruder].currentTemperature == temperature)
|
||||
return;
|
||||
|
||||
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE)
|
||||
{ // The UM2 family doesn't support temperature commands (they are fixed in the firmware)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait)
|
||||
*output_stream << "M109";
|
||||
else
|
||||
*output_stream << "M104";
|
||||
if (extruder != current_extruder)
|
||||
*output_stream << " T" << extruder;
|
||||
*output_stream << " S" << temperature << new_line;
|
||||
#ifdef ASSERT_INSANE_OUTPUT
|
||||
assert(temperature >= 0);
|
||||
#endif // ASSERT_INSANE_OUTPUT
|
||||
*output_stream << " S" << PrecisionedDouble{1, temperature} << new_line;
|
||||
extruder_attr[extruder].currentTemperature = temperature;
|
||||
}
|
||||
|
||||
void GCodeExport::writeBedTemperatureCommand(double temperature, bool wait)
|
||||
{
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE)
|
||||
{ // The UM2 family doesn't support temperature commands (they are fixed in the firmware)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait)
|
||||
*output_stream << "M190 S";
|
||||
else
|
||||
*output_stream << "M140 S";
|
||||
*output_stream << temperature << new_line;
|
||||
*output_stream << PrecisionedDouble{1, temperature} << new_line;
|
||||
}
|
||||
|
||||
void GCodeExport::writeAcceleration(double acceleration)
|
||||
{
|
||||
if (current_acceleration != acceleration)
|
||||
{
|
||||
*output_stream << "M204 S" << PrecisionedDouble{0, acceleration} << new_line; // Print and Travel acceleration
|
||||
current_acceleration = acceleration;
|
||||
estimateCalculator.setAcceleration(acceleration);
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeExport::writeJerk(double jerk)
|
||||
{
|
||||
if (current_jerk != jerk)
|
||||
{
|
||||
if (getFlavor() == EGCodeFlavor::REPETIER)
|
||||
{
|
||||
*output_stream << "M207 X";
|
||||
}
|
||||
else
|
||||
{
|
||||
*output_stream << "M205 X";
|
||||
}
|
||||
*output_stream << PrecisionedDouble{2, jerk} << new_line;
|
||||
current_jerk = jerk;
|
||||
estimateCalculator.setMaxXyJerk(jerk);
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeExport::writeMaxZFeedrate(double max_z_feedrate)
|
||||
{
|
||||
if (current_max_z_feedrate != max_z_feedrate)
|
||||
{
|
||||
*output_stream << "M203 Z" << int(max_z_feedrate * 60) << new_line;
|
||||
current_max_z_feedrate = max_z_feedrate;
|
||||
estimateCalculator.setMaxZFeedrate(max_z_feedrate);
|
||||
}
|
||||
}
|
||||
|
||||
double GCodeExport::getCurrentMaxZFeedrate()
|
||||
{
|
||||
return current_max_z_feedrate;
|
||||
}
|
||||
|
||||
void GCodeExport::finalize(const char* endCode)
|
||||
|
||||
+111
-149
@@ -12,6 +12,7 @@
|
||||
#include "timeEstimate.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "commandSocket.h"
|
||||
#include "RetractionConfig.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -29,133 +30,6 @@ struct CoastingConfig
|
||||
double coasting_min_volume; //!< The minimal volume printed to build up enough pressure to leek the coasting_volume
|
||||
};
|
||||
|
||||
/*!
|
||||
* The retraction configuration used in the GCodePathConfig of each feature (and the travel config)
|
||||
*/
|
||||
class RetractionConfig
|
||||
{
|
||||
public:
|
||||
double distance; //!< The distance retracted (in mm)
|
||||
double speed; //!< The speed with which to retract (in mm/s)
|
||||
double primeSpeed; //!< the speed with which to unretract (in mm/s)
|
||||
double prime_volume; //!< the amount of material primed after unretracting (in mm^3)
|
||||
int zHop; //!< the amount with which to lift the head during a retraction-travel
|
||||
int retraction_min_travel_distance; //!< Minimal distance traversed to even consider retracting (in micron)
|
||||
double retraction_extrusion_window; //!< Window of mm extruded filament in which to limit the amount of retractions
|
||||
int retraction_count_max; //!< The maximum amount of retractions allowed to occur in the RetractionConfig::retraction_extrusion_window
|
||||
};
|
||||
|
||||
/*!
|
||||
* The GCodePathConfig is the configuration for moves/extrusion actions. This defines at which width the line is printed and at which speed.
|
||||
*/
|
||||
class GCodePathConfig
|
||||
{
|
||||
private:
|
||||
double speed_iconic; //!< movement speed (mm/s) specific to this print feature
|
||||
double speed; //!< current movement speed (mm/s) (modified by layer_nr etc.)
|
||||
int line_width; //!< width of the line extruded
|
||||
double flow; //!< extrusion flow modifier in %
|
||||
int layer_thickness; //!< layer height in micron
|
||||
double extrusion_mm3_per_mm;//!< mm^3 filament moved per mm line traversed
|
||||
public:
|
||||
PrintFeatureType type; //!< name of the feature type
|
||||
RetractionConfig *const retraction_config; //!< The retraction configuration to use when retracting after a part of this feature has been printed.
|
||||
|
||||
GCodePathConfig(RetractionConfig* retraction_config, PrintFeatureType type)
|
||||
: speed_iconic(0)
|
||||
, speed(0)
|
||||
, line_width(0)
|
||||
, extrusion_mm3_per_mm(0.0)
|
||||
, type(type)
|
||||
, retraction_config(retraction_config)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Initialize some of the member variables.
|
||||
*
|
||||
* \warning GCodePathConfig::setLayerHeight still has to be called before this object can be used.
|
||||
*
|
||||
* \param speed The regular speed with which to print this feature
|
||||
* \param line_width The line width for this feature
|
||||
* \param flow The flow modifier to apply to the extruded filament when printing this feature
|
||||
*/
|
||||
void init(double speed, int line_width, double flow)
|
||||
{
|
||||
speed_iconic = speed;
|
||||
this->speed = speed;
|
||||
this->line_width = line_width;
|
||||
this->flow = flow;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the layer height and (re)compute the extrusion_per_mm
|
||||
*/
|
||||
void setLayerHeight(int layer_height)
|
||||
{
|
||||
this->layer_thickness = layer_height;
|
||||
calculateExtrusion();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the speed to somewhere between the @p min_speed and the speed_iconic.
|
||||
*
|
||||
* This functions should not be called with @p layer_nr > @p max_speed_layer !
|
||||
*
|
||||
* \param min_speed The speed at layer zero
|
||||
* \param layer_nr The layer number
|
||||
* \param max_speed_layer The layer number for which the speed_iconic should be used.
|
||||
*/
|
||||
void smoothSpeed(double min_speed, int layer_nr, double max_speed_layer)
|
||||
{
|
||||
speed = (speed_iconic*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the speed to the iconic speed, i.e. the normal speed of the feature type for which this is a config.
|
||||
*/
|
||||
void setSpeedIconic()
|
||||
{
|
||||
speed = speed_iconic;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Can only be called after the layer height has been set (which is done while writing the gcode!)
|
||||
*/
|
||||
double getExtrusionMM3perMM()
|
||||
{
|
||||
return extrusion_mm3_per_mm;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the movement speed in mm/s
|
||||
*/
|
||||
double getSpeed()
|
||||
{
|
||||
return speed;
|
||||
}
|
||||
|
||||
int getLineWidth()
|
||||
{
|
||||
return line_width;
|
||||
}
|
||||
|
||||
bool isTravelPath()
|
||||
{
|
||||
return line_width == 0;
|
||||
}
|
||||
|
||||
double getFlowPercentage()
|
||||
{
|
||||
return flow;
|
||||
}
|
||||
|
||||
private:
|
||||
void calculateExtrusion()
|
||||
{
|
||||
extrusion_mm3_per_mm = INT2MM(line_width) * INT2MM(layer_thickness) * double(flow) / 100.0;
|
||||
}
|
||||
};
|
||||
|
||||
//The GCodeExport class writes the actual GCode. This is the only class that knows how GCode looks and feels.
|
||||
// Any customizations on GCodes flavors are done in this class.
|
||||
@@ -164,15 +38,20 @@ class GCodeExport : public NoCopy
|
||||
private:
|
||||
struct ExtruderTrainAttributes
|
||||
{
|
||||
Point3 prime_pos; //!< The location this nozzle is primed before printing
|
||||
bool prime_pos_is_abs; //!< Whether the prime position is absolute, rather than relative to the last given position
|
||||
bool is_primed; //!< Whether this extruder has currently already been primed in this print
|
||||
|
||||
bool is_used; //!< Whether this extruder train is actually used during the printing of all meshgroups
|
||||
int nozzle_size; //!< The nozzle size label of the nozzle (e.g. 0.4mm; irrespective of tolerances)
|
||||
Point nozzle_offset;
|
||||
char extruderCharacter;
|
||||
std::string material_guid; //!< The GUID for the material used by this extruder
|
||||
|
||||
std::string start_code;
|
||||
std::string end_code;
|
||||
double filament_area; //!< in mm^2 for non-volumetric, cylindrical filament
|
||||
|
||||
RetractionConfig extruder_switch_retraction_config; //!< Retraction configuration used when performing extruder switches
|
||||
|
||||
double totalFilament; //!< total filament used per extruder in mm^3
|
||||
int currentTemperature;
|
||||
int initial_temp; //!< Temperature this nozzle needs to be at the start of the print.
|
||||
@@ -186,7 +65,11 @@ private:
|
||||
std::deque<double> extruded_volume_at_previous_n_retractions; // in mm^3
|
||||
|
||||
ExtruderTrainAttributes()
|
||||
: nozzle_offset(0,0)
|
||||
: prime_pos(0, 0, 0)
|
||||
, prime_pos_is_abs(false)
|
||||
, is_primed(false)
|
||||
, is_used(false)
|
||||
, nozzle_offset(0,0)
|
||||
, extruderCharacter(0)
|
||||
, start_code("")
|
||||
, end_code("")
|
||||
@@ -212,7 +95,20 @@ private:
|
||||
double current_e_value; //!< The last E value written to gcode (in mm or mm^3)
|
||||
Point3 currentPosition;
|
||||
double currentSpeed; //!< The current speed (F values / 60) in mm/s
|
||||
int zPos; // TODO: why is this different from currentPosition.z ? zPos is set every layer, while currentPosition.z is set every move. However, the z position is generally not changed within a layer!
|
||||
double current_acceleration; //!< The current acceleration in the XY direction (in mm/s^2)
|
||||
double current_jerk; //!< The current jerk in the XY direction (in mm/s^3)
|
||||
double current_max_z_feedrate; //!< The current max z speed
|
||||
|
||||
AABB3D total_bounding_box; //!< The bounding box of all g-code.
|
||||
|
||||
/*!
|
||||
* The z position to be used on the next xy move, if the head wasn't in the correct z position yet.
|
||||
*
|
||||
* \see GCodeExport::writeMove(Point, double, double)
|
||||
*
|
||||
* \note After GCodeExport::writeMove(Point, double, double) has been called currentPosition.z coincides with this value
|
||||
*/
|
||||
int current_layer_z;
|
||||
int isZHopped; //!< The amount by which the print head is currently z hopped, or zero if it is not z hopped. (A z hop is used during travel moves to avoid collision with other layer parts)
|
||||
|
||||
int current_extruder;
|
||||
@@ -272,20 +168,24 @@ public:
|
||||
*
|
||||
* \param print_time The total print time in seconds of the whole gcode (if known)
|
||||
* \param filament_used The total mm^3 filament used for each extruder or a vector of the wrong size of unknown
|
||||
* \param mat_ids The material ids for each material.
|
||||
* \param mat_ids The material GUIDs for each material.
|
||||
* \return The string representing the file header
|
||||
*/
|
||||
std::string getFileHeader(const double* print_time = nullptr, const std::vector<double>& filament_used = std::vector<double>(), const std::vector<int16_t>& mat_ids = std::vector<int16_t>());
|
||||
std::string getFileHeader(const double* print_time = nullptr, const std::vector<double>& filament_used = std::vector<double>(), const std::vector<std::string>& mat_ids = std::vector<std::string>());
|
||||
|
||||
void setLayerNr(unsigned int layer_nr);
|
||||
|
||||
void setOutputStream(std::ostream* stream);
|
||||
|
||||
int getNozzleSize(int extruder_idx);
|
||||
bool getExtruderIsUsed(const int extruder_nr) const; //!< Returns whether the extruder with the given index is used up until the current meshgroup
|
||||
|
||||
Point getExtruderOffset(int id);
|
||||
|
||||
Point getGcodePos(int64_t x, int64_t y, int extruder_train);
|
||||
int getNozzleSize(const int extruder_nr) const;
|
||||
|
||||
Point getExtruderOffset(const int id) const;
|
||||
|
||||
std::string getMaterialGUID(const int extruder_nr) const; //!< returns the GUID of the material used for the nozzle with id \p extruder_nr
|
||||
|
||||
Point getGcodePos(const int64_t x, const int64_t y, const int extruder_train) const;
|
||||
|
||||
void setFlavor(EGCodeFlavor flavor);
|
||||
EGCodeFlavor getFlavor();
|
||||
@@ -329,8 +229,14 @@ public:
|
||||
void resetTotalPrintTimeAndFilament();
|
||||
|
||||
void writeComment(std::string comment);
|
||||
void writeTypeComment(const char* type);
|
||||
void writeTypeComment(PrintFeatureType type);
|
||||
|
||||
/*!
|
||||
* Write a comment saying what (estimated) time has passed up to this point
|
||||
*
|
||||
* \param time The time passed up till this point
|
||||
*/
|
||||
void writeTimeComment(const double time);
|
||||
void writeLayerComment(int layer_nr);
|
||||
void writeLayerCountComment(int layer_count);
|
||||
|
||||
@@ -345,40 +251,96 @@ public:
|
||||
|
||||
void writeDelay(double timeAmount);
|
||||
|
||||
void writeMove(Point p, double speed, double extrusion_per_mm);
|
||||
void writeMove(Point p, double speed, double extrusion_mm3_per_mm);
|
||||
|
||||
void writeMove(Point3 p, double speed, double extrusion_per_mm);
|
||||
void writeMove(Point3 p, double speed, double extrusion_mm3_per_mm);
|
||||
private:
|
||||
void writeMove(int x, int y, int z, double speed, double extrusion_per_mm);
|
||||
void writeMove(int x, int y, int z, double speed, double extrusion_mm3_per_mm);
|
||||
/*!
|
||||
* The writeMove when flavor == BFB
|
||||
*/
|
||||
void writeMoveBFB(int x, int y, int z, double speed, double extrusion_per_mm);
|
||||
void writeMoveBFB(int x, int y, int z, double speed, double extrusion_mm3_per_mm);
|
||||
public:
|
||||
void writeRetraction(RetractionConfig* config, bool force = false, bool extruder_switch = false);
|
||||
|
||||
void writeRetraction_extruderSwitch();
|
||||
|
||||
void switchExtruder(int newExtruder);
|
||||
|
||||
|
||||
/*!
|
||||
* Start a z hop with the given \p hop_height
|
||||
*
|
||||
* \param hop_height The height to move above the current layer
|
||||
*/
|
||||
void writeZhopStart(int hop_height);
|
||||
|
||||
/*!
|
||||
* End a z hop: go back to the layer height
|
||||
*
|
||||
*/
|
||||
void writeZhopEnd();
|
||||
|
||||
/*!
|
||||
* Start the new_extruder:
|
||||
* - set new extruder
|
||||
* - zero E value
|
||||
* - write extruder start gcode
|
||||
*
|
||||
* \param new_extruder The extruder to start with
|
||||
*/
|
||||
void startExtruder(int new_extruder);
|
||||
|
||||
/*!
|
||||
* Switch to the new_extruder:
|
||||
* - perform neccesary retractions
|
||||
* - fiddle with E-values
|
||||
* - write extruder end gcode
|
||||
* - set new extruder
|
||||
* - write extruder start gcode
|
||||
*
|
||||
* \param new_extruder The extruder to switch to
|
||||
* \param retraction_config_old_extruder The extruder switch retraction config of the old extruder, to perform the extruder switch retraction with.
|
||||
*/
|
||||
void switchExtruder(int new_extruder, const RetractionConfig& retraction_config_old_extruder);
|
||||
|
||||
void writeCode(const char* str);
|
||||
|
||||
/*!
|
||||
* Write the gcode for priming the current extruder train so that it can be used.
|
||||
*
|
||||
* \param travel_speed The travel speed when priming involves a movement
|
||||
*/
|
||||
void writePrimeTrain();
|
||||
void writePrimeTrain(double travel_speed);
|
||||
|
||||
void writeFanCommand(double speed);
|
||||
|
||||
void writeTemperatureCommand(int extruder, double temperature, bool wait = false);
|
||||
void writeBedTemperatureCommand(double temperature, bool wait = false);
|
||||
|
||||
/*!
|
||||
* Write the command for setting the acceleration to a specific value
|
||||
*/
|
||||
void writeAcceleration(double acceleration);
|
||||
|
||||
/*!
|
||||
* Write the command for setting the jerk to a specific value
|
||||
*/
|
||||
void writeJerk(double jerk);
|
||||
|
||||
/*!
|
||||
* Write the command for setting the maximum z feedrate to a specific value
|
||||
*/
|
||||
void writeMaxZFeedrate(double max_z_feedrate);
|
||||
|
||||
/*!
|
||||
* Get the last set max z feedrate value sent in the gcode.
|
||||
*
|
||||
* Returns a value <= 0 when no value is set.
|
||||
*/
|
||||
double getCurrentMaxZFeedrate();
|
||||
|
||||
/*!
|
||||
* Set member variables using the settings in \p settings
|
||||
*
|
||||
* \param settings The meshgroup to get the global bed temp from and to get the extruder trains from which to get the nozzle temperatures
|
||||
*/
|
||||
void preSetup(MeshGroup* settings);
|
||||
void preSetup(const MeshGroup* settings);
|
||||
|
||||
/*!
|
||||
* Handle the initial (bed/nozzle) temperatures before any gcode is processed.
|
||||
|
||||
+426
-233
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+156
-88
@@ -1,22 +1,26 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef GCODE_PLANNER_H
|
||||
#define GCODE_PLANNER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gcodeExport.h"
|
||||
#include "comb.h"
|
||||
#include "pathPlanning/Comb.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "wallOverlap.h"
|
||||
#include "commandSocket.h"
|
||||
#include "FanSpeedLayerTime.h"
|
||||
#include "SpaceFillType.h"
|
||||
#include "GCodePathConfig.h"
|
||||
|
||||
#include "utils/optional.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class SliceDataStorage;
|
||||
class SliceLayerPart;
|
||||
|
||||
/*!
|
||||
* A gcode command to insert before a specific path.
|
||||
@@ -36,7 +40,9 @@ struct NozzleTempInsert
|
||||
, extruder(extruder)
|
||||
, temperature(temperature)
|
||||
, wait(wait)
|
||||
{}
|
||||
{
|
||||
assert(temperature != 0 && temperature != -1 && "Temperature command must be set!");
|
||||
}
|
||||
|
||||
/*!
|
||||
* Write the temperature command at the current position in the gcode.
|
||||
@@ -48,14 +54,15 @@ struct NozzleTempInsert
|
||||
}
|
||||
};
|
||||
|
||||
class GCodePlanner; // forward declaration so that TimeMaterialEstimates can be a friend
|
||||
class ExtruderPlan; // forward declaration so that TimeMaterialEstimates can be a friend
|
||||
|
||||
|
||||
/*!
|
||||
* Time and material estimates for a portion of paths, e.g. layer, extruder plan, path.
|
||||
*/
|
||||
class TimeMaterialEstimates
|
||||
{
|
||||
friend class GCodePlanner;
|
||||
friend class ExtruderPlan; // cause there the naive estimates are calculated
|
||||
private:
|
||||
double extrude_time; //!< Time in seconds occupied by extrusion
|
||||
double unretracted_travel_time; //!< Time in seconds occupied by non-retracted travel (non-extrusion)
|
||||
@@ -220,6 +227,7 @@ public:
|
||||
SpaceFillType space_fill_type; //!< The type of space filling of which this path is a part
|
||||
float flow; //!< A type-independent flow configuration (used for wall overlap compensation)
|
||||
bool retract; //!< Whether the path is a move path preceded by a retraction move; whether the path is a retracted move path.
|
||||
bool perform_z_hop; //!< Whether to perform a z_hop in this path, which is assumed to be a travel path.
|
||||
std::vector<Point> points; //!< The points constituting this path.
|
||||
bool done;//!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one.
|
||||
|
||||
@@ -259,6 +267,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class GCodePlanner; // forward declaration so that ExtruderPlan can be a friend
|
||||
class LayerPlanBuffer; // forward declaration so that ExtruderPlan can be a friend
|
||||
|
||||
/*!
|
||||
* An extruder plan contains all planned paths (GCodePath) pertaining to a single extruder train.
|
||||
*
|
||||
@@ -266,27 +277,30 @@ public:
|
||||
*/
|
||||
class ExtruderPlan
|
||||
{
|
||||
public:
|
||||
friend class GCodePlanner; // TODO: GCodePlanner still does a lot which should actually be handled in this class.
|
||||
friend class LayerPlanBuffer; // TODO: LayerPlanBuffer handles paths directly
|
||||
protected:
|
||||
std::vector<GCodePath> paths; //!< The paths planned for this extruder
|
||||
std::list<NozzleTempInsert> inserts; //!< The nozzle temperature command inserts, to be inserted in between paths
|
||||
|
||||
int extruder; //!< The extruder used for this paths in the current plan.
|
||||
double required_temp; //!< The required temperature at the start of this extruder plan.
|
||||
double heated_pre_travel_time; //!< The time at the start of this ExtruderPlan during which the head travels and has a temperature of initial_print_temperature
|
||||
double initial_printing_temperature; //!< The required temperature at the start of this extruder plan.
|
||||
double printing_temperature; //!< The normal temperature for printing this extruder plan. That start and end of this extruder plan may deviate because of the initial and final print temp
|
||||
std::optional<std::list<NozzleTempInsert>::iterator> printing_temperature_command; //!< The command to heat from the printing temperature of this extruder plan to the printing temperature of the next extruder plan (if it has the same extruder).
|
||||
std::optional<double> prev_extruder_standby_temp; //!< The temperature to which to set the previous extruder. Not used if the previous extruder plan was the same extruder.
|
||||
|
||||
TimeMaterialEstimates estimates; //!< Accumulated time and material estimates for all planned paths within this extruder plan.
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Simple contructor.
|
||||
*
|
||||
* \warning Doesn't set the required temperature yet.
|
||||
*
|
||||
* \param extruder The extruder number for which this object is a plan.
|
||||
* \param start_position The position the head is when this extruder plan starts
|
||||
*/
|
||||
ExtruderPlan(int extruder)
|
||||
: extruder(extruder)
|
||||
, required_temp(-1)
|
||||
{
|
||||
}
|
||||
ExtruderPlan(int extruder, Point start_position, int layer_nr, bool is_initial_layer, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config);
|
||||
|
||||
/*!
|
||||
* Add a new Insert, constructed with the given arguments
|
||||
@@ -335,6 +349,97 @@ public:
|
||||
inserts.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Applying speed corrections for minimal layer times and determine the fanSpeed.
|
||||
*
|
||||
* \param force_minimal_layer_time Whether we should apply speed changes and perhaps a head lift in order to meet the minimal layer time
|
||||
*/
|
||||
void processFanSpeedAndMinimalLayerTime(bool force_minimal_layer_time);
|
||||
|
||||
/*!
|
||||
* Set the extrude speed factor. This is used for printing slower than normal.
|
||||
*
|
||||
* Leaves the extrusion speed as is for values of 1.0
|
||||
*
|
||||
* \param speedFactor The factor by which to alter the extrusion move speed
|
||||
*/
|
||||
void setExtrudeSpeedFactor(double speedFactor);
|
||||
|
||||
/*!
|
||||
* Get the extrude speed factor. This is used for printing slower than normal.
|
||||
*
|
||||
* \return The factor by which to alter the extrusion move speed
|
||||
*/
|
||||
double getExtrudeSpeedFactor();
|
||||
|
||||
/*!
|
||||
* Set the travel speed factor. This is used for performing non-extrusion travel moves slower than normal.
|
||||
*
|
||||
* Leaves the extrusion speed as is for values of 1.0
|
||||
*
|
||||
* \param speedFactor The factor by which to alter the non-extrusion move speed
|
||||
*/
|
||||
void setTravelSpeedFactor(double speedFactor);
|
||||
|
||||
/*!
|
||||
* Get the travel speed factor. This is used for travelling slower than normal.
|
||||
*
|
||||
* Limited to at most 1.0
|
||||
*
|
||||
* \return The factor by which to alter the non-extrusion move speed
|
||||
*/
|
||||
double getTravelSpeedFactor();
|
||||
|
||||
/*!
|
||||
* Get the fan speed computed for this extruder plan
|
||||
*
|
||||
* \warning assumes ExtruderPlan::processFanSpeedAndMinimalLayerTime has already been called
|
||||
*
|
||||
* \return The fan speed computed in processFanSpeedAndMinimalLayerTime
|
||||
*/
|
||||
double getFanSpeed();
|
||||
protected:
|
||||
|
||||
Point start_position; //!< The position the print head was at at the start of this extruder plan
|
||||
|
||||
int layer_nr; //!< The layer number at which we are currently printing.
|
||||
bool is_initial_layer; //!< Whether this extruder plan is printed on the very first layer (which might be raft)
|
||||
|
||||
int layer_thickness; //!< The thickness of this layer in Z-direction
|
||||
|
||||
FanSpeedLayerTimeSettings& fan_speed_layer_time_settings; //!< The fan speed and layer time settings used to limit this extruder plan
|
||||
|
||||
const RetractionConfig& retraction_config; //!< The retraction settings for the extruder of this plan
|
||||
|
||||
double extrudeSpeedFactor; //!< The factor by which to alter the extrusion move speed
|
||||
double travelSpeedFactor; //!< The factor by which to alter the non-extrusion move speed
|
||||
|
||||
double extraTime; //!< Extra waiting time at the and of this extruder plan, so that the filament can cool
|
||||
double totalPrintTime; //!< The total naive time estimate for this extruder plan
|
||||
|
||||
double fan_speed; //!< The fan speed to be used during this extruder plan
|
||||
|
||||
/*!
|
||||
* Set the fan speed to be used while printing this extruder plan
|
||||
*
|
||||
* \param fan_speed The speed for the fan
|
||||
*/
|
||||
void setFanSpeed(double fan_speed);
|
||||
|
||||
/*!
|
||||
* Force the minimal layer time to hold by slowing down and lifting the head if required.
|
||||
*
|
||||
*/
|
||||
void forceMinimalLayerTime(double minTime, double minimalSpeed, double travelTime, double extrusionTime);
|
||||
|
||||
/*!
|
||||
* Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates (without accounting for MergeInfillLines)
|
||||
* and store them in each ExtruderPlan and each GCodePath.
|
||||
*
|
||||
* \return the total estimates of this layer
|
||||
*/
|
||||
TimeMaterialEstimates computeNaiveTimeEstimates();
|
||||
};
|
||||
|
||||
class LayerPlanBuffer; // forward declaration to prevent circular dependency
|
||||
@@ -352,10 +457,12 @@ class LayerPlanBuffer; // forward declaration to prevent circular dependency
|
||||
class GCodePlanner : public NoCopy
|
||||
{
|
||||
friend class LayerPlanBuffer;
|
||||
friend class GCodePlannerTest;
|
||||
private:
|
||||
SliceDataStorage& storage; //!< The polygon data obtained from FffPolygonProcessor
|
||||
|
||||
int layer_nr; //!< The layer number of this layer plan
|
||||
int is_initial_layer; //!< Whether this is the first layer (which might be raft)
|
||||
|
||||
int z;
|
||||
|
||||
@@ -365,23 +472,16 @@ private:
|
||||
Point lastPosition;
|
||||
|
||||
std::vector<ExtruderPlan> extruder_plans; //!< should always contain at least one ExtruderPlan
|
||||
|
||||
bool was_inside; //!< Whether the last planned (extrusion) move was inside a layer part
|
||||
bool is_inside; //!< Whether the destination of the next planned travel move is inside a layer part
|
||||
|
||||
int last_extruder_previous_layer; //!< The last id of the extruder with which was printed in the previous layer
|
||||
SettingsBaseVirtual* last_planned_extruder_setting_base; //!< The setting base of the last planned extruder.
|
||||
SliceLayerPart* was_inside; //!< The layer part the last planned (extrusion) move was inside (if any)
|
||||
SliceLayerPart* is_inside; //!< The layer part the destination of the next planned travel move is inside (if any)
|
||||
Polygons comb_boundary_inside; //!< The boundary within which to comb, or to move into when performing a retraction.
|
||||
Comb* comb;
|
||||
|
||||
RetractionConfig* last_retraction_config;
|
||||
|
||||
FanSpeedLayerTimeSettings& fan_speed_layer_time_settings;
|
||||
|
||||
double extrudeSpeedFactor;
|
||||
double travelSpeedFactor;
|
||||
|
||||
double fan_speed;
|
||||
|
||||
double extraTime;
|
||||
double totalPrintTime;
|
||||
std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder;
|
||||
|
||||
private:
|
||||
/*!
|
||||
@@ -395,7 +495,8 @@ private:
|
||||
* \return A path with the given config which is now the last path in GCodePlanner::paths
|
||||
*/
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0, bool spiralize = false);
|
||||
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Force GCodePlanner::getLatestPathWithConfig to return a new path.
|
||||
*
|
||||
@@ -407,17 +508,24 @@ private:
|
||||
* - when changing extruder, the same travel config is used, but its extruder field is changed.
|
||||
*/
|
||||
void forceNewPathStart();
|
||||
public:
|
||||
|
||||
/*!
|
||||
*
|
||||
* \param fan_speed_layer_time_settings_per_extruder The fan speed and layer time settings for each extruder.
|
||||
* \param travel_avoid_other_parts Whether to avoid other layer parts when travaeling through air.
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
* \param last_position The position of the head at the start of this gcode layer
|
||||
* \param combing_mode Whether combing is enabled and full or within infill only.
|
||||
*/
|
||||
GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_height, Point last_position, int current_extruder, bool is_inside_mesh, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
GCodePlanner(SliceDataStorage& storage, int layer_nr, int z, int layer_height, Point last_position, int current_extruder, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
~GCodePlanner();
|
||||
|
||||
void overrideFanSpeeds(double speed);
|
||||
/*!
|
||||
* Get the settings base of the last extruder planned.
|
||||
* \return the settings base of the last extruder planned.
|
||||
*/
|
||||
SettingsBaseVirtual* getLastPlannedExtruderTrainSettings();
|
||||
private:
|
||||
/*!
|
||||
* Compute the boundary within which to comb, or to move into when performing a retraction.
|
||||
@@ -445,19 +553,11 @@ public:
|
||||
return was_inside;
|
||||
}
|
||||
/*!
|
||||
* send a polygon through the command socket from the previous point to the given point
|
||||
* send a line segment through the command socket from the previous point to the given point \p to
|
||||
*/
|
||||
void sendPolygon(PrintFeatureType print_feature_type, Point from, Point to, int line_width)
|
||||
void sendLineTo(PrintFeatureType print_feature_type, Point to, int line_width)
|
||||
{
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
// we should send this travel as a non-retraction move
|
||||
cura::Polygons pathPoly;
|
||||
PolygonRef path = pathPoly.newPoly();
|
||||
path.add(from);
|
||||
path.add(to);
|
||||
CommandSocket::getInstance()->sendPolygons(print_feature_type, layer_nr, pathPoly, line_width);
|
||||
}
|
||||
CommandSocket::sendLineTo(print_feature_type, to, line_width);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -465,8 +565,9 @@ public:
|
||||
*
|
||||
* Features like infill, walls, skin etc. are considered inside.
|
||||
* Features like prime tower and support are considered outside.
|
||||
* \param inside_part The part in which the newly planned position is inside, or nullptr if not inside anything
|
||||
*/
|
||||
void setIsInside(bool going_to_comb);
|
||||
void setIsInside(SliceLayerPart* inside_part);
|
||||
|
||||
bool setExtruder(int extruder);
|
||||
|
||||
@@ -478,28 +579,7 @@ public:
|
||||
return extruder_plans.back().extruder;
|
||||
}
|
||||
|
||||
void setExtrudeSpeedFactor(double speedFactor)
|
||||
{
|
||||
this->extrudeSpeedFactor = speedFactor;
|
||||
}
|
||||
double getExtrudeSpeedFactor()
|
||||
{
|
||||
return this->extrudeSpeedFactor;
|
||||
}
|
||||
void setTravelSpeedFactor(double speedFactor)
|
||||
{
|
||||
if (speedFactor < 1) speedFactor = 1.0;
|
||||
this->travelSpeedFactor = speedFactor;
|
||||
}
|
||||
double getTravelSpeedFactor()
|
||||
{
|
||||
return this->travelSpeedFactor;
|
||||
}
|
||||
|
||||
void setFanSpeed(double _fan_speed)
|
||||
{
|
||||
fan_speed = _fan_speed;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Add a travel path to a certain point, retract if needed and when avoiding boundary crossings:
|
||||
@@ -507,7 +587,7 @@ public:
|
||||
*
|
||||
* \param p The point to travel to
|
||||
*/
|
||||
void addTravel(Point p);
|
||||
GCodePath& addTravel(Point p);
|
||||
|
||||
/*!
|
||||
* Add a travel path to a certain point and retract if needed.
|
||||
@@ -517,7 +597,7 @@ public:
|
||||
* \param p The point to travel to
|
||||
* \param path (optional) The travel path to which to add the point \p p
|
||||
*/
|
||||
void addTravel_simple(Point p, GCodePath* path = nullptr);
|
||||
GCodePath& addTravel_simple(Point p, GCodePath* path = nullptr);
|
||||
|
||||
/*!
|
||||
* Add an extrusion move to a certain point, optionally with a different flow than the one in the \p config.
|
||||
@@ -536,9 +616,10 @@ public:
|
||||
* \param startIdx The index of the starting vertex of the \p polygon
|
||||
* \param config The config with which to print the polygon lines
|
||||
* \param wall_overlap_computation The wall overlap compensation calculator for each given segment (optionally nullptr)
|
||||
* \param wall_0_wipe_dist The distance to travel along the polygon after it has been laid down, in order to wipe the start and end of the wall together
|
||||
* \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over this polygon
|
||||
*/
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, bool spiralize = false);
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, coord_t wall_0_wipe_dist = 0, bool spiralize = false);
|
||||
|
||||
/*!
|
||||
* Add polygons to the gcode with optimized order.
|
||||
@@ -553,9 +634,10 @@ public:
|
||||
* \param config The config with which to print the polygon lines
|
||||
* \param wall_overlap_computation The wall overlap compensation calculator for each given segment (optionally nullptr)
|
||||
* \param z_seam_type The seam type / poly start optimizer
|
||||
* \param wall_0_wipe_dist The distance to travel along each polygon after it has been laid down, in order to wipe the start and end of the wall together
|
||||
* \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over each polygon printed
|
||||
*/
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST, bool spiralize = false);
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST, coord_t wall_0_wipe_dist = 0, bool spiralize = false);
|
||||
|
||||
/*!
|
||||
* Add lines to the gcode with optimized order.
|
||||
@@ -567,15 +649,15 @@ public:
|
||||
void addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist = 0);
|
||||
|
||||
/*!
|
||||
* Compute naive time estimates (without accountign for slow down at corners etc.) and naive material estimates (without accounting for MergeInfillLines)
|
||||
* Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates (without accounting for MergeInfillLines)
|
||||
* and store them in each ExtruderPlan and each GCodePath.
|
||||
*
|
||||
* \warning This function recomputes values which are already computed if you've called processFanSpeedAndMinimalLayerTime
|
||||
*
|
||||
* \return the total estimates of this layer
|
||||
*/
|
||||
TimeMaterialEstimates computeNaiveTimeEstimates();
|
||||
|
||||
void forceMinimalLayerTime(double minTime, double minimalSpeed, double travelTime, double extrusionTime);
|
||||
|
||||
/*!
|
||||
* Write the planned paths to gcode
|
||||
*
|
||||
@@ -584,8 +666,9 @@ public:
|
||||
void writeGCode(GCodeExport& gcode);
|
||||
|
||||
/*!
|
||||
* Complete all GcodePathConfig s by
|
||||
* - altering speed to conform to speed_layer_0
|
||||
* Complete all GcodePathConfigs by
|
||||
* - altering speeds to conform to speed_print_layer_0 and
|
||||
* speed_travel_layer_0
|
||||
* - setting the layer_height (and thereby computing the extrusionMM3perMM)
|
||||
*/
|
||||
void completeConfigs();
|
||||
@@ -622,22 +705,6 @@ public:
|
||||
*/
|
||||
bool writePathWithCoasting(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume);
|
||||
|
||||
/*!
|
||||
* Write a retraction: either an extruder switch retraction or a normal retraction based on the last extrusion paths retraction config.
|
||||
* \param gcode The gcode to write the planned paths to
|
||||
* \param extruder_plan_idx The index of the current extruder plan
|
||||
* \param path_idx_travel_after Index in GCodePlanner::paths to the travel move before which to do the retraction
|
||||
*/
|
||||
void writeRetraction(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx_travel_after);
|
||||
|
||||
/*!
|
||||
* Write a retraction: either an extruder switch retraction or a normal retraction based on the given retraction config.
|
||||
* \param gcode The gcode to write the planned paths to
|
||||
* \param extruder_switch_retract Whether to write an extruder switch retract
|
||||
* \param retraction_config The config used.
|
||||
*/
|
||||
void writeRetraction(GCodeExport& gcode, bool extruder_switch_retract, RetractionConfig* retraction_config);
|
||||
|
||||
/*!
|
||||
* Applying speed corrections for minimal layer times and determine the fanSpeed.
|
||||
*/
|
||||
@@ -648,8 +715,9 @@ public:
|
||||
* This is supposed to be called when the nozzle is around the boundary of a layer part, not when the nozzle is in the middle of support, or in the middle of the air.
|
||||
*
|
||||
* \param distance The distance to the comb boundary after we moved inside it.
|
||||
* \param part_outline The part in which we last resided
|
||||
*/
|
||||
void moveInsideCombBoundary(int distance);
|
||||
void moveInsideCombBoundary(int distance, const SliceLayerPart& part);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+131
-39
@@ -6,11 +6,21 @@
|
||||
|
||||
namespace cura {
|
||||
|
||||
int Infill::computeScanSegmentIdx(int x, int line_width)
|
||||
{
|
||||
if (x < 0)
|
||||
{
|
||||
return (x + 1) / line_width - 1;
|
||||
// - 1 because -1 belongs to scansegment -1
|
||||
// + 1 because -line_width belongs to scansegment -1
|
||||
}
|
||||
return x / line_width;
|
||||
}
|
||||
|
||||
void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
{
|
||||
if (in_outline.size() == 0) return;
|
||||
if (line_distance == 0) return;
|
||||
const Polygons* outline = &in_outline;
|
||||
Polygons outline_offsetted;
|
||||
switch(pattern)
|
||||
{
|
||||
@@ -18,15 +28,22 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
generateGridInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::LINES:
|
||||
generateLineInfill(result_lines, line_distance, fill_angle);
|
||||
generateLineInfill(result_lines, line_distance, fill_angle, 0);
|
||||
break;
|
||||
case EFillMethod::CUBIC:
|
||||
generateCubicInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::TETRAHEDRAL:
|
||||
generateTetrahedralInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::TRIANGLES:
|
||||
generateTriangleInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC:
|
||||
outline_offsetted = in_outline.offset(outline_offset - infill_line_width / 2); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
outline = &outline_offsetted;
|
||||
generateConcentricInfill(*outline, result_polygons, line_distance);
|
||||
generateConcentricInfill(result_polygons, line_distance);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC_3D:
|
||||
generateConcentric3DInfill(result_polygons);
|
||||
break;
|
||||
case EFillMethod::ZIG_ZAG:
|
||||
generateZigZagInfill(result_lines, line_distance, fill_angle, connected_zigzags, use_endpieces);
|
||||
@@ -37,30 +54,89 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
}
|
||||
}
|
||||
|
||||
void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int inset_value)
|
||||
void Infill::generateConcentricInfill(Polygons& result, int inset_value)
|
||||
{
|
||||
while(outline.size() > 0)
|
||||
Polygons first_concentric_wall = in_outline.offset(outline_offset - line_distance + infill_line_width / 2); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
|
||||
result.add(first_concentric_wall);
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
result.add(outline);
|
||||
outline = outline.offset(-inset_value);
|
||||
}
|
||||
const Polygons inner = first_concentric_wall.offset(infill_line_width / 2 + perimeter_gaps_extra_offset);
|
||||
const Polygons gaps_here = in_outline.difference(inner);
|
||||
perimeter_gaps->add(gaps_here);
|
||||
}
|
||||
generateConcentricInfill(first_concentric_wall, result, inset_value);
|
||||
}
|
||||
|
||||
void Infill::generateConcentricInfill(Polygons& first_concentric_wall, Polygons& result, int inset_value)
|
||||
{
|
||||
Polygons* prev_inset = &first_concentric_wall;
|
||||
Polygons next_inset;
|
||||
while (prev_inset->size() > 0)
|
||||
{
|
||||
next_inset = prev_inset->offset(-inset_value);
|
||||
result.add(next_inset);
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
const Polygons outer = prev_inset->offset(-infill_line_width / 2 - perimeter_gaps_extra_offset);
|
||||
const Polygons inner = next_inset.offset(infill_line_width / 2 + perimeter_gaps_extra_offset);
|
||||
const Polygons gaps_here = outer.difference(inner);
|
||||
perimeter_gaps->add(gaps_here);
|
||||
}
|
||||
prev_inset = &next_inset;
|
||||
}
|
||||
}
|
||||
|
||||
void Infill::generateConcentric3DInfill(Polygons& result)
|
||||
{
|
||||
int period = line_distance * 2;
|
||||
int shift = int64_t(one_over_sqrt_2 * z) % period;
|
||||
shift = std::min(shift, period - shift); // symmetry due to the fact that we are applying the shift in both directions
|
||||
shift = std::min(shift, period / 2 - infill_line_width / 2); // don't put lines too close to each other
|
||||
shift = std::max(shift, infill_line_width / 2); // don't put lines too close to each other
|
||||
Polygons first_wall;
|
||||
// in contrast to concentric infill we dont do "- infill_line_width / 2" cause this is already handled by the max two lines above
|
||||
first_wall = in_outline.offset(outline_offset - shift);
|
||||
generateConcentricInfill(first_wall, result, period);
|
||||
first_wall = in_outline.offset(outline_offset - period + shift);
|
||||
generateConcentricInfill(first_wall, result, period);
|
||||
}
|
||||
|
||||
void Infill::generateGridInfill(Polygons& result)
|
||||
{
|
||||
generateLineInfill(result, line_distance, fill_angle);
|
||||
generateLineInfill(result, line_distance, fill_angle + 90);
|
||||
generateLineInfill(result, line_distance, fill_angle, 0);
|
||||
generateLineInfill(result, line_distance, fill_angle + 90, 0);
|
||||
}
|
||||
|
||||
void Infill::generateCubicInfill(Polygons& result)
|
||||
{
|
||||
int64_t shift = one_over_sqrt_2 * z;
|
||||
generateLineInfill(result, line_distance, fill_angle, shift);
|
||||
generateLineInfill(result, line_distance, fill_angle + 120, shift);
|
||||
generateLineInfill(result, line_distance, fill_angle + 240, shift);
|
||||
}
|
||||
|
||||
void Infill::generateTetrahedralInfill(Polygons& result)
|
||||
{
|
||||
int period = line_distance * 2;
|
||||
int shift = int64_t(one_over_sqrt_2 * z) % period;
|
||||
shift = std::min(shift, period - shift); // symmetry due to the fact that we are applying the shift in both directions
|
||||
shift = std::min(shift, period / 2 - infill_line_width / 2); // don't put lines too close to each other
|
||||
shift = std::max(shift, infill_line_width / 2); // don't put lines too close to each other
|
||||
generateLineInfill(result, period, fill_angle, shift);
|
||||
generateLineInfill(result, period, fill_angle, -shift);
|
||||
generateLineInfill(result, period, fill_angle + 90, shift);
|
||||
generateLineInfill(result, period, fill_angle + 90, -shift);
|
||||
}
|
||||
|
||||
void Infill::generateTriangleInfill(Polygons& result)
|
||||
{
|
||||
generateLineInfill(result, line_distance, fill_angle);
|
||||
generateLineInfill(result, line_distance, fill_angle + 60);
|
||||
generateLineInfill(result, line_distance, fill_angle + 120);
|
||||
generateLineInfill(result, line_distance, fill_angle, 0);
|
||||
generateLineInfill(result, line_distance, fill_angle + 60, 0);
|
||||
generateLineInfill(result, line_distance, fill_angle + 120, 0);
|
||||
}
|
||||
|
||||
void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list)
|
||||
void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list, int64_t shift)
|
||||
{
|
||||
auto addLine = [&](Point from, Point to)
|
||||
{
|
||||
@@ -84,7 +160,7 @@ void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix,
|
||||
};
|
||||
|
||||
int scanline_idx = 0;
|
||||
for(int64_t x = scanline_min_idx * line_distance; x < boundary.max.X; x += line_distance)
|
||||
for(int64_t x = scanline_min_idx * line_distance + shift; x < boundary.max.X; x += line_distance)
|
||||
{
|
||||
std::vector<int64_t>& crossings = cut_list[scanline_idx];
|
||||
qsort(crossings.data(), crossings.size(), sizeof(int64_t), compare_int64_t);
|
||||
@@ -100,19 +176,17 @@ void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix,
|
||||
}
|
||||
}
|
||||
|
||||
void Infill::generateLineInfill(Polygons& result, int line_distance, const double& fill_angle)
|
||||
void Infill::generateLineInfill(Polygons& result, int line_distance, const double& fill_angle, int64_t shift)
|
||||
{
|
||||
PointMatrix rotation_matrix(fill_angle);
|
||||
NoZigZagConnectorProcessor lines_processor(rotation_matrix, result);
|
||||
bool connected_zigzags = false;
|
||||
bool safe_outline_offset = false;
|
||||
generateLinearBasedInfill(outline_offset, safe_outline_offset, result, line_distance, rotation_matrix, lines_processor, connected_zigzags);
|
||||
generateLinearBasedInfill(outline_offset, result, line_distance, rotation_matrix, lines_processor, connected_zigzags, shift);
|
||||
}
|
||||
|
||||
|
||||
void Infill::generateZigZagInfill(Polygons& result, const int line_distance, const double& fill_angle, const bool connected_zigzags, const bool use_endpieces)
|
||||
{
|
||||
bool safe_outline_offset = true;
|
||||
|
||||
PointMatrix rotation_matrix(fill_angle);
|
||||
if (use_endpieces)
|
||||
@@ -120,18 +194,18 @@ void Infill::generateZigZagInfill(Polygons& result, const int line_distance, con
|
||||
if (connected_zigzags)
|
||||
{
|
||||
ZigzagConnectorProcessorConnectedEndPieces zigzag_processor(rotation_matrix, result);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ZigzagConnectorProcessorDisconnectedEndPieces zigzag_processor(rotation_matrix, result);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ZigzagConnectorProcessorNoEndPieces zigzag_processor(rotation_matrix, result);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +232,7 @@ void Infill::generateZigZagInfill(Polygons& result, const int line_distance, con
|
||||
* Edit: the term scansegment is wrong, since I call a boundary segment leaving from an even scanline to the left as belonging to an even scansegment,
|
||||
* while I also call a boundary segment leaving from an even scanline toward the right as belonging to an even scansegment.
|
||||
*/
|
||||
void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags)
|
||||
void Infill::generateLinearBasedInfill(const int outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags, int64_t extra_shift)
|
||||
{
|
||||
if (line_distance == 0)
|
||||
{
|
||||
@@ -168,19 +242,25 @@ void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outli
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int shift = extra_shift + this->shift;
|
||||
|
||||
Polygons outline;
|
||||
if (outline_offset != 0)
|
||||
{
|
||||
outline = in_outline.offset(outline_offset);
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
perimeter_gaps->add(in_outline.difference(outline.offset(infill_line_width / 2 + perimeter_gaps_extra_offset)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outline = in_outline;
|
||||
}
|
||||
|
||||
outline = outline.offset(infill_overlap);
|
||||
|
||||
|
||||
outline = outline.offset(infill_overlap);
|
||||
|
||||
if (outline.size() == 0)
|
||||
{
|
||||
return;
|
||||
@@ -188,10 +268,19 @@ void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outli
|
||||
|
||||
outline.applyMatrix(rotation_matrix);
|
||||
|
||||
if (shift < 0)
|
||||
{
|
||||
shift = line_distance - (-shift) % line_distance;
|
||||
}
|
||||
else
|
||||
{
|
||||
shift = shift % line_distance;
|
||||
}
|
||||
|
||||
AABB boundary(outline);
|
||||
|
||||
int scanline_min_idx = boundary.min.X / line_distance;
|
||||
int line_count = (boundary.max.X + (line_distance - 1)) / line_distance - scanline_min_idx;
|
||||
int scanline_min_idx = computeScanSegmentIdx(boundary.min.X - shift, line_distance);
|
||||
int line_count = computeScanSegmentIdx(boundary.max.X - shift, line_distance) + 1 - scanline_min_idx;
|
||||
|
||||
std::vector<std::vector<int64_t> > cut_list; // mapping from scanline to all intersections with polygon segments
|
||||
|
||||
@@ -217,26 +306,29 @@ void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outli
|
||||
continue;
|
||||
}
|
||||
|
||||
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -line_distance)) / line_distance; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
|
||||
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -line_distance)) / line_distance; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
|
||||
int scanline_idx0;
|
||||
int scanline_idx1;
|
||||
// this way of handling the indices takes care of the case where a boundary line segment ends exactly on a scanline:
|
||||
// in case the next segment moves back from that scanline either 2 or 0 scanline-boundary intersections are created
|
||||
// otherwise only 1 will be created, counting as an actual intersection
|
||||
int direction = 1;
|
||||
if (p0.X > p1.X)
|
||||
if (p0.X < p1.X)
|
||||
{
|
||||
direction = -1;
|
||||
scanline_idx1 += 1; // only consider the scanlines in between the scansegments
|
||||
scanline_idx0 = computeScanSegmentIdx(p0.X - shift, line_distance) + 1; // + 1 cause we don't cross the scanline of the first scan segment
|
||||
scanline_idx1 = computeScanSegmentIdx(p1.X - shift, line_distance); // -1 cause the vertex point is handled in the next segment (or not in the case which looks like >)
|
||||
}
|
||||
else
|
||||
{
|
||||
scanline_idx0 += 1; // only consider the scanlines in between the scansegments
|
||||
direction = -1;
|
||||
scanline_idx0 = computeScanSegmentIdx(p0.X - shift, line_distance); // -1 cause the vertex point is handled in the previous segment (or not in the case which looks like >)
|
||||
scanline_idx1 = computeScanSegmentIdx(p1.X - shift, line_distance) + 1; // + 1 cause we don't cross the scanline of the first scan segment
|
||||
}
|
||||
|
||||
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1 + direction; scanline_idx += direction)
|
||||
{
|
||||
int x = scanline_idx * line_distance;
|
||||
int x = scanline_idx * line_distance + shift;
|
||||
int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X);
|
||||
assert(scanline_idx - scanline_min_idx >= 0 && scanline_idx - scanline_min_idx < int(cut_list.size()) && "reading infill cutlist index out of bounds!");
|
||||
cut_list[scanline_idx - scanline_min_idx].push_back(y);
|
||||
Point scanline_linesegment_intersection(x, y);
|
||||
zigzag_connector_processor.registerScanlineSegmentIntersection(scanline_linesegment_intersection, scanline_idx % 2 == 0);
|
||||
@@ -256,7 +348,7 @@ void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outli
|
||||
return; // don't add connection if boundary already contains whole outline!
|
||||
}
|
||||
|
||||
addLineInfill(result, rotation_matrix, scanline_min_idx, line_distance, boundary, cut_list);
|
||||
addLineInfill(result, rotation_matrix, scanline_min_idx, line_distance, boundary, cut_list, shift);
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+78
-8
@@ -20,6 +20,8 @@ namespace cura
|
||||
|
||||
class Infill
|
||||
{
|
||||
static constexpr int perimeter_gaps_extra_offset = 15; // extra offset so that the perimeter gaps aren't created everywhere due to rounding errors
|
||||
|
||||
EFillMethod pattern; //!< the space filling pattern of the infill to generate
|
||||
const Polygons& in_outline; //!< a reference polygon for getting the actual area within which to generate infill (see outline_offset)
|
||||
int outline_offset; //!< Offset from Infill::in_outline to get the actual area within which to generate infill
|
||||
@@ -27,11 +29,35 @@ class Infill
|
||||
int line_distance; //!< The distance between two infill lines / polygons
|
||||
int infill_overlap; //!< the distance by which to overlap with the actual area within which to generate infill
|
||||
double fill_angle; //!< for linear infill types: the angle of the infill lines (or the angle of the grid)
|
||||
int64_t z; //!< height of the layer for which we generate infill
|
||||
int64_t shift; //!< shift of the scanlines in the direction perpendicular to the fill_angle
|
||||
Polygons* perimeter_gaps; //!< (optional output) The areas in between consecutive insets when Concentric infill is used.
|
||||
bool connected_zigzags; //!< (ZigZag) Whether endpieces of zigzag infill should be connected to the nearest infill line on both sides of the zigzag connector
|
||||
bool use_endpieces; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself
|
||||
|
||||
static constexpr double one_over_sqrt_2 = 0.7071067811865475244008443621048490392848359376884740; //!< 1.0 / sqrt(2.0)
|
||||
public:
|
||||
Infill(EFillMethod pattern, const Polygons& in_outline, int outline_offset, int infill_line_width, int line_distance, int infill_overlap, double fill_angle, bool connected_zigzags = false, bool use_endpieces = false)
|
||||
/*!
|
||||
* \warning If \p perimeter_gaps is given, then the difference between the \p in_outline
|
||||
* and the polygons which result from offsetting it by the \p outline_offset
|
||||
* and then expanding it again by half the \p infill_line_width
|
||||
* is added to the \p perimeter_gaps
|
||||
*
|
||||
* \param[out] perimeter_gaps (optional output) The areas in between consecutive insets when Concentric infill is used.
|
||||
*/
|
||||
Infill(EFillMethod pattern
|
||||
, const Polygons& in_outline
|
||||
, int outline_offset
|
||||
, int infill_line_width
|
||||
, int line_distance
|
||||
, int infill_overlap
|
||||
, double fill_angle
|
||||
, int64_t z
|
||||
, int64_t shift
|
||||
, Polygons* perimeter_gaps = nullptr
|
||||
, bool connected_zigzags = false
|
||||
, bool use_endpieces = false
|
||||
)
|
||||
: pattern(pattern)
|
||||
, in_outline(in_outline)
|
||||
, outline_offset(outline_offset)
|
||||
@@ -39,6 +65,9 @@ public:
|
||||
, line_distance(line_distance)
|
||||
, infill_overlap(infill_overlap)
|
||||
, fill_angle(fill_angle)
|
||||
, z(z)
|
||||
, shift(shift)
|
||||
, perimeter_gaps(perimeter_gaps)
|
||||
, connected_zigzags(connected_zigzags)
|
||||
, use_endpieces(use_endpieces)
|
||||
{
|
||||
@@ -52,14 +81,41 @@ public:
|
||||
void generate(Polygons& result_polygons, Polygons& result_lines);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Function which returns the scanline_idx for a given x coordinate
|
||||
*
|
||||
* For negative \p x this is different from simple division.
|
||||
*
|
||||
* \warning \p line_distance is assumed to be positive
|
||||
*
|
||||
* \param x the point to get the scansegment index for
|
||||
* \param line_distance the width of the scan segments
|
||||
*/
|
||||
static inline int computeScanSegmentIdx(int x, int line_distance);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill
|
||||
* \param outline The actual outline of the area within which to generate infill
|
||||
* Generate sparse concentric infill
|
||||
*
|
||||
* Also adds \ref Inifll::perimeter_gaps between \ref Infill::in_outline and the first wall
|
||||
*
|
||||
* \param result (output) The resulting polygons
|
||||
* \param inset_value The offset between each consecutive two polygons
|
||||
*/
|
||||
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value);
|
||||
void generateConcentricInfill(Polygons& result, int inset_value);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill starting from a specific outer wall
|
||||
* \param first_wall The outer wall from which to start
|
||||
* \param result (output) The resulting polygons
|
||||
* \param inset_value The offset between each consecutive two polygons
|
||||
*/
|
||||
void generateConcentricInfill(Polygons& first_wall, Polygons& result, int inset_value);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill
|
||||
* \param result (output) The resulting polygons
|
||||
*/
|
||||
void generateConcentric3DInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Generate a rectangular grid of infill lines
|
||||
@@ -67,6 +123,18 @@ private:
|
||||
*/
|
||||
void generateGridInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Generate a shifting triangular grid of infill lines, which combine with consecutive layers into a cubic pattern
|
||||
* \param result (output) The resulting lines
|
||||
*/
|
||||
void generateCubicInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Generate a double shifting square grid of infill lines, which combine with consecutive layers into a tetrahedral pattern
|
||||
* \param result (output) The resulting lines
|
||||
*/
|
||||
void generateTetrahedralInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Generate a triangular grid of infill lines
|
||||
* \param result (output) The resulting lines
|
||||
@@ -81,8 +149,9 @@ private:
|
||||
* \param line_distance The distance between two lines which are in the same direction
|
||||
* \param boundary The axis aligned boundary box within which the polygon is
|
||||
* \param cut_list A mapping of each scanline to all y-coordinates (in the space transformed by rotation_matrix) where the polygons are crossing the scanline
|
||||
* \param total_shift total shift of the scanlines in the direction perpendicular to the fill_angle.
|
||||
*/
|
||||
void addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list);
|
||||
void addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list, int64_t total_shift);
|
||||
|
||||
/*!
|
||||
* generate lines within the area of \p in_outline, at regular intervals of \p line_distance
|
||||
@@ -93,8 +162,9 @@ private:
|
||||
* \param result (output) The resulting lines
|
||||
* \param line_distance The distance between two lines which are in the same direction
|
||||
* \param fill_angle The angle of the generated lines
|
||||
* \param extra_shift extra shift of the scanlines in the direction perpendicular to the fill_angle
|
||||
*/
|
||||
void generateLineInfill(Polygons& result, int line_distance, const double& fill_angle);
|
||||
void generateLineInfill(Polygons& result, int line_distance, const double& fill_angle, int64_t extra_shift);
|
||||
|
||||
/*!
|
||||
* Function for creating linear based infill types (Lines, ZigZag).
|
||||
@@ -105,14 +175,14 @@ private:
|
||||
* It is called only from Infill::generateLineinfill and Infill::generateZigZagInfill.
|
||||
*
|
||||
* \param outline_offset An offset from the reference polygon (Infill::in_outline) to get the actual outline within which to generate infill
|
||||
* \param safe_outline_offset Whether to consider removing overlapping wall parts (not so for normal line infill)
|
||||
* \param result (output) The resulting lines
|
||||
* \param line_distance The distance between two lines which are in the same direction
|
||||
* \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill
|
||||
* \param zigzag_connector_processor The processor used to generate zigzag connectors
|
||||
* \param connected_zigzags Whether to connect the endpiece zigzag segments on both sides to the same infill line
|
||||
* \param extra_shift extra shift of the scanlines in the direction perpendicular to the fill_angle
|
||||
*/
|
||||
void generateLinearBasedInfill(const int outline_offset, bool safe_outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags);
|
||||
void generateLinearBasedInfill(const int outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags, int64_t extra_shift);
|
||||
|
||||
/*!
|
||||
*
|
||||
|
||||
@@ -46,7 +46,6 @@ void createLayerParts(SliceMeshStorage& mesh, Slicer* slicer, bool union_layers,
|
||||
{
|
||||
for(unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
mesh.layers.push_back(SliceLayer());
|
||||
mesh.layers[layer_nr].sliceZ = slicer->layers[layer_nr].z;
|
||||
mesh.layers[layer_nr].printZ = slicer->layers[layer_nr].z;
|
||||
createLayerWithParts(mesh.layers[layer_nr], &slicer->layers[layer_nr], union_layers, union_all_remove_holes);
|
||||
|
||||
+84
-72
@@ -25,31 +25,32 @@ namespace cura
|
||||
|
||||
void print_usage()
|
||||
{
|
||||
cura::logError("\n");
|
||||
cura::logError("usage:\n");
|
||||
cura::logError("CuraEngine help\n");
|
||||
cura::logError("\tShow this help message\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("CuraEngine connect <host>[:<port>] [-j <settings.def.json>]\n");
|
||||
cura::logError(" --connect <host>[:<port>]\n\tConnect to <host> via a command socket, \n\tinstead of passing information via the command line\n");
|
||||
cura::logError(" -j<settings.def.json>\n\tLoad settings.json file to register all settings and their defaults\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("CuraEngine slice [-v] [-p] [-j <settings.json>] [-s <settingkey>=<value>] [-g] [-e<extruder_nr>] [-o <output.gcode>] [-l <model.stl>] [--next]\n");
|
||||
cura::logError(" -v\n\tIncrease the verbose level (show log messages).\n");
|
||||
cura::logError(" -p\n\tLog progress information.\n");
|
||||
cura::logError(" -j\n\tLoad settings.def.json file to register all settings and their defaults.\n");
|
||||
cura::logError(" -s <setting>=<value>\n\tSet a setting to a value for the last supplied object, \n\textruder train, or general settings.\n");
|
||||
cura::logError(" -l <model_file>\n\tLoad an STL model. \n");
|
||||
cura::logError(" -g\n\tSwitch setting focus to the current mesh group only.\n\tUsed for one-at-a-time printing.\n");
|
||||
cura::logError(" -e<extruder_nr>\n\tSwitch setting focus to the extruder train with the given number.\n");
|
||||
cura::logError(" --next\n\tGenerate gcode for the previously supplied mesh group and append that to \n\tthe gcode of further models for one-at-a-time printing.\n");
|
||||
cura::logError(" -o <output_file>\n\tSpecify a file to which to write the generated gcode.\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("The settings are appended to the last supplied object:\n");
|
||||
cura::logError("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e0 [extruder train 0 settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object settings] \n\t--next [next group settings]\n\t... etc.\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("In order to load machine definitions from custom locations, you need to create the environment variable CURA_ENGINE_SEARCH_PATH, which should contain all search paths delimited by a (semi-)colon.\n");
|
||||
cura::logError("\n");
|
||||
logAlways("\n");
|
||||
logAlways("usage:\n");
|
||||
logAlways("CuraEngine help\n");
|
||||
logAlways("\tShow this help message\n");
|
||||
logAlways("\n");
|
||||
logAlways("CuraEngine connect <host>[:<port>] [-j <settings.def.json>]\n");
|
||||
logAlways(" --connect <host>[:<port>]\n\tConnect to <host> via a command socket, \n\tinstead of passing information via the command line\n");
|
||||
logAlways(" -j<settings.def.json>\n\tLoad settings.json file to register all settings and their defaults\n");
|
||||
logAlways(" -v\n\tIncrease the verbose level (show log messages).\n");
|
||||
logAlways("\n");
|
||||
logAlways("CuraEngine slice [-v] [-p] [-j <settings.json>] [-s <settingkey>=<value>] [-g] [-e<extruder_nr>] [-o <output.gcode>] [-l <model.stl>] [--next]\n");
|
||||
logAlways(" -v\n\tIncrease the verbose level (show log messages).\n");
|
||||
logAlways(" -p\n\tLog progress information.\n");
|
||||
logAlways(" -j\n\tLoad settings.def.json file to register all settings and their defaults.\n");
|
||||
logAlways(" -s <setting>=<value>\n\tSet a setting to a value for the last supplied object, \n\textruder train, or general settings.\n");
|
||||
logAlways(" -l <model_file>\n\tLoad an STL model. \n");
|
||||
logAlways(" -g\n\tSwitch setting focus to the current mesh group only.\n\tUsed for one-at-a-time printing.\n");
|
||||
logAlways(" -e<extruder_nr>\n\tSwitch setting focus to the extruder train with the given number.\n");
|
||||
logAlways(" --next\n\tGenerate gcode for the previously supplied mesh group and append that to \n\tthe gcode of further models for one-at-a-time printing.\n");
|
||||
logAlways(" -o <output_file>\n\tSpecify a file to which to write the generated gcode.\n");
|
||||
logAlways("\n");
|
||||
logAlways("The settings are appended to the last supplied object:\n");
|
||||
logAlways("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e0 [extruder train 0 settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object settings] \n\t--next [next group settings]\n\t... etc.\n");
|
||||
logAlways("\n");
|
||||
logAlways("In order to load machine definitions from custom locations, you need to create the environment variable CURA_ENGINE_SEARCH_PATH, which should contain all search paths delimited by a (semi-)colon.\n");
|
||||
logAlways("\n");
|
||||
}
|
||||
|
||||
//Signal handler for a "floating point exception", which can also be integer division by zero errors.
|
||||
@@ -70,10 +71,10 @@ void print_call(int argc, char **argv)
|
||||
|
||||
void connect(int argc, char **argv)
|
||||
{
|
||||
CommandSocket::instantiate();
|
||||
std::string ip;
|
||||
int port = 49674;
|
||||
|
||||
|
||||
// parse ip port
|
||||
std::string ip_port(argv[2]);
|
||||
if (ip_port.find(':') != std::string::npos)
|
||||
{
|
||||
@@ -97,7 +98,8 @@ void connect(int argc, char **argv)
|
||||
argn++;
|
||||
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn], FffProcessor::getInstance()))
|
||||
{
|
||||
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
|
||||
cura::logError("Failed to load json file: %s\n", argv[argn]);
|
||||
std::exit(1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -109,7 +111,8 @@ void connect(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CommandSocket::instantiate();
|
||||
CommandSocket::getInstance()->connect(ip, port);
|
||||
}
|
||||
|
||||
@@ -123,7 +126,7 @@ void slice(int argc, char **argv)
|
||||
|
||||
int extruder_train_nr = 0;
|
||||
|
||||
SettingsBase* last_extruder_train = meshgroup->createExtruderTrain(0);
|
||||
SettingsBase* last_extruder_train = nullptr;
|
||||
// extruder defaults cannot be loaded yet cause no json has been parsed
|
||||
SettingsBase* last_settings_object = FffProcessor::getInstance();
|
||||
for(int argn = 2; argn < argc; argn++)
|
||||
@@ -138,14 +141,15 @@ void slice(int argc, char **argv)
|
||||
try {
|
||||
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
|
||||
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
|
||||
meshgroup->finalize();
|
||||
log("Loaded from disk in %5.3fs\n", FffProcessor::getInstance()->time_keeper.restart());
|
||||
|
||||
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_nr, train);
|
||||
meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
}
|
||||
|
||||
meshgroup->finalize();
|
||||
|
||||
//start slicing
|
||||
FffProcessor::getInstance()->processMeshGroup(meshgroup);
|
||||
|
||||
@@ -155,7 +159,6 @@ void slice(int argc, char **argv)
|
||||
meshgroup = new MeshGroup(FffProcessor::getInstance());
|
||||
last_extruder_train = meshgroup->createExtruderTrain(0);
|
||||
last_settings_object = meshgroup;
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(0, last_extruder_train);
|
||||
|
||||
}catch(...){
|
||||
cura::logError("Unknown exception\n");
|
||||
@@ -179,7 +182,8 @@ void slice(int argc, char **argv)
|
||||
argn++;
|
||||
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn], last_settings_object))
|
||||
{
|
||||
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
|
||||
cura::logError("Failed to load json file: %s\n", argv[argn]);
|
||||
std::exit(1);
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
@@ -187,17 +191,22 @@ void slice(int argc, char **argv)
|
||||
extruder_train_nr = int(*str - '0'); // TODO: parse int instead (now "-e10"="-e:" , "-e11"="-e;" , "-e12"="-e<" .. etc)
|
||||
last_settings_object = meshgroup->createExtruderTrain(extruder_train_nr);
|
||||
last_extruder_train = last_settings_object;
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_train_nr, last_extruder_train);
|
||||
break;
|
||||
case 'l':
|
||||
argn++;
|
||||
|
||||
log("Loading %s from disk...\n", argv[argn]);
|
||||
// transformation = // TODO: get a transformation from somewhere
|
||||
|
||||
|
||||
transformation = last_settings_object->getSettingAsPointMatrix("mesh_rotation_matrix"); // the transformation applied to a model when loaded
|
||||
|
||||
if (!last_extruder_train)
|
||||
{
|
||||
last_extruder_train = meshgroup->createExtruderTrain(0); // assume a json has already been provided on the command line
|
||||
}
|
||||
if (!loadMeshIntoMeshGroup(meshgroup, argv[argn], transformation, last_extruder_train))
|
||||
{
|
||||
logError("Failed to load model: %s\n", argv[argn]);
|
||||
std::exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -250,8 +259,7 @@ void slice(int argc, char **argv)
|
||||
int extruder_count = FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count");
|
||||
for (extruder_train_nr = 0; extruder_train_nr < extruder_count; extruder_train_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_train_nr); // create new extruder train objects or use already existing ones
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_train_nr, train);
|
||||
meshgroup->createExtruderTrain(extruder_train_nr); // create new extruder train objects or use already existing ones
|
||||
}
|
||||
|
||||
|
||||
@@ -296,23 +304,23 @@ int main(int argc, char **argv)
|
||||
|
||||
Progress::init();
|
||||
|
||||
|
||||
logCopyright("\n");
|
||||
logCopyright("Cura_SteamEngine version %s\n", VERSION);
|
||||
logCopyright("Copyright (C) 2014 David Braam\n");
|
||||
logCopyright("\n");
|
||||
logCopyright("This program is free software: you can redistribute it and/or modify\n");
|
||||
logCopyright("it under the terms of the GNU Affero General Public License as published by\n");
|
||||
logCopyright("the Free Software Foundation, either version 3 of the License, or\n");
|
||||
logCopyright("(at your option) any later version.\n");
|
||||
logCopyright("\n");
|
||||
logCopyright("This program is distributed in the hope that it will be useful,\n");
|
||||
logCopyright("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
|
||||
logCopyright("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
|
||||
logCopyright("GNU Affero General Public License for more details.\n");
|
||||
logCopyright("\n");
|
||||
logCopyright("You should have received a copy of the GNU Affero General Public License\n");
|
||||
logCopyright("along with this program. If not, see <http://www.gnu.org/licenses/>.\n");
|
||||
std::cerr << std::boolalpha;
|
||||
logAlways("\n");
|
||||
logAlways("Cura_SteamEngine version %s\n", VERSION);
|
||||
logAlways("Copyright (C) 2014 David Braam\n");
|
||||
logAlways("\n");
|
||||
logAlways("This program is free software: you can redistribute it and/or modify\n");
|
||||
logAlways("it under the terms of the GNU Affero General Public License as published by\n");
|
||||
logAlways("the Free Software Foundation, either version 3 of the License, or\n");
|
||||
logAlways("(at your option) any later version.\n");
|
||||
logAlways("\n");
|
||||
logAlways("This program is distributed in the hope that it will be useful,\n");
|
||||
logAlways("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
|
||||
logAlways("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
|
||||
logAlways("GNU Affero General Public License for more details.\n");
|
||||
logAlways("\n");
|
||||
logAlways("You should have received a copy of the GNU Affero General Public License\n");
|
||||
logAlways("along with this program. If not, see <http://www.gnu.org/licenses/>.\n");
|
||||
|
||||
|
||||
if (argc < 2)
|
||||
@@ -347,6 +355,7 @@ int main(int argc, char **argv)
|
||||
bool inherit_viz = false;
|
||||
bool warning_viz = false;
|
||||
bool error_viz = false;
|
||||
bool global_only_viz = false;
|
||||
if (argc >= 6)
|
||||
{
|
||||
char* str = argv[5];
|
||||
@@ -368,6 +377,9 @@ int main(int argc, char **argv)
|
||||
case 'w':
|
||||
warning_viz = true;
|
||||
break;
|
||||
case 'g':
|
||||
global_only_viz = true;
|
||||
break;
|
||||
default:
|
||||
cura::logError("Unknown option: %c\n", *str);
|
||||
print_call(argc, argv);
|
||||
@@ -379,26 +391,26 @@ int main(int argc, char **argv)
|
||||
}
|
||||
else
|
||||
{
|
||||
cura::logError("\n");
|
||||
cura::logError("usage:\n");
|
||||
cura::logError("CuraEngine analyse <fdmPrinter.def.json> <output.gv> <engine_settings_list> -[p|i|e|w]\n");
|
||||
cura::logError("\tGenerate a grpah to visualize the setting inheritance structure.\n");
|
||||
cura::logError("\t<fdmPrinter.def.json>\n\tThe base seting definitions file.\n");
|
||||
cura::logError("\t<output.gv>\n\tThe output file.\n");
|
||||
cura::logError("\t<engine_settings_list>\n\tA text file with all setting keys used in the engine, separated by newlines.\n");
|
||||
cura::logError("\t-[p|i|e|w]\n\tOptions for what to include in the visualization\n");
|
||||
cura::logError("\t\tp\tVisualize the parent-child relationship.\n");
|
||||
cura::logError("\t\ti\tVisualize inheritance function relationships.\n");
|
||||
cura::logError("\t\te\tVisualize (max/min) error function relationships.\n");
|
||||
cura::logError("\t\tw\tVisualize (max/min) warning function relationships.\n");
|
||||
cura::logError("\n");
|
||||
cura::log("\n");
|
||||
cura::log("usage:\n");
|
||||
cura::log("CuraEngine analyse <fdmPrinter.def.json> <output.gv> <engine_settings_list> -[p|i|e|w]\n");
|
||||
cura::log("\tGenerate a grpah to visualize the setting inheritance structure.\n");
|
||||
cura::log("\t<fdmPrinter.def.json>\n\tThe base seting definitions file.\n");
|
||||
cura::log("\t<output.gv>\n\tThe output file.\n");
|
||||
cura::log("\t<engine_settings_list>\n\tA text file with all setting keys used in the engine, separated by newlines.\n");
|
||||
cura::log("\t-[p|i|e|w]\n\tOptions for what to include in the visualization\n");
|
||||
cura::log("\t\tp\tVisualize the parent-child relationship.\n");
|
||||
cura::log("\t\ti\tVisualize inheritance function relationships.\n");
|
||||
cura::log("\t\te\tVisualize (max/min) error function relationships.\n");
|
||||
cura::log("\t\tw\tVisualize (max/min) warning function relationships.\n");
|
||||
cura::log("\n");
|
||||
|
||||
}
|
||||
|
||||
SettingsToGv gv_out(argv[3], argv[4], parent_child_viz, inherit_viz, error_viz, warning_viz);
|
||||
SettingsToGv gv_out(argv[3], argv[4], parent_child_viz, inherit_viz, error_viz, warning_viz, global_only_viz);
|
||||
if (gv_out.generate(std::string(argv[2])))
|
||||
{
|
||||
cura::logError("ERROR: Failed to analyse json file: %s\n", argv[2]);
|
||||
cura::logError("Failed to analyse json file: %s\n", argv[2]);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
+14
-13
@@ -53,9 +53,10 @@ void Mesh::finish()
|
||||
for(unsigned int i=0; i<faces.size(); i++)
|
||||
{
|
||||
MeshFace& face = faces[i];
|
||||
face.connected_face_index[0] = getFaceIdxWithPoints(face.vertex_index[0], face.vertex_index[1], i); // faces are connected via the outside
|
||||
face.connected_face_index[1] = getFaceIdxWithPoints(face.vertex_index[1], face.vertex_index[2], i);
|
||||
face.connected_face_index[2] = getFaceIdxWithPoints(face.vertex_index[2], face.vertex_index[0], i);
|
||||
// faces are connected via the outside
|
||||
face.connected_face_index[0] = getFaceIdxWithPoints(face.vertex_index[0], face.vertex_index[1], i, face.vertex_index[2]);
|
||||
face.connected_face_index[1] = getFaceIdxWithPoints(face.vertex_index[1], face.vertex_index[2], i, face.vertex_index[0]);
|
||||
face.connected_face_index[2] = getFaceIdxWithPoints(face.vertex_index[2], face.vertex_index[0], i, face.vertex_index[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +72,13 @@ AABB3D Mesh::getAABB() const
|
||||
{
|
||||
return aabb;
|
||||
}
|
||||
void Mesh::expandXY(int64_t offset)
|
||||
{
|
||||
if (offset)
|
||||
{
|
||||
aabb.expandXY(offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Mesh::findIndexOfVertex(const Point3& v)
|
||||
@@ -116,17 +124,13 @@ See <a href="http://stackoverflow.com/questions/14066933/direct-way-of-computing
|
||||
|
||||
|
||||
*/
|
||||
int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
|
||||
int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx, int notFaceVertexIdx) const
|
||||
{
|
||||
std::vector<int> candidateFaces; // in case more than two faces meet at an edge, multiple candidates are generated
|
||||
int notFaceVertexIdx = -1; // index of the third vertex of the face corresponding to notFaceIdx
|
||||
for(int f : vertices[idx0].connected_faces) // search through all faces connected to the first vertex and find those that are also connected to the second
|
||||
{
|
||||
if (f == notFaceIdx)
|
||||
{
|
||||
for (int i = 0; i<3; i++) // find the vertex which is not idx0 or idx1
|
||||
if (faces[f].vertex_index[i] != idx0 && faces[f].vertex_index[i] != idx1)
|
||||
notFaceVertexIdx = faces[f].vertex_index[i];
|
||||
continue;
|
||||
}
|
||||
if ( faces[f].vertex_index[0] == idx1 // && faces[f].vertex_index[1] == idx0 // next face should have the right direction!
|
||||
@@ -136,12 +140,10 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
|
||||
|
||||
}
|
||||
|
||||
if (candidateFaces.size() == 0) { cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx); return -1; }
|
||||
if (candidateFaces.size() == 0) { cura::logWarning("Couldn't find face connected to face %i.\n", notFaceIdx); return -1; }
|
||||
if (candidateFaces.size() == 1) { return candidateFaces[0]; }
|
||||
|
||||
|
||||
if (notFaceVertexIdx < 0) { cura::logError("Couldn't find third point on face %i.\n", notFaceIdx); return -1; }
|
||||
|
||||
if (candidateFaces.size() % 2 == 0) cura::log("Warning! Edge with uneven number of faces connecting it!(%i)\n", candidateFaces.size()+1);
|
||||
|
||||
FPoint3 vn = vertices[idx1].p - vertices[idx0].p;
|
||||
@@ -176,7 +178,6 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
|
||||
if (angle == 0)
|
||||
{
|
||||
cura::log("Warning! Overlapping faces: face %i and face %i.\n", notFaceIdx, candidateFace);
|
||||
std::cerr<< n.vSize() <<"; "<<n1.vSize()<<";"<<n0.vSize() <<std::endl;
|
||||
}
|
||||
if (angle < smallestAngle)
|
||||
{
|
||||
@@ -184,7 +185,7 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
|
||||
bestIdx = candidateFace;
|
||||
}
|
||||
}
|
||||
if (bestIdx < 0) cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx);
|
||||
if (bestIdx < 0) cura::logWarning("Couldn't find face connected to face %i.\n", notFaceIdx);
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
|
||||
+12
-3
@@ -72,6 +72,7 @@ public:
|
||||
Point3 min() const; //!< min (in x,y and z) vertex of the bounding box
|
||||
Point3 max() const; //!< max (in x,y and z) vertex of the bounding box
|
||||
AABB3D getAABB() const; //!< Get the axis aligned bounding box
|
||||
void expandXY(int64_t offset); //!< Register applied horizontal expansion in the AABB
|
||||
|
||||
/*!
|
||||
* Offset the whole mesh (all vertices and the bounding box).
|
||||
@@ -87,11 +88,19 @@ public:
|
||||
|
||||
private:
|
||||
int findIndexOfVertex(const Point3& v); //!< find index of vertex close to the given point, or create a new vertex and return its index.
|
||||
|
||||
/*!
|
||||
Get the index of the face connected to the face with index \p notFaceIdx, via vertices \p idx0 and \p idx1.
|
||||
In case multiple faces connect with the same edge, return the next counter-clockwise face when viewing from \p idx1 to \p idx0.
|
||||
* Get the index of the face connected to the face with index \p notFaceIdx, via vertices \p idx0 and \p idx1.
|
||||
*
|
||||
* In case multiple faces connect with the same edge, return the next counter-clockwise face when viewing from \p idx1 to \p idx0.
|
||||
*
|
||||
* \param idx0 the first vertex index
|
||||
* \param idx1 the second vertex index
|
||||
* \param notFaceIdx the index of a face which shouldn't be returned
|
||||
* \param notFaceVertexIdx should be the third vertex of face \p notFaceIdx.
|
||||
* \return the face index of a face sharing the edge from \p idx0 to \p idx1
|
||||
*/
|
||||
int getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const;
|
||||
int getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx, int notFaceVertexIdx) const;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+27
-8
@@ -3,20 +3,26 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes, bool alternate_carve_order)
|
||||
{
|
||||
//Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas.
|
||||
for (unsigned int volume_1_idx = 0; volume_1_idx < volumes.size(); volume_1_idx++)
|
||||
for (unsigned int volume_1_idx = 1; volume_1_idx < volumes.size(); volume_1_idx++)
|
||||
{
|
||||
Slicer& volume_1 = *volumes[volume_1_idx];
|
||||
if (volume_1.mesh->getSettingBoolean("infill_mesh"))
|
||||
if (volume_1.mesh->getSettingBoolean("infill_mesh")
|
||||
|| volume_1.mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| volume_1.mesh->getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (unsigned int volume_2_idx = 0; volume_2_idx < volume_1_idx; volume_2_idx++)
|
||||
{
|
||||
Slicer& volume_2 = *volumes[volume_2_idx];
|
||||
if (volume_2.mesh->getSettingBoolean("infill_mesh"))
|
||||
if (volume_2.mesh->getSettingBoolean("infill_mesh")
|
||||
|| volume_2.mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| volume_2.mesh->getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -28,7 +34,14 @@ void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
SlicerLayer& layer1 = volume_1.layers[layerNr];
|
||||
SlicerLayer& layer2 = volume_2.layers[layerNr];
|
||||
layer1.polygons = layer1.polygons.difference(layer2.polygons);
|
||||
if (alternate_carve_order && layerNr % 2 == 0)
|
||||
{
|
||||
layer2.polygons = layer2.polygons.difference(layer1.polygons);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer1.polygons = layer1.polygons.difference(layer2.polygons);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,17 +61,24 @@ void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
int overlap = volume->mesh->getSettingInMicrons("multiple_mesh_overlap");
|
||||
if (volume->mesh->getSettingBoolean("infill_mesh")
|
||||
|| volume->mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| volume->mesh->getSettingBoolean("support_mesh")
|
||||
|| overlap == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
AABB3D aabb(volume->mesh->getAABB());
|
||||
aabb.expandXY(overlap); // expand to account for the case where two models and their bounding boxes are adjacent along the X or Y-direction
|
||||
for (unsigned int layer_nr = 0; layer_nr < volume->layers.size(); layer_nr++)
|
||||
{
|
||||
Polygons all_other_volumes;
|
||||
for (Slicer* other_volume : volumes)
|
||||
{
|
||||
if (other_volume->mesh->getSettingBoolean("infill_mesh")
|
||||
|| !other_volume->mesh->getAABB().hit(volume->mesh->getAABB())
|
||||
|| other_volume->mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| other_volume->mesh->getSettingBoolean("support_mesh")
|
||||
|| !other_volume->mesh->getAABB().hit(aabb)
|
||||
|| other_volume == volume
|
||||
)
|
||||
{
|
||||
continue;
|
||||
@@ -66,10 +86,9 @@ void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes)
|
||||
SlicerLayer& other_volume_layer = other_volume->layers[layer_nr];
|
||||
all_other_volumes = all_other_volumes.unionPolygons(other_volume_layer.polygons.offset(offset_to_merge_other_merged_volumes));
|
||||
}
|
||||
all_other_volumes = all_other_volumes.offset(-offset_to_merge_other_merged_volumes);
|
||||
|
||||
SlicerLayer& volume_layer = volume->layers[layer_nr];
|
||||
volume_layer.polygons.unionPolygons(all_other_volumes.intersection(volume_layer.polygons.offset(overlap / 2)));
|
||||
volume_layer.polygons = volume_layer.polygons.unionPolygons(all_other_volumes.intersection(volume_layer.polygons.offset(overlap / 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
/* This file contains code to help fixing up and changing layers that are build from multiple volumes. */
|
||||
namespace cura {
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &meshes);
|
||||
/*!
|
||||
*
|
||||
* \param alternate_carve_order Whether to switch which model carves out of which with every layer
|
||||
*/
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &meshes, bool alternate_carve_order);
|
||||
|
||||
/*!
|
||||
* Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "pathOrderOptimizer.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/BucketGrid2D.h"
|
||||
#include "utils/SparsePointGridInclusive.h"
|
||||
#include "utils/linearAlg2D.h"
|
||||
|
||||
#define INLINE static inline
|
||||
@@ -153,7 +153,7 @@ int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
|
||||
void LineOrderOptimizer::optimize()
|
||||
{
|
||||
int gridSize = 5000; // the size of the cells in the hash grid. TODO
|
||||
BucketGrid2D<unsigned int> line_bucket_grid(gridSize);
|
||||
SparsePointGridInclusive<unsigned int> line_bucket_grid(gridSize);
|
||||
bool picked[polygons.size()];
|
||||
memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses
|
||||
|
||||
@@ -188,14 +188,16 @@ void LineOrderOptimizer::optimize()
|
||||
int best_line_idx = -1;
|
||||
float best_score = std::numeric_limits<float>::infinity(); // distance score for the best next line
|
||||
|
||||
for(unsigned int close_line_poly_idx : line_bucket_grid.findNearbyObjects(prev_point)) /// check if single-line-polygon is close to last point
|
||||
/// check if single-line-polygon is close to last point
|
||||
for(unsigned int close_line_idx :
|
||||
line_bucket_grid.getNearbyVals(prev_point, gridSize))
|
||||
{
|
||||
if (picked[close_line_poly_idx] || polygons[close_line_poly_idx].size() < 1)
|
||||
if (picked[close_line_idx] || polygons[close_line_idx].size() < 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
updateBestLine(close_line_poly_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal);
|
||||
updateBestLine(close_line_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal);
|
||||
}
|
||||
|
||||
if (best_line_idx == -1) /// if single-line-polygon hasn't been found yet
|
||||
|
||||
@@ -18,7 +18,7 @@ class PathOrderOptimizer
|
||||
{
|
||||
public:
|
||||
EZSeamType type;
|
||||
Point startPoint; //!< The location of the nozzle before starting to print the current layer
|
||||
Point startPoint; //!< A location near the prefered start location
|
||||
std::vector<PolygonRef> polygons; //!< the parts of the layer (in arbitrary order)
|
||||
std::vector<int> polyStart; //!< polygons[i][polyStart[i]] = point of polygon i which is to be the starting point in printing the polygon
|
||||
std::vector<int> polyOrder; //!< the optimized order as indices in #polygons
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "Comb.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional> // function
|
||||
#include <unordered_set>
|
||||
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/PolygonsPointIndex.h"
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "../utils/SVG.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
Polygons Comb::getCombOutlines()
|
||||
{
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
bool include_helper_parts = false;
|
||||
return storage.getLayerOutlines(layer_nr, include_helper_parts);
|
||||
}
|
||||
else
|
||||
{
|
||||
return storage.raftOutline;
|
||||
}
|
||||
}
|
||||
|
||||
LocToLineGrid& Comb::getOutsideLocToLine()
|
||||
{
|
||||
return *outside_loc_to_line;
|
||||
}
|
||||
|
||||
Polygons& Comb::getBoundaryOutside()
|
||||
{
|
||||
return *boundary_outside;
|
||||
}
|
||||
|
||||
Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
, layer_nr(layer_nr)
|
||||
, offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls
|
||||
, max_move_inside_distance2(offset_from_outlines * 2 * offset_from_outlines * 2)
|
||||
, offset_from_outlines_outside(travel_avoid_distance)
|
||||
, offset_from_inside_to_outside(offset_from_outlines + offset_from_outlines_outside)
|
||||
, max_crossing_dist2(offset_from_inside_to_outside * offset_from_inside_to_outside * 2) // so max_crossing_dist = offset_from_inside_to_outside * sqrt(2) =approx 1.5 to allow for slightly diagonal crossings and slightly inaccurate crossing computation
|
||||
, avoid_other_parts(travel_avoid_other_parts)
|
||||
, boundary_inside( comb_boundary_inside )
|
||||
, partsView_inside( boundary_inside.splitIntoPartsView() ) // WARNING !! changes the order of boundary_inside !!
|
||||
, outlines(getCombOutlines())
|
||||
, inside_loc_to_line(PolygonUtils::createLocToLineGrid(boundary_inside, comb_boundary_offset))
|
||||
, boundary_outside(
|
||||
[&storage, layer_nr, travel_avoid_distance]()
|
||||
{
|
||||
return storage.getLayerOutlines(layer_nr, false).offset(travel_avoid_distance);
|
||||
}
|
||||
)
|
||||
, outside_loc_to_line(
|
||||
[](Comb* comber, const int64_t offset_from_inside_to_outside)
|
||||
{
|
||||
return PolygonUtils::createLocToLineGrid(comber->getBoundaryOutside(), offset_from_inside_to_outside * 3 / 2);
|
||||
}
|
||||
, this
|
||||
, offset_from_inside_to_outside
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
Comb::~Comb()
|
||||
{
|
||||
if (inside_loc_to_line)
|
||||
{
|
||||
delete inside_loc_to_line;
|
||||
}
|
||||
}
|
||||
|
||||
bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _startInside, bool _endInside, int64_t max_comb_distance_ignored, bool via_outside_makes_combing_fail, bool fail_on_unavoidable_obstacles)
|
||||
{
|
||||
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Move start and end point inside the comb boundary
|
||||
unsigned int start_inside_poly = NO_INDEX;
|
||||
const bool startInside = moveInside(_startInside, startPoint, start_inside_poly);
|
||||
|
||||
unsigned int end_inside_poly = NO_INDEX;
|
||||
const bool endInside = moveInside(_endInside, endPoint, end_inside_poly);
|
||||
|
||||
unsigned int start_part_boundary_poly_idx;
|
||||
unsigned int end_part_boundary_poly_idx;
|
||||
unsigned int start_part_idx = (start_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(start_inside_poly, &start_part_boundary_poly_idx);
|
||||
unsigned int end_part_idx = (end_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(end_inside_poly, &end_part_boundary_poly_idx);
|
||||
|
||||
if (startInside && endInside && start_part_idx == end_part_idx)
|
||||
{ // normal combing within part
|
||||
PolygonsPart part = partsView_inside.assemblePart(start_part_idx);
|
||||
combPaths.emplace_back();
|
||||
return LinePolygonsCrossings::comb(part, *inside_loc_to_line, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
}
|
||||
else
|
||||
{ // comb inside part to edge (if needed) >> move through air avoiding other parts >> comb inside end part upto the endpoint (if needed)
|
||||
// INSIDE | in_between | OUTSIDE | in_between | INSIDE
|
||||
// ^crossing_1_in ^crossing_1_mid ^crossing_1_out ^crossing_2_out ^crossing_2_mid ^crossing_2_in
|
||||
//
|
||||
// when startPoint is inside crossing_1_in is of interest
|
||||
// when it is in between inside and outside it is equal to crossing_1_mid
|
||||
|
||||
if (via_outside_makes_combing_fail)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Crossing start_crossing(startPoint, startInside, start_part_idx, start_part_boundary_poly_idx, boundary_inside, inside_loc_to_line);
|
||||
Crossing end_crossing(endPoint, endInside, end_part_idx, end_part_boundary_poly_idx, boundary_inside, inside_loc_to_line);
|
||||
|
||||
{ // find crossing over the in-between area between inside and outside
|
||||
start_crossing.findCrossingInOrMid(partsView_inside, endPoint);
|
||||
end_crossing.findCrossingInOrMid(partsView_inside, start_crossing.in_or_mid);
|
||||
}
|
||||
|
||||
bool skip_avoid_other_parts_path = false;
|
||||
if (skip_avoid_other_parts_path && vSize2(start_crossing.in_or_mid - end_crossing.in_or_mid) < offset_from_inside_to_outside * offset_from_inside_to_outside * 4)
|
||||
{ // parts are next to eachother, i.e. the direct crossing will always be smaller than two crossings via outside
|
||||
skip_avoid_other_parts_path = true;
|
||||
}
|
||||
|
||||
if (avoid_other_parts && !skip_avoid_other_parts_path)
|
||||
{ // compute the crossing points when moving through air
|
||||
// comb through all air, since generally the outside consists of a single part
|
||||
|
||||
bool success = start_crossing.findOutside(*boundary_outside, end_crossing.in_or_mid, fail_on_unavoidable_obstacles, *this);
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
success = end_crossing.findOutside(*boundary_outside, start_crossing.out, fail_on_unavoidable_obstacles, *this);
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// generate the actual comb paths
|
||||
if (startInside)
|
||||
{
|
||||
// start to boundary
|
||||
assert(start_crossing.dest_part.size() > 0 && "The part we start inside when combing should have been computed already!");
|
||||
combPaths.emplace_back();
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(start_crossing.dest_part, *inside_loc_to_line, startPoint, start_crossing.in_or_mid, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{ // Couldn't comb between start point and computed crossing from the start part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// throught air from boundary to boundary
|
||||
if (avoid_other_parts && !skip_avoid_other_parts_path)
|
||||
{
|
||||
combPaths.emplace_back();
|
||||
combPaths.throughAir = true;
|
||||
if ( vSize(start_crossing.in_or_mid - end_crossing.in_or_mid) < vSize(start_crossing.in_or_mid - start_crossing.out) + vSize(end_crossing.in_or_mid - end_crossing.out) )
|
||||
{ // via outside is moving more over the in-between zone
|
||||
combPaths.back().push_back(start_crossing.in_or_mid);
|
||||
combPaths.back().push_back(end_crossing.in_or_mid);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(*boundary_outside, *outside_loc_to_line, start_crossing.out, end_crossing.out, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // directly through air (not avoiding other parts)
|
||||
combPaths.emplace_back();
|
||||
combPaths.throughAir = true;
|
||||
combPaths.back().cross_boundary = true; // note: we don't actually know whether this is cross boundary, but it might very well be
|
||||
combPaths.back().push_back(start_crossing.in_or_mid);
|
||||
combPaths.back().push_back(end_crossing.in_or_mid);
|
||||
}
|
||||
if (skip_avoid_other_parts_path)
|
||||
{
|
||||
if (startInside == endInside && start_part_idx == end_part_idx)
|
||||
{
|
||||
if (startInside)
|
||||
{ // both start and end are inside
|
||||
combPaths.back().cross_boundary = PolygonUtils::polygonCollidesWithLineSegment(startPoint, endPoint, *inside_loc_to_line);
|
||||
}
|
||||
else
|
||||
{ // both start and end are outside
|
||||
combPaths.back().cross_boundary = PolygonUtils::polygonCollidesWithLineSegment(startPoint, endPoint, *outside_loc_to_line);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
combPaths.back().cross_boundary = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (endInside)
|
||||
{
|
||||
// boundary to end
|
||||
assert(end_crossing.dest_part.size() > 0 && "The part we end up inside when combing should have been computed already!");
|
||||
combPaths.emplace_back();
|
||||
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(end_crossing.dest_part, *inside_loc_to_line, end_crossing.in_or_mid, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{ // Couldn't comb between end point and computed crossing to the end part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Comb::Crossing::Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside, const LocToLineGrid* inside_loc_to_line)
|
||||
: dest_is_inside(dest_is_inside)
|
||||
, boundary_inside(boundary_inside)
|
||||
, inside_loc_to_line(inside_loc_to_line)
|
||||
, dest_point(dest_point)
|
||||
, dest_part_idx(dest_part_idx)
|
||||
{
|
||||
if (dest_is_inside)
|
||||
{
|
||||
dest_crossing_poly = boundary_inside[dest_part_boundary_crossing_poly_idx]; // initialize with most obvious poly, cause mostly a combing move will move outside the part, rather than inside a hole in the part
|
||||
}
|
||||
}
|
||||
|
||||
bool Comb::moveInside(bool is_inside, Point& dest_point, unsigned int& inside_poly)
|
||||
{
|
||||
if (is_inside)
|
||||
{
|
||||
coord_t max_move_inside_distance2_here = std::numeric_limits<coord_t>::max(); // the distance which would make the moveInside fail
|
||||
if (storage.getSettingAsCombingMode("retraction_combing") == cura::CombingMode::NO_SKIN)
|
||||
{ // if we perform no_skin combing, then a far move inside is likely a consequence of there meing skin in between the destination point and the inside comb boundary
|
||||
// if we perform normal combing, then a far move inside is likely to be a consequence of sharp pointy segments in the layer part
|
||||
max_move_inside_distance2_here = max_move_inside_distance2;
|
||||
}
|
||||
Point original_dest_point = dest_point;
|
||||
ClosestPolygonPoint cpp = PolygonUtils::ensureInsideOrOutside(boundary_inside, dest_point, offset_extra_start_end, max_move_inside_distance2_here, &boundary_inside, inside_loc_to_line);
|
||||
if (!cpp.isValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vSize2(dest_point - original_dest_point) > max_move_inside_distance2 // only check for collision with outlines for long moves
|
||||
&& PolygonUtils::polygonCollidesWithLineSegment(outlines, dest_point, original_dest_point))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
inside_poly = cpp.poly_idx;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Comb::Crossing::findCrossingInOrMid(const PartsView& partsView_inside, const Point close_to)
|
||||
{
|
||||
if (dest_is_inside)
|
||||
{ // in-case
|
||||
// find the point on the start inside-polygon closest to the endpoint, but also kind of close to the start point
|
||||
Point _dest_point(dest_point); // copy to local variable for lambda capture
|
||||
std::function<int(Point)> close_towards_start_penalty_function([_dest_point](Point candidate){ return vSize2((candidate - _dest_point) / 10); });
|
||||
dest_part = partsView_inside.assemblePart(dest_part_idx);
|
||||
|
||||
ClosestPolygonPoint boundary_crossing_point;
|
||||
{ // set [result] to a point on the destination part closest to close_to (but also a bit close to fest_point)
|
||||
std::unordered_set<unsigned int> dest_part_poly_indices;
|
||||
for (unsigned int poly_idx : partsView_inside[dest_part_idx])
|
||||
{
|
||||
dest_part_poly_indices.emplace(poly_idx);
|
||||
}
|
||||
coord_t dist2_score = std::numeric_limits<coord_t>::max();
|
||||
std::function<bool (const PolygonsPointIndex&)> line_processor
|
||||
= [close_to, _dest_point, &boundary_crossing_point, &dist2_score, &dest_part_poly_indices](const PolygonsPointIndex& boundary_segment)
|
||||
{
|
||||
if (dest_part_poly_indices.find(boundary_segment.poly_idx) == dest_part_poly_indices.end())
|
||||
{ // we're not looking at a polygon from the dest_part
|
||||
return true; // a.k.a. continue;
|
||||
}
|
||||
Point closest_here = LinearAlg2D::getClosestOnLineSegment(close_to, boundary_segment.p(), boundary_segment.next().p());
|
||||
coord_t dist2_score_here = vSize2(close_to - closest_here) + vSize2(_dest_point - closest_here) / 10;
|
||||
if (dist2_score_here < dist2_score)
|
||||
{
|
||||
dist2_score = dist2_score_here;
|
||||
boundary_crossing_point = ClosestPolygonPoint(closest_here, boundary_segment.point_idx, boundary_segment.getPolygon(), boundary_segment.poly_idx);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
inside_loc_to_line->processLine(std::make_pair(dest_point, close_to), line_processor);
|
||||
}
|
||||
|
||||
Point result(boundary_crossing_point.p()); // the inside point of the crossing
|
||||
if (!boundary_crossing_point.isValid())
|
||||
{ // no point has been found in the sparse grid
|
||||
result = dest_point;
|
||||
}
|
||||
|
||||
int64_t max_dist2 = std::numeric_limits<int64_t>::max();
|
||||
ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::ensureInsideOrOutside(dest_part, result, boundary_crossing_point, offset_dist_to_get_from_on_the_polygon_to_outside, max_dist2, &boundary_inside, inside_loc_to_line, close_towards_start_penalty_function);
|
||||
if (crossing_1_in_cp.isValid())
|
||||
{
|
||||
dest_crossing_poly = crossing_1_in_cp.poly;
|
||||
in_or_mid = result;
|
||||
}
|
||||
else
|
||||
{ // part is too small to be ensuring a point inside with the given distance
|
||||
in_or_mid = dest_point; // just use the startPoint or endPoint itself
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
in_or_mid = dest_point; // mid-case
|
||||
}
|
||||
};
|
||||
|
||||
bool Comb::Crossing::findOutside(const Polygons& outside, const Point close_to, const bool fail_on_unavoidable_obstacles, Comb& comber)
|
||||
{
|
||||
out = in_or_mid;
|
||||
if (dest_is_inside || outside.inside(in_or_mid, true)) // start in_between
|
||||
{ // move outside
|
||||
Point preferred_crossing_1_out = in_or_mid + normal(close_to - in_or_mid, comber.offset_from_inside_to_outside);
|
||||
std::function<int(Point)> close_to_penalty_function([preferred_crossing_1_out](Point candidate){ return vSize2((candidate - preferred_crossing_1_out) / 2); });
|
||||
std::optional<ClosestPolygonPoint> crossing_1_out_cpp = PolygonUtils::findClose(in_or_mid, outside, comber.getOutsideLocToLine(), close_to_penalty_function);
|
||||
if (crossing_1_out_cpp)
|
||||
{
|
||||
out = PolygonUtils::moveOutside(*crossing_1_out_cpp, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
else
|
||||
{
|
||||
PolygonUtils::moveOutside(outside, out, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
int64_t in_out_dist2_1 = vSize2(out - in_or_mid);
|
||||
if (dest_is_inside && in_out_dist2_1 > comber.max_crossing_dist2) // moveInside moved too far
|
||||
{ // if move is too far over in_between
|
||||
// find crossing closer by
|
||||
assert(dest_crossing_poly && "destination crossing poly should have been instantiated!");
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(outside, *dest_crossing_poly, dest_point, close_to, comber);
|
||||
if (best)
|
||||
{
|
||||
in_or_mid = PolygonUtils::moveInside(best->first, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
out = PolygonUtils::moveOutside(best->second, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
if (fail_on_unavoidable_obstacles && vSize2(out - in_or_mid) > comber.max_crossing_dist2) // moveInside moved still too far
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> Comb::Crossing::findBestCrossing(const Polygons& outside, const PolygonRef from, const Point estimated_start, const Point estimated_end, Comb& comber)
|
||||
{
|
||||
ClosestPolygonPoint* best_in = nullptr;
|
||||
ClosestPolygonPoint* best_out = nullptr;
|
||||
int64_t best_detour_score = std::numeric_limits<int64_t>::max();
|
||||
int64_t best_crossing_dist2;
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> crossing_out_candidates = PolygonUtils::findClose(from, outside, comber.getOutsideLocToLine());
|
||||
bool seen_close_enough_connection = false;
|
||||
for (std::pair<ClosestPolygonPoint, ClosestPolygonPoint>& crossing_candidate : crossing_out_candidates)
|
||||
{
|
||||
int64_t crossing_dist2 = vSize2(crossing_candidate.first.location - crossing_candidate.second.location);
|
||||
if (crossing_dist2 > comber.max_crossing_dist2 * 2)
|
||||
{ // preliminary filtering
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t dist_to_start = vSize(crossing_candidate.second.location - estimated_start); // use outside location, so that the crossing direction is taken into account
|
||||
int64_t dist_to_end = vSize(crossing_candidate.second.location - estimated_end);
|
||||
int64_t detour_dist = dist_to_start + dist_to_end;
|
||||
int64_t detour_score = crossing_dist2 + detour_dist * detour_dist / 1000; // prefer a closest connection over a detour
|
||||
// The detour distance is generally large compared to the crossing distance.
|
||||
// While the crossing is generally about 1mm across,
|
||||
// the distance between an arbitrary point and the boundary may well be a couple of centimetres.
|
||||
// So the crossing_dist2 is about 1.000.000 while the detour_dist_2 is in the order of 400.000.000
|
||||
// In the end we just want to choose between two points which have the _same_ crossing distance, modulo rounding error.
|
||||
if ((!seen_close_enough_connection && detour_score < best_detour_score) // keep the best as long as we havent seen one close enough (so that we may walk along the polygon to find a closer connection from it in the code below)
|
||||
|| (!seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2) // make the one which is close enough the best as soon as we see one close enough
|
||||
|| (seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2 && detour_score < best_detour_score)) // update to keep the best crossing which is close enough already
|
||||
{
|
||||
if (!seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2)
|
||||
{
|
||||
seen_close_enough_connection = true;
|
||||
}
|
||||
best_in = &crossing_candidate.first;
|
||||
best_out = &crossing_candidate.second;
|
||||
best_detour_score = detour_score;
|
||||
best_crossing_dist2 = crossing_dist2;
|
||||
}
|
||||
}
|
||||
if (best_detour_score == std::numeric_limits<int64_t>::max())
|
||||
{ // i.e. if best_in == nullptr or if best_out == nullptr
|
||||
return std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>();
|
||||
}
|
||||
if (best_crossing_dist2 > comber.max_crossing_dist2)
|
||||
{ // find closer point on line segments, rather than moving between vertices of the polygons only
|
||||
PolygonUtils::walkToNearestSmallestConnection(*best_in, *best_out);
|
||||
best_crossing_dist2 = vSize2(best_in->location - best_out->location);
|
||||
if (best_crossing_dist2 > comber.max_crossing_dist2)
|
||||
{
|
||||
return std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>();
|
||||
}
|
||||
}
|
||||
return std::make_shared<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>(*best_in, *best_out);
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,185 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef PATH_PLANNING_COMB_H
|
||||
#define PATH_PLANNING_COMB_H
|
||||
|
||||
#include <memory> // shared_ptr
|
||||
|
||||
#include "../utils/optional.h"
|
||||
#include "../utils/polygon.h"
|
||||
#include "../utils/SparsePointGridInclusive.h"
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/LazyInitialization.h"
|
||||
|
||||
#include "LinePolygonsCrossings.h"
|
||||
#include "CombPath.h"
|
||||
#include "CombPaths.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class SliceDataStorage;
|
||||
|
||||
/*!
|
||||
* Class for generating a full combing actions from a travel move from a start point to an end point.
|
||||
* A single Comb object is used for each layer.
|
||||
*
|
||||
* Comb::calc is the main function of this class.
|
||||
*
|
||||
* Typical output: A combing path to the boundary of the polygon + a move through air avoiding other parts in the layer + a combing path from the boundary of the ending polygon to the end point.
|
||||
* Each of these three is a CombPath; the first and last are within Comb::boundary_inside while the middle is outside of Comb::boundary_outside.
|
||||
* Between these there is a little gap where the nozzle crosses the boundary of an object approximately perpendicular to its boundary.
|
||||
*
|
||||
* As an optimization, the combing paths inside are calculated on specifically those PolygonsParts within which to comb, while the coundary_outside isn't split into outside parts,
|
||||
* because generally there is only one outside part; encapsulated holes occur less often.
|
||||
*/
|
||||
class Comb
|
||||
{
|
||||
friend class LinePolygonsCrossings;
|
||||
private:
|
||||
/*!
|
||||
* A crossing from the inside boundary to the outside boundary.
|
||||
*
|
||||
* 'dest' is either the startPoint or the endpoint of a whole combing move.
|
||||
*/
|
||||
class Crossing
|
||||
{
|
||||
public:
|
||||
bool dest_is_inside; //!< Whether the startPoint or endPoint is inside the inside boundary
|
||||
Point in_or_mid; //!< The point on the inside boundary, or in between the inside and outside boundary if the start/end point isn't inside the inside boudary
|
||||
Point out; //!< The point on the outside boundary
|
||||
PolygonsPart dest_part; //!< The assembled inside-boundary PolygonsPart in which the dest_point lies. (will only be initialized when Crossing::dest_is_inside holds)
|
||||
std::optional<PolygonRef> dest_crossing_poly; //!< The polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon)
|
||||
const Polygons& boundary_inside; //!< The inside boundary as in \ref Comb::boundary_inside
|
||||
const LocToLineGrid* inside_loc_to_line; //!< The loc to line grid \ref Comb::inside_loc_to_line
|
||||
|
||||
/*!
|
||||
* Simple constructor
|
||||
*
|
||||
* \param dest_point Either the eventual startPoint or the eventual endPoint of this combing move.
|
||||
* \param dest_is_inside Whether the startPoint or endPoint is inside the inside boundary.
|
||||
* \param dest_part_idx The index into Comb:partsView_inside of the part in which the \p dest_point is.
|
||||
* \param dest_part_boundary_crossing_poly_idx The index in \p boundary_inside of the polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon).
|
||||
* \param boundary_inside The boundary within which to comb.
|
||||
*/
|
||||
Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside, const LocToLineGrid* inside_loc_to_line);
|
||||
|
||||
/*!
|
||||
* Find the not-outside location (Combing::in_or_mid) of the crossing between to the outside boundary
|
||||
*
|
||||
* \param partsView_inside Structured indices onto Comb::boundary_inside which shows which polygons belong to which part.
|
||||
* \param close_to[in] Try to get a crossing close to this point
|
||||
*/
|
||||
void findCrossingInOrMid(const PartsView& partsView_inside, const Point close_to);
|
||||
|
||||
/*!
|
||||
* Find the outside location (Combing::out)
|
||||
*
|
||||
* \param outside The outside boundary polygons
|
||||
* \param close_to A point to get closer to when there are multiple candidates on the outside boundary which are almost equally close to the Crossing::in_or_mid
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \param comber[in] The combing calculator which has references to the offsets and boundaries to use in combing.
|
||||
*/
|
||||
bool findOutside(const Polygons& outside, const Point close_to, const bool fail_on_unavoidable_obstacles, Comb& comber);
|
||||
|
||||
private:
|
||||
const Point dest_point; //!< Either the eventual startPoint or the eventual endPoint of this combing move
|
||||
unsigned int dest_part_idx; //!< The index into Comb:partsView_inside of the part in which the \p dest_point is.
|
||||
|
||||
/*!
|
||||
* Find the best crossing from some inside polygon to the outside boundary.
|
||||
*
|
||||
* The detour from \p estimated_start to \p estimated_end is minimized.
|
||||
*
|
||||
* \param outside The outside boundary polygons
|
||||
* \param from From which inside boundary the crossing to the outside starts or ends
|
||||
* \param estimated_start The one point to which to stay close when evaluating crossings which cross about the same distance
|
||||
* \param estimated_end The other point to which to stay close when evaluating crossings which cross about the same distance
|
||||
* \param comber[in] The combing calculator which has references to the offsets and boundaries to use in combing.
|
||||
* \return A pair of which the first is the crossing point on the inside boundary and the second the crossing point on the outside boundary
|
||||
*/
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> findBestCrossing(const Polygons& outside, const PolygonRef from, Point estimated_start, Point estimated_end, Comb& comber);
|
||||
};
|
||||
|
||||
|
||||
SliceDataStorage& storage; //!< The storage from which to compute the outside boundary, when needed.
|
||||
const int layer_nr; //!< The layer number for the layer for which to compute the outside boundary, when needed.
|
||||
|
||||
const int64_t offset_from_outlines; //!< Offset from the boundary of a part to the comb path. (nozzle width / 2)
|
||||
const int64_t max_move_inside_distance2; //!< Maximal distance of a point to the Comb::boundary_inside which is still to be considered inside. (very sharp corners not allowed :S)
|
||||
const int64_t offset_from_outlines_outside; //!< Offset from the boundary of a part to a travel path which avoids it by this distance.
|
||||
const int64_t offset_from_inside_to_outside; //!< The sum of the offsets for the inside and outside boundary Comb::offset_from_outlines and Comb::offset_from_outlines_outside
|
||||
const int64_t max_crossing_dist2; //!< The maximal distance by which to cross the in_between area between inside and outside
|
||||
static const int64_t max_moveOutside_distance2 = INT64_MAX; //!< Any point which is not inside should be considered outside.
|
||||
static const int64_t offset_dist_to_get_from_on_the_polygon_to_outside = 40; //!< in order to prevent on-boundary vs crossing boundary confusions (precision thing)
|
||||
static const int64_t offset_extra_start_end = 100; //!< Distance to move start point and end point toward eachother to extra avoid collision with the boundaries.
|
||||
|
||||
const bool avoid_other_parts; //!< Whether to perform inverse combing a.k.a. avoid parts.
|
||||
|
||||
Polygons& boundary_inside; //!< The boundary within which to comb.
|
||||
PartsView partsView_inside; //!< Structured indices onto boundary_inside which shows which polygons belong to which part.
|
||||
Polygons outlines; //!< The actual boundary between the model and air
|
||||
LocToLineGrid* inside_loc_to_line; //!< The SparsePointGridInclusive mapping locations to line segments of the inner boundary.
|
||||
LazyInitialization<Polygons> boundary_outside; //!< The boundary outside of which to stay to avoid collision with other layer parts. This is a pointer cause we only compute it when we move outside the boundary (so not when there is only a single part in the layer)
|
||||
LazyInitialization<LocToLineGrid, Comb*, const int64_t> outside_loc_to_line; //!< The SparsePointGridInclusive mapping locations to line segments of the outside boundary.
|
||||
|
||||
/*!
|
||||
* Get the outlines of the meshes or raft for this layer
|
||||
*/
|
||||
Polygons getCombOutlines();
|
||||
|
||||
/*!
|
||||
* Get the SparsePointGridInclusive mapping locations to line segments of the outside boundary. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
LocToLineGrid& getOutsideLocToLine();
|
||||
|
||||
/*!
|
||||
* Get the boundary_outside, which is an offset from the outlines of all meshes in the layer. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
Polygons& getBoundaryOutside();
|
||||
|
||||
/*!
|
||||
* Move the startPoint or endPoint inside when it should be inside
|
||||
* \param is_inside[in] Whether the \p dest_point should be inside
|
||||
* \param dest_point[in,out] The point to move
|
||||
* \param start_inside_poly[out] The polygon in which the point has been moved
|
||||
* \return Whether we have moved the point inside
|
||||
*/
|
||||
bool moveInside(bool is_inside, Point& dest_point, unsigned int& start_inside_poly);
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Initializes the combing areas for every mesh in the layer (not support)
|
||||
*
|
||||
* \warning \ref Comb::calc changes the order of polygons in \p Comb::comb_boundary_inside
|
||||
*
|
||||
* \param storage Where the layer polygon data is stored
|
||||
* \param layer_nr The number of the layer for which to generate the combing areas.
|
||||
* \param comb_boundary_inside The comb boundary within which to comb within layer parts.
|
||||
* \param offset_from_outlines The offset from the outline polygon, to create the combing boundary in case there is no second wall.
|
||||
* \param travel_avoid_other_parts Whether to avoid other layer parts when traveling through air.
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
*/
|
||||
Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t offset_from_outlines, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
|
||||
~Comb();
|
||||
|
||||
/*!
|
||||
* Calculate the comb paths (if any) - one for each polygon combed alternated with travel paths
|
||||
*
|
||||
* \warning Changes the order of polygons in \ref Comb::comb_boundary_inside
|
||||
*
|
||||
* \param startPoint Where to start moving from
|
||||
* \param endPoint Where to move to
|
||||
* \param combPoints Output parameter: The points along the combing path, excluding the \p startPoint (?) and \p endPoint
|
||||
* \param startInside Whether we want to start inside the comb boundary
|
||||
* \param endInside Whether we want to end up inside the comb boundary
|
||||
* \param via_outside_makes_combing_fail When going through air is inavoidable, stop calculation early and return false.
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \return Whether combing has succeeded; otherwise a retraction is needed.
|
||||
*/
|
||||
bool calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside, bool endInside, int64_t max_comb_distance_ignored, bool via_outside_makes_combing_fail, bool fail_on_unavoidable_obstacles);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//PATH_PLANNING_COMB_H
|
||||
@@ -0,0 +1,17 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef PATH_PLANNING_COMB_PATH_H
|
||||
#define PATH_PLANNING_COMB_PATH_H
|
||||
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
struct CombPath : public std::vector<Point> //!< A single path either inside or outise the parts
|
||||
{
|
||||
bool cross_boundary = false; //!< Whether the path crosses a boundary.
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//PATH_PLANNING_COMB_PATH_H
|
||||
@@ -0,0 +1,17 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef PATH_PLANNING_COMB_PATHS_H
|
||||
#define PATH_PLANNING_COMB_PATHS_H
|
||||
|
||||
#include "CombPath.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
struct CombPaths : public std::vector<CombPath> //!< A list of paths alternating between inside a part and outside a part
|
||||
{
|
||||
bool throughAir = false; //!< Whether the path is one which moves through air.
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//PATH_PLANNING_COMB_PATHS_H
|
||||
@@ -0,0 +1,227 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "LinePolygonsCrossings.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "../utils/SVG.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
bool LinePolygonsCrossings::calcScanlineCrossings(bool fail_on_unavoidable_obstacles)
|
||||
{
|
||||
|
||||
min_crossing_idx = NO_INDEX;
|
||||
max_crossing_idx = NO_INDEX;
|
||||
|
||||
for(unsigned int poly_idx = 0; poly_idx < boundary.size(); poly_idx++)
|
||||
{
|
||||
PolyCrossings minMax(poly_idx);
|
||||
PolygonRef poly = boundary[poly_idx];
|
||||
Point p0 = transformation_matrix.apply(poly[poly.size() - 1]);
|
||||
for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
Point p1 = transformation_matrix.apply(poly[point_idx]);
|
||||
if ((p0.Y >= transformed_startPoint.Y && p1.Y <= transformed_startPoint.Y) || (p1.Y >= transformed_startPoint.Y && p0.Y <= transformed_startPoint.Y))
|
||||
{ // if line segment crosses the line through the transformed start and end point (aka scanline)
|
||||
if (p1.Y == p0.Y) //Line segment is parallel with the scanline. That means that both endpoints lie on the scanline, so they will have intersected with the adjacent line.
|
||||
{
|
||||
p0 = p1;
|
||||
continue;
|
||||
}
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y); // intersection point between line segment and the scanline
|
||||
|
||||
if (x >= transformed_startPoint.X && x <= transformed_endPoint.X)
|
||||
{
|
||||
if (!((p1.Y == transformed_startPoint.Y && p1.Y < p0.Y) || (p0.Y == transformed_startPoint.Y && p0.Y < p1.Y)))
|
||||
{ // perform edge case only for line segments on and below the scanline, not for line segments on and above.
|
||||
// \/ will be no crossings and /\ two, but most importantly | will be one crossing.
|
||||
minMax.n_crossings++;
|
||||
}
|
||||
if(x < minMax.min.x) //For the leftmost intersection, move x left to stay outside of the border.
|
||||
//Note: The actual distance from the intersection to the border is almost always less than dist_to_move_boundary_point_outside, since it only moves along the direction of the scanline.
|
||||
{
|
||||
minMax.min.x = x;
|
||||
minMax.min.point_idx = point_idx;
|
||||
}
|
||||
if(x > minMax.max.x) //For the rightmost intersection, move x right to stay outside of the border.
|
||||
{
|
||||
minMax.max.x = x;
|
||||
minMax.max.point_idx = point_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
|
||||
if (fail_on_unavoidable_obstacles && minMax.n_crossings % 2 == 1)
|
||||
{ // if start area and end area are not the same
|
||||
return false;
|
||||
}
|
||||
else if (minMax.min.point_idx != NO_INDEX) // then always also max.point_idx != NO_INDEX
|
||||
{ // if this polygon crossed the scanline
|
||||
if (min_crossing_idx == NO_INDEX || minMax.min.x < crossings[min_crossing_idx].min.x) { min_crossing_idx = crossings.size(); }
|
||||
if (max_crossing_idx == NO_INDEX || minMax.max.x > crossings[max_crossing_idx].max.x) { max_crossing_idx = crossings.size(); }
|
||||
crossings.push_back(minMax);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LinePolygonsCrossings::lineSegmentCollidesWithBoundary()
|
||||
{
|
||||
Point diff = endPoint - startPoint;
|
||||
|
||||
transformation_matrix = PointMatrix(diff);
|
||||
transformed_startPoint = transformation_matrix.apply(startPoint);
|
||||
transformed_endPoint = transformation_matrix.apply(endPoint);
|
||||
|
||||
for(PolygonRef poly : boundary)
|
||||
{
|
||||
Point p0 = transformation_matrix.apply(poly.back());
|
||||
for(Point p1_ : poly)
|
||||
{
|
||||
Point p1 = transformation_matrix.apply(p1_);
|
||||
// when the boundary just touches the line don't disambiguate between the boundary moving on to actually cross the line
|
||||
// and the boundary bouncing back, resulting in not a real collision - to keep the algorithm simple.
|
||||
//
|
||||
// disregard overlapping line segments; probably the next or previous line segment is not overlapping, but will give a collision
|
||||
// when the boundary line segment fully overlaps with the line segment this edge case is not viewed as a collision
|
||||
if (p1.Y != p0.Y && ((p0.Y >= transformed_startPoint.Y && p1.Y <= transformed_startPoint.Y) || (p1.Y >= transformed_startPoint.Y && p0.Y <= transformed_startPoint.Y)))
|
||||
{
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
|
||||
|
||||
if (x > transformed_startPoint.X && x < transformed_endPoint.X)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool LinePolygonsCrossings::getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles)
|
||||
{
|
||||
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored) || !lineSegmentCollidesWithBoundary())
|
||||
{
|
||||
//We're not crossing any boundaries. So skip the comb generation.
|
||||
combPath.push_back(startPoint);
|
||||
combPath.push_back(endPoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool success = calcScanlineCrossings(fail_on_unavoidable_obstacles);
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CombPath basicPath;
|
||||
getBasicCombingPath(basicPath);
|
||||
optimizePath(basicPath, combPath);
|
||||
// combPath = basicPath; // uncomment to disable comb path optimization
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void LinePolygonsCrossings::getBasicCombingPath(CombPath& combPath)
|
||||
{
|
||||
for (PolyCrossings* crossing = getNextPolygonAlongScanline(transformed_startPoint.X)
|
||||
; crossing != nullptr
|
||||
; crossing = getNextPolygonAlongScanline(crossing->max.x))
|
||||
{
|
||||
getBasicCombingPath(*crossing, combPath);
|
||||
}
|
||||
combPath.push_back(endPoint);
|
||||
}
|
||||
|
||||
void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, CombPath& combPath)
|
||||
{
|
||||
PolygonRef poly = boundary[polyCrossings.poly_idx];
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x - std::abs(dist_to_move_boundary_point_outside), transformed_startPoint.Y)));
|
||||
if ( ( polyCrossings.max.point_idx - polyCrossings.min.point_idx + poly.size() ) % poly.size()
|
||||
< poly.size() / 2 )
|
||||
{ // follow the path in the same direction as the winding order of the boundary polygon
|
||||
for(unsigned int point_idx = polyCrossings.min.point_idx
|
||||
; point_idx != polyCrossings.max.point_idx
|
||||
; point_idx = (point_idx < poly.size() - 1) ? (point_idx + 1) : (0))
|
||||
{
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // follow the path in the opposite direction of the winding order of the boundary polygon
|
||||
unsigned int min_idx = (polyCrossings.min.point_idx == 0)? poly.size() - 1: polyCrossings.min.point_idx - 1;
|
||||
unsigned int max_idx = (polyCrossings.max.point_idx == 0)? poly.size() - 1: polyCrossings.max.point_idx - 1;
|
||||
|
||||
for(unsigned int point_idx = min_idx; point_idx != max_idx; point_idx = (point_idx > 0) ? (point_idx - 1) : (poly.size() - 1))
|
||||
{
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x + std::abs(dist_to_move_boundary_point_outside), transformed_startPoint.Y)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
LinePolygonsCrossings::PolyCrossings* LinePolygonsCrossings::getNextPolygonAlongScanline(int64_t x)
|
||||
{
|
||||
PolyCrossings* ret = nullptr;
|
||||
for(PolyCrossings& crossing : crossings)
|
||||
{
|
||||
if (crossing.min.x > x && (ret == nullptr || crossing.min.x < ret->min.x) )
|
||||
{
|
||||
ret = &crossing;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimized_comb_path)
|
||||
{
|
||||
optimized_comb_path.push_back(startPoint);
|
||||
for(unsigned int point_idx = 1; point_idx<comb_path.size(); point_idx++)
|
||||
{
|
||||
if(comb_path[point_idx] == comb_path[point_idx - 1]) //Two points are the same. Skip the second.
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Point& current_point = optimized_comb_path.back();
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(current_point, comb_path[point_idx], loc_to_line_grid))
|
||||
{
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(current_point, comb_path[point_idx - 1], loc_to_line_grid))
|
||||
{
|
||||
comb_path.cross_boundary = true;
|
||||
}
|
||||
optimized_comb_path.push_back(comb_path[point_idx - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// : dont add the newest point
|
||||
|
||||
// TODO: add the below extra optimization? (+/- 7% extra computation time, +/- 2% faster print for Dual_extrusion_support_generation.stl)
|
||||
while (optimized_comb_path.size() > 1)
|
||||
{
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx], loc_to_line_grid))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
optimized_comb_path.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
optimized_comb_path.push_back(comb_path.back());
|
||||
return true;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,24 +1,15 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef COMB_H
|
||||
#define COMB_H
|
||||
#ifndef PATH_PLANNING_LINE_POLYGONS_CROSSINGS_H
|
||||
#define PATH_PLANNING_LINE_POLYGONS_CROSSINGS_H
|
||||
|
||||
#include <memory> // shared_ptr
|
||||
#include "../utils/polygon.h"
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/SparseLineGrid.h"
|
||||
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/BucketGrid2D.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "CombPath.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
struct CombPath : public std::vector<Point> //!< A single path either inside or outise the parts
|
||||
{
|
||||
bool cross_boundary = false; //!< Whether the path crosses a boundary.
|
||||
};
|
||||
struct CombPaths : public std::vector<CombPath> //!< A list of paths alternating between inside a part and outside a part
|
||||
{
|
||||
bool throughAir = false; //!< Whether the path is one which moves through air.
|
||||
};
|
||||
|
||||
/*!
|
||||
* Class for generating a combing move action from point a to point b and avoiding collision with other parts when moving through air.
|
||||
@@ -64,6 +55,7 @@ private:
|
||||
unsigned int poly_idx; //!< The index of the polygon which crosses the scanline
|
||||
Crossing min; //!< The point where the polygon first crosses the scanline.
|
||||
Crossing max; //!< The point where the polygon last crosses the scanline.
|
||||
int n_crossings; //!< The number of times the polygon crossed the scanline.
|
||||
/*!
|
||||
* Create a PolyCrossings with minimal initialization. PolyCrossings::min and PolyCrossings::max are not yet computed.
|
||||
* \param poly_idx The index of the polygon in LinePolygonsCrossings::boundary
|
||||
@@ -71,6 +63,7 @@ private:
|
||||
PolyCrossings(unsigned int poly_idx)
|
||||
: poly_idx(poly_idx)
|
||||
, min(INT64_MAX, NO_INDEX), max(INT64_MIN, NO_INDEX)
|
||||
, n_crossings(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -89,14 +82,15 @@ private:
|
||||
unsigned int max_crossing_idx; //!< The index into LinePolygonsCrossings::crossings to the crossing with the maximal PolyCrossings::max crossing of all PolyCrossings's.
|
||||
|
||||
Polygons& boundary; //!< The boundary not to cross during combing.
|
||||
LocToLineGrid& loc_to_line_grid; //!< Mapping from locations to line segments of \ref LinePolygonsCrossings::boundary
|
||||
Point startPoint; //!< The start point of the scanline.
|
||||
Point endPoint; //!< The end point of the scanline.
|
||||
|
||||
int64_t dist_to_move_boundary_point_outside; //!< The distance used to move outside or inside so that a boundary point doesn't intersect with the boundary anymore. Neccesary due to computational rounding problems. Use negative value for insicde combing.
|
||||
|
||||
PointMatrix transformation_matrix; //!< The transformation which rotates everything such that the scanline is aligned with the x-axis.
|
||||
Point transformed_startPoint; //!< The LinePolygonsCrossings::startPoint as transformed by Comb::transformation_matrix
|
||||
Point transformed_endPoint; //!< The LinePolygonsCrossings::endPoint as transformed by Comb::transformation_matrix
|
||||
Point transformed_startPoint; //!< The LinePolygonsCrossings::startPoint as transformed by Comb::transformation_matrix such that it has (roughly) the same Y as transformed_endPoint
|
||||
Point transformed_endPoint; //!< The LinePolygonsCrossings::endPoint as transformed by Comb::transformation_matrix such that it has (roughly) the same Y as transformed_startPoint
|
||||
|
||||
|
||||
/*!
|
||||
@@ -109,15 +103,19 @@ private:
|
||||
|
||||
/*!
|
||||
* Calculate Comb::crossings, Comb::min_crossing_idx and Comb::max_crossing_idx.
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \return Whether combing succeeded, i.e. when fail_on_unavoidable_obstacles: we didn't cross any gaps/other parts
|
||||
*/
|
||||
void calcScanlineCrossings();
|
||||
bool calcScanlineCrossings(bool fail_on_unavoidable_obstacles);
|
||||
|
||||
/*!
|
||||
* Get the basic combing path and optimize it.
|
||||
*
|
||||
* \param combPath Output parameter: the points along the combing path.
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \return Whether combing succeeded, i.e. we didn't cross any gaps/other parts
|
||||
*/
|
||||
void getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored = MM2INT(1.5));
|
||||
bool getCombingPath(CombPath& combPath, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles);
|
||||
|
||||
/*!
|
||||
* Get the basic combing path, without shortcuts. The path goes straight toward the endPoint and follows the boundary when it hits it, until it passes the scanline again.
|
||||
@@ -168,8 +166,12 @@ private:
|
||||
* \param end the end point
|
||||
* \param dist_to_move_boundary_point_outside Distance used to move a point from a boundary so that it doesn't intersect with it anymore. (Precision issue)
|
||||
*/
|
||||
LinePolygonsCrossings(Polygons& boundary, Point& start, Point& end, int64_t dist_to_move_boundary_point_outside)
|
||||
: boundary(boundary), startPoint(start), endPoint(end), dist_to_move_boundary_point_outside(dist_to_move_boundary_point_outside)
|
||||
LinePolygonsCrossings(Polygons& boundary, LocToLineGrid& loc_to_line_grid, Point& start, Point& end, int64_t dist_to_move_boundary_point_outside)
|
||||
: boundary(boundary)
|
||||
, loc_to_line_grid(loc_to_line_grid)
|
||||
, startPoint(start)
|
||||
, endPoint(end)
|
||||
, dist_to_move_boundary_point_outside(dist_to_move_boundary_point_outside)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -178,103 +180,20 @@ public:
|
||||
/*!
|
||||
* The main function of this class: calculate one combing path within the boundary.
|
||||
* \param boundary The polygons to follow when calculating the basic combing path
|
||||
* \param loc_to_line_grid A sparse grid mapping cells to all line segments of (at least) \p boundary in those cells
|
||||
* \param startPoint From where to start the combing move.
|
||||
* \param endPoint Where to end the combing move.
|
||||
* \param combPath Output parameter: the combing path generated.
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \return Whether combing succeeded, i.e. we didn't cross any gaps/other parts
|
||||
*/
|
||||
static void comb(Polygons& boundary, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside, int64_t max_comb_distance_ignored = MM2INT(1.5))
|
||||
static bool comb(Polygons& boundary, LocToLineGrid& loc_to_line_grid, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles)
|
||||
{
|
||||
LinePolygonsCrossings linePolygonsCrossings(boundary, startPoint, endPoint, dist_to_move_boundary_point_outside);
|
||||
linePolygonsCrossings.getCombingPath(combPath, max_comb_distance_ignored);
|
||||
LinePolygonsCrossings linePolygonsCrossings(boundary, loc_to_line_grid, startPoint, endPoint, dist_to_move_boundary_point_outside);
|
||||
return linePolygonsCrossings.getCombingPath(combPath, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
};
|
||||
};
|
||||
|
||||
class SliceDataStorage;
|
||||
|
||||
/*!
|
||||
* Class for generating a full combing actions from a travel move from a start point to an end point.
|
||||
* A single Comb object is used for each layer.
|
||||
*
|
||||
* Comb::calc is the main function of this class.
|
||||
*
|
||||
* Typical output: A combing path to the boundary of the polygon + a move through air avoiding other parts in the layer + a combing path from the boundary of the ending polygon to the end point.
|
||||
* Each of these three is a CombPath; the first and last are within Comb::boundary_inside while the middle is outside of Comb::boundary_outside.
|
||||
* Between these there is a little gap where the nozzle crosses the boundary of an object approximately perpendicular to its boundary.
|
||||
*
|
||||
* As an optimization, the combing paths inside are calculated on specifically those PolygonsParts within which to comb, while the coundary_outside isn't split into outside parts,
|
||||
* because generally there is only one outside part; encapsulated holes occur less often.
|
||||
*/
|
||||
class Comb
|
||||
{
|
||||
friend class LinePolygonsCrossings;
|
||||
private:
|
||||
SliceDataStorage& storage; //!< The storage from which to compute the outside boundary, when needed.
|
||||
const int layer_nr; //!< The layer number for the layer for which to compute the outside boundary, when needed.
|
||||
|
||||
const int64_t offset_from_outlines; //!< Offset from the boundary of a part to the comb path. (nozzle width / 2)
|
||||
const int64_t max_moveInside_distance2; //!< Maximal distance of a point to the Comb::boundary_inside which is still to be considered inside. (very sharp corners not allowed :S)
|
||||
const int64_t offset_from_outlines_outside; //!< Offset from the boundary of a part to a travel path which avoids it by this distance.
|
||||
const int64_t max_crossing_dist2; //!< The maximal distance by which to cross the in_between area between inside and outside
|
||||
static const int64_t max_moveOutside_distance2 = INT64_MAX; //!< Any point which is not inside should be considered outside.
|
||||
static const int64_t offset_dist_to_get_from_on_the_polygon_to_outside = 40; //!< in order to prevent on-boundary vs crossing boundary confusions (precision thing)
|
||||
static const int64_t offset_extra_start_end = 100; //!< Distance to move start point and end point toward eachother to extra avoid collision with the boundaries.
|
||||
|
||||
const bool avoid_other_parts; //!< Whether to perform inverse combing a.k.a. avoid parts.
|
||||
|
||||
Polygons& boundary_inside; //!< The boundary within which to comb.
|
||||
Polygons* boundary_outside; //!< The boundary outside of which to stay to avoid collision with other layer parts. This is a pointer cause we only compute it when we move outside the boundary (so not when there is only a single part in the layer)
|
||||
BucketGrid2D<PolygonsPointIndex>* outside_loc_to_line; //!< The BucketGrid mapping locations to line segments of the outside boundary.
|
||||
PartsView partsView_inside; //!< Structured indices onto boundary_inside which shows which polygons belong to which part.
|
||||
|
||||
/*!
|
||||
* Get the boundary_outside, which is an offset from the outlines of all meshes in the layer. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
Polygons& getBoundaryOutside();
|
||||
|
||||
/*!
|
||||
* Get the BucketGrid mapping locations to line segments of the outside boundary. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
BucketGrid2D<PolygonsPointIndex>& getOutsideLocToLine();
|
||||
|
||||
/*!
|
||||
* Find the best crossing from some inside polygon to the outside boundary.
|
||||
*
|
||||
* The detour from \p estimated_start to \p estimated_end is minimized.
|
||||
*
|
||||
* \param from From which inside boundary the crossing to the outside starts or ends
|
||||
* \param estimated_start The one point to which to stay close when evaluating crossings which cross about the same distance
|
||||
* \param estimated_end The other point to which to stay close when evaluating crossings which cross about the same distance
|
||||
* \return A pair of which the first is the crossing point on the inside boundary and the second the crossing point on the outside boundary
|
||||
*/
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> findBestCrossing(PolygonRef from, Point estimated_start, Point estimated_end);
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Initializes the combing areas for every mesh in the layer (not support)
|
||||
* \param storage Where the layer polygon data is stored
|
||||
* \param layer_nr The number of the layer for which to generate the combing areas.
|
||||
* \param comb_boundary_inside The comb boundary within which to comb within layer parts.
|
||||
* \param offset_from_outlines The offset from the outline polygon, to create the combing boundary in case there is no second wall.
|
||||
* \param travel_avoid_other_parts Whether to avoid other layer parts when traveling through air.
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
*/
|
||||
Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t offset_from_outlines, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
|
||||
~Comb();
|
||||
|
||||
/*!
|
||||
* Calculate the comb paths (if any) - one for each polygon combed alternated with travel paths
|
||||
*
|
||||
* \param startPoint Where to start moving from
|
||||
* \param endPoint Where to move to
|
||||
* \param combPoints Output parameter: The points along the combing path, excluding the \p startPoint (?) and \p endPoint
|
||||
* \param startInside Whether we want to start inside the comb boundary
|
||||
* \param endInside Whether we want to end up inside the comb boundary
|
||||
* \return Whether combing has succeeded; otherwise a retraction is needed.
|
||||
*/
|
||||
bool calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside = false, bool endInside = false, int64_t max_comb_distance_ignored = MM2INT(1.5));
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//COMB_H
|
||||
#endif//PATH_PLANNING_LINE_POLYGONS_CROSSINGS_H
|
||||
+71
-8
@@ -1,23 +1,86 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include <clipper/clipper.hpp>
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "raft.h"
|
||||
#include "support.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
void generateRaft(SliceDataStorage& storage, int distance)
|
||||
void Raft::generate(SliceDataStorage& storage, int distance)
|
||||
{
|
||||
assert(storage.raftOutline.size() == 0 && "Raft polygon isn't generated yet, so should be empty!");
|
||||
storage.raftOutline = storage.getLayerOutlines(0, true).offset(distance, ClipperLib::jtRound);
|
||||
const int shield_line_width = storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("adhesion_extruder_nr"))->getSettingInMicrons("skirt_brim_line_width");
|
||||
if (storage.draft_protection_shield.size() > 0)
|
||||
{
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(storage.draft_protection_shield.offset(distance));
|
||||
Polygons draft_shield_raft = storage.draft_protection_shield.offset(shield_line_width) // start half a line width outside shield
|
||||
.difference(storage.draft_protection_shield.offset(-distance - shield_line_width / 2, ClipperLib::jtRound)); // end distance inside shield
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(draft_shield_raft);
|
||||
}
|
||||
else if (storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0)
|
||||
if (storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0)
|
||||
{
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(storage.oozeShield[0].offset(distance));
|
||||
}
|
||||
else
|
||||
{
|
||||
storage.raftOutline = storage.getLayerOutlines(0, true).offset(distance);
|
||||
const Polygons& ooze_shield = storage.oozeShield[0];
|
||||
Polygons ooze_shield_raft = ooze_shield.offset(shield_line_width) // start half a line width outside shield
|
||||
.difference(ooze_shield.offset(-distance - shield_line_width / 2, ClipperLib::jtRound)); // end distance inside shield
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(ooze_shield_raft);
|
||||
}
|
||||
storage.raftOutline = storage.raftOutline.offset(1000).offset(-1000); // remove small holes
|
||||
}
|
||||
|
||||
int Raft::getTotalThickness(const SliceDataStorage& storage)
|
||||
{
|
||||
const ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("adhesion_extruder_nr"));
|
||||
return train.getSettingInMicrons("raft_base_thickness")
|
||||
+ train.getSettingInMicrons("raft_interface_thickness")
|
||||
+ train.getSettingAsCount("raft_surface_layers") * train.getSettingInMicrons("raft_surface_thickness");
|
||||
}
|
||||
|
||||
int Raft::getZdiffBetweenRaftAndLayer1(const SliceDataStorage& storage)
|
||||
{
|
||||
const ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("adhesion_extruder_nr"));
|
||||
if (storage.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::RAFT)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
const int64_t airgap = std::max(0, train.getSettingInMicrons("raft_airgap"));
|
||||
const int64_t layer_0_overlap = storage.getSettingInMicrons("layer_0_z_overlap");
|
||||
|
||||
const int64_t layer_height_0 = storage.getSettingInMicrons("layer_height_0");
|
||||
|
||||
const int64_t z_diff_raft_to_bottom_of_layer_1 = std::max(int64_t(0), airgap + layer_height_0 - layer_0_overlap);
|
||||
return z_diff_raft_to_bottom_of_layer_1;
|
||||
}
|
||||
|
||||
|
||||
int Raft::getFillerLayerCount(const SliceDataStorage& storage)
|
||||
{
|
||||
const int64_t normal_layer_height = storage.getSettingInMicrons("layer_height");
|
||||
const unsigned int filler_layer_count = round_divide(getZdiffBetweenRaftAndLayer1(storage), normal_layer_height);
|
||||
return filler_layer_count;
|
||||
}
|
||||
|
||||
int Raft::getFillerLayerHeight(const SliceDataStorage& storage)
|
||||
{
|
||||
if (storage.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::RAFT)
|
||||
{
|
||||
const int64_t normal_layer_height = storage.getSettingInMicrons("layer_height");
|
||||
return normal_layer_height;
|
||||
}
|
||||
const unsigned int filler_layer_height = round_divide(getZdiffBetweenRaftAndLayer1(storage), getFillerLayerCount(storage));
|
||||
return filler_layer_height;
|
||||
}
|
||||
|
||||
|
||||
int Raft::getTotalExtraLayers(const SliceDataStorage& storage)
|
||||
{
|
||||
const ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("adhesion_extruder_nr"));
|
||||
if (train.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::RAFT)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 2 + train.getSettingAsCount("raft_surface_layers") + getFillerLayerCount(storage);
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+37
-1
@@ -6,7 +6,43 @@
|
||||
|
||||
namespace cura {
|
||||
|
||||
void generateRaft(SliceDataStorage& storage, int distance);
|
||||
class Raft
|
||||
{
|
||||
public:
|
||||
static void generate(SliceDataStorage& storage, int distance);
|
||||
|
||||
/*!
|
||||
* Get the height difference between the raft and the bottom of layer 1.
|
||||
*
|
||||
* This is used for the filler layers because they don't use the layer_0_z_overlap
|
||||
*/
|
||||
static int getZdiffBetweenRaftAndLayer1(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Get the amount of layers to fill the airgap and initial layer with helper parts (support, prime tower, etc.)
|
||||
*
|
||||
* The initial layer gets a separate filler layer because we don't want to apply the layer_0_z_overlap to it.
|
||||
*/
|
||||
static int getFillerLayerCount(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Get the layer height of the filler layers in between the raft and layer 1
|
||||
*/
|
||||
static int getFillerLayerHeight(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Get the total thickness of the raft (without airgap)
|
||||
*/
|
||||
static int getTotalThickness(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Get the total amount of extra layers below zero because there is a raft.
|
||||
*
|
||||
* This includes the filler layers which are introduced in the air gap.
|
||||
*/
|
||||
static int getTotalExtraLayers(const SliceDataStorage& storage);
|
||||
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
@@ -127,13 +127,14 @@ int SettingRegistry::loadExtruderJSONsettings(unsigned int extruder_nr, Settings
|
||||
{
|
||||
if (extruder_nr >= extruder_train_ids.size())
|
||||
{
|
||||
return -1;
|
||||
logWarning("Couldn't load extruder.def.json file for extruder %i. Index out of bounds.\n Loading first extruder definition instead.\n", extruder_nr);
|
||||
extruder_nr = 0;
|
||||
}
|
||||
|
||||
std::string definition_file;
|
||||
bool found = getDefinitionFile(extruder_train_ids[extruder_nr], definition_file);
|
||||
if (!found)
|
||||
{
|
||||
logError("Couldn't find extruder.def.json file for extruder %i.\n", extruder_nr);
|
||||
return -1;
|
||||
}
|
||||
bool warn_base_file_duplicates = false;
|
||||
@@ -150,7 +151,7 @@ int SettingRegistry::loadJSONsettings(std::string filename, SettingsBase* settin
|
||||
if (err) { return err; }
|
||||
|
||||
{ // add parent folder to search paths
|
||||
char filename_cstr[500];
|
||||
char filename_cstr[filename.size()];
|
||||
std::strcpy(filename_cstr, filename.c_str()); // copy the string because dirname(.) changes the input string!!!
|
||||
std::string folder_name = std::string(dirname(filename_cstr));
|
||||
search_paths.emplace(folder_name);
|
||||
@@ -162,6 +163,7 @@ int SettingRegistry::loadJSONsettings(std::string filename, SettingsBase* settin
|
||||
bool found = getDefinitionFile(json_document["inherits"].GetString(), child_filename);
|
||||
if (!found)
|
||||
{
|
||||
cura::logError("Inherited JSON file \"%s\" not found\n", json_document["inherits"].GetString());
|
||||
return -1;
|
||||
}
|
||||
err = loadJSONsettings(child_filename, settings_base, warn_base_file_duplicates); // load child first
|
||||
@@ -216,22 +218,6 @@ int SettingRegistry::loadJSONsettingsFromDoc(rapidjson::Document& json_document,
|
||||
return 3;
|
||||
}
|
||||
|
||||
{ // handle machine name
|
||||
std::string machine_name = "Unknown";
|
||||
if (json_document.HasMember("name"))
|
||||
{
|
||||
const rapidjson::Value& machine_name_field = json_document["name"];
|
||||
if (machine_name_field.IsString())
|
||||
{
|
||||
machine_name = machine_name_field.GetString();
|
||||
}
|
||||
}
|
||||
SettingConfig& machine_name_setting = addSetting("machine_name", "Machine Name");
|
||||
machine_name_setting.setDefault(machine_name);
|
||||
machine_name_setting.setType("string");
|
||||
settings_base->_setSetting(machine_name_setting.getKey(), machine_name_setting.getDefaultValue());
|
||||
}
|
||||
|
||||
if (json_document.HasMember("settings"))
|
||||
{
|
||||
std::list<std::string> path;
|
||||
@@ -261,7 +247,7 @@ void SettingRegistry::handleChildren(const rapidjson::Value& settings_list, std:
|
||||
{
|
||||
if (!settings_list.IsObject())
|
||||
{
|
||||
logError("ERROR: json settings list is not an object!\n");
|
||||
logError("json settings list is not an object!\n");
|
||||
return;
|
||||
}
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = settings_list.MemberBegin(); setting_iterator != settings_list.MemberEnd(); ++setting_iterator)
|
||||
@@ -294,19 +280,20 @@ void SettingRegistry::handleSetting(const rapidjson::Value::ConstMemberIterator&
|
||||
const rapidjson::Value& json_setting = json_setting_it->value;
|
||||
if (!json_setting.IsObject())
|
||||
{
|
||||
logError("ERROR: json setting is not an object!\n");
|
||||
logError("json setting is not an object!\n");
|
||||
return;
|
||||
}
|
||||
std::string name = json_setting_it->name.GetString();
|
||||
if (json_setting.HasMember("type") && json_setting["type"].IsString() && json_setting["type"].GetString() == std::string("category"))
|
||||
{ // skip category objects
|
||||
setting_key_to_config[name] = nullptr; // add the category name to the mapping, but don't instantiate a setting config for it.
|
||||
return;
|
||||
}
|
||||
if (settingIsUsedByEngine(json_setting))
|
||||
{
|
||||
if (!json_setting.HasMember("label") || !json_setting["label"].IsString())
|
||||
{
|
||||
logError("ERROR: json setting \"%s\" has no label!\n", name.c_str());
|
||||
logError("json setting \"%s\" has no label!\n", name.c_str());
|
||||
return;
|
||||
}
|
||||
std::string label = json_setting["label"].GetString();
|
||||
@@ -314,7 +301,7 @@ void SettingRegistry::handleSetting(const rapidjson::Value::ConstMemberIterator&
|
||||
SettingConfig* setting = getSettingConfig(name);
|
||||
if (warn_duplicates && setting)
|
||||
{
|
||||
cura::logError("Duplicate definition of setting: %s a.k.a. \"%s\" was already claimed by \"%s\"\n", name.c_str(), label.c_str(), getSettingConfig(name)->getLabel().c_str());
|
||||
cura::logWarning("Duplicate definition of setting: %s a.k.a. \"%s\" was already claimed by \"%s\"\n", name.c_str(), label.c_str(), getSettingConfig(name)->getLabel().c_str());
|
||||
}
|
||||
if (!setting)
|
||||
{
|
||||
@@ -322,6 +309,10 @@ void SettingRegistry::handleSetting(const rapidjson::Value::ConstMemberIterator&
|
||||
}
|
||||
_loadSettingValues(setting, json_setting_it, settings_base);
|
||||
}
|
||||
else
|
||||
{
|
||||
setting_key_to_config[name] = nullptr; // add the setting name to the mapping, but don't instantiate a setting config for it.
|
||||
}
|
||||
}
|
||||
|
||||
SettingConfig& SettingRegistry::addSetting(std::string name, std::string label)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "rapidjson/rapidjson.h"
|
||||
#include "rapidjson/document.h"
|
||||
@@ -36,13 +37,16 @@ class SettingsToGv
|
||||
|
||||
FILE* out;
|
||||
std::set<std::string> engine_settings;
|
||||
bool parent_child_viz, inherit_viz, error_viz, warning_viz;
|
||||
|
||||
std::unordered_map<std::string, std::string> setting_to_color;
|
||||
bool parent_child_viz, inherit_viz, error_viz, warning_viz, global_only_viz;
|
||||
public:
|
||||
SettingsToGv(std::string output_filename, std::string engine_settings_filename, bool parent_child_viz, bool inherit_viz, bool error_viz, bool warning_viz)
|
||||
SettingsToGv(std::string output_filename, std::string engine_settings_filename, bool parent_child_viz, bool inherit_viz, bool error_viz, bool warning_viz, bool global_only_viz)
|
||||
: parent_child_viz(parent_child_viz)
|
||||
, inherit_viz(inherit_viz)
|
||||
, error_viz(error_viz)
|
||||
, warning_viz(warning_viz)
|
||||
, global_only_viz(global_only_viz)
|
||||
{
|
||||
out = fopen(output_filename.c_str(), "w");
|
||||
fprintf(out, "digraph G {\n");
|
||||
@@ -60,14 +64,31 @@ public:
|
||||
private:
|
||||
void generateEdge(const std::string& parent, const std::string& child, RelationType relation_type)
|
||||
{
|
||||
if (engine_settings.find(parent) != engine_settings.end())
|
||||
if (global_only_viz)
|
||||
{
|
||||
fprintf(out, "%s [color=green];\n", parent.c_str());
|
||||
auto parent_it = setting_to_color.find(parent);
|
||||
if (parent_it != setting_to_color.end())
|
||||
{
|
||||
fprintf(out, "%s [color=%s];\n", parent_it->first.c_str(), parent_it->second.c_str());
|
||||
}
|
||||
auto child_it = setting_to_color.find(child);
|
||||
if (child_it != setting_to_color.end())
|
||||
{
|
||||
fprintf(out, "%s [color=%s];\n", child_it->first.c_str(), child_it->second.c_str());
|
||||
}
|
||||
}
|
||||
if (engine_settings.find(child) != engine_settings.end())
|
||||
else
|
||||
{
|
||||
fprintf(out, "%s [color=green];\n", child.c_str());
|
||||
if (engine_settings.find(parent) != engine_settings.end())
|
||||
{
|
||||
fprintf(out, "%s [color=green];\n", parent.c_str());
|
||||
}
|
||||
if (engine_settings.find(child) != engine_settings.end())
|
||||
{
|
||||
fprintf(out, "%s [color=green];\n", child.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string color;
|
||||
switch (relation_type)
|
||||
{
|
||||
@@ -126,7 +147,10 @@ private:
|
||||
inherited_setting_string != "if" && inherited_setting_string != "else" && inherited_setting_string != "and"
|
||||
&& inherited_setting_string != "or" && inherited_setting_string != "math" && inherited_setting_string != "ceil"
|
||||
&& inherited_setting_string != "int" && inherited_setting_string != "round" && inherited_setting_string != "max" // exclude operators and functions
|
||||
&& inherited_setting_string != "log" // exclude functions
|
||||
&& inherited_setting_string != "grid" && inherited_setting_string != "triangles" // exclude enum values
|
||||
&& inherited_setting_string != "cubic" && inherited_setting_string != "tetrahedral" // exclude enum values
|
||||
&& inherited_setting_string != "raft" // exclude enum values
|
||||
&& function.c_str()[regex_match.position() + regex_match.length()] != '\'') // exclude enum terms
|
||||
{
|
||||
if (inherited_setting_string == parent)
|
||||
@@ -157,12 +181,41 @@ private:
|
||||
|
||||
if (data.HasMember("type") && data["type"].IsString() && data["type"].GetString() != std::string("category"))
|
||||
{
|
||||
if (global_only_viz)
|
||||
{
|
||||
std::string color;
|
||||
if (!data.HasMember("settable_per_mesh") || data["settable_per_mesh"].GetBool() == true)
|
||||
{
|
||||
color = "green";
|
||||
}
|
||||
else if (data.HasMember("settable_per_mesh") && data["settable_per_mesh"].GetBool() == false)
|
||||
{
|
||||
if (!data.HasMember("settable_per_extruder") || data["settable_per_extruder"].GetBool() == true)
|
||||
{
|
||||
color = "yellow";
|
||||
}
|
||||
else if (data.HasMember("settable_per_extruder") && data["settable_per_extruder"].GetBool() == false)
|
||||
{
|
||||
if (!data.HasMember("settable_per_meshgroup") || data["settable_per_meshgroup"].GetBool() == true)
|
||||
{
|
||||
color = "orange";
|
||||
}
|
||||
else if (data.HasMember("settable_per_meshgroup") && data["settable_per_meshgroup"].GetBool() == false)
|
||||
{
|
||||
color = "red";
|
||||
}
|
||||
}
|
||||
}
|
||||
setting_to_color.emplace(name, color);
|
||||
// fprintf(out, "%s [color=%s];\n", name.c_str(), color.c_str());
|
||||
}
|
||||
|
||||
bool generated_edge_inherit = createFunctionEdges(data, "inherit_function", parent, name, RelationType::INHERIT_FUNCTION);
|
||||
bool generated_edge_max = createFunctionEdges(data, "max_value", parent, name, RelationType::ERROR_FUNCTION);
|
||||
bool generated_edge_min = createFunctionEdges(data, "min_value", parent, name, RelationType::ERROR_FUNCTION);
|
||||
bool generated_edge_max_warn = createFunctionEdges(data, "max_value_warning", parent, name, RelationType::WARNING_FUNCTION);
|
||||
bool generated_edge_min_warn = createFunctionEdges(data, "min_value_warning", parent, name, RelationType::WARNING_FUNCTION);
|
||||
|
||||
bool generated_edge_inherit = createFunctionEdges(data, "value", parent, name, RelationType::INHERIT_FUNCTION);
|
||||
bool generated_edge_max = createFunctionEdges(data, "maximum_value", parent, name, RelationType::ERROR_FUNCTION);
|
||||
bool generated_edge_min = createFunctionEdges(data, "minimum_value", parent, name, RelationType::ERROR_FUNCTION);
|
||||
bool generated_edge_max_warn = createFunctionEdges(data, "maximum_value_warning", parent, name, RelationType::WARNING_FUNCTION);
|
||||
bool generated_edge_min_warn = createFunctionEdges(data, "minimum_value_warning", parent, name, RelationType::WARNING_FUNCTION);
|
||||
if (generated_edge_inherit || generated_edge_max_warn || generated_edge_min_warn || generated_edge_max || generated_edge_min)
|
||||
{
|
||||
generated_edge = true;
|
||||
|
||||
+152
-49
@@ -3,6 +3,8 @@
|
||||
#include <fstream>
|
||||
#include <stdio.h>
|
||||
#include <sstream> // ostringstream
|
||||
#include <regex> // regex parsing for temp flow graph
|
||||
#include <string> // stod (string to double)
|
||||
#include "../utils/logoutput.h"
|
||||
|
||||
#include "settings.h"
|
||||
@@ -31,6 +33,8 @@ std::string toString(EGCodeFlavor flavor)
|
||||
return "RepRap(Volumetric)";
|
||||
case EGCodeFlavor::GRIFFIN:
|
||||
return "Griffin";
|
||||
case EGCodeFlavor::REPETIER:
|
||||
return "Repetier";
|
||||
case EGCodeFlavor::REPRAP:
|
||||
default:
|
||||
return "RepRap";
|
||||
@@ -38,7 +42,7 @@ std::string toString(EGCodeFlavor flavor)
|
||||
}
|
||||
|
||||
SettingsBaseVirtual::SettingsBaseVirtual()
|
||||
: parent(NULL)
|
||||
: parent(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -48,7 +52,7 @@ SettingsBaseVirtual::SettingsBaseVirtual(SettingsBaseVirtual* parent)
|
||||
}
|
||||
|
||||
SettingsBase::SettingsBase()
|
||||
: SettingsBaseVirtual(NULL)
|
||||
: SettingsBaseVirtual(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -76,24 +80,34 @@ void SettingsBase::setSetting(std::string key, std::string value)
|
||||
}
|
||||
else
|
||||
{
|
||||
cura::logError("Warning: setting an unregistered setting %s\n", key.c_str() );
|
||||
cura::logWarning("Setting an unregistered setting %s to %s\n", key.c_str(), value.c_str());
|
||||
_setSetting(key, value); // Handy when programmers are in the process of introducing a new setting
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsBase::setSettingInheritBase(std::string key, const SettingsBaseVirtual& parent)
|
||||
{
|
||||
setting_inherit_base.emplace(key, &parent);
|
||||
}
|
||||
|
||||
|
||||
std::string SettingsBase::getSettingString(std::string key) const
|
||||
{
|
||||
if (setting_values.find(key) != setting_values.end())
|
||||
{
|
||||
return setting_values.at(key);
|
||||
}
|
||||
if (setting_inherit_base.find(key) != setting_inherit_base.end())
|
||||
{
|
||||
return setting_inherit_base.at(key)->getSettingString(key);
|
||||
}
|
||||
if (parent)
|
||||
{
|
||||
return parent->getSettingString(key);
|
||||
}
|
||||
|
||||
const_cast<SettingsBase&>(*this).setting_values[key] = "";
|
||||
cura::logError("Unregistered setting %s\n", key.c_str());
|
||||
cura::logWarning("Unregistered setting %s\n", key.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -102,6 +116,12 @@ void SettingsMessenger::setSetting(std::string key, std::string value)
|
||||
parent->setSetting(key, value);
|
||||
}
|
||||
|
||||
void SettingsMessenger::setSettingInheritBase(std::string key, const SettingsBaseVirtual& new_parent)
|
||||
{
|
||||
parent->setSettingInheritBase(key, new_parent);
|
||||
}
|
||||
|
||||
|
||||
std::string SettingsMessenger::getSettingString(std::string key) const
|
||||
{
|
||||
return parent->getSettingString(key);
|
||||
@@ -119,6 +139,17 @@ int SettingsBaseVirtual::getSettingAsCount(std::string key) const
|
||||
return atoi(value.c_str());
|
||||
}
|
||||
|
||||
unsigned int SettingsBaseVirtual::getSettingAsLayerNumber(std::string key) const
|
||||
{
|
||||
const unsigned int indicated_layer_number = stoul(getSettingString(key));
|
||||
if (indicated_layer_number < 1) //Input checking: Layer 0 is not allowed.
|
||||
{
|
||||
cura::logWarning("Invalid layer number %i for setting %s.", indicated_layer_number, key.c_str());
|
||||
return 0; //Assume layer 1.
|
||||
}
|
||||
return indicated_layer_number - 1; //Input starts counting at layer 1, but engine code starts counting at layer 0.
|
||||
}
|
||||
|
||||
double SettingsBaseVirtual::getSettingInMillimeters(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
@@ -130,6 +161,12 @@ int SettingsBaseVirtual::getSettingInMicrons(std::string key) const
|
||||
return getSettingInMillimeters(key) * 1000.0;
|
||||
}
|
||||
|
||||
double SettingsBaseVirtual::getSettingInAngleDegrees(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return atof(value.c_str());
|
||||
}
|
||||
|
||||
double SettingsBaseVirtual::getSettingInAngleRadians(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
@@ -158,7 +195,7 @@ double SettingsBaseVirtual::getSettingInDegreeCelsius(std::string key) const
|
||||
double SettingsBaseVirtual::getSettingInMillimetersPerSecond(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return std::max(1.0, atof(value.c_str()));
|
||||
return std::max(0.0, atof(value.c_str()));
|
||||
}
|
||||
|
||||
double SettingsBaseVirtual::getSettingInCubicMillimeters(std::string key) const
|
||||
@@ -179,63 +216,105 @@ double SettingsBaseVirtual::getSettingInSeconds(std::string key) const
|
||||
return std::max(0.0, atof(value.c_str()));
|
||||
}
|
||||
|
||||
DraftShieldHeightLimitation SettingsBaseVirtual::getSettingAsDraftShieldHeightLimitation(const std::string key) const
|
||||
{
|
||||
const std::string value = getSettingString(key);
|
||||
if (value == "full")
|
||||
{
|
||||
return DraftShieldHeightLimitation::FULL;
|
||||
}
|
||||
else if (value == "limited")
|
||||
{
|
||||
return DraftShieldHeightLimitation::LIMITED;
|
||||
}
|
||||
return DraftShieldHeightLimitation::FULL; //Default.
|
||||
}
|
||||
|
||||
FlowTempGraph SettingsBaseVirtual::getSettingAsFlowTempGraph(std::string key) const
|
||||
{
|
||||
FlowTempGraph ret;
|
||||
const char* c_str = getSettingString(key).c_str();
|
||||
char const* char_p = c_str;
|
||||
while (*char_p != '[')
|
||||
std::string value_string = getSettingString(key);
|
||||
if (value_string.empty())
|
||||
{
|
||||
if (*char_p == '\0') //We've reached the end of string without encountering the first opening bracket.
|
||||
{
|
||||
return ret; //Empty at this point.
|
||||
}
|
||||
char_p++;
|
||||
return ret; //Empty at this point.
|
||||
}
|
||||
char_p++; // skip the '['
|
||||
for (; *char_p != '\0'; char_p++)
|
||||
std::regex regex("(\\[([^,\\[]*),([^,\\]]*)\\])");
|
||||
// match with:
|
||||
// - the last opening bracket '['
|
||||
// - then a bunch of characters until the first comma
|
||||
// - a comma
|
||||
// - a bunch of cahracters until the first closing bracket ']'
|
||||
// matches with any substring which looks like "[ 124.512 , 124.1 ]"
|
||||
|
||||
// default constructor = end-of-sequence:
|
||||
std::regex_token_iterator<std::string::iterator> rend;
|
||||
|
||||
int submatches[] = { 1, 2, 3 }; // match whole pair, first number and second number of a pair
|
||||
std::regex_token_iterator<std::string::iterator> match_iter(value_string.begin(), value_string.end(), regex, submatches);
|
||||
while (match_iter != rend)
|
||||
{
|
||||
while (*char_p != '[')
|
||||
{
|
||||
if (*char_p == '\0') //We've reached the end of string without finding the next opening bracket.
|
||||
{
|
||||
return ret; //Don't continue parsing this item then. Just stop and return.
|
||||
}
|
||||
char_p++;
|
||||
}
|
||||
char_p++; // skip the '['
|
||||
char* end;
|
||||
double first = strtod(char_p, &end); //If not a valid number, this becomes zero.
|
||||
char_p = end;
|
||||
while (*char_p != ',')
|
||||
{
|
||||
if (*char_p == '\0') //We've reached the end of string without finding the comma.
|
||||
{
|
||||
return ret; //This entry is incomplete.
|
||||
}
|
||||
char_p++;
|
||||
}
|
||||
char_p++; // skip the ','
|
||||
double second = strtod(char_p, &end); //If not a valid number, this becomes zero.
|
||||
ret.data.emplace_back(first, second);
|
||||
char_p = end;
|
||||
while (*char_p != ']')
|
||||
{
|
||||
if (*char_p == '\0') //We've reached the end of string without finding the closing bracket.
|
||||
{
|
||||
return ret; //This entry is probably complete and has been added, but stop searching.
|
||||
}
|
||||
char_p++;
|
||||
}
|
||||
char_p++; // skip the ']'
|
||||
if (*char_p == ']' || *char_p == '\0')
|
||||
match_iter++; // match the whole pair
|
||||
if (match_iter == rend)
|
||||
{
|
||||
break;
|
||||
}
|
||||
std::string first_substring = *match_iter++;
|
||||
std::string second_substring = *match_iter++;
|
||||
try
|
||||
{
|
||||
double first = std::stod(first_substring);
|
||||
double second = std::stod(second_substring);
|
||||
ret.data.emplace_back(first, second);
|
||||
}
|
||||
catch (const std::invalid_argument& e)
|
||||
{
|
||||
logError("Couldn't read 2D graph element [%s,%s] in setting '%s'. Ignored.\n", first_substring.c_str(), second_substring.c_str(), key.c_str());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FMatrix3x3 SettingsBaseVirtual::getSettingAsPointMatrix(std::string key) const
|
||||
{
|
||||
FMatrix3x3 ret;
|
||||
|
||||
std::string value_string = getSettingString(key);
|
||||
if (value_string.empty())
|
||||
{
|
||||
return ret; // standard matrix ([1,0,0],[0,1,0],[0,0,1])
|
||||
}
|
||||
|
||||
std::string num("([^,\\] ]*)"); // match with anything but the next ',' ']' or space and capture the match
|
||||
std::ostringstream row; // match with "[num,num,num]" and ignore whitespace
|
||||
row << "\\s*\\[\\s*" << num << "\\s*,\\s*" << num << "\\s*,\\s*" << num << "\\s*\\]\\s*";
|
||||
|
||||
std::ostringstream matrix; // match with "[row,row,row]" and ignore whitespace
|
||||
matrix << "\\s*\\[" << row.str() << "\\s*,\\s*" << row.str() << "\\s*,\\s*" << row.str() << "\\]\\s*";
|
||||
|
||||
std::regex point_matrix_regex(matrix.str());
|
||||
std::cmatch sub_matches; // same as std::match_results<const char*> cm;
|
||||
std::regex_match(value_string.c_str(), sub_matches, point_matrix_regex);
|
||||
|
||||
if (sub_matches.size() != 10) // one match for the whole string
|
||||
{
|
||||
logWarning("Mesh transformation matrix could not be parsed!\n\tFormat should be [[f,f,f],[f,f,f],[f,f,f]] allowing whitespace anywhere in between.\n\tWhile what was given was \"%s\".\n", value_string.c_str());
|
||||
return ret; // standard matrix ([1,0,0],[0,1,0],[0,0,1])
|
||||
}
|
||||
|
||||
unsigned int sub_match_idx = 1; // skip the first because the first submatch is the whole string
|
||||
for (unsigned int x = 0; x < 3; x++)
|
||||
{
|
||||
for (unsigned int y = 0; y < 3; y++)
|
||||
{
|
||||
std::sub_match<const char*> sub_match = sub_matches[sub_match_idx];
|
||||
ret.m[y][x] = strtod(std::string(sub_match.str()).c_str(), nullptr);
|
||||
sub_match_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
EGCodeFlavor SettingsBaseVirtual::getSettingAsGCodeFlavor(std::string key) const
|
||||
{
|
||||
@@ -252,6 +331,8 @@ EGCodeFlavor SettingsBaseVirtual::getSettingAsGCodeFlavor(std::string key) const
|
||||
return EGCodeFlavor::MACH3;
|
||||
else if (value == "RepRap (Volumatric)")
|
||||
return EGCodeFlavor::REPRAP_VOLUMATRIC;
|
||||
else if (value == "Repetier")
|
||||
return EGCodeFlavor::REPETIER;
|
||||
return EGCodeFlavor::REPRAP;
|
||||
}
|
||||
|
||||
@@ -262,10 +343,16 @@ EFillMethod SettingsBaseVirtual::getSettingAsFillMethod(std::string key) const
|
||||
return EFillMethod::LINES;
|
||||
if (value == "grid")
|
||||
return EFillMethod::GRID;
|
||||
if (value == "cubic")
|
||||
return EFillMethod::CUBIC;
|
||||
if (value == "tetrahedral")
|
||||
return EFillMethod::TETRAHEDRAL;
|
||||
if (value == "triangles")
|
||||
return EFillMethod::TRIANGLES;
|
||||
if (value == "concentric")
|
||||
return EFillMethod::CONCENTRIC;
|
||||
if (value == "concentric_3d")
|
||||
return EFillMethod::CONCENTRIC_3D;
|
||||
if (value == "zigzag")
|
||||
return EFillMethod::ZIG_ZAG;
|
||||
return EFillMethod::NONE;
|
||||
@@ -278,6 +365,8 @@ EPlatformAdhesion SettingsBaseVirtual::getSettingAsPlatformAdhesion(std::string
|
||||
return EPlatformAdhesion::BRIM;
|
||||
if (value == "raft")
|
||||
return EPlatformAdhesion::RAFT;
|
||||
if (value == "none")
|
||||
return EPlatformAdhesion::NONE;
|
||||
return EPlatformAdhesion::SKIRT;
|
||||
}
|
||||
|
||||
@@ -315,6 +404,20 @@ ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key) const
|
||||
return ESurfaceMode::NORMAL;
|
||||
}
|
||||
|
||||
FillPerimeterGapMode SettingsBaseVirtual::getSettingAsFillPerimeterGapMode(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "nowhere")
|
||||
{
|
||||
return FillPerimeterGapMode::NOWHERE;
|
||||
}
|
||||
if (value == "everywhere")
|
||||
{
|
||||
return FillPerimeterGapMode::EVERYWHERE;
|
||||
}
|
||||
return FillPerimeterGapMode::NOWHERE;
|
||||
}
|
||||
|
||||
CombingMode SettingsBaseVirtual::getSettingAsCombingMode(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
|
||||
@@ -86,6 +86,8 @@ enum class EGCodeFlavor
|
||||
* M227 is used to initialize a single extrusion train.
|
||||
**/
|
||||
GRIFFIN = 6,
|
||||
|
||||
REPETIER = 7,
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -102,20 +104,24 @@ enum class EFillMethod
|
||||
{
|
||||
LINES,
|
||||
GRID,
|
||||
CUBIC,
|
||||
TETRAHEDRAL,
|
||||
TRIANGLES,
|
||||
CONCENTRIC,
|
||||
CONCENTRIC_3D,
|
||||
ZIG_ZAG,
|
||||
NONE
|
||||
};
|
||||
|
||||
/*!
|
||||
* Type of platform adheasion
|
||||
* Type of platform adhesion.
|
||||
*/
|
||||
enum class EPlatformAdhesion
|
||||
{
|
||||
SKIRT,
|
||||
BRIM,
|
||||
RAFT
|
||||
RAFT,
|
||||
NONE
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -142,6 +148,12 @@ enum class ESurfaceMode
|
||||
BOTH
|
||||
};
|
||||
|
||||
enum class FillPerimeterGapMode
|
||||
{
|
||||
NOWHERE,
|
||||
EVERYWHERE
|
||||
};
|
||||
|
||||
enum class CombingMode
|
||||
{
|
||||
OFF,
|
||||
@@ -149,6 +161,15 @@ enum class CombingMode
|
||||
NO_SKIN
|
||||
};
|
||||
|
||||
/*!
|
||||
* How the draft shield height is limited.
|
||||
*/
|
||||
enum class DraftShieldHeightLimitation
|
||||
{
|
||||
FULL, //Draft shield takes full height of the print.
|
||||
LIMITED //Draft shield is limited by draft_shield_height setting.
|
||||
};
|
||||
|
||||
enum class SupportDistPriority
|
||||
{
|
||||
XY_OVERRIDES_Z,
|
||||
@@ -175,7 +196,16 @@ public:
|
||||
virtual std::string getSettingString(std::string key) const = 0;
|
||||
|
||||
virtual void setSetting(std::string key, std::string value) = 0;
|
||||
|
||||
|
||||
/*!
|
||||
* Set the parent settings base for inheriting a setting to a specific setting base.
|
||||
* This overrides the use of \ref SettingsBaseVirtual::parent.
|
||||
*
|
||||
* \param key The setting for which to override the inheritance
|
||||
* \param parent The setting base from which to obtain the setting instead.
|
||||
*/
|
||||
virtual void setSettingInheritBase(std::string key, const SettingsBaseVirtual& parent) = 0;
|
||||
|
||||
virtual ~SettingsBaseVirtual() {}
|
||||
|
||||
SettingsBaseVirtual(); //!< SettingsBaseVirtual without a parent settings object
|
||||
@@ -186,7 +216,18 @@ public:
|
||||
|
||||
int getSettingAsIndex(std::string key) const;
|
||||
int getSettingAsCount(std::string key) const;
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Interprets a setting as a layer number.
|
||||
*
|
||||
* The input of the layer number is one-based. This translates it to
|
||||
* zero-based numbering.
|
||||
*
|
||||
* \return Zero-based numbering of a layer number setting.
|
||||
*/
|
||||
unsigned int getSettingAsLayerNumber(std::string key) const;
|
||||
|
||||
double getSettingInAngleDegrees(std::string key) const;
|
||||
double getSettingInAngleRadians(std::string key) const;
|
||||
double getSettingInMillimeters(std::string key) const;
|
||||
int getSettingInMicrons(std::string key) const;
|
||||
@@ -196,15 +237,18 @@ public:
|
||||
double getSettingInCubicMillimeters(std::string key) const;
|
||||
double getSettingInPercentage(std::string key) const;
|
||||
double getSettingInSeconds(std::string key) const;
|
||||
|
||||
|
||||
FlowTempGraph getSettingAsFlowTempGraph(std::string key) const;
|
||||
|
||||
FMatrix3x3 getSettingAsPointMatrix(std::string key) const;
|
||||
|
||||
DraftShieldHeightLimitation getSettingAsDraftShieldHeightLimitation(const std::string key) const;
|
||||
EGCodeFlavor getSettingAsGCodeFlavor(std::string key) const;
|
||||
EFillMethod getSettingAsFillMethod(std::string key) const;
|
||||
EPlatformAdhesion getSettingAsPlatformAdhesion(std::string key) const;
|
||||
ESupportType getSettingAsSupportType(std::string key) const;
|
||||
EZSeamType getSettingAsZSeamType(std::string key) const;
|
||||
ESurfaceMode getSettingAsSurfaceMode(std::string key) const;
|
||||
FillPerimeterGapMode getSettingAsFillPerimeterGapMode(std::string key) const;
|
||||
CombingMode getSettingAsCombingMode(std::string key);
|
||||
SupportDistPriority getSettingAsSupportDistPriority(std::string key);
|
||||
};
|
||||
@@ -222,6 +266,11 @@ class SettingsBase : public SettingsBaseVirtual
|
||||
friend class SettingRegistry;
|
||||
private:
|
||||
std::unordered_map<std::string, std::string> setting_values;
|
||||
|
||||
/*!
|
||||
* Mapping for each setting which must inherit from a different setting base than \ref SettingsBaseVirtual::parent
|
||||
*/
|
||||
std::unordered_map<std::string, const SettingsBaseVirtual*> setting_inherit_base;
|
||||
public:
|
||||
SettingsBase(); //!< SettingsBase without a parent settings object
|
||||
SettingsBase(SettingsBaseVirtual* parent); //!< construct a SettingsBase with a parent settings object
|
||||
@@ -232,6 +281,7 @@ public:
|
||||
* \param value the value
|
||||
*/
|
||||
void setSetting(std::string key, std::string value);
|
||||
void setSettingInheritBase(std::string key, const SettingsBaseVirtual& parent); //!< See \ref SettingsBaseVirtual::setSettingInheritBase
|
||||
std::string getSettingString(std::string key) const; //!< Get a setting from this SettingsBase (or any ancestral SettingsBase)
|
||||
|
||||
std::string getAllLocalSettingsString() const
|
||||
@@ -272,6 +322,7 @@ public:
|
||||
SettingsMessenger(SettingsBaseVirtual* parent); //!< construct a SettingsMessenger with a parent settings object
|
||||
|
||||
void setSetting(std::string key, std::string value); //!< Set a setting of the parent SettingsBase to a given value
|
||||
void setSettingInheritBase(std::string key, const SettingsBaseVirtual& parent); //!< See \ref SettingsBaseVirtual::setSettingInheritBase
|
||||
std::string getSettingString(std::string key) const; //!< Get a setting from the parent SettingsBase (or any further ancestral SettingsBase)
|
||||
};
|
||||
|
||||
|
||||
+158
-39
@@ -1,5 +1,8 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include <cmath> // std::ceil
|
||||
|
||||
#include "skin.h"
|
||||
#include "utils/math.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
#define MIN_AREA_SIZE (0.4 * 0.4)
|
||||
@@ -8,19 +11,19 @@ namespace cura
|
||||
{
|
||||
|
||||
|
||||
void generateSkins(int layerNr, SliceMeshStorage& mesh, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic)
|
||||
void generateSkins(int layerNr, SliceMeshStorage& mesh, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_line_width, int insetCount, bool no_small_gaps_heuristic)
|
||||
{
|
||||
generateSkinAreas(layerNr, mesh, innermost_wall_extrusion_width, downSkinCount, upSkinCount, wall_line_count, no_small_gaps_heuristic);
|
||||
generateSkinAreas(layerNr, mesh, innermost_wall_line_width, downSkinCount, upSkinCount, wall_line_count, no_small_gaps_heuristic);
|
||||
|
||||
SliceLayer* layer = &mesh.layers[layerNr];
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
{
|
||||
SliceLayerPart* part = &layer->parts[partNr];
|
||||
generateSkinInsets(part, extrusionWidth, insetCount);
|
||||
generateSkinInsets(part, innermost_wall_line_width, insetCount);
|
||||
}
|
||||
}
|
||||
|
||||
void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_extrusion_width, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic)
|
||||
void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, const int innermost_wall_line_width, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
|
||||
@@ -35,11 +38,11 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_
|
||||
|
||||
if (int(part.insets.size()) < wall_line_count)
|
||||
{
|
||||
continue; // the last wall is not present, the part should only get inter preimeter gaps, but no skin.
|
||||
continue; // the last wall is not present, the part should only get inter perimeter gaps, but no skin.
|
||||
}
|
||||
|
||||
Polygons upskin = part.insets.back().offset(-innermost_wall_extrusion_width/2);
|
||||
Polygons downskin = (downSkinCount == 0)? Polygons() : upskin;
|
||||
Polygons upskin = part.insets.back().offset(-innermost_wall_line_width / 2);
|
||||
Polygons downskin = (downSkinCount == 0) ? Polygons() : upskin;
|
||||
if (upSkinCount == 0) upskin = Polygons();
|
||||
|
||||
auto getInsidePolygons = [&part, wall_line_count](SliceLayer& layer2)
|
||||
@@ -49,7 +52,7 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_
|
||||
{
|
||||
if (part.boundaryBox.hit(part2.boundaryBox))
|
||||
{
|
||||
unsigned int wall_idx = std::min(wall_line_count, (int) part2.insets.size()) - 1;
|
||||
unsigned int wall_idx = std::max(0, std::min(wall_line_count, (int) part2.insets.size()) - 1);
|
||||
result.add(part2.insets[wall_idx]);
|
||||
}
|
||||
}
|
||||
@@ -80,7 +83,7 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_
|
||||
downskin = downskin.difference(not_air); // skin overlaps with the walls
|
||||
}
|
||||
|
||||
if (layer_nr < static_cast<int>(mesh.layers.size()) - 1 && upSkinCount > 0)
|
||||
if (layer_nr < static_cast<int>(mesh.layers.size()) - 1 - upSkinCount && upSkinCount > 0)
|
||||
{
|
||||
Polygons not_air = getInsidePolygons(mesh.layers[layer_nr + 1]);
|
||||
for (int upskin_layer_nr = layer_nr + 2; upskin_layer_nr < layer_nr + upSkinCount + 1; upskin_layer_nr++)
|
||||
@@ -104,7 +107,7 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_
|
||||
}
|
||||
|
||||
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount)
|
||||
void generateSkinInsets(SliceLayerPart* part, const int wall_line_width, int insetCount)
|
||||
{
|
||||
if (insetCount == 0)
|
||||
{
|
||||
@@ -118,13 +121,14 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
skin_part.insets.push_back(Polygons());
|
||||
if (i == 0)
|
||||
{
|
||||
skin_part.insets[0] = skin_part.outline.offset(- extrusionWidth/2);
|
||||
} else
|
||||
skin_part.insets[0] = skin_part.outline.offset(-wall_line_width / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
skin_part.insets[i] = skin_part.insets[i - 1].offset(-extrusionWidth);
|
||||
skin_part.insets[i] = skin_part.insets[i - 1].offset(-wall_line_width);
|
||||
}
|
||||
|
||||
// optimize polygons: remove unnnecesary verts
|
||||
// optimize polygons: remove unnecessary verts
|
||||
skin_part.insets[i].simplify();
|
||||
if (skin_part.insets[i].size() < 1)
|
||||
{
|
||||
@@ -135,18 +139,26 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
}
|
||||
}
|
||||
|
||||
void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count)
|
||||
void generateInfill(int layerNr, SliceMeshStorage& mesh, const int innermost_wall_line_width, int infill_skin_overlap, int wall_line_count)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layerNr];
|
||||
|
||||
int extra_offset = 0;
|
||||
EFillMethod fill_pattern = mesh.getSettingAsFillMethod("infill_pattern");
|
||||
if ((fill_pattern == EFillMethod::CONCENTRIC || fill_pattern == EFillMethod::CONCENTRIC_3D)
|
||||
&& layerNr % 2 == 0
|
||||
&& mesh.getSettingInMicrons("infill_line_distance") > mesh.getSettingInMicrons("infill_line_width") * 2)
|
||||
{
|
||||
extra_offset = -innermost_wall_line_width;
|
||||
}
|
||||
|
||||
for(SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
if (int(part.insets.size()) < wall_line_count)
|
||||
{
|
||||
part.infill_area_per_combine.emplace_back(); // put empty polygons as initial infill_per_combine
|
||||
continue; // the last wall is not present, the part should only get inter preimeter gaps, but no infill.
|
||||
}
|
||||
Polygons infill = part.insets.back().offset(-innermost_wall_extrusion_width / 2 - infill_skin_overlap);
|
||||
Polygons infill = part.insets.back().offset(extra_offset - innermost_wall_line_width / 2 - infill_skin_overlap);
|
||||
|
||||
for(SliceLayerPart& part2 : layer.parts)
|
||||
{
|
||||
@@ -159,20 +171,111 @@ void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extr
|
||||
}
|
||||
}
|
||||
infill.removeSmallAreas(MIN_AREA_SIZE);
|
||||
|
||||
part.infill_area = infill.offset(infill_skin_overlap);
|
||||
part.infill_area_per_combine.push_back(part.infill_area);
|
||||
|
||||
Polygons final_infill = infill.offset(infill_skin_overlap);
|
||||
|
||||
if (mesh.getSettingBoolean("infill_hollow"))
|
||||
{
|
||||
part.print_outline = part.print_outline.difference(final_infill);
|
||||
}
|
||||
else
|
||||
{
|
||||
part.infill_area = final_infill;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh, unsigned int gradual_infill_step_height, unsigned int max_infill_steps)
|
||||
{
|
||||
// no early-out for this function; it needs to initialize the [infill_area_per_combine_per_density]
|
||||
float layer_skip_count = 8; // skip every so many layers as to ignore small gaps in the model making computation more easy
|
||||
if (!mesh.getSettingBoolean("skin_no_small_gaps_heuristic"))
|
||||
{
|
||||
layer_skip_count = 1;
|
||||
}
|
||||
unsigned int gradual_infill_step_layer_count = round_divide(gradual_infill_step_height, mesh.getSettingInMicrons("layer_height")); // The difference in layer count between consecutive density infill areas
|
||||
|
||||
// make gradual_infill_step_height divisable by layer_skip_count
|
||||
float n_skip_steps_per_gradual_step = std::max(1.0f, std::ceil(gradual_infill_step_layer_count / layer_skip_count)); // only decrease layer_skip_count to make it a divisor of gradual_infill_step_layer_count
|
||||
layer_skip_count = gradual_infill_step_layer_count / n_skip_steps_per_gradual_step;
|
||||
|
||||
|
||||
size_t min_layer = mesh.getSettingAsCount("bottom_layers");
|
||||
size_t max_layer = mesh.layers.size() - 1 - mesh.getSettingAsCount("top_layers");
|
||||
|
||||
for (size_t layer_idx = 0; layer_idx < mesh.layers.size(); layer_idx++)
|
||||
{ // loop also over layers which don't contain infill cause of bottom_ and top_layer to initialize their infill_area_per_combine_per_density
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
assert(part.infill_area_per_combine_per_density.size() == 0 && "infill_area_per_combine_per_density is supposed to be uninitialized");
|
||||
|
||||
const Polygons& infill_area = part.getOwnInfillArea();
|
||||
|
||||
if (infill_area.size() == 0 || layer_idx < min_layer || layer_idx > max_layer)
|
||||
{ // initialize infill_area_per_combine_per_density empty
|
||||
part.infill_area_per_combine_per_density.emplace_back(); // create a new infill_area_per_combine
|
||||
part.infill_area_per_combine_per_density.back().emplace_back(); // put empty infill area in the newly constructed infill_area_per_combine
|
||||
// note: no need to copy part.infill_area, cause it's the empty vector anyway
|
||||
continue;
|
||||
}
|
||||
Polygons less_dense_infill = infill_area; // one step less dense with each infill_step
|
||||
for (unsigned int infill_step = 0; infill_step < max_infill_steps; infill_step++)
|
||||
{
|
||||
size_t min_layer = layer_idx + infill_step * gradual_infill_step_layer_count + layer_skip_count;
|
||||
size_t max_layer = layer_idx + (infill_step + 1) * gradual_infill_step_layer_count;
|
||||
|
||||
for (float upper_layer_idx = min_layer; static_cast<unsigned int>(upper_layer_idx) <= max_layer; upper_layer_idx += layer_skip_count)
|
||||
{
|
||||
if (static_cast<unsigned int>(upper_layer_idx) >= mesh.layers.size())
|
||||
{
|
||||
less_dense_infill.clear();
|
||||
break;
|
||||
}
|
||||
SliceLayer& upper_layer = mesh.layers[static_cast<unsigned int>(upper_layer_idx)];
|
||||
Polygons relevent_upper_polygons;
|
||||
for (SliceLayerPart& upper_layer_part : upper_layer.parts)
|
||||
{
|
||||
if (!upper_layer_part.boundaryBox.hit(part.boundaryBox))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
relevent_upper_polygons.add(upper_layer_part.getOwnInfillArea());
|
||||
}
|
||||
less_dense_infill = less_dense_infill.intersection(relevent_upper_polygons);
|
||||
}
|
||||
if (less_dense_infill.size() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// add new infill_area_per_combine for the current density
|
||||
part.infill_area_per_combine_per_density.emplace_back();
|
||||
std::vector<Polygons>& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back();
|
||||
const Polygons more_dense_infill = infill_area.difference(less_dense_infill);
|
||||
infill_area_per_combine_current_density.push_back(more_dense_infill);
|
||||
|
||||
if (less_dense_infill.size() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
part.infill_area_per_combine_per_density.emplace_back();
|
||||
std::vector<Polygons>& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back();
|
||||
infill_area_per_combine_current_density.push_back(infill_area);
|
||||
part.infill_area_own = nullptr; // clear infill_area_own, it's not needed any more.
|
||||
assert(part.infill_area_per_combine_per_density.size() != 0 && "infill_area_per_combine_per_density is now initialized");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount)
|
||||
{
|
||||
// Note that *all* parts should have an [infill_area_per_combine] with one element in it, which up till now only contains the exact same polygons as [infill].
|
||||
if(amount <= 1) //If we must combine 1 layer, nothing needs to be combined. Combining 0 layers is invalid.
|
||||
if (mesh.layers.empty() || mesh.layers.size() - 1 < static_cast<size_t>(mesh.getSettingAsCount("top_layers")) || mesh.getSettingAsCount("infill_line_distance") <= 0) //No infill is even generated.
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (mesh.layers.empty() || mesh.layers.size() - 1 < static_cast<size_t>(mesh.getSettingAsCount("top_layers")) || mesh.getSettingAsCount("infill_line_distance") <= 0) //No infill is even generated.
|
||||
if(amount <= 1) //If we must combine 1 layer, nothing needs to be combined. Combining 0 layers is invalid.
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -187,30 +290,46 @@ void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount)
|
||||
for(size_t layer_idx = min_layer;layer_idx <= max_layer;layer_idx += amount) //Skip every few layers, but extrude more.
|
||||
{
|
||||
SliceLayer* layer = &mesh.layers[layer_idx];
|
||||
|
||||
for(unsigned int n = 1;n < amount;n++)
|
||||
for(unsigned int combine_count_here = 1; combine_count_here < amount; combine_count_here++)
|
||||
{
|
||||
if(layer_idx < n)
|
||||
if(layer_idx < combine_count_here)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
SliceLayer* layer2 = &mesh.layers[layer_idx - n];
|
||||
for(SliceLayerPart& part : layer->parts)
|
||||
size_t lower_layer_idx = layer_idx - combine_count_here;
|
||||
if (lower_layer_idx < min_layer)
|
||||
{
|
||||
Polygons result;
|
||||
for(SliceLayerPart& part2 : layer2->parts)
|
||||
{
|
||||
if(part.boundaryBox.hit(part2.boundaryBox))
|
||||
break;
|
||||
}
|
||||
SliceLayer* lower_layer = &mesh.layers[lower_layer_idx];
|
||||
for (SliceLayerPart& part : layer->parts)
|
||||
{
|
||||
for (unsigned int density_idx = 0; density_idx < part.infill_area_per_combine_per_density.size(); density_idx++)
|
||||
{ // go over each density of gradual infill (these density areas overlap!)
|
||||
std::vector<Polygons>& infill_area_per_combine = part.infill_area_per_combine_per_density[density_idx];
|
||||
Polygons result;
|
||||
for (SliceLayerPart& lower_layer_part : lower_layer->parts)
|
||||
{
|
||||
Polygons intersection = part.infill_area_per_combine[n - 1].intersection(part2.infill_area_per_combine[0]).offset(-200).offset(200);
|
||||
result.add(intersection);
|
||||
part.infill_area_per_combine[n - 1] = part.infill_area_per_combine[n - 1].difference(intersection);
|
||||
part2.infill_area_per_combine[0] = part2.infill_area_per_combine[0].difference(intersection);
|
||||
}
|
||||
}
|
||||
if (part.boundaryBox.hit(lower_layer_part.boundaryBox))
|
||||
{
|
||||
|
||||
part.infill_area_per_combine.push_back(result);
|
||||
Polygons intersection = infill_area_per_combine[combine_count_here - 1].intersection(lower_layer_part.infill_area).offset(-200).offset(200);
|
||||
result.add(intersection); // add area to be thickened
|
||||
infill_area_per_combine[combine_count_here - 1] = infill_area_per_combine[combine_count_here - 1].difference(intersection); // remove thickened area from less thick layer here
|
||||
if (density_idx < lower_layer_part.infill_area_per_combine_per_density.size())
|
||||
{ // only remove from *same density* areas on layer below
|
||||
// If there are no same density areas, then it's ok to print them anyway
|
||||
// Don't remove other density areas
|
||||
unsigned int lower_density_idx = density_idx;
|
||||
std::vector<Polygons>& lower_infill_area_per_combine = lower_layer_part.infill_area_per_combine_per_density[lower_density_idx];
|
||||
lower_infill_area_per_combine[0] = lower_infill_area_per_combine[0].difference(intersection); // remove thickened area from lower (thickened) layer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
infill_area_per_combine.push_back(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
-16
@@ -11,38 +11,43 @@ namespace cura
|
||||
*
|
||||
* \param layerNr The index of the layer for which to generate the skins.
|
||||
* \param mesh The storage where the layer outline information (input) is stored and where the skin insets and fill areas (output) are stored.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param downSkinCount The number of layers of bottom skin
|
||||
* \param upSkinCount The number of layers of top skin
|
||||
* \param wall_line_count The number of walls, i.e. the number of the wall from which to offset.
|
||||
* \param innermost_wall_extrusion_width The line width of the inner most wall
|
||||
* \param innermost_wall_line_width The line width of the inner most wall
|
||||
* \param insetCount The number of perimeters to surround the skin
|
||||
* \param no_small_gaps_heuristic A heuristic which assumes there will be no small gaps between bottom and top skin with a z size smaller than the skin size itself
|
||||
*/
|
||||
void generateSkins(int layerNr, SliceMeshStorage& mesh, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic);
|
||||
void generateSkins(int layerNr, SliceMeshStorage& mesh, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_line_width, int insetCount, bool no_small_gaps_heuristic);
|
||||
|
||||
/*!
|
||||
* Generate the skin areas (outlines)
|
||||
*
|
||||
* \param layerNr The index of the layer for which to generate the skins.
|
||||
* \param mesh The storage where the layer outline information (input) is stored and where the skin outline (output) is stored.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param downSkinCount The number of layers of bottom skin
|
||||
* \param upSkinCount The number of layers of top skin
|
||||
* \param wall_line_count The number of walls, i.e. the number of the wall from which to offset.
|
||||
* \param no_small_gaps_heuristic A heuristic which assumes there will be no small gaps between bottom and top skin with a z size smaller than the skin size itself
|
||||
* \param mesh The storage where the layer outline information (input) is stored
|
||||
* and where the skin outline (output) is stored.
|
||||
* \param innermost_wall_line_width The line width of the walls around the skin, by which
|
||||
* we must inset for each wall.
|
||||
* \param downSkinCount The number of layers of bottom skin.
|
||||
* \param upSkinCount The number of layers of top skin.
|
||||
* \param wall_line_count The number of walls, i.e. the number of the wall from
|
||||
* which to offset.
|
||||
* \param no_small_gaps_heuristic A heuristic which assumes there will be no
|
||||
* small gaps between bottom and top skin with a z size smaller than the skin
|
||||
* size itself.
|
||||
*/
|
||||
void generateSkinAreas(int layerNr, SliceMeshStorage& mesh, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic);
|
||||
void generateSkinAreas(int layerNr, SliceMeshStorage& mesh, const int innermost_wall_line_width, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic);
|
||||
|
||||
/*!
|
||||
* Generate the skin insets.
|
||||
*
|
||||
* \param layerNr The index of the layer for which to generate the skins.
|
||||
* \param part The part where the skin outline information (input) is stored and where the skin insets (output) are stored.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param insetCount The number of perimeters to surround the skin
|
||||
* \param part The part where the skin outline information (input) is stored and
|
||||
* where the skin insets (output) are stored.
|
||||
* \param wall_line_width The width of the perimeters around the skin.
|
||||
* \param insetCount The number of perimeters to surround the skin.
|
||||
*/
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount);
|
||||
void generateSkinInsets(SliceLayerPart* part, const int wall_line_width, int insetCount);
|
||||
|
||||
/*!
|
||||
* Generate Infill by offsetting from the last wall.
|
||||
@@ -54,11 +59,11 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
* \param layerNr The index of the layer for which to generate the infill
|
||||
* \param mesh The storage where the layer outline information (input) is stored and where the skin outline (output) is stored.
|
||||
* \param part The part where the insets (input) are stored and where the infill (output) is stored.
|
||||
* \param innermost_wall_extrusion_width width of the innermost wall lines
|
||||
* \param innermost_wall_line_width width of the innermost wall lines
|
||||
* \param infill_skin_overlap overlap distance between infill and skin
|
||||
* \param wall_line_count The number of walls, i.e. the number of the wall from which to offset.
|
||||
*/
|
||||
void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count);
|
||||
void generateInfill(int layerNr, SliceMeshStorage& mesh, const int innermost_wall_line_width, int infill_skin_overlap, int wall_line_count);
|
||||
|
||||
/*!
|
||||
* \brief Combines the infill of multiple layers for a specified mesh.
|
||||
@@ -72,6 +77,28 @@ void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extr
|
||||
*/
|
||||
void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount);
|
||||
|
||||
/*!
|
||||
* Class containing all skin and infill area computation functions
|
||||
*/
|
||||
class SkinInfillAreaComputation
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Generate infill areas which cause a gradually less dense infill structure from top to bottom.
|
||||
*
|
||||
* The areas generated overlap, so that more dense infill adds on to less dense infill.
|
||||
* That way you don't have infill lines which are broken when they cross a border between separated infill areas - if they would be as such.
|
||||
*
|
||||
* This function also guarantees that the SliceLayerPart::infill_area_per_combine_per_density is initialized with at least one item.
|
||||
* The last item in the list will be equal to the infill_area after this function.
|
||||
*
|
||||
* \param gradual_infill_step_height // The height difference between consecutive density infill areas
|
||||
* \param max_infill_steps the maximum exponent of division of infill density. At 5 the least dense infill will be 2^4 * infill_line_distance i.e. one 16th as dense
|
||||
*/
|
||||
static void generateGradualInfill(SliceMeshStorage& mesh, unsigned int gradual_infill_step_height, unsigned int max_infill_steps);
|
||||
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//SKIN_H
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "skirt.h"
|
||||
#include "support.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void generateSkirt(SliceDataStorage& storage, int distance, int count, int minLength)
|
||||
{
|
||||
if (count == 0) return;
|
||||
|
||||
bool externalOnly = (distance > 0); // whether to include holes or not
|
||||
|
||||
int primary_extruder = 0; // TODO allow for other extruder to be primary
|
||||
int primary_extrusion_width = storage.meshgroup->getExtruderTrain(primary_extruder)->getSettingInMicrons("skirt_line_width");
|
||||
|
||||
Polygons& skirt_primary_extruder = storage.skirt[primary_extruder];
|
||||
|
||||
bool get_convex_hull = count == 1 && distance > 0;
|
||||
|
||||
Polygons first_layer_outline = storage.getLayerOutlines(0, true, externalOnly);
|
||||
|
||||
std::vector<Polygons> skirts;
|
||||
for(int skirtNr=0; skirtNr<count;skirtNr++)
|
||||
{
|
||||
int offsetDistance = distance + primary_extrusion_width * skirtNr + primary_extrusion_width / 2;
|
||||
|
||||
skirts.emplace_back(first_layer_outline.offset(offsetDistance, ClipperLib::jtRound));
|
||||
Polygons& skirt_polygons = skirts.back();
|
||||
|
||||
//Remove small inner skirt holes. Holes have a negative area, remove anything smaller then 100x extrusion "area"
|
||||
for(unsigned int n=0; n<skirt_polygons.size(); n++)
|
||||
{
|
||||
double area = skirt_polygons[n].area();
|
||||
if (area < 0 && area > -primary_extrusion_width * primary_extrusion_width * 100)
|
||||
skirt_polygons.remove(n--);
|
||||
}
|
||||
|
||||
if (get_convex_hull)
|
||||
{
|
||||
skirt_polygons = skirt_polygons.convexHull();
|
||||
}
|
||||
|
||||
skirt_primary_extruder.add(skirt_polygons);
|
||||
|
||||
int length = skirt_primary_extruder.polygonLength();
|
||||
if (skirtNr + 1 >= count && length > 0 && length < minLength) // make brim have more lines when total length is too small
|
||||
count++;
|
||||
}
|
||||
|
||||
|
||||
if (false) // the code below is for the old prime tower
|
||||
{ //Add a skirt UNDER the prime tower to make it stick better.
|
||||
Polygons prime_tower = storage.primeTower.ground_poly.offset(-primary_extrusion_width / 2);
|
||||
std::queue<Polygons> prime_tower_insets;
|
||||
while(prime_tower.size() > 0)
|
||||
{
|
||||
prime_tower_insets.emplace(prime_tower);
|
||||
prime_tower = prime_tower.offset(-primary_extrusion_width);
|
||||
}
|
||||
while (!prime_tower_insets.empty())
|
||||
{
|
||||
Polygons& inset = prime_tower_insets.back();
|
||||
skirt_primary_extruder.add(inset);
|
||||
prime_tower_insets.pop();
|
||||
}
|
||||
}
|
||||
|
||||
{ // process other extruders' brim/skirt (as one brim line around the old brim)
|
||||
int offset_distance = 0;
|
||||
int last_width = primary_extrusion_width;
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
if (extruder == primary_extruder) { continue; }
|
||||
int width = storage.meshgroup->getExtruderTrain(extruder)->getSettingInMicrons("skirt_line_width");
|
||||
offset_distance += last_width / 2 + width/2;
|
||||
last_width = width;
|
||||
while (storage.skirt[extruder].polygonLength() < minLength)
|
||||
{
|
||||
storage.skirt[extruder].add(skirts.back().offset(offset_distance, ClipperLib::jtRound));
|
||||
offset_distance += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,22 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef SKIRT_H
|
||||
#define SKIRT_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Generate skirt or brim (depending on parameters); when \p distance > 0 and \p count == 1 the skirt is generated, which has slighly different configuration.
|
||||
*
|
||||
* \param storage Storage containing the parts at the first layer
|
||||
* \param distance The distance of the first outset from the parts at the first layer
|
||||
* \param count Number of outsets / brim lines
|
||||
* \param minLength The minimum length the skirt should have (enforced by taking more outsets)
|
||||
*/
|
||||
void generateSkirt(SliceDataStorage& storage, int distance, int count, int minLength);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//SKIRT_H
|
||||
+183
-90
@@ -5,6 +5,18 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
Polygons& SliceLayerPart::getOwnInfillArea()
|
||||
{
|
||||
if (infill_area_own)
|
||||
{
|
||||
return *infill_area_own;
|
||||
}
|
||||
else
|
||||
{
|
||||
return infill_area;
|
||||
}
|
||||
}
|
||||
|
||||
Polygons SliceLayer::getOutlines(bool external_polys_only) const
|
||||
{
|
||||
Polygons ret;
|
||||
@@ -55,50 +67,79 @@ void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls) const
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<RetractionConfig> SliceDataStorage::initializeRetractionConfigs()
|
||||
{
|
||||
std::vector<RetractionConfig> ret;
|
||||
ret.resize(meshgroup->getExtruderCount()); // initializes with constructor RetractionConfig()
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<GCodePathConfig> SliceDataStorage::initializeTravelConfigs()
|
||||
{
|
||||
std::vector<GCodePathConfig> ret;
|
||||
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
RetractionConfig* retraction_config = nullptr;
|
||||
travel_config_per_extruder.emplace_back(retraction_config, PrintFeatureType::MoveCombing);
|
||||
travel_config_per_extruder.emplace_back(PrintFeatureType::MoveCombing);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
std::vector<GCodePathConfig> SliceDataStorage::initializeSkirtConfigs()
|
||||
|
||||
std::vector<GCodePathConfig> SliceDataStorage::initializeSkirtBrimConfigs()
|
||||
{
|
||||
std::vector<GCodePathConfig> ret;
|
||||
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
RetractionConfig* extruder_retraction_config = &retraction_config_per_extruder[extruder];
|
||||
skirt_config.emplace_back(extruder_retraction_config, PrintFeatureType::Skirt);
|
||||
skirt_brim_config.emplace_back(PrintFeatureType::SkirtBrim);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SliceDataStorage::SliceDataStorage(MeshGroup* meshgroup) : SettingsMessenger(meshgroup),
|
||||
meshgroup(meshgroup != nullptr ? meshgroup : new MeshGroup(FffProcessor::getInstance())), //If no mesh group is provided, we roll our own.
|
||||
print_layer_count(0),
|
||||
retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
extruder_switch_retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
travel_config_per_extruder(initializeTravelConfigs()),
|
||||
skirt_config(initializeSkirtConfigs()),
|
||||
raft_base_config(&retraction_config_per_extruder[getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
raft_interface_config(&retraction_config_per_extruder[getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
raft_surface_config(&retraction_config_per_extruder[getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
support_config(&retraction_config_per_extruder[getSettingAsIndex("support_infill_extruder_nr")], PrintFeatureType::Support),
|
||||
support_roof_config(&retraction_config_per_extruder[getSettingAsIndex("support_roof_extruder_nr")], PrintFeatureType::Skin),
|
||||
max_object_height_second_to_last_extruder(-1)
|
||||
skirt_brim_config(initializeSkirtBrimConfigs()),
|
||||
raft_base_config(PrintFeatureType::SupportInterface),
|
||||
raft_interface_config(PrintFeatureType::Support),
|
||||
raft_surface_config(PrintFeatureType::SupportInterface),
|
||||
support_config(PrintFeatureType::Support),
|
||||
support_skin_config(PrintFeatureType::SupportInterface),
|
||||
max_print_height_second_to_last_extruder(-1)
|
||||
{
|
||||
}
|
||||
|
||||
SliceLayerPart* SliceDataStorage::getPartInside(int layer_nr, Point location)
|
||||
{
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
if ((unsigned int)layer_nr >= mesh.layers.size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
if (part.outline.inside(location))
|
||||
{
|
||||
return ∂
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only) const
|
||||
{
|
||||
if (layer_nr < 0)
|
||||
if (layer_nr < 0 && layer_nr < -Raft::getFillerLayerCount(*this))
|
||||
{ // when processing raft
|
||||
if (include_helper_parts)
|
||||
{
|
||||
@@ -125,21 +166,28 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
|
||||
else
|
||||
{
|
||||
Polygons total;
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
layer.getOutlines(total, external_polys_only);
|
||||
if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make all getSetting functions const??
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
|
||||
if (mesh.getSettingBoolean("infill_mesh") || mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
layer.getOutlines(total, external_polys_only);
|
||||
if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make all getSetting functions const??
|
||||
{
|
||||
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (include_helper_parts)
|
||||
{
|
||||
if (support.generated)
|
||||
{
|
||||
total.add(support.supportLayers[layer_nr].supportAreas);
|
||||
total.add(support.supportLayers[layer_nr].roofs);
|
||||
total.add(support.supportLayers[std::max(0, layer_nr)].supportAreas);
|
||||
total.add(support.supportLayers[std::max(0, layer_nr)].skin);
|
||||
}
|
||||
total.add(primeTower.ground_poly);
|
||||
}
|
||||
@@ -149,7 +197,7 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
|
||||
|
||||
Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts) const
|
||||
{
|
||||
if (layer_nr < 0)
|
||||
if (layer_nr < 0 && layer_nr < -Raft::getFillerLayerCount(*this))
|
||||
{ // when processing raft
|
||||
if (include_helper_parts)
|
||||
{
|
||||
@@ -163,21 +211,24 @@ Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool inc
|
||||
else
|
||||
{
|
||||
Polygons total;
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
layer.getSecondOrInnermostWalls(total);
|
||||
if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make getSetting const? make settings.setting_values mapping mutable??
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
layer.getSecondOrInnermostWalls(total);
|
||||
if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make getSetting const? make settings.setting_values mapping mutable??
|
||||
{
|
||||
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (include_helper_parts)
|
||||
{
|
||||
if (support.generated)
|
||||
{
|
||||
total.add(support.supportLayers[layer_nr].supportAreas);
|
||||
total.add(support.supportLayers[layer_nr].roofs);
|
||||
total.add(support.supportLayers[std::max(0, layer_nr)].supportAreas);
|
||||
total.add(support.supportLayers[std::max(0, layer_nr)].skin);
|
||||
}
|
||||
total.add(primeTower.ground_poly);
|
||||
}
|
||||
@@ -186,77 +237,25 @@ Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool inc
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::vector<bool> SliceDataStorage::getExtrudersUsed(int layer_nr)
|
||||
std::vector<bool> SliceDataStorage::getExtrudersUsed() const
|
||||
{
|
||||
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
if (layer_nr < 0)
|
||||
|
||||
if (getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true; // raft
|
||||
}
|
||||
else
|
||||
{
|
||||
if (layer_nr == 0)
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt[extr_nr].size() > 0)
|
||||
if (skirt_brim[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ooze shield, draft shield
|
||||
|
||||
// support
|
||||
if (support.supportLayers[layer_nr].supportAreas.size() > 0)
|
||||
{
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret[getSettingAsIndex("support_infill_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
if (support.supportLayers[layer_nr].roofs.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_roof_extruder_nr")] = true;
|
||||
}
|
||||
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
int extr_nr = mesh.getSettingAsIndex("extruder_nr");
|
||||
if (layer.parts.size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
{
|
||||
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ooze shield, draft shield ..?
|
||||
@@ -265,12 +264,106 @@ std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
// support is presupposed to be present...
|
||||
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
|
||||
ret[getSettingAsIndex("support_infill_extruder_nr")] = true;
|
||||
ret[getSettingAsIndex("support_roof_extruder_nr")] = true;
|
||||
ret[getSettingAsIndex("support_interface_extruder_nr")] = true;
|
||||
|
||||
// all meshes are presupposed to actually have content
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh.getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<bool> SliceDataStorage::getExtrudersUsed(int layer_nr) const
|
||||
{
|
||||
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
|
||||
bool include_adhesion = true;
|
||||
bool include_helper_parts = true;
|
||||
bool include_models = true;
|
||||
if (layer_nr < 0)
|
||||
{
|
||||
include_models = false;
|
||||
if (layer_nr < -Raft::getFillerLayerCount(*this))
|
||||
{
|
||||
include_helper_parts = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
layer_nr = 0; // because the helper parts are copied from the initial layer in the filler layer
|
||||
include_adhesion = false;
|
||||
}
|
||||
}
|
||||
else if (layer_nr > 0 || getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT)
|
||||
{ // only include adhesion only for layers where platform adhesion actually occurs
|
||||
// i.e. layers < 0 are for raft, layer 0 is for brim/skirt
|
||||
include_adhesion = false;
|
||||
}
|
||||
if (include_adhesion)
|
||||
{
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt_brim[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ooze shield, draft shield ..?
|
||||
|
||||
if (include_helper_parts)
|
||||
{
|
||||
// support
|
||||
if (layer_nr < int(support.supportLayers.size()))
|
||||
{
|
||||
const SupportLayer& support_layer = support.supportLayers[layer_nr];
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
if (support_layer.supportAreas.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (support_layer.supportAreas.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_infill_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
if (support_layer.skin.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_interface_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (include_models)
|
||||
{
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
if (layer_nr >= int(mesh.layers.size()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
if (layer.parts.size() > 0)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
+105
-48
@@ -3,6 +3,7 @@
|
||||
#define SLICE_DATA_STORAGE_H
|
||||
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/optional.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/NoCopy.h"
|
||||
#include "utils/AABB.h"
|
||||
@@ -10,6 +11,7 @@
|
||||
#include "gcodePlanner.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "PrimeTower.h"
|
||||
#include "GCodePathConfig.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -17,11 +19,11 @@ namespace cura
|
||||
* A SkinPart is a connected area designated as top and/or bottom skin.
|
||||
* Surrounding each non-bridged skin area with an outline may result in better top skins.
|
||||
* It's filled during FffProcessor.processSliceData(.) and used in FffProcessor.writeGCode(.) to generate the final gcode.
|
||||
*/
|
||||
*/
|
||||
class SkinPart
|
||||
{
|
||||
public:
|
||||
PolygonsPart outline; //!< The skinOutline is the area which needs to be 100% filled to generate a proper top&bottom filling. It's filled by the "skin" module.
|
||||
PolygonsPart outline; //!< The skinOutline is the area which needs to be 100% filled to generate a proper top&bottom filling. It's filled by the "skin" module.
|
||||
std::vector<Polygons> insets; //!< The skin can have perimeters so that the skin lines always start at a perimeter instead of in the middle of an infill cell.
|
||||
};
|
||||
/*!
|
||||
@@ -37,8 +39,41 @@ public:
|
||||
Polygons print_outline; //!< An approximation to the outline of what's actually printed, based on the outer wall. Too small parts will be omitted compared to the outline.
|
||||
std::vector<Polygons> insets; //!< The insets are generated with: an offset of (index * line_width + line_width/2) compared to the outline. The insets are also known as perimeters, and printed inside out.
|
||||
std::vector<SkinPart> skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets.
|
||||
Polygons infill_area; //!< The areas which need to be filled with sparse (0-99%) infill. Like SliceLayerPart::outline, this class member is not used to actually determine the feature area, but is used to compute the infill_area_per_combine and the inside comb boundary.
|
||||
std::vector<Polygons> infill_area_per_combine; //!< The areas which need to be filled with sparse (0-99%) infill for different thicknesses. The infill_area is an array to support thicker layers of sparse infill. infill_area[n] is infill_area of (n+1) layers thick.
|
||||
/*!
|
||||
* The areas inside of the mesh.
|
||||
* Like SliceLayerPart::outline, this class member is not used to actually determine the feature area,
|
||||
* but is used to compute the inside comb boundary.
|
||||
*/
|
||||
Polygons infill_area;
|
||||
|
||||
/*!
|
||||
* The areas which need to be filled with sparse (0-99%) infill.
|
||||
* Like SliceLayerPart::outline, this class member is not used to actually determine the feature area,
|
||||
* but is used to compute the infill_area_per_combine_per_density.
|
||||
*
|
||||
* These polygons may be cleared once they have been used to generate gradual infill and/or infill combine.
|
||||
*
|
||||
* If these polygons are not initialized, simply use the normal infill area.
|
||||
*/
|
||||
std::optional<Polygons> infill_area_own;
|
||||
|
||||
/*!
|
||||
* The areas which need to be filled with sparse (0-99%) infill for different thicknesses.
|
||||
* The infill_area is an array to support thicker layers of sparse infill and areas of different infill density.
|
||||
* infill_area[x][n] is infill_area of (n+1) layers thick.
|
||||
*
|
||||
* infill_area[0] corresponds to the most dense infill area.
|
||||
* infill_area[x] will lie fully inside infill_area[x+1].
|
||||
* infill_area_per_combine_per_density.back()[0] == part.infill area initially
|
||||
*/
|
||||
std::vector<std::vector<Polygons>> infill_area_per_combine_per_density;
|
||||
|
||||
/*!
|
||||
* Get the infill_area_own (or when it's not instantiated: the normal infill_area)
|
||||
* \see SliceLayerPart::infill_area_own
|
||||
* \return the own infill area
|
||||
*/
|
||||
Polygons& getOwnInfillArea();
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -52,7 +87,7 @@ public:
|
||||
int printZ; //!< The height at which this layer needs to be printed. Can differ from sliceZ due to the raft.
|
||||
std::vector<SliceLayerPart> parts; //!< An array of LayerParts which contain the actual data. The parts are printed one at a time to minimize travel outside of the 3D model.
|
||||
Polygons openPolyLines; //!< A list of lines which were never hooked up into a 2D polygon. (Currently unused in normal operation)
|
||||
|
||||
|
||||
/*!
|
||||
* Get the all outlines of all layer parts in this layer.
|
||||
*
|
||||
@@ -60,7 +95,7 @@ public:
|
||||
* \return A collection of all the outline polygons
|
||||
*/
|
||||
Polygons getOutlines(bool external_polys_only = false) const;
|
||||
|
||||
|
||||
/*!
|
||||
* Get the all outlines of all layer parts in this layer.
|
||||
* Add those polygons to @p result.
|
||||
@@ -69,13 +104,13 @@ public:
|
||||
* \param result The result: a collection of all the outline polygons
|
||||
*/
|
||||
void getOutlines(Polygons& result, bool external_polys_only = false) const;
|
||||
|
||||
|
||||
/*!
|
||||
* Collects the second wall of every part, or the outer wall if it has no second, or the outline, if it has no outer wall.
|
||||
* \return The collection of all polygons thus obtained
|
||||
*/
|
||||
Polygons getSecondOrInnermostWalls() const;
|
||||
|
||||
|
||||
/*!
|
||||
* Collects the second wall of every part, or the outer wall if it has no second, or the outline, if it has no outer wall.
|
||||
* Add those polygons to @p result.
|
||||
@@ -89,16 +124,18 @@ class SupportLayer
|
||||
{
|
||||
public:
|
||||
Polygons supportAreas; //!< normal support areas
|
||||
Polygons roofs; //!< the support areas which are to be printed as denser roofs. Note that the roof areas and support areas are mutually exclusive.
|
||||
Polygons skin; //!< the support areas which are to be printed as denser roofs and/or bottoms. Note that the roof/bottom areas and support areas should be mutually exclusive.
|
||||
Polygons support_mesh; //!< Areas from support meshes
|
||||
Polygons anti_overhang; //!< Areas where no overhang should be detected.
|
||||
};
|
||||
|
||||
class SupportStorage
|
||||
{
|
||||
public:
|
||||
bool generated; //!< whether generateSupportGrid(.) has completed (successfully)
|
||||
|
||||
|
||||
int layer_nr_max_filled_layer; //!< the layer number of the uppermost layer with content
|
||||
|
||||
|
||||
std::vector<SupportLayer> supportLayers;
|
||||
|
||||
SupportStorage() : generated(false), layer_nr_max_filled_layer(-1) { }
|
||||
@@ -111,20 +148,24 @@ class SliceMeshStorage : public SettingsMessenger // passes on settings from a M
|
||||
public:
|
||||
std::vector<SliceLayer> layers;
|
||||
|
||||
int layer_nr_max_filled_layer; //!< the layer number of the uppermost layer with content
|
||||
|
||||
RetractionConfig retraction_config;
|
||||
int layer_nr_max_filled_layer; //!< the layer number of the uppermost layer with content (modified while infill meshes are processed)
|
||||
|
||||
GCodePathConfig inset0_config;
|
||||
GCodePathConfig insetX_config;
|
||||
GCodePathConfig skin_config;
|
||||
std::vector<GCodePathConfig> infill_config;
|
||||
|
||||
SliceMeshStorage(SettingsBaseVirtual* settings)
|
||||
: SettingsMessenger(settings), layer_nr_max_filled_layer(0), inset0_config(&retraction_config, PrintFeatureType::OuterWall), insetX_config(&retraction_config, PrintFeatureType::InnerWall), skin_config(&retraction_config, PrintFeatureType::Skin)
|
||||
|
||||
SliceMeshStorage(SettingsBaseVirtual* settings, unsigned int slice_layer_count)
|
||||
: SettingsMessenger(settings)
|
||||
, layer_nr_max_filled_layer(0)
|
||||
, inset0_config(PrintFeatureType::OuterWall)
|
||||
, insetX_config(PrintFeatureType::InnerWall)
|
||||
, skin_config(PrintFeatureType::Skin)
|
||||
{
|
||||
layers.resize(slice_layer_count);
|
||||
infill_config.reserve(MAX_INFILL_COMBINE);
|
||||
for(int n=0; n<MAX_INFILL_COMBINE; n++)
|
||||
infill_config.emplace_back(&retraction_config, PrintFeatureType::Infill);
|
||||
infill_config.emplace_back(PrintFeatureType::Infill);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,48 +173,55 @@ class SliceDataStorage : public SettingsMessenger, NoCopy
|
||||
{
|
||||
public:
|
||||
MeshGroup* meshgroup; // needed to pass on the per extruder settings.. (TODO: put this somewhere else? Put the per object settings here directly, or a pointer only to the per object settings.)
|
||||
|
||||
|
||||
unsigned int print_layer_count; //!< The total number of layers (except the raft and filler layers)
|
||||
|
||||
Point3 model_size, model_min, model_max;
|
||||
std::vector<SliceMeshStorage> meshes;
|
||||
|
||||
std::vector<RetractionConfig> retraction_config_per_extruder; //!< used for support, skirt, etc.
|
||||
|
||||
std::vector<RetractionConfig> retraction_config_per_extruder; //!< Retraction config per extruder.
|
||||
std::vector<RetractionConfig> extruder_switch_retraction_config_per_extruder; //!< Retraction config per extruder for when performing an extruder switch
|
||||
|
||||
std::vector<GCodePathConfig> travel_config_per_extruder; //!< The config used for travel moves (only speed is set!)
|
||||
|
||||
std::vector<GCodePathConfig> skirt_config; //!< config for skirt per extruder
|
||||
std::vector<GCodePathConfig> skirt_brim_config; //!< Configuration for skirt and brim per extruder.
|
||||
std::vector<CoastingConfig> coasting_config; //!< coasting config per extruder
|
||||
|
||||
|
||||
GCodePathConfig raft_base_config;
|
||||
GCodePathConfig raft_interface_config;
|
||||
GCodePathConfig raft_surface_config;
|
||||
|
||||
|
||||
GCodePathConfig support_config;
|
||||
GCodePathConfig support_roof_config;
|
||||
|
||||
GCodePathConfig support_skin_config; //!< The config to use to print the dense roofs and bottoms of support
|
||||
|
||||
SupportStorage support;
|
||||
|
||||
Polygons skirt[MAX_EXTRUDERS]; //!< Skirt polygons per extruder, ordered from inner to outer polygons
|
||||
|
||||
Polygons skirt_brim[MAX_EXTRUDERS]; //!< Skirt and brim polygons per extruder, ordered from inner to outer polygons.
|
||||
Polygons raftOutline; //Storage for the outline of the raft. Will be filled with lines when the GCode is generated.
|
||||
|
||||
int max_object_height_second_to_last_extruder; //!< Used in multi-extrusion: the layer number beyond which all models are printed with the same extruder
|
||||
|
||||
int max_print_height_second_to_last_extruder; //!< Used in multi-extrusion: the layer number beyond which all models are printed with the same extruder
|
||||
std::vector<int> max_print_height_per_extruder; //!< For each extruder the highest layer number at which it is used.
|
||||
std::vector<size_t> max_print_height_order; //!< Ordered indices into max_print_height_per_extruder: back() will return the extruder number with the highest print height.
|
||||
|
||||
PrimeTower primeTower;
|
||||
|
||||
|
||||
std::vector<Polygons> oozeShield; //oozeShield per layer
|
||||
Polygons draft_protection_shield; //!< The polygons for a heightened skirt which protects from warping by gusts of wind and acts as a heated chamber.
|
||||
Point wipePoint;
|
||||
|
||||
|
||||
/*!
|
||||
* Construct the initial retraction_config_per_extruder
|
||||
*/
|
||||
std::vector<RetractionConfig> initializeRetractionConfigs();
|
||||
|
||||
/*!
|
||||
* Construct the initial travel_config_per_extruder
|
||||
*/
|
||||
std::vector<GCodePathConfig> initializeTravelConfigs();
|
||||
|
||||
/*!
|
||||
* Construct the initial skirt_config s for each extruder
|
||||
* Construct the initial skirt & brim configurations for each extruder.
|
||||
*/
|
||||
std::vector<GCodePathConfig> initializeSkirtConfigs();
|
||||
std::vector<GCodePathConfig> initializeSkirtBrimConfigs();
|
||||
|
||||
/*!
|
||||
* \brief Creates a new slice data storage that stores the slice data of the
|
||||
@@ -186,11 +234,20 @@ public:
|
||||
* \param meshgroup The mesh group to load into this data storage, if any.
|
||||
*/
|
||||
SliceDataStorage(MeshGroup* meshgroup);
|
||||
|
||||
|
||||
~SliceDataStorage()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Check in which part \p location lies, if in any.
|
||||
*
|
||||
* \param layer_nr The layer for which to check
|
||||
* \param location The location to check
|
||||
* \return The part in which \p location lie, or nullptr, if it's outside all parts.
|
||||
*/
|
||||
SliceLayerPart* getPartInside(int layer_nr, Point location);
|
||||
|
||||
/*!
|
||||
* Get all outlines within a given layer.
|
||||
*
|
||||
@@ -199,7 +256,7 @@ public:
|
||||
* \param external_polys_only whether to disregard all hole polygons
|
||||
*/
|
||||
Polygons getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only = false) const;
|
||||
|
||||
|
||||
/*!
|
||||
* Collects the second wall of every part, or the outer wall if it has no second, or the outline, if it has no outer wall.
|
||||
*
|
||||
@@ -209,21 +266,21 @@ public:
|
||||
* \param include_helper_parts whether to include support and prime tower
|
||||
*/
|
||||
Polygons getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts) const;
|
||||
|
||||
/*!
|
||||
* Get the extruder numbers of all extruders used in a given layer.
|
||||
*
|
||||
* \param layer_nr the index of the layer for which to get the extruders used (negative layer numbers indicate the raft)
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed(int layer_nr);
|
||||
|
||||
|
||||
/*!
|
||||
* Get the extruders used.
|
||||
*
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed();
|
||||
std::vector<bool> getExtrudersUsed() const;
|
||||
|
||||
/*!
|
||||
* Get the extruders used on a particular layer.
|
||||
*
|
||||
* \param layer_nr the layer for which to check
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed(int layer_nr) const;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+523
-144
@@ -5,13 +5,13 @@
|
||||
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/SparsePointGridInclusive.h"
|
||||
|
||||
#include "slicer.h"
|
||||
#include "debug.h" // TODO remove
|
||||
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
int largest_neglected_gap_first_phase = MM2INT(0.01); //!< distance between two line segments regarded as connected
|
||||
int largest_neglected_gap_second_phase = MM2INT(0.02); //!< distance between two line segments regarded as connected
|
||||
int max_stitch1 = MM2INT(10.0); //!< maximal distance stitched between open polylines to form polygons
|
||||
@@ -31,17 +31,17 @@ void SlicerLayer::makeBasicPolygonLoops(const Mesh* mesh, Polygons& open_polylin
|
||||
|
||||
void SlicerLayer::makeBasicPolygonLoop(const Mesh* mesh, Polygons& open_polylines, unsigned int start_segment_idx)
|
||||
{
|
||||
|
||||
|
||||
Polygon poly;
|
||||
poly.add(segments[start_segment_idx].start);
|
||||
|
||||
|
||||
for (int segment_idx = start_segment_idx; segment_idx != -1; )
|
||||
{
|
||||
SlicerSegment& segment = segments[segment_idx];
|
||||
poly.add(segment.end);
|
||||
segment.addedToPolygon = true;
|
||||
segment_idx = getNextSegmentIdx(mesh, segment, start_segment_idx);
|
||||
if (segment_idx == static_cast<int>(start_segment_idx))
|
||||
if (segment_idx == static_cast<int>(start_segment_idx))
|
||||
{ // polyon is closed
|
||||
polygons.add(poly);
|
||||
return;
|
||||
@@ -51,163 +51,498 @@ void SlicerLayer::makeBasicPolygonLoop(const Mesh* mesh, Polygons& open_polyline
|
||||
open_polylines.add(poly);
|
||||
}
|
||||
|
||||
int SlicerLayer::tryFaceNextSegmentIdx(const Mesh* mesh, const SlicerSegment& segment, int face_idx, unsigned int start_segment_idx) const
|
||||
{
|
||||
decltype(face_idx_to_segment_idx.begin()) it;
|
||||
auto it_end = face_idx_to_segment_idx.end();
|
||||
it = face_idx_to_segment_idx.find(face_idx);
|
||||
if (it != it_end)
|
||||
{
|
||||
int segment_idx = (*it).second;
|
||||
Point p1 = segments[segment_idx].start;
|
||||
Point diff = segment.end - p1;
|
||||
if (shorterThen(diff, largest_neglected_gap_first_phase))
|
||||
{
|
||||
if (segment_idx == static_cast<int>(start_segment_idx))
|
||||
{
|
||||
return start_segment_idx;
|
||||
}
|
||||
if (segments[segment_idx].addedToPolygon)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return segment_idx;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int SlicerLayer::getNextSegmentIdx(const Mesh* mesh, const SlicerSegment& segment, unsigned int start_segment_idx)
|
||||
{
|
||||
int next_segment_idx = -1;
|
||||
const MeshFace& face = mesh->faces[segment.faceIndex];
|
||||
for (unsigned int face_edge_idx = 0; face_edge_idx < 3; face_edge_idx++)
|
||||
{ // check segments in connected faces
|
||||
decltype(face_idx_to_segment_idx.begin()) it;
|
||||
if (face.connected_face_index[face_edge_idx] > -1 && (it = face_idx_to_segment_idx.find(face.connected_face_index[face_edge_idx])) != face_idx_to_segment_idx.end())
|
||||
|
||||
bool segment_ended_at_edge = segment.endVertex == nullptr;
|
||||
if (segment_ended_at_edge)
|
||||
{
|
||||
int face_to_try = segment.endOtherFaceIdx;
|
||||
if (face_to_try == -1)
|
||||
{
|
||||
int segment_idx = (*it).second;
|
||||
Point p1 = segments[segment_idx].start;
|
||||
Point diff = segment.end - p1;
|
||||
if (shorterThen(diff, largest_neglected_gap_first_phase))
|
||||
return -1;
|
||||
}
|
||||
return tryFaceNextSegmentIdx(mesh,segment,face_to_try,start_segment_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// segment ended at vertex
|
||||
|
||||
const std::vector<uint32_t> &faces_to_try = segment.endVertex->connected_faces;
|
||||
for (int face_to_try : faces_to_try)
|
||||
{
|
||||
int result_segment_idx =
|
||||
tryFaceNextSegmentIdx(mesh,segment,face_to_try,start_segment_idx);
|
||||
if (result_segment_idx == static_cast<int>(start_segment_idx))
|
||||
{
|
||||
if (segment_idx == static_cast<int>(start_segment_idx))
|
||||
{
|
||||
return start_segment_idx;
|
||||
}
|
||||
if (segments[segment_idx].addedToPolygon)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
next_segment_idx = segment_idx; // not immediately returned since we might still encounter the start_segment_idx
|
||||
return start_segment_idx;
|
||||
}
|
||||
else if (result_segment_idx != -1)
|
||||
{
|
||||
// not immediately returned since we might still encounter the start_segment_idx
|
||||
next_segment_idx = result_segment_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next_segment_idx;
|
||||
}
|
||||
|
||||
void SlicerLayer::connectOpenPolylines(Polygons& open_polylines)
|
||||
{
|
||||
// TODO use some space partitioning data structure to make this run faster than O(n^2)
|
||||
for(unsigned int open_polyline_idx = 0; open_polyline_idx < open_polylines.size(); open_polyline_idx++)
|
||||
{
|
||||
PolygonRef open_polyline = open_polylines[open_polyline_idx];
|
||||
|
||||
if (open_polyline.size() < 1) continue;
|
||||
for(unsigned int open_polyline_other_idx = 0; open_polyline_other_idx < open_polylines.size(); open_polyline_other_idx++)
|
||||
{
|
||||
PolygonRef open_polyline_other = open_polylines[open_polyline_other_idx];
|
||||
|
||||
if (open_polyline_other.size() < 1) continue;
|
||||
|
||||
Point diff = open_polyline.back() - open_polyline_other[0];
|
||||
bool allow_reverse = false;
|
||||
// Search a bit fewer cells but at cost of covering more area.
|
||||
// Since acceptance area is small to start with, the extra is unlikely to hurt much.
|
||||
coord_t cell_size = largest_neglected_gap_first_phase * 2;
|
||||
connectOpenPolylinesImpl(open_polylines, largest_neglected_gap_second_phase, cell_size, allow_reverse);
|
||||
}
|
||||
|
||||
if (shorterThen(diff, largest_neglected_gap_second_phase))
|
||||
void SlicerLayer::stitch(Polygons& open_polylines)
|
||||
{
|
||||
bool allow_reverse = true;
|
||||
connectOpenPolylinesImpl(open_polylines, max_stitch1, max_stitch1, allow_reverse);
|
||||
}
|
||||
|
||||
const SlicerLayer::Terminus SlicerLayer::Terminus::INVALID_TERMINUS{~static_cast<Index>(0U)};
|
||||
|
||||
bool SlicerLayer::PossibleStitch::operator<(const PossibleStitch& other) const
|
||||
{
|
||||
// better if lower distance
|
||||
if (dist2 > other.dist2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (dist2 < other.dist2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// better if in order instead of reversed
|
||||
if (!in_order() && other.in_order())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// better if lower Terminus::Index for terminus_0
|
||||
// This just defines a more total order and isn't strictly necessary.
|
||||
if (terminus_0.asIndex() > other.terminus_0.asIndex())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (terminus_0.asIndex() < other.terminus_0.asIndex())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// better if lower Terminus::Index for terminus_1
|
||||
// This just defines a more total order and isn't strictly necessary.
|
||||
if (terminus_1.asIndex() > other.terminus_1.asIndex())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (terminus_1.asIndex() < other.terminus_1.asIndex())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The stitches have equal goodness
|
||||
return false;
|
||||
}
|
||||
|
||||
std::priority_queue<SlicerLayer::PossibleStitch>
|
||||
SlicerLayer::findPossibleStitches(
|
||||
const Polygons& open_polylines,
|
||||
coord_t max_dist, coord_t cell_size,
|
||||
bool allow_reverse) const
|
||||
{
|
||||
std::priority_queue<PossibleStitch> stitch_queue;
|
||||
|
||||
// maximum distance squared
|
||||
int64_t max_dist2 = max_dist * max_dist;
|
||||
|
||||
// Represents a terminal point of a polyline in open_polylines.
|
||||
struct StitchGridVal
|
||||
{
|
||||
unsigned int polyline_idx;
|
||||
// Depending on the SparsePointGridInclusive, either the start point or the
|
||||
// end point of the polyline
|
||||
Point polyline_term_pt;
|
||||
};
|
||||
|
||||
struct StitchGridValLocator
|
||||
{
|
||||
Point operator()(const StitchGridVal& val) const
|
||||
{
|
||||
return val.polyline_term_pt;
|
||||
}
|
||||
};
|
||||
|
||||
// Used to find nearby end points within a fixed maximum radius
|
||||
SparsePointGrid<StitchGridVal,StitchGridValLocator> grid_ends(cell_size);
|
||||
// Used to find nearby start points within a fixed maximum radius
|
||||
SparsePointGrid<StitchGridVal,StitchGridValLocator> grid_starts(cell_size);
|
||||
|
||||
// populate grids
|
||||
|
||||
// Inserts the ends of all polylines into the grid (does not
|
||||
// insert the starts of the polylines).
|
||||
for(unsigned int polyline_0_idx = 0; polyline_0_idx < open_polylines.size(); polyline_0_idx++)
|
||||
{
|
||||
const PolygonRef polyline_0 = open_polylines[polyline_0_idx];
|
||||
|
||||
if (polyline_0.size() < 1) continue;
|
||||
|
||||
StitchGridVal grid_val;
|
||||
grid_val.polyline_idx = polyline_0_idx;
|
||||
grid_val.polyline_term_pt = polyline_0.back();
|
||||
grid_ends.insert(grid_val);
|
||||
}
|
||||
|
||||
// Inserts the start of all polylines into the grid.
|
||||
if (allow_reverse)
|
||||
{
|
||||
for(unsigned int polyline_0_idx = 0; polyline_0_idx < open_polylines.size(); polyline_0_idx++)
|
||||
{
|
||||
const PolygonRef polyline_0 = open_polylines[polyline_0_idx];
|
||||
|
||||
if (polyline_0.size() < 1) continue;
|
||||
|
||||
StitchGridVal grid_val;
|
||||
grid_val.polyline_idx = polyline_0_idx;
|
||||
grid_val.polyline_term_pt = polyline_0[0];
|
||||
grid_starts.insert(grid_val);
|
||||
}
|
||||
}
|
||||
|
||||
// search for nearby end points
|
||||
for(unsigned int polyline_1_idx = 0; polyline_1_idx < open_polylines.size(); polyline_1_idx++)
|
||||
{
|
||||
const PolygonRef polyline_1 = open_polylines[polyline_1_idx];
|
||||
|
||||
if (polyline_1.size() < 1) continue;
|
||||
|
||||
std::vector<StitchGridVal> nearby_ends;
|
||||
|
||||
// Check for stitches that append polyline_1 onto polyline_0
|
||||
// in natural order. These are stitches that use the end of
|
||||
// polyline_0 and the start of polyline_1.
|
||||
nearby_ends = grid_ends.getNearby(polyline_1[0], max_dist);
|
||||
for (const auto& nearby_end : nearby_ends)
|
||||
{
|
||||
Point diff = nearby_end.polyline_term_pt - polyline_1[0];
|
||||
int64_t dist2 = vSize2(diff);
|
||||
if (dist2 < max_dist2)
|
||||
{
|
||||
if (open_polyline_idx == open_polyline_other_idx)
|
||||
PossibleStitch poss_stitch;
|
||||
poss_stitch.dist2 = dist2;
|
||||
poss_stitch.terminus_0 = Terminus{nearby_end.polyline_idx, true};
|
||||
poss_stitch.terminus_1 = Terminus{polyline_1_idx, false};
|
||||
stitch_queue.push(poss_stitch);
|
||||
}
|
||||
}
|
||||
|
||||
if (allow_reverse)
|
||||
{
|
||||
// Check for stitches that append polyline_1 onto polyline_0
|
||||
// by reversing order of polyline_1. These are stitches that
|
||||
// use the end of polyline_0 and the end of polyline_1.
|
||||
nearby_ends = grid_ends.getNearby(polyline_1.back(), max_dist);
|
||||
for (const auto& nearby_end : nearby_ends)
|
||||
{
|
||||
// Disallow stitching with self with same end point
|
||||
if (nearby_end.polyline_idx == polyline_1_idx)
|
||||
{
|
||||
polygons.add(open_polyline);
|
||||
open_polyline.clear();
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
Point diff = nearby_end.polyline_term_pt - polyline_1.back();
|
||||
int64_t dist2 = vSize2(diff);
|
||||
if (dist2 < max_dist2)
|
||||
{
|
||||
for (unsigned int line_idx = 0; line_idx < open_polyline_other.size(); line_idx++)
|
||||
{
|
||||
open_polyline.add(open_polyline_other[line_idx]);
|
||||
}
|
||||
open_polyline_other.clear();
|
||||
PossibleStitch poss_stitch;
|
||||
poss_stitch.dist2 = dist2;
|
||||
poss_stitch.terminus_0 = Terminus{nearby_end.polyline_idx, true};
|
||||
poss_stitch.terminus_1 = Terminus{polyline_1_idx, true};
|
||||
stitch_queue.push(poss_stitch);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for stitches that append polyline_1 onto polyline_0
|
||||
// by reversing order of polyline_0. These are stitches that
|
||||
// use the start of polyline_0 and the start of polyline_1.
|
||||
std::vector<StitchGridVal> nearby_starts =
|
||||
grid_starts.getNearby(polyline_1[0], max_dist);
|
||||
for (const auto& nearby_start : nearby_starts)
|
||||
{
|
||||
// Disallow stitching with self with same end point
|
||||
if (nearby_start.polyline_idx == polyline_1_idx)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Point diff = nearby_start.polyline_term_pt - polyline_1[0];
|
||||
int64_t dist2 = vSize2(diff);
|
||||
if (dist2 < max_dist2)
|
||||
{
|
||||
PossibleStitch poss_stitch;
|
||||
poss_stitch.dist2 = dist2;
|
||||
poss_stitch.terminus_0 = Terminus{nearby_start.polyline_idx, false};
|
||||
poss_stitch.terminus_1 = Terminus{polyline_1_idx, false};
|
||||
stitch_queue.push(poss_stitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stitch_queue;
|
||||
}
|
||||
|
||||
void SlicerLayer::stitch(Polygons& open_polylines)
|
||||
{ // TODO This is an inefficient implementation which can run in O(n^3) time.
|
||||
// below code closes smallest gaps first
|
||||
while(1)
|
||||
void SlicerLayer::planPolylineStitch(
|
||||
const Polygons& open_polylines,
|
||||
Terminus& terminus_0, Terminus& terminus_1, bool reverse[2]) const
|
||||
{
|
||||
size_t polyline_0_idx = terminus_0.getPolylineIdx();
|
||||
size_t polyline_1_idx = terminus_1.getPolylineIdx();
|
||||
bool back_0 = terminus_0.isEnd();
|
||||
bool back_1 = terminus_1.isEnd();
|
||||
reverse[0] = false;
|
||||
reverse[1] = false;
|
||||
if (back_0)
|
||||
{
|
||||
int64_t best_dist2 = max_stitch1 * max_stitch1;
|
||||
unsigned int best_polyline_1_idx = -1;
|
||||
unsigned int best_polyline_2_idx = -1;
|
||||
bool reversed = false;
|
||||
for(unsigned int polyline_1_idx = 0; polyline_1_idx < open_polylines.size(); polyline_1_idx++)
|
||||
if (back_1)
|
||||
{
|
||||
PolygonRef polyline_1 = open_polylines[polyline_1_idx];
|
||||
|
||||
if (polyline_1.size() < 1) continue;
|
||||
for(unsigned int polyline_2_idx = 0; polyline_2_idx < open_polylines.size(); polyline_2_idx++)
|
||||
// back of both polylines
|
||||
// we can reverse either one and then append onto the other
|
||||
// reverse the smaller polyline
|
||||
if (open_polylines[polyline_0_idx].size() <
|
||||
open_polylines[polyline_1_idx].size())
|
||||
{
|
||||
PolygonRef polyline_2 = open_polylines[polyline_2_idx];
|
||||
|
||||
if (polyline_2.size() < 1) continue;
|
||||
|
||||
Point diff = polyline_1.back() - polyline_2[0];
|
||||
int64_t dist2 = vSize2(diff);
|
||||
if (dist2 < best_dist2)
|
||||
{
|
||||
best_dist2 = dist2;
|
||||
best_polyline_1_idx = polyline_1_idx;
|
||||
best_polyline_2_idx = polyline_2_idx;
|
||||
reversed = false;
|
||||
}
|
||||
|
||||
if (polyline_1_idx != polyline_2_idx)
|
||||
{
|
||||
Point diff = polyline_1.back() - polyline_2.back();
|
||||
int64_t dist2 = vSize2(diff);
|
||||
if (dist2 < best_dist2)
|
||||
{
|
||||
best_dist2 = dist2;
|
||||
best_polyline_1_idx = polyline_1_idx;
|
||||
best_polyline_2_idx = polyline_2_idx;
|
||||
reversed = true;
|
||||
}
|
||||
}
|
||||
std::swap(terminus_0,terminus_1);
|
||||
}
|
||||
reverse[1] = true;
|
||||
} else {
|
||||
// back of 0, front of 1
|
||||
// already in order, nothing to do
|
||||
}
|
||||
|
||||
if (best_dist2 >= max_stitch1 * max_stitch1)
|
||||
break; // this code is reached if there was nothing to stitch within the distance limits
|
||||
|
||||
PolygonRef polyline_1 = open_polylines[best_polyline_1_idx];
|
||||
PolygonRef polyline_2 = open_polylines[best_polyline_2_idx];
|
||||
|
||||
if (best_polyline_1_idx == best_polyline_2_idx)
|
||||
{ // connect last piece of 'circle'
|
||||
polygons.add(polyline_1);
|
||||
polyline_1.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (back_1)
|
||||
{
|
||||
// front of 0, back of 1
|
||||
// in order if we swap 0 and 1
|
||||
std::swap(terminus_0,terminus_1);
|
||||
}
|
||||
else
|
||||
{ // connect two polylines
|
||||
if (reversed)
|
||||
{
|
||||
// front of both polylines
|
||||
// we can reverse either one and then prepend to the other
|
||||
// reverse the smaller polyline
|
||||
if (open_polylines[polyline_0_idx].size() >
|
||||
open_polylines[polyline_1_idx].size())
|
||||
{
|
||||
if (polyline_1.size() > polyline_2.size()) // decide which polygon to copy into the other
|
||||
{
|
||||
for(int poly_idx = polyline_2.size()-1; poly_idx >= 0; poly_idx--)
|
||||
polyline_1.add(polyline_2[poly_idx]);
|
||||
polyline_2.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int poly_idx = polyline_1.size()-1; poly_idx >= 0; poly_idx--)
|
||||
polyline_2.add(polyline_1[poly_idx]);
|
||||
polyline_1.clear();
|
||||
}
|
||||
// note that either way we end up with the end of former polyline_1 next to the start of former polyline_2
|
||||
}
|
||||
else
|
||||
{
|
||||
for(Point& p : polyline_2)
|
||||
polyline_1.add(p);
|
||||
polyline_2.clear();
|
||||
std::swap(terminus_0,terminus_1);
|
||||
}
|
||||
reverse[0] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SlicerLayer::joinPolylines(PolygonRef& polyline_0, PolygonRef& polyline_1,
|
||||
const bool reverse[2]) const
|
||||
{
|
||||
if (reverse[0])
|
||||
{
|
||||
// reverse polyline_0
|
||||
size_t size_0 = polyline_0.size();
|
||||
for (size_t idx = 0U; idx != size_0/2; ++idx)
|
||||
{
|
||||
std::swap(polyline_0[idx], polyline_0[size_0-1-idx]);
|
||||
}
|
||||
}
|
||||
if (reverse[1])
|
||||
{
|
||||
// reverse polyline_1 by adding in reverse order
|
||||
for(int poly_idx = polyline_1.size() - 1; poly_idx >= 0; poly_idx--)
|
||||
polyline_0.add(polyline_1[poly_idx]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// append polyline_1 onto polyline_0
|
||||
for(Point& p : polyline_1)
|
||||
polyline_0.add(p);
|
||||
}
|
||||
polyline_1.clear();
|
||||
}
|
||||
|
||||
SlicerLayer::TerminusTrackingMap::TerminusTrackingMap(Terminus::Index end_idx) :
|
||||
m_terminus_old_to_cur_map(end_idx)
|
||||
{
|
||||
// Initialize map to everything points to itself since nothing has moved yet.
|
||||
for (size_t idx = 0U; idx != end_idx; ++idx)
|
||||
{
|
||||
m_terminus_old_to_cur_map[idx] = Terminus{idx};
|
||||
}
|
||||
m_terminus_cur_to_old_map = m_terminus_old_to_cur_map;
|
||||
}
|
||||
|
||||
void SlicerLayer::TerminusTrackingMap::updateMap(
|
||||
size_t num_terms,
|
||||
const Terminus *cur_terms, const Terminus *next_terms,
|
||||
size_t num_removed_terms,
|
||||
const Terminus *removed_cur_terms)
|
||||
{
|
||||
// save old locations
|
||||
std::vector<Terminus> old_terms(num_terms);
|
||||
for (size_t idx = 0U; idx != num_terms; ++idx)
|
||||
{
|
||||
old_terms[idx] = getOldFromCur(cur_terms[idx]);
|
||||
}
|
||||
// update using maps old <-> cur and cur <-> next
|
||||
for (size_t idx = 0U; idx != num_terms; ++idx)
|
||||
{
|
||||
m_terminus_old_to_cur_map[old_terms[idx].asIndex()] = next_terms[idx];
|
||||
Terminus next_term = next_terms[idx];
|
||||
if (next_term != Terminus::INVALID_TERMINUS)
|
||||
{
|
||||
m_terminus_cur_to_old_map[next_term.asIndex()] = old_terms[idx];
|
||||
}
|
||||
}
|
||||
// remove next locations that no longer exist
|
||||
for (size_t rem_idx = 0U; rem_idx != num_removed_terms; ++rem_idx)
|
||||
{
|
||||
m_terminus_cur_to_old_map[removed_cur_terms[rem_idx].asIndex()] =
|
||||
Terminus::INVALID_TERMINUS;
|
||||
}
|
||||
}
|
||||
|
||||
void SlicerLayer::connectOpenPolylinesImpl(Polygons& open_polylines, coord_t max_dist, coord_t cell_size, bool allow_reverse)
|
||||
{
|
||||
// below code closes smallest gaps first
|
||||
|
||||
std::priority_queue<PossibleStitch> stitch_queue =
|
||||
findPossibleStitches(open_polylines, max_dist, cell_size, allow_reverse);
|
||||
|
||||
static const Terminus INVALID_TERMINUS = Terminus::INVALID_TERMINUS;
|
||||
Terminus::Index terminus_end_idx = Terminus::endIndexFromPolylineEndIndex(open_polylines.size());
|
||||
// Keeps track of how polyline end point locations move around
|
||||
TerminusTrackingMap terminus_tracking_map(terminus_end_idx);
|
||||
|
||||
while (!stitch_queue.empty())
|
||||
{
|
||||
// Get the next best stitch
|
||||
PossibleStitch next_stitch;
|
||||
next_stitch = stitch_queue.top();
|
||||
stitch_queue.pop();
|
||||
Terminus old_terminus_0 = next_stitch.terminus_0;
|
||||
Terminus terminus_0 = terminus_tracking_map.getCurFromOld(old_terminus_0);
|
||||
if (terminus_0 == INVALID_TERMINUS)
|
||||
{
|
||||
// if we already used this terminus, then this stitch is no longer usable
|
||||
continue;
|
||||
}
|
||||
Terminus old_terminus_1 = next_stitch.terminus_1;
|
||||
Terminus terminus_1 = terminus_tracking_map.getCurFromOld(old_terminus_1);
|
||||
if (terminus_1 == INVALID_TERMINUS)
|
||||
{
|
||||
// if we already used this terminus, then this stitch is no longer usable
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t best_polyline_0_idx = terminus_0.getPolylineIdx();
|
||||
size_t best_polyline_1_idx = terminus_1.getPolylineIdx();
|
||||
|
||||
// check to see if this completes a polygon
|
||||
bool completed_poly = best_polyline_0_idx == best_polyline_1_idx;
|
||||
if (completed_poly)
|
||||
{
|
||||
// finished polygon
|
||||
PolygonRef polyline_0 = open_polylines[best_polyline_0_idx];
|
||||
polygons.add(polyline_0);
|
||||
polyline_0.clear();
|
||||
Terminus cur_terms[2] = {{best_polyline_0_idx, false},
|
||||
{best_polyline_0_idx, true}};
|
||||
for (size_t idx = 0U; idx != 2U; ++idx)
|
||||
{
|
||||
terminus_tracking_map.markRemoved(cur_terms[idx]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// we need to join these polylines
|
||||
|
||||
// plan how to join polylines
|
||||
bool reverse[2];
|
||||
planPolylineStitch(open_polylines, terminus_0, terminus_1, reverse);
|
||||
|
||||
// need to reread since planPolylineStitch can swap terminus_0/1
|
||||
best_polyline_0_idx = terminus_0.getPolylineIdx();
|
||||
best_polyline_1_idx = terminus_1.getPolylineIdx();
|
||||
PolygonRef polyline_0 = open_polylines[best_polyline_0_idx];
|
||||
PolygonRef polyline_1 = open_polylines[best_polyline_1_idx];
|
||||
|
||||
// join polylines according to plan
|
||||
joinPolylines(polyline_0, polyline_1, reverse);
|
||||
|
||||
// update terminus_tracking_map
|
||||
Terminus cur_terms[4] = {{best_polyline_0_idx, false},
|
||||
{best_polyline_0_idx, true},
|
||||
{best_polyline_1_idx, false},
|
||||
{best_polyline_1_idx, true}};
|
||||
Terminus next_terms[4] = {{best_polyline_0_idx, false},
|
||||
INVALID_TERMINUS,
|
||||
INVALID_TERMINUS,
|
||||
{best_polyline_0_idx, true}};
|
||||
if (reverse[0])
|
||||
{
|
||||
std::swap(next_terms[0],next_terms[1]);
|
||||
}
|
||||
if (reverse[1])
|
||||
{
|
||||
std::swap(next_terms[2],next_terms[3]);
|
||||
}
|
||||
// cur_terms -> next_terms has movement map
|
||||
// best_polyline_1 is always removed
|
||||
terminus_tracking_map.updateMap(4U, cur_terms, next_terms,
|
||||
2U, &cur_terms[2]);
|
||||
}
|
||||
}
|
||||
|
||||
void SlicerLayer::stitch_extensive(Polygons& open_polylines)
|
||||
{
|
||||
//For extensive stitching find 2 open polygons that are touching 2 closed polygons.
|
||||
// Then find the shortest path over this polygon that can be used to connect the open polygons,
|
||||
// And generate a path over this shortest bit to link up the 2 open polygons.
|
||||
// (If these 2 open polygons are the same polygon, then the final result is a closed polyon)
|
||||
|
||||
|
||||
while(1)
|
||||
{
|
||||
unsigned int best_polyline_1_idx = -1;
|
||||
@@ -217,12 +552,12 @@ void SlicerLayer::stitch_extensive(Polygons& open_polylines)
|
||||
best_result.polygonIdx = -1;
|
||||
best_result.pointIdxA = -1;
|
||||
best_result.pointIdxB = -1;
|
||||
|
||||
|
||||
for(unsigned int polyline_1_idx = 0; polyline_1_idx < open_polylines.size(); polyline_1_idx++)
|
||||
{
|
||||
PolygonRef polyline_1 = open_polylines[polyline_1_idx];
|
||||
if (polyline_1.size() < 1) continue;
|
||||
|
||||
|
||||
{
|
||||
GapCloserResult res = findPolygonGapCloser(polyline_1[0], polyline_1.back());
|
||||
if (res.len > 0 && res.len < best_result.len)
|
||||
@@ -237,7 +572,7 @@ void SlicerLayer::stitch_extensive(Polygons& open_polylines)
|
||||
{
|
||||
PolygonRef polyline_2 = open_polylines[polyline_2_idx];
|
||||
if (polyline_2.size() < 1 || polyline_1_idx == polyline_2_idx) continue;
|
||||
|
||||
|
||||
GapCloserResult res = findPolygonGapCloser(polyline_1[0], polyline_2.back());
|
||||
if (res.len > 0 && res.len < best_result.len)
|
||||
{
|
||||
@@ -247,7 +582,7 @@ void SlicerLayer::stitch_extensive(Polygons& open_polylines)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (best_result.len < POINT_MAX)
|
||||
{
|
||||
if (best_polyline_1_idx == best_polyline_2_idx)
|
||||
@@ -325,7 +660,7 @@ GapCloserResult SlicerLayer::findPolygonGapCloser(Point ip0, Point ip1)
|
||||
ret.pointIdxA = c1.pointIdx;
|
||||
ret.pointIdxB = c2.pointIdx;
|
||||
ret.AtoB = true;
|
||||
|
||||
|
||||
if (ret.pointIdxA == ret.pointIdxB)
|
||||
{
|
||||
//Connection points are on the same line segment.
|
||||
@@ -351,7 +686,7 @@ GapCloserResult SlicerLayer::findPolygonGapCloser(Point ip0, Point ip1)
|
||||
p0 = p1;
|
||||
}
|
||||
lenB += vSize(p0 - ip0);
|
||||
|
||||
|
||||
if (lenA < lenB)
|
||||
{
|
||||
ret.AtoB = true;
|
||||
@@ -373,7 +708,7 @@ ClosePolygonResult SlicerLayer::findPolygonPointClosestTo(Point input)
|
||||
for(unsigned int i=0; i<polygons[n].size(); i++)
|
||||
{
|
||||
Point p1 = polygons[n][i];
|
||||
|
||||
|
||||
//Q = A + Normal( B - A ) * ((( B - A ) dot ( P - A )) / VSize( A - B ));
|
||||
Point pDiff = p1 - p0;
|
||||
int64_t lineLength = vSize(pDiff);
|
||||
@@ -402,18 +737,18 @@ ClosePolygonResult SlicerLayer::findPolygonPointClosestTo(Point input)
|
||||
void SlicerLayer::makePolygons(const Mesh* mesh, bool keep_none_closed, bool extensive_stitching)
|
||||
{
|
||||
Polygons open_polylines;
|
||||
|
||||
|
||||
makeBasicPolygonLoops(mesh, open_polylines);
|
||||
|
||||
|
||||
connectOpenPolylines(open_polylines);
|
||||
|
||||
|
||||
// TODO: (?) for mesh surface mode: connect open polygons. Maybe the above algorithm can create two open polygons which are actually connected when the starting segment is in the middle between the two open polygons.
|
||||
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::NORMAL)
|
||||
{ // don't stitch when using (any) mesh surface mode, i.e. also don't stitch when using mixed mesh surface and closed polygons, because then polylines which are supposed to be open will be closed
|
||||
stitch(open_polylines);
|
||||
}
|
||||
|
||||
|
||||
if (extensive_stitching)
|
||||
{
|
||||
stitch_extensive(open_polylines);
|
||||
@@ -427,7 +762,7 @@ void SlicerLayer::makePolygons(const Mesh* mesh, bool keep_none_closed, bool ext
|
||||
openPolylines.add(polyline);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (PolygonRef polyline : open_polylines)
|
||||
{
|
||||
if (polyline.size() > 0)
|
||||
@@ -441,9 +776,9 @@ void SlicerLayer::makePolygons(const Mesh* mesh, bool keep_none_closed, bool ext
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
polygons.simplify();
|
||||
|
||||
|
||||
polygons.removeDegenerateVerts(); // remove verts connected to overlapping line segments
|
||||
|
||||
|
||||
int xy_offset = mesh->getSettingInMicrons("xy_offset");
|
||||
if (xy_offset != 0)
|
||||
{
|
||||
@@ -452,24 +787,30 @@ void SlicerLayer::makePolygons(const Mesh* mesh, bool keep_none_closed, bool ext
|
||||
}
|
||||
|
||||
|
||||
Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool keep_none_closed, bool extensive_stitching)
|
||||
Slicer::Slicer(Mesh* mesh, int initial, int thickness, int slice_layer_count, bool keep_none_closed, bool extensive_stitching)
|
||||
: mesh(mesh)
|
||||
{
|
||||
assert(layer_count > 0);
|
||||
assert(slice_layer_count > 0);
|
||||
|
||||
layers.resize(layer_count);
|
||||
|
||||
for(int32_t layer_nr = 0; layer_nr < layer_count; layer_nr++)
|
||||
TimeKeeper slice_timer;
|
||||
|
||||
layers.resize(slice_layer_count);
|
||||
|
||||
|
||||
for(int32_t layer_nr = 0; layer_nr < slice_layer_count; layer_nr++)
|
||||
{
|
||||
layers[layer_nr].z = initial + thickness * layer_nr;
|
||||
}
|
||||
|
||||
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < mesh->faces.size(); mesh_idx++)
|
||||
{
|
||||
MeshFace& face = mesh->faces[mesh_idx];
|
||||
Point3 p0 = mesh->vertices[face.vertex_index[0]].p;
|
||||
Point3 p1 = mesh->vertices[face.vertex_index[1]].p;
|
||||
Point3 p2 = mesh->vertices[face.vertex_index[2]].p;
|
||||
const MeshFace& face = mesh->faces[mesh_idx];
|
||||
const MeshVertex& v0 = mesh->vertices[face.vertex_index[0]];
|
||||
const MeshVertex& v1 = mesh->vertices[face.vertex_index[1]];
|
||||
const MeshVertex& v2 = mesh->vertices[face.vertex_index[2]];
|
||||
Point3 p0 = v0.p;
|
||||
Point3 p1 = v1.p;
|
||||
Point3 p2 = v2.p;
|
||||
int32_t minZ = p0.z;
|
||||
int32_t maxZ = p0.z;
|
||||
if (p1.z < minZ) minZ = p1.z;
|
||||
@@ -482,22 +823,56 @@ Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool kee
|
||||
int32_t z = layer_nr * thickness + initial;
|
||||
if (z < minZ) continue;
|
||||
if (layer_nr < 0) continue;
|
||||
|
||||
|
||||
SlicerSegment s;
|
||||
s.endVertex = nullptr;
|
||||
int end_edge_idx = -1;
|
||||
if (p0.z < z && p1.z >= z && p2.z >= z)
|
||||
{
|
||||
s = project2D(p0, p2, p1, z);
|
||||
end_edge_idx = 0;
|
||||
if (p1.z == z)
|
||||
{
|
||||
s.endVertex = &v1;
|
||||
}
|
||||
}
|
||||
else if (p0.z > z && p1.z < z && p2.z < z)
|
||||
{
|
||||
s = project2D(p0, p1, p2, z);
|
||||
end_edge_idx = 2;
|
||||
|
||||
}
|
||||
|
||||
else if (p1.z < z && p0.z >= z && p2.z >= z)
|
||||
{
|
||||
s = project2D(p1, p0, p2, z);
|
||||
end_edge_idx = 1;
|
||||
if (p2.z == z)
|
||||
{
|
||||
s.endVertex = &v2;
|
||||
}
|
||||
}
|
||||
else if (p1.z > z && p0.z < z && p2.z < z)
|
||||
{
|
||||
s = project2D(p1, p2, p0, z);
|
||||
end_edge_idx = 0;
|
||||
|
||||
}
|
||||
|
||||
else if (p2.z < z && p1.z >= z && p0.z >= z)
|
||||
{
|
||||
s = project2D(p2, p1, p0, z);
|
||||
end_edge_idx = 2;
|
||||
if (p0.z == z)
|
||||
{
|
||||
s.endVertex = &v0;
|
||||
}
|
||||
}
|
||||
else if (p2.z > z && p1.z < z && p0.z < z)
|
||||
{
|
||||
s = project2D(p2, p0, p1, z);
|
||||
end_edge_idx = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Not all cases create a segment, because a point of a face could create just a dot, and two touching faces
|
||||
@@ -506,14 +881,18 @@ Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool kee
|
||||
}
|
||||
layers[layer_nr].face_idx_to_segment_idx.insert(std::make_pair(mesh_idx, layers[layer_nr].segments.size()));
|
||||
s.faceIndex = mesh_idx;
|
||||
s.endOtherFaceIdx = face.connected_face_index[end_edge_idx];
|
||||
s.addedToPolygon = false;
|
||||
layers[layer_nr].segments.push_back(s);
|
||||
}
|
||||
}
|
||||
log("slice of mesh took %.3f seconds\n",slice_timer.restart());
|
||||
for(unsigned int layer_nr=0; layer_nr<layers.size(); layer_nr++)
|
||||
{
|
||||
layers[layer_nr].makePolygons(mesh, keep_none_closed, extensive_stitching);
|
||||
}
|
||||
mesh->expandXY(mesh->getSettingInMicrons("xy_offset"));
|
||||
log("slice make polygons took %.3f seconds\n",slice_timer.restart());
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+412
-30
@@ -2,6 +2,8 @@
|
||||
#ifndef SLICER_H
|
||||
#define SLICER_H
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "mesh.h"
|
||||
#include "utils/polygon.h"
|
||||
/*
|
||||
@@ -14,8 +16,13 @@ class SlicerSegment
|
||||
{
|
||||
public:
|
||||
Point start, end;
|
||||
int faceIndex;
|
||||
bool addedToPolygon;
|
||||
int faceIndex = -1;
|
||||
// The index of the other face connected via the edge that created end
|
||||
int endOtherFaceIdx = -1;
|
||||
// If end corresponds to a vertex of the mesh, then this is populated
|
||||
// with the vertex that it ended on.
|
||||
const MeshVertex *endVertex = nullptr;
|
||||
bool addedToPolygon = false;
|
||||
};
|
||||
|
||||
class ClosePolygonResult
|
||||
@@ -23,17 +30,17 @@ class ClosePolygonResult
|
||||
//The line on which the point lays is between pointIdx-1 and pointIdx
|
||||
public:
|
||||
Point intersectionPoint;
|
||||
int polygonIdx;
|
||||
unsigned int pointIdx;
|
||||
int polygonIdx = -1;
|
||||
unsigned int pointIdx = -1;
|
||||
};
|
||||
class GapCloserResult
|
||||
{
|
||||
public:
|
||||
int64_t len;
|
||||
int polygonIdx;
|
||||
unsigned int pointIdxA;
|
||||
unsigned int pointIdxB;
|
||||
bool AtoB;
|
||||
int64_t len = -1;
|
||||
int polygonIdx = -1;
|
||||
unsigned int pointIdxA = -1;
|
||||
unsigned int pointIdxB = -1;
|
||||
bool AtoB = false;
|
||||
};
|
||||
|
||||
class SlicerLayer
|
||||
@@ -41,14 +48,14 @@ class SlicerLayer
|
||||
public:
|
||||
std::vector<SlicerSegment> segments;
|
||||
std::unordered_map<int, int> face_idx_to_segment_idx; // topology
|
||||
|
||||
int z;
|
||||
|
||||
int z = -1;
|
||||
Polygons polygons;
|
||||
Polygons openPolylines;
|
||||
|
||||
/*!
|
||||
* Connect the segments into polygons for this layer of this \p mesh
|
||||
*
|
||||
*
|
||||
* \param[in] mesh The mesh data for which we are connecting sliced segments (The face data is used)
|
||||
* \param keepNoneClosed Whether to throw away the data for segments which we couldn't stitch into a polygon
|
||||
* \param extensiveStitching Whether to perform extra work to try and close polylines into polygons when there are large gaps
|
||||
@@ -58,7 +65,7 @@ public:
|
||||
protected:
|
||||
/*!
|
||||
* Connect the segments into loops which correctly form polygons (don't perform stitching here)
|
||||
*
|
||||
*
|
||||
* \param[in] mesh The mesh data for which we are connecting sliced segments (The face data is used)
|
||||
* \param[out] open_polylines The polylines which are stiched, but couldn't be closed into a loop
|
||||
*/
|
||||
@@ -66,7 +73,7 @@ protected:
|
||||
|
||||
/*!
|
||||
* Connect the segments into a loop, starting from the segment with index \p start_segment_idx
|
||||
*
|
||||
*
|
||||
* \param[in] mesh The mesh data for which we are connecting sliced segments (The face data is used)
|
||||
* \param[out] open_polylines The polylines which are stiched, but couldn't be closed into a loop
|
||||
* \param[in] start_segment_idx The index into SlicerLayer::segments for the first segment from which to start the polygon loop
|
||||
@@ -77,7 +84,7 @@ protected:
|
||||
* Get the next segment connected to the end of \p segment.
|
||||
* Used to make closed polygon loops.
|
||||
* Return ASAP if segment is (also) connected to SlicerLayer::segments[\p start_segment_idx]
|
||||
*
|
||||
*
|
||||
* \param[in] mesh The mesh data for which we are connecting sliced segments (The face data is used)
|
||||
* \param[in] segment The segment from which to start looking for the next
|
||||
* \param[in] start_segment_idx The index to the segment which when conected to \p segment will immediately stop looking for further candidates.
|
||||
@@ -87,18 +94,18 @@ protected:
|
||||
/*!
|
||||
* Connecting polygons that are not closed yet, as models are not always perfect manifold we need to join some stuff up to get proper polygons.
|
||||
* First link up polygon ends that are within 2 microns.
|
||||
*
|
||||
*
|
||||
* Clears all open polylines which are used up in the process
|
||||
*
|
||||
*
|
||||
* \param[in,out] open_polylines The polylines which are stiched, but couldn't be closed into a loop
|
||||
*/
|
||||
void connectOpenPolylines(Polygons& open_polylines);
|
||||
|
||||
/*!
|
||||
* Link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time.
|
||||
*
|
||||
*
|
||||
* Clears all open polylines which are used up in the process
|
||||
*
|
||||
*
|
||||
* \param[in,out] open_polylines The polylines which are stiched, but couldn't be closed into a loop yet
|
||||
*/
|
||||
void stitch(Polygons& open_polylines);
|
||||
@@ -109,12 +116,371 @@ protected:
|
||||
|
||||
/*!
|
||||
* Try to close up polylines into polygons while they have large gaps in them.
|
||||
*
|
||||
*
|
||||
* Clears all open polylines which are used up in the process
|
||||
*
|
||||
*
|
||||
* \param[in,out] open_polylines The polylines which are stiched, but couldn't be closed into a loop yet
|
||||
*/
|
||||
void stitch_extensive(Polygons& open_polylines);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief This class represents the location of an end point of a
|
||||
* polyline in a polyline vector.
|
||||
*
|
||||
* The location records the index in the polyline vector and
|
||||
* whether this is the vertex at the start of the polyline or the
|
||||
* vertex at the end.
|
||||
*/
|
||||
class Terminus
|
||||
{
|
||||
public:
|
||||
/*! A representation of Terminus that can be used as an array index.
|
||||
*
|
||||
* See \ref asIndex() for more information.
|
||||
*/
|
||||
using Index = size_t;
|
||||
|
||||
/*! A Terminus value representing an invalid value.
|
||||
*
|
||||
* This is used to record when Terminus are removed.
|
||||
*/
|
||||
static const Terminus INVALID_TERMINUS;
|
||||
|
||||
/*! Constructor leaving uninitialized. */
|
||||
Terminus()
|
||||
{}
|
||||
|
||||
/*! Constructor from Index representation.
|
||||
*
|
||||
* Terminus{t.asIndex()} == t for all Terminus t.
|
||||
*/
|
||||
Terminus(Index idx)
|
||||
{
|
||||
m_idx = idx;
|
||||
}
|
||||
|
||||
/*! Constuctor from the polyline index and which end of the polyline.
|
||||
*
|
||||
* Terminus{t.getPolylineIdx(), t.isEnd()} == t for all Terminus t.
|
||||
*/
|
||||
Terminus(size_t polyline_idx, bool is_end)
|
||||
{
|
||||
m_idx = polyline_idx * 2 + (is_end ? 1 : 0);
|
||||
}
|
||||
|
||||
/*! Gets the polyline index for this Terminus. */
|
||||
size_t getPolylineIdx() const
|
||||
{
|
||||
return m_idx / 2;
|
||||
}
|
||||
|
||||
/*! Gets whether this Terminus represents the end point of the polyline. */
|
||||
bool isEnd() const
|
||||
{
|
||||
return (m_idx & 1) == 1;
|
||||
}
|
||||
|
||||
/*! Gets the Index representation of this Terminus.
|
||||
*
|
||||
* The index representation much satisfy the following:
|
||||
* 1. for all Terminus t0, t1: t0 == t1 implies t0.asIndex() == t1.asIndex()
|
||||
* 2. for all Terminus t0, t1: t0 != t1 implies t0.asIndex() != t1.asIndex()
|
||||
* 3. t0.asIndex() >= 0
|
||||
* 4. if y = \ref endIndexFromPolylineEndIndex(x), then for all Terminus t
|
||||
* if t.getPolylineIdx() < x then t.asIndex() < y
|
||||
*
|
||||
* In addition, the Index representation should be reasonably
|
||||
* compact for efficiency. This means that for polyline index
|
||||
* in [0,x) and Terminus t with t.getPolylineIdx() < x, the
|
||||
* set of containing all t.asIndex() union {0} should be
|
||||
* small. In other words, t.asIndex() should map to [0,y)
|
||||
* where y is as small as possible.
|
||||
*/
|
||||
Index asIndex() const
|
||||
{
|
||||
return m_idx;
|
||||
}
|
||||
|
||||
/*! Calculates the Terminus end Index from the polyline vector end index.
|
||||
*
|
||||
* \param[in] polyline_end_idx The index of the first invalid
|
||||
* element of the polyline vector.
|
||||
* \return The Index for the first invalid Terminus for the polyline
|
||||
* vector.
|
||||
*/
|
||||
static Index endIndexFromPolylineEndIndex(unsigned int polyline_end_idx)
|
||||
{
|
||||
return polyline_end_idx*2;
|
||||
}
|
||||
|
||||
/*! Tests for equality.
|
||||
*
|
||||
* Two Terminus are equal if they return the same results for
|
||||
* \ref getPolylineIdx() and \ref isEnd().
|
||||
*/
|
||||
bool operator==(const Terminus &other)
|
||||
{
|
||||
return m_idx == other.m_idx;
|
||||
}
|
||||
|
||||
/*! Tests for inequality. */
|
||||
bool operator!=(const Terminus &other)
|
||||
{
|
||||
return m_idx != other.m_idx;
|
||||
}
|
||||
|
||||
private:
|
||||
/*! The Index representation of the Terminus.
|
||||
*
|
||||
* The polyline_idx and end flags are calculated from this on demand.
|
||||
*/
|
||||
Index m_idx = -1;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Represents a possible stitch between two polylines.
|
||||
*
|
||||
* This represents the possibility of creating a new merged
|
||||
* polyline from appending terminus_1.getPolylineIdx() onto
|
||||
* terminus_0.getPolylineIdx() using the Terminus points as the
|
||||
* join point. Consider polylines A -> B and C -> D. If
|
||||
* terminus_0 is B and terminus_1 is C, then this stitch
|
||||
* represents A -> B -> C -> D. If terminus_0 is C and terminus_1
|
||||
* is A, then this stitch represents D -> C -> A -> B. In
|
||||
* general, this stitch represents the polyline:
|
||||
* the other terminus of polyline 0 -> terminus_0 -> terminus_1
|
||||
* -> the other terminus of polyline 1.
|
||||
*
|
||||
* This class also stores the squared distance involved in making
|
||||
* the stitch.
|
||||
*/
|
||||
struct PossibleStitch
|
||||
{
|
||||
/*! Squared distance from terminus_0 to terminus_1. */
|
||||
int64_t dist2 = -1;
|
||||
/*! The Terminus representing the end of polyline_0 where the
|
||||
* join would happen. */
|
||||
Terminus terminus_0;
|
||||
/*! The Terminus representing the end of polyline_1 where the
|
||||
* join would happen. */
|
||||
Terminus terminus_1;
|
||||
|
||||
/*! True if this stitch doesn't require any polyline reversals.
|
||||
*
|
||||
* If this is true, then the polylines can be appended using
|
||||
* their natural order.
|
||||
*/
|
||||
bool in_order() const
|
||||
{
|
||||
// in order if using back of line 0 and front of line 1
|
||||
return terminus_0.isEnd() &&
|
||||
!terminus_1.isEnd();
|
||||
}
|
||||
|
||||
/*! Orders PossibleStitch by goodness.
|
||||
*
|
||||
* Better PossibleStitch are > then worse PossibleStitch.
|
||||
* priority_queue will give greatest first so greatest
|
||||
* must be most desirable stitch
|
||||
*/
|
||||
bool operator<(const PossibleStitch &other) const;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Tracks movements of polyline end point locations (Terminus).
|
||||
*
|
||||
* Tracks the movement of polyline end point locations within the
|
||||
* polyline vector as polylines are joined, reversed, and used to
|
||||
* form polygons.
|
||||
*/
|
||||
class TerminusTrackingMap
|
||||
{
|
||||
public:
|
||||
/*! Initializes the TerminusTrackingMap with the size indicated.
|
||||
*
|
||||
* \param end_idx The first invalid Terminus::Index. This usually
|
||||
* comes from \ref Terminus::endIndexFromPolylineEndIndex().
|
||||
*/
|
||||
TerminusTrackingMap(Terminus::Index end_idx);
|
||||
|
||||
/*! Given the old Terminus location returns the current location.
|
||||
*
|
||||
* If the old location is no longer the endpoint of a polyline
|
||||
* in the polyline vector, then this returns
|
||||
* Terminus::INVALID_TERMINUS. As long as the old location is
|
||||
* still an endpoint in the polyline vector, then
|
||||
* getCurFromOld(old) will always refer to the same point.
|
||||
* Endpoints are removed from the polyline vector as polylines
|
||||
* are merged or converted to Polygons.
|
||||
*
|
||||
* \param old The old Terminus location. Must not be
|
||||
* INVALID_TERMINUS.
|
||||
* \return The current Terminus location or INVALID_TERMINUS
|
||||
* if the old endpoint is no longer an endpoint.
|
||||
*/
|
||||
Terminus getCurFromOld(const Terminus &old) const
|
||||
{
|
||||
return m_terminus_old_to_cur_map[old.asIndex()];
|
||||
}
|
||||
|
||||
/*! Given the current Terminus location returns the old location.
|
||||
*
|
||||
* \param cur The current Terminus location. Must not be
|
||||
* INVALID_TERMINUS.
|
||||
* \return The old Terminus location. Returns
|
||||
* INVALID_TERMINUS if the old Terminus location was
|
||||
* removed (used to form a Polygon).
|
||||
*/
|
||||
Terminus getOldFromCur(const Terminus &cur) const
|
||||
{
|
||||
return m_terminus_cur_to_old_map[cur.asIndex()];
|
||||
}
|
||||
|
||||
/*! Mark the current Terminus as being removed.
|
||||
*
|
||||
* This marks the current Terminus as being removed from the
|
||||
* polyline vector.
|
||||
*/
|
||||
void markRemoved(const Terminus &cur)
|
||||
{
|
||||
Terminus old = getOldFromCur(cur);
|
||||
m_terminus_old_to_cur_map[old.asIndex()] = Terminus::INVALID_TERMINUS;
|
||||
m_terminus_cur_to_old_map[cur.asIndex()] = Terminus::INVALID_TERMINUS;
|
||||
}
|
||||
|
||||
/*! Update the map for movement of Terminus.
|
||||
*
|
||||
* This updates the map for the movement / removal of Terminus
|
||||
* locations. next_terms[i] should refer to the same point as
|
||||
* cur_terms[i] for i < num_terms, unless the Terminus was
|
||||
* removed. If the Terminus was removed, next_terms[i] should
|
||||
* be INVALID_TERMINUS.
|
||||
*
|
||||
* removed_cur_terms should refer to those Terminus that are
|
||||
* no longer present after the update. removed_cur_terms
|
||||
* should be the set of terminus values that are in cur_terms
|
||||
* but not in next_terms, i.e. viewing the inputs as sets:
|
||||
* removed_cur_terms = next_terms - cur_terms. It is passed
|
||||
* separately to avoid calculating the set difference since
|
||||
* the caller generally has this information readily
|
||||
* available.
|
||||
*
|
||||
* \param num_terms The number of Terminus that changed.
|
||||
* \param cur_terms The current Terminus locations. Must be
|
||||
* of size num_terms. Must not contain INVALID_TERMINUS.
|
||||
* \param next_terms The Terminus locations after the update.
|
||||
* Must be of size num_terms. A value of INVALID_TERMINUS
|
||||
* indicates that the Terminus was removed.
|
||||
* \param num_removed_terms The number of Terminus locations
|
||||
* that are being removed by the update.
|
||||
* \param removed_cur_terms The Terminus locations that will
|
||||
* be removed after the update.
|
||||
*/
|
||||
void updateMap(size_t num_terms,
|
||||
const Terminus *cur_terms, const Terminus *next_terms,
|
||||
size_t num_removed_terms,
|
||||
const Terminus *removed_cur_terms);
|
||||
|
||||
private:
|
||||
/*! map from old terminus location to current terminus location */
|
||||
std::vector<Terminus> m_terminus_old_to_cur_map;
|
||||
/*! map from current terminus location to old terminus location */
|
||||
std::vector<Terminus> m_terminus_cur_to_old_map;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Try to find a segment from face \p face_idx to continue \p segment.
|
||||
*
|
||||
* \param[in] mesh The mesh being sliced.
|
||||
* \param[in] segment The previous segment that we want to find a continuation for.
|
||||
* \param[in] face_idx The index of the face that might have generated a continuation segment.
|
||||
* \param[in] start_segment_idx The index of the segment that started this polyline.
|
||||
*/
|
||||
int tryFaceNextSegmentIdx(const Mesh* mesh, const SlicerSegment& segment,
|
||||
int face_idx, unsigned int start_segment_idx) const;
|
||||
|
||||
/*!
|
||||
* Find possible allowed stitches in goodness order.
|
||||
*
|
||||
* This finds all stitches that are allowed by the parameters.
|
||||
* The stitches are returned in a priority_queue that returns them
|
||||
* in order from best to worst stitch.
|
||||
*
|
||||
* \param open_polylines The polylines to try to stitch together.
|
||||
* \param max_dist The maximum distance between end points for an
|
||||
* allowed stitch.
|
||||
* \param cell_size The cell size to use for the SparsePointGridInclusive. This
|
||||
* affects speed, but does not otherwise affect the results.
|
||||
* This value should generally be close to max_dist.
|
||||
* \param allow_reverse Whether stitches are allowed that reverse
|
||||
* the order of a polyline.
|
||||
* \return The stitches that are allowed in order from best to worst.
|
||||
*/
|
||||
std::priority_queue<PossibleStitch> findPossibleStitches(
|
||||
const Polygons& open_polylines, coord_t max_dist, coord_t cell_size,
|
||||
bool allow_reverse) const;
|
||||
|
||||
/*! Plans the best way to perform a stitch.
|
||||
*
|
||||
* Let polyline_0 be open_polylines[terminus_0.getPolylineIdx()] and
|
||||
* polyline_1 be open_polylines[terminus_1.getPolylineIdx()].
|
||||
*
|
||||
* The plan consists of appending polyline_1 to polyline_0. If
|
||||
* reverse[0] is true, then polyline_0 should be reversed before
|
||||
* appending. If reverse[1] is true, then polyline_1 should be
|
||||
* reversed before appending. Note that terminus_0 and terminus_1
|
||||
* may be swapped by this function.
|
||||
*
|
||||
* \param[in] open_polylines The polyline storage vector.
|
||||
* \param[in,out] terminus_0 the Terminus on polyline_0 to join at.
|
||||
* \param[in,out] terminus_1 the Terminus on polyline_1 to join at.
|
||||
* \param[out] reverse Whether the polylines need to be reversed.
|
||||
*/
|
||||
void planPolylineStitch(const Polygons& open_polylines,
|
||||
Terminus& terminus_0, Terminus& terminus_1,
|
||||
bool reverse[2]) const;
|
||||
|
||||
/*! Joins polyline_1 onto polyline_0.
|
||||
*
|
||||
* Appends polyline_1 to polyline_0. It reverses the polylines first if either
|
||||
* reverse[i] is true. Clears polyline_1.
|
||||
*
|
||||
* \param[in,out] polyline_0 On input, the polyline that will form
|
||||
* the first part of the joined polyline. On output, the
|
||||
* joined polyline.
|
||||
* \param[in,out] polyline_1 On input, the polyline that will form
|
||||
* the second of the joined polyline. On output, an empty
|
||||
* polyline.
|
||||
* \param[in] reverse Whether to reverse the polylines before
|
||||
* joining. reverse[0] indicates whether to reverse
|
||||
* polyline_0 and reverse[1] indicates whether to reverse
|
||||
* polyline_1
|
||||
*/
|
||||
void joinPolylines(PolygonRef& polyline_0, PolygonRef& polyline_1,
|
||||
const bool reverse[2]) const;
|
||||
|
||||
/*!
|
||||
* Connecting polylines that are not closed yet.
|
||||
*
|
||||
* Any polylines that are closed by this function are added to
|
||||
* this->polygons. All possible polyline joins that meet the
|
||||
* distance and reversal criteria will be performed. This
|
||||
* function will not introduce any copies of the same polyline
|
||||
* segment.
|
||||
*
|
||||
* \param[in,out] open_polylines The polylines which couldn't be
|
||||
* closed into a loop
|
||||
* \param[in] max_dist The maximum distance that polyline ends can
|
||||
* be separated and still be joined.
|
||||
* \param[in] cell_size The cell size to use internally in the
|
||||
* grid. This affects speed but not results.
|
||||
* \param[in] allow_reverse If true, then this function is allowed
|
||||
* to reverse edge directions to merge polylines.
|
||||
*/
|
||||
void connectOpenPolylinesImpl(Polygons& open_polylines,
|
||||
coord_t max_dist, coord_t cell_size,
|
||||
bool allow_reverse);
|
||||
};
|
||||
|
||||
class Slicer
|
||||
@@ -122,20 +488,36 @@ class Slicer
|
||||
public:
|
||||
std::vector<SlicerLayer> layers;
|
||||
|
||||
const Mesh* mesh; //!< The sliced mesh
|
||||
|
||||
Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool keepNoneClosed, bool extensiveStitching);
|
||||
|
||||
const Mesh* mesh = nullptr; //!< The sliced mesh
|
||||
|
||||
Slicer(Mesh* mesh, int initial, int thickness, int slice_layer_count, bool keepNoneClosed, bool extensiveStitching);
|
||||
|
||||
/*!
|
||||
* Linear interpolation
|
||||
*
|
||||
* Get the Y of a point with X \p x in the line through (\p x0, \p y0) and (\p x1, \p y1)
|
||||
*/
|
||||
int64_t interpolate(int64_t x, int64_t x0, int64_t x1, int64_t y0, int64_t y1) const
|
||||
{
|
||||
int64_t dx_01 = x1 - x0;
|
||||
int64_t num = (y1 - y0) * (x - x0);
|
||||
num += num > 0 ? dx_01/2 : -dx_01/2; // add in offset to round result
|
||||
int64_t y = y0 + num / dx_01;
|
||||
return y;
|
||||
}
|
||||
|
||||
SlicerSegment project2D(Point3& p0, Point3& p1, Point3& p2, int32_t z) const
|
||||
{
|
||||
SlicerSegment seg;
|
||||
seg.start.X = p0.x + int64_t(p1.x - p0.x) * int64_t(z - p0.z) / int64_t(p1.z - p0.z);
|
||||
seg.start.Y = p0.y + int64_t(p1.y - p0.y) * int64_t(z - p0.z) / int64_t(p1.z - p0.z);
|
||||
seg.end.X = p0.x + int64_t(p2.x - p0.x) * int64_t(z - p0.z) / int64_t(p2.z - p0.z);
|
||||
seg.end.Y = p0.y + int64_t(p2.y - p0.y) * int64_t(z - p0.z) / int64_t(p2.z - p0.z);
|
||||
|
||||
seg.start.X = interpolate(z, p0.z, p1.z, p0.x, p1.x);
|
||||
seg.start.Y = interpolate(z, p0.z, p1.z, p0.y, p1.y);
|
||||
seg.end .X = interpolate(z, p0.z, p2.z, p0.x, p2.x);
|
||||
seg.end .Y = interpolate(z, p0.z, p2.z, p0.y, p2.y);
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
|
||||
void dumpSegmentsToHTML(const char* filename);
|
||||
};
|
||||
|
||||
|
||||
+205
-93
@@ -1,16 +1,19 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "support.h"
|
||||
|
||||
#include <cmath> // sqrt
|
||||
#include <utility> // pair
|
||||
#include <deque>
|
||||
#include <cmath> // round
|
||||
|
||||
#include "support.h"
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "progress/Progress.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
Polygons AreaSupport::join(Polygons& supportLayer_up, Polygons& supportLayer_this, int64_t supportJoinDistance, int64_t smoothing_distance, int min_smoothing_area, bool conical_support, int64_t conical_support_offset, int64_t conical_smallest_breadth)
|
||||
Polygons AreaSupport::join(Polygons& supportLayer_up, Polygons& supportLayer_this, int64_t supportJoinDistance, int64_t smoothing_distance, int max_smoothing_angle, bool conical_support, int64_t conical_support_offset, int64_t conical_smallest_breadth)
|
||||
{
|
||||
Polygons joined;
|
||||
if (conical_support)
|
||||
@@ -30,28 +33,74 @@ Polygons AreaSupport::join(Polygons& supportLayer_up, Polygons& supportLayer_thi
|
||||
joined = joined.offset(supportJoinDistance)
|
||||
.offset(-supportJoinDistance);
|
||||
}
|
||||
if (smoothing_distance > 0)
|
||||
joined = joined.smooth(smoothing_distance, min_smoothing_area);
|
||||
|
||||
|
||||
// remove jagged line pieces introduced by unioning separate overhang areas for consectuive layers
|
||||
//
|
||||
// support may otherwise look like:
|
||||
// _____________________ .
|
||||
// / \ } dist_from_lower_layer
|
||||
// /__ __\ /
|
||||
// /''--...........--''\ `\ .
|
||||
// / \ } dist_from_lower_layer
|
||||
// /__ __\ ./
|
||||
// /''--...........--''\ `\ .
|
||||
// / \ } dist_from_lower_layer
|
||||
// /_______________________\ ,/
|
||||
// rather than
|
||||
// _____________________
|
||||
// / \ .
|
||||
// / \ .
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// |_______________________|
|
||||
//
|
||||
// dist_from_lower_layer may be up to max_dist_from_lower_layer (see below), but that value may be extremely high
|
||||
joined = joined.smooth_outward(max_smoothing_angle, smoothing_distance);
|
||||
|
||||
return joined;
|
||||
}
|
||||
|
||||
void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count)
|
||||
{
|
||||
int max_layer_nr_support_mesh_filled;
|
||||
for (max_layer_nr_support_mesh_filled = storage.support.supportLayers.size() - 1; max_layer_nr_support_mesh_filled >= 0; max_layer_nr_support_mesh_filled--)
|
||||
{
|
||||
const SupportLayer& support_layer = storage.support.supportLayers[max_layer_nr_support_mesh_filled];
|
||||
if (support_layer.supportAreas.size() > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, max_layer_nr_support_mesh_filled);
|
||||
for (int layer_nr = 0; layer_nr < max_layer_nr_support_mesh_filled; layer_nr++)
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[max_layer_nr_support_mesh_filled];
|
||||
support_layer.support_mesh = support_layer.support_mesh.unionPolygons();
|
||||
}
|
||||
|
||||
// initialization of supportAreasPerLayer
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count ; layer_idx++)
|
||||
storage.support.supportLayers.emplace_back();
|
||||
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
if (layer_count > storage.support.supportLayers.size())
|
||||
{ // there might alsready be anti_overhang_area data in the supportLayers
|
||||
storage.support.supportLayers.resize(layer_count);
|
||||
}
|
||||
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
{
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
if (mesh.getSettingBoolean("infill_mesh") || mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::vector<Polygons> supportAreas;
|
||||
supportAreas.resize(layer_count, Polygons());
|
||||
generateSupportAreas(storage, mesh_idx, layer_count, supportAreas);
|
||||
|
||||
if (mesh.getSettingBoolean("support_roof_enable"))
|
||||
if (mesh.getSettingBoolean("support_interface_enable"))
|
||||
{
|
||||
generateSupportRoofs(storage, supportAreas, layer_count, storage.getSettingInMicrons("layer_height"), mesh.getSettingInMicrons("support_roof_height"));
|
||||
generateSupportInterface(storage, mesh, supportAreas, layer_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -64,10 +113,9 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int l
|
||||
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count ; layer_idx++)
|
||||
{
|
||||
storage.support.supportLayers[layer_idx].supportAreas = storage.support.supportLayers[layer_idx].supportAreas.unionPolygons();
|
||||
Polygons& support_areas = storage.support.supportLayers[layer_idx].supportAreas;
|
||||
support_areas = support_areas.unionPolygons();
|
||||
}
|
||||
|
||||
storage.support.generated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -86,55 +134,62 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
|
||||
// given settings
|
||||
ESupportType support_type = mesh.getSettingAsSupportType("support_type");
|
||||
ESupportType support_type = storage.getSettingAsSupportType("support_type");
|
||||
|
||||
if (!mesh.getSettingBoolean("support_enable"))
|
||||
return;
|
||||
if (support_type == ESupportType::NONE)
|
||||
return;
|
||||
|
||||
double supportAngle = mesh.getSettingInAngleRadians("support_angle");
|
||||
bool supportOnBuildplateOnly = support_type == ESupportType::PLATFORM_ONLY;
|
||||
int supportZDistanceBottom = mesh.getSettingInMicrons("support_bottom_distance");
|
||||
int supportZDistanceTop = mesh.getSettingInMicrons("support_top_distance");
|
||||
int join_distance = mesh.getSettingInMicrons("support_join_distance");
|
||||
int support_bottom_stair_step_height = mesh.getSettingInMicrons("support_bottom_stair_step_height");
|
||||
int smoothing_distance = mesh.getSettingInMicrons("support_area_smoothing");
|
||||
|
||||
int extension_offset = mesh.getSettingInMicrons("support_offset");
|
||||
|
||||
int supportTowerDiameter = mesh.getSettingInMicrons("support_tower_diameter");
|
||||
int supportMinAreaSqrt = mesh.getSettingInMicrons("support_minimal_diameter");
|
||||
double supportTowerRoofAngle = mesh.getSettingInAngleRadians("support_tower_roof_angle");
|
||||
|
||||
//std::cerr <<" towerDiameter=" << towerDiameter <<", supportMinAreaSqrt=" << supportMinAreaSqrt << std::endl;
|
||||
|
||||
int min_smoothing_area = 100*100; // minimal area for which to perform smoothing
|
||||
int z_layer_distance_tower = 1; // start tower directly below overhang point
|
||||
|
||||
int layerThickness = storage.getSettingInMicrons("layer_height");
|
||||
int extrusionWidth = storage.getSettingInMicrons("support_line_width");
|
||||
int supportXYDistance = mesh.getSettingInMicrons("support_xy_distance");
|
||||
int support_xy_distance_overhang = mesh.getSettingInMicrons("support_xy_distance_overhang");
|
||||
const double supportAngle = mesh.getSettingInAngleRadians("support_angle");
|
||||
const bool supportOnBuildplateOnly = support_type == ESupportType::PLATFORM_ONLY;
|
||||
const int supportZDistanceBottom = mesh.getSettingInMicrons("support_bottom_distance");
|
||||
const int supportZDistanceTop = mesh.getSettingInMicrons("support_top_distance");
|
||||
const int join_distance = mesh.getSettingInMicrons("support_join_distance");
|
||||
const int support_bottom_stair_step_height = mesh.getSettingInMicrons("support_bottom_stair_step_height");
|
||||
|
||||
bool use_support_xy_distance_overhang = mesh.getSettingAsSupportDistPriority("support_xy_overrides_z") == SupportDistPriority::Z_OVERRIDES_XY; // whether to use a different xy distance at overhangs
|
||||
const int extension_offset = mesh.getSettingInMicrons("support_offset");
|
||||
|
||||
const int supportTowerDiameter = mesh.getSettingInMicrons("support_tower_diameter");
|
||||
const int supportMinAreaSqrt = mesh.getSettingInMicrons("support_minimal_diameter");
|
||||
const double supportTowerRoofAngle = mesh.getSettingInAngleRadians("support_tower_roof_angle");
|
||||
|
||||
const int layerThickness = storage.getSettingInMicrons("layer_height");
|
||||
const int supportXYDistance = mesh.getSettingInMicrons("support_xy_distance");
|
||||
const int support_xy_distance_overhang = mesh.getSettingInMicrons("support_xy_distance_overhang");
|
||||
|
||||
const bool use_support_xy_distance_overhang = mesh.getSettingAsSupportDistPriority("support_xy_overrides_z") == SupportDistPriority::Z_OVERRIDES_XY; // whether to use a different xy distance at overhangs
|
||||
|
||||
const double conical_support_angle = mesh.getSettingInAngleRadians("support_conical_angle");
|
||||
const bool conical_support = mesh.getSettingBoolean("support_conical_enabled") && conical_support_angle != 0;
|
||||
const int64_t conical_smallest_breadth = mesh.getSettingInMicrons("support_conical_min_width");
|
||||
|
||||
int support_skin_extruder_nr = storage.getSettingAsIndex("support_interface_extruder_nr");
|
||||
int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr");
|
||||
bool interface_enable = mesh.getSettingBoolean("support_interface_enable");
|
||||
|
||||
bool conical_support = mesh.getSettingBoolean("support_conical_enabled");
|
||||
double conical_support_angle = mesh.getSettingInAngleRadians("support_conical_angle");
|
||||
int64_t conical_smallest_breadth = mesh.getSettingInMicrons("support_conical_min_width");
|
||||
|
||||
if (conical_support_angle == 0)
|
||||
{
|
||||
conical_support = false;
|
||||
}
|
||||
|
||||
// derived settings:
|
||||
const int max_smoothing_angle = 135; // maximum angle of inner corners to be smoothed
|
||||
int smoothing_distance;
|
||||
{ // compute best smoothing_distance
|
||||
ExtruderTrain& infill_train = *storage.meshgroup->getExtruderTrain(support_infill_extruder_nr);
|
||||
int support_infill_line_width = infill_train.getSettingInMicrons("support_interface_line_width");
|
||||
smoothing_distance = support_infill_line_width;
|
||||
if (interface_enable)
|
||||
{
|
||||
ExtruderTrain& interface_train = *storage.meshgroup->getExtruderTrain(support_skin_extruder_nr);
|
||||
int support_interface_line_width = interface_train.getSettingInMicrons("support_interface_line_width");
|
||||
smoothing_distance = std::max(support_interface_line_width, smoothing_distance);
|
||||
}
|
||||
}
|
||||
|
||||
const int z_layer_distance_tower = 1; // start tower directly below overhang point
|
||||
|
||||
|
||||
int supportLayerThickness = layerThickness;
|
||||
|
||||
int layerZdistanceTop = std::max(0, supportZDistanceTop / supportLayerThickness) + 1; // support must always be 1 layer below overhang
|
||||
unsigned int layerZdistanceBottom = std::max(0, supportZDistanceBottom / supportLayerThickness);
|
||||
const unsigned int layerZdistanceTop = std::max(0U, round_up_divide(supportZDistanceTop, supportLayerThickness)) + 1; // support must always be 1 layer below overhang
|
||||
const unsigned int layerZdistanceBottom = std::max(0U, round_up_divide(supportZDistanceBottom, supportLayerThickness));
|
||||
|
||||
double tanAngle = tan(supportAngle) - 0.01; // the XY-component of the supportAngle
|
||||
int max_dist_from_lower_layer = tanAngle * supportLayerThickness; // max dist which can be bridged
|
||||
@@ -157,9 +212,8 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
|
||||
// early out
|
||||
|
||||
if ( layerZdistanceTop + 1 > (int) support_layer_count )
|
||||
if ( layerZdistanceTop + 1 > support_layer_count )
|
||||
{
|
||||
storage.support.generated = false; // no (first layer) support can be generated
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,7 +222,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
|
||||
|
||||
std::vector<std::pair<int, std::vector<Polygons>>> overhang_points; // stores overhang_points along with the layer index at which the overhang point occurs
|
||||
AreaSupport::detectOverhangPoints(storage, mesh, overhang_points, layer_count, supportMinAreaSqrt, extrusionWidth);
|
||||
AreaSupport::detectOverhangPoints(storage, mesh, overhang_points, layer_count, supportMinAreaSqrt);
|
||||
|
||||
std::deque<std::pair<Polygons, Polygons>> basic_and_full_overhang_above;
|
||||
for (unsigned int layer_idx = support_layer_count - 1; layer_idx != support_layer_count - 1 - layerZdistanceTop ; layer_idx--)
|
||||
@@ -176,7 +230,6 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
basic_and_full_overhang_above.push_front(computeBasicAndFullOverhang(storage, mesh, layer_idx, max_dist_from_lower_layer));
|
||||
}
|
||||
|
||||
bool still_in_upper_empty_layers = true;
|
||||
int overhang_points_pos = overhang_points.size() - 1;
|
||||
Polygons supportLayer_last;
|
||||
std::vector<Polygons> towerRoofs;
|
||||
@@ -193,14 +246,12 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
}
|
||||
|
||||
Polygons& supportLayer_this = overhang;
|
||||
|
||||
|
||||
if (extension_offset)
|
||||
{
|
||||
supportLayer_this = supportLayer_this.offset(extension_offset);
|
||||
}
|
||||
|
||||
supportLayer_this.simplify(50); // TODO: hardcoded value!
|
||||
|
||||
|
||||
if (supportMinAreaSqrt > 0)
|
||||
{
|
||||
// handle straight walls
|
||||
@@ -211,10 +262,11 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
|
||||
if (layer_idx+1 < support_layer_count)
|
||||
{ // join with support from layer up
|
||||
supportLayer_this = AreaSupport::join(supportLayer_last, supportLayer_this, join_distance, smoothing_distance, min_smoothing_area, conical_support, conical_support_offset, conical_smallest_breadth);
|
||||
supportLayer_this = AreaSupport::join(supportLayer_last, supportLayer_this, join_distance, smoothing_distance, max_smoothing_angle, conical_support, conical_support_offset, conical_smallest_breadth);
|
||||
}
|
||||
|
||||
|
||||
|
||||
supportLayer_this = supportLayer_this.unionPolygons(storage.support.supportLayers[layer_idx].support_mesh);
|
||||
|
||||
// move up from model
|
||||
if (layerZdistanceBottom > 0 && layer_idx >= layerZdistanceBottom)
|
||||
{
|
||||
@@ -249,12 +301,6 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
|
||||
supportAreas[layer_idx] = supportLayer_this;
|
||||
|
||||
if (still_in_upper_empty_layers && supportLayer_this.size() > 0)
|
||||
{
|
||||
storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, (int)layer_idx);
|
||||
still_in_upper_empty_layers = false;
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::SUPPORT, storage.meshes.size() * mesh_idx + support_layer_count - layer_idx, support_layer_count * storage.meshes.size());
|
||||
}
|
||||
|
||||
@@ -291,6 +337,19 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
supportAreas[layer_idx] = touching_buildplate;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (unsigned int layer_idx = supportAreas.size() - 1; layer_idx != (unsigned int) std::max(-1, storage.support.layer_nr_max_filled_layer) ; layer_idx--)
|
||||
{
|
||||
const Polygons& support_here = supportAreas[layer_idx];
|
||||
if (support_here.size() > 0)
|
||||
{
|
||||
storage.support.layer_nr_max_filled_layer = layer_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
storage.support.generated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +372,9 @@ std::pair<Polygons, Polygons> AreaSupport::computeBasicAndFullOverhang(const Sli
|
||||
Polygons supportLayer_supported = supportLayer_supporter.offset(max_dist_from_lower_layer);
|
||||
Polygons basic_overhang = supportLayer_supportee.difference(supportLayer_supported);
|
||||
|
||||
const SupportLayer& support_layer = storage.support.supportLayers[layer_idx];
|
||||
basic_overhang = basic_overhang.difference(support_layer.anti_overhang);
|
||||
|
||||
// Polygons support_extension = basic_overhang.offset(max_dist_from_lower_layer);
|
||||
// support_extension = support_extension.intersection(supportLayer_supported);
|
||||
// support_extension = support_extension.intersection(supportLayer_supportee);
|
||||
@@ -331,11 +393,12 @@ void AreaSupport::detectOverhangPoints(
|
||||
SliceMeshStorage& mesh,
|
||||
std::vector<std::pair<int, std::vector<Polygons>>>& overhang_points, // stores overhang_points along with the layer index at which the overhang point occurs)
|
||||
int layer_count,
|
||||
int supportMinAreaSqrt,
|
||||
int extrusionWidth
|
||||
)
|
||||
int supportMinAreaSqrt
|
||||
)
|
||||
{
|
||||
for (int layer_idx = 0 ; layer_idx < layer_count ; layer_idx++)
|
||||
ExtruderTrain* infill_extr = storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("support_infill_extruder_nr"));
|
||||
const unsigned int support_line_width = infill_extr->getSettingInMicrons("support_line_width");
|
||||
for (int layer_idx = 0; layer_idx < layer_count; layer_idx++)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
@@ -343,17 +406,25 @@ void AreaSupport::detectOverhangPoints(
|
||||
if (part.outline.outerPolygon().area() < supportMinAreaSqrt * supportMinAreaSqrt)
|
||||
{
|
||||
Polygons part_poly_computed;
|
||||
Polygons& part_poly = (part.insets.size() > 0)? part.insets[0] : part_poly_computed; // don't copy inset if its already computed
|
||||
if (part.insets.size() == 0) { part_poly_computed = part.outline.offset(-extrusionWidth/2); }
|
||||
Polygons& part_poly = (part.insets.size() > 0) ? part.insets[0] : part_poly_computed; // don't copy inset if its already computed
|
||||
if (part.insets.size() == 0)
|
||||
{
|
||||
part_poly_computed = part.outline.offset(-support_line_width / 2);
|
||||
}
|
||||
|
||||
if (part_poly.size() > 0)
|
||||
{
|
||||
Polygons part_poly_recomputed = part_poly.difference(storage.support.supportLayers[layer_idx].anti_overhang);
|
||||
if (part_poly_recomputed.size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (overhang_points.size() > 0 && overhang_points.back().first == layer_idx)
|
||||
overhang_points.back().second.push_back(part_poly);
|
||||
overhang_points.back().second.push_back(part_poly_recomputed);
|
||||
else
|
||||
{
|
||||
std::vector<Polygons> small_part_polys;
|
||||
small_part_polys.push_back(part_poly);
|
||||
small_part_polys.push_back(part_poly_recomputed);
|
||||
overhang_points.emplace_back<std::pair<int, std::vector<Polygons>>>(std::make_pair(layer_idx, small_part_polys));
|
||||
}
|
||||
}
|
||||
@@ -404,15 +475,21 @@ void AreaSupport::handleTowers(
|
||||
}
|
||||
|
||||
// make tower roofs
|
||||
//for (Polygons& tower_roof : towerRoofs)
|
||||
for (unsigned int r = 0; r < towerRoofs.size(); r++)
|
||||
for (unsigned int roof_idx = 0; roof_idx < towerRoofs.size(); roof_idx++)
|
||||
{
|
||||
supportLayer_this = supportLayer_this.unionPolygons(towerRoofs[r]);
|
||||
|
||||
Polygons& tower_roof = towerRoofs[r];
|
||||
if (tower_roof.size() > 0 && tower_roof[0].area() < supportTowerDiameter * supportTowerDiameter)
|
||||
Polygons& tower_roof = towerRoofs[roof_idx];
|
||||
if (tower_roof.size() > 0)
|
||||
{
|
||||
towerRoofs[r] = tower_roof.offset(towerRoofExpansionDistance);
|
||||
supportLayer_this = supportLayer_this.unionPolygons(tower_roof);
|
||||
|
||||
if (tower_roof[0].area() < supportTowerDiameter * supportTowerDiameter)
|
||||
{
|
||||
tower_roof = tower_roof.offset(towerRoofExpansionDistance);
|
||||
}
|
||||
else
|
||||
{
|
||||
tower_roof.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,25 +542,60 @@ void AreaSupport::handleWallStruts(
|
||||
}
|
||||
|
||||
|
||||
void AreaSupport::generateSupportRoofs(SliceDataStorage& storage, std::vector<Polygons>& supportAreas, unsigned int layer_count, int layerThickness, int support_roof_height)
|
||||
void AreaSupport::generateSupportInterface(SliceDataStorage& storage, const SliceMeshStorage& mesh, std::vector<Polygons>& support_areas, const unsigned int layer_count)
|
||||
{
|
||||
int roof_layer_count = support_roof_height / layerThickness;
|
||||
|
||||
const unsigned int roof_layer_count = round_divide(mesh.getSettingInMicrons("support_roof_height"), storage.getSettingInMicrons("layer_height"));
|
||||
const unsigned int bottom_layer_count = round_divide(mesh.getSettingInMicrons("support_bottom_height"), storage.getSettingInMicrons("layer_height"));
|
||||
const unsigned int z_distance_bottom = round_up_divide(mesh.getSettingInMicrons("support_bottom_distance"), storage.getSettingInMicrons("layer_height"));
|
||||
const unsigned int z_distance_top = round_up_divide(mesh.getSettingInMicrons("support_top_distance"), storage.getSettingInMicrons("layer_height"));
|
||||
|
||||
const int skip_layer_count = std::max(1u, round_divide(mesh.getSettingInMicrons("support_interface_skip_height"), storage.getSettingInMicrons("layer_height")));
|
||||
const int interface_line_width = storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("support_interface_extruder_nr"))->getSettingInMicrons("support_interface_line_width");
|
||||
|
||||
std::vector<SupportLayer>& supportLayers = storage.support.supportLayers;
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count; layer_idx++)
|
||||
{
|
||||
SupportLayer& layer = supportLayers[layer_idx];
|
||||
|
||||
if (layer_idx + roof_layer_count < supportLayers.size())
|
||||
|
||||
const unsigned int top_layer_idx_above = layer_idx + roof_layer_count + z_distance_top;
|
||||
const unsigned int bottom_layer_idx_below = std::max(0, int(layer_idx) - int(bottom_layer_count) - int(z_distance_bottom));
|
||||
if (top_layer_idx_above < supportLayers.size())
|
||||
{
|
||||
Polygons roofs = supportAreas[layer_idx].difference(supportAreas[layer_idx + roof_layer_count]);
|
||||
roofs.removeSmallAreas(1.0);
|
||||
layer.roofs.add(roofs);
|
||||
layer.supportAreas.add(supportAreas[layer_idx].difference(layer.roofs));
|
||||
Polygons roofs;
|
||||
if (roof_layer_count > 0)
|
||||
{
|
||||
Polygons model;
|
||||
const unsigned int n_scans = std::max(1u, (roof_layer_count - 1) / skip_layer_count);
|
||||
const float z_skip = std::max(1.0f, float(roof_layer_count - 1) / float(n_scans));
|
||||
for (float layer_idx_above = top_layer_idx_above; layer_idx_above > layer_idx + z_distance_top; layer_idx_above -= z_skip)
|
||||
{
|
||||
const Polygons outlines_above = mesh.layers[std::round(layer_idx_above)].getOutlines();
|
||||
model = model.unionPolygons(outlines_above);
|
||||
}
|
||||
roofs = support_areas[layer_idx].intersection(model);
|
||||
}
|
||||
Polygons bottoms;
|
||||
if (bottom_layer_count > 0)
|
||||
{
|
||||
Polygons model;
|
||||
const unsigned int n_scans = std::max(1u, (bottom_layer_count - 1) / skip_layer_count);
|
||||
const float z_skip = std::max(1.0f, float(bottom_layer_count - 1) / float(n_scans));
|
||||
for (float layer_idx_below = bottom_layer_idx_below; std::round(layer_idx_below) < (int)(layer_idx - z_distance_bottom); layer_idx_below += z_skip)
|
||||
{
|
||||
const Polygons outlines_below = mesh.layers[std::round(layer_idx_below)].getOutlines();
|
||||
model = model.unionPolygons(outlines_below);
|
||||
}
|
||||
bottoms = support_areas[layer_idx].intersection(model);
|
||||
}
|
||||
// expand skin a bit so that we're sure it's not too thin to be printed.
|
||||
Polygons skin = roofs.unionPolygons(bottoms).offset(interface_line_width).intersection(support_areas[layer_idx]);
|
||||
skin.removeSmallAreas(1.0);
|
||||
layer.skin.add(skin);
|
||||
layer.supportAreas.add(support_areas[layer_idx].difference(layer.skin));
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.roofs.add(layer.supportAreas);
|
||||
layer.skin.add(support_areas[layer_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-11
@@ -12,12 +12,11 @@ class AreaSupport {
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Generate the support areas and support roof areas for all models.
|
||||
* Generate the support areas and support skin areas for all models.
|
||||
* \param storage data storage containing the input layer outline data and containing the output support storage per layer
|
||||
* \param layer_count total number of layers
|
||||
*/
|
||||
static void generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Generate support polygons over all layers for one object.
|
||||
@@ -33,14 +32,14 @@ private:
|
||||
|
||||
|
||||
/*!
|
||||
* Generate support roof areas and non-roof areas for a given mesh.
|
||||
* Generate support skin areas and non-skin areas for a given mesh.
|
||||
*
|
||||
* \param storage Output storage: support area + support roof area output
|
||||
* \param supportAreas The basic support areas for the current mesh
|
||||
* \param layerThickness The layer height
|
||||
* \param support_roof_height The thickness of the hammock in z directiontt
|
||||
* \param storage Output storage: support area + support skin area output
|
||||
* \param mesh The mesh to generate support skins for.
|
||||
* \param support_areas The basic support areas for the current mesh
|
||||
* \param layer_count The number of layers in this mesh group.
|
||||
*/
|
||||
static void generateSupportRoofs(SliceDataStorage& storage, std::vector<Polygons>& supportAreas, unsigned int layer_count, int layerThickness, int support_roof_height);
|
||||
static void generateSupportInterface(SliceDataStorage& storage, const SliceMeshStorage& mesh, std::vector<Polygons>& support_areas, const unsigned int layer_count);
|
||||
|
||||
/*!
|
||||
* Join current support layer with the support of the layer above, (make support conical) and perform smoothing etc operations.
|
||||
@@ -64,15 +63,13 @@ private:
|
||||
* \param overhang_points stores overhang_points along with the layer index at which the overhang point occurs
|
||||
* \param layer_count total number of layers
|
||||
* \param supportMinAreaSqrt diameter of the minimal area which can be supported without a specialized strut
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
static void detectOverhangPoints(
|
||||
SliceDataStorage& storage,
|
||||
SliceMeshStorage& mesh,
|
||||
std::vector<std::pair<int, std::vector<Polygons>>>& overhang_points,
|
||||
int layer_count,
|
||||
int supportMinAreaSqrt,
|
||||
int extrusionWidth
|
||||
int supportMinAreaSqrt
|
||||
);
|
||||
|
||||
/*!
|
||||
|
||||
-317
@@ -1,317 +0,0 @@
|
||||
/*
|
||||
* The contnts of this file may be overwritten at any time!
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib> // rand
|
||||
|
||||
|
||||
|
||||
/*
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/polygon.h"
|
||||
// Test whether polygon.inside(point) returns correct results.
|
||||
void test_poly_inside_and_centerOfMass()
|
||||
{
|
||||
{
|
||||
Polygon poly;
|
||||
poly.add(Point(2000,2000)); // /
|
||||
poly.add(Point(1000,1000)); // / /
|
||||
poly.add(Point(1100,100)); // |/
|
||||
|
||||
assert (!poly.inside(Point(-2000,1000)));
|
||||
assert (poly.inside(Point(1010,1000)));
|
||||
assert (!poly.inside(Point(5000,1000)));
|
||||
assert (poly.inside(Point(1111,1100)));
|
||||
assert (!poly.inside(Point(2001,2001)));
|
||||
assert (poly.inside(Point(1999,1998)));
|
||||
|
||||
std::cerr << "poly.centerOfMass() = " << poly.centerOfMass() << std::endl;
|
||||
Point center = poly.centerOfMass();
|
||||
for (int i = 0 ; i < 1000; i++)
|
||||
{
|
||||
Point translation(rand()%4000 - 2000, rand()%4000 - 2000);
|
||||
Polygon translated;
|
||||
for (Point& p : poly)
|
||||
{
|
||||
translated.add(p + translation);
|
||||
}
|
||||
Point translated_center = center + translation;
|
||||
if (vSize2(translated.centerOfMass() - translated_center) > 5*5)
|
||||
{
|
||||
std::cerr << "ERROR! test failed! " << std::endl;
|
||||
|
||||
std::cerr << "translated.centerOfMass() = " << translated.centerOfMass() << std::endl;
|
||||
std::cerr << "translated_center = " << translated_center << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
Polygon poly;
|
||||
poly.add(Point(0,0));
|
||||
poly.add(Point(100,0)); //
|
||||
poly.add(Point(100,100)); // |\ /|
|
||||
poly.add(Point(50,50)); // | \/ |
|
||||
poly.add(Point(0,100)); // |____|
|
||||
|
||||
assert (poly.inside(Point(60,50)));
|
||||
assert (!poly.inside(Point(50,60)));
|
||||
assert (poly.inside(Point(60,40)));
|
||||
assert (poly.inside(Point(50,40)));
|
||||
assert (!poly.inside(Point(-1,100)));
|
||||
assert (!poly.inside(Point(-10,-10)));
|
||||
|
||||
std::cerr << "poly.centerOfMass() = " << poly.centerOfMass() << std::endl;
|
||||
Point center = poly.centerOfMass();
|
||||
for (int i = 0 ; i < 1000; i++)
|
||||
{
|
||||
Point translation(rand()%4000 - 2000, rand()%4000 - 2000);
|
||||
Polygon translated;
|
||||
for (Point& p : poly)
|
||||
{
|
||||
translated.add(p + translation);
|
||||
}
|
||||
Point translated_center = center + translation;
|
||||
if (vSize2(translated.centerOfMass() - translated_center) > 5*5)
|
||||
{
|
||||
std::cerr << "ERROR! test failed! " << std::endl;
|
||||
|
||||
std::cerr << "translated.centerOfMass() = " << translated.centerOfMass() << std::endl;
|
||||
std::cerr << "translated_center = " << translated_center << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
Polygon poly;
|
||||
poly.add(Point( 0,2000)); // |\ .
|
||||
poly.add(Point( 0, 0)); // | > .
|
||||
poly.add(Point(1000,1000)); // |/
|
||||
|
||||
assert (poly.inside(Point(500,1000)));
|
||||
assert (poly.inside(Point(200,500)));
|
||||
assert (poly.inside(Point(200,1500)));
|
||||
assert (poly.inside(Point(800,1000)));
|
||||
assert (!poly.inside(Point(-10,1000)));
|
||||
assert (!poly.inside(Point(1100,1000)));
|
||||
assert (!poly.inside(Point(600,500)));
|
||||
assert (!poly.inside(Point(600,1500)));
|
||||
assert (!poly.inside(Point(2000,1000)));
|
||||
|
||||
std::cerr << "poly.centerOfMass() = " << poly.centerOfMass() << std::endl;
|
||||
Point center = poly.centerOfMass();
|
||||
for (int i = 0 ; i < 1000; i++)
|
||||
{
|
||||
Point translation(rand()%4000 - 2000, rand()%4000 - 2000);
|
||||
Polygon translated;
|
||||
for (Point& p : poly)
|
||||
{
|
||||
translated.add(translation - Point(-p.X, p.Y));
|
||||
}
|
||||
Point translated_center = translation - Point(-center.X, center.Y);
|
||||
if (vSize2(translated.centerOfMass() - translated_center) > 5*5)
|
||||
{
|
||||
std::cerr << "ERROR! test failed! " << std::endl;
|
||||
std::cerr << "translated.centerOfMass() = " << translated.centerOfMass() << std::endl;
|
||||
std::cerr << "translated_center = " << translated_center << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/*
|
||||
struct LocationItem
|
||||
{
|
||||
Point p;
|
||||
int i;
|
||||
LocationItem(Point p, int i) : p(p), i(i) {};
|
||||
LocationItem() : p(0,0), i(-1) {};
|
||||
};
|
||||
|
||||
#include "utils/BucketGrid2D.h"
|
||||
void test_BucketGrid2D()
|
||||
{
|
||||
|
||||
BucketGrid2D<LocationItem> bg(1000);
|
||||
for (int i = 0; i < 20000; i++)
|
||||
{
|
||||
Point p(rand()%100000, rand()%100000);
|
||||
LocationItem li(p, i);
|
||||
bg.insert(p, li);
|
||||
}
|
||||
// {Point p(00,00); int i = 1; bg.insert(p, i);}
|
||||
// {Point p(05,05); int i = 2; bg.insert(p, i);}
|
||||
// {Point p(14,15); int i = 3; bg.insert(p, i);}
|
||||
// {Point p(25,25); int i = 4; bg.insert(p, i);}
|
||||
// {Point p(39,39); int i = 5; bg.insert(p, i);}
|
||||
// {Point p(300,300); int i = 6; bg.insert(p, i);}
|
||||
|
||||
Point to(rand()%100000,rand()%100000);
|
||||
std::cerr << to << std::endl;
|
||||
LocationItem result;
|
||||
if (bg.findNearestObject(to, result))
|
||||
{
|
||||
std::cerr << "best: " << result.p << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "nothing found!" << std::endl;
|
||||
}
|
||||
//bg.debug();
|
||||
}*/
|
||||
|
||||
/*
|
||||
#include <math.h>
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
void test_findClosestConnection()
|
||||
{
|
||||
srand(1234);
|
||||
if (false)
|
||||
{
|
||||
Polygon poly2;
|
||||
poly2.add(Point(0,300));
|
||||
poly2.add(Point(100,300)); // ____
|
||||
poly2.add(Point(100,200)); // | |
|
||||
poly2.add(Point(50,250)); // | /\ |
|
||||
poly2.add(Point(0,200)); // |/ \|
|
||||
|
||||
Polygon poly1;
|
||||
poly1.add(Point(0,0));
|
||||
poly1.add(Point(100,0)); //
|
||||
poly1.add(Point(100,100)); // |\ /|
|
||||
poly1.add(Point(50,50)); // | \/ |
|
||||
poly1.add(Point(0,100)); // |____|
|
||||
|
||||
ClosestPolygonPoint result1 (poly1);
|
||||
ClosestPolygonPoint result2 (poly2);
|
||||
|
||||
findSmallestConnection(result1, result2, 3);
|
||||
std::cerr << result1.location << " -- " << result2.location << std::endl;
|
||||
}
|
||||
|
||||
if (false)
|
||||
{
|
||||
Polygon poly2;
|
||||
poly2.add(Point(0,300));
|
||||
poly2.add(Point(100,300)); // ____
|
||||
poly2.add(Point(100,200)); // | |
|
||||
poly2.add(Point(50,250)); // | /\ |
|
||||
poly2.add(Point(10,105)); // |/ \|
|
||||
|
||||
Polygon poly1;
|
||||
poly1.add(Point(0,0));
|
||||
poly1.add(Point(100,0)); //
|
||||
poly1.add(Point(100,100)); // |\ /|
|
||||
poly1.add(Point(50,50)); // | \/ |
|
||||
poly1.add(Point(0,100)); // |____|
|
||||
|
||||
ClosestPolygonPoint result1 (poly1);
|
||||
ClosestPolygonPoint result2 (poly2);
|
||||
|
||||
findSmallestConnection(result1, result2, 3);
|
||||
std::cerr << result1.location << " -- " << result2.location << std::endl;
|
||||
}
|
||||
|
||||
double creationTime = 0;
|
||||
double evalTime = 0;
|
||||
long totalLength = 0;
|
||||
TimeKeeper timer;
|
||||
for (int i = 0; i < 10000; i++)
|
||||
{ // for vizualization as csv with e.g. Rstudio
|
||||
Polygon poly1;
|
||||
double dist = 100;
|
||||
for (double a = 0; a < 360; a += 1)
|
||||
{
|
||||
dist += int(rand()%3) -1;
|
||||
Point p(static_cast<int>(dist * std::cos(a/180.0*3.1415)), static_cast<int>(dist * std::sin(a/180.0*3.1415)));
|
||||
p = p + Point(0, 200);
|
||||
if ( a ==0)
|
||||
poly1.add(p);
|
||||
else
|
||||
poly1.add((poly1.back() + p) / 2);
|
||||
// std::cerr << poly1.back().X << ", " << poly1.back().Y << std::endl;
|
||||
}
|
||||
// std::cerr << " " << std::endl;
|
||||
Polygon poly2;
|
||||
dist = 100;
|
||||
for (double a = 0; a < 360; a += 1)
|
||||
{
|
||||
|
||||
dist += int(rand()%3) - 1;
|
||||
Point p(static_cast<int>(dist * std::cos(a/180.0*3.1415)), static_cast<int>(dist * std::sin(a/180.0*3.1415)));
|
||||
if ( a ==0)
|
||||
poly2.add(p);
|
||||
else
|
||||
poly2.add((poly2.back() + p) / 2);
|
||||
// std::cerr << poly2.back().X << ", " << poly2.back().Y << std::endl;
|
||||
}
|
||||
creationTime += timer.restart();
|
||||
ClosestPolygonPoint result1 (poly1);
|
||||
ClosestPolygonPoint result2 (poly2);
|
||||
|
||||
findSmallestConnection(result1, result2, 240);
|
||||
totalLength += vSize(result1.location - result2.location);
|
||||
evalTime += timer.restart();
|
||||
// std::cerr << " " << std::endl;
|
||||
// std::cerr << result1.location.X << " , " << result1.location.Y << std::endl;
|
||||
// std::cerr << result2.location.X << " , " << result2.location.Y << std::endl;
|
||||
// std::cerr << " " << std::endl;
|
||||
}
|
||||
|
||||
std::cerr << "creationTime : " << creationTime << std::endl;
|
||||
std::cerr << "evalTime : " << evalTime << std::endl;
|
||||
std::cerr << "totalLength : " << totalLength << std::endl;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#include "utils/polygon.h"
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void test_clipper()
|
||||
{
|
||||
Polygon p;
|
||||
p.emplace_back(0, 11004);
|
||||
p.emplace_back(0, 10129);
|
||||
p.emplace_back(0, 9185);
|
||||
p.emplace_back(0, 8477);
|
||||
p.emplace_back(1, 8491);
|
||||
p.emplace_back(418, 8861);
|
||||
p.emplace_back(1080, 9389);
|
||||
p.emplace_back(2106, 10142);
|
||||
p.emplace_back(3000, 10757);
|
||||
p.emplace_back(3000, 12010);
|
||||
p.emplace_back(3000, 12790);
|
||||
p.emplace_back(3000, 13485);
|
||||
p.emplace_back(3000, 14088);
|
||||
p.emplace_back(3000, 14601);
|
||||
p.emplace_back(3000, 15354);
|
||||
p.emplace_back(3000, 24867);
|
||||
p.emplace_back(3000, 25469);
|
||||
p.emplace_back(3000, 26303);
|
||||
p.emplace_back(3000, 27421);
|
||||
p.emplace_back(3000, 28242);
|
||||
p.emplace_back(2107, 28856);
|
||||
p.emplace_back(1080, 29610);
|
||||
p.emplace_back(608, 29986);
|
||||
p.emplace_back(1, 30508);
|
||||
p.emplace_back(1, 30522);
|
||||
p.emplace_back(0, 11772);
|
||||
|
||||
Polygons polys;
|
||||
polys.add(p);
|
||||
|
||||
// polys.debugOutputHTML("output/problem_test.html", true);
|
||||
// polys.offset(-400).debugOutputHTML("output/problem_test_offset.html", true);
|
||||
polys.removeDegenerateVerts();
|
||||
// polys.offset(-400).debugOutputHTML("output/problem_test_offset_solved.html", true);
|
||||
}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// test_findClosestConnection();
|
||||
test_clipper();
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
+40
-12
@@ -2,22 +2,33 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "timeEstimate.h"
|
||||
#include "settings/settings.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
#define MINIMUM_PLANNER_SPEED 0.05// (mm/sec)
|
||||
|
||||
const double max_feedrate[TimeEstimateCalculator::NUM_AXIS] = {600, 600, 40, 25};
|
||||
const double minimumfeedrate = 0.01;
|
||||
const double acceleration = 3000;
|
||||
const double max_acceleration[TimeEstimateCalculator::NUM_AXIS] = {9000,9000,100,10000};
|
||||
const double max_xy_jerk = 20.0;
|
||||
const double max_z_jerk = 0.4;
|
||||
const double max_e_jerk = 5.0;
|
||||
void TimeEstimateCalculator::setFirmwareDefaults(const SettingsBaseVirtual* settings_base)
|
||||
{
|
||||
max_feedrate[X_AXIS] = settings_base->getSettingInMillimetersPerSecond("machine_max_feedrate_x");
|
||||
max_feedrate[Y_AXIS] = settings_base->getSettingInMillimetersPerSecond("machine_max_feedrate_y");
|
||||
max_feedrate[Z_AXIS] = settings_base->getSettingInMillimetersPerSecond("machine_max_feedrate_z");
|
||||
max_feedrate[E_AXIS] = settings_base->getSettingInMillimetersPerSecond("machine_max_feedrate_e");
|
||||
max_acceleration[X_AXIS] = settings_base->getSettingInMillimetersPerSecond("machine_max_acceleration_x");
|
||||
max_acceleration[Y_AXIS] = settings_base->getSettingInMillimetersPerSecond("machine_max_acceleration_y");
|
||||
max_acceleration[Z_AXIS] = settings_base->getSettingInMillimetersPerSecond("machine_max_acceleration_z");
|
||||
max_acceleration[E_AXIS] = settings_base->getSettingInMillimetersPerSecond("machine_max_acceleration_e");
|
||||
max_xy_jerk = settings_base->getSettingInMillimetersPerSecond("machine_max_jerk_xy");
|
||||
max_z_jerk = settings_base->getSettingInMillimetersPerSecond("machine_max_jerk_z");
|
||||
max_e_jerk = settings_base->getSettingInMillimetersPerSecond("machine_max_jerk_e");
|
||||
minimumfeedrate = settings_base->getSettingInMillimetersPerSecond("machine_minimum_feedrate");
|
||||
acceleration = settings_base->getSettingInMillimetersPerSecond("machine_acceleration");
|
||||
}
|
||||
|
||||
template<typename T> const T square(const T& a) { return a * a; }
|
||||
|
||||
void TimeEstimateCalculator::setPosition(Position newPos)
|
||||
{
|
||||
@@ -29,6 +40,20 @@ void TimeEstimateCalculator::addTime(double time)
|
||||
extra_time += time;
|
||||
}
|
||||
|
||||
void TimeEstimateCalculator::setAcceleration(double acc)
|
||||
{
|
||||
acceleration = acc;
|
||||
}
|
||||
|
||||
void TimeEstimateCalculator::setMaxXyJerk(double jerk)
|
||||
{
|
||||
max_xy_jerk = jerk;
|
||||
}
|
||||
|
||||
void TimeEstimateCalculator::setMaxZFeedrate(double max_z_feedrate)
|
||||
{
|
||||
max_feedrate[Z_AXIS] = max_z_feedrate;
|
||||
}
|
||||
|
||||
void TimeEstimateCalculator::reset()
|
||||
{
|
||||
@@ -65,10 +90,13 @@ static inline double intersection_distance(double initial_rate, double final_rat
|
||||
// This function gives the time it needs to accelerate from an initial speed to reach a final distance.
|
||||
static inline double acceleration_time_from_distance(double initial_feedrate, double distance, double acceleration)
|
||||
{
|
||||
double discriminant = sqrt(square(initial_feedrate) - 2 * acceleration * -distance);
|
||||
return (-initial_feedrate + discriminant) / acceleration;
|
||||
double discriminant = square(initial_feedrate) - 2 * acceleration * -distance;
|
||||
//If discriminant is negative, we're moving in the wrong direction.
|
||||
//Making the discriminant 0 then gives the extremum of the parabola instead of the intersection.
|
||||
discriminant = std::max(0.0, discriminant);
|
||||
return (-initial_feedrate + sqrt(discriminant)) / acceleration;
|
||||
}
|
||||
|
||||
|
||||
// Calculates trapezoid parameters so that the entry- and exit-speed is compensated by the provided factors.
|
||||
void TimeEstimateCalculator::calculate_trapezoid_for_block(Block *block, double entry_factor, double exit_factor)
|
||||
{
|
||||
|
||||
+22
-7
@@ -6,7 +6,9 @@
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
class SettingsBaseVirtual;
|
||||
|
||||
/*!
|
||||
* The TimeEstimateCalculator class generates a estimate of printing time calculated with acceleration in mind.
|
||||
* Some of this code has been adapted from the Marlin sources.
|
||||
@@ -21,6 +23,7 @@ public:
|
||||
const static unsigned int Z_AXIS = 2;
|
||||
const static unsigned int E_AXIS = 3;
|
||||
|
||||
|
||||
class Position
|
||||
{
|
||||
public:
|
||||
@@ -54,7 +57,14 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
double extra_time;
|
||||
double max_feedrate[NUM_AXIS] = {600, 600, 40, 25};
|
||||
double minimumfeedrate = 0.01;
|
||||
double acceleration = 3000;
|
||||
double max_acceleration[NUM_AXIS] = {9000, 9000, 100, 10000};
|
||||
double max_xy_jerk = 20.0;
|
||||
double max_z_jerk = 0.4;
|
||||
double max_e_jerk = 5.0;
|
||||
double extra_time = 0.0;
|
||||
|
||||
Position previous_feedrate;
|
||||
double previous_nominal_feedrate;
|
||||
@@ -63,14 +73,19 @@ private:
|
||||
|
||||
std::vector<Block> blocks;
|
||||
public:
|
||||
TimeEstimateCalculator()
|
||||
: extra_time(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the movement configuration of the firmware.
|
||||
*
|
||||
* \param settings_base Where to get the settings from
|
||||
*/
|
||||
void setFirmwareDefaults(const SettingsBaseVirtual* settings_base);
|
||||
void setPosition(Position newPos);
|
||||
void plan(Position newPos, double feedRate);
|
||||
void addTime(double time);
|
||||
void setAcceleration(double acc); //!< Set the default acceleration to \p acc
|
||||
void setMaxXyJerk(double jerk); //!< Set the max xy jerk to \p jerk
|
||||
void setMaxZFeedrate(double max_z_feedrate); //!< Set the maximal feedrate in the z direction to \p max_z_feedrate
|
||||
|
||||
void reset();
|
||||
|
||||
double calculate();
|
||||
|
||||
@@ -24,6 +24,12 @@ AABB::AABB(const Polygons& polys)
|
||||
calculate(polys);
|
||||
}
|
||||
|
||||
AABB::AABB(const PolygonRef poly)
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
calculate(poly);
|
||||
}
|
||||
|
||||
void AABB::calculate(const Polygons& polys)
|
||||
{
|
||||
min = Point(POINT_MAX, POINT_MAX);
|
||||
@@ -37,6 +43,16 @@ void AABB::calculate(const Polygons& polys)
|
||||
}
|
||||
}
|
||||
|
||||
void AABB::calculate(const PolygonRef poly)
|
||||
{
|
||||
min = Point(POINT_MAX, POINT_MAX);
|
||||
max = Point(POINT_MIN, POINT_MIN);
|
||||
for (const Point& p : poly)
|
||||
{
|
||||
include(p);
|
||||
}
|
||||
}
|
||||
|
||||
bool AABB::hit(const AABB& other) const
|
||||
{
|
||||
if (max.X < other.min.X) return false;
|
||||
|
||||
@@ -20,8 +20,10 @@ public:
|
||||
AABB(); //!< initializes with invalid min and max
|
||||
AABB(Point& min, Point& max); //!< initializes with given min and max
|
||||
AABB(const Polygons& polys); //!< Computes the boundary box for the given polygons
|
||||
AABB(const PolygonRef poly); //!< Computes the boundary box for the given polygons
|
||||
|
||||
void calculate(const Polygons& polys); //!< Calculates the aabb for the given polygons (throws away old min and max data of this aabb)
|
||||
void calculate(const PolygonRef poly); //!< Calculates the aabb for the given polygon (throws away old min and max data of this aabb)
|
||||
|
||||
/*!
|
||||
* Check whether this aabb overlaps with another.
|
||||
|
||||
+20
-6
@@ -15,12 +15,15 @@ AABB3D::AABB3D()
|
||||
|
||||
bool AABB3D::hit(const AABB3D& other) const
|
||||
{
|
||||
if (max.x < other.min.y) return false;
|
||||
if (min.x > other.max.y) return false;
|
||||
if (max.y < other.min.y) return false;
|
||||
if (min.y > other.max.y) return false;
|
||||
if (max.z < other.min.z) return false;
|
||||
if (min.z > other.max.z) return false;
|
||||
if ( max.x < other.min.x
|
||||
|| min.x > other.max.x
|
||||
|| max.y < other.min.y
|
||||
|| min.y > other.max.y
|
||||
|| max.z < other.min.z
|
||||
|| min.z > other.max.z)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -46,5 +49,16 @@ void AABB3D::offset(Point offset)
|
||||
max += offset;
|
||||
}
|
||||
|
||||
void AABB3D::expandXY(int outset)
|
||||
{
|
||||
min -= Point3(outset, outset, 0);
|
||||
max += Point3(outset, outset, 0);
|
||||
if (min.x > max.x || min.y > max.y)
|
||||
{ // make this AABB3D invalid
|
||||
*this = AABB3D();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
@@ -49,6 +49,13 @@ struct AABB3D
|
||||
* \param offset The offset with which to offset the AABB3D.
|
||||
*/
|
||||
void offset(Point offset);
|
||||
|
||||
/*!
|
||||
* Offset the bounding box in the horizontal direction; outward or inward.
|
||||
*
|
||||
* \param outset the distance (positive or negative) to expand the bounding box outward
|
||||
*/
|
||||
void expandXY(int outset);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_BUCKET_GRID_2D_H
|
||||
#define UTILS_BUCKET_GRID_2D_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <functional> // std::function
|
||||
|
||||
#include "logoutput.h"
|
||||
#include "intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Container for items with location for which the lookup for nearby items is optimized.
|
||||
*
|
||||
* It functions by hashing the items location and lookuping up based on the hash of that location and the hashes of nearby locations.
|
||||
*
|
||||
* We're mapping a cell location multiple times to an object within the cell,
|
||||
* instead of mapping each cell location only once to a vector of objects within the cell.
|
||||
*
|
||||
* The first (current) implementation has the overhead of 'bucket-collisions' where all mappings of two different cells get placed in the same bucket,
|
||||
* which causes findNearby to loop over unneeded elements.
|
||||
* The second (alternative) implementation has the overhead and indirection of creating vectors and all that comes with it."
|
||||
*
|
||||
*/
|
||||
template<typename T>
|
||||
class BucketGrid2D
|
||||
{
|
||||
private:
|
||||
|
||||
typedef Point Cellidx;
|
||||
/*!
|
||||
* Returns a point for which the hash is at a grid position of \p relative_hash relative to \p p.
|
||||
*
|
||||
* \param p The point for which to get the relative point to hash
|
||||
* \param relative_hash The relative position - in grid terms - of the relative point.
|
||||
* \return A point for which the hash is at a grid position of \p relative_hash relative to \p p.
|
||||
*/
|
||||
inline Point getRelativeForHash(const Point& p, const Cellidx& relative_hash) const
|
||||
{
|
||||
return p + relative_hash * squareSize;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* A hash class representing the hash function object.
|
||||
*/
|
||||
struct PointHasher
|
||||
{
|
||||
|
||||
/*!
|
||||
* The basic hash function for a 2D grid position.
|
||||
* \param p The grid location to hash
|
||||
* \return the hash
|
||||
*/
|
||||
inline uint32_t pointHash_simple(const Cellidx& p) const
|
||||
{
|
||||
return p.X ^ (p.Y << 8);
|
||||
}
|
||||
|
||||
/*!
|
||||
* The hash function for a 2D position.
|
||||
* \param point The location to hash
|
||||
* \return the hash
|
||||
*/
|
||||
inline uint32_t pointHash(const Point& point) const
|
||||
{
|
||||
Cellidx p = point / squareSize;
|
||||
return pointHash_simple(p);
|
||||
}
|
||||
/*
|
||||
inline uint32_t pointHash(const Point& point, const Point& relativeHash) const
|
||||
{
|
||||
Point p = p / squareSize + relativeHash;
|
||||
return pointHash_simple(p);
|
||||
}*/
|
||||
|
||||
/*!
|
||||
* The horizontal and vertical size of a cell in the grid; the width and height of a bucket.
|
||||
*/
|
||||
int squareSize;
|
||||
|
||||
/*!
|
||||
* Basic constructor.
|
||||
* \param squareSize The horizontal and vertical size of a cell in the grid; the width and height of a bucket.
|
||||
*/
|
||||
PointHasher(int squareSize) : squareSize(squareSize) {};
|
||||
|
||||
/*!
|
||||
* See PointHasher::pointHash
|
||||
*/
|
||||
uint32_t operator()(const Point& p) const { return pointHash(p); };
|
||||
|
||||
};
|
||||
|
||||
/*!
|
||||
* A helper predicate object which allways returns false when comparing two objects.
|
||||
*
|
||||
* This is used for mapping each point to a unique object, even when two objects have the same point associated with it.
|
||||
*/
|
||||
struct NeverEqual
|
||||
{
|
||||
template<typename S>
|
||||
bool operator()(const S& p1, const S& p2) const { return false; };
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Basic constructor.
|
||||
* \param squareSize The horizontal and vertical size of a cell in the grid; the width and height of a bucket.
|
||||
*/
|
||||
int squareSize;
|
||||
|
||||
PointHasher point_hasher; //!< The hasher used by the unordered_map
|
||||
|
||||
int max_load_factor; //!< The average number of elements per cell/bucket
|
||||
/*!
|
||||
* The map type used to associate points with their objects.
|
||||
*/
|
||||
typedef typename std::unordered_map<Point, T, PointHasher, NeverEqual> Map;
|
||||
|
||||
/*!
|
||||
* The map used to associate points with their objects.
|
||||
*/
|
||||
Map point2object;
|
||||
|
||||
|
||||
public:
|
||||
/*!
|
||||
* The constructor for a bucket grid.
|
||||
*
|
||||
* \param squareSize The horizontal and vertical size of a cell in the grid; the width and height of a bucket.
|
||||
* \param initial_map_size The number of elements to be inserted
|
||||
*/
|
||||
BucketGrid2D(int squareSize, unsigned int initial_map_size = 4)
|
||||
: squareSize(squareSize)
|
||||
, point_hasher(squareSize)
|
||||
, max_load_factor(2)
|
||||
, point2object(initial_map_size / max_load_factor, point_hasher)
|
||||
{
|
||||
point2object.max_load_factor(max_load_factor); // we expect each cell to contain at least two points on average
|
||||
point2object.reserve(initial_map_size);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Find all objects with a point in a grid cell at a distance of one cell from the cell of \p p.
|
||||
*
|
||||
* \warning Objects may occur multiple times in the output!
|
||||
*
|
||||
* \param p The point for which to find close points.
|
||||
* \param ret Ouput parameter: all objects close to \p p.
|
||||
*/
|
||||
void findNearbyObjects(Point& p, std::vector<T>& ret) const
|
||||
{
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
for (int y = -1; y <= 1; y++)
|
||||
{
|
||||
Point relative_point = getRelativeForHash(p, Point(x,y));
|
||||
int bucket_idx = point2object.bucket(relative_point); // when the hash is not a hash of a present item, the bucket_idx returned may be one already encountered
|
||||
for ( auto local_it = point2object.begin(bucket_idx); local_it!= point2object.end(bucket_idx); ++local_it )
|
||||
{
|
||||
if (point_hasher(relative_point) == point_hasher(local_it->first))
|
||||
{
|
||||
ret.push_back(local_it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Find all objects with a point in a grid cell at a distance of one cell from the cell of \p p.
|
||||
*
|
||||
* \warning Objects may occur multiple times in the output!
|
||||
*
|
||||
* \param p The point for which to find close points.
|
||||
* \return All objects close to \p p.
|
||||
*/
|
||||
std::vector<T> findNearbyObjects(Point& p) const
|
||||
{
|
||||
std::vector<T> ret;
|
||||
findNearbyObjects(p, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const std::function<bool(Point, const T&)> no_precondition;
|
||||
|
||||
/*!
|
||||
* Find the nearest object to a given lcoation \p p, if there is any in a neighboring cell in the grid.
|
||||
*
|
||||
* \param p The point for which to find the nearest object.
|
||||
* \param nearby Output parameter: the nearest object, if any
|
||||
* \param precondition A precondition which must be satisfied before considering a \p object at a specific \p location as output
|
||||
* \return Whether an object has been found.
|
||||
*/
|
||||
bool findNearestObject(Point& p, T& nearby, std::function<bool(Point location, const T& object)> precondition = no_precondition) const
|
||||
{
|
||||
bool found = false;
|
||||
int64_t bestDist2 = squareSize * 9; // 9 > sqrt(2*2 + 2*2)^2 which is the square of the largest distance of a point to a point in a neighboring cell
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
for (int y = -1; y <= 1; y++)
|
||||
{
|
||||
int bucket_idx = point2object.bucket(getRelativeForHash(p, Point(x,y)));
|
||||
for ( auto local_it = point2object.begin(bucket_idx); local_it!= point2object.end(bucket_idx); ++local_it )
|
||||
{
|
||||
if (!precondition(local_it->first, local_it->second))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int32_t dist2 = vSize2(local_it->first - p);
|
||||
if (dist2 < bestDist2)
|
||||
{
|
||||
found = true;
|
||||
nearby = local_it->second;
|
||||
bestDist2 = dist2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* Insert a new point into the bucket grid.
|
||||
*
|
||||
* \param p The location associated with \p t.
|
||||
* \param t The object to insert in the grid cell for position \p p.
|
||||
*/
|
||||
void insert(Point& p, T t)
|
||||
{
|
||||
// typedef typename Map::iterator iter;
|
||||
// std::pair<iter, bool> emplaced =
|
||||
point2object.emplace(p, t);
|
||||
// if (! emplaced.second)
|
||||
// logError("Error! BucketGrid2D couldn't insert object!");
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
template<typename T>
|
||||
const std::function<bool(Point, const T&)> BucketGrid2D<T>::no_precondition = [](Point loc, const T&) { return true; };
|
||||
|
||||
}//namespace cura
|
||||
#endif//BUCKET_GRID_2D_H
|
||||
@@ -0,0 +1,129 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_LAZY_INITIALIZATION_H
|
||||
#define UTILS_LAZY_INITIALIZATION_H
|
||||
|
||||
#include <functional> // bind, function
|
||||
|
||||
#include "optional.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for initializing an object only when it's requested
|
||||
*
|
||||
* Credits to Johannes Goller
|
||||
*
|
||||
* \tparam T The type of the object to instantiate lazily
|
||||
* \tparam Args The types of the arguments to the constructor or constructor function object
|
||||
*/
|
||||
template <typename T, typename... Args>
|
||||
class LazyInitialization : public std::optional<T>
|
||||
{
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Delayed constructor call of T class
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the constructor at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[args...]()
|
||||
{
|
||||
return new T(args...);
|
||||
}
|
||||
)
|
||||
{ }
|
||||
|
||||
/*!
|
||||
* Delayed function call for creating a T object
|
||||
*
|
||||
* Performs a copy from the return value of the function on the stack to the heap.
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the function object at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(const std::function<T (Args...)>& f, Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[f, args...]()
|
||||
{
|
||||
return new T(f(args...));
|
||||
}
|
||||
)
|
||||
{ }
|
||||
|
||||
/*!
|
||||
* Delayed function call for creating a T object
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the function object at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(const std::function<T* (Args...)>& f, Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[f, args...]()
|
||||
{
|
||||
return f(args...);
|
||||
}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
LazyInitialization(LazyInitialization<T, Args...>& other) //!< copy constructor
|
||||
: std::optional<T>(other)
|
||||
, constructor(other.constructor)
|
||||
{
|
||||
}
|
||||
|
||||
LazyInitialization(LazyInitialization<T, Args...>&& other) //!< move constructor
|
||||
: std::optional<T>(other)
|
||||
{
|
||||
constructor = std::move(other.constructor);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Dereference this lazy object
|
||||
*
|
||||
* Calls constructor if object isn't constructed yet.
|
||||
*/
|
||||
T& operator*()
|
||||
{
|
||||
if (!std::optional<T>::instance)
|
||||
{
|
||||
std::optional<T>::instance = constructor();
|
||||
}
|
||||
return std::optional<T>::operator*();
|
||||
}
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
if (!std::optional<T>::instance)
|
||||
{
|
||||
std::optional<T>::instance = constructor();
|
||||
}
|
||||
return std::optional<T>::operator->();
|
||||
}
|
||||
|
||||
LazyInitialization<T, Args...>& operator=(LazyInitialization<T, Args...>&& other)
|
||||
{
|
||||
std::optional<T>::operator=(other);
|
||||
constructor = other.constructor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(LazyInitialization<T, Args...>& other)
|
||||
{
|
||||
std::optional<T>::swap(other);
|
||||
std::swap(constructor, other.constructor);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<T* ()> constructor;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif // UTILS_LAZY_INITIALIZATION_H
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário