Comparar commits
564 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| d50f67e583 | |||
| b2d837efde | |||
| c0495edf48 | |||
| 1574292945 | |||
| ed3f9f107c | |||
| 18bf08a1b5 | |||
| 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 | |||
| 71ea91b61b | |||
| ddf7bbe06e | |||
| a50d6fe5a9 | |||
| 0bd5ae9e01 | |||
| ce85280971 | |||
| 019116cd33 | |||
| b0f725c1e5 | |||
| 288f053abd | |||
| 0fd0dd6869 | |||
| dee0ae12d6 | |||
| c2571d41af | |||
| c8fd58376b | |||
| bce0a8a3a8 | |||
| 25c2f7086d | |||
| 941f4e3a66 | |||
| 6d8d453df9 | |||
| 90c212b52e | |||
| 4fd499f7a1 | |||
| 28aedf57c5 | |||
| 28e6a54961 | |||
| b1b49f16e0 | |||
| 6b72d52c65 | |||
| 07d416691a | |||
| 960c56a9fa | |||
| c202d1b47a | |||
| 0b05457fac | |||
| fb2dac0f96 | |||
| e2e9cf8ea8 | |||
| 57ec92a645 | |||
| 6a26649fe4 | |||
| 5334fb881e | |||
| bc9d7aced8 | |||
| 5bb794dc91 | |||
| 7d928703a5 | |||
| 8961df4342 |
+10
-4
@@ -28,10 +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
|
||||
@@ -68,7 +69,7 @@ set(engine_SRCS # Except main.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
|
||||
@@ -100,9 +101,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.
|
||||
@@ -112,9 +116,11 @@ set(engine_TEST
|
||||
set(engine_TEST_INFILL
|
||||
)
|
||||
set(engine_TEST_UTILS
|
||||
BucketGrid2DTest
|
||||
SparseGridTest
|
||||
LinearAlg2DTest
|
||||
PolygonUtilsTest
|
||||
PolygonTest
|
||||
StringTest
|
||||
)
|
||||
|
||||
# Generating ProtoBuf protocol
|
||||
@@ -166,4 +172,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)
|
||||
|
||||
+33
-3
@@ -13,6 +13,7 @@ message Slice
|
||||
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
|
||||
@@ -55,19 +56,42 @@ message Polygon {
|
||||
SupportInfillType = 7;
|
||||
MoveCombingType = 8;
|
||||
MoveRetractionType = 9;
|
||||
SupportInterfaceType = 10;
|
||||
}
|
||||
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 PrintTimeMaterialEstimates { // The print time for the whole print and material estimates for each extruder
|
||||
float time = 1; // Total time estimate
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -86,8 +110,14 @@ message Setting {
|
||||
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; // Header string to be prenpended before the rest of the gcode sent from the engine
|
||||
bytes data = 2; //Header string to be prepended before the rest of the g-code sent from the engine.
|
||||
}
|
||||
|
||||
message SlicingFinished {
|
||||
|
||||
+13
-13
@@ -19,33 +19,33 @@ 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
|
||||
=======
|
||||
|
||||
+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
|
||||
|
||||
|
||||
@@ -40,6 +40,12 @@
|
||||
"label": "Mesh position z",
|
||||
"default_value": 0
|
||||
},
|
||||
"mesh_rotation_matrix": {
|
||||
"label": "Mesh Rotation Matrix",
|
||||
"description": "Transformation matrix to be applied to the model when loading it from file.",
|
||||
"type": "string",
|
||||
"default_value": "[[1,0,0], [0,1,0], [0,0,1]]"
|
||||
},
|
||||
"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": "bool",
|
||||
|
||||
@@ -20,7 +20,7 @@ void ConicalOverhang::apply(Slicer* slicer, double angle, int layer_thickness)
|
||||
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, 100*100);
|
||||
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
|
||||
|
||||
@@ -13,4 +13,15 @@ ExtruderTrain::ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr)
|
||||
{
|
||||
}
|
||||
|
||||
bool ExtruderTrain::getIsUsed() const
|
||||
{
|
||||
return is_used;
|
||||
}
|
||||
|
||||
void ExtruderTrain::setIsUsed(bool used)
|
||||
{
|
||||
is_used = used;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -10,9 +10,13 @@ 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();
|
||||
|
||||
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);
|
||||
|
||||
};
|
||||
|
||||
+237
-150
@@ -1,6 +1,7 @@
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "FffGcodeWriter.h"
|
||||
#include "FffProcessor.h"
|
||||
#include "progress/Progress.h"
|
||||
@@ -44,11 +45,11 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
|
||||
initConfigs(storage);
|
||||
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
{ // skirt
|
||||
storage.skirt_config[extruder].setLayerHeight(getSettingInMicrons("layer_height_0"));
|
||||
{ //Skirt and brim.
|
||||
storage.skirt_brim_config[extruder].setLayerHeight(getSettingInMicrons("layer_height_0"));
|
||||
skirt_brim_is_processed[extruder] = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
layer_plan_buffer.setPreheatConfig(*storage.meshgroup);
|
||||
|
||||
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
|
||||
@@ -72,14 +73,16 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
|
||||
if (has_raft)
|
||||
{
|
||||
processRaft(storage, total_layers);
|
||||
// process filler layers to fill the airgap with helper object (support etc) so that they stick better to the raft.
|
||||
for (int layer_nr = -Raft::getFillerLayerCount(storage); layer_nr < 0; layer_nr++)
|
||||
{
|
||||
processLayer(storage, layer_nr, total_layers);
|
||||
}
|
||||
}
|
||||
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
last_prime_tower_poly_printed[extruder] = -1; // layer 0 has its prime tower printed during the brim (?)
|
||||
|
||||
|
||||
for(unsigned int layer_nr=0; layer_nr<total_layers; layer_nr++)
|
||||
{
|
||||
processLayer(storage, layer_nr, total_layers, has_raft);
|
||||
processLayer(storage, layer_nr, total_layers);
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::FINISH, &time_keeper);
|
||||
@@ -88,6 +91,9 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
|
||||
max_object_height = std::max(max_object_height, storage.model_max.z);
|
||||
|
||||
layer_plan_buffer.flush();
|
||||
|
||||
constexpr bool force = true;
|
||||
gcode.writeRetraction(&storage.retraction_config_per_extruder[gcode.getExtruderNr()], force); // retract after finishing each meshgroup
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setConfigFanSpeedLayerTime(SliceDataStorage& storage)
|
||||
@@ -102,7 +108,12 @@ void FffGcodeWriter::setConfigFanSpeedLayerTime(SliceDataStorage& storage)
|
||||
fan_speed_layer_time_settings.cool_fan_speed_min = train->getSettingInPercentage("cool_fan_speed_min");
|
||||
fan_speed_layer_time_settings.cool_fan_speed_max = train->getSettingInPercentage("cool_fan_speed_max");
|
||||
fan_speed_layer_time_settings.cool_min_speed = train->getSettingInMillimetersPerSecond("cool_min_speed");
|
||||
fan_speed_layer_time_settings.cool_fan_full_layer = train->getSettingAsCount("cool_fan_full_layer");
|
||||
fan_speed_layer_time_settings.cool_fan_full_layer = train->getSettingAsLayerNumber("cool_fan_full_layer");
|
||||
if (!train->getSettingBoolean("cool_fan_enabled"))
|
||||
{
|
||||
fan_speed_layer_time_settings.cool_fan_speed_min = 0;
|
||||
fan_speed_layer_time_settings.cool_fan_speed_max = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,17 +162,18 @@ void FffGcodeWriter::setConfigRetraction(SliceDataStorage& storage)
|
||||
void FffGcodeWriter::initConfigs(SliceDataStorage& storage)
|
||||
{
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
{ // skirt
|
||||
{ //Skirt and brim.
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
storage.skirt_config[extruder].init(train->getSettingInMillimetersPerSecond("skirt_speed"), train->getSettingInMillimetersPerSecond("acceleration_skirt"), train->getSettingInMillimetersPerSecond("jerk_skirt"), train->getSettingInMicrons("skirt_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
storage.skirt_brim_config[extruder].init(train->getSettingInMillimetersPerSecond("skirt_brim_speed"), train->getSettingInMillimetersPerSecond("acceleration_skirt_brim"), train->getSettingInMillimetersPerSecond("jerk_skirt_brim"), train->getSettingInMicrons("skirt_brim_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
storage.travel_config_per_extruder[extruder].init(train->getSettingInMillimetersPerSecond("speed_travel"), train->getSettingInMillimetersPerSecond("acceleration_travel"), train->getSettingInMillimetersPerSecond("jerk_travel"), 0, 0);
|
||||
}
|
||||
|
||||
{ // support
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_infill_extruder_nr"));
|
||||
storage.support_config.init(getSettingInMillimetersPerSecond("speed_support_infill"), getSettingInMillimetersPerSecond("acceleration_support_infill"), getSettingInMillimetersPerSecond("jerk_support_infill"), getSettingInMicrons("support_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
|
||||
storage.support_roof_config.init(getSettingInMillimetersPerSecond("speed_support_roof"), getSettingInMillimetersPerSecond("acceleration_support_roof"), getSettingInMillimetersPerSecond("jerk_support_roof"), getSettingInMicrons("support_roof_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
SettingsBase* infill_train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_infill_extruder_nr"));
|
||||
storage.support_config.init(infill_train->getSettingInMillimetersPerSecond("speed_support_infill"), infill_train->getSettingInMillimetersPerSecond("acceleration_support_infill"), infill_train->getSettingInMillimetersPerSecond("jerk_support_infill"), infill_train->getSettingInMicrons("support_line_width"), infill_train->getSettingInPercentage("material_flow"));
|
||||
|
||||
SettingsBase* interface_train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_interface_extruder_nr"));
|
||||
storage.support_skin_config.init(interface_train->getSettingInMillimetersPerSecond("speed_support_interface"), interface_train->getSettingInMillimetersPerSecond("acceleration_support_interface"), interface_train->getSettingInMillimetersPerSecond("jerk_support_interface"), interface_train->getSettingInMicrons("support_interface_line_width"), interface_train->getSettingInPercentage("material_flow"));
|
||||
}
|
||||
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
@@ -176,7 +188,7 @@ void FffGcodeWriter::initConfigs(SliceDataStorage& storage)
|
||||
}
|
||||
}
|
||||
|
||||
storage.primeTower.initConfigs(storage.meshgroup, storage.retraction_config_per_extruder);
|
||||
storage.primeTower.initConfigs(storage.meshgroup);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
@@ -231,6 +243,7 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
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);
|
||||
ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(start_extruder_nr);
|
||||
constexpr bool wait = true;
|
||||
@@ -245,6 +258,7 @@ void FffGcodeWriter::processNextMeshGroupCode(SliceDataStorage& storage)
|
||||
{
|
||||
gcode.writeFanCommand(0);
|
||||
gcode.resetExtrusionValue();
|
||||
CommandSocket::setSendCurrentPosition(gcode.getPositionXY());
|
||||
gcode.setZ(max_object_height + 5000);
|
||||
gcode.writeMove(gcode.getPositionXY(), storage.meshgroup->getExtruderTrain(gcode.getExtruderNr())->getSettingInMillimetersPerSecond("speed_travel"), 0);
|
||||
last_position_planned = Point(storage.model_min.x, storage.model_min.y);
|
||||
@@ -257,9 +271,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
|
||||
CombingMode combing_mode = storage.getSettingAsCombingMode("retraction_combing");
|
||||
|
||||
int n_raft_surface_layers = train->getSettingAsCount("raft_surface_layers");
|
||||
|
||||
|
||||
int z = 0;
|
||||
|
||||
{ // set configs
|
||||
@@ -272,7 +284,9 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
storage.raft_surface_config.init(train->getSettingInMillimetersPerSecond("raft_surface_speed"), train->getSettingInMillimetersPerSecond("raft_surface_acceleration"), train->getSettingInMillimetersPerSecond("raft_surface_jerk"), train->getSettingInMicrons("raft_surface_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
storage.raft_surface_config.setLayerHeight(train->getSettingInMicrons("raft_surface_thickness"));
|
||||
}
|
||||
|
||||
|
||||
const int initial_raft_layer_nr = -Raft::getTotalExtraLayers(storage);
|
||||
|
||||
// some infill config for all lines infill generation below
|
||||
int offset_from_poly_outline = 0;
|
||||
double fill_overlap = 0; // raft line shouldn't be expanded - there is no boundary polygon printed
|
||||
@@ -281,7 +295,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
|
||||
{ // raft base layer
|
||||
|
||||
int layer_nr = -n_raft_surface_layers - 2;
|
||||
int layer_nr = initial_raft_layer_nr;
|
||||
int layer_height = train->getSettingInMicrons("raft_base_thickness");
|
||||
z += layer_height;
|
||||
int64_t comb_offset = train->getSettingInMicrons("raft_base_line_spacing");
|
||||
@@ -294,13 +308,15 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
}
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
|
||||
CommandSocket::getInstance()->sendOptimizedLayerInfo(layer_nr, z, layer_height);
|
||||
}
|
||||
gcode_layer.addPolygonsByOptimizer(storage.raftOutline, &storage.raft_base_config);
|
||||
|
||||
Polygons wall = storage.raftOutline.offset(-storage.raft_base_config.getLineWidth() / 2);
|
||||
gcode_layer.addPolygonsByOptimizer(wall, &storage.raft_base_config);
|
||||
|
||||
Polygons raftLines;
|
||||
double fill_angle = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, storage.raft_base_config.getLineWidth(), train->getSettingInMicrons("raft_base_line_spacing"), fill_overlap, fill_angle, z, extra_infill_shift);
|
||||
Infill infill_comp(EFillMethod::LINES, wall, offset_from_poly_outline, storage.raft_base_config.getLineWidth(), train->getSettingInMicrons("raft_base_line_spacing"), fill_overlap, fill_angle, z, extra_infill_shift);
|
||||
infill_comp.generate(raft_polygons, raftLines);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_base_config, SpaceFillType::Lines);
|
||||
|
||||
@@ -313,7 +329,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
}
|
||||
|
||||
{ // raft interface layer
|
||||
int layer_nr = -n_raft_surface_layers - 1;
|
||||
int layer_nr = initial_raft_layer_nr + 1;
|
||||
int layer_height = train->getSettingInMicrons("raft_interface_thickness");
|
||||
z += layer_height;
|
||||
int64_t comb_offset = train->getSettingInMicrons("raft_interface_line_spacing");
|
||||
@@ -322,7 +338,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
|
||||
CommandSocket::getInstance()->sendOptimizedLayerInfo(layer_nr, z, layer_height);
|
||||
}
|
||||
|
||||
Polygons raftLines;
|
||||
@@ -342,17 +358,17 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
|
||||
int layer_height = train->getSettingInMicrons("raft_surface_thickness");
|
||||
|
||||
for (int raftSurfaceLayer=1; raftSurfaceLayer <= n_raft_surface_layers; raftSurfaceLayer++)
|
||||
for (int raftSurfaceLayer = 1; raftSurfaceLayer <= train->getSettingAsCount("raft_surface_layers"); raftSurfaceLayer++)
|
||||
{ // raft surface layers
|
||||
int layer_nr = -n_raft_surface_layers + raftSurfaceLayer - 1;
|
||||
const int layer_nr = initial_raft_layer_nr + 2 + raftSurfaceLayer - 1; // 2: 1 base layer, 1 interface layer
|
||||
z += layer_height;
|
||||
int64_t comb_offset = train->getSettingInMicrons("raft_surface_line_spacing");
|
||||
const int64_t comb_offset = train->getSettingInMicrons("raft_surface_line_spacing");
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings_per_extruder, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
gcode_layer.setIsInside(true);
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
|
||||
CommandSocket::getInstance()->sendOptimizedLayerInfo(layer_nr, z, layer_height);
|
||||
}
|
||||
|
||||
Polygons raft_lines;
|
||||
@@ -371,14 +387,39 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_nr, unsigned int total_layers, bool has_raft)
|
||||
void FffGcodeWriter::processLayer(SliceDataStorage& storage, int layer_nr, unsigned int total_layers)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::EXPORT, layer_nr+1, total_layers);
|
||||
|
||||
Progress::messageProgress(Progress::Stage::EXPORT, std::max(0, layer_nr) + 1, total_layers);
|
||||
logDebug("GcodeWriter processing layer %i of %i\n", layer_nr, total_layers);
|
||||
|
||||
int layer_thickness = getSettingInMicrons("layer_height");
|
||||
if (layer_nr == 0)
|
||||
int64_t z;
|
||||
bool include_helper_parts = true;
|
||||
if (layer_nr < 0)
|
||||
{
|
||||
layer_thickness = getSettingInMicrons("layer_height_0");
|
||||
#ifdef DEBUG
|
||||
assert(getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT && "negative layer_number means post-raft, pre-model layer!");
|
||||
#endif // DEBUG
|
||||
const int filler_layer_count = Raft::getFillerLayerCount(storage);
|
||||
layer_thickness = Raft::getFillerLayerHeight(storage);
|
||||
z = Raft::getTotalThickness(storage) + (filler_layer_count + layer_nr + 1) * layer_thickness;
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendOptimizedLayerInfo(layer_nr, z, layer_thickness);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
z = storage.meshes[0].layers[layer_nr].printZ;
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
if (getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT)
|
||||
{
|
||||
include_helper_parts = false;
|
||||
}
|
||||
layer_thickness = getSettingInMicrons("layer_height_0");
|
||||
}
|
||||
}
|
||||
|
||||
bool avoid_other_parts = false;
|
||||
@@ -404,58 +445,69 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_
|
||||
}
|
||||
int64_t comb_offset_from_outlines = max_inner_wall_width * 2;
|
||||
|
||||
int64_t z = storage.meshes[0].layers[layer_nr].printZ;
|
||||
|
||||
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_thickness, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings_per_extruder, getSettingAsCombingMode("retraction_combing"), comb_offset_from_outlines, avoid_other_parts, avoid_distance);
|
||||
|
||||
if (layer_nr == 0)
|
||||
{ // process the skirt of the starting extruder
|
||||
if (include_helper_parts && layer_nr == 0)
|
||||
{ // process the skirt or the brim of the starting extruder.
|
||||
int extruder_nr = getSettingAsIndex("adhesion_extruder_nr");
|
||||
if (storage.skirt[extruder_nr].size() > 0)
|
||||
if (storage.skirt_brim[extruder_nr].size() > 0)
|
||||
{
|
||||
gcode_layer.setExtruder(extruder_nr);
|
||||
processSkirt(storage, gcode_layer, extruder_nr);
|
||||
processSkirtBrim(storage, gcode_layer, extruder_nr);
|
||||
}
|
||||
}
|
||||
|
||||
int extruder_nr_before = gcode_layer.getExtruder();
|
||||
addSupportToGCode(storage, gcode_layer, layer_nr, extruder_nr_before, true);
|
||||
|
||||
processOozeShield(storage, gcode_layer, layer_nr);
|
||||
|
||||
processDraftShield(storage, gcode_layer, layer_nr);
|
||||
|
||||
//Figure out in which order to print the meshes, do this by looking at the current extruder and preferer the meshes that use that extruder.
|
||||
std::vector<unsigned int> mesh_order = calculateMeshOrder(storage, gcode_layer.getExtruder());
|
||||
for(unsigned int mesh_idx : mesh_order)
|
||||
if (include_helper_parts)
|
||||
{
|
||||
SliceMeshStorage* mesh = &storage.meshes[mesh_idx];
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
addSupportToGCode(storage, gcode_layer, std::max(0, layer_nr), extruder_nr_before, true);
|
||||
|
||||
processOozeShield(storage, gcode_layer, std::max(0, layer_nr));
|
||||
|
||||
processDraftShield(storage, gcode_layer, std::max(0, layer_nr));
|
||||
}
|
||||
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
//Figure out in which order to print the meshes, do this by looking at the current extruder and preferer the meshes that use that extruder.
|
||||
std::vector<unsigned int> mesh_order = calculateMeshOrder(storage, gcode_layer.getExtruder());
|
||||
for(unsigned int mesh_idx : mesh_order)
|
||||
{
|
||||
addMeshLayerToGCode_meshSurfaceMode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addMeshLayerToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
SliceMeshStorage* mesh = &storage.meshes[mesh_idx];
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
{
|
||||
addMeshLayerToGCode_meshSurfaceMode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addMeshLayerToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addSupportToGCode(storage, gcode_layer, layer_nr, extruder_nr_before, false);
|
||||
|
||||
if (layer_nr == 0)
|
||||
{ // add skirt for all extruders which haven't primed the skirt yet
|
||||
if (include_helper_parts)
|
||||
{
|
||||
addSupportToGCode(storage, gcode_layer, std::max(0, layer_nr), extruder_nr_before, false);
|
||||
}
|
||||
|
||||
if (include_helper_parts && layer_nr == 0)
|
||||
{ //Add skirt for all extruders which haven't primed the skirt or brim yet.
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
if (gcode.getExtruderIsUsed(extruder_nr) && !skirt_is_processed[extruder_nr])
|
||||
if (gcode.getExtruderIsUsed(extruder_nr) && !skirt_brim_is_processed[extruder_nr] && storage.skirt_brim[extruder_nr].size() > 0)
|
||||
{
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, extruder_nr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (include_helper_parts)
|
||||
{ // add prime tower if it hasn't already been added
|
||||
// print the prime tower if it hasn't been printed yet
|
||||
int prev_extruder = gcode_layer.getExtruder(); // most likely the same extruder as we are extruding with now
|
||||
addPrimeTower(storage, gcode_layer, layer_nr, prev_extruder);
|
||||
addPrimeTower(storage, gcode_layer, std::max(0, layer_nr), prev_extruder);
|
||||
}
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
@@ -465,28 +517,31 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processSkirt(SliceDataStorage& storage, GCodePlanner& gcode_layer, unsigned int extruder_nr)
|
||||
void FffGcodeWriter::processSkirtBrim(SliceDataStorage& storage, GCodePlanner& gcode_layer, unsigned int extruder_nr)
|
||||
{
|
||||
if (skirt_is_processed[extruder_nr])
|
||||
if (skirt_brim_is_processed[extruder_nr])
|
||||
{
|
||||
return;
|
||||
}
|
||||
Polygons& skirt = storage.skirt[extruder_nr];
|
||||
skirt_is_processed[extruder_nr] = true;
|
||||
if (skirt.size() == 0)
|
||||
Polygons& skirt_brim = storage.skirt_brim[extruder_nr];
|
||||
skirt_brim_is_processed[extruder_nr] = true;
|
||||
if (skirt_brim.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
gcode_layer.addTravel(skirt[skirt.size()-1].closestPointTo(gcode_layer.getLastPosition()));
|
||||
gcode_layer.addPolygonsByOptimizer(skirt, &storage.skirt_config[extruder_nr]);
|
||||
|
||||
gcode_layer.addTravel(skirt_brim.back().closestPointTo(gcode_layer.getLastPosition()));
|
||||
gcode_layer.addPolygonsByOptimizer(skirt_brim, &storage.skirt_brim_config[extruder_nr]);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processOozeShield(SliceDataStorage& storage, GCodePlanner& gcode_layer, unsigned int layer_nr)
|
||||
{
|
||||
if (storage.oozeShield.size() > 0)
|
||||
if (layer_nr == 0 && storage.getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::BRIM)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(storage.oozeShield[layer_nr], &storage.skirt_config[0]); // TODO: skirt config idx should correspond to ooze shield extruder nr
|
||||
return; // ooze shield already generated by brim
|
||||
}
|
||||
if (storage.oozeShield.size() > 0 && layer_nr < storage.oozeShield.size())
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(storage.oozeShield[layer_nr], &storage.skirt_brim_config[0]); //TODO: Skirt and brim configuration index should correspond to draft shield extruder number.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,19 +551,28 @@ void FffGcodeWriter::processDraftShield(SliceDataStorage& storage, GCodePlanner&
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int draft_shield_height = getSettingInMicrons("draft_shield_height");
|
||||
int layer_height_0 = getSettingInMicrons("layer_height_0");
|
||||
int layer_height = getSettingInMicrons("layer_height");
|
||||
|
||||
int max_screen_layer = (draft_shield_height - layer_height_0) / layer_height + 1;
|
||||
|
||||
if (int(layer_nr) > max_screen_layer)
|
||||
if (!getSettingBoolean("draft_shield_enabled"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (layer_nr == 0 && storage.getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::BRIM)
|
||||
{
|
||||
return; // ooze shield already generated by brim
|
||||
}
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, &storage.skirt_config[0]); // TODO: skirt config idx should correspond to draft shield extruder nr
|
||||
if (getSettingAsDraftShieldHeightLimitation("draft_shield_height_limitation") == DraftShieldHeightLimitation::LIMITED)
|
||||
{
|
||||
const int draft_shield_height = getSettingInMicrons("draft_shield_height");
|
||||
const int layer_height_0 = getSettingInMicrons("layer_height_0");
|
||||
const int layer_height = getSettingInMicrons("layer_height");
|
||||
const unsigned int max_screen_layer = (unsigned int)((draft_shield_height - layer_height_0) / layer_height + 1);
|
||||
if (layer_nr > max_screen_layer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, &storage.skirt_brim_config[0]); //TODO: Skirt and brim configuration index should correspond to draft shield extruder number.
|
||||
}
|
||||
|
||||
std::vector<unsigned int> FffGcodeWriter::calculateMeshOrder(SliceDataStorage& storage, int current_extruder)
|
||||
@@ -633,13 +697,12 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
int infill_angle = 45;
|
||||
if ((infill_pattern == EFillMethod::LINES || infill_pattern == EFillMethod::ZIG_ZAG))
|
||||
{
|
||||
unsigned int combined_infill_layers = std::max(1, mesh->getSettingInMicrons("infill_sparse_thickness") / std::max(getSettingInMicrons("layer_height"), 1));
|
||||
unsigned int combined_infill_layers = std::max(1U, round_divide(mesh->getSettingInMicrons("infill_sparse_thickness"), std::max(getSettingInMicrons("layer_height"), 1)));
|
||||
if ((layer_nr / combined_infill_layers) & 1)
|
||||
{ // switch every [combined_infill_layers] layers
|
||||
infill_angle += 90;
|
||||
}
|
||||
}
|
||||
int infill_line_width = mesh->infill_config[0].getLineWidth();
|
||||
|
||||
int infill_line_distance = mesh->getSettingInMicrons("infill_line_distance");
|
||||
int infill_overlap = mesh->getSettingInMicrons("infill_overlap_mm");
|
||||
@@ -648,16 +711,16 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
|
||||
if (mesh->getSettingBoolean("infill_before_walls"))
|
||||
{
|
||||
processMultiLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, infill_line_width);
|
||||
processSingleLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, infill_line_width);
|
||||
processMultiLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle);
|
||||
processSingleLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle);
|
||||
}
|
||||
|
||||
processInsets(gcode_layer, mesh, part, layer_nr, z_seam_type);
|
||||
|
||||
if (!mesh->getSettingBoolean("infill_before_walls"))
|
||||
{
|
||||
processMultiLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, infill_line_width);
|
||||
processSingleLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, infill_line_width);
|
||||
processMultiLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle);
|
||||
processSingleLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle);
|
||||
}
|
||||
|
||||
EFillMethod skin_pattern = mesh->getSettingAsFillMethod("top_bottom_pattern");
|
||||
@@ -668,16 +731,16 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
}
|
||||
if (skin_alternate_rotation && ( layer_nr / 2 ) & 1)
|
||||
skin_angle -= 45;
|
||||
|
||||
|
||||
int64_t skin_overlap = mesh->getSettingInMicrons("skin_overlap_mm");
|
||||
processSkin(gcode_layer, mesh, part, layer_nr, skin_overlap, skin_angle, mesh->skin_config.getLineWidth());
|
||||
|
||||
processSkin(gcode_layer, mesh, part, layer_nr, skin_overlap, skin_angle);
|
||||
|
||||
//After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter.
|
||||
if (!mesh->getSettingBoolean("magic_spiralize") || static_cast<int>(layer_nr) < mesh->getSettingAsCount("bottom_layers"))
|
||||
{
|
||||
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons((mesh->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
|
||||
}
|
||||
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
}
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
@@ -690,7 +753,7 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
|
||||
|
||||
|
||||
void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int infill_angle, int extrusion_width)
|
||||
void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int infill_angle)
|
||||
{
|
||||
int64_t z = layer_nr * getSettingInMicrons("layer_height");
|
||||
if (infill_line_distance > 0)
|
||||
@@ -698,6 +761,7 @@ void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMes
|
||||
//Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers)
|
||||
for(unsigned int combine_idx = 1; combine_idx < part.infill_area_per_combine_per_density[0].size(); combine_idx++)
|
||||
{
|
||||
const unsigned int infill_line_width = mesh->infill_config[combine_idx].getLineWidth();
|
||||
EFillMethod infill_pattern = mesh->getSettingAsFillMethod("infill_pattern");
|
||||
Polygons infill_polygons;
|
||||
Polygons infill_lines;
|
||||
@@ -710,7 +774,8 @@ void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMes
|
||||
{
|
||||
infill_line_distance_here /= 2;
|
||||
}
|
||||
Infill infill_comp(infill_pattern, part.infill_area_per_combine_per_density[density_idx][combine_idx], 0, extrusion_width, infill_line_distance_here, infill_overlap, infill_angle, z, infill_shift, false, false);
|
||||
|
||||
Infill infill_comp(infill_pattern, part.infill_area_per_combine_per_density[density_idx][combine_idx], 0, infill_line_width, infill_line_distance_here, infill_overlap, infill_angle, z, infill_shift, false, false);
|
||||
infill_comp.generate(infill_polygons, infill_lines);
|
||||
}
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[combine_idx]);
|
||||
@@ -719,13 +784,13 @@ void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMes
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int infill_angle, int extrusion_width)
|
||||
void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int infill_angle)
|
||||
{
|
||||
|
||||
if (infill_line_distance == 0 || part.infill_area_per_combine_per_density[0].size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const unsigned int infill_line_width = mesh->infill_config[0].getLineWidth();
|
||||
|
||||
//Combine the 1 layer thick infill with the top/bottom skin and print that as one thing.
|
||||
Polygons infill_polygons;
|
||||
@@ -766,7 +831,7 @@ void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMe
|
||||
// ^ highest density line dist
|
||||
infill_line_distance_here /= 2;
|
||||
}
|
||||
Infill infill_comp(pattern, part.infill_area_per_combine_per_density[density_idx][0], 0, extrusion_width, infill_line_distance_here, infill_overlap, infill_angle, z, infill_shift, false, false);
|
||||
Infill infill_comp(pattern, part.infill_area_per_combine_per_density[density_idx][0], 0, infill_line_width, infill_line_distance_here, infill_overlap, infill_angle, z, infill_shift, false, false);
|
||||
infill_comp.generate(infill_polygons, infill_lines);
|
||||
}
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[0]);
|
||||
@@ -798,9 +863,15 @@ void FffGcodeWriter::processInsets(GCodePlanner& gcode_layer, SliceMeshStorage*
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->insetX_config, nullptr, EZSeamType::SHORTEST, false);
|
||||
}
|
||||
}
|
||||
int processed_inset_number = -1;
|
||||
for(int inset_number=part.insets.size()-1; inset_number>-1; inset_number--)
|
||||
{
|
||||
if (inset_number == 0)
|
||||
processed_inset_number = inset_number;
|
||||
if (mesh->getSettingBoolean("outer_inset_first"))
|
||||
{
|
||||
processed_inset_number = part.insets.size() - 1 - inset_number;
|
||||
}
|
||||
if (processed_inset_number == 0)
|
||||
{
|
||||
if (!compensate_overlap_0)
|
||||
{
|
||||
@@ -817,11 +888,11 @@ void FffGcodeWriter::processInsets(GCodePlanner& gcode_layer, SliceMeshStorage*
|
||||
{
|
||||
if (!compensate_overlap_x)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[inset_number], &mesh->insetX_config);
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[processed_inset_number], &mesh->insetX_config);
|
||||
}
|
||||
else
|
||||
{
|
||||
Polygons& outer_wall = part.insets[inset_number];
|
||||
Polygons& outer_wall = part.insets[processed_inset_number];
|
||||
WallOverlapComputation wall_overlap_computation(outer_wall, mesh->getSettingInMicrons("wall_line_width_x"));
|
||||
gcode_layer.addPolygonsByOptimizer(outer_wall, &mesh->insetX_config, &wall_overlap_computation);
|
||||
}
|
||||
@@ -831,9 +902,10 @@ void FffGcodeWriter::processInsets(GCodePlanner& gcode_layer, SliceMeshStorage*
|
||||
}
|
||||
|
||||
|
||||
void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int skin_angle, int extrusion_width)
|
||||
void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int skin_angle)
|
||||
{
|
||||
int64_t z = layer_nr * getSettingInMicrons("layer_height");
|
||||
const unsigned int skin_line_width = mesh->skin_config.getLineWidth();
|
||||
|
||||
for(SkinPart& skin_part : part.skin_parts) // TODO: optimize parts order
|
||||
{
|
||||
@@ -848,40 +920,40 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
{
|
||||
pattern = EFillMethod::LINES;
|
||||
skin_angle = bridge;
|
||||
}
|
||||
}
|
||||
Polygons* inner_skin_outline = nullptr;
|
||||
int offset_from_inner_skin_outline = 0;
|
||||
if (pattern != EFillMethod::CONCENTRIC)
|
||||
{
|
||||
for (Polygons& skin_perimeter : skin_part.insets)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(skin_perimeter, &mesh->skin_config); // add polygons to gcode in inward order
|
||||
gcode_layer.addPolygonsByOptimizer(skin_perimeter, &mesh->insetX_config); // add polygons to gcode in inward order
|
||||
}
|
||||
if (skin_part.insets.size() > 0)
|
||||
{
|
||||
inner_skin_outline = &skin_part.insets.back();
|
||||
offset_from_inner_skin_outline = -extrusion_width/2;
|
||||
offset_from_inner_skin_outline = -mesh->insetX_config.getLineWidth() / 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (inner_skin_outline == nullptr)
|
||||
{
|
||||
inner_skin_outline = &skin_part.outline;
|
||||
}
|
||||
|
||||
int extra_infill_shift = 0;
|
||||
Infill infill_comp(pattern, *inner_skin_outline, offset_from_inner_skin_outline, extrusion_width, extrusion_width, skin_overlap, skin_angle, z, extra_infill_shift, false, false);
|
||||
Infill infill_comp(pattern, *inner_skin_outline, offset_from_inner_skin_outline, skin_line_width, skin_line_width, skin_overlap, skin_angle, z, extra_infill_shift, false, false);
|
||||
infill_comp.generate(skin_polygons, skin_lines);
|
||||
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(skin_polygons, &mesh->skin_config);
|
||||
|
||||
|
||||
if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, SpaceFillType::Lines, mesh->getSettingInMicrons("infill_wipe_dist"));
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, SpaceFillType::Lines, mesh->getSettingInMicrons("infill_wipe_dist"));
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -891,27 +963,27 @@ void FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner&
|
||||
if (!storage.support.generated || layer_nr > storage.support.layer_nr_max_filled_layer)
|
||||
return;
|
||||
|
||||
int support_roof_extruder_nr = getSettingAsIndex("support_roof_extruder_nr");
|
||||
int support_skin_extruder_nr = getSettingAsIndex("support_interface_extruder_nr");
|
||||
int support_infill_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
|
||||
bool print_support_before_rest = support_infill_extruder_nr == extruder_nr_before
|
||||
|| support_roof_extruder_nr == extruder_nr_before;
|
||||
|| support_skin_extruder_nr == extruder_nr_before;
|
||||
// TODO: always print support after rest when only one nozzle is used for the whole meshgroup
|
||||
|
||||
if (print_support_before_rest != before_rest)
|
||||
return;
|
||||
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
|
||||
if (support_layer.roofs.size() == 0 && support_layer.supportAreas.size() == 0)
|
||||
if (support_layer.skin.size() == 0 && support_layer.supportAreas.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int current_extruder_nr = gcode_layer.getExtruder();
|
||||
|
||||
if (support_layer.roofs.size() > 0)
|
||||
if (support_layer.skin.size() > 0)
|
||||
{
|
||||
if (support_roof_extruder_nr != support_infill_extruder_nr && support_roof_extruder_nr == current_extruder_nr)
|
||||
if (support_skin_extruder_nr != support_infill_extruder_nr && support_skin_extruder_nr == current_extruder_nr)
|
||||
{
|
||||
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
|
||||
@@ -939,12 +1011,14 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
|
||||
int64_t z = layer_nr * getSettingInMicrons("layer_height");
|
||||
|
||||
int support_line_distance = getSettingInMicrons("support_line_distance");
|
||||
int extrusion_width = storage.support_config.getLineWidth();
|
||||
EFillMethod support_pattern = getSettingAsFillMethod("support_pattern");
|
||||
const ExtruderTrain& infill_extr = *storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_infill_extruder_nr"));
|
||||
int support_line_distance = infill_extr.getSettingInMicrons("support_line_distance"); // first layer line distance must be the same as the second layer line distance
|
||||
const int support_line_width = storage.support_config.getLineWidth();
|
||||
EFillMethod support_pattern = infill_extr.getSettingAsFillMethod("support_pattern"); // first layer pattern must be same as other layers
|
||||
if (layer_nr == 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG)) { support_pattern = EFillMethod::GRID; }
|
||||
|
||||
int support_infill_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
int infill_extruder_nr_here = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
const ExtruderTrain& infill_extr_here = *storage.meshgroup->getExtruderTrain(infill_extruder_nr_here);
|
||||
|
||||
Polygons& support = storage.support.supportLayers[layer_nr].supportAreas;
|
||||
|
||||
@@ -966,24 +1040,24 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
int offset_from_outline = 0;
|
||||
if (support_pattern == EFillMethod::GRID || support_pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
Polygons boundary = island.offset(-extrusion_width / 2);
|
||||
Polygons boundary = island.offset(-support_line_width / 2);
|
||||
if (boundary.size() > 0)
|
||||
{
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_infill_extruder_nr); // only switch extruder if we're sure we're going to switch
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, infill_extruder_nr_here); // only switch extruder if we're sure we're going to switch
|
||||
gcode_layer.addPolygonsByOptimizer(boundary, &storage.support_config);
|
||||
}
|
||||
offset_from_outline = -extrusion_width;
|
||||
support_infill_overlap = storage.meshgroup->getExtruderTrain(support_infill_extruder_nr)->getSettingInMicrons("infill_overlap_mm"); // support lines area should be expanded outward to overlap with the boundary polygon
|
||||
offset_from_outline = -support_line_width;
|
||||
support_infill_overlap = infill_extr_here.getSettingInMicrons("infill_overlap_mm"); // support lines area should be expanded outward to overlap with the boundary polygon
|
||||
}
|
||||
|
||||
int extra_infill_shift = 0;
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, extrusion_width, support_line_distance, support_infill_overlap, 0, z, extra_infill_shift, getSettingBoolean("support_connect_zigzags"), true);
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, support_line_width, support_line_distance, support_infill_overlap, 0, z, extra_infill_shift, infill_extr.getSettingBoolean("support_connect_zigzags"), true);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines);
|
||||
if (support_lines.size() > 0 || support_polygons.size() > 0)
|
||||
{
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_infill_extruder_nr); // only switch extruder if we're sure we're going to switch
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, infill_extruder_nr_here); // only switch extruder if we're sure we're going to switch
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_config, (support_pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
@@ -994,18 +1068,20 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
|
||||
{
|
||||
if (!storage.support.generated
|
||||
|| layer_nr > storage.support.layer_nr_max_filled_layer
|
||||
|| storage.support.supportLayers[layer_nr].roofs.size() == 0)
|
||||
|| storage.support.supportLayers[layer_nr].skin.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t z = layer_nr * getSettingInMicrons("layer_height");
|
||||
|
||||
EFillMethod pattern = getSettingAsFillMethod("support_roof_pattern");
|
||||
int support_line_distance = getSettingInMicrons("support_roof_line_distance");
|
||||
int skin_extruder_nr = getSettingAsIndex("support_interface_extruder_nr");
|
||||
const ExtruderTrain& interface_extr = *storage.meshgroup->getExtruderTrain(skin_extruder_nr);
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, skin_extruder_nr);
|
||||
|
||||
EFillMethod pattern = interface_extr.getSettingAsFillMethod("support_interface_pattern");
|
||||
int support_line_distance = interface_extr.getSettingInMicrons("support_interface_line_distance");
|
||||
|
||||
int roof_extruder_nr = getSettingAsIndex("support_roof_extruder_nr");
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, roof_extruder_nr);
|
||||
|
||||
bool all_roofs_are_low = true;
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
@@ -1013,6 +1089,7 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
|
||||
if (mesh.getSettingInMicrons("support_roof_height") >= 2 * getSettingInMicrons("layer_height"))
|
||||
{
|
||||
all_roofs_are_low = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1029,17 +1106,17 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
|
||||
{
|
||||
fillAngle = 45 + (layer_nr % 2) * 90; // alternate between the two kinds of diagonal: / and \ .
|
||||
}
|
||||
int support_skin_overlap = 0; // the roofs should never be expanded outwards
|
||||
int support_skin_overlap = 0; // the skin (roofs/bottoms) should never be expanded outwards
|
||||
int outline_offset = 0;
|
||||
int extra_infill_shift = 0;
|
||||
|
||||
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].roofs, outline_offset, storage.support_roof_config.getLineWidth(), support_line_distance, support_skin_overlap, fillAngle, z, extra_infill_shift, false, true);
|
||||
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].skin, outline_offset, storage.support_skin_config.getLineWidth(), support_line_distance, support_skin_overlap, fillAngle, z, extra_infill_shift, false, true);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines);
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_roof_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_roof_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_skin_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr)
|
||||
@@ -1053,9 +1130,9 @@ void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanne
|
||||
|
||||
if (extruder_changed)
|
||||
{
|
||||
if (layer_nr == 0 && !skirt_is_processed[extruder_nr])
|
||||
if (layer_nr == 0 && !skirt_brim_is_processed[extruder_nr])
|
||||
{
|
||||
processSkirt(storage, gcode_layer, extruder_nr);
|
||||
processSkirtBrim(storage, gcode_layer, extruder_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1072,28 +1149,34 @@ void FffGcodeWriter::addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcod
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool prime_tower_dir_outward = getSettingBoolean("prime_tower_dir_outward");
|
||||
|
||||
bool wipe = getSettingBoolean("prime_tower_wipe_enabled");
|
||||
|
||||
storage.primeTower.addToGcode(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed);
|
||||
|
||||
storage.primeTower.addToGcode(storage, gcodeLayer, gcode, layer_nr, prev_extruder, wipe);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::finalize()
|
||||
{
|
||||
double print_time = gcode.getTotalPrintTime();
|
||||
std::vector<double> filament_used;
|
||||
std::vector<std::string> material_ids;
|
||||
for (int extr_nr = 0; extr_nr < getSettingAsCount("machine_extruder_count"); extr_nr++)
|
||||
{
|
||||
filament_used.emplace_back(gcode.getTotalFilamentUsed(extr_nr));
|
||||
material_ids.emplace_back(gcode.getMaterialGUID(extr_nr));
|
||||
}
|
||||
std::string prefix = gcode.getFileHeader(&print_time, filament_used, material_ids);
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
double print_time = gcode.getTotalPrintTime();
|
||||
std::vector<double> filament_used;
|
||||
std::vector<std::string> material_ids;
|
||||
for (int extr_nr = 0; extr_nr < getSettingAsCount("machine_extruder_count"); extr_nr++)
|
||||
{
|
||||
filament_used.emplace_back(gcode.getTotalFilamentUsed(extr_nr));
|
||||
material_ids.emplace_back(gcode.getMaterialGUID(extr_nr));
|
||||
}
|
||||
std::string prefix = gcode.getFileHeader(&print_time, filament_used, material_ids);
|
||||
CommandSocket::getInstance()->sendGCodePrefix(prefix);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string prefix = gcode.getFileHeader(&print_time, filament_used, material_ids);
|
||||
log("Gcode header after slicing:\n");
|
||||
log(prefix.c_str());
|
||||
log("End of gcode header.\n");
|
||||
}
|
||||
if (getSettingBoolean("acceleration_enabled"))
|
||||
{
|
||||
gcode.writeAcceleration(getSettingInMillimetersPerSecond("machine_acceleration"));
|
||||
@@ -1102,6 +1185,10 @@ void FffGcodeWriter::finalize()
|
||||
{
|
||||
gcode.writeJerk(getSettingInMillimetersPerSecond("machine_max_jerk_xy"));
|
||||
}
|
||||
if (gcode.getCurrentMaxZFeedrate() > 0)
|
||||
{
|
||||
gcode.writeMaxZFeedrate(getSettingInMillimetersPerSecond("machine_max_feedrate_z"));
|
||||
}
|
||||
gcode.finalize(getSettingString("machine_end_gcode").c_str());
|
||||
for (int e = 0; e < getSettingAsCount("machine_extruder_count"); e++)
|
||||
{
|
||||
|
||||
+24
-29
@@ -60,13 +60,10 @@ 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_is_processed[MAX_EXTRUDERS]; //!< Whether the skirt polygons have been processed into planned paths for each extruder train
|
||||
bool skirt_brim_is_processed[MAX_EXTRUDERS];
|
||||
|
||||
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.
|
||||
|
||||
@@ -82,10 +79,6 @@ public:
|
||||
, is_inside_mesh_layer_part(false)
|
||||
{
|
||||
max_object_height = 0;
|
||||
for (unsigned int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++)
|
||||
{
|
||||
skirt_is_processed[extruder_nr] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -173,11 +166,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);
|
||||
|
||||
@@ -200,29 +195,32 @@ 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.
|
||||
* Add the skirt or the brim to the layer plan \p gcodeLayer.
|
||||
*
|
||||
* \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 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 processSkirt(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int extruder_nr);
|
||||
void processSkirtBrim(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int extruder_nr);
|
||||
|
||||
/*!
|
||||
* Adds the ooze shield to the layer plan \p gcodeLayer.
|
||||
@@ -294,9 +292,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.
|
||||
@@ -307,9 +304,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.
|
||||
@@ -330,9 +326,8 @@ private:
|
||||
* \param layer_nr The current layer number.
|
||||
* \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.
|
||||
@@ -351,7 +346,7 @@ private:
|
||||
*/
|
||||
void 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.
|
||||
|
||||
+104
-97
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <map> // multimap (ordered map allowing duplicate keys)
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "slicer.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
@@ -11,11 +12,10 @@
|
||||
#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"
|
||||
@@ -27,7 +27,7 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
|
||||
bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper)
|
||||
{
|
||||
if (!sliceModel(meshgroup, timeKeeper, storage))
|
||||
@@ -40,6 +40,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);
|
||||
@@ -98,7 +114,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
|
||||
//carveMultipleVolumes(storage.meshes);
|
||||
|
||||
carveMultipleVolumes(slicerList);
|
||||
generateMultipleVolumesOverlap(slicerList);
|
||||
|
||||
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.
|
||||
@@ -124,9 +141,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)
|
||||
@@ -187,24 +202,24 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
}
|
||||
|
||||
unsigned int print_layer_count = 0;
|
||||
if (CommandSocket::isInstantiated())
|
||||
{ // send layer info
|
||||
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];
|
||||
print_layer_count = layer_nr + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (layer != nullptr)
|
||||
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)
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, layer->printZ, layer_nr == 0? getSettingInMicrons("layer_height_0") : getSettingInMicrons("layer_height"));
|
||||
layer = &mesh.layers[layer_nr];
|
||||
print_layer_count = layer_nr + 1;
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,19 +228,21 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
|
||||
//layerparts2HTML(storage, "output/output.html");
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
|
||||
|
||||
AreaSupport::generateSupportAreas(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
|
||||
// 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"), print_layer_count); // changes total_layers!
|
||||
if (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, print_layer_count);
|
||||
|
||||
/*
|
||||
if (storage.support.generated)
|
||||
@@ -233,10 +250,8 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; 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"));
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -244,11 +259,14 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
// handle helpers
|
||||
storage.primeTower.computePrimeTowerMax(storage);
|
||||
storage.primeTower.generatePaths(storage, print_layer_count);
|
||||
|
||||
logDebug("Processing ooze shield\n");
|
||||
processOozeShield(storage);
|
||||
|
||||
processOozeShield(storage, print_layer_count);
|
||||
|
||||
logDebug("Processing draft shield\n");
|
||||
processDraftShield(storage, print_layer_count);
|
||||
|
||||
logDebug("Processing platform adhesion\n");
|
||||
processPlatformAdhesion(storage);
|
||||
|
||||
// meshes post processing
|
||||
@@ -281,6 +299,7 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
// walls
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
{
|
||||
logDebug("Processing insets for layer %i of %i\n", layer_number, total_layers);
|
||||
processInsets(mesh, layer_number);
|
||||
double progress = inset_skin_progress_estimate.progress(layer_number);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100);
|
||||
@@ -300,8 +319,6 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
{
|
||||
AABB3D aabb = storage.meshgroup->meshes[mesh_idx].getAABB();
|
||||
AABB3D other_aabb = storage.meshgroup->meshes[other_mesh_idx].getAABB();
|
||||
aabb.expandXY(mesh.getSettingInMicrons("xy_offset"));
|
||||
other_aabb.expandXY(other_mesh.getSettingInMicrons("xy_offset"));
|
||||
if (aabb.hit(other_aabb))
|
||||
{
|
||||
process_infill = true;
|
||||
@@ -318,6 +335,7 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
}
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
{
|
||||
logDebug("Processing skins and infill layer %i of %i\n", layer_number, total_layers);
|
||||
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, process_infill);
|
||||
@@ -404,7 +422,7 @@ void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh,
|
||||
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
|
||||
@@ -430,23 +448,16 @@ void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int lay
|
||||
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)
|
||||
{
|
||||
{
|
||||
// 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.
|
||||
if (storage.support.generated)
|
||||
{
|
||||
return; // the first layer will have support and therefore not be empty
|
||||
}
|
||||
int n_empty_first_layers = 0;
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
{
|
||||
@@ -492,11 +503,10 @@ void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, unsigned
|
||||
{
|
||||
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"));
|
||||
|
||||
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
|
||||
@@ -505,64 +515,65 @@ void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, unsigned
|
||||
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::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_object_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_object_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_object_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_object_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)
|
||||
{
|
||||
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(total_layers);
|
||||
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 < total_layers && layer_nr < draft_shield_layers; layer_nr += layer_skip)
|
||||
{
|
||||
draft_shield = draft_shield.unionPolygons(storage.getLayerOutlines(layer_nr, true));
|
||||
}
|
||||
|
||||
|
||||
const int draft_shield_dist = getSettingInMicrons("draft_shield_dist");
|
||||
storage.draft_protection_shield = draft_shield.approxConvexHull(draft_shield_dist);
|
||||
}
|
||||
|
||||
@@ -572,22 +583,18 @@ 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;
|
||||
}
|
||||
|
||||
Polygons skirt_sent = storage.skirt[0];
|
||||
for (int extruder = 1; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
skirt_sent.add(storage.skirt[extruder]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -117,9 +129,8 @@ 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.
|
||||
@@ -136,14 +147,13 @@ private:
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, unsigned int total_layers);
|
||||
|
||||
/*!
|
||||
* 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'
|
||||
*
|
||||
@@ -154,10 +164,8 @@ private:
|
||||
* \param[in,out] mesh where the outer wall is retrieved and stored in.
|
||||
*/
|
||||
void processFuzzyWalls(SliceMeshStorage& mesh);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
}//namespace cura
|
||||
#endif // FFF_AREA_GENERATOR_H
|
||||
|
||||
+1
-24
@@ -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()); }
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,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
|
||||
@@ -62,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.
|
||||
@@ -73,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
|
||||
|
||||
+49
-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,39 @@ 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 +325,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 +334,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);
|
||||
|
||||
|
||||
+141
-140
@@ -1,5 +1,7 @@
|
||||
#include "PrimeTower.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "ExtruderTrain.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "gcodeExport.h"
|
||||
@@ -9,34 +11,38 @@
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
|
||||
PrimeTower::PrimeTower()
|
||||
: current_pre_wipe_location_idx(0)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++)
|
||||
{
|
||||
last_prime_tower_poly_printed[extruder_nr] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrimeTower::initConfigs(MeshGroup* meshgroup, std::vector<RetractionConfig>& retraction_config_per_extruder)
|
||||
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++)
|
||||
{
|
||||
ExtruderTrain* train = meshgroup->getExtruderTrain(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(MeshGroup* meshgroup, int layer_thickness)
|
||||
void PrimeTower::setConfigs(const MeshGroup* meshgroup, const int layer_thickness)
|
||||
{
|
||||
|
||||
extruder_count = meshgroup->getSettingAsCount("machine_extruder_count");
|
||||
|
||||
|
||||
extruder_count = meshgroup->getExtruderCount();
|
||||
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
{
|
||||
GCodePathConfig& conf = config_per_extruder[extr];
|
||||
@@ -49,7 +55,7 @@ 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");
|
||||
extruder_count = storage.meshgroup->getExtruderCount();
|
||||
|
||||
int max_object_height_per_extruder[extruder_count];
|
||||
std::fill_n(max_object_height_per_extruder, extruder_count, -1); // unitialize all as -1
|
||||
@@ -65,9 +71,9 @@ void PrimeTower::computePrimeTowerMax(SliceDataStorage& storage)
|
||||
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]
|
||||
int support_skin_extruder_nr = storage.getSettingAsIndex("support_interface_extruder_nr"); // TODO: support skin extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_skin_extruder_nr] =
|
||||
std::max( max_object_height_per_extruder[support_skin_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
}
|
||||
{ // // compute max_object_height_second_to_last_extruder
|
||||
@@ -92,17 +98,16 @@ void PrimeTower::computePrimeTowerMax(SliceDataStorage& storage)
|
||||
{
|
||||
storage.max_object_height_second_to_last_extruder = -1;
|
||||
}
|
||||
else
|
||||
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();
|
||||
PolygonRef p = 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
|
||||
@@ -112,69 +117,28 @@ void PrimeTower::generateGroundpoly(SliceDataStorage& storage)
|
||||
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);
|
||||
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, unsigned int total_layers)
|
||||
{
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingBoolean("prime_tower_enable"))
|
||||
{
|
||||
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");
|
||||
@@ -183,7 +147,7 @@ void PrimeTower::generatePaths3(SliceDataStorage& storage)
|
||||
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;
|
||||
int outline_offset = -line_width;
|
||||
int line_distance = line_width;
|
||||
double fill_angle = 45 + pattern_idx * 90;
|
||||
Polygons& result_lines = patterns[pattern_idx];
|
||||
@@ -193,9 +157,8 @@ void PrimeTower::generatePaths3(SliceDataStorage& storage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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, bool wipe)
|
||||
{
|
||||
if (!( storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0) )
|
||||
{
|
||||
@@ -210,99 +173,137 @@ void PrimeTower::addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer,
|
||||
{ // don't print the prime tower if it has been printed already
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
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);
|
||||
// pre-wipe:
|
||||
if (wipe)
|
||||
{
|
||||
preWipe(storage, gcodeLayer, new_extruder);
|
||||
}
|
||||
|
||||
addToGcode_denseInfill(storage, gcodeLayer, gcode, layer_nr, prev_extruder);
|
||||
|
||||
// post-wipe:
|
||||
if (false && wipe) // TODO: make a separate setting for the post-wipe!
|
||||
{ //Make sure we wipe the old extruder on the prime tower.
|
||||
gcodeLayer.addTravel(post_wipe_point - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
void PrimeTower::addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder)
|
||||
{
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
|
||||
|
||||
Polygons& pattern = patterns_per_extruder[new_extruder][layer_nr % 2];
|
||||
|
||||
|
||||
|
||||
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);
|
||||
|
||||
Polygon outer_wall = ground_poly.offset(-config.getLineWidth() / 2).back();
|
||||
gcodeLayer.addPolygon(outer_wall, start_idx, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern, &config, SpaceFillType::Lines);
|
||||
|
||||
|
||||
last_prime_tower_poly_printed[new_extruder] = layer_nr;
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::Support, layer_nr, pattern, config.getLineWidth());
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
CommandSocket::sendPolygons(PrintFeatureType::Support, pattern, config.getLineWidth());
|
||||
}
|
||||
|
||||
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)
|
||||
Point PrimeTower::getLocationBeforePrimeTower(const SliceDataStorage& storage)
|
||||
{
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
Point ret(0, 0);
|
||||
int absolute_starting_points = 0;
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
|
||||
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)
|
||||
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)
|
||||
{
|
||||
Point from = getLocationBeforePrimeTower(storage);
|
||||
|
||||
// take the closer corner of the wipe tower and generate wipe locations on that side only:
|
||||
//
|
||||
// |
|
||||
// |
|
||||
// +-----
|
||||
// .
|
||||
// ^ nozzle switch location
|
||||
|
||||
PolygonsPointIndex segment_start; // from where to start the sequence of wipe points
|
||||
PolygonsPointIndex segment_end; // where to end the sequence of wipe points
|
||||
|
||||
// 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)
|
||||
{
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
gcodeLayer.addPolygonsByOptimizer(insets[(prime_tower_dir_outward)? insets.size() - 1 - n : n], &config);
|
||||
segment_start = prev;
|
||||
segment_end = closest_vert;
|
||||
}
|
||||
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));
|
||||
else
|
||||
{
|
||||
segment_start = closest_vert;
|
||||
segment_end = next;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
// TODO: come up with alternatives for better segments once the prime tower can be different shapes
|
||||
|
||||
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);
|
||||
// for hollow wipe tower:
|
||||
// start from above
|
||||
// go to the level of the previous layer
|
||||
// wipe
|
||||
// go to normal layer height (automatically on the next extrusion move...
|
||||
gcode_layer.addTravel(start); // TODO: verify that this move has a z hop ==> cylindric wipe tower
|
||||
// gcode_layer.makeLastPathZhopped which calls forceNewPathStart TODO ==> cylindric wipe tower
|
||||
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
|
||||
|
||||
+117
-27
@@ -1,9 +1,12 @@
|
||||
#ifndef PRIME_TOWER_H
|
||||
#define PRIME_TOWER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "GCodePathConfig.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "utils/polygon.h" // Polygons
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -15,49 +18,136 @@ 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;
|
||||
int extruder_count; //!< number of extruders
|
||||
std::vector<GCodePathConfig> config_per_extruder; //!< Path config for prime tower for each extruder
|
||||
|
||||
Point post_wipe_point; //!< location to post-wipe the unused nozzle off on
|
||||
|
||||
std::vector<PolyLine> extruder_paths; //!< Precomputed so that we don't need to generate the paths each layer over again
|
||||
|
||||
std::vector<ClosestPolygonPoint> pre_wipe_locations; //!< The differernt locations where to pre-wipe the active nozzle
|
||||
const unsigned int pre_wipe_location_skip = 8; //!< How big the steps are when stepping through \ref PrimeTower::wipe_locations
|
||||
const unsigned int number_of_pre_wipe_locations = 13; //!< 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
|
||||
|
||||
class WallInfill
|
||||
{
|
||||
|
||||
};
|
||||
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);
|
||||
Polygons ground_poly; //!< The outline of the prime tower to be used for each layer
|
||||
|
||||
/*!
|
||||
* 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
|
||||
*
|
||||
* \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, unsigned int total_layers);
|
||||
|
||||
/*!
|
||||
* Compute the maximum layer at which a layer switch will occur and store the result in \ref SliceDataStorage::max_object_height_second_to_last_extruder
|
||||
*
|
||||
* \param[in,out] storage Where to retrieve area data and extruder settings for those areas; where to store the max_object_height_second_to_last_extruder
|
||||
*/
|
||||
void computePrimeTowerMax(SliceDataStorage& storage);
|
||||
|
||||
PrimeTower();
|
||||
|
||||
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);
|
||||
PrimeTower(); //!< basic constructor
|
||||
|
||||
/*!
|
||||
* 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 wipe Whether to wipe of the (not previous, but) current nozzle on the wipe tower (only occurs if previous extruder is different fromt he current one)
|
||||
*/
|
||||
void addToGcode(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, bool wipe);
|
||||
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 area where the prime tower should be.
|
||||
*
|
||||
* \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
|
||||
*/
|
||||
void addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_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,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);
|
||||
first_layer_outline.add(storage.primeTower.ground_poly); // don't remove parts of the prime tower, but make a brim for it
|
||||
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)); // 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);
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
+10
-25
@@ -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];
|
||||
@@ -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"); // ___ ___ .
|
||||
|
||||
+65
-45
@@ -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(PrintFeatureType::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);
|
||||
@@ -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:
|
||||
@@ -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;
|
||||
|
||||
+395
-46
@@ -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::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);
|
||||
|
||||
@@ -175,6 +323,7 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
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())
|
||||
{
|
||||
@@ -186,19 +335,44 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
{
|
||||
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();
|
||||
@@ -243,14 +417,13 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list, const google
|
||||
{ // 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
|
||||
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
|
||||
}
|
||||
|
||||
for (auto extruder : settings_per_extruder_train)
|
||||
{
|
||||
int extruder_nr = extruder.id();
|
||||
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
ExtruderTrain* train = meshgroup->getExtruderTrain(extruder_nr);
|
||||
for (auto setting : extruder.settings().settings())
|
||||
{
|
||||
train->setSetting(setting.name(), setting.value());
|
||||
@@ -320,41 +493,99 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list, const google
|
||||
}
|
||||
#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, const 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
|
||||
@@ -368,6 +599,7 @@ void CommandSocket::sendProgressStage(Progress::Stage stage)
|
||||
void CommandSocket::sendPrintTimeMaterialEstimates()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
logDebug("Sending print time and material estimates.\n");
|
||||
auto message = std::make_shared<cura::proto::PrintTimeMaterialEstimates>();
|
||||
|
||||
message->set_time(FffProcessor::getInstance()->getTotalPrintTime());
|
||||
@@ -381,6 +613,7 @@ void CommandSocket::sendPrintTimeMaterialEstimates()
|
||||
}
|
||||
|
||||
private_data->socket->sendMessage(message);
|
||||
logDebug("Done sending print time and material estimates.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -398,20 +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 %d layers.", private_data->current_layer_count);
|
||||
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();
|
||||
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
|
||||
}
|
||||
@@ -419,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
|
||||
}
|
||||
|
||||
@@ -454,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;
|
||||
}
|
||||
@@ -467,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
|
||||
|
||||
+48
-12
@@ -50,19 +50,44 @@ public:
|
||||
*/
|
||||
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, const 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 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);
|
||||
|
||||
@@ -87,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.
|
||||
*
|
||||
@@ -114,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
|
||||
+84
-57
@@ -7,6 +7,7 @@
|
||||
#include "utils/logoutput.h"
|
||||
#include "PrintFeature.h"
|
||||
#include "utils/Date.h"
|
||||
#include "utils/string.h" // MMtoStream, PrecisionedDouble
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -26,6 +27,7 @@ GCodeExport::GCodeExport()
|
||||
currentSpeed = 1;
|
||||
current_acceleration = -1;
|
||||
current_jerk = -1;
|
||||
current_max_z_feedrate = -1;
|
||||
|
||||
isZHopped = 0;
|
||||
setFlavor(EGCodeFlavor::REPRAP);
|
||||
@@ -38,30 +40,36 @@ GCodeExport::~GCodeExport()
|
||||
{
|
||||
}
|
||||
|
||||
void GCodeExport::preSetup(const 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 (const Mesh& mesh : settings->meshes)
|
||||
for (const Mesh& mesh : meshgroup->meshes)
|
||||
{
|
||||
extruder_attr[mesh.getSettingAsIndex("extruder_nr")].is_used = true;
|
||||
}
|
||||
|
||||
for (unsigned int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
const ExtruderTrain* train = settings->getExtruderTrain(extruder_nr);
|
||||
const ExtruderTrain* train = meshgroup->getExtruderTrain(extruder_nr);
|
||||
|
||||
if (settings->getSettingAsIndex("adhesion_extruder_nr") == int(extruder_nr)
|
||||
|| (settings->getSettingBoolean("support_enable") && settings->getSettingAsIndex("support_infill_extruder_nr") == int(extruder_nr))
|
||||
|| (settings->getSettingBoolean("support_enable") && settings->getSettingAsIndex("support_extruder_nr_layer_0") == int(extruder_nr))
|
||||
|| (settings->getSettingBoolean("support_enable") && settings->getSettingBoolean("support_roof_enable") && settings->getSettingAsIndex("support_roof_extruder_nr") == int(extruder_nr))
|
||||
)
|
||||
if (meshgroup->getSettingAsIndex("adhesion_extruder_nr") == int(extruder_nr))
|
||||
{
|
||||
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"));
|
||||
@@ -76,11 +84,11 @@ void GCodeExport::preSetup(const MeshGroup* settings)
|
||||
|
||||
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 = settings->getSettingInMicrons("machine_width");
|
||||
machine_dimensions.y = settings->getSettingInMicrons("machine_depth");
|
||||
machine_dimensions.z = settings->getSettingInMicrons("machine_height");
|
||||
machine_dimensions.x = meshgroup->getSettingInMicrons("machine_width");
|
||||
machine_dimensions.y = meshgroup->getSettingInMicrons("machine_depth");
|
||||
machine_dimensions.z = meshgroup->getSettingInMicrons("machine_height");
|
||||
|
||||
machine_name = settings->getSettingString("machine_name");
|
||||
machine_name = meshgroup->getSettingString("machine_name");
|
||||
|
||||
if (flavor == EGCodeFlavor::BFB)
|
||||
{
|
||||
@@ -91,7 +99,7 @@ void GCodeExport::preSetup(const MeshGroup* settings)
|
||||
new_line = "\n";
|
||||
}
|
||||
|
||||
estimateCalculator.setFirmwareDefaults(settings);
|
||||
estimateCalculator.setFirmwareDefaults(meshgroup);
|
||||
}
|
||||
|
||||
void GCodeExport::setInitialTemps(const MeshGroup& settings)
|
||||
@@ -189,27 +197,27 @@ void GCodeExport::setOutputStream(std::ostream* stream)
|
||||
*output_stream << std::fixed;
|
||||
}
|
||||
|
||||
bool GCodeExport::getExtruderIsUsed(int extruder_nr)
|
||||
bool GCodeExport::getExtruderIsUsed(const int extruder_nr) const
|
||||
{
|
||||
return extruder_attr[extruder_nr].is_used;
|
||||
}
|
||||
|
||||
int GCodeExport::getNozzleSize(int extruder_nr)
|
||||
int GCodeExport::getNozzleSize(const int extruder_nr) const
|
||||
{
|
||||
return extruder_attr[extruder_nr].nozzle_size;
|
||||
}
|
||||
|
||||
Point GCodeExport::getExtruderOffset(int id)
|
||||
Point GCodeExport::getExtruderOffset(const int id) const
|
||||
{
|
||||
return extruder_attr[id].nozzle_offset;
|
||||
}
|
||||
|
||||
std::string GCodeExport::getMaterialGUID(int extruder_nr)
|
||||
std::string GCodeExport::getMaterialGUID(const int extruder_nr) const
|
||||
{
|
||||
return extruder_attr[extruder_nr].material_guid;
|
||||
}
|
||||
|
||||
Point GCodeExport::getGcodePos(int64_t x, int64_t y, int extruder_train)
|
||||
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); }
|
||||
@@ -251,7 +259,7 @@ EGCodeFlavor GCodeExport::getFlavor()
|
||||
|
||||
void GCodeExport::setZ(int z)
|
||||
{
|
||||
this->zPos = z;
|
||||
this->current_layer_z = z;
|
||||
}
|
||||
|
||||
Point3 GCodeExport::getPosition()
|
||||
@@ -402,7 +410,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:
|
||||
@@ -459,7 +467,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)
|
||||
@@ -486,7 +494,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.
|
||||
@@ -511,10 +519,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);
|
||||
@@ -526,8 +532,9 @@ 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
|
||||
|
||||
@@ -549,7 +556,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;
|
||||
@@ -562,7 +569,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);
|
||||
@@ -570,7 +577,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);
|
||||
}
|
||||
@@ -582,7 +589,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);
|
||||
}
|
||||
@@ -594,30 +601,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);
|
||||
@@ -692,8 +691,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;
|
||||
@@ -709,7 +708,7 @@ void GCodeExport::writeZhopStart(int hop_height)
|
||||
if (hop_height > 0)
|
||||
{
|
||||
isZHopped = hop_height;
|
||||
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z + isZHopped) << new_line;
|
||||
*output_stream << "G1 Z" << MMtoStream{currentPosition.z + isZHopped} << new_line;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,6 +732,8 @@ void GCodeExport::startExtruder(int new_extruder)
|
||||
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;
|
||||
@@ -796,7 +797,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
|
||||
{
|
||||
@@ -819,7 +820,10 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
|
||||
*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;
|
||||
}
|
||||
|
||||
@@ -829,14 +833,14 @@ void GCodeExport::writeBedTemperatureCommand(double temperature, bool 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" << acceleration << new_line; // Print and Travel acceleration
|
||||
*output_stream << "M204 S" << PrecisionedDouble{0, acceleration} << new_line; // Print and Travel acceleration
|
||||
current_acceleration = acceleration;
|
||||
estimateCalculator.setAcceleration(acceleration);
|
||||
}
|
||||
@@ -846,12 +850,35 @@ void GCodeExport::writeJerk(double jerk)
|
||||
{
|
||||
if (current_jerk != jerk)
|
||||
{
|
||||
*output_stream << "M205 X" << jerk << new_line;
|
||||
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)
|
||||
{
|
||||
writeFanCommand(0);
|
||||
|
||||
+31
-11
@@ -42,7 +42,7 @@ private:
|
||||
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 the current meshgroup
|
||||
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;
|
||||
@@ -97,8 +97,16 @@ private:
|
||||
double currentSpeed; //!< The current speed (F values / 60) in mm/s
|
||||
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
|
||||
|
||||
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!
|
||||
/*!
|
||||
* 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;
|
||||
@@ -167,15 +175,15 @@ public:
|
||||
|
||||
void setOutputStream(std::ostream* stream);
|
||||
|
||||
bool getExtruderIsUsed(int extruder_nr); //!< Returns whether the extruder with the given index is used up until the current meshgroup
|
||||
bool getExtruderIsUsed(const int extruder_nr) const; //!< Returns whether the extruder with the given index is used up until the current meshgroup
|
||||
|
||||
int getNozzleSize(int extruder_nr);
|
||||
int getNozzleSize(const int extruder_nr) const;
|
||||
|
||||
Point getExtruderOffset(int id);
|
||||
Point getExtruderOffset(const int id) const;
|
||||
|
||||
std::string getMaterialGUID(int extruder_nr); //!< returns the GUID of the material used for the nozzle with id \p extruder_nr
|
||||
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(int64_t x, int64_t y, int extruder_train);
|
||||
Point getGcodePos(const int64_t x, const int64_t y, const int extruder_train) const;
|
||||
|
||||
void setFlavor(EGCodeFlavor flavor);
|
||||
EGCodeFlavor getFlavor();
|
||||
@@ -241,15 +249,15 @@ 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);
|
||||
|
||||
@@ -307,6 +315,18 @@ public:
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
||||
+78
-34
@@ -1,8 +1,8 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include <cstring>
|
||||
#include "gcodePlanner.h"
|
||||
#include "pathOrderOptimizer.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "debug.h" // debugging
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "MergeInfillLines.h"
|
||||
|
||||
@@ -578,7 +578,7 @@ void ExtruderPlan::processFanSpeedAndMinimalLayerTime(bool force_minimal_layer_t
|
||||
if (layer_nr < fsml.cool_fan_full_layer)
|
||||
{
|
||||
//Slow down the fan on the layers below the [cool_fan_full_layer], where layer 0 is speed 0.
|
||||
fan_speed = fan_speed * layer_nr / fsml.cool_fan_full_layer;
|
||||
fan_speed = fan_speed * std::max(0, layer_nr) / fsml.cool_fan_full_layer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,6 +608,8 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
{
|
||||
completeConfigs();
|
||||
|
||||
CommandSocket::setLayerForSend(layer_nr);
|
||||
CommandSocket::setSendCurrentPosition( gcode.getPositionXY() );
|
||||
gcode.setLayerNr(layer_nr);
|
||||
|
||||
gcode.writeLayerComment(layer_nr);
|
||||
@@ -618,18 +620,26 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
GCodePathConfig* last_extrusion_config = nullptr; // used to check whether we need to insert a TYPE comment in the gcode.
|
||||
|
||||
int extruder = gcode.getExtruderNr();
|
||||
bool acceleration_enabled = storage.getSettingBoolean("acceleration_enabled");
|
||||
bool jerk_enabled = storage.getSettingBoolean("jerk_enabled");
|
||||
|
||||
for(unsigned int extruder_plan_idx = 0; extruder_plan_idx < extruder_plans.size(); extruder_plan_idx++)
|
||||
{
|
||||
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[gcode.getExtruderNr()];
|
||||
|
||||
ExtruderPlan& extruder_plan = extruder_plans[extruder_plan_idx];
|
||||
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[extruder_plan.extruder];
|
||||
|
||||
if (extruder != extruder_plan.extruder)
|
||||
{
|
||||
int prev_extruder = extruder;
|
||||
extruder = extruder_plan.extruder;
|
||||
gcode.switchExtruder(extruder, storage.extruder_switch_retraction_config_per_extruder[prev_extruder]);
|
||||
|
||||
const ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
if (train->getSettingInMillimetersPerSecond("max_feedrate_z_override") > 0)
|
||||
{
|
||||
gcode.writeMaxZFeedrate(train->getSettingInMillimetersPerSecond("max_feedrate_z_override"));
|
||||
}
|
||||
|
||||
{ // require printing temperature to be met
|
||||
constexpr bool wait = true;
|
||||
gcode.writeTemperatureCommand(extruder, extruder_plan.required_temp, wait);
|
||||
@@ -651,18 +661,27 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
extruder_plan.inserts.sort([](const NozzleTempInsert& a, const NozzleTempInsert& b) -> bool {
|
||||
return a.path_idx < b.path_idx;
|
||||
} );
|
||||
|
||||
|
||||
const ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
if (train->getSettingInMillimetersPerSecond("max_feedrate_z_override") > 0)
|
||||
{
|
||||
gcode.writeMaxZFeedrate(train->getSettingInMillimetersPerSecond("max_feedrate_z_override"));
|
||||
}
|
||||
bool speed_equalize_flow_enabled = train->getSettingBoolean("speed_equalize_flow_enabled");
|
||||
double speed_equalize_flow_max = train->getSettingInMillimetersPerSecond("speed_equalize_flow_max");
|
||||
int64_t nozzle_size = gcode.getNozzleSize(extruder);
|
||||
|
||||
for(unsigned int path_idx = 0; path_idx < paths.size(); path_idx++)
|
||||
{
|
||||
extruder_plan.handleInserts(path_idx, gcode);
|
||||
|
||||
GCodePath& path = paths[path_idx];
|
||||
|
||||
if (storage.getSettingBoolean("acceleration_enabled"))
|
||||
if (acceleration_enabled)
|
||||
{
|
||||
gcode.writeAcceleration(path.config->getAcceleration());
|
||||
}
|
||||
if (storage.getSettingBoolean("jerk_enabled"))
|
||||
if (jerk_enabled)
|
||||
{
|
||||
gcode.writeJerk(path.config->getJerk());
|
||||
}
|
||||
@@ -680,21 +699,21 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
gcode.writeTypeComment(path.config->type);
|
||||
last_extrusion_config = path.config;
|
||||
}
|
||||
|
||||
double speed = path.config->getSpeed();
|
||||
|
||||
if (path.isTravelPath())// Only apply the extrudeSpeed to extrusion moves
|
||||
// Apply the relevant factor
|
||||
if (path.config->isTravelPath())
|
||||
speed *= extruder_plan.getTravelSpeedFactor();
|
||||
else
|
||||
speed *= extruder_plan.getExtrudeSpeedFactor();
|
||||
|
||||
int64_t nozzle_size = 400; // TODO
|
||||
|
||||
if (MergeInfillLines(gcode, layer_nr, paths, extruder_plan, storage.travel_config_per_extruder[extruder], nozzle_size).mergeInfillLines(speed, path_idx)) // !! has effect on path_idx !!
|
||||
|
||||
if (MergeInfillLines(gcode, layer_nr, paths, extruder_plan, storage.travel_config_per_extruder[extruder], nozzle_size, speed_equalize_flow_enabled, speed_equalize_flow_max).mergeInfillLines(path_idx)) // !! has effect on path_idx !!
|
||||
{ // !! has effect on path_idx !!
|
||||
// works when path_idx is the index of the travel move BEFORE the infill lines to be merged
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (path.config->isTravelPath())
|
||||
{ // early comp for travel paths, which are handled more simply
|
||||
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
@@ -731,7 +750,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
&& shorterThen(paths[path_idx+2].points.back() - paths[path_idx+1].points.back(), 2 * nozzle_size) // consecutive extrusion is close by
|
||||
)
|
||||
{
|
||||
sendPolygon(paths[path_idx+2].config->type, gcode.getPositionXY(), paths[path_idx+2].points.back(), paths[path_idx+2].getLineWidth());
|
||||
sendLineTo(paths[path_idx+2].config->type, paths[path_idx+2].points.back(), paths[path_idx+2].getLineWidth());
|
||||
gcode.writeMove(paths[path_idx+2].points.back(), speed, paths[path_idx+1].getExtrusionMM3perMM());
|
||||
path_idx += 2;
|
||||
}
|
||||
@@ -739,7 +758,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
{
|
||||
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
||||
sendLineTo(path.config->type, path.points[point_idx], path.getLineWidth());
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
@@ -772,7 +791,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
length += vSizeMM(p0 - p1);
|
||||
p0 = p1;
|
||||
gcode.setZ(z + layer_thickness * length / totalLength);
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
||||
sendLineTo(path.config->type, path.points[point_idx], path.getLineWidth());
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
@@ -780,7 +799,6 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
}
|
||||
} // paths for this extruder /\ .
|
||||
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
if (train->getSettingBoolean("cool_lift_head") && extruder_plan.extraTime > 0.0)
|
||||
{
|
||||
gcode.writeComment("Small layer, adding delay");
|
||||
@@ -815,7 +833,7 @@ void GCodePlanner::overrideFanSpeeds(double speed)
|
||||
void GCodePlanner::completeConfigs()
|
||||
{
|
||||
storage.support_config.setLayerHeight(layer_thickness);
|
||||
storage.support_roof_config.setLayerHeight(layer_thickness);
|
||||
storage.support_skin_config.setLayerHeight(layer_thickness);
|
||||
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
@@ -838,39 +856,65 @@ void GCodePlanner::completeConfigs()
|
||||
void GCodePlanner::processInitialLayersSpeedup()
|
||||
{
|
||||
int initial_speedup_layers = storage.getSettingAsCount("speed_slowdown_layers");
|
||||
if (static_cast<int>(layer_nr) < initial_speedup_layers)
|
||||
if (layer_nr >= 0 && layer_nr < initial_speedup_layers)
|
||||
{
|
||||
GCodePathConfig::BasicConfig initial_layer_speed_config;
|
||||
int extruder_nr_support_infill = storage.getSettingAsIndex((layer_nr == 0)? "support_extruder_nr_layer_0" : "support_infill_extruder_nr");
|
||||
initial_layer_speed_config.speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("speed_layer_0");
|
||||
initial_layer_speed_config.acceleration = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("acceleration_layer_0");
|
||||
initial_layer_speed_config.jerk = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("jerk_layer_0");
|
||||
initial_layer_speed_config.speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("speed_print_layer_0");
|
||||
initial_layer_speed_config.acceleration = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("acceleration_print_layer_0");
|
||||
initial_layer_speed_config.jerk = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("jerk_print_layer_0");
|
||||
|
||||
//Support (global).
|
||||
storage.support_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
|
||||
|
||||
int extruder_nr_support_roof = storage.getSettingAsIndex("support_roof_extruder_nr");
|
||||
initial_layer_speed_config.speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_roof)->getSettingInMillimetersPerSecond("speed_layer_0");
|
||||
initial_layer_speed_config.acceleration = storage.meshgroup->getExtruderTrain(extruder_nr_support_roof)->getSettingInMillimetersPerSecond("acceleration_layer_0");
|
||||
initial_layer_speed_config.jerk = storage.meshgroup->getExtruderTrain(extruder_nr_support_roof)->getSettingInMillimetersPerSecond("jerk_layer_0");
|
||||
storage.support_roof_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
|
||||
//Support roof (global).
|
||||
int extruder_nr_support_skin = storage.getSettingAsIndex("support_interface_extruder_nr");
|
||||
initial_layer_speed_config.speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_skin)->getSettingInMillimetersPerSecond("speed_print_layer_0");
|
||||
initial_layer_speed_config.acceleration = storage.meshgroup->getExtruderTrain(extruder_nr_support_skin)->getSettingInMillimetersPerSecond("acceleration_print_layer_0");
|
||||
initial_layer_speed_config.jerk = storage.meshgroup->getExtruderTrain(extruder_nr_support_skin)->getSettingInMillimetersPerSecond("jerk_print_layer_0");
|
||||
storage.support_skin_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
|
||||
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); ++extruder_nr)
|
||||
{
|
||||
const ExtruderTrain* extruder_train = storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
initial_layer_speed_config.speed = extruder_train->getSettingInMillimetersPerSecond("speed_travel_layer_0");
|
||||
initial_layer_speed_config.acceleration = extruder_train->getSettingInMillimetersPerSecond("acceleration_travel_layer_0");
|
||||
initial_layer_speed_config.jerk = extruder_train->getSettingInMillimetersPerSecond("jerk_travel_layer_0");
|
||||
|
||||
//Travel speed (per extruder).
|
||||
storage.travel_config_per_extruder[extruder_nr].smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
|
||||
}
|
||||
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
initial_layer_speed_config.speed = mesh.getSettingInMillimetersPerSecond("speed_layer_0");
|
||||
initial_layer_speed_config.acceleration = mesh.getSettingInMillimetersPerSecond("acceleration_layer_0");
|
||||
initial_layer_speed_config.jerk = mesh.getSettingInMillimetersPerSecond("jerk_layer_0");
|
||||
initial_layer_speed_config.speed = mesh.getSettingInMillimetersPerSecond("speed_print_layer_0");
|
||||
initial_layer_speed_config.acceleration = mesh.getSettingInMillimetersPerSecond("acceleration_print_layer_0");
|
||||
initial_layer_speed_config.jerk = mesh.getSettingInMillimetersPerSecond("jerk_print_layer_0");
|
||||
|
||||
//Outer wall speed (per mesh).
|
||||
mesh.inset0_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
|
||||
|
||||
//Inner wall speed (per mesh).
|
||||
mesh.insetX_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
|
||||
|
||||
//Skin speed (per mesh).
|
||||
mesh.skin_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
|
||||
|
||||
for (unsigned int idx = 0; idx < MAX_INFILL_COMBINE; idx++)
|
||||
{
|
||||
//Infill speed (per combine part per mesh).
|
||||
mesh.infill_config[idx].smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layers);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (static_cast<int>(layer_nr) == initial_speedup_layers)
|
||||
else if (layer_nr == initial_speedup_layers) //At the topmost layer of the gradient, reset all speeds to the typical speeds.
|
||||
{
|
||||
storage.support_config.setSpeedIconic();
|
||||
storage.support_roof_config.setSpeedIconic();
|
||||
storage.support_skin_config.setSpeedIconic();
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); ++extruder_nr)
|
||||
{
|
||||
storage.travel_config_per_extruder[extruder_nr].setSpeedIconic();
|
||||
}
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
mesh.inset0_config.setSpeedIconic();
|
||||
@@ -1007,10 +1051,10 @@ bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, unsigned int extrud
|
||||
{ // write normal extrude path:
|
||||
for(unsigned int point_idx = 0; point_idx <= point_idx_before_start; point_idx++)
|
||||
{
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
||||
sendLineTo(path.config->type, path.points[point_idx], path.getLineWidth());
|
||||
gcode.writeMove(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), start, path.getLineWidth());
|
||||
sendLineTo(path.config->type, start, path.getLineWidth());
|
||||
gcode.writeMove(start, extrude_speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
|
||||
|
||||
+7
-13
@@ -1,3 +1,4 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef GCODE_PLANNER_H
|
||||
#define GCODE_PLANNER_H
|
||||
|
||||
@@ -542,19 +543,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);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -660,8 +653,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();
|
||||
|
||||
+80
-69
@@ -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.
|
||||
@@ -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:
|
||||
@@ -124,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++)
|
||||
@@ -139,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);
|
||||
|
||||
@@ -156,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");
|
||||
@@ -180,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':
|
||||
@@ -188,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
|
||||
{
|
||||
@@ -251,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
|
||||
}
|
||||
|
||||
|
||||
@@ -297,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)
|
||||
@@ -348,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];
|
||||
@@ -369,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);
|
||||
@@ -380,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);
|
||||
}
|
||||
|
||||
+6
-3
@@ -72,6 +72,10 @@ AABB3D Mesh::getAABB() const
|
||||
{
|
||||
return aabb;
|
||||
}
|
||||
void Mesh::expandXY(int64_t offset)
|
||||
{
|
||||
aabb.expandXY(offset);
|
||||
}
|
||||
|
||||
|
||||
int Mesh::findIndexOfVertex(const Point3& v)
|
||||
@@ -133,7 +137,7 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx, int notFaceVe
|
||||
|
||||
}
|
||||
|
||||
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]; }
|
||||
|
||||
|
||||
@@ -171,7 +175,6 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx, int notFaceVe
|
||||
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)
|
||||
{
|
||||
@@ -179,7 +182,7 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx, int notFaceVe
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -61,6 +61,7 @@ void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
if (other_volume->mesh->getSettingBoolean("infill_mesh")
|
||||
|| !other_volume->mesh->getAABB().hit(aabb)
|
||||
|| other_volume == volume
|
||||
)
|
||||
{
|
||||
continue;
|
||||
@@ -68,10 +69,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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,11 +21,11 @@ Polygons& Comb::getBoundaryOutside()
|
||||
return *boundary_outside;
|
||||
}
|
||||
|
||||
BucketGrid2D<PolygonsPointIndex>& Comb::getOutsideLocToLine()
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& Comb::getOutsideLocToLine()
|
||||
{
|
||||
Polygons& outside = getBoundaryOutside();
|
||||
if (!outside_loc_to_line)
|
||||
{
|
||||
Polygons& outside = getBoundaryOutside();
|
||||
outside_loc_to_line = PolygonUtils::createLocToLineGrid(outside, offset_from_inside_to_outside * 3 / 2);
|
||||
}
|
||||
return *outside_loc_to_line;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <memory> // shared_ptr
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "../utils/BucketGrid2D.h"
|
||||
#include "../utils/SparsePointGridInclusive.h"
|
||||
#include "../utils/polygonUtils.h"
|
||||
|
||||
#include "LinePolygonsCrossings.h"
|
||||
@@ -113,7 +113,7 @@ private:
|
||||
|
||||
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.
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* outside_loc_to_line; //!< The SparsePointGridInclusive 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.
|
||||
|
||||
/*!
|
||||
@@ -122,9 +122,9 @@ private:
|
||||
Polygons& getBoundaryOutside();
|
||||
|
||||
/*!
|
||||
* Get the BucketGrid mapping locations to line segments of the outside boundary. Calculate it when it hasn't been calculated yet.
|
||||
* Get the SparsePointGridInclusive mapping locations to line segments of the outside boundary. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
BucketGrid2D<PolygonsPointIndex>& getOutsideLocToLine();
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& getOutsideLocToLine();
|
||||
|
||||
/*!
|
||||
* Move the startPoint or endPoint inside when it should be inside
|
||||
|
||||
@@ -85,12 +85,19 @@ bool LinePolygonsCrossings::lineSegmentCollidesWithBoundary()
|
||||
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))
|
||||
// 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;
|
||||
}
|
||||
|
||||
+65
-8
@@ -1,25 +1,82 @@
|
||||
/** 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, ClipperLib::jtRound));
|
||||
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, ClipperLib::jtRound));
|
||||
}
|
||||
else
|
||||
{
|
||||
storage.raftOutline = storage.getLayerOutlines(0, true).offset(distance, ClipperLib::jtRound);
|
||||
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"));
|
||||
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;
|
||||
@@ -217,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;
|
||||
@@ -262,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)
|
||||
@@ -295,7 +280,7 @@ 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();
|
||||
@@ -308,7 +293,7 @@ void SettingRegistry::handleSetting(const rapidjson::Value::ConstMemberIterator&
|
||||
{
|
||||
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();
|
||||
@@ -316,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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -33,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";
|
||||
@@ -78,24 +80,34 @@ void SettingsBase::setSetting(std::string key, std::string value)
|
||||
}
|
||||
else
|
||||
{
|
||||
cura::logError("Warning: setting an unregistered setting %s to %s\n", key.c_str(), value.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 "";
|
||||
}
|
||||
|
||||
@@ -104,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);
|
||||
@@ -121,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);
|
||||
@@ -132,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);
|
||||
@@ -160,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
|
||||
@@ -181,6 +216,20 @@ 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;
|
||||
@@ -225,6 +274,47 @@ FlowTempGraph SettingsBaseVirtual::getSettingAsFlowTempGraph(std::string key) co
|
||||
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
|
||||
{
|
||||
@@ -241,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ enum class EGCodeFlavor
|
||||
* M227 is used to initialize a single extrusion train.
|
||||
**/
|
||||
GRIFFIN = 6,
|
||||
|
||||
REPETIER = 7,
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -111,7 +113,7 @@ enum class EFillMethod
|
||||
};
|
||||
|
||||
/*!
|
||||
* Type of platform adheasion
|
||||
* Type of platform adhesion.
|
||||
*/
|
||||
enum class EPlatformAdhesion
|
||||
{
|
||||
@@ -151,6 +153,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,
|
||||
@@ -177,7 +188,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
|
||||
@@ -188,7 +208,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;
|
||||
@@ -198,9 +229,11 @@ 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;
|
||||
@@ -224,6 +257,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
|
||||
@@ -234,6 +272,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
|
||||
@@ -274,6 +313,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)
|
||||
};
|
||||
|
||||
|
||||
+17
-14
@@ -1,6 +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)
|
||||
@@ -9,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];
|
||||
|
||||
@@ -39,8 +41,8 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_
|
||||
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)
|
||||
@@ -81,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++)
|
||||
@@ -105,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)
|
||||
{
|
||||
@@ -119,10 +121,11 @@ 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 unnecessary verts
|
||||
@@ -136,7 +139,7 @@ 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];
|
||||
|
||||
@@ -146,7 +149,7 @@ void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extr
|
||||
{
|
||||
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(-innermost_wall_line_width / 2 - infill_skin_overlap);
|
||||
|
||||
for(SliceLayerPart& part2 : layer.parts)
|
||||
{
|
||||
@@ -172,7 +175,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh, un
|
||||
{
|
||||
layer_skip_count = 1;
|
||||
}
|
||||
unsigned int gradual_infill_step_layer_count = gradual_infill_step_height / mesh.getSettingInMicrons("layer_height"); // The difference in layer count between consecutive density infill areas
|
||||
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
|
||||
|
||||
+21
-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.
|
||||
|
||||
@@ -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 = storage.getSettingAsIndex("adhesion_extruder_nr");
|
||||
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.approxConvexHull();
|
||||
}
|
||||
|
||||
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
|
||||
+37
-29
@@ -67,13 +67,13 @@ 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;
|
||||
@@ -83,33 +83,35 @@ std::vector<GCodePathConfig> SliceDataStorage::initializeTravelConfigs()
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
std::vector<GCodePathConfig> SliceDataStorage::initializeSkirtConfigs()
|
||||
|
||||
std::vector<GCodePathConfig> SliceDataStorage::initializeSkirtBrimConfigs()
|
||||
{
|
||||
std::vector<GCodePathConfig> ret;
|
||||
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
skirt_config.emplace_back(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.
|
||||
retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
extruder_switch_retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
travel_config_per_extruder(initializeTravelConfigs()),
|
||||
skirt_config(initializeSkirtConfigs()),
|
||||
raft_base_config(PrintFeatureType::Support),
|
||||
skirt_brim_config(initializeSkirtBrimConfigs()),
|
||||
raft_base_config(PrintFeatureType::SupportInterface),
|
||||
raft_interface_config(PrintFeatureType::Support),
|
||||
raft_surface_config(PrintFeatureType::Support),
|
||||
raft_surface_config(PrintFeatureType::SupportInterface),
|
||||
support_config(PrintFeatureType::Support),
|
||||
support_roof_config(PrintFeatureType::Skin),
|
||||
support_skin_config(PrintFeatureType::SupportInterface),
|
||||
max_object_height_second_to_last_extruder(-1)
|
||||
{
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -136,25 +138,28 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
|
||||
else
|
||||
{
|
||||
Polygons total;
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
if (mesh.getSettingBoolean("infill_mesh"))
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
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 (mesh.getSettingBoolean("infill_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);
|
||||
}
|
||||
@@ -164,7 +169,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)
|
||||
{
|
||||
@@ -178,21 +183,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);
|
||||
}
|
||||
@@ -211,7 +219,7 @@ std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
{ // 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;
|
||||
@@ -225,7 +233,7 @@ 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)
|
||||
|
||||
@@ -124,7 +124,7 @@ 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.
|
||||
};
|
||||
|
||||
class SupportStorage
|
||||
@@ -180,7 +180,7 @@ public:
|
||||
|
||||
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;
|
||||
@@ -188,11 +188,11 @@ public:
|
||||
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
|
||||
@@ -200,20 +200,21 @@ public:
|
||||
|
||||
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
|
||||
|
||||
+519
-140
@@ -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(const Mesh* mesh, int initial, int thickness, int slice_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(slice_layer_count > 0);
|
||||
|
||||
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++)
|
||||
{
|
||||
const 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 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(const Mesh* mesh, int initial, int thickness, int slice_layer_cou
|
||||
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(const Mesh* mesh, int initial, int thickness, int slice_layer_cou
|
||||
}
|
||||
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
|
||||
|
||||
+393
-27
@@ -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,13 +488,13 @@ class Slicer
|
||||
public:
|
||||
std::vector<SlicerLayer> layers;
|
||||
|
||||
const Mesh* mesh; //!< The sliced mesh
|
||||
|
||||
Slicer(const Mesh* mesh, int initial, int thickness, int slice_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
|
||||
@@ -139,7 +505,7 @@ public:
|
||||
int64_t y = y0 + num / dx_01;
|
||||
return y;
|
||||
}
|
||||
|
||||
|
||||
SlicerSegment project2D(Point3& p0, Point3& p1, Point3& p2, int32_t z) const
|
||||
{
|
||||
SlicerSegment seg;
|
||||
@@ -151,7 +517,7 @@ public:
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
|
||||
void dumpSegmentsToHTML(const char* filename);
|
||||
};
|
||||
|
||||
|
||||
+145
-70
@@ -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,9 +33,33 @@ 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;
|
||||
}
|
||||
|
||||
@@ -45,13 +72,17 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int l
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
{
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
if (mesh.getSettingBoolean("infill_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 +95,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;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -93,48 +123,55 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
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 +194,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 +204,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--)
|
||||
@@ -193,14 +229,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,7 +245,7 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -291,6 +325,8 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
supportAreas[layer_idx] = touching_buildplate;
|
||||
}
|
||||
}
|
||||
|
||||
storage.support.generated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -331,11 +367,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,8 +380,11 @@ 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)
|
||||
{
|
||||
@@ -465,25 +505,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
-10
@@ -12,7 +12,7 @@ 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
|
||||
*/
|
||||
@@ -33,14 +33,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 +64,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
|
||||
);
|
||||
|
||||
/*!
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "timeEstimate.h"
|
||||
#include "settings/settings.h"
|
||||
#include "settings/settings.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -27,7 +29,6 @@ void TimeEstimateCalculator::setFirmwareDefaults(const SettingsBaseVirtual* sett
|
||||
acceleration = settings_base->getSettingInMillimetersPerSecond("machine_acceleration");
|
||||
}
|
||||
|
||||
template<typename T> const T square(const T& a) { return a * a; }
|
||||
|
||||
void TimeEstimateCalculator::setPosition(Position newPos)
|
||||
{
|
||||
@@ -49,6 +50,11 @@ 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()
|
||||
{
|
||||
extra_time = 0.0;
|
||||
|
||||
@@ -84,6 +84,8 @@ public:
|
||||
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.
|
||||
|
||||
@@ -1,259 +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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the size (height, width) of the cells.
|
||||
*/
|
||||
int getCellSize() const
|
||||
{
|
||||
return squareSize;
|
||||
}
|
||||
/*!
|
||||
* 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
|
||||
@@ -25,4 +25,87 @@ float LinearAlg2D::getAngleLeft(const Point& a, const Point& b, const Point& c)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool LinearAlg2D::getPointOnLineWithDist(const Point p, const Point a, const Point b, int64_t dist, Point& result)
|
||||
{
|
||||
// result
|
||||
// v
|
||||
// b<----r---a.......x
|
||||
// '-. :
|
||||
// '-. :
|
||||
// '-.p
|
||||
const Point ab = b - a;
|
||||
const int64_t ab_size = vSize(ab);
|
||||
const Point ap = p - a;
|
||||
const int64_t ax_size = (ab_size < 50)? dot(normal(ab, 1000), ap) / 1000 : dot(ab, ap) / ab_size;
|
||||
const int64_t ap_size2 = vSize2(ap);
|
||||
const int64_t px_size = sqrt(std::max(int64_t(0), ap_size2 - ax_size * ax_size));
|
||||
if (px_size > dist)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const int64_t xr_size = sqrt(dist * dist - px_size * px_size);
|
||||
if (ax_size <= 0)
|
||||
{ // x lies before ab
|
||||
const int64_t ar_size = xr_size + ax_size;
|
||||
if (ar_size < 0 || ar_size > ab_size)
|
||||
{ // r lies outisde of ab
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = a + normal(ab, ar_size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (ax_size >= ab_size)
|
||||
{ // x lies after ab
|
||||
// result
|
||||
// v
|
||||
// a-----r-->b.......x
|
||||
// '-. :
|
||||
// '-. :
|
||||
// '-.p
|
||||
const int64_t ar_size = ax_size - xr_size;
|
||||
if (ar_size < 0 || ar_size > ab_size)
|
||||
{ // r lies outisde of ab
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = a + normal(ab, ar_size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else // ax_size > 0 && ax_size < ab_size
|
||||
{ // x lies on ab
|
||||
// result is either or
|
||||
// v v
|
||||
// a-----r-----------x-----------r----->b
|
||||
// '-. : .-'
|
||||
// '-. : .-'
|
||||
// '-.p.-'
|
||||
// or there is not result:
|
||||
// v v
|
||||
// r a-------x---->b r
|
||||
// '-. : .-'
|
||||
// '-. : .-'
|
||||
// '-.p.-'
|
||||
// try r in both directions
|
||||
const int64_t ar1_size = ax_size - xr_size;
|
||||
if (ar1_size >= 0)
|
||||
{
|
||||
result = a + normal(ab, ar1_size);
|
||||
return true;
|
||||
}
|
||||
const int64_t ar2_size = ax_size + xr_size;
|
||||
if (ar2_size < ab_size)
|
||||
{
|
||||
result = a + normal(ab, ar2_size);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "ListPolyIt.h"
|
||||
|
||||
#include <cmath> // isfinite
|
||||
#include <sstream> // ostream
|
||||
|
||||
#include "AABB.h" // for debug output svg html
|
||||
#include "SVG.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
void ListPolyIt::convertPolygonsToLists(Polygons& polys, ListPolygons& result)
|
||||
{
|
||||
for (PolygonRef poly : polys)
|
||||
{
|
||||
result.emplace_back();
|
||||
convertPolygonToList(poly, result.back());
|
||||
}
|
||||
}
|
||||
|
||||
void ListPolyIt::convertPolygonToList(PolygonRef poly, ListPolygon& result)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
Point last = poly.back();
|
||||
#endif // DEBUG
|
||||
for (Point& p : poly)
|
||||
{
|
||||
result.push_back(p);
|
||||
#ifdef DEBUG
|
||||
// usually polygons shouldn't have such degenerate verts
|
||||
// in PolygonProximityLinker (where this function is (also) used) it is
|
||||
// required to not have degenerate verts, because verts are mapped
|
||||
// to links, but if two different verts are at the same place the mapping fails.
|
||||
assert(p != last);
|
||||
last = p;
|
||||
#endif // DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ListPolyIt::convertListPolygonsToPolygons(ListPolygons& list_polygons, Polygons& polygons)
|
||||
{
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
polygons[poly_idx].clear();
|
||||
convertListPolygonToPolygon(list_polygons[poly_idx], polygons[poly_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
void ListPolyIt::convertListPolygonToPolygon(ListPolygon& list_polygon, PolygonRef polygon)
|
||||
{
|
||||
for (Point& p : list_polygon)
|
||||
{
|
||||
polygon.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
ListPolyIt ListPolyIt::insertPointNonDuplicate(const ListPolyIt before, const ListPolyIt after, const Point to_insert)
|
||||
{
|
||||
if (to_insert == before.p())
|
||||
{
|
||||
return before;
|
||||
}
|
||||
else if (to_insert == after.p())
|
||||
{
|
||||
return after;
|
||||
}
|
||||
else
|
||||
{
|
||||
ListPolygon& poly = *after.poly;
|
||||
return ListPolyIt(poly, poly.insert(after.it, to_insert));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,150 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_LIST_POLY_IT_H
|
||||
#define UTILS_LIST_POLY_IT_H
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
|
||||
#include "intpoint.h"
|
||||
#include "polygon.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A wrapper class for a ListPolygon::iterator and a reference to the containing ListPolygon
|
||||
*/
|
||||
class ListPolyIt
|
||||
{
|
||||
public:
|
||||
ListPolygon* poly; //!< The polygon
|
||||
ListPolygon::iterator it; //!< The iterator into ListPolyIt::poly
|
||||
ListPolyIt(const ListPolyIt& other)
|
||||
: poly(other.poly)
|
||||
, it(other.it)
|
||||
{
|
||||
}
|
||||
ListPolyIt(ListPolygon& poly, ListPolygon::iterator it)
|
||||
: poly(&poly)
|
||||
, it(it)
|
||||
{
|
||||
}
|
||||
Point& p() const
|
||||
{
|
||||
return *it;
|
||||
}
|
||||
/*!
|
||||
* Test whether two iterators refer to the same polygon in the same polygon list.
|
||||
*
|
||||
* \param other The ListPolyIt to test for equality
|
||||
* \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument.
|
||||
*/
|
||||
bool operator==(const ListPolyIt& other) const
|
||||
{
|
||||
return poly == other.poly && it == other.it;
|
||||
}
|
||||
bool operator!=(const ListPolyIt& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
void operator=(const ListPolyIt& other)
|
||||
{
|
||||
poly = other.poly;
|
||||
it = other.it;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
ListPolyIt& operator++()
|
||||
{
|
||||
++it;
|
||||
if (it == poly->end()) { it = poly->begin(); }
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
ListPolyIt& operator--()
|
||||
{
|
||||
if (it == poly->begin()) { it = poly->end(); }
|
||||
--it;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
ListPolyIt next() const
|
||||
{
|
||||
ListPolyIt ret(*this);
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
ListPolyIt prev() const
|
||||
{
|
||||
ListPolyIt ret(*this);
|
||||
--ret;
|
||||
return ret;
|
||||
}
|
||||
//! Remove this point from the list polygon
|
||||
void remove() const
|
||||
{
|
||||
poly->erase(it);
|
||||
}
|
||||
/*!
|
||||
* Convert Polygons to ListPolygons
|
||||
*
|
||||
* \param polys The polygons to convert
|
||||
* \param result The converted polygons
|
||||
*/
|
||||
static void convertPolygonsToLists(Polygons& polys, ListPolygons& result);
|
||||
/*!
|
||||
* Convert Polygons to ListPolygons
|
||||
*
|
||||
* \param polys The polygons to convert
|
||||
* \param result The converted polygons
|
||||
*/
|
||||
static void convertPolygonToList(PolygonRef poly, ListPolygon& result);
|
||||
/*!
|
||||
* Convert ListPolygons to Polygons
|
||||
*
|
||||
* \param list_polygons The polygons to convert
|
||||
* \param polygons The converted polygons
|
||||
*/
|
||||
static void convertListPolygonsToPolygons(ListPolygons& list_polygons, Polygons& polygons);
|
||||
/*!
|
||||
* Convert ListPolygons to Polygons
|
||||
*
|
||||
* \param list_polygons The polygons to convert
|
||||
* \param polygons The converted polygons
|
||||
*/
|
||||
static void convertListPolygonToPolygon(ListPolygon& list_polygon, PolygonRef polygon);
|
||||
|
||||
/*!
|
||||
* Insert a point into a ListPolygon if it's not a duplicate of the point before or the point after.
|
||||
*
|
||||
* \param before Iterator to the point before the point to insert
|
||||
* \param after Iterator to the point after the point to insert
|
||||
* \param to_insert The point to insert into the ListPolygon in between \p before and \p after
|
||||
* \return Iterator to the newly inserted point, or \p before or \p after in case to_insert was already in the polygon
|
||||
*/
|
||||
static ListPolyIt insertPointNonDuplicate(const ListPolyIt before, const ListPolyIt after, const Point to_insert);
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
namespace std
|
||||
{
|
||||
/*!
|
||||
* Hash function for \ref ListPolyIt
|
||||
*/
|
||||
template <>
|
||||
struct hash<cura::ListPolyIt>
|
||||
{
|
||||
size_t operator()(const cura::ListPolyIt& lpi) const
|
||||
{
|
||||
return std::hash<cura::Point>()(lpi.p());
|
||||
}
|
||||
};
|
||||
}//namespace std
|
||||
|
||||
|
||||
|
||||
#endif//UTILS_LIST_POLY_IT_H
|
||||
@@ -0,0 +1,499 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include <cmath> // isfinite
|
||||
#include <sstream> // ostream
|
||||
|
||||
#include "PolygonProximityLinker.h"
|
||||
#include "linearAlg2D.h"
|
||||
|
||||
#include "AABB.h" // for debug output svg html
|
||||
#include "SVG.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
PolygonProximityLinker::PolygonProximityLinker(Polygons& polygons, int proximity_distance)
|
||||
: polygons(polygons)
|
||||
, proximity_distance(proximity_distance)
|
||||
, proximity_distance_2(proximity_distance * proximity_distance)
|
||||
, line_grid(proximity_distance, polygons.pointCount(), 3.0f)
|
||||
{
|
||||
// heuristic reserve a good amount of elements
|
||||
proximity_point_links.reserve(polygons.pointCount()); // When the whole model consists of thin walls, there will generally be a link for every point, plus some endings minus some points which map to eachother
|
||||
|
||||
// convert to list polygons for insertion of points
|
||||
ListPolyIt::convertPolygonsToLists(polygons, list_polygons);
|
||||
|
||||
// link each corner to itself
|
||||
addSharpCorners();
|
||||
|
||||
// map each vertex onto nearby line segments
|
||||
findProximatePoints();
|
||||
|
||||
// add links where line segments diverge from below the proximity distance to over the proximity distance
|
||||
addProximityEndings();
|
||||
|
||||
// convert list polygons back
|
||||
ListPolyIt::convertListPolygonsToPolygons(list_polygons, polygons);
|
||||
// proximity2HTML("linker.html");
|
||||
}
|
||||
|
||||
bool PolygonProximityLinker::isLinked(Point from)
|
||||
{
|
||||
return point_to_link.find(from) != point_to_link.end();
|
||||
}
|
||||
|
||||
|
||||
std::pair<PolygonProximityLinker::Point2Link::iterator, PolygonProximityLinker::Point2Link::iterator> PolygonProximityLinker::getLinks(Point from)
|
||||
{
|
||||
std::pair<Point2Link::iterator, Point2Link::iterator> from_link_pair = point_to_link.equal_range(from);
|
||||
#ifdef DEBUG
|
||||
for (Point2Link::iterator it = from_link_pair.first; it != from_link_pair.second; ++it)
|
||||
{
|
||||
if (!(it->second.a.p() == from || it->second.b.p() == from))
|
||||
{
|
||||
std::cerr << " ERROR!\n" << it->first << " == " << from << "\n";
|
||||
std::cerr << "should be either " << it->second.a.p() << " or " << it->second.b.p() << "\n";
|
||||
std::cerr << (from_link_pair.first == from_link_pair.second) << " ; " << (from_link_pair.first == point_to_link.end()) << " ; " << (from_link_pair.second == point_to_link.end()) << "\n";
|
||||
std::cerr << std::hash<Point>()(from) << " hashes " << std::hash<Point>()(it->second.a.p()) << " or " << std::hash<Point>()(it->second.b.p()) << "\n";
|
||||
// std::cerr << "ERROR! some point got mapped to a link which doesn't have the point as one of the end points!\n";
|
||||
|
||||
std::cerr << "\n all links:\n";
|
||||
for (std::pair<const Point, const ProximityPointLink> pair : point_to_link)
|
||||
{
|
||||
std::cerr << pair.first << " : " << pair.second.a.p() << "-" <<pair.second.b.p() << "\n";
|
||||
}
|
||||
|
||||
std::cerr << "\n link set \n";
|
||||
for (const ProximityPointLink link : proximity_point_links)
|
||||
{
|
||||
std::cerr << link.a.p() << "-" << link.b.p() << " hashes as " << std::hash<ProximityPointLink>()(link) << "\n";
|
||||
}
|
||||
assert(false && "some point got mapped to a link which doesn't have the point as one of the end points!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return from_link_pair;
|
||||
}
|
||||
|
||||
void PolygonProximityLinker::createLineGrid()
|
||||
{
|
||||
for (unsigned int poly_idx = 0; poly_idx < list_polygons.size(); poly_idx++)
|
||||
{
|
||||
ListPolygon& poly = list_polygons[poly_idx];
|
||||
for (ListPolygon::iterator it = poly.begin(); it != poly.end(); ++it)
|
||||
{
|
||||
ListPolyIt point_it(poly, it);
|
||||
line_grid.insert(point_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonProximityLinker::findProximatePoints()
|
||||
{
|
||||
createLineGrid();
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < list_polygons.size(); poly_idx++)
|
||||
{
|
||||
ListPolygon& poly = list_polygons[poly_idx];
|
||||
for (ListPolygon::iterator it = poly.begin(); it != poly.end(); ++it)
|
||||
{
|
||||
ListPolyIt point_it(poly, it);
|
||||
if (new_points.find(point_it) == new_points.end())
|
||||
{
|
||||
// handle new_points separately
|
||||
// to prevent this:
|
||||
// 1 3 5 7
|
||||
// o<-.
|
||||
// : 'o<-.
|
||||
// : /: o<.
|
||||
// : / : /: 'o<.
|
||||
// : / : / : / :
|
||||
// :/ :/ :/ : etc.
|
||||
// o--->o-->o-->o->
|
||||
// 2 4 6 8
|
||||
std::unordered_set<ListPolyIt> nearby_lines;
|
||||
auto process_func = [&nearby_lines](const ListPolyIt& elem)
|
||||
{
|
||||
nearby_lines.emplace(elem);
|
||||
};
|
||||
line_grid.processNearby(point_it.p(), proximity_distance, process_func);
|
||||
for (const ListPolyIt& nearby_line : nearby_lines)
|
||||
{
|
||||
findProximatePoints(point_it, *nearby_line.poly, nearby_line, nearby_line.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const ListPolyIt& new_point_it : new_points)
|
||||
{
|
||||
// link with existing points, but don't introduce new points for line segments
|
||||
// to prevent this:
|
||||
// 1 3 5 7
|
||||
// o<-.
|
||||
// : 'o<-.
|
||||
// : /: o<.
|
||||
// : / : /: 'o<.
|
||||
// : / : / : / :
|
||||
// :/ :/ :/ : etc.
|
||||
// o--->o-->o-->o->
|
||||
// 2 4 6 8
|
||||
std::unordered_set<ListPolyIt> nearby_vert_its;
|
||||
auto process_func = [&nearby_vert_its](const ListPolyIt& elem)
|
||||
{
|
||||
nearby_vert_its.emplace(elem);
|
||||
};
|
||||
line_grid.processNearby(new_point_it.p(), proximity_distance, process_func);
|
||||
// because we use the same line_grid as before the resulting nearby_points
|
||||
// will also have points which are not nearby, (But when the line segment *is* nearby.)
|
||||
// but at least we don't have to create a whole new SparsePointGrid
|
||||
for (const ListPolyIt& nearby_vert_it : nearby_vert_its)
|
||||
{
|
||||
Point new_point = new_point_it.p();
|
||||
Point nearby_vert = nearby_vert_it.p();
|
||||
int64_t dist2 = vSize2(new_point - nearby_vert);
|
||||
if (dist2 < proximity_distance_2
|
||||
&& new_point != nearby_vert // not the same point
|
||||
)
|
||||
{
|
||||
addProximityLink(new_point_it, nearby_vert_it, sqrt(dist2), ProximityPointLinkType::NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonProximityLinker::findProximatePoints(const ListPolyIt a_point_it, ListPolygon& to_list_poly, const ListPolyIt b_from_it, const ListPolyIt b_to_it)
|
||||
{
|
||||
const Point& a_point = a_point_it.p();
|
||||
|
||||
const Point& b_from = b_from_it.p();
|
||||
const Point& b_to = b_to_it.p();
|
||||
|
||||
if (a_point_it == b_from_it || a_point_it == b_to_it) // we currently consider a linesegment directly connected to [from]
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Point closest = LinearAlg2D::getClosestOnLineSegment(a_point, b_from, b_to);
|
||||
|
||||
int64_t dist2 = vSize2(closest - a_point);
|
||||
|
||||
if (dist2 > proximity_distance_2
|
||||
|| (a_point_it.poly == &to_list_poly
|
||||
&& dot(a_point_it.next().p() - a_point, b_to - b_from) > 0
|
||||
&& dot(a_point - a_point_it.prev().p(), b_to - b_from) > 0 ) // line segments are likely connected, because the winding order is in the same general direction
|
||||
)
|
||||
{ // line segment too far away to be proximate
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t dist = sqrt(dist2);
|
||||
|
||||
if (shorterThen(closest - b_from, 10))
|
||||
{
|
||||
addProximityLink(a_point_it, b_from_it, dist, ProximityPointLinkType::NORMAL);
|
||||
}
|
||||
else if (shorterThen(closest - b_to, 10))
|
||||
{
|
||||
addProximityLink(a_point_it, b_to_it, dist, ProximityPointLinkType::NORMAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (new_points.find(a_point_it) == new_points.end())
|
||||
{
|
||||
// don't introduce new points for newly introduced points
|
||||
// to prevent this:
|
||||
// 1 3 5 7
|
||||
// o<-.
|
||||
// : 'o<-.
|
||||
// : /: o<.
|
||||
// : / : /: 'o<.
|
||||
// : / : / : / :
|
||||
// :/ :/ :/ : etc.
|
||||
// o--->o-->o-->o->
|
||||
// 2 4 6 8
|
||||
ListPolyIt new_it = addNewPolyPoint(closest, b_from_it, b_to_it, b_to_it);
|
||||
addProximityLink(a_point_it, new_it, dist, ProximityPointLinkType::NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool PolygonProximityLinker::addProximityLink(ListPolyIt from, ListPolyIt to, int64_t dist, const ProximityPointLinkType type)
|
||||
{
|
||||
std::pair<ProximityPointLinks::iterator, bool> result =
|
||||
proximity_point_links.emplace(from, to, dist, type);
|
||||
|
||||
if (!result.second)
|
||||
{ // links was already made!
|
||||
return false;
|
||||
}
|
||||
ProximityPointLinks::iterator it = result.first;
|
||||
assert(it->a == from);
|
||||
assert(it->b == to);
|
||||
assert(it->a.p() == from.p());
|
||||
assert(it->b.p() == to.p());
|
||||
addToPoint2LinkMap(from.p(), it);
|
||||
addToPoint2LinkMap(to.p(), it);
|
||||
|
||||
return result.second;
|
||||
}
|
||||
|
||||
bool PolygonProximityLinker::addCornerLink(ListPolyIt corner_point, const ProximityPointLinkType type)
|
||||
{
|
||||
constexpr int dist = 0;
|
||||
std::pair<ProximityPointLinks::iterator, bool> result =
|
||||
proximity_point_links.emplace(corner_point, corner_point, dist, type);
|
||||
|
||||
if (!result.second)
|
||||
{ // links was already made!
|
||||
return false;
|
||||
}
|
||||
ProximityPointLinks::iterator it = result.first;
|
||||
assert(it->a == corner_point);
|
||||
assert(it->b == corner_point);
|
||||
addToPoint2LinkMap(corner_point.p(), it);
|
||||
|
||||
return result.second;
|
||||
}
|
||||
|
||||
void PolygonProximityLinker::addProximityEndings()
|
||||
{
|
||||
ProximityPointLinks new_links; // Where to store the new links temporarily (Don't add them to the map we are iterating over!)
|
||||
for (const ProximityPointLink& link : proximity_point_links)
|
||||
{
|
||||
if (link.dist == proximity_distance)
|
||||
{ // its ending itself
|
||||
continue;
|
||||
}
|
||||
const ListPolyIt& a_1 = link.a;
|
||||
const ListPolyIt& b_1 = link.b;
|
||||
// an overlap segment can be an ending in two directions
|
||||
{
|
||||
ListPolyIt a_2 = a_1.next();
|
||||
ListPolyIt b_2 = b_1.prev();
|
||||
addProximityEnding(link, a_2, b_2, a_2, b_1, new_links);
|
||||
}
|
||||
{
|
||||
ListPolyIt a_2 = a_1.prev();
|
||||
ListPolyIt b_2 = b_1.next();
|
||||
addProximityEnding(link, a_2, b_2, a_1, b_2, new_links);
|
||||
}
|
||||
}
|
||||
for (const ProximityPointLink& link : new_links)
|
||||
{
|
||||
addProximityLink(link.a, link.b, link.dist, link.type);
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonProximityLinker::addProximityEnding(const ProximityPointLink& link, const ListPolyIt& a2_it, const ListPolyIt& b2_it, const ListPolyIt& a_after_middle, const ListPolyIt& b_after_middle, ProximityPointLinks& result)
|
||||
{
|
||||
Point& a1 = link.a.p();
|
||||
Point& a2 = a2_it.p();
|
||||
Point& b1 = link.b.p();
|
||||
Point& b2 = b2_it.p();
|
||||
Point a = a2 - a1;
|
||||
Point b = b2 - b1;
|
||||
|
||||
if (isLinked(a2_it.p()) && isLinked(b2_it.p())) // overlap area stops at one side
|
||||
{
|
||||
// TODO: add proximity endings between point and line ?
|
||||
// would be good for:
|
||||
// ----+
|
||||
// |
|
||||
// +-----
|
||||
// would be bad for
|
||||
// ----+-+-----
|
||||
return;
|
||||
}
|
||||
if (isLinked(a2_it, link.b) || isLinked(b2_it, link.a))
|
||||
{ // other side of ending continues to overlap with the same ending
|
||||
// link considered
|
||||
// *
|
||||
// o<--o<--o<--
|
||||
// : : .` one more link from the upper side
|
||||
// o-->o last overlap point on this side
|
||||
// |
|
||||
// v
|
||||
// 0
|
||||
return;
|
||||
}
|
||||
if (a2_it == b2_it)
|
||||
{ // overlap ends in pointy end
|
||||
// o-->o-->o
|
||||
// : : : \,
|
||||
// : : : o wasn't linked yet because it's connected to the upper and lower part
|
||||
// : : :,/
|
||||
// o<--o<--o
|
||||
result.emplace(ProximityPointLink(a2_it, a2_it, 0, ProximityPointLinkType::ENDING_CORNER));
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t dist = proximityEndingDistance(a1, a2, b1, b2, link.dist);
|
||||
if (dist < 0) { return; }
|
||||
int64_t a_length2 = vSize2(a);
|
||||
int64_t b_length2 = vSize2(b);
|
||||
if (dist*dist > std::min(a_length2, b_length2) )
|
||||
{ // TODO remove this /\ case if error below is never shown
|
||||
// DEBUG_PRINTLN("Next point should have been linked already!!");
|
||||
dist = std::sqrt(std::min(a_length2, b_length2));
|
||||
if (a_length2 < b_length2)
|
||||
{
|
||||
Point b_p = b1 + normal(b, dist);
|
||||
ListPolyIt new_b = addNewPolyPoint(b_p, link.b, b2_it, b_after_middle);
|
||||
result.emplace(ProximityPointLink(a2_it, new_b, proximity_distance, ProximityPointLinkType::ENDING));
|
||||
}
|
||||
else if (b_length2 < a_length2)
|
||||
{
|
||||
Point a_p = a1 + normal(a, dist);
|
||||
ListPolyIt new_a = addNewPolyPoint(a_p, link.a, a2_it, a_after_middle);
|
||||
result.emplace(ProximityPointLink(new_a, b2_it, proximity_distance, ProximityPointLinkType::ENDING));
|
||||
}
|
||||
else // equal
|
||||
{
|
||||
result.emplace(ProximityPointLink(a2_it, b2_it, proximity_distance, ProximityPointLinkType::ENDING));
|
||||
}
|
||||
}
|
||||
else if (dist > 0)
|
||||
{
|
||||
Point a_p = a1 + normal(a, dist);
|
||||
ListPolyIt new_a = addNewPolyPoint(a_p, link.a, a2_it, a_after_middle);
|
||||
Point b_p = b1 + normal(b, dist);
|
||||
ListPolyIt new_b = addNewPolyPoint(b_p, link.b, b2_it, b_after_middle);
|
||||
result.emplace(ProximityPointLink(new_a, new_b, proximity_distance, ProximityPointLinkType::ENDING));
|
||||
}
|
||||
else if (dist == 0)
|
||||
{
|
||||
// addProximityLink(link.a, link.b, proximity_distance);
|
||||
// won't be inserted any way, because there already is such a link!
|
||||
}
|
||||
}
|
||||
|
||||
ListPolyIt PolygonProximityLinker::addNewPolyPoint(const Point point, const ListPolyIt line_start, const ListPolyIt line_end, const ListPolyIt before_this)
|
||||
{
|
||||
if (point == line_start.p())
|
||||
{
|
||||
return line_start;
|
||||
}
|
||||
if (point == line_end.p())
|
||||
{
|
||||
return line_end;
|
||||
}
|
||||
ListPolygon::iterator new_p = before_this.poly->insert(before_this.it, point);
|
||||
ListPolyIt lpi(*before_this.poly, new_p);
|
||||
line_grid.insert(lpi);
|
||||
// TODO: remove new part of old segment from the line_grid (?)
|
||||
new_points.emplace(lpi);
|
||||
return lpi;
|
||||
}
|
||||
|
||||
int64_t PolygonProximityLinker::proximityEndingDistance(Point& a1, Point& a2, Point& b1, Point& b2, int a1b1_dist)
|
||||
{
|
||||
int overlap = proximity_distance - a1b1_dist;
|
||||
Point a = a2-a1;
|
||||
Point b = b2-b1;
|
||||
double cos_angle = INT2MM2(dot(a, b)) / vSizeMM(a) / vSizeMM(b);
|
||||
// result == .5*overlap / tan(.5*angle) == .5*overlap / tan(.5*acos(cos_angle))
|
||||
// [wolfram alpha] == 0.5*overlap * sqrt(cos_angle+1)/sqrt(1-cos_angle)
|
||||
// [assuming positive x] == 0.5*overlap / sqrt( 2 / (cos_angle + 1) - 1 )
|
||||
if (cos_angle <= 0
|
||||
|| ! std::isfinite(cos_angle) )
|
||||
{
|
||||
return -1; // line_width / 2;
|
||||
}
|
||||
else if (cos_angle > .9999) // values near 1 can lead too large numbers for 1/x
|
||||
{
|
||||
return std::min(vSize(b), vSize(a));
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t dist = overlap * double ( 1.0 / (2.0 * sqrt(2.0 / (cos_angle+1.0) - 1.0)) );
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonProximityLinker::addSharpCorners()
|
||||
{
|
||||
for (ListPolygon& poly : list_polygons)
|
||||
{
|
||||
ListPolyIt here(poly, --poly.end());
|
||||
ListPolyIt prev = here.prev();
|
||||
for (ListPolygon::iterator it = poly.begin(); it != poly.end(); ++it)
|
||||
{
|
||||
ListPolyIt next(poly, it);
|
||||
|
||||
if (LinearAlg2D::isAcuteCorner(prev.p(), here.p(), next.p()) > 0)
|
||||
{
|
||||
addCornerLink(here, ProximityPointLinkType::SHARP_CORNER);
|
||||
}
|
||||
|
||||
prev = here;
|
||||
here = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonProximityLinker::addToPoint2LinkMap(Point p, ProximityPointLinks::iterator it)
|
||||
{
|
||||
const ProximityPointLink& link = *it;
|
||||
point_to_link.emplace(p, link); // copy element from proximity_point_links set to Point2Link map
|
||||
assert(p == link.a.p() || p == link.b.p());
|
||||
}
|
||||
|
||||
|
||||
void PolygonProximityLinker::proximity2HTML(const char* filename) const
|
||||
{
|
||||
PolygonProximityLinker copy = *this; // copy, cause getFlow might change the state of the overlap computation!
|
||||
|
||||
AABB aabb(copy.polygons);
|
||||
|
||||
aabb.expand(200);
|
||||
|
||||
SVG svg(filename, aabb, Point(1024 * 2, 1024 * 2));
|
||||
|
||||
|
||||
svg.writeAreas(copy.polygons);
|
||||
|
||||
{ // output points and coords
|
||||
for (ListPolygon poly : copy.list_polygons)
|
||||
{
|
||||
for (Point& p : poly)
|
||||
{
|
||||
svg.writePoint(p, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // output links
|
||||
// output normal links
|
||||
for (const ProximityPointLink& link : copy.proximity_point_links)
|
||||
{
|
||||
svg.writePoint(link.a.p(), false, 3, SVG::Color::GRAY);
|
||||
svg.writePoint(link.b.p(), false, 3, SVG::Color::GRAY);
|
||||
Point a = svg.transform(link.a.p());
|
||||
Point b = svg.transform(link.b.p());
|
||||
svg.printf("<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:rgb(%d,%d,0);stroke-width:1\" />", a.X, a.Y, b.X, b.Y, link.dist == proximity_distance? 0 : 255, link.dist==proximity_distance? 255 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PolygonProximityLinker::isLinked(ListPolyIt a, ListPolyIt b)
|
||||
{
|
||||
ProximityPointLink test_link(a, b, 0, ProximityPointLinkType::NORMAL);
|
||||
return proximity_point_links.find(test_link) != proximity_point_links.end();
|
||||
}
|
||||
|
||||
const ProximityPointLink* PolygonProximityLinker::getLink(ListPolyIt a, ListPolyIt b)
|
||||
{
|
||||
ProximityPointLink test_link(a, b, 0, ProximityPointLinkType::NORMAL);
|
||||
ProximityPointLinks::const_iterator found = proximity_point_links.find(test_link);
|
||||
|
||||
if (found != proximity_point_links.end())
|
||||
{
|
||||
return &*found;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,222 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_POLYGON_PROXIMITY_LINKER_H
|
||||
#define UTILS_POLYGON_PROXIMITY_LINKER_H
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
#include <utility> // pair
|
||||
|
||||
#include <functional> // hash function object
|
||||
|
||||
#include "intpoint.h"
|
||||
#include "polygon.h"
|
||||
|
||||
#include "ListPolyIt.h"
|
||||
#include "ProximityPointLink.h"
|
||||
#include "SparseLineGrid.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for computing which parts of polygons are close to which other parts of polygons
|
||||
* A link always occurs between a point already on a polygon and either another point of a polygon or a point on a line segment of a polygon.
|
||||
*
|
||||
* In the latter case we insert the point into the polygon so that we can later look up by how much to reduce the extrusion at the corresponding line segment.
|
||||
* This is the reason that the polygons are converted to (linked) lists before the proximity linking computation takes place, after which they are converted back.
|
||||
*
|
||||
* At the end of a sequence of proximity links the polygon segments diverge away from each other.
|
||||
* Therefore points are introduced on the line segments involved and a link is created with a link distance of exactly the PolygonProximityLinker::proximity_distance.
|
||||
*
|
||||
* We end up with links which include a boolean field to represent whether the link is already processed from outside.
|
||||
* This is used by functions which use the PolygonProximityLinker class when there is being looped over points in a polygon, which by definition loops over all links twice.
|
||||
*
|
||||
* Each point on the polygons maps to a link, so that we can easily look up which links corresponds to the current line segment being handled when compensating for wall overlaps for example.
|
||||
*
|
||||
* The main functionality of this class is performed by the constructor.
|
||||
*/
|
||||
class PolygonProximityLinker
|
||||
{
|
||||
public:
|
||||
typedef std::unordered_set<ProximityPointLink> ProximityPointLinks; //!< The type of PolygonProximityLinker::overlap_point_links
|
||||
typedef std::unordered_multimap<Point, ProximityPointLink> Point2Link; //!< The type of PolygonProximityLinker::point_to_link
|
||||
|
||||
private:
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*!
|
||||
* Locator to retrieve line segment data from a \ref ListPolyIt
|
||||
*/
|
||||
struct ListPolyItSegmentLocator
|
||||
{
|
||||
std::pair<Point, Point> operator()(const ListPolyIt& val) const
|
||||
{
|
||||
return std::pair<Point, Point>(val.p(), val.next().p());
|
||||
}
|
||||
};
|
||||
|
||||
Polygons& polygons; //!< The polygons for which to compensate overlapping walls for
|
||||
ListPolygons list_polygons; //!< The PolygonProximityLinker::polygons converted
|
||||
|
||||
int proximity_distance; //!< The line width of the walls
|
||||
int proximity_distance_2; //!< The squared line width of the walls
|
||||
|
||||
SparseLineGrid<ListPolyIt, ListPolyItSegmentLocator> line_grid; //!< Mapping from locations to lines
|
||||
|
||||
std::unordered_set<ListPolyIt> new_points; //!< The newly inserted points to create new links.
|
||||
|
||||
ProximityPointLinks proximity_point_links; //!< mapping from each link to its attributes
|
||||
|
||||
Point2Link point_to_link; //!< mapping from each point to the/a corresponding link (collisions are ignored as of yet)
|
||||
|
||||
/*!
|
||||
* Create the initial \ref PolygonProximityLinker::line_grid
|
||||
*
|
||||
* Map locations where line segments of the input polygons occur to iterators pointing to the start of those line segments.
|
||||
*
|
||||
* Note that when points are introduced which split line segments in two,
|
||||
* the line_grid maps locations to segments which are not close any more.
|
||||
*/
|
||||
void createLineGrid();
|
||||
|
||||
void findProximatePoints(); //!< find the basic proximity links (for trapezoids) and record them into PolygonProximityLinker::overlap_point_links
|
||||
|
||||
/*!
|
||||
* Find the basic proximity link (for a trapezoid) between a given point and a line segment
|
||||
* and record them into PolygonProximityLinker::overlap_point_links
|
||||
*
|
||||
* \param a_point_it Iterator to the point from which to check for proximity
|
||||
* \param to_list_poly The polygon in which the line segment occurs
|
||||
* \param b_from_it iterator to the one end point of the line segment
|
||||
* \param b_to_it iterator to the other end point of the line segment
|
||||
*/
|
||||
void findProximatePoints(const ListPolyIt a_point_it, ListPolygon& to_list_poly, const ListPolyIt b_from_it, const ListPolyIt b_to_it);
|
||||
|
||||
/*!
|
||||
* Add a new point to the polygon on a line segment between \p line_start and \p line_end
|
||||
*
|
||||
* Don't add the point if it's already the same as either of the end points of the line segment.
|
||||
*
|
||||
* \param point The point to insert
|
||||
* \param line_start The start of the line segment on which to insert
|
||||
* \param line_end The end of the line segment on which to insert
|
||||
* \param before_this Either \p line_start or \p line_end such that inserting the new point before \p before_this results in the point being in between the two
|
||||
* \return An iterator to the newly inserted point, or an existing point if \p coincided with either end point of the line
|
||||
*/
|
||||
ListPolyIt addNewPolyPoint(const Point point, const ListPolyIt line_start, const ListPolyIt line_end, const ListPolyIt before_this);
|
||||
|
||||
/*!
|
||||
* Add a link between \p from and \p to to PolygonProximityLinker::overlap_point_links and add the appropriate mappings to PolygonProximityLinker::point_to_link
|
||||
*
|
||||
* \param from The one point of the link
|
||||
* \param to The other point of the link
|
||||
* \param dist The distance between the two points
|
||||
* \param type The type of the link being introduced
|
||||
* \return Whether the point has been added
|
||||
*/
|
||||
bool addProximityLink(ListPolyIt from, ListPolyIt to, int64_t dist, const ProximityPointLinkType type);
|
||||
|
||||
|
||||
/*!
|
||||
* Add a link for the corner at \p corner_point to PolygonProximityLinker::overlap_point_links and add the appropriate mappings to PolygonProximityLinker::point_to_link
|
||||
*
|
||||
* This is done by adding a link between the point and itself.
|
||||
*
|
||||
* \param corner_point The one point of the link
|
||||
* \param type The type of the link being introduced
|
||||
* \return Whether the point has been added
|
||||
*/
|
||||
bool addCornerLink(ListPolyIt corner_point, const ProximityPointLinkType type);
|
||||
|
||||
/*!
|
||||
* Add links for the ending points of proximity regions, supporting the residual triangles.
|
||||
*/
|
||||
void addProximityEndings();
|
||||
|
||||
/*!
|
||||
* Add a link for the ending point of a given proximity region, if it is an ending.
|
||||
*
|
||||
* \param link_pair The link which might be an ending
|
||||
* \param a_next The next point from ListPolyIt::a of \p link
|
||||
* \param b_next The next point from ListPolyIt::b of \p link (in the opposite direction of \p a_next)
|
||||
* \param a_before_middle Where to insert a new point for a if this is indeed en ending
|
||||
* \param b_before_middle Where to insert a new point for b if this is indeed en ending
|
||||
* \param[out] result Where to store a link if a new one has been generated
|
||||
*/
|
||||
void addProximityEnding(const ProximityPointLink& link, const ListPolyIt& a_next, const ListPolyIt& b_next, const ListPolyIt& a_before_middle, const ListPolyIt& b_before_middle, ProximityPointLinks& result);
|
||||
|
||||
/*!
|
||||
* Compute the distance between the points of the last link and the points introduced to account for the proximity endings.
|
||||
*/
|
||||
int64_t proximityEndingDistance(Point& a1, Point& a2, Point& b1, Point& b2, int a1b1_dist);
|
||||
|
||||
/*!
|
||||
* Add proximity links for sharp corners, so that the proximity of two consecutive line segments is compensated for.
|
||||
*
|
||||
* Currently UNIMPLEMENTED.
|
||||
*/
|
||||
void addSharpCorners();
|
||||
|
||||
/*!
|
||||
* Map a point to a link in PolygonProximityLinker::point_to_link
|
||||
*
|
||||
* \param p The key
|
||||
* \param it The value
|
||||
*/
|
||||
void addToPoint2LinkMap(Point p, ProximityPointLinks::iterator it);
|
||||
|
||||
public:
|
||||
void proximity2HTML(const char* filename) const; //!< debug
|
||||
|
||||
/*!
|
||||
* Computes the neccesary priliminaries in order to efficiently compute the flow when generatign gcode paths.
|
||||
* \param polygons The wall polygons for which to compute the overlaps
|
||||
*/
|
||||
PolygonProximityLinker(Polygons& polygons, int proximity_distance);
|
||||
|
||||
/*!
|
||||
* Check whether a point has any links
|
||||
* \param from the point for which to check whether it has any links
|
||||
* \return Whether a link has been created between the point and another point
|
||||
*/
|
||||
bool isLinked(Point from);
|
||||
|
||||
/*!
|
||||
* Get all links connected to a given point.
|
||||
*
|
||||
* The returned pair is an iterator range;
|
||||
* The first is the starting iterator (inclusive)
|
||||
* and the second is the end iterator (exclusive).
|
||||
*
|
||||
* Note that the returned iterators point to a pair,
|
||||
* for which the second is the actual link.
|
||||
* The first is \p from
|
||||
*
|
||||
* \param from The point to get all connected links for
|
||||
* \return a pair containing two iterators
|
||||
*/
|
||||
std::pair<PolygonProximityLinker::Point2Link::iterator, PolygonProximityLinker::Point2Link::iterator> getLinks(Point from);
|
||||
|
||||
/*!
|
||||
* Check whether two points are linked
|
||||
* \param a an iterator to the first point (in \ref PolygonProximityLinker::list_polygons)
|
||||
* \param b an iterator to the second point (in \ref PolygonProximityLinker::list_polygons)
|
||||
* \return Whether a link has been created between the two points
|
||||
*/
|
||||
bool isLinked(ListPolyIt a, ListPolyIt b);
|
||||
|
||||
/*!
|
||||
* Get the link between two points if they are linked already
|
||||
* \param a an iterator to the first point (in \ref PolygonProximityLinker::list_polygons)
|
||||
* \param b an iterator to the second point (in \ref PolygonProximityLinker::list_polygons)
|
||||
* \return The link between the two points, or nullptr
|
||||
*/
|
||||
const ProximityPointLink* getLink(ListPolyIt a, ListPolyIt b);
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
#endif//UTILS_POLYGON_PROXIMITY_LINKER_H
|
||||
@@ -0,0 +1,118 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_POLYGONS_POINT_INDEX_H
|
||||
#define UTILS_POLYGONS_POINT_INDEX_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "intpoint.h"
|
||||
#include "polygon.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A class for iterating over the points in one of the polygons in a \ref Polygons object
|
||||
*/
|
||||
class PolygonsPointIndex
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The polygons into which this index is indexing
|
||||
*/
|
||||
const Polygons* polygons; // (pointer to const polygons)
|
||||
unsigned int poly_idx; //!< The index of the polygon in \ref PolygonsPointIndex::polygons
|
||||
unsigned int point_idx; //!< The index of the point in the polygon in \ref PolygonsPointIndex::polygons
|
||||
PolygonsPointIndex()
|
||||
: polygons(nullptr)
|
||||
, poly_idx(0)
|
||||
, point_idx(0)
|
||||
{
|
||||
}
|
||||
PolygonsPointIndex(const Polygons* polygons, unsigned int poly_idx, unsigned int point_idx)
|
||||
: polygons(polygons)
|
||||
, poly_idx(poly_idx)
|
||||
, point_idx(point_idx)
|
||||
{
|
||||
}
|
||||
Point p() const
|
||||
{
|
||||
if (!polygons)
|
||||
{
|
||||
return Point(0, 0);
|
||||
}
|
||||
return (*polygons)[poly_idx][point_idx];
|
||||
}
|
||||
/*!
|
||||
* Test whether two iterators refer to the same polygon in the same polygon list.
|
||||
*
|
||||
* \param other The PolygonsPointIndex to test for equality
|
||||
* \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument.
|
||||
*/
|
||||
bool operator==(const PolygonsPointIndex& other) const
|
||||
{
|
||||
return polygons == other.polygons && poly_idx == other.poly_idx && point_idx == other.point_idx;
|
||||
}
|
||||
bool operator!=(const PolygonsPointIndex& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
void operator=(const PolygonsPointIndex& other)
|
||||
{
|
||||
polygons = other.polygons;
|
||||
poly_idx = other.poly_idx;
|
||||
point_idx = other.point_idx;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PolygonsPointIndex& operator++()
|
||||
{
|
||||
point_idx = (point_idx + 1) % (*polygons)[poly_idx].size();
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
PolygonsPointIndex& operator--()
|
||||
{
|
||||
if (point_idx == 0)
|
||||
{
|
||||
point_idx = (*polygons)[poly_idx].size();
|
||||
}
|
||||
point_idx--;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PolygonsPointIndex next() const
|
||||
{
|
||||
PolygonsPointIndex ret(*this);
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
PolygonsPointIndex prev() const
|
||||
{
|
||||
PolygonsPointIndex ret(*this);
|
||||
--ret;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
namespace std
|
||||
{
|
||||
/*!
|
||||
* Hash function for \ref PolygonsPointIndex
|
||||
*/
|
||||
template <>
|
||||
struct hash<cura::PolygonsPointIndex>
|
||||
{
|
||||
size_t operator()(const cura::PolygonsPointIndex& lpi) const
|
||||
{
|
||||
return std::hash<cura::Point>()(lpi.p());
|
||||
}
|
||||
};
|
||||
}//namespace std
|
||||
|
||||
|
||||
|
||||
#endif//UTILS_POLYGONS_POINT_INDEX_H
|
||||
@@ -0,0 +1,20 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "ProximityPointLink.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
ProximityPointLink::ProximityPointLink(const ListPolyIt a, const ListPolyIt b, int dist, const ProximityPointLinkType type)
|
||||
: a(a)
|
||||
, b(b)
|
||||
, dist(dist)
|
||||
, type(type)
|
||||
{
|
||||
}
|
||||
|
||||
bool ProximityPointLink::operator==(const ProximityPointLink& other) const
|
||||
{
|
||||
return (a == other.a && b == other.b) || (a == other.b && b == other.a);
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,63 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef PROXIMITY_POINT_LINK_H
|
||||
#define PROXIMITY_POINT_LINK_H
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
#include <utility> // pair
|
||||
|
||||
#include <functional> // hash function object
|
||||
|
||||
#include "intpoint.h"
|
||||
#include "polygon.h"
|
||||
#include "linearAlg2D.h"
|
||||
#include "optional.h"
|
||||
|
||||
#include "ListPolyIt.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Type of ProximityPointLink signifying why/how it was created
|
||||
*/
|
||||
enum class ProximityPointLinkType
|
||||
{
|
||||
NORMAL, // Point is close to line segment or to another point
|
||||
ENDING, // link where two line segments diverge and have the maximum proximity, i.e. where the overlap will be zero
|
||||
ENDING_CORNER, // when an overlap area ends in a point
|
||||
SHARP_CORNER // The corner in the polygon is so sharp that it will overlap with itself
|
||||
};
|
||||
|
||||
/*!
|
||||
* A class recording the amount of overlap implicitly by recording the distance between two points on two different polygons or one and the same polygon.
|
||||
* The order of the two points doesn't matter.
|
||||
*/
|
||||
struct ProximityPointLink
|
||||
{
|
||||
const ListPolyIt a; //!< the one point (invalidated after list_polygons have been cleared!)
|
||||
const ListPolyIt b; //!< the other point (invalidated after list_polygons have been cleared!)
|
||||
const int dist; //!< The distance between the two points
|
||||
const ProximityPointLinkType type; //!< The type of link; why/how it was created
|
||||
ProximityPointLink(const ListPolyIt a, const ListPolyIt b, int dist, const ProximityPointLinkType type);
|
||||
bool operator==(const ProximityPointLink& other) const;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct hash<cura::ProximityPointLink>
|
||||
{
|
||||
size_t operator()(const cura::ProximityPointLink & pp) const
|
||||
{ // has to be symmetric wrt a and b!
|
||||
return std::hash<cura::Point>()(pp.a.p()) + std::hash<cura::Point>()(pp.b.p());
|
||||
}
|
||||
};
|
||||
}//namespace std
|
||||
|
||||
|
||||
#endif//PROXIMITY_POINT_LINK_H
|
||||
+10
-3
@@ -47,6 +47,7 @@ private:
|
||||
const AABB aabb; // the boundary box to display
|
||||
const Point aabb_size;
|
||||
const Point border;
|
||||
const Point canvas_size;
|
||||
const double scale;
|
||||
|
||||
public:
|
||||
@@ -54,6 +55,7 @@ public:
|
||||
: aabb(aabb)
|
||||
, aabb_size(aabb.max - aabb.min)
|
||||
, border(200,100)
|
||||
, canvas_size(canvas_size)
|
||||
, scale(std::min(double(canvas_size.X - border.X * 2) / aabb_size.X, double(canvas_size.Y - border.Y * 2) / aabb_size.Y))
|
||||
{
|
||||
out = fopen(filename, "w");
|
||||
@@ -77,7 +79,12 @@ public:
|
||||
*/
|
||||
Point transform(const Point& p)
|
||||
{
|
||||
return Point((p.X-aabb.min.X)*scale, (p.Y-aabb.min.Y)*scale) + border;
|
||||
return Point((p.X-aabb.min.X)*scale, canvas_size.X - border.X - (p.Y-aabb.min.Y)*scale) + border;
|
||||
}
|
||||
|
||||
void writeComment(std::string comment)
|
||||
{
|
||||
fprintf(out, "<!-- %s -->\n", comment.c_str());
|
||||
}
|
||||
|
||||
void writeAreas(const Polygons& polygons, Color color = Color::GRAY, Color outline_color = Color::BLACK)
|
||||
@@ -167,11 +174,11 @@ public:
|
||||
fprintf(out,"\" />\n"); //Write the end of the tag.
|
||||
}
|
||||
|
||||
void writeLine(const Point& a, const Point& b, Color color = Color::BLACK)
|
||||
void writeLine(const Point& a, const Point& b, Color color = Color::BLACK, int stroke_width = 1)
|
||||
{
|
||||
Point fa = transform(a);
|
||||
Point fb = transform(b);
|
||||
fprintf(out, "<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:%s;stroke-width:1\" />\n", fa.X, fa.Y, fb.X, fb.Y, toString(color).c_str());
|
||||
fprintf(out, "<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:%s;stroke-width:%i\" />\n", fa.X, fa.Y, fb.X, fb.Y, toString(color).c_str(), stroke_width);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
/** Copyright (C) 2016 Scott Lenser - Released under terms of the AGPLv3 License */
|
||||
|
||||
#ifndef UTILS_SPARSE_GRID_H
|
||||
#define UTILS_SPARSE_GRID_H
|
||||
|
||||
#include "intpoint.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace cura {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \note This is an abstract template class which doesn't have any functions to insert elements.
|
||||
* \see SparsePointGrid
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
*/
|
||||
template<class ElemT>
|
||||
class SparseGrid
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparseGrid(coord_t cell_size, size_t elem_reserve=0U, float max_load_factor=1.0f);
|
||||
|
||||
/*! \brief Returns all data within radius of query_pt.
|
||||
*
|
||||
* Finds all elements with location within radius of \p query_pt. May
|
||||
* return additional elements that are beyond radius.
|
||||
*
|
||||
* Average running time is a*(1 + 2 * radius / cell_size)**2 +
|
||||
* b*cnt where a and b are proportionality constance and cnt is
|
||||
* the number of returned items. The search will return items in
|
||||
* an area of (2*radius + cell_size)**2 on average. The max range
|
||||
* of an item from the query_point is radius + cell_size.
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \return Vector of elements found
|
||||
*/
|
||||
std::vector<Elem> getNearby(const Point &query_pt, coord_t radius) const;
|
||||
|
||||
static const std::function<bool(const Elem&)> no_precondition;
|
||||
|
||||
/*!
|
||||
* Find the nearest element to a given \p query_pt within \p radius.
|
||||
*
|
||||
* \param[in] query_pt The point for which to find the nearest object.
|
||||
* \param[in] radius The search radius.
|
||||
* \param[out] elem_nearest the nearest element. Only valid if function returns true.
|
||||
* \param[in] precondition A precondition which must return true for an element
|
||||
* to be considered for output
|
||||
* \return True if and only if an object has been found within the radius.
|
||||
*/
|
||||
bool getNearest(const Point &query_pt, coord_t radius, Elem &elem_nearest,
|
||||
const std::function<bool(const Elem& elem)> precondition = no_precondition) const;
|
||||
|
||||
/*! \brief Process elements from cells that might contain sought after points.
|
||||
*
|
||||
* Processes elements from cell that might have elements within \p
|
||||
* radius of \p query_pt. Processes all elements that are within
|
||||
* radius of query_pt. May process elements that are up to radius +
|
||||
* cell_size from query_pt.
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell.
|
||||
*/
|
||||
template<class ProcessFunc>
|
||||
void processNearby(const Point &query_pt, coord_t radius,
|
||||
ProcessFunc &process_func) const;
|
||||
|
||||
coord_t getCellSize() const;
|
||||
|
||||
protected:
|
||||
using GridPoint = Point;
|
||||
using grid_coord_t = coord_t;
|
||||
using GridMap = std::unordered_multimap<GridPoint, Elem>;
|
||||
|
||||
/*! \brief Process elements from the cell indicated by \p grid_pt.
|
||||
*
|
||||
* \param[in] grid_pt The grid coordinates of the cell.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell.
|
||||
*/
|
||||
template<class ProcessFunc>
|
||||
void processFromCell(const GridPoint &grid_pt,
|
||||
ProcessFunc &process_func) const;
|
||||
|
||||
/*! \brief Compute the grid coordinates of a point.
|
||||
*
|
||||
* \param[in] point The actual location.
|
||||
* \return The grid coordinates that correspond to \p point.
|
||||
*/
|
||||
GridPoint toGridPoint(const Point& point) const;
|
||||
|
||||
/*! \brief Compute the grid coordinate of a print space coordinate.
|
||||
*
|
||||
* \param[in] coord The actual location.
|
||||
* \return The grid coordinate that corresponds to \p coord.
|
||||
*/
|
||||
grid_coord_t toGridCoord(const coord_t& coord) const;
|
||||
|
||||
/*! \brief Compute the lowest point in a grid cell.
|
||||
* The lowest point is the point in the grid cell closest to the origin.
|
||||
*
|
||||
* \param[in] location The grid location.
|
||||
* \return The print space coordinates that correspond to \p location.
|
||||
*/
|
||||
Point toLowerCorner(const GridPoint& location) const;
|
||||
|
||||
/*! \brief Compute the lowest coord in a grid cell.
|
||||
* The lowest point is the point in the grid cell closest to the origin.
|
||||
*
|
||||
* \param[in] grid_coord The grid coordinate.
|
||||
* \return The print space coordinate that corresponds to \p grid_coord.
|
||||
*/
|
||||
coord_t toLowerCoord(const grid_coord_t& grid_coord) const;
|
||||
|
||||
/*! \brief Map from grid locations (GridPoint) to elements (Elem). */
|
||||
GridMap m_grid;
|
||||
/*! \brief The cell (square) size. */
|
||||
coord_t m_cell_size;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#define SGI_TEMPLATE template<class ElemT>
|
||||
#define SGI_THIS SparseGrid<ElemT>
|
||||
|
||||
SGI_TEMPLATE
|
||||
SGI_THIS::SparseGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor)
|
||||
{
|
||||
assert(cell_size > 0U);
|
||||
|
||||
m_cell_size = cell_size;
|
||||
|
||||
// Must be before the reserve call.
|
||||
m_grid.max_load_factor(max_load_factor);
|
||||
if (elem_reserve != 0U) {
|
||||
m_grid.reserve(elem_reserve);
|
||||
}
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
typename SGI_THIS::GridPoint SGI_THIS::toGridPoint(const Point &point) const
|
||||
{
|
||||
return Point(toGridCoord(point.X), toGridCoord(point.Y));
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
typename SGI_THIS::grid_coord_t SGI_THIS::toGridCoord(const coord_t& coord) const
|
||||
{
|
||||
// This mapping via truncation results in the cells with
|
||||
// GridPoint.x==0 being twice as large and similarly for
|
||||
// GridPoint.y==0. This doesn't cause any incorrect behavior,
|
||||
// just changes the running time slightly. The change in running
|
||||
// time from this is probably not worth doing a proper floor
|
||||
// operation.
|
||||
return coord / m_cell_size;
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
typename cura::Point SGI_THIS::toLowerCorner(const GridPoint& location) const
|
||||
{
|
||||
return cura::Point(toLowerCoord(location.X), toLowerCoord(location.Y));
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
typename cura::coord_t SGI_THIS::toLowerCoord(const grid_coord_t& grid_coord) const
|
||||
{
|
||||
// This mapping via truncation results in the cells with
|
||||
// GridPoint.x==0 being twice as large and similarly for
|
||||
// GridPoint.y==0. This doesn't cause any incorrect behavior,
|
||||
// just changes the running time slightly. The change in running
|
||||
// time from this is probably not worth doing a proper floor
|
||||
// operation.
|
||||
return grid_coord * m_cell_size;
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
template<class ProcessFunc>
|
||||
void SGI_THIS::processFromCell(
|
||||
const GridPoint &grid_pt,
|
||||
ProcessFunc &process_func) const
|
||||
{
|
||||
auto grid_range = m_grid.equal_range(grid_pt);
|
||||
for (auto iter = grid_range.first; iter != grid_range.second; ++iter)
|
||||
{
|
||||
process_func(iter->second);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
template<class ProcessFunc>
|
||||
void SGI_THIS::processNearby(const Point &query_pt, coord_t radius,
|
||||
ProcessFunc &process_func) const
|
||||
{
|
||||
Point min_loc(query_pt.X - radius, query_pt.Y - radius);
|
||||
Point max_loc(query_pt.X + radius, query_pt.Y + radius);
|
||||
|
||||
GridPoint min_grid = toGridPoint(min_loc);
|
||||
GridPoint max_grid = toGridPoint(max_loc);
|
||||
|
||||
for (coord_t grid_y = min_grid.Y; grid_y <= max_grid.Y; ++grid_y)
|
||||
{
|
||||
for (coord_t grid_x = min_grid.X; grid_x <= max_grid.X; ++grid_x)
|
||||
{
|
||||
GridPoint grid_pt(grid_x,grid_y);
|
||||
processFromCell(grid_pt, process_func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
std::vector<typename SGI_THIS::Elem>
|
||||
SGI_THIS::getNearby(const Point &query_pt, coord_t radius) const
|
||||
{
|
||||
std::vector<Elem> ret;
|
||||
auto process_func = [&ret](const Elem &elem)
|
||||
{
|
||||
ret.push_back(elem);
|
||||
};
|
||||
processNearby(query_pt, radius, process_func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
const std::function<bool(const typename SGI_THIS::Elem &)>
|
||||
SGI_THIS::no_precondition =
|
||||
[](const typename SGI_THIS::Elem &)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
SGI_TEMPLATE
|
||||
bool SGI_THIS::getNearest(
|
||||
const Point &query_pt, coord_t radius, Elem &elem_nearest,
|
||||
const std::function<bool(const Elem& elem)> precondition) const
|
||||
{
|
||||
bool found = false;
|
||||
int64_t best_dist2 = static_cast<int64_t>(radius) * radius;
|
||||
auto process_func =
|
||||
[&query_pt, &elem_nearest, &found, &best_dist2, &precondition](const Elem &elem)
|
||||
{
|
||||
if (!precondition(elem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int64_t dist2 = vSize2(elem.point - query_pt);
|
||||
if (dist2 < best_dist2)
|
||||
{
|
||||
found = true;
|
||||
elem_nearest = elem;
|
||||
best_dist2 = dist2;
|
||||
}
|
||||
};
|
||||
processNearby(query_pt, radius, process_func);
|
||||
return found;
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
coord_t SGI_THIS::getCellSize() const
|
||||
{
|
||||
return m_cell_size;
|
||||
}
|
||||
|
||||
#undef SGI_TEMPLATE
|
||||
#undef SGI_THIS
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // UTILS_SPARSE_GRID_H
|
||||
@@ -0,0 +1,230 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
|
||||
#ifndef UTILS_SPARSE_LINE_GRID_H
|
||||
#define UTILS_SPARSE_LINE_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "intpoint.h"
|
||||
#include "SparseGrid.h"
|
||||
#include "SVG.h" // debug
|
||||
|
||||
namespace cura {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
* \tparam Locator The functor to get the start and end locations from ElemT.
|
||||
* must have: std::pair<Point, Point> operator()(const ElemT &elem) const
|
||||
* which returns the location associated with val.
|
||||
*/
|
||||
template<class ElemT, class Locator>
|
||||
class SparseLineGrid : public SparseGrid<ElemT>
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparseLineGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f);
|
||||
|
||||
/*! \brief Inserts elem into the sparse grid.
|
||||
*
|
||||
* \param[in] elem The element to be inserted.
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
void debugHTML(std::string filename);
|
||||
|
||||
static void debugTest();
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
using grid_coord_t = typename SparseGrid<ElemT>::grid_coord_t;
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
grid_coord_t nonzero_sign(grid_coord_t z);
|
||||
};
|
||||
|
||||
|
||||
|
||||
#define SGI_TEMPLATE template<class ElemT, class Locator>
|
||||
#define SGI_THIS SparseLineGrid<ElemT, Locator>
|
||||
|
||||
SGI_TEMPLATE
|
||||
SGI_THIS::SparseLineGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor)
|
||||
: SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor)
|
||||
{
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
void SGI_THIS::insert(const Elem &elem)
|
||||
{
|
||||
const std::pair<Point, Point> line = m_locator(elem);
|
||||
Point start = line.first;
|
||||
Point end = line.second;
|
||||
if (end.X < start.X)
|
||||
{ // make sure X increases between start and end
|
||||
std::swap(start, end);
|
||||
}
|
||||
|
||||
const GridPoint start_cell = SparseGrid<ElemT>::toGridPoint(start);
|
||||
const GridPoint end_cell = SparseGrid<ElemT>::toGridPoint(end);
|
||||
const coord_t y_diff = end.Y - start.Y;
|
||||
const grid_coord_t y_dir = nonzero_sign(y_diff);
|
||||
|
||||
grid_coord_t x_cell_start = start_cell.X;
|
||||
for (grid_coord_t cell_y = start_cell.Y; cell_y * y_dir <= end_cell.Y * y_dir; cell_y += y_dir)
|
||||
{ // for all Y from start to end
|
||||
// nearest y coordinate of the cells in the next row
|
||||
coord_t nearest_next_y = SparseGrid<ElemT>::toLowerCoord(cell_y + ((nonzero_sign(cell_y) == y_dir || cell_y == 0) ? y_dir : coord_t(0)));
|
||||
grid_coord_t x_cell_end; // the X coord of the last cell to include from this row
|
||||
if (y_diff == 0)
|
||||
{
|
||||
x_cell_end = end_cell.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
coord_t area = (end.X - start.X) * (nearest_next_y - start.Y);
|
||||
// corresponding_x: the x coordinate corresponding to nearest_next_y
|
||||
coord_t corresponding_x = start.X + area / y_diff;
|
||||
x_cell_end = SparseGrid<ElemT>::toGridCoord(corresponding_x + ((corresponding_x < 0) && ((area % y_diff) != 0)));
|
||||
if (x_cell_end < start_cell.X)
|
||||
{ // process at least one cell!
|
||||
x_cell_end = x_cell_start;
|
||||
}
|
||||
}
|
||||
|
||||
for (grid_coord_t cell_x = x_cell_start; cell_x <= x_cell_end; ++cell_x)
|
||||
{
|
||||
GridPoint grid_loc(cell_x, cell_y);
|
||||
SparseGrid<ElemT>::m_grid.emplace(grid_loc, elem);
|
||||
if (grid_loc == end_cell)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO: this causes at least a one cell overlap for each row, which
|
||||
// includes extra cells when crossing precisely on the corners
|
||||
// where positive slope where x > 0 and negative slope where x < 0
|
||||
x_cell_start = x_cell_end;
|
||||
}
|
||||
assert(false && "We should have returned already before here!");
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
typename SGI_THIS::grid_coord_t SGI_THIS::nonzero_sign(grid_coord_t z)
|
||||
{
|
||||
return (z >= 0) - (z < 0);
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
void SGI_THIS::debugHTML(std::string filename)
|
||||
{
|
||||
AABB aabb;
|
||||
for (std::pair<GridPoint, ElemT> cell: SparseGrid<ElemT>::m_grid)
|
||||
{
|
||||
aabb.include(SparseGrid<ElemT>::toLowerCorner(cell.first));
|
||||
aabb.include(SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(nonzero_sign(cell.first.X), nonzero_sign(cell.first.Y))));
|
||||
}
|
||||
SVG svg(filename.c_str(), aabb);
|
||||
for (std::pair<GridPoint, ElemT> cell: SparseGrid<ElemT>::m_grid)
|
||||
{
|
||||
// doesn't draw cells at x = 0 or y = 0 correctly (should be double size)
|
||||
Point lb = SparseGrid<ElemT>::toLowerCorner(cell.first);
|
||||
Point lt = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(0, nonzero_sign(cell.first.Y)));
|
||||
Point rt = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(nonzero_sign(cell.first.X), nonzero_sign(cell.first.Y)));
|
||||
Point rb = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(nonzero_sign(cell.first.X), 0));
|
||||
if (lb.X == 0)
|
||||
{
|
||||
lb.X = -SparseGrid<ElemT>::m_cell_size;
|
||||
lt.X = -SparseGrid<ElemT>::m_cell_size;
|
||||
}
|
||||
if (lb.Y == 0)
|
||||
{
|
||||
lb.Y = -SparseGrid<ElemT>::m_cell_size;
|
||||
rb.Y = -SparseGrid<ElemT>::m_cell_size;
|
||||
}
|
||||
// svg.writePoint(lb, true, 1);
|
||||
svg.writeLine(lb, lt, SVG::Color::GRAY);
|
||||
svg.writeLine(lt, rt, SVG::Color::GRAY);
|
||||
svg.writeLine(rt, rb, SVG::Color::GRAY);
|
||||
svg.writeLine(rb, lb, SVG::Color::GRAY);
|
||||
|
||||
std::pair<Point, Point> line = m_locator(cell.second);
|
||||
svg.writePoint(line.first, true);
|
||||
svg.writePoint(line.second, true);
|
||||
svg.writeLine(line.first, line.second, SVG::Color::BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
void SGI_THIS::debugTest()
|
||||
{
|
||||
struct PairLocator
|
||||
{
|
||||
std::pair<Point, Point> operator()(const std::pair<Point, Point>& val) const
|
||||
{
|
||||
return val;
|
||||
}
|
||||
};
|
||||
SparseLineGrid<std::pair<Point, Point>, PairLocator> line_grid(10);
|
||||
|
||||
// straight lines
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(50, 0), Point(50, 70)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(0, 90), Point(50, 90)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(253, 103), Point(253, 173)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(203, 193), Point(253, 193)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-50, 0), Point(-50, -70)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(0, -90), Point(-50, -90)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-253, -103), Point(-253, -173)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-203, -193), Point(-253, -193)));
|
||||
|
||||
// diagonal lines
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(113, 133), Point(166, 125)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(13, 73), Point(26, 25)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(166, 33), Point(113, 25)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(26, 173), Point(13, 125)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-24, -18), Point(-19, -64)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-113, -133), Point(-166, -125)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-166, -33), Point(-113, -25)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-26, -173), Point(-13, -125)));
|
||||
|
||||
// diagonal lines exactly crossing cell corners
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(160, 190), Point(220, 170)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(60, 130), Point(80, 70)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(220, 90), Point(160, 70)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(80, 220), Point(60, 160)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-160, -190), Point(-220, -170)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-60, -130), Point(-80, -70)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-220, -90), Point(-160, -70)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-80, -220), Point(-60, -160)));
|
||||
|
||||
// single cell
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(203, 213), Point(203, 213)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(223, 213), Point(223, 215)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(243, 213), Point(245, 213)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(263, 213), Point(265, 215)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(283, 215), Point(285, 213)));
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(-203, -213), Point(-203, -213)));
|
||||
|
||||
// around origin
|
||||
line_grid.insert(std::make_pair<Point, Point>(Point(20, -20), Point(-20, 20)));
|
||||
|
||||
line_grid.debugHTML("line_grid.html");
|
||||
}
|
||||
|
||||
|
||||
#undef SGI_TEMPLATE
|
||||
#undef SGI_THIS
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // UTILS_SPARSE_LINE_GRID_H
|
||||
@@ -0,0 +1,76 @@
|
||||
/** Copyright (C) 2016 Scott Lenser - Released under terms of the AGPLv3 License */
|
||||
|
||||
#ifndef UTILS_SPARSE_POINT_GRID_H
|
||||
#define UTILS_SPARSE_POINT_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "intpoint.h"
|
||||
#include "SparseGrid.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
* \tparam Locator The functor to get the location from ElemT. Locator
|
||||
* must have: Point operator()(const ElemT &elem) const
|
||||
* which returns the location associated with val.
|
||||
*/
|
||||
template<class ElemT, class Locator>
|
||||
class SparsePointGrid : public SparseGrid<ElemT>
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparsePointGrid(coord_t cell_size, size_t elem_reserve=0U, float max_load_factor=1.0f);
|
||||
|
||||
/*! \brief Inserts elem into the sparse grid.
|
||||
*
|
||||
* \param[in] elem The element to be inserted.
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#define SGI_TEMPLATE template<class ElemT, class Locator>
|
||||
#define SGI_THIS SparsePointGrid<ElemT, Locator>
|
||||
|
||||
SGI_TEMPLATE
|
||||
SGI_THIS::SparsePointGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor)
|
||||
: SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor)
|
||||
{
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
void SGI_THIS::insert(const Elem &elem)
|
||||
{
|
||||
Point loc = m_locator(elem);
|
||||
GridPoint grid_loc = SparseGrid<ElemT>::toGridPoint(loc);
|
||||
|
||||
SparseGrid<ElemT>::m_grid.emplace(grid_loc,elem);
|
||||
}
|
||||
|
||||
|
||||
#undef SGI_TEMPLATE
|
||||
#undef SGI_THIS
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // UTILS_SPARSE_POINT_GRID_H
|
||||
@@ -0,0 +1,126 @@
|
||||
/** Copyright (C) 2016 Scott Lenser - Released under terms of the AGPLv3 License */
|
||||
|
||||
#ifndef UTILS_SPARSE_POINT_GRID_INCLUSIVE_H
|
||||
#define UTILS_SPARSE_POINT_GRID_INCLUSIVE_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "intpoint.h"
|
||||
#include "SparsePointGrid.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
namespace SparsePointGridInclusiveImpl {
|
||||
|
||||
template<class Val>
|
||||
struct SparsePointGridInclusiveElem
|
||||
{
|
||||
SparsePointGridInclusiveElem()
|
||||
{
|
||||
}
|
||||
|
||||
SparsePointGridInclusiveElem(const Point &point_, const Val &val_) :
|
||||
point(point_),
|
||||
val(val_)
|
||||
{
|
||||
}
|
||||
|
||||
Point point;
|
||||
Val val;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct Locatoror
|
||||
{
|
||||
Point operator()(const SparsePointGridInclusiveElem<T> &elem)
|
||||
{
|
||||
return elem.point;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace SparseGridImpl
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby values efficiently.
|
||||
*
|
||||
* \tparam Val The value type to store.
|
||||
*/
|
||||
template<class Val>
|
||||
class SparsePointGridInclusive : public SparsePointGrid<SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem<Val>,
|
||||
SparsePointGridInclusiveImpl::Locatoror<Val> >
|
||||
{
|
||||
public:
|
||||
using Base = SparsePointGrid<SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem<Val>,
|
||||
SparsePointGridInclusiveImpl::Locatoror<Val> >;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparsePointGridInclusive(coord_t cell_size, size_t elem_reserve=0U, float max_load_factor=1.0f);
|
||||
|
||||
/*! \brief Inserts an element with specified point and value into the sparse grid.
|
||||
*
|
||||
* This is a convenience wrapper over \ref SparsePointGrid::insert()
|
||||
*
|
||||
* \param[in] point The location for the element.
|
||||
* \param[in] val The value for the element.
|
||||
*/
|
||||
void insert(const Point &point, const Val &val);
|
||||
|
||||
/*! \brief Returns all values within radius of query_pt.
|
||||
*
|
||||
* Finds all values with location within radius of \p query_pt. May
|
||||
* return additional values that are beyond radius.
|
||||
*
|
||||
* See \ref getNearby().
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \return Vector of values found
|
||||
*/
|
||||
std::vector<Val> getNearbyVals(const Point &query_pt, coord_t radius) const;
|
||||
|
||||
};
|
||||
|
||||
#define SG_TEMPLATE template<class Val>
|
||||
#define SG_THIS SparsePointGridInclusive<Val>
|
||||
|
||||
SG_TEMPLATE
|
||||
SG_THIS::SparsePointGridInclusive(coord_t cell_size, size_t elem_reserve, float max_load_factor) :
|
||||
Base(cell_size,elem_reserve,max_load_factor)
|
||||
{
|
||||
}
|
||||
|
||||
SG_TEMPLATE
|
||||
void SG_THIS::insert(const Point &point, const Val &val)
|
||||
{
|
||||
typename SG_THIS::Elem elem(point,val);
|
||||
Base::insert(elem);
|
||||
}
|
||||
|
||||
SG_TEMPLATE
|
||||
std::vector<Val>
|
||||
SG_THIS::getNearbyVals(const Point &query_pt, coord_t radius) const
|
||||
{
|
||||
std::vector<Val> ret;
|
||||
auto process_func = [&ret](const typename SG_THIS::Elem &elem)
|
||||
{
|
||||
ret.push_back(elem.val);
|
||||
};
|
||||
this->processNearby(query_pt, radius, process_func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#undef SG_TEMPLATE
|
||||
#undef SG_THIS
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // UTILS_SPARSE_POINT_GRID_INCLUSIVE_H
|
||||
@@ -0,0 +1,87 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_SYMMETRIC_PAIR
|
||||
#define UTILS_SYMMETRIC_PAIR
|
||||
|
||||
#include <utility> // pair
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A utility class for a pair of which the order between the first and the second doesn't matter.
|
||||
*
|
||||
* \tparam A The type of both elements of the pair.
|
||||
*/
|
||||
template<class A>
|
||||
class SymmetricPair : public std::pair<A, A>
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Forwarding std::pair constructor
|
||||
*/
|
||||
template<class U>
|
||||
SymmetricPair(const SymmetricPair<U>& pr)
|
||||
: std::pair<A, A>(pr)
|
||||
{
|
||||
}
|
||||
/*!
|
||||
* Forwarding std::pair constructor
|
||||
*/
|
||||
template<class U>
|
||||
SymmetricPair(SymmetricPair<U>&& pr)
|
||||
: std::pair<A, A>(pr)
|
||||
{
|
||||
}
|
||||
/*!
|
||||
* Forwarding std::pair constructor
|
||||
*/
|
||||
SymmetricPair(const A& first, const A& second)
|
||||
: std::pair<A, A>(first, second)
|
||||
{
|
||||
}
|
||||
/*!
|
||||
* Forwarding std::pair constructor
|
||||
*/
|
||||
template<class U>
|
||||
SymmetricPair(U&& first, U&& second)
|
||||
: std::pair<A, A>(first, second)
|
||||
{
|
||||
}
|
||||
/*!
|
||||
* Forwarding std::pair constructor
|
||||
*/
|
||||
template <class... Args1, class... Args2>
|
||||
SymmetricPair(std::piecewise_construct_t pwc, std::tuple<Args1...> first_args, std::tuple<Args2...> second_args)
|
||||
: std::pair<A, A>(pwc, first_args, second_args)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Equality operator which checks if two SymmetricPairs are equal regardless of the order between first and second
|
||||
*/
|
||||
bool operator==(const SymmetricPair& other) const
|
||||
{
|
||||
return (std::pair<A, A>::first == other.first && std::pair<A, A>::second == other.second) || (std::pair<A, A>::first == other.second && std::pair<A, A>::second == other.first);
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
/*!
|
||||
* Hash operator which creates a hash regardless of the order between first and second
|
||||
*/
|
||||
template<class A>
|
||||
struct hash<cura::SymmetricPair<A>>
|
||||
{
|
||||
size_t operator()(const cura::SymmetricPair<A>& pr) const
|
||||
{ // has to be symmetric wrt a and b!
|
||||
return std::hash<A>()(pr.first) + std::hash<A>()(pr.second);
|
||||
}
|
||||
};
|
||||
}//namespace std
|
||||
|
||||
|
||||
#endif // UTILS_SYMMETRIC_PAIR
|
||||
@@ -22,10 +22,14 @@ static inline double getTime()
|
||||
return double(GetTickCount()) / 1000.0;
|
||||
#else // not __WIN32
|
||||
#if USE_CPU_TIME // Use cpu usage time if available, otherwise wall clock time
|
||||
int ret;
|
||||
struct rusage usage;
|
||||
ret = getrusage(RUSAGE_SELF,&usage);
|
||||
assert(ret==0);
|
||||
#ifdef DEBUG
|
||||
int ret = getrusage(RUSAGE_SELF, &usage);
|
||||
assert(ret == 0);
|
||||
((void)ret);
|
||||
#else
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
#endif
|
||||
double user_time = double(usage.ru_utime.tv_sec) + double(usage.ru_utime.tv_usec) / 1000000.0;
|
||||
double sys_time = double(usage.ru_stime.tv_sec) + double(usage.ru_stime.tv_usec) / 1000000.0;
|
||||
return user_time + sys_time;
|
||||
|
||||
@@ -136,6 +136,8 @@ inline Point3 operator*(const double d, const Point3& rhs) {
|
||||
return rhs * d;
|
||||
}
|
||||
|
||||
using coord_t = ClipperLib::cInt;
|
||||
|
||||
/* 64bit Points are used mostly troughout the code, these are the 2D points from ClipperLib */
|
||||
typedef ClipperLib::IntPoint Point;
|
||||
|
||||
@@ -153,13 +155,15 @@ static Point no_point(std::numeric_limits<int32_t>::infinity(), std::numeric_lim
|
||||
INLINE Point operator-(const Point& p0) { return Point(-p0.X, -p0.Y); }
|
||||
INLINE Point operator+(const Point& p0, const Point& p1) { return Point(p0.X+p1.X, p0.Y+p1.Y); }
|
||||
INLINE Point operator-(const Point& p0, const Point& p1) { return Point(p0.X-p1.X, p0.Y-p1.Y); }
|
||||
INLINE Point operator*(const Point& p0, const int32_t i) { return Point(p0.X*i, p0.Y*i); }
|
||||
INLINE Point operator*(const int32_t i, const Point& p0) { return p0 * i; }
|
||||
template<typename T> INLINE Point operator*(const Point& p0, const T i) { return Point(p0.X * i, p0.Y * i); }
|
||||
template<typename T> INLINE Point operator*(const T i, const Point& p0) { return p0 * i; }
|
||||
INLINE Point operator/(const Point& p0, const int32_t i) { return Point(p0.X/i, p0.Y/i); }
|
||||
INLINE Point operator/(const Point& p0, const Point& p1) { return Point(p0.X/p1.X, p0.Y/p1.Y); }
|
||||
|
||||
INLINE Point& operator += (Point& p0, const Point& p1) { p0.X += p1.X; p0.Y += p1.Y; return p0; }
|
||||
INLINE Point& operator -= (Point& p0, const Point& p1) { p0.X -= p1.X; p0.Y -= p1.Y; return p0; }
|
||||
template<typename T> INLINE Point& operator *= (Point& p0, const T i) { p0.X *= i; p0.Y *= i; return p0; }
|
||||
template<typename T> INLINE Point& operator /= (Point& p0, const T i) { p0.X /= i; p0.Y /= i; return p0; }
|
||||
|
||||
//INLINE bool operator==(const Point& p0, const Point& p1) { return p0.X==p1.X&&p0.Y==p1.Y; }
|
||||
//INLINE bool operator!=(const Point& p0, const Point& p1) { return p0.X!=p1.X||p0.Y!=p1.Y; }
|
||||
|
||||
@@ -63,8 +63,34 @@ public:
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* Find whether a point projected on a line segment would be projected to
|
||||
* - properly on the line : zero returned
|
||||
* - closer to \p a : -1 returned
|
||||
* - closer to \p b : 1 returned
|
||||
*
|
||||
* \param from The point to check in relation to the line segment
|
||||
* \param a The start point of the line segment
|
||||
* \param b The end point of the line segment
|
||||
* \return the sign of the projection wrt the line segment
|
||||
*/
|
||||
inline static short pointIsProjectedBeyondLine(const Point from, const Point a, const Point b)
|
||||
{
|
||||
const Point vec = b - a;
|
||||
const Point point_vec = from - a;
|
||||
const int64_t dot_prod = dot(point_vec, vec);
|
||||
if (dot_prod < 0)
|
||||
{ // point is projected to before ab
|
||||
return -1;
|
||||
}
|
||||
if (dot_prod > vSize2(vec))
|
||||
{ // point is projected to after ab
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Find the point closest to \p from on the line from \p p0 to \p p1
|
||||
*/
|
||||
@@ -209,9 +235,79 @@ public:
|
||||
* \return the angle in radians between 0 and 2 * pi of the corner in \p b
|
||||
*/
|
||||
static float getAngleLeft(const Point& a, const Point& b, const Point& c);
|
||||
|
||||
/*!
|
||||
* Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows.
|
||||
*
|
||||
* The returned value is zero for \p p lying (approximately) on the line going through \p a and \p b
|
||||
* The value is positive for values lying to the left and negative for values lying to the right when looking from \p a to \p b.
|
||||
*
|
||||
* \param p the point to check
|
||||
* \param a the from point of the line
|
||||
* \param b the to point of the line
|
||||
* \return a positive value when \p p lies to the left of the line from \p a to \p b
|
||||
*/
|
||||
static inline int64_t pointIsLeftOfLine(const Point& p, const Point& a, const Point& b)
|
||||
{
|
||||
return (b.X - a.X) * (p.Y - a.Y) - (b.Y - a.Y) * (p.X - a.X);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get a point on the line segment (\p a - \p b)with a given distance to point \p p
|
||||
*
|
||||
* In case there are two possible point that meet the criteria, choose the one closest to a.
|
||||
*
|
||||
* \param p The reference point
|
||||
* \param a Start of the line segment
|
||||
* \param b End of the line segment
|
||||
* \param dist The required distance of \p result to \p p
|
||||
* \param[out] result The result (if any was found)
|
||||
* \return Whether any such point has been found
|
||||
*/
|
||||
static bool getPointOnLineWithDist(const Point p, const Point a, const Point b, int64_t dist, Point& result);
|
||||
|
||||
/*!
|
||||
* Get the squared distance from a point \p p to the line on which \p a and \p b lie
|
||||
*/
|
||||
static inline int64_t getDist2FromLine(const Point p, const Point a, const Point b)
|
||||
{
|
||||
// x.......a------------b
|
||||
// :
|
||||
// :
|
||||
// p
|
||||
// return px_size
|
||||
Point vab = b - a;
|
||||
Point vap = p - a;
|
||||
int64_t dott = dot(vab, vap);
|
||||
int64_t ax_size2 = dott * dott / vSize2(vab);
|
||||
int64_t ap_size2 = vSize2(vap);
|
||||
int64_t px_size2 = std::max(int64_t(0), ap_size2 - ax_size2);
|
||||
return px_size2;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Check whether a corner is acute or obtuse.
|
||||
*
|
||||
* This function is irrespective of the order between \p a and \p c;
|
||||
* the lowest angle among bot hsides of the corner is always chosen.
|
||||
*
|
||||
* isAcuteCorner(a, b, c) === isAcuteCorner(c, b, a)
|
||||
*
|
||||
* \param a start of first line segment
|
||||
* \param b end of first segment and start of second line segment
|
||||
* \param c end of second line segment
|
||||
* \return positive if acute, negative if obtuse, zero if 90 degree corner
|
||||
*/
|
||||
static inline int isAcuteCorner(const Point a, const Point b, const Point c)
|
||||
{
|
||||
Point ba = a - b;
|
||||
Point bc = c - b;
|
||||
return dot(ba, bc);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
#endif//UTILS_LINEAR_ALG_2D_H
|
||||
#endif//UTILS_LINEAR_ALG_2D_H
|
||||
|
||||
@@ -23,6 +23,7 @@ void logError(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
fprintf(stderr, "[ERROR] ");
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
fflush(stderr);
|
||||
@@ -32,12 +33,13 @@ void logWarning(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
fprintf(stderr, "[WARNING] ");
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
void logCopyright(const char* fmt, ...)
|
||||
void logAlways(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
@@ -57,6 +59,21 @@ void log(const char* fmt, ...)
|
||||
va_end(args);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
void logDebug(const char* fmt, ...)
|
||||
{
|
||||
if (verbose_level < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
fprintf(stderr, "[DEBUG] ");
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
void logProgress(const char* type, int value, int maxValue, float percent)
|
||||
{
|
||||
if (!progressLogging)
|
||||
|
||||
@@ -14,7 +14,10 @@ void logWarning(const char* fmt, ...);
|
||||
//Report a message if the verbose level is 1 or higher. (defined as _log to prevent clash with log() function from <math.h>)
|
||||
void log(const char* fmt, ...);
|
||||
//Report an copyright message (always reported, independed of verbose level)
|
||||
void logCopyright(const char* fmt, ...);
|
||||
void logAlways(const char* fmt, ...);
|
||||
|
||||
//Report a message if the verbose level is 2 or higher. (defined as _log to prevent clash with log() function from <math.h>)
|
||||
void logDebug(const char* fmt, ...);
|
||||
|
||||
//Report engine progress to interface if any. Only if "enableProgressLogging()" has been called.
|
||||
void logProgress(const char* type, int value, int maxValue, float percent);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_MATH_H
|
||||
#define UTILS_MATH_H
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
template<typename T> inline T square(const T& a) { return a * a; }
|
||||
|
||||
inline unsigned int round_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer
|
||||
{
|
||||
return (dividend + divisor / 2) / divisor;
|
||||
}
|
||||
inline unsigned int round_up_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer
|
||||
{
|
||||
return (dividend + divisor - 1) / divisor;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
#endif // UTILS_MATH_H
|
||||
|
||||
+4
-11
@@ -114,11 +114,11 @@ public:
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
constexpr T* operator->()
|
||||
constexpr T* operator->() const
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
constexpr T& operator*() &
|
||||
constexpr T& operator*() const&
|
||||
{
|
||||
return *instance;
|
||||
}
|
||||
@@ -126,21 +126,14 @@ public:
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
constexpr T& value() &
|
||||
constexpr T& value() const&
|
||||
{
|
||||
return *instance;
|
||||
}
|
||||
template<class U>
|
||||
constexpr T value_or(U&& default_value) const&
|
||||
{
|
||||
if (instance)
|
||||
{
|
||||
return *instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
return instance ? *instance : default_value;
|
||||
}
|
||||
void swap(optional& other)
|
||||
{
|
||||
|
||||
+850
-87
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+161
-72
@@ -9,6 +9,7 @@
|
||||
#include <algorithm> // std::reverse, fill_n array
|
||||
#include <cmath> // fabs
|
||||
#include <limits> // int64_t.min
|
||||
#include <list>
|
||||
|
||||
#include "intpoint.h"
|
||||
|
||||
@@ -25,6 +26,11 @@ namespace cura {
|
||||
class PartsView;
|
||||
class Polygons;
|
||||
|
||||
class ListPolyIt;
|
||||
|
||||
typedef std::list<Point> ListPolygon; //!< A polygon represented by a linked list instead of a vector
|
||||
typedef std::vector<ListPolygon> ListPolygons; //!< Polygons represented by a vector of linked lists instead of a vector of vectors
|
||||
|
||||
const static int clipper_init = (0);
|
||||
#define NO_INDEX (std::numeric_limits<unsigned int>::max())
|
||||
|
||||
@@ -64,7 +70,7 @@ public:
|
||||
{
|
||||
path->push_back(p);
|
||||
}
|
||||
|
||||
|
||||
PolygonRef& operator=(const PolygonRef& other) { path = other.path; return *this; }
|
||||
|
||||
bool operator==(const PolygonRef& other) const =delete;
|
||||
@@ -216,7 +222,7 @@ public:
|
||||
* \param border_result What to return when the point is exactly on the border
|
||||
* \return Whether the point \p p is inside this polygon (or \p border_result when it is on the border)
|
||||
*/
|
||||
bool _inside(Point p, bool border_result = false);
|
||||
bool _inside(Point p, bool border_result = false) const;
|
||||
|
||||
/*!
|
||||
* Clipper function.
|
||||
@@ -224,7 +230,7 @@ public:
|
||||
*
|
||||
* http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/PointInPolygon.htm
|
||||
*/
|
||||
bool inside(Point p, bool border_result = false)
|
||||
bool inside(Point p, bool border_result = false) const
|
||||
{
|
||||
int res = ClipperLib::PointInPolygon(p, *path);
|
||||
if (res == -1)
|
||||
@@ -234,6 +240,28 @@ public:
|
||||
return res == 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Smooth out small perpendicular segments and store the result in \p result.
|
||||
* Smoothing is performed by removing the inner most vertex of a line segment smaller than \p remove_length
|
||||
* which has an angle with the next and previous line segment smaller than roughly 150*
|
||||
*
|
||||
* Note that in its current implementation this function doesn't remove line segments with an angle smaller than 30*
|
||||
* Such would be the case for an N shape.
|
||||
*
|
||||
* \param remove_length The length of the largest segment removed
|
||||
* \param result (output) The result polygon, assumed to be empty
|
||||
*/
|
||||
void smooth(int remove_length, PolygonRef result);
|
||||
|
||||
/*!
|
||||
* Smooth out sharp inner corners, by taking a shortcut which bypasses the corner
|
||||
*
|
||||
* \param angle The maximum angle of inner corners to be smoothed out
|
||||
* \param shortcut_length The desired length of the shortcut line segment introduced (shorter shortcuts may be unavoidable)
|
||||
* \param result The resulting polygon
|
||||
*/
|
||||
void smooth_outward(float angle, int shortcut_length, PolygonRef result) const;
|
||||
|
||||
/*!
|
||||
* Smooth out the polygon and store the result in \p result.
|
||||
* Smoothing is performed by removing vertices for which both connected line segments are smaller than \p remove_length
|
||||
@@ -241,36 +269,13 @@ public:
|
||||
* \param remove_length The length of the largest segment removed
|
||||
* \param result (output) The result polygon, assumed to be empty
|
||||
*/
|
||||
void smooth(int remove_length, PolygonRef result)
|
||||
{
|
||||
PolygonRef& thiss = *this;
|
||||
ClipperLib::Path* poly = result.path;
|
||||
if (size() > 0)
|
||||
{
|
||||
poly->push_back(thiss[0]);
|
||||
}
|
||||
for (unsigned int poly_idx = 1; poly_idx < size(); poly_idx++)
|
||||
{
|
||||
Point& last = thiss[poly_idx - 1];
|
||||
Point& now = thiss[poly_idx];
|
||||
Point& next = thiss[(poly_idx + 1) % size()];
|
||||
if (shorterThen(last - now, remove_length) && shorterThen(now - next, remove_length))
|
||||
{
|
||||
poly_idx++; // skip the next line piece (dont escalate the removal of edges)
|
||||
if (poly_idx < size())
|
||||
{
|
||||
poly->push_back(thiss[poly_idx]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
poly->push_back(thiss[poly_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
void smooth2(int remove_length, PolygonRef result);
|
||||
|
||||
/*!
|
||||
* removes consecutive line segments with same orientation and changes this polygon
|
||||
* Removes consecutive line segments with same orientation and changes this polygon.
|
||||
*
|
||||
* Removes verts which are connected to line segments which are both too small.
|
||||
* Removes verts which detour from a direct line from the previous and next vert by a too small amount.
|
||||
*
|
||||
* \param smallest_line_segment_squared maximal squared length of removed line segments
|
||||
* \param allowed_error_distance_squared The square of the distance of the middle point to the line segment of the consecutive and previous point for which the middle point is removed
|
||||
@@ -309,6 +314,63 @@ public:
|
||||
|
||||
friend class Polygons;
|
||||
friend class Polygon;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Smooth out a simple corner consisting of two linesegments.
|
||||
*
|
||||
* Auxiliary function for \ref smooth_outward
|
||||
*
|
||||
* \param poly The polygon in which to find the corner
|
||||
* \param p0 The point before the corner
|
||||
* \param p1 The corner
|
||||
* \param p2 The point after the corner
|
||||
* \param p0_it Iterator to the point before the corner
|
||||
* \param p1_it Iterator to the corner
|
||||
* \param p2_it Iterator to the point after the corner
|
||||
* \param v10 Vector from \p p1 to \p p0
|
||||
* \param v12 Vector from \p p1 to \p p2
|
||||
* \param v02 Vector from \p p0 to \p p2
|
||||
* \param shortcut_length The desired length ofthe shortcutting line
|
||||
* \param cos_angle The cosine on the angle in L 012
|
||||
*/
|
||||
static void smooth_corner_simple(ListPolygon& poly, const Point p0, const Point p1, const Point p2, const ListPolyIt p0_it, const ListPolyIt p1_it, const ListPolyIt p2_it, const Point v10, const Point v12, const Point v02, const int64_t shortcut_length, float cos_angle);
|
||||
|
||||
/*!
|
||||
* Smooth out a complex corner where the shortcut bypasses more than two line segments
|
||||
*
|
||||
* Auxiliary function for \ref smooth_outward
|
||||
*
|
||||
* \warning This function might try to remove the whole polygon
|
||||
* Error code -1 means the whole polygon should be removed (which means it is a hole polygon)
|
||||
*
|
||||
*
|
||||
* \param poly The polygon in which to find the corner
|
||||
* \param p1 The corner point
|
||||
* \param[in,out] p0_it Iterator to the last point checked before \p p1 to consider cutting off
|
||||
* \param[in,out] p2_it Iterator to the last point checked after \p p1 to consider cutting off
|
||||
* \param shortcut_length The desired length ofthe shortcutting line
|
||||
* \return Whether this whole polygon whould be removed by the smoothing
|
||||
*/
|
||||
static bool smooth_corner_complex(ListPolygon& poly, const Point p1, ListPolyIt& p0_it, ListPolyIt& p2_it, const int64_t shortcut_length);
|
||||
|
||||
/*!
|
||||
* Try to take a step away from the corner point in order to take a bigger shortcut.
|
||||
*
|
||||
* Try to take the shortcut from a place as far away from the corner as the place we are taking the shortcut to.
|
||||
*
|
||||
* Auxiliary function for \ref smooth_outward
|
||||
*
|
||||
* \param[in] p1 The corner point
|
||||
* \param[in] shortcut_length2 The square of the desired length ofthe shortcutting line
|
||||
* \param[in,out] p0_it Iterator to the previously checked point somewhere beyond \p p1. Updated for the next iteration.
|
||||
* \param[in,out] p2_it Iterator to the previously checked point somewhere before \p p1. Updated for the next iteration.
|
||||
* \param[in,out] forward_is_blocked Whether trying another step forward is blocked by the smoothing outward condition. Updated for the next iteration.
|
||||
* \param[in,out] backward_is_blocked Whether trying another step backward is blocked by the smoothing outward condition. Updated for the next iteration.
|
||||
* \param[in,out] forward_is_too_far Whether trying another step forward is blocked by the shortcut length condition. Updated for the next iteration.
|
||||
* \param[in,out] backward_is_too_far Whether trying another step backward is blocked by the shortcut length condition. Updated for the next iteration.
|
||||
*/
|
||||
static void smooth_outward_step(const Point p1, const int64_t shortcut_length2, ListPolyIt& p0_it, ListPolyIt& p2_it, bool& forward_is_blocked, bool& backward_is_blocked, bool& forward_is_too_far, bool& backward_is_too_far);
|
||||
};
|
||||
|
||||
class Polygon : public PolygonRef
|
||||
@@ -341,6 +403,8 @@ public:
|
||||
return paths.size();
|
||||
}
|
||||
|
||||
unsigned int pointCount() const; //!< Return the amount of points in all polygons
|
||||
|
||||
PolygonRef operator[] (unsigned int index)
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
@@ -383,11 +447,22 @@ public:
|
||||
{
|
||||
paths.push_back(*poly.path);
|
||||
}
|
||||
void add(Polygon&& other_poly)
|
||||
{
|
||||
paths.emplace_back(std::move(*other_poly));
|
||||
}
|
||||
void add(const Polygons& other)
|
||||
{
|
||||
for(unsigned int n=0; n<other.paths.size(); n++)
|
||||
paths.push_back(other.paths[n]);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void emplace_back(Args... args)
|
||||
{
|
||||
paths.emplace_back(args...);
|
||||
}
|
||||
|
||||
PolygonRef newPoly()
|
||||
{
|
||||
paths.emplace_back();
|
||||
@@ -448,16 +523,9 @@ public:
|
||||
clipper.Execute(ClipperLib::ctXor, ret.paths);
|
||||
return ret;
|
||||
}
|
||||
Polygons offset(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miter_limit = 1.2) const
|
||||
{
|
||||
Polygons ret;
|
||||
ClipperLib::ClipperOffset clipper(miter_limit, 10.0);
|
||||
clipper.AddPaths(paths, joinType, ClipperLib::etClosedPolygon);
|
||||
clipper.MiterLimit = miter_limit;
|
||||
clipper.Execute(ret.paths, distance);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Polygons offset(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miter_limit = 1.2) const;
|
||||
|
||||
Polygons offsetPolyLine(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter) const
|
||||
{
|
||||
Polygons ret;
|
||||
@@ -508,12 +576,7 @@ public:
|
||||
* \return the convex hull (approximately)
|
||||
*
|
||||
*/
|
||||
Polygons approxConvexHull(int extra_outset = 0)
|
||||
{
|
||||
int overshoot = 100000; // 10 cm (hardcoded value)
|
||||
|
||||
return offset(overshoot, ClipperLib::jtRound).offset(-overshoot+extra_outset, ClipperLib::jtRound);
|
||||
}
|
||||
Polygons approxConvexHull(int extra_outset = 0);
|
||||
|
||||
/*!
|
||||
* Convex hull of all the points in the polygons.
|
||||
@@ -522,35 +585,35 @@ public:
|
||||
*/
|
||||
Polygon convexHull() const;
|
||||
|
||||
Polygons smooth(int remove_length, int min_area) //!< removes points connected to small lines
|
||||
{
|
||||
Polygons ret;
|
||||
for (unsigned int p = 0; p < size(); p++)
|
||||
{
|
||||
PolygonRef poly(paths[p]);
|
||||
if (poly.area() < min_area || poly.size() <= 5) // when optimally removing, a poly with 5 pieces results in a triangle. Smaller polys dont have area!
|
||||
{
|
||||
ret.add(poly);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (poly.size() == 0)
|
||||
continue;
|
||||
if (poly.size() < 4)
|
||||
ret.add(poly);
|
||||
else
|
||||
poly.smooth(remove_length, ret.newPoly());
|
||||
|
||||
/*!
|
||||
* Smooth out small perpendicular segments
|
||||
* Smoothing is performed by removing the inner most vertex of a line segment smaller than \p remove_length
|
||||
* which has an angle with the next and previous line segment smaller than roughly 150*
|
||||
*
|
||||
* Note that in its current implementation this function doesn't remove line segments with an angle smaller than 30*
|
||||
* Such would be the case for an N shape.
|
||||
*
|
||||
* \param remove_length The length of the largest segment removed
|
||||
* \return The smoothed polygon
|
||||
*/
|
||||
Polygons smooth(int remove_length);
|
||||
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/*!
|
||||
* Smooth out sharp inner corners, by taking a shortcut which bypasses the corner
|
||||
*
|
||||
* \param angle The maximum angle of inner corners to be smoothed out
|
||||
* \param shortcut_length The desired length of the shortcut line segment introduced (shorter shortcuts may be unavoidable)
|
||||
* \return The resulting polygons
|
||||
*/
|
||||
Polygons smooth_outward(float angle, int shortcut_length);
|
||||
|
||||
Polygons smooth2(int remove_length, int min_area); //!< removes points connected to small lines
|
||||
|
||||
/*!
|
||||
* removes points connected to similarly oriented lines
|
||||
*
|
||||
* \param smallest_line_segment_squared maximal squared length of removed line segments
|
||||
* \param allowed_error_distance_squared The square of the distance of the middle point to the line segment of the consecutive and previous point for which the middle point is removed
|
||||
* \param smallest_line_segment maximal length of removed line segments
|
||||
* \param allowed_error_distance The distance of the middle point to the line segment of the consecutive and previous point for which the middle point is removed
|
||||
*/
|
||||
void simplify(int smallest_line_segment = 10, int allowed_error_distance = 5)
|
||||
{
|
||||
@@ -567,13 +630,39 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Remove all but the polygons on the very outside.
|
||||
* Exclude holes and parts within holes.
|
||||
* \return the resulting polygons.
|
||||
*/
|
||||
Polygons getOutsidePolygons() const;
|
||||
|
||||
/*!
|
||||
* Exclude holes which have no parts inside of them.
|
||||
* \return the resulting polygons.
|
||||
*/
|
||||
Polygons removeEmptyHoles() const;
|
||||
|
||||
/*!
|
||||
* Return hole polygons which have no parts inside of them.
|
||||
* \return the resulting polygons.
|
||||
*/
|
||||
Polygons getEmptyHoles() const;
|
||||
|
||||
/*!
|
||||
* Split up the polygons into groups according to the even-odd rule.
|
||||
* Each PolygonsPart in the result has an outline as first polygon, whereas the rest are holes.
|
||||
*/
|
||||
std::vector<PolygonsPart> splitIntoParts(bool unionAll = false) const;
|
||||
private:
|
||||
/*!
|
||||
* recursive part of \ref Polygons::removeEmptyHoles and \ref Polygons::getEmptyHoles
|
||||
* \param node The node of the polygons part to process
|
||||
* \param remove_holes Whether to remove empty holes or everything but the empty holes
|
||||
* \param ret Where to store polygons which are not empty holes
|
||||
*/
|
||||
void removeEmptyHoles_processPolyTreeNode(const ClipperLib::PolyNode& node, const bool remove_holes, Polygons& ret) const;
|
||||
void splitIntoParts_processPolyTreeNode(ClipperLib::PolyNode* node, std::vector<PolygonsPart>& ret) const;
|
||||
public:
|
||||
/*!
|
||||
|
||||
+154
-33
@@ -2,10 +2,10 @@
|
||||
#include "polygonUtils.h"
|
||||
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
|
||||
#include "linearAlg2D.h"
|
||||
#include "BucketGrid2D.h"
|
||||
#include "../debug.h"
|
||||
#include "SparsePointGridInclusive.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include "AABB.h"
|
||||
@@ -17,7 +17,70 @@ namespace cura
|
||||
|
||||
const std::function<int(Point)> PolygonUtils::no_penalty_function = [](Point){ return 0; };
|
||||
|
||||
Point PolygonUtils::getBoundaryPointWithOffset(PolygonRef poly, unsigned int point_idx, int64_t offset)
|
||||
int64_t PolygonUtils::segmentLength(PolygonsPointIndex start, PolygonsPointIndex end)
|
||||
{
|
||||
assert(start.poly_idx == end.poly_idx);
|
||||
int64_t segment_length = 0;
|
||||
Point prev_vert = start.p();
|
||||
const PolygonRef poly = (*start.polygons)[start.poly_idx];
|
||||
for (unsigned int point_idx = 1; point_idx <= poly.size(); point_idx++)
|
||||
{
|
||||
unsigned int vert_idx = (start.point_idx + point_idx) % poly.size();
|
||||
Point vert = poly[vert_idx];
|
||||
segment_length += vSize(vert - prev_vert);
|
||||
|
||||
if (vert_idx == end.point_idx)
|
||||
{ // break at the end of the loop, so that [end] and [start] may be the same
|
||||
return segment_length;
|
||||
}
|
||||
prev_vert = vert;
|
||||
}
|
||||
assert(false && "The segment end should have been encountered!");
|
||||
return segment_length;
|
||||
}
|
||||
|
||||
void PolygonUtils::spreadDots(PolygonsPointIndex start, PolygonsPointIndex end, unsigned int n_dots, std::vector<ClosestPolygonPoint>& result)
|
||||
{
|
||||
assert(start.poly_idx == end.poly_idx);
|
||||
int64_t segment_length = segmentLength(start, end);
|
||||
|
||||
const PolygonRef poly = (*start.polygons)[start.poly_idx];
|
||||
unsigned int n_dots_in_between = n_dots;
|
||||
if (start == end)
|
||||
{
|
||||
result.emplace_back(start.p(), start.point_idx, poly);
|
||||
n_dots_in_between--; // generate one less below, because we already pushed a point to the result
|
||||
}
|
||||
|
||||
int64_t wipe_point_dist = segment_length / (n_dots_in_between + 1); // distance between two wipe points; keep a distance at both sides of the segment
|
||||
|
||||
int64_t dist_past_vert_to_insert_point = wipe_point_dist;
|
||||
unsigned int n_points_generated = 0;
|
||||
PolygonsPointIndex vert = start;
|
||||
while (true)
|
||||
{
|
||||
Point p0 = vert.p();
|
||||
Point p1 = vert.next().p();
|
||||
Point p0p1 = p1 - p0;
|
||||
int64_t p0p1_length = vSize(p0p1);
|
||||
|
||||
for ( ; dist_past_vert_to_insert_point < p0p1_length && n_points_generated < n_dots_in_between; dist_past_vert_to_insert_point += wipe_point_dist)
|
||||
{
|
||||
result.emplace_back(p0 + normal(p0p1, dist_past_vert_to_insert_point), vert.point_idx, poly);
|
||||
n_points_generated++;
|
||||
}
|
||||
dist_past_vert_to_insert_point -= p0p1_length;
|
||||
|
||||
++vert;
|
||||
if (vert == end)
|
||||
{ // break at end of loop to allow for [start] and [end] being the same, meaning the full polygon
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(result.size() == n_dots && "we didn't generate as many wipe locations as we asked for.");
|
||||
}
|
||||
|
||||
Point PolygonUtils::getVertexInwardNormal(PolygonRef poly, unsigned int point_idx)
|
||||
{
|
||||
Point p1 = poly[point_idx];
|
||||
|
||||
@@ -51,11 +114,31 @@ Point PolygonUtils::getBoundaryPointWithOffset(PolygonRef poly, unsigned int poi
|
||||
|
||||
Point off0 = turn90CCW(normal(p1 - p0, MM2INT(10.0))); // 10.0 for some precision
|
||||
Point off1 = turn90CCW(normal(p2 - p1, MM2INT(10.0))); // 10.0 for some precision
|
||||
Point n = normal(off0 + off1, -offset);
|
||||
|
||||
return p1 + n;
|
||||
Point n = off0 + off1;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
Point PolygonUtils::getBoundaryPointWithOffset(PolygonRef poly, unsigned int point_idx, int64_t offset)
|
||||
{
|
||||
return poly[point_idx] + normal(getVertexInwardNormal(poly, point_idx), -offset);
|
||||
}
|
||||
|
||||
Point PolygonUtils::moveInsideDiagonally(ClosestPolygonPoint point_on_boundary, int64_t inset)
|
||||
{
|
||||
Point p0 = point_on_boundary.poly[point_on_boundary.point_idx];
|
||||
Point p1 = point_on_boundary.poly[(point_on_boundary.point_idx + 1) % point_on_boundary.poly.size()];
|
||||
if (vSize2(p0 - point_on_boundary.location) < vSize2(p1 - point_on_boundary.location))
|
||||
{
|
||||
return point_on_boundary.location + normal(getVertexInwardNormal(point_on_boundary.poly, point_on_boundary.point_idx), inset);
|
||||
}
|
||||
else
|
||||
{
|
||||
return point_on_boundary.location + normal(getVertexInwardNormal(point_on_boundary.poly, (point_on_boundary.point_idx + 1) % point_on_boundary.poly.size()), inset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned int PolygonUtils::moveOutside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||
{
|
||||
return moveInside(polygons, from, -distance, maxDist2);
|
||||
@@ -320,15 +403,21 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons
|
||||
* Clipper seems to fuck up sometimes.
|
||||
*/
|
||||
#ifdef DEBUG
|
||||
try
|
||||
{
|
||||
int offset_performed = offset / 2;
|
||||
AABB aabb(insetted);
|
||||
aabb.expand(std::abs(preferred_dist_inside) * 2);
|
||||
SVG svg("debug.html", aabb);
|
||||
svg.writeComment("Original polygon in black");
|
||||
svg.writePolygon(closest_poly, SVG::Color::BLACK);
|
||||
for (auto point : closest_poly)
|
||||
{
|
||||
svg.writePoint(point, true, 2);
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << "Offsetted polygon in blue with offset " << offset_performed;
|
||||
svg.writeComment(ss.str());
|
||||
svg.writePolygons(insetted, SVG::Color::BLUE);
|
||||
for (auto poly : insetted)
|
||||
{
|
||||
@@ -337,10 +426,15 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons
|
||||
svg.writePoint(point, true, 2);
|
||||
}
|
||||
}
|
||||
svg.writeComment("From location");
|
||||
svg.writePoint(from, false, 5, SVG::Color::GREEN);
|
||||
svg.writeComment("Location computed to be inside the black polygon");
|
||||
svg.writePoint(inside.location, false, 5, SVG::Color::RED);
|
||||
}
|
||||
logError("ERROR! ERROR!\n\tClipper::offset failed. See generated debug.html!\n\tBlack is original\n\tBlue is offsetted polygon\n");
|
||||
catch(...)
|
||||
{
|
||||
}
|
||||
logError("Clipper::offset failed. See generated debug.html!\n\tBlack is original\n\tBlue is offsetted polygon\n");
|
||||
#endif
|
||||
return ClosestPolygonPoint(polygons[0]);
|
||||
}
|
||||
@@ -527,7 +621,44 @@ ClosestPolygonPoint PolygonUtils::findClosest(Point from, const PolygonRef polyg
|
||||
return ClosestPolygonPoint(best, bestPos, polygon);
|
||||
}
|
||||
|
||||
BucketGrid2D<PolygonsPointIndex>* PolygonUtils::createLocToLineGrid(const Polygons& polygons, int square_size)
|
||||
PolygonsPointIndex PolygonUtils::findNearestVert(const Point from, const Polygons& polys)
|
||||
{
|
||||
int64_t best_dist2 = std::numeric_limits<int64_t>::max();
|
||||
PolygonsPointIndex closest_vert;
|
||||
for (unsigned int poly_idx = 0; poly_idx < polys.size(); poly_idx++)
|
||||
{
|
||||
const PolygonRef poly = polys[poly_idx];
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
int64_t dist2 = vSize2(poly[point_idx] - from);
|
||||
if (dist2 < best_dist2)
|
||||
{
|
||||
best_dist2 = dist2;
|
||||
closest_vert = PolygonsPointIndex(&polys, poly_idx, point_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return closest_vert;
|
||||
}
|
||||
|
||||
unsigned int PolygonUtils::findNearestVert(const Point from, const PolygonRef poly)
|
||||
{
|
||||
int64_t best_dist2 = std::numeric_limits<int64_t>::max();
|
||||
unsigned int closest_vert_idx = -1;
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
int64_t dist2 = vSize2(poly[point_idx] - from);
|
||||
if (dist2 < best_dist2)
|
||||
{
|
||||
best_dist2 = dist2;
|
||||
closest_vert_idx = point_idx;
|
||||
}
|
||||
}
|
||||
return closest_vert_idx;
|
||||
}
|
||||
|
||||
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* PolygonUtils::createLocToLineGrid(const Polygons& polygons, int square_size)
|
||||
{
|
||||
unsigned int n_points = 0;
|
||||
for (const auto& poly : polygons)
|
||||
@@ -535,52 +666,39 @@ BucketGrid2D<PolygonsPointIndex>* PolygonUtils::createLocToLineGrid(const Polygo
|
||||
n_points += poly.size();
|
||||
}
|
||||
|
||||
BucketGrid2D<PolygonsPointIndex>* ret = new BucketGrid2D<PolygonsPointIndex>(square_size, n_points);
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* ret = new SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>(square_size, n_points);
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
const PolygonRef poly = polygons[poly_idx];
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
Point& p1 = poly[point_idx];
|
||||
Point& p2 = poly[(point_idx + 1) % poly.size()];
|
||||
|
||||
ret->insert(p1, PolygonsPointIndex(poly_idx, point_idx));
|
||||
Point vec = p2 - p1;
|
||||
int64_t vec_length = vSize(vec);
|
||||
for (int64_t dist_along_line = square_size; dist_along_line < vec_length; dist_along_line += square_size)
|
||||
{
|
||||
Point point_along_line = p1 + vec * dist_along_line / vec_length;
|
||||
|
||||
ret->insert(point_along_line, PolygonsPointIndex(poly_idx, point_idx));
|
||||
}
|
||||
ret->insert(PolygonsPointIndex(&polygons, poly_idx, point_idx));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The current implemetnation can check the same line segment multiple times,
|
||||
* since the same line segment can occur in multiple cells if it it longer than the cell size of the BucketGrid.
|
||||
* since the same line segment can occur in multiple cells if it it longer than the cell size of the SparsePointGridInclusive.
|
||||
*
|
||||
* We could skip the duplication by keeping a vector of vectors of bools.
|
||||
*
|
||||
*/
|
||||
std::optional<ClosestPolygonPoint> PolygonUtils::findClose(Point from, const Polygons& polygons, const BucketGrid2D<PolygonsPointIndex>& loc_to_line, const std::function<int(Point)>& penalty_function)
|
||||
std::optional<ClosestPolygonPoint> PolygonUtils::findClose(
|
||||
Point from, const Polygons& polygons,
|
||||
const SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& loc_to_line,
|
||||
const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
std::vector<PolygonsPointIndex> near_lines;
|
||||
loc_to_line.findNearbyObjects(from, near_lines);
|
||||
std::vector<PolygonsPointIndex> near_lines =
|
||||
loc_to_line.getNearby(from, loc_to_line.getCellSize());
|
||||
|
||||
Point best(0, 0);
|
||||
|
||||
int64_t closest_dist2_score = std::numeric_limits<int64_t>::max();
|
||||
PolygonsPointIndex best_point_poly_idx(NO_INDEX, NO_INDEX);
|
||||
PolygonsPointIndex best_point_poly_idx(nullptr, NO_INDEX, NO_INDEX);
|
||||
for (PolygonsPointIndex& point_poly_index : near_lines)
|
||||
{
|
||||
const PolygonRef poly = polygons[point_poly_index.poly_idx];
|
||||
@@ -608,7 +726,10 @@ std::optional<ClosestPolygonPoint> PolygonUtils::findClose(Point from, const Pol
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> PolygonUtils::findClose(const PolygonRef from, const Polygons& destination, const BucketGrid2D< PolygonsPointIndex >& destination_loc_to_line, const std::function<int(Point)>& penalty_function)
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> PolygonUtils::findClose(
|
||||
const PolygonRef from, const Polygons& destination,
|
||||
const SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& destination_loc_to_line,
|
||||
const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> ret;
|
||||
int p0_idx = from.size() - 1;
|
||||
|
||||
+90
-22
@@ -2,11 +2,14 @@
|
||||
#ifndef UTILS_POLYGON_UTILS_H
|
||||
#define UTILS_POLYGON_UTILS_H
|
||||
|
||||
#include <vector>
|
||||
#include <functional> // function
|
||||
|
||||
#include "polygon.h"
|
||||
#include "BucketGrid2D.h"
|
||||
#include "SparsePointGridInclusive.h"
|
||||
#include "SparseLineGrid.h"
|
||||
#include "optional.h"
|
||||
#include "PolygonsPointIndex.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -23,6 +26,10 @@ struct ClosestPolygonPoint
|
||||
ClosestPolygonPoint(Point p, int pos, PolygonRef poly) : location(p), poly(poly), poly_idx(NO_INDEX), point_idx(pos) {};
|
||||
ClosestPolygonPoint(Point p, int pos, PolygonRef poly, int poly_idx) : location(p), poly(poly), poly_idx(poly_idx), point_idx(pos) {};
|
||||
ClosestPolygonPoint(PolygonRef poly) : poly(poly), poly_idx(NO_INDEX), point_idx(NO_INDEX) {};
|
||||
Point p() const
|
||||
{ // conformity with other classes
|
||||
return location;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -34,19 +41,18 @@ struct GivenDistPoint
|
||||
int pos; //!< Index to the first point in the polygon of the line segment on which the result was found
|
||||
};
|
||||
|
||||
struct PolygonsPointIndex
|
||||
/*!
|
||||
* Locator to extract a line segment out of a \ref PolygonsPointIndex
|
||||
*/
|
||||
struct PolygonsPointIndexSegmentLocator
|
||||
{
|
||||
unsigned int poly_idx;
|
||||
unsigned int point_idx;
|
||||
PolygonsPointIndex()
|
||||
: poly_idx(0)
|
||||
, point_idx(0)
|
||||
{
|
||||
}
|
||||
PolygonsPointIndex(unsigned int poly_idx, unsigned int point_idx)
|
||||
: poly_idx(poly_idx)
|
||||
, point_idx(point_idx)
|
||||
std::pair<Point, Point> operator()(const PolygonsPointIndex& val) const
|
||||
{
|
||||
PolygonRef poly = (*val.polygons)[val.poly_idx];
|
||||
Point start = poly[val.point_idx];
|
||||
unsigned int next_point_idx = (val.point_idx + 1) % poly.size();
|
||||
Point end = poly[next_point_idx];
|
||||
return std::pair<Point, Point>(start, end);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,6 +61,44 @@ class PolygonUtils
|
||||
public:
|
||||
static const std::function<int(Point)> no_penalty_function; //!< Function always returning zero
|
||||
|
||||
/*!
|
||||
* compute the length of a segment of a polygon
|
||||
*
|
||||
* if \p end == \p start then the full polygon is taken
|
||||
*
|
||||
* \warning assumes that start and end lie on the same polygon!
|
||||
*
|
||||
* \param start The start vertex of the segment
|
||||
* \param end the end vertex of the segment
|
||||
* \return the total length of all the line segments in between the two vertices.
|
||||
*/
|
||||
static int64_t segmentLength(PolygonsPointIndex start, PolygonsPointIndex end);
|
||||
|
||||
/*!
|
||||
* Generate evenly spread out dots along a segment of a polygon
|
||||
*
|
||||
* Start at a distance from \p start and end at a distance from \p end,
|
||||
* unless \p end == \p start; then that point is in the result
|
||||
*
|
||||
* \warning Assumes that start and end lie on the same polygon!
|
||||
*
|
||||
* \param start The start vertex of the segment
|
||||
* \param end the end vertex of the segment
|
||||
* \param n_dots number of dots to spread out
|
||||
* \param result Where to store the generated points
|
||||
*/
|
||||
static void spreadDots(PolygonsPointIndex start, PolygonsPointIndex end, unsigned int n_dots, std::vector<ClosestPolygonPoint>& result);
|
||||
|
||||
/*!
|
||||
* Get the normal of a boundary point, pointing outward.
|
||||
* Only the direction is set.
|
||||
* Nothing is said about the length of the vector returned.
|
||||
*
|
||||
* \param poly The polygon.
|
||||
* \param point_idx The index of the point in the polygon.
|
||||
*/
|
||||
static Point getVertexInwardNormal(PolygonRef poly, unsigned int point_idx);
|
||||
|
||||
/*!
|
||||
* Get a point from the \p poly with a given \p offset.
|
||||
*
|
||||
@@ -65,6 +109,14 @@ public:
|
||||
*/
|
||||
static Point getBoundaryPointWithOffset(PolygonRef poly, unsigned int point_idx, int64_t offset);
|
||||
|
||||
/*!
|
||||
* Move a point away from the boundary by looking at the boundary normal of the nearest vert.
|
||||
*
|
||||
* \param point_on_boundary The object holding the point on the boundary along with the information of which line segment the point is on.
|
||||
* \param offset The distance the point has to be moved inward from the polygon.
|
||||
*/
|
||||
static Point moveInsideDiagonally(ClosestPolygonPoint point_on_boundary, int64_t inset);
|
||||
|
||||
/*!
|
||||
* Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance.
|
||||
* Given a \p distance more than zero, the point will end up inside, and conversely outside.
|
||||
@@ -227,7 +279,23 @@ public:
|
||||
static ClosestPolygonPoint findClosest(Point from, const PolygonRef polygon, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* Create a BucketGrid mapping from locations to line segments occurring in the \p polygons
|
||||
* Find the nearest vertex to \p from in \p polys
|
||||
* \param from the point from where to look
|
||||
* \param polys The polygons in which to search
|
||||
* \return The nearest vertex on the polygons
|
||||
*/
|
||||
static PolygonsPointIndex findNearestVert(const Point from, const Polygons& polys);
|
||||
|
||||
/*!
|
||||
* Find the nearest vertex to \p from in \p poly
|
||||
* \param from the point from where to look
|
||||
* \param poly The polygon in which to search
|
||||
* \return The index to the nearest vertex on the polygon
|
||||
*/
|
||||
static unsigned int findNearestVert(const Point from, const PolygonRef poly);
|
||||
|
||||
/*!
|
||||
* Create a SparsePointGridInclusive mapping from locations to line segments occurring in the \p polygons
|
||||
*
|
||||
* \warning The caller of this function is responsible for deleting the returned object
|
||||
*
|
||||
@@ -235,35 +303,35 @@ public:
|
||||
* \param square_size The cell size used to bundle line segments (also used to chop up lines so that multiple cells contain the same long line)
|
||||
* \return A bucket grid mapping spatial locations to poly-point indices into \p polygons
|
||||
*/
|
||||
static BucketGrid2D<PolygonsPointIndex>* createLocToLineGrid(const Polygons& polygons, int square_size);
|
||||
static SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* createLocToLineGrid(const Polygons& polygons, int square_size);
|
||||
|
||||
/*!
|
||||
* Find the line segment closest to a given point \p from within a cell-block of a size defined in the BucketGrid \p loc_to_line
|
||||
* Find the line segment closest to a given point \p from within a cell-block of a size defined in the SparsePointGridInclusive \p loc_to_line
|
||||
*
|
||||
* \note The penalty term is applied to the *squared* distance score.
|
||||
* Note also that almost only nearby points are considered even when the penalty function would favour points farther away.
|
||||
*
|
||||
* \param from The location to find a polygon edge close to
|
||||
* \param polygons The polygons for which the \p loc_to_line has been built up
|
||||
* \param loc_to_line A BucketGrid mapping locations to starting vertices of line segmetns of the \p polygons
|
||||
* \param loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segmetns of the \p polygons
|
||||
* \param penalty_function A function returning a penalty term on the squared distance score of a candidate point.
|
||||
* \return The nearest point on the polygon if the polygon was within a distance equal to the cell_size of the BucketGrid
|
||||
* \return The nearest point on the polygon if the polygon was within a distance equal to the cell_size of the SparsePointGridInclusive
|
||||
*/
|
||||
static std::optional<ClosestPolygonPoint> findClose(Point from, const Polygons& polygons, const BucketGrid2D<PolygonsPointIndex>& loc_to_line, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
static std::optional<ClosestPolygonPoint> findClose(Point from, const Polygons& polygons, const SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& loc_to_line, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* Find the line segment closest to any point on \p from within cell-blocks of a size defined in the BucketGrid \p destination_loc_to_line
|
||||
* Find the line segment closest to any point on \p from within cell-blocks of a size defined in the SparsePointGridInclusive \p destination_loc_to_line
|
||||
*
|
||||
* \note The penalty term is applied to the *squared* distance score.
|
||||
* Note also that almost only nearby points are considered even when the penalty function would favour points farther away.
|
||||
*
|
||||
* \param from The polygon for which to find a polygon edge close to
|
||||
* \param destination The polygons for which the \p destination_loc_to_line has been built up
|
||||
* \param destination_loc_to_line A BucketGrid mapping locations to starting vertices of line segments of the \p destination
|
||||
* \param destination_loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segments of the \p destination
|
||||
* \param penalty_function A function returning a penalty term on the squared distance score of a candidate point.
|
||||
* \return A collection of near crossing from the \p from polygon to the \p destination polygon. Each element in the sollection is a pair with as first a cpp in the \p from polygon and as second a cpp in the \p destination polygon.
|
||||
*/
|
||||
static std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> findClose(const PolygonRef from, const Polygons& destination, const BucketGrid2D< PolygonsPointIndex >& destination_loc_to_line, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
static std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> findClose(const PolygonRef from, const Polygons& destination, const SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& destination_loc_to_line, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* Find the next point (going along the direction of the polygon) with a distance \p dist from the point \p from within the \p poly.
|
||||
|
||||
+131
-3
@@ -1,7 +1,9 @@
|
||||
#ifndef STRING_H
|
||||
#define STRING_H
|
||||
#ifndef UTILS_STRING_H
|
||||
#define UTILS_STRING_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <cstdio> // sprintf
|
||||
#include <sstream> // ostringstream
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -19,5 +21,131 @@ static inline int stringcasecompare(const char* a, const char* b)
|
||||
return *a - *b;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Efficient conversion of micron integer type to millimeter string.
|
||||
*
|
||||
* \param coord The micron unit to convert
|
||||
* \param ss The output stream to write the string to
|
||||
*/
|
||||
static inline void writeInt2mm(const int64_t coord, std::ostream& ss)
|
||||
{
|
||||
char buffer[24];
|
||||
int char_count = sprintf(buffer, "%ld", coord); // convert int to string
|
||||
int end_pos = char_count; // the first character not to write any more
|
||||
int trailing_zeros = 1;
|
||||
while (trailing_zeros < 4 && buffer[char_count - trailing_zeros] == '0')
|
||||
{
|
||||
trailing_zeros++;
|
||||
}
|
||||
trailing_zeros--;
|
||||
end_pos = char_count - trailing_zeros;
|
||||
if (trailing_zeros == 3)
|
||||
{ // no need to write the decimal dot
|
||||
buffer[char_count - trailing_zeros] = '\0';
|
||||
ss << buffer;
|
||||
return;
|
||||
}
|
||||
if (char_count <= 3)
|
||||
{
|
||||
int start = 0; // where to start writing from the buffer
|
||||
if (coord < 0)
|
||||
{
|
||||
ss << '-';
|
||||
start = 1;
|
||||
}
|
||||
ss << '.';
|
||||
for (int nulls = char_count - start; nulls < 3; nulls++)
|
||||
{ // fill up to 3 decimals with zeros
|
||||
ss << '0';
|
||||
}
|
||||
buffer[char_count - trailing_zeros] = '\0';
|
||||
ss << (static_cast<char*>(buffer) + start);
|
||||
}
|
||||
else
|
||||
{
|
||||
char prev = '.';
|
||||
int pos;
|
||||
for (pos = char_count - 3; pos <= end_pos; pos++)
|
||||
{ // shift all characters and insert the decimal dot
|
||||
char next_prev = buffer[pos];
|
||||
buffer[pos] = prev;
|
||||
prev = next_prev;
|
||||
}
|
||||
buffer[pos] = '\0';
|
||||
ss << buffer;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Struct to make it possible to inline calls to writeInt2mm with writing other stuff to the output stream
|
||||
*/
|
||||
struct MMtoStream
|
||||
{
|
||||
int64_t value; //!< The coord in micron
|
||||
|
||||
friend inline std::ostream& operator<< (std::ostream& out, const MMtoStream precision_and_input)
|
||||
{
|
||||
writeInt2mm(precision_and_input.value, out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Efficient writing of a double to a stringstream
|
||||
*
|
||||
* writes with \p precision digits after the decimal dot, but removes trailing zeros
|
||||
*
|
||||
* \warning only works with precision up to 9 and input up to 10^14
|
||||
*
|
||||
* \param precision The number of (non-zero) digits after the decimal dot
|
||||
* \param coord double to output
|
||||
* \param ss The output stream to write the string to
|
||||
*/
|
||||
static inline void writeDoubleToStream(const unsigned int precision, const double coord, std::ostream& ss)
|
||||
{
|
||||
char format[5] = "%.xf"; // write a float with [x] digits after the dot
|
||||
format[2] = '0' + precision; // set [x]
|
||||
char buffer[24];
|
||||
int char_count = snprintf(buffer, 24, format, coord);
|
||||
if (char_count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (buffer[char_count - precision - 1] == '.')
|
||||
{
|
||||
int non_nul_pos = char_count - 1;
|
||||
while (buffer[non_nul_pos] == '0')
|
||||
{
|
||||
non_nul_pos--;
|
||||
}
|
||||
if (buffer[non_nul_pos] == '.')
|
||||
{
|
||||
buffer[non_nul_pos] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[non_nul_pos + 1] = '\0';
|
||||
}
|
||||
}
|
||||
ss << buffer;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Struct to make it possible to inline calls to writeDoubleToStream with writing other stuff to the output stream
|
||||
*/
|
||||
struct PrecisionedDouble
|
||||
{
|
||||
unsigned int precision; //!< Number of digits after the decimal mark with which to convert to string
|
||||
double value; //!< The double value
|
||||
|
||||
friend inline std::ostream& operator<< (std::ostream& out, const PrecisionedDouble precision_and_input)
|
||||
{
|
||||
writeDoubleToStream(precision_and_input.precision, precision_and_input.value, out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
#endif//STRING_H
|
||||
|
||||
#endif//UTILS_STRING_H
|
||||
|
||||
+195
-405
@@ -1,437 +1,227 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "wallOverlap.h"
|
||||
|
||||
#include <cmath> // isfinite
|
||||
#include <sstream>
|
||||
|
||||
#include "debug.h"
|
||||
#include "utils/AABB.h" // for debug output svg html
|
||||
#include "utils/SVG.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
WallOverlapComputation::WallOverlapComputation(Polygons& polygons, int lineWidth)
|
||||
: polygons(polygons)
|
||||
, line_width(lineWidth)
|
||||
WallOverlapComputation::WallOverlapComputation(Polygons& polygons, int line_width)
|
||||
: overlap_linker(polygons, line_width)
|
||||
, line_width(line_width)
|
||||
{
|
||||
unsigned int n_points = 0;
|
||||
for (PolygonRef poly : polygons)
|
||||
{
|
||||
n_points += poly.size();
|
||||
}
|
||||
|
||||
// reserve enough elements so that iterators don't get invalidated
|
||||
overlap_point_links.reserve(n_points * 2); // generally enough, unless there are a lot of 3-way intersections in the model
|
||||
overlap_point_links_endings.reserve(n_points * 2); // any point can at most introduce two endings
|
||||
|
||||
// convert to list polygons for insertion of points
|
||||
convertPolygonsToLists(polygons, list_polygons);
|
||||
|
||||
findOverlapPoints();
|
||||
addOverlapEndings();
|
||||
// TODO: add sharp corners
|
||||
|
||||
// convert list polygons back
|
||||
convertListPolygonsToPolygons(list_polygons, polygons);
|
||||
// wallOverlaps2HTML("output/output.html");
|
||||
// list_polygons.clear(); // clear up some space! (unneccesary? it's just for the time the gcode is being generated...)
|
||||
}
|
||||
|
||||
|
||||
void WallOverlapComputation::findOverlapPoints()
|
||||
{
|
||||
for (unsigned int poly_idx = 0; poly_idx < list_polygons.size(); poly_idx++)
|
||||
{
|
||||
ListPolygon& poly = list_polygons[poly_idx];
|
||||
for (unsigned int poly2_idx = 0; poly2_idx <= poly_idx; poly2_idx++)
|
||||
{
|
||||
for (ListPolygon::iterator it = poly.begin(); it != poly.end(); ++it)
|
||||
{
|
||||
ListPolyIt lpi(poly, it);
|
||||
if (poly_idx == poly2_idx)
|
||||
{
|
||||
// ListPolygon::iterator it2(it);
|
||||
// ++it2;
|
||||
// if (it2 != poly.end())
|
||||
{
|
||||
findOverlapPoints(lpi, poly2_idx, it);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
findOverlapPoints(lpi, poly2_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void WallOverlapComputation::convertPolygonsToLists(Polygons& polys, ListPolygons& result)
|
||||
{
|
||||
for (PolygonRef poly : polys)
|
||||
{
|
||||
result.emplace_back();
|
||||
for (Point& p : poly)
|
||||
{
|
||||
result.back().push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallOverlapComputation::convertListPolygonsToPolygons(ListPolygons& list_polygons, Polygons& polygons)
|
||||
{
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
polygons[poly_idx].clear();
|
||||
for (Point& p : list_polygons[poly_idx])
|
||||
{
|
||||
polygons[poly_idx].add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallOverlapComputation::findOverlapPoints(ListPolyIt from, unsigned int to_list_poly_idx)
|
||||
{
|
||||
findOverlapPoints(from, to_list_poly_idx, list_polygons[to_list_poly_idx].begin());
|
||||
}
|
||||
|
||||
void WallOverlapComputation::findOverlapPoints(ListPolyIt from_it, unsigned int to_list_poly_idx, const ListPolygon::iterator start)
|
||||
{
|
||||
ListPolygon& to_list_poly = list_polygons[to_list_poly_idx];
|
||||
Point& from = from_it.p();
|
||||
ListPolygon::iterator last_it = to_list_poly.end();
|
||||
last_it--;
|
||||
for (ListPolygon::iterator it = start; it != to_list_poly.end(); ++it)
|
||||
{
|
||||
Point& last_point = *last_it;
|
||||
Point& point = *it;
|
||||
|
||||
if (&from_it.poly == &to_list_poly
|
||||
&& (
|
||||
(from_it.it == last_it || from_it.it == it) // we currently consider a linesegment directly connected to [from]
|
||||
|| (from_it.prev().it == it || from_it.next().it == last_it) // line segment from [last_point] to [point] is connected to line segment of which [from] is the other end
|
||||
)
|
||||
)
|
||||
{
|
||||
last_it = it;
|
||||
continue;
|
||||
}
|
||||
Point closest = LinearAlg2D::getClosestOnLineSegment(from, last_point, point);
|
||||
|
||||
int64_t dist2 = vSize2(closest - from);
|
||||
|
||||
if (dist2 > line_width * line_width
|
||||
|| (&from_it.poly == &to_list_poly
|
||||
&& dot(from_it.next().p() - from, point - last_point) > 0
|
||||
&& dot(from - from_it.prev().p(), point - last_point) > 0 ) // line segments are likely connected, because the winding order is in the same general direction
|
||||
)
|
||||
{ // line segment too far away to have overlap
|
||||
last_it = it;
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t dist = sqrt(dist2);
|
||||
|
||||
if (shorterThen(closest - last_point, 10))
|
||||
{
|
||||
addOverlapPoint(from_it, ListPolyIt(to_list_poly, last_it), dist);
|
||||
}
|
||||
else if (shorterThen(closest - point, 10))
|
||||
{
|
||||
addOverlapPoint(from_it, ListPolyIt(to_list_poly, it), dist);
|
||||
}
|
||||
else
|
||||
{
|
||||
ListPolygon::iterator new_it = to_list_poly.insert(it, closest);
|
||||
addOverlapPoint(from_it, ListPolyIt(to_list_poly, new_it), dist);
|
||||
}
|
||||
|
||||
last_it = it;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool WallOverlapComputation::addOverlapPoint(ListPolyIt from, ListPolyIt to, int64_t dist)
|
||||
{
|
||||
WallOverlapPointLink link(from, to);
|
||||
WallOverlapPointLinkAttributes attr(dist, false);
|
||||
std::pair<WallOverlapPointLinks::iterator, bool> result =
|
||||
overlap_point_links.emplace(link, attr);
|
||||
|
||||
if (! result.second)
|
||||
{ // we already have the link
|
||||
// DEBUG_PRINTLN("couldn't emplace in overlap_point_links! : ");
|
||||
result.first->second = attr;
|
||||
}
|
||||
|
||||
WallOverlapPointLinks::iterator it = result.first;
|
||||
addToPoint2LinkMap(*it->first.a.it, it);
|
||||
addToPoint2LinkMap(*it->first.b.it, it);
|
||||
|
||||
|
||||
return result.second;
|
||||
}
|
||||
|
||||
bool WallOverlapComputation::addOverlapPoint_endings(ListPolyIt from, ListPolyIt to, int64_t dist)
|
||||
{
|
||||
WallOverlapPointLink link(from, to);
|
||||
WallOverlapPointLinkAttributes attr(dist, false);
|
||||
std::pair<WallOverlapPointLinks::iterator, bool> result =
|
||||
overlap_point_links_endings.emplace(link, attr);
|
||||
|
||||
if (! result.second)
|
||||
{
|
||||
// DEBUG_PRINTLN("couldn't emplace in overlap_point_links! : ");
|
||||
result.first->second = attr;
|
||||
}
|
||||
|
||||
WallOverlapPointLinks::iterator it = result.first;
|
||||
addToPoint2LinkMap(*it->first.a.it, it);
|
||||
addToPoint2LinkMap(*it->first.b.it, it);
|
||||
|
||||
|
||||
return result.second;
|
||||
}
|
||||
|
||||
void WallOverlapComputation::addOverlapEndings()
|
||||
{
|
||||
for (std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> link_pair : overlap_point_links)
|
||||
{
|
||||
|
||||
if (link_pair.second.dist == line_width)
|
||||
{ // its ending itself
|
||||
continue;
|
||||
}
|
||||
WallOverlapPointLink& link = link_pair.first;
|
||||
const ListPolyIt& a_1 = link.a;
|
||||
const ListPolyIt& b_1 = link.b;
|
||||
// an overlap segment can be an ending in two directions
|
||||
{
|
||||
ListPolyIt a_2 = a_1.next();
|
||||
ListPolyIt b_2 = b_1.prev();
|
||||
addOverlapEnding(link_pair, a_2, b_2, a_2, b_1);
|
||||
}
|
||||
{
|
||||
ListPolyIt a_2 = a_1.prev();
|
||||
ListPolyIt b_2 = b_1.next();
|
||||
addOverlapEnding(link_pair, a_2, b_2, a_1, b_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallOverlapComputation::addOverlapEnding(std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> link_pair, const ListPolyIt& a2_it, const ListPolyIt& b2_it, const ListPolyIt& a_after_middle, const ListPolyIt& b_after_middle)
|
||||
{
|
||||
WallOverlapPointLink& link = link_pair.first;
|
||||
Point& a1 = link.a.p();
|
||||
Point& a2 = a2_it.p();
|
||||
Point& b1 = link.b.p();
|
||||
Point& b2 = b2_it.p();
|
||||
Point a = a2-a1;
|
||||
Point b = b2-b1;
|
||||
|
||||
if (point_to_link.find(a2_it.p()) == point_to_link.end()
|
||||
|| point_to_link.find(b2_it.p()) == point_to_link.end())
|
||||
{
|
||||
int64_t dist = overlapEndingDistance(a1, a2, b1, b2, link_pair.second.dist);
|
||||
if (dist < 0) { return; }
|
||||
int64_t a_length2 = vSize2(a);
|
||||
int64_t b_length2 = vSize2(b);
|
||||
if (dist*dist > std::min(a_length2, b_length2) )
|
||||
{ // TODO remove this /\ case if error below is never shown
|
||||
// DEBUG_PRINTLN("Next point should have been linked already!!");
|
||||
dist = std::sqrt(std::min(a_length2, b_length2));
|
||||
if (a_length2 < b_length2)
|
||||
{
|
||||
Point b_p = b1 + normal(b, dist);
|
||||
ListPolygon::iterator new_b = link.b.poly.insert(b_after_middle.it, b_p);
|
||||
addOverlapPoint_endings(a2_it, ListPolyIt(link.b.poly, new_b), line_width);
|
||||
}
|
||||
else if (b_length2 < a_length2)
|
||||
{
|
||||
Point a_p = a1 + normal(a, dist);
|
||||
ListPolygon::iterator new_a = link.a.poly.insert(a_after_middle.it, a_p);
|
||||
addOverlapPoint_endings(ListPolyIt(link.a.poly, new_a), b2_it, line_width);
|
||||
}
|
||||
else // equal
|
||||
{
|
||||
addOverlapPoint_endings(a2_it, b2_it, line_width);
|
||||
}
|
||||
}
|
||||
if (dist > 0)
|
||||
{
|
||||
Point a_p = a1 + normal(a, dist);
|
||||
ListPolygon::iterator new_a = link.a.poly.insert(a_after_middle.it, a_p);
|
||||
Point b_p = b1 + normal(b, dist);
|
||||
ListPolygon::iterator new_b = link.b.poly.insert(b_after_middle.it, b_p);
|
||||
addOverlapPoint_endings(ListPolyIt(link.a.poly, new_a), ListPolyIt(link.b.poly, new_b), line_width);
|
||||
}
|
||||
else if (dist == 0)
|
||||
{
|
||||
addOverlapPoint_endings(link.a, link.b, line_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t WallOverlapComputation::overlapEndingDistance(Point& a1, Point& a2, Point& b1, Point& b2, int a1b1_dist)
|
||||
{
|
||||
int overlap = line_width - a1b1_dist;
|
||||
Point a = a2-a1;
|
||||
Point b = b2-b1;
|
||||
double cos_angle = INT2MM2(dot(a, b)) / vSizeMM(a) / vSizeMM(b);
|
||||
// result == .5*overlap / tan(.5*angle) == .5*overlap / tan(.5*acos(cos_angle))
|
||||
// [wolfram alpha] == 0.5*overlap * sqrt(cos_angle+1)/sqrt(1-cos_angle)
|
||||
// [assuming positive x] == 0.5*overlap / sqrt( 2 / (cos_angle + 1) - 1 )
|
||||
if (cos_angle <= 0
|
||||
|| ! std::isfinite(cos_angle) )
|
||||
{
|
||||
return -1; // line_width / 2;
|
||||
}
|
||||
else if (cos_angle > .9999) // values near 1 can lead too large numbers for 1/x
|
||||
{
|
||||
return std::min(vSize(b), vSize(a));
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t dist = overlap * double ( 1.0 / (2.0 * sqrt(2.0 / (cos_angle+1.0) - 1.0)) );
|
||||
return dist;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void WallOverlapComputation::addSharpCorners()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void WallOverlapComputation::addToPoint2LinkMap(Point p, WallOverlapPointLinks::iterator it)
|
||||
{
|
||||
point_to_link.emplace(p, it);
|
||||
// TODO: what to do if the map already contained a link? > three-way overlap
|
||||
}
|
||||
|
||||
float WallOverlapComputation::getFlow(Point& from, Point& to)
|
||||
{
|
||||
Point2Link::iterator from_link_pair = point_to_link.find(from);
|
||||
if (from_link_pair == point_to_link.end()) { return 1; }
|
||||
WallOverlapPointLinks::iterator from_link = from_link_pair->second;
|
||||
WallOverlapPointLinkAttributes& from_attr = from_link->second;
|
||||
using Point2LinkIt = PolygonProximityLinker::Point2Link::iterator;
|
||||
|
||||
Point2Link::iterator to_link_pair = point_to_link.find(to);
|
||||
if (to_link_pair == point_to_link.end()) { return 1; }
|
||||
WallOverlapPointLinks::iterator to_link = to_link_pair->second;
|
||||
WallOverlapPointLinkAttributes& to_attr = to_link->second;
|
||||
|
||||
if (!from_attr.passed || !to_attr.passed)
|
||||
{
|
||||
from_attr.passed = true;
|
||||
to_attr.passed = true;
|
||||
if (!overlap_linker.isLinked(from))
|
||||
{ // [from] is not linked
|
||||
return 1;
|
||||
}
|
||||
const std::pair<Point2LinkIt, Point2LinkIt> to_links = overlap_linker.getLinks(to);
|
||||
if (to_links.first == to_links.second)
|
||||
{ // [to] is not linked
|
||||
return 1;
|
||||
}
|
||||
from_attr.passed = true;
|
||||
to_attr.passed = true;
|
||||
|
||||
// both points have already been passed
|
||||
|
||||
float avg_link_dist = 0.5 * ( INT2MM(from_link->second.dist) + INT2MM(to_link->second.dist) );
|
||||
|
||||
float ratio = avg_link_dist / INT2MM(line_width);
|
||||
|
||||
if (ratio > 1.0) { return 1.0; }
|
||||
|
||||
return ratio;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void WallOverlapComputation::debugCheck()
|
||||
{
|
||||
for (std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> pair : overlap_point_links)
|
||||
int64_t overlap_area = 0;
|
||||
// note that we don't need to loop over all from_links, because they are handled in the previous getFlow(.) call (or in the very last)
|
||||
for (Point2LinkIt to_link_it = to_links.first; to_link_it != to_links.second; ++to_link_it)
|
||||
{
|
||||
if (std::abs(vSize( pair.first.a.p() - pair.first.b.p()) - pair.second.dist) > 10)
|
||||
DEBUG_PRINTLN(vSize( pair.first.a.p() - pair.first.b.p())<<" != " << pair.second.dist);
|
||||
|
||||
const ProximityPointLink& to_link = to_link_it->second;
|
||||
ListPolyIt to_it = to_link.a;
|
||||
ListPolyIt to_other_it = to_link.b;
|
||||
if (to_link.a.p() != to)
|
||||
{
|
||||
assert(to_link.b.p() == to && "Either part of the link should be the point in the link!");
|
||||
std::swap(to_it, to_other_it);
|
||||
}
|
||||
ListPolyIt from_it = to_it.prev();
|
||||
|
||||
if (from_it.p() != from)
|
||||
{
|
||||
logWarning("Polygon has multiple verts at the same place: (%lli, %lli); PolygonProximityLinker fails in such a case!\n", from.X, from.Y);
|
||||
}
|
||||
|
||||
ListPolyIt to_other_next_it = to_other_it.next(); // move towards [from]; the lines on the other side move in the other direction
|
||||
// to from
|
||||
// o<--o<--T<--F
|
||||
// | : :
|
||||
// v : :
|
||||
// o-->o-->o-->o
|
||||
// , ,
|
||||
// ; to_other_next
|
||||
// to other
|
||||
|
||||
bool are_in_same_general_direction = dot(from - to, to_other_it.p() - to_other_next_it.p()) > 0;
|
||||
// handle multiple points linked to [to]
|
||||
// o<<<T<<<F
|
||||
// / |
|
||||
// / |
|
||||
// o>>>o>>>o
|
||||
// , ,
|
||||
// ; to other next
|
||||
// to other
|
||||
if (!are_in_same_general_direction)
|
||||
{
|
||||
overlap_area += handlePotentialOverlap(to_it, to_it, to_link, to_other_next_it, to_other_it);
|
||||
}
|
||||
|
||||
// handle multiple points linked to [to_other]
|
||||
// o<<<T<<<F
|
||||
// | /
|
||||
// | /
|
||||
// o>>>o>>>o
|
||||
bool all_are_in_same_general_direction = are_in_same_general_direction && dot(from - to, to_other_it.prev().p() - to_other_it.p()) > 0;
|
||||
if (!all_are_in_same_general_direction)
|
||||
{
|
||||
overlap_area += handlePotentialOverlap(from_it, to_it, to_link, to_other_it, to_other_it);
|
||||
}
|
||||
|
||||
// handle normal case where the segment from-to overlaps with another segment
|
||||
// o<<<T<<<F
|
||||
// | |
|
||||
// | |
|
||||
// o>>>o>>>o
|
||||
// , ,
|
||||
// ; to other next
|
||||
// to other
|
||||
if (!are_in_same_general_direction)
|
||||
{
|
||||
overlap_area += handlePotentialOverlap(from_it, to_it, to_link, to_other_next_it, to_other_it);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t normal_area = vSize(from - to) * line_width;
|
||||
float ratio = float(normal_area - overlap_area) / normal_area;
|
||||
// clamp the ratio because overlap compensation might be faulty because
|
||||
// WallOverlapComputation::getApproxOverlapArea only gives roughly accurate results
|
||||
return std::min(1.0f, std::max(0.0f, ratio));
|
||||
}
|
||||
|
||||
void WallOverlapComputation::debugCheckNonePassedYet()
|
||||
int64_t WallOverlapComputation::handlePotentialOverlap(const ListPolyIt from_it, const ListPolyIt to_it, const ProximityPointLink& to_link, const ListPolyIt from_other_it, const ListPolyIt to_other_it)
|
||||
{
|
||||
for (std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> pair : overlap_point_links)
|
||||
if (from_it == to_other_it && from_it == from_other_it)
|
||||
{ // don't compute overlap with a line and itself
|
||||
return 0;
|
||||
}
|
||||
const ProximityPointLink* from_link = overlap_linker.getLink(from_it, from_other_it);
|
||||
if (!from_link)
|
||||
{
|
||||
if (pair.second.passed)
|
||||
{
|
||||
logError("ERROR: WallOverlapComputation link passed just after contruction!!!\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (!getIsPassed(to_link, *from_link))
|
||||
{ // check whether the segment is already passed
|
||||
setIsPassed(to_link, *from_link);
|
||||
return 0;
|
||||
}
|
||||
return getApproxOverlapArea(from_it.p(), to_it.p(), to_link.dist, to_other_it.p(), from_other_it.p(), from_link->dist);
|
||||
}
|
||||
|
||||
int64_t WallOverlapComputation::getApproxOverlapArea(const Point from, const Point to, const int64_t to_dist, const Point other_from, const Point other_to, const int64_t from_dist)
|
||||
{
|
||||
const int64_t overlap_width_2 = line_width * 2 - from_dist - to_dist; //Twice the width of the overlap area, perpendicular to the lines.
|
||||
|
||||
// check whether the line segment overlaps with the point if one of the line segments is just a point
|
||||
if (from == to)
|
||||
{
|
||||
if (LinearAlg2D::pointIsProjectedBeyondLine(from, other_from, other_to) != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
const int64_t overlap_length_2 = vSize(other_to - other_from); //Twice the length of the overlap area, alongside the lines.
|
||||
return overlap_length_2 * overlap_width_2 / 4; //Area = width * height.
|
||||
}
|
||||
if (other_from == other_to)
|
||||
{
|
||||
if (LinearAlg2D::pointIsProjectedBeyondLine(other_from, from, to) != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
const int64_t overlap_length_2 = vSize(from - to); //Twice the length of the overlap area, alongside the lines.
|
||||
return overlap_length_2 * overlap_width_2 / 4; //Area = width * height.
|
||||
}
|
||||
|
||||
short from_rel = LinearAlg2D::pointIsProjectedBeyondLine(from, other_from, other_to);
|
||||
short to_rel = LinearAlg2D::pointIsProjectedBeyondLine(to, other_from, other_to);
|
||||
short other_from_rel = LinearAlg2D::pointIsProjectedBeyondLine(other_from, from, to);
|
||||
short other_to_rel = LinearAlg2D::pointIsProjectedBeyondLine(other_to, from, to);
|
||||
if (from_rel != 0 && to_rel == from_rel && other_from_rel != 0 && other_to_rel == other_from_rel)
|
||||
{
|
||||
// both segments project fully beyond or before each other
|
||||
// for example: or:
|
||||
// O<------O . O------>O
|
||||
// : : \_
|
||||
// ' O------->O O------>O
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (from_rel != 0 && from_rel == other_from_rel && to_rel == 0 && other_to_rel == 0)
|
||||
{
|
||||
// only ends of line segments overlap
|
||||
//
|
||||
// to_proj
|
||||
// ^^^^^
|
||||
// O<--+----O
|
||||
// : :
|
||||
// O-----+-->O
|
||||
// ,,,,,
|
||||
// other_to_proj
|
||||
const Point other_vec = other_from - other_to;
|
||||
const int64_t to_proj = dot(to - other_to, other_vec) / vSize(other_vec);
|
||||
|
||||
const Point vec = from - to;
|
||||
const int64_t other_to_proj = dot(other_to - to, vec) / vSize(vec);
|
||||
|
||||
const int64_t overlap_length_2 = to_proj + other_to_proj; //Twice the length of the overlap area, alongside the lines.
|
||||
return overlap_length_2 * overlap_width_2 / 4; //Area = width * height.
|
||||
}
|
||||
if (to_rel != 0 && to_rel == other_to_rel && from_rel == 0 && other_from_rel == 0)
|
||||
{
|
||||
// only beginnings of line segments overlap
|
||||
//
|
||||
// from_proj
|
||||
// ^^^^^
|
||||
// O<---+---O
|
||||
// : :
|
||||
// O---+---->O
|
||||
// ,,,,,
|
||||
// other_from_proj
|
||||
const Point other_vec = other_to - other_from;
|
||||
const int64_t from_proj = dot(from - other_from, other_vec) / vSize(other_vec);
|
||||
|
||||
const Point vec = to - from;
|
||||
const int64_t other_from_proj = dot(other_from - from, vec) / vSize(vec);
|
||||
|
||||
const int64_t overlap_length_2 = from_proj + other_from_proj; //Twice the length of the overlap area, alongside the lines.
|
||||
return overlap_length_2 * overlap_width_2 / 4; //Area = width * height.
|
||||
}
|
||||
|
||||
//More complex case.
|
||||
const Point from_middle = other_to + from; // don't divide by two just yet
|
||||
const Point to_middle = other_from + to; // don't divide by two just yet
|
||||
|
||||
const int64_t overlap_length_2 = vSize(from_middle - to_middle); //(An approximation of) twice the length of the overlap area, alongside the lines.
|
||||
return overlap_length_2 * overlap_width_2 / 4; //Area = width * height.
|
||||
}
|
||||
|
||||
bool WallOverlapComputation::getIsPassed(const ProximityPointLink& link_a, const ProximityPointLink& link_b)
|
||||
{
|
||||
return passed_links.find(SymmetricPair<ProximityPointLink>(link_a, link_b)) != passed_links.end();
|
||||
}
|
||||
|
||||
void WallOverlapComputation::setIsPassed(const ProximityPointLink& link_a, const ProximityPointLink& link_b)
|
||||
{
|
||||
passed_links.emplace(link_a, link_b);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void WallOverlapComputation::wallOverlaps2HTML(const char* filename) const
|
||||
{
|
||||
|
||||
WallOverlapComputation copy = *this; // copy, cause getFlow might change the state of the overlap computation!
|
||||
|
||||
AABB aabb(copy.polygons);
|
||||
|
||||
aabb.expand(200);
|
||||
|
||||
SVG svg(filename, aabb, Point(1024 * 2, 1024 * 2));
|
||||
|
||||
|
||||
svg.writeAreas(copy.polygons);
|
||||
|
||||
{ // output points and coords
|
||||
for (ListPolygon poly : copy.list_polygons)
|
||||
{
|
||||
for (Point& p : poly)
|
||||
{
|
||||
svg.writePoint(p, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // output links
|
||||
// output normal links
|
||||
for (std::pair<WallOverlapPointLink , WallOverlapPointLinkAttributes> link_pair : copy.overlap_point_links)
|
||||
{
|
||||
WallOverlapPointLink& link = link_pair.first;
|
||||
Point a = svg.transform(link.a.p());
|
||||
Point b = svg.transform(link.b.p());
|
||||
svg.printf("<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:rgb(%d,%d,0);stroke-width:1\" />", a.X, a.Y, b.X, b.Y, link_pair.second.dist == line_width? 0 : 255, link_pair.second.dist==line_width? 255 : 0);
|
||||
}
|
||||
|
||||
// output ending links
|
||||
for (std::pair<WallOverlapPointLink , WallOverlapPointLinkAttributes> link_pair : copy.overlap_point_links_endings)
|
||||
{
|
||||
WallOverlapPointLink& link = link_pair.first;
|
||||
Point a = svg.transform(link.a.p());
|
||||
Point b = svg.transform(link.b.p());
|
||||
svg.printf("<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:rgb(%d,%d,0);stroke-width:1\" />", a.X, a.Y, b.X, b.Y, link_pair.second.dist == line_width? 0 : 255, link_pair.second.dist==line_width? 255 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
{ // output flow
|
||||
for (ListPolygon poly : copy.list_polygons)
|
||||
{
|
||||
Point p0 = poly.back();
|
||||
svg.writePoint(p0, false, 5, SVG::Color::BLUE); // make start points of each poly blue
|
||||
for (Point& p1 : poly)
|
||||
{
|
||||
Point middle = (p0 + p1) / 2;
|
||||
|
||||
float flow = copy.getFlow(p0, p1);
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "flow: " << flow;
|
||||
svg.writeText(middle, oss.str());
|
||||
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+82
-204
@@ -1,3 +1,4 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef WALL_OVERLAP_H
|
||||
#define WALL_OVERLAP_H
|
||||
|
||||
@@ -11,8 +12,10 @@
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/linearAlg2D.h"
|
||||
#include "utils/SymmetricPair.h"
|
||||
|
||||
#include "debug.h" // TODO remove
|
||||
#include "utils/ProximityPointLink.h"
|
||||
#include "utils/PolygonProximityLinker.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -26,215 +29,27 @@ namespace cura
|
||||
* The amount of overlap between two locations is recorded in a link, so that we can look up the overlap at a given point in the polygon.
|
||||
* A link always occurs between a point already on a polygon and either another point of a polygon or a point on a line segment of a polygon.
|
||||
* In the latter case we insert the point into the polygon so that we can later look up by how much to reduce the extrusion at the corresponding line segment.
|
||||
* This is the reason that the polygons are converted to linked lists before the wall overlap compensation computation takes place, after which they are converted back.
|
||||
*
|
||||
* At the end of a sequence of trapezoids the overlap area generally ends with a residual triangle.
|
||||
* Therefore points are introduced on the line segments involved and a link is created with overlap zero.
|
||||
*
|
||||
* We end up with a mapping from each link to a boolean value representing whether the trapezoid is already compensated for.
|
||||
* Each point on the polygons then maps to a link (and its corresponding boolean), so that we can easily look up which links corresponds
|
||||
* \see PolygonProximityLinker
|
||||
*
|
||||
* Each point on the polygons then maps to a link, so that we can easily look up which links corresponds
|
||||
* to the current line segment being produced when producing gcode.
|
||||
*
|
||||
* When producing gcode, the first line crossing the overlap area is laid down normally and the second line is reduced by the overlap amount.
|
||||
* For this reason the function WallOverlapComputation::getFlow changes the internal state of this WallOverlapComputation.
|
||||
* For this reason the function WallOverlapComputation::getFlow changes the internal state of the PolygonProximityLinker.
|
||||
*
|
||||
* The main functionality of this class is performed by the constructor.
|
||||
* The main functionality of this class is performed by the constructor, by calling the constructor of PolygonProximityLinker.
|
||||
* The adjustment during gcode generation is made with the help of WallOverlapComputation::getFlow
|
||||
*/
|
||||
class WallOverlapComputation
|
||||
{
|
||||
|
||||
typedef std::list<Point> ListPolygon; //!< A polygon represented by a linked list instead of a vector
|
||||
typedef std::vector<ListPolygon> ListPolygons; //!< Polygons represented by a vector of linked lists instead of a vector of vectors
|
||||
|
||||
/*!
|
||||
* Convert Polygons to ListPolygons
|
||||
*
|
||||
* \param polys The polygons to convert
|
||||
* \param result The converted polygons
|
||||
*/
|
||||
static void convertPolygonsToLists(Polygons& polys, ListPolygons& result);
|
||||
/*!
|
||||
* Convert ListPolygons to Polygons
|
||||
*
|
||||
* \param list_polygons The polygons to convert
|
||||
* \param polygons The converted polygons
|
||||
*/
|
||||
static void convertListPolygonsToPolygons(ListPolygons& list_polygons, Polygons& polygons);
|
||||
|
||||
|
||||
/*!
|
||||
* A wrapper class for a ListPolygon::iterator and a reference to the containing ListPolygon
|
||||
*/
|
||||
struct ListPolyIt
|
||||
{
|
||||
ListPolygon& poly; //!< The polygon
|
||||
ListPolygon::iterator it; //!< The iterator into ListPolyIt::poly
|
||||
ListPolyIt(const ListPolyIt& other)
|
||||
: poly(other.poly), it(other.it) { }
|
||||
ListPolyIt(ListPolygon& poly, ListPolygon::iterator it)
|
||||
: poly(poly), it(it) { }
|
||||
Point& p() const { return *it; }
|
||||
/*!
|
||||
* Test whether two iterators refer to the same polygon in the same polygon list.
|
||||
*
|
||||
* \param other The ListPolyIt to test for equality
|
||||
* \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument.
|
||||
*/
|
||||
bool operator==(const ListPolyIt& other) const
|
||||
{
|
||||
return &poly == &other.poly && it == other.it;
|
||||
}
|
||||
void operator=(const ListPolyIt& other) { poly = other.poly; it = other.it; }
|
||||
//!< move the iterator forward (and wrap around at the end)
|
||||
ListPolyIt& operator++()
|
||||
{
|
||||
++it;
|
||||
if (it == poly.end()) { it = poly.begin(); }
|
||||
return *this;
|
||||
}
|
||||
//!< move the iterator backward (and wrap around at the beginning)
|
||||
ListPolyIt& operator--()
|
||||
{
|
||||
if (it == poly.begin()) { it = poly.end(); }
|
||||
--it;
|
||||
return *this;
|
||||
}
|
||||
ListPolyIt next() const
|
||||
{
|
||||
ListPolyIt ret(*this);
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
ListPolyIt prev() const
|
||||
{
|
||||
ListPolyIt ret(*this);
|
||||
--ret;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
/*!
|
||||
* A class recording the amount of overlap implicitly by recording the distance between two points on two different polygons or one and the same polygon.
|
||||
* The order of the two points doesn't matter.
|
||||
*/
|
||||
struct WallOverlapPointLink
|
||||
{
|
||||
const ListPolyIt a; //!< the one point (invalidated after list_polygons have been cleared!)
|
||||
const ListPolyIt b; //!< the other point (invalidated after list_polygons have been cleared!)
|
||||
WallOverlapPointLink(const ListPolyIt a, const ListPolyIt b) : a(a), b(b) { }
|
||||
bool operator==(const WallOverlapPointLink& other) const { return (a == other.a && b == other.b) || (a == other.b && b == other.a); }
|
||||
};
|
||||
|
||||
/*!
|
||||
* The hash function object for WallOverlapPointLink
|
||||
*/
|
||||
struct WallOverlapPointLink_Hasher
|
||||
{
|
||||
std::size_t operator()(const WallOverlapPointLink& pp) const
|
||||
{
|
||||
return std::hash<Point>()(*pp.a.it) + std::hash<Point>()(*pp.b.it);
|
||||
}
|
||||
};
|
||||
PolygonProximityLinker overlap_linker;
|
||||
int64_t line_width;
|
||||
|
||||
struct WallOverlapPointLinkAttributes
|
||||
{
|
||||
int dist; //!< The distance between the two points
|
||||
bool passed; //!< Whether this point has been passed while writing gcode
|
||||
WallOverlapPointLinkAttributes(int dist, bool passed) : dist(dist), passed(passed) { }
|
||||
};
|
||||
|
||||
typedef std::unordered_map<WallOverlapPointLink, WallOverlapPointLinkAttributes, WallOverlapPointLink_Hasher> WallOverlapPointLinks; //!< The type of WallOverlapComputation::overlap_point_links
|
||||
typedef std::unordered_map<Point, WallOverlapPointLinks::iterator> Point2Link; //!< The type of WallOverlapComputation::point_to_link \warning mapping to iterators which might get invalidated!
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
Polygons& polygons; //!< The polygons for which to compensate overlapping walls for
|
||||
ListPolygons list_polygons; //!< The WallOverlapComputation::polygons converted
|
||||
|
||||
int line_width; //!< The line width of the walls
|
||||
|
||||
WallOverlapPointLinks overlap_point_links; //!< mapping from each link to its attributes
|
||||
WallOverlapPointLinks overlap_point_links_endings; //!< mapping from each ending link to its attributes (which has a distance field equal to WallOverlapComputation::line_width). Note that this is a separate map from WallOverlapComputation::overlap_point_links, because that magically solved a bug .
|
||||
|
||||
Point2Link point_to_link; //!< mapping from each point to the/a corresponding link (collisions are ignored as of yet) \warning mapping to iterators which might get invalidated!
|
||||
|
||||
void findOverlapPoints(); //!< find the basic overlap links (for trapezoids) and record them into WallOverlapComputation::overlap_point_links
|
||||
/*!
|
||||
* find the basic overlap links (for trapezoids) between a given point and a polygon and record them into WallOverlapComputation::overlap_point_links
|
||||
*
|
||||
* \param from The point from which to check for overlap
|
||||
* \param to_list_poly_idx The index into WallOverlapComputation::list_polygons for the polygon to check
|
||||
*/
|
||||
void findOverlapPoints(ListPolyIt from, unsigned int to_list_poly_idx);
|
||||
/*!
|
||||
* Find the basic overlap links (for trapezoids) between a given point and a polygon up from a particular index and record them into WallOverlapComputation::overlap_point_links
|
||||
*
|
||||
* This function is used for finding overlaps within a single polygon. It then uses a \p start different from the first point in the polygon.
|
||||
*
|
||||
* \param from The point from which to check for overlap
|
||||
* \param to_list_poly_idx The index into WallOverlapComputation::list_polygons for the polygon to check
|
||||
* \param start Where to start looking into the polygon with index \p to_list_poly_idx
|
||||
*/
|
||||
void findOverlapPoints(ListPolyIt from, unsigned int to_list_poly_idx, const ListPolygon::iterator start);
|
||||
|
||||
/*!
|
||||
* Add a link between \p from and \p to to WallOverlapComputation::overlap_point_links and add the appropriate mappings to WallOverlapComputation::point_to_link
|
||||
*
|
||||
* \param from The one point of the link
|
||||
* \param to The other point of the link
|
||||
* \param dist The distance between the two points
|
||||
* \return Whether the point has been added
|
||||
*/
|
||||
bool addOverlapPoint(ListPolyIt from, ListPolyIt to, int64_t dist);
|
||||
/*!
|
||||
* Add a link between \p from and \p to to WallOverlapComputation::overlap_point_links_endings and add the appropriate mappings to WallOverlapComputation::point_to_link
|
||||
*
|
||||
* \param from The one point of the link
|
||||
* \param to The other point of the link
|
||||
* \param dist The distance between the two points
|
||||
* \return Whether the point has been added
|
||||
*/
|
||||
bool addOverlapPoint_endings(ListPolyIt from, ListPolyIt to, int64_t dist);
|
||||
|
||||
/*!
|
||||
* Add links for the ending points of overlap regions, supporting the residual triangles.
|
||||
*/
|
||||
void addOverlapEndings();
|
||||
|
||||
/*!
|
||||
* Add a link for the ending point of a given overlap region, if it is an ending.
|
||||
*
|
||||
* \param link_pair The link which might be an ending
|
||||
* \param a_next The next point from ListPolyIt::a of \p link
|
||||
* \param b_next The next point from ListPolyIt::b of \p link (in the opposite direction of \p a_next)
|
||||
* \param a_before_middle Where to insert a new point for a if this is indeed en ending
|
||||
* \param b_before_middle Where to insert a new point for b if this is indeed en ending
|
||||
*/
|
||||
void addOverlapEnding(std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> link_pair, const ListPolyIt& a_next, const ListPolyIt& b_next, const ListPolyIt& a_before_middle, const ListPolyIt& b_before_middle);
|
||||
|
||||
/*!
|
||||
* Compute the distance between the points of the last link and the points introduced to account for the overlap endings.
|
||||
*/
|
||||
int64_t overlapEndingDistance(Point& a1, Point& a2, Point& b1, Point& b2, int a1b1_dist);
|
||||
|
||||
|
||||
/*!
|
||||
* Add overlap links for sharp corners, so that the overlap of two consecutive line segments is compensated for.
|
||||
*
|
||||
* Currently UNIMPLEMENTED.
|
||||
*/
|
||||
void addSharpCorners();
|
||||
|
||||
/*!
|
||||
* Map a point to a link in WallOverlapComputation::point_to_link
|
||||
*
|
||||
* \param p The key
|
||||
* \param it The value
|
||||
*/
|
||||
void addToPoint2LinkMap(Point p, WallOverlapPointLinks::iterator it);
|
||||
|
||||
std::unordered_set<SymmetricPair<ProximityPointLink>> passed_links;
|
||||
public:
|
||||
/*!
|
||||
* Compute the flow for a given line segment in the wall.
|
||||
@@ -246,19 +61,82 @@ public:
|
||||
* \return a value between zero and one representing the reduced flow of the line segment
|
||||
*/
|
||||
float getFlow(Point& from, Point& to);
|
||||
|
||||
void debugCheck(); //!< debug
|
||||
|
||||
void debugCheckNonePassedYet(); //!< check whether no link has passed set to true
|
||||
|
||||
void wallOverlaps2HTML(const char* filename) const; //!< debug
|
||||
|
||||
/*!
|
||||
* Computes the neccesary priliminaries in order to efficiently compute the flow when generatign gcode paths.
|
||||
* \param polygons The wall polygons for which to compute the overlaps
|
||||
*/
|
||||
WallOverlapComputation(Polygons& polygons, int lineWidth);
|
||||
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Check whether \p from_it and \p from_other_it are connected and if so,
|
||||
* return the overlap area between those and the link \p to_link
|
||||
*
|
||||
* This presupposes that \p to_link and the link from \p from_it to \p from_other_it forms a single overlap quadrilateral
|
||||
*
|
||||
* from_other to_other
|
||||
* o<--------o
|
||||
* ? :
|
||||
* ? :
|
||||
* ? :
|
||||
* o-------->o
|
||||
* from to
|
||||
*
|
||||
* \param from_it The first point possibly invovled in the second link
|
||||
* \param to_it The first point of \p to_link connected to \p from_it
|
||||
* \param to_link The first link involved in the overlap: from \p from_it to \p to_it
|
||||
* \param from_other_it The second point possibly involved in the second link
|
||||
* \param to_other_it The second point of \p to_link connected to \p from_other_it
|
||||
* \return The overlap area between the two links, or zero if there was no such link
|
||||
*/
|
||||
int64_t handlePotentialOverlap(const ListPolyIt from_it, const ListPolyIt to_it, const ProximityPointLink& to_link, const ListPolyIt from_other_it, const ListPolyIt to_other_it);
|
||||
|
||||
/*!
|
||||
* Compute the approximate overlap area between two line segments
|
||||
* or between a line segment and a point when one of the line segments has the same start as end point.
|
||||
*
|
||||
* other_to other_from
|
||||
* o<--------o
|
||||
* : :
|
||||
* :,,,,,,,,,:
|
||||
* ://///////: \
|
||||
* ://///////: } overlap area
|
||||
* ://///////: /
|
||||
* :''''''''':
|
||||
* : :
|
||||
* o-------->o
|
||||
* from to
|
||||
*
|
||||
* \param from The starting point of the one line segment
|
||||
* \param to the end point of the one line segment
|
||||
* \param to_dist The distance between \p to and \p to_other
|
||||
* \param other_from The starting point of the other line segment (across the overlap of \p to)
|
||||
* \param other_to The end point of the other line segment (across the overlap of \p from)
|
||||
* \param from_dist The distance between \p from and \p from_other
|
||||
*/
|
||||
int64_t getApproxOverlapArea(const Point from, const Point to, const int64_t to_dist, const Point other_from, const Point other_to, const int64_t from_dist);
|
||||
|
||||
/*!
|
||||
* Check whether an overlap segment between two consecutive links is already passed
|
||||
*
|
||||
* \note \p link_a and \p link_b are assumed to be consecutive
|
||||
*
|
||||
* \param link_a the one link of the overlap area
|
||||
* \param link_b the other link of the overlap area
|
||||
* \return whether the link has already been passed once
|
||||
*/
|
||||
bool getIsPassed(const ProximityPointLink& link_a, const ProximityPointLink& link_b);
|
||||
|
||||
/*!
|
||||
* Mark an overlap area between two consecutive links as being passed once already.
|
||||
*
|
||||
* \note \p link_a and \p link_b are assumed to be consecutive
|
||||
*
|
||||
* \param link_a the one link of the overlap area
|
||||
* \param link_b the other link of the overlap area
|
||||
*/
|
||||
void setIsPassed(const ProximityPointLink& link_a, const ProximityPointLink& link_b);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -9,12 +9,18 @@
|
||||
#include "gcodePlanner.h"
|
||||
#include "MeshGroup.h"
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
ENUM( WeaveSegmentType, UP, DOWN, FLAT, MOVE, DOWN_AND_FLAT); // DOWN_AND_FLAT is for parts of the roof which can either be viewed as flat or as down, since their [to] location is an up move with zero length
|
||||
enum class WeaveSegmentType
|
||||
{
|
||||
UP,
|
||||
DOWN,
|
||||
FLAT,
|
||||
MOVE,
|
||||
DOWN_AND_FLAT // DOWN_AND_FLAT is for parts of the roof which can either be viewed as flat or as down, since their [to] location is an up move with zero length
|
||||
};
|
||||
|
||||
|
||||
struct WeaveConnectionSegment
|
||||
|
||||
+64
-17
@@ -10,6 +10,7 @@
|
||||
# * All settings random
|
||||
|
||||
import ast #For safe function evaluation.
|
||||
import math #For evaluating setting inheritance functions.
|
||||
import sys
|
||||
import subprocess
|
||||
import os
|
||||
@@ -22,29 +23,51 @@ import threading
|
||||
from xml.etree import ElementTree
|
||||
|
||||
|
||||
# Mock function that occurs in fdmprinter.def.json
|
||||
# Use wrapper to provide locals
|
||||
def extruderValueWrapper(_locals):
|
||||
def extruderValue(extruder_nr, parameter):
|
||||
return eval(parameter, globals(), _locals)
|
||||
return extruderValue
|
||||
|
||||
|
||||
# Mock function that occurs in fdmprinter.def.json
|
||||
# Use wrapper to provide locals
|
||||
def extruderValuesWrapper(_locals):
|
||||
def extruderValues(parameter):
|
||||
return [eval(parameter, globals(), _locals)]
|
||||
return extruderValues
|
||||
|
||||
|
||||
# Mock function that occurs in fdmprinter.def.json
|
||||
# Use wrapper to provide locals
|
||||
def resolveOrValueWrapper(_locals):
|
||||
def resolveOrValue(parameter):
|
||||
return eval(parameter, globals(), _locals)
|
||||
return resolveOrValue
|
||||
|
||||
|
||||
## The TestSuite class stores the test results of a single set of tests.
|
||||
# TestSuite objects are created by the TestResults class.
|
||||
class TestSuite():
|
||||
class TestSuite:
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
self._successes = []
|
||||
self._failures = []
|
||||
|
||||
## Add a successfull test result to the test suite.
|
||||
## Add a successful test result to the test suite.
|
||||
def success(self, class_name, test_name):
|
||||
#print('Success:', class_name, test_name)
|
||||
self._successes.append((class_name, test_name))
|
||||
|
||||
## Add a failed test result to the test suite.
|
||||
def failure(self, class_name, test_name, error_message):
|
||||
#print('Failure:', class_name, test_name, error_message)
|
||||
self._failures.append((class_name, test_name, error_message))
|
||||
|
||||
## Return the number of tests in this test suite
|
||||
def getTestCount(self):
|
||||
return self.getSuccessCount() + self.getFailureCount()
|
||||
|
||||
## Return the number of successfull tests in this test suite
|
||||
## Return the number of successful tests in this test suite
|
||||
def getSuccessCount(self):
|
||||
return len(self._successes)
|
||||
|
||||
@@ -55,7 +78,7 @@ class TestSuite():
|
||||
|
||||
## The TestResults class stores a group of TestSuite objects, each TestSuite object contains failed and successful test.
|
||||
# This class can output the result of the tests in a JUnit xml format for parsing in Jenkins.
|
||||
class TestResults():
|
||||
class TestResults:
|
||||
def __init__(self):
|
||||
self._testsuites = []
|
||||
|
||||
@@ -93,7 +116,7 @@ class TestResults():
|
||||
return ElementTree.ElementTree(xml).write(filename, "utf-8", True)
|
||||
|
||||
|
||||
class Setting():
|
||||
class Setting:
|
||||
## Creates a new setting from a JSON node.
|
||||
#
|
||||
# Some parts of the setting may have to be evaluated as functions. For
|
||||
@@ -106,12 +129,15 @@ class Setting():
|
||||
# \param locals The local variables for eventual function evaluation.
|
||||
def __init__(self, key, data, locals):
|
||||
self._key = key
|
||||
self._default = data["default_value"]
|
||||
if "value" in data: #Evaluate "value" if we can, otherwise just take default_value.
|
||||
self._default = self._evaluateFunction(data.get("value", "0"), locals)
|
||||
else:
|
||||
self._default = data.get("default_value", 0)
|
||||
self._type = data["type"]
|
||||
self._min_value = self._evaluateFunction(data.get("min_value", None), locals)
|
||||
self._max_value = self._evaluateFunction(data.get("max_value", None), locals)
|
||||
self._min_value_warning = self._evaluateFunction(data.get("min_value_warning", None), locals)
|
||||
self._max_value_warning = self._evaluateFunction(data.get("max_value_warning", None), locals)
|
||||
self._min_value = self._evaluateFunction(data.get("minimum_value", None), locals)
|
||||
self._max_value = self._evaluateFunction(data.get("maximum_value", None), locals)
|
||||
self._min_value_warning = self._evaluateFunction(data.get("minimum_value_warning", None), locals)
|
||||
self._max_value_warning = self._evaluateFunction(data.get("maximum_value_warning", None), locals)
|
||||
self._options = data.get("options", None)
|
||||
if self._options is not None:
|
||||
self._options = list(self._options.keys())
|
||||
@@ -159,9 +185,13 @@ class Setting():
|
||||
if self._type == "enum":
|
||||
return self._options
|
||||
if self._type == "str":
|
||||
return self._default
|
||||
return [self._default]
|
||||
if self._type == "extruder":
|
||||
return self._default # TODO: also allow for other values below machine_extruder_count
|
||||
return [self._default] # TODO: also allow for other values below machine_extruder_count
|
||||
if self._type == "polygon":
|
||||
return [self._default]
|
||||
if self._type == "polygons":
|
||||
return [self._default]
|
||||
print("Unknown setting type:", self._type)
|
||||
|
||||
## Return a random value for this setting. The returned value will be a valid value according to the settings json file.
|
||||
@@ -200,6 +230,7 @@ class Setting():
|
||||
def _evaluateFunction(self, code, locals):
|
||||
if not code: #The input was None. This setting value doesn't exist in the JSON.
|
||||
return None
|
||||
|
||||
try:
|
||||
tree = ast.parse(code, "eval")
|
||||
compiled = compile(code, self._key, "eval")
|
||||
@@ -212,12 +243,13 @@ class Setting():
|
||||
|
||||
return eval(compiled, globals(), locals)
|
||||
|
||||
class EngineTest():
|
||||
class EngineTest:
|
||||
def __init__(self, json_filename, engine_filename, models):
|
||||
self._json_filename = json_filename
|
||||
self._json = json.load(open(json_filename, "r"))
|
||||
self._locals = {}
|
||||
self._addAllLocals() #Fills the _locals dictionary.
|
||||
self._addLocalsFunctions() # Add mock functions used in fdmprinter
|
||||
self._engine = engine_filename
|
||||
self._models = models
|
||||
self._settings = {}
|
||||
@@ -289,8 +321,10 @@ class EngineTest():
|
||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p.error = ""
|
||||
t = threading.Thread(target=self._abortProcess, args=(p,))
|
||||
p.aborting = False
|
||||
t.start()
|
||||
stdout, stderr = p.communicate()
|
||||
p.aborting = True
|
||||
if p.error == "Timeout":
|
||||
return "Timeout: %s" % (' '.join(cmd))
|
||||
if p.wait() != 0:
|
||||
@@ -298,7 +332,10 @@ class EngineTest():
|
||||
return None
|
||||
|
||||
def _abortProcess(self, p):
|
||||
time.sleep(60)
|
||||
for i in range(0, 60):
|
||||
time.sleep(1) #Check every 1000ms if we need to abort the thread.
|
||||
if p.aborting:
|
||||
break
|
||||
if p.poll() is None:
|
||||
p.terminate()
|
||||
p.error = "Timeout"
|
||||
@@ -313,6 +350,16 @@ class EngineTest():
|
||||
for key, data in self._json["settings"].items(): # top level categories
|
||||
self._addLocals(data["children"]) # the actual settings in each category
|
||||
|
||||
def _addLocalsFunctions(self):
|
||||
extruderValue = extruderValueWrapper(self._locals)
|
||||
self._locals['extruderValue'] = extruderValue
|
||||
|
||||
extruderValues = extruderValuesWrapper(self._locals)
|
||||
self._locals['extruderValues'] = extruderValues
|
||||
|
||||
resolveOrValue = resolveOrValueWrapper(self._locals)
|
||||
self._locals['resolveOrValue'] = resolveOrValue
|
||||
|
||||
## Adds the default values in a node of the setting tree to the locals.
|
||||
#
|
||||
# The results are stored in self._locals, keyed by the setting name.
|
||||
@@ -358,4 +405,4 @@ if __name__ == "__main__":
|
||||
et.getResults().saveXML("output.xml")
|
||||
if args.simple:
|
||||
if et.getResults().getFailureCount() > 0:
|
||||
sys.exit(1)
|
||||
sys.exit(1)
|
||||
@@ -1,220 +0,0 @@
|
||||
//Copyright (c) 2015 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "BucketGrid2DTest.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(BucketGrid2DTest);
|
||||
|
||||
void BucketGrid2DTest::setUp()
|
||||
{
|
||||
//Do nothing.
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::tearDown()
|
||||
{
|
||||
//Do nothing.
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearbyObjectsFarTest()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
input.emplace_back(0, 100);
|
||||
const Point target(100, 100);
|
||||
std::unordered_set<Point> near;
|
||||
std::unordered_set<Point> far;
|
||||
far.emplace(0, 100);
|
||||
findNearbyObjectsAssert(input, target, 10, near, far);
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearbyObjectsLine2Test()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
for (long long x = 0; x < 200; x++)
|
||||
{
|
||||
input.emplace_back(x, 95);
|
||||
}
|
||||
const Point target(99, 100); //Slightly shifted.
|
||||
const unsigned long long grid_size = 10;
|
||||
std::unordered_set<Point> near;
|
||||
std::unordered_set<Point> far;
|
||||
for (const Point point : input)
|
||||
{
|
||||
unsigned long long distance = vSize(point - target);
|
||||
if (distance < grid_size)
|
||||
{
|
||||
near.insert(point);
|
||||
}
|
||||
else if (distance > grid_size * 2) //Grid size * 2 are guaranteed to be considered "far".
|
||||
{
|
||||
far.insert(point);
|
||||
}
|
||||
}
|
||||
findNearbyObjectsAssert(input, target, grid_size, near, far);
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearbyObjectsLineTest()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
for (long long x = 0; x < 200; x++)
|
||||
{
|
||||
input.emplace_back(x, 95);
|
||||
}
|
||||
const Point target(100, 100);
|
||||
const unsigned long long grid_size = 10;
|
||||
std::unordered_set<Point> near;
|
||||
std::unordered_set<Point> far;
|
||||
for (const Point point : input)
|
||||
{
|
||||
unsigned long long distance = vSize(point - target);
|
||||
if (distance < grid_size)
|
||||
{
|
||||
near.insert(point);
|
||||
}
|
||||
else if (distance > grid_size * 2) //Grid size * 2 are guaranteed to be considered "far".
|
||||
{
|
||||
far.insert(point);
|
||||
}
|
||||
}
|
||||
findNearbyObjectsAssert(input, target, grid_size, near, far);
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearbyObjectsNearTest()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
input.emplace_back(95, 100);
|
||||
const Point target(100, 100);
|
||||
std::unordered_set<Point> near;
|
||||
near.emplace(95, 100);
|
||||
std::unordered_set<Point> far;
|
||||
findNearbyObjectsAssert(input, target, 10, near, far);
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearbyObjectsSameTest()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
input.emplace_back(100, 100);
|
||||
const Point target(100, 100);
|
||||
std::unordered_set<Point> near;
|
||||
near.emplace(100, 100);
|
||||
std::unordered_set<Point> far;
|
||||
findNearbyObjectsAssert(input, target, 10, near, far);
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearestObjectChoiceTest()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
input.emplace_back(95, 100);
|
||||
input.emplace_back(103, 100);
|
||||
input.emplace_back(200, 100);
|
||||
findNearestObjectAssert(input, Point(100, 100), 10, new Point(103, 100));
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearestObjectEqualTest()
|
||||
{
|
||||
std::vector<Point> registered_points;
|
||||
registered_points.emplace_back(95, 100);
|
||||
registered_points.emplace_back(105, 100);
|
||||
Point target = Point(100, 100);
|
||||
const unsigned long long grid_size = 10;
|
||||
const Point expected1 = Point(95, 100);
|
||||
const Point expected2 = Point(105, 100);
|
||||
|
||||
BucketGrid2D<Point> grid(grid_size);
|
||||
for (Point point : registered_points)
|
||||
{
|
||||
grid.insert(point, point);
|
||||
}
|
||||
|
||||
Point result;
|
||||
const bool success = grid.findNearestObject(target, result, BucketGrid2D<Point>::no_precondition); //The acutal call to test.
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "findNearestObject returned " << success << " but should've returned true.";
|
||||
CPPUNIT_ASSERT_MESSAGE(ss.str(), success);
|
||||
}
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "findNearestObject reported the nearest point to be " << result << " (distance " << vSize(target - result) << "), but it should've been " << expected1 << " (distance " << vSize(expected1 - target) << ") or " << expected2 << " (distance " << vSize(expected2 - target) << ").";
|
||||
CPPUNIT_ASSERT_MESSAGE(ss.str(), result == expected1 || result == expected2);
|
||||
}
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearestObjectFilterTest()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
input.emplace_back(95, 100);
|
||||
input.emplace_back(98, 100);
|
||||
input.emplace_back(106, 100);
|
||||
std::function<bool(Point, const Point&)> filter = [&] (Point position, const Point& object) -> bool { return position.X > 100; };
|
||||
findNearestObjectAssert(input, Point(100, 100), 10, new Point(106, 100), filter);
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearestObjectNoneTest()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
findNearestObjectAssert(input, Point(100, 100), 10, nullptr);
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearestObjectSameTest()
|
||||
{
|
||||
std::vector<Point> input;
|
||||
input.emplace_back(100, 100);
|
||||
findNearestObjectAssert(input, Point(100, 100), 10, new Point(100, 100));
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearbyObjectsAssert(const std::vector<Point>& registered_points, Point target, const unsigned long long grid_size, const std::unordered_set<Point>& expected_near, const std::unordered_set<Point>& expected_far)
|
||||
{
|
||||
BucketGrid2D<Point> grid(grid_size);
|
||||
for(Point point : registered_points)
|
||||
{
|
||||
grid.insert(point, point);
|
||||
}
|
||||
|
||||
const std::vector<Point> result = grid.findNearbyObjects(target); //The actual call to test.
|
||||
|
||||
//Note that the result may contain the same point more than once. This test is robust against that.
|
||||
for (const Point point : expected_near) //Are all near points reported as near?
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Point " << point << " is near " << target << " (distance " << vSize(point - target) << "), but findNearbyObjects didn't report it as such. Grid size: " << grid_size;
|
||||
CPPUNIT_ASSERT_MESSAGE(ss.str(), std::find(result.begin(), result.end(), point) != result.end()); //Must be in result.
|
||||
}
|
||||
for (const Point point : expected_far) //Are all far points NOT reported as near?
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Point " << point << " is far from " << target << " (distance " << vSize(point - target) << "), but findNearbyObjects thought it was near. Grid size: " << grid_size;
|
||||
CPPUNIT_ASSERT_MESSAGE(ss.str(), std::find(result.begin(), result.end(), point) == result.end()); //Must not be in result.
|
||||
}
|
||||
}
|
||||
|
||||
void BucketGrid2DTest::findNearestObjectAssert(const std::vector<Point>& registered_points, Point target, const unsigned long long grid_size, Point* expected, std::function<bool(Point location, const Point& object)> precondition)
|
||||
{
|
||||
BucketGrid2D<Point> grid(grid_size);
|
||||
for (Point point : registered_points)
|
||||
{
|
||||
grid.insert(point, point);
|
||||
}
|
||||
|
||||
Point result;
|
||||
const bool success = grid.findNearestObject(target, result, precondition); //The acutal call to test.
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "findNearestObject returned " << success << " but should've returned " << (expected != nullptr) << ".";
|
||||
CPPUNIT_ASSERT_MESSAGE(ss.str(), success == (expected != nullptr));
|
||||
}
|
||||
if (expected)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "findNearestObject reported the nearest point to be " << result << " (distance " << vSize(target - result) << "), but it was " << *expected << " (distance " << vSize(*expected - target) << ").";
|
||||
CPPUNIT_ASSERT_MESSAGE(ss.str(), result == *expected);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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