Comparar commits
525 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 89a8fd8b4f | |||
| c73cf897ac | |||
| 43408040c7 | |||
| 0cd3c7829b | |||
| 05c4f3da01 | |||
| 44631f3a6f | |||
| 1ca622b048 | |||
| 17e02edff7 | |||
| 563317f47d | |||
| 4b3a4f57a1 | |||
| 0f15b885f2 | |||
| ecceb71fdd | |||
| b8d019ba4a | |||
| 310eba6e16 | |||
| 82c8ac78a5 | |||
| 60e5b6088e | |||
| f8d17c09bf | |||
| 72605c9d51 | |||
| dad342fefd | |||
| 5b0c3fd4aa | |||
| 7693630f5f | |||
| e4a23e2bef | |||
| f5e3cf4c91 | |||
| 22013f70ab | |||
| 7cf19f06ec | |||
| 314ee7ab2d | |||
| 16d67fcd3e | |||
| 508a093e26 | |||
| 97660d4c80 | |||
| 1f4640e4c2 | |||
| b7b9f15063 | |||
| 97432cf9f0 | |||
| c081f80d26 | |||
| 4307d39cc9 | |||
| 61f1b7d3ba | |||
| f4118da3e3 | |||
| dba466a029 | |||
| 019711e24b | |||
| 043110db69 | |||
| b53295ffd2 | |||
| 319ce6fcfd | |||
| 9d76bb3b3a | |||
| 4bc944ebf6 | |||
| d331687392 | |||
| 8a517de1d1 | |||
| bcd3347862 | |||
| aa8e928f0c | |||
| 4529164cdf | |||
| b4140d7772 | |||
| a9809f3581 | |||
| 2cc01756a0 | |||
| a5e9659676 | |||
| 1cf75345ba | |||
| a771600dec | |||
| 15df20fe99 | |||
| 4179178756 | |||
| a3b85c0362 | |||
| 0084d16c98 | |||
| 9ede9ea524 | |||
| 05be030c45 | |||
| e4d2161de4 | |||
| 0a824aac3c | |||
| cbcd5df2f7 | |||
| 31493500f1 | |||
| 3c5e745f83 | |||
| c3689e0b58 | |||
| d3e455290a | |||
| 2e6cd36f20 | |||
| 9fc4a427cd | |||
| 5729908023 | |||
| c5b90b0ad9 | |||
| 382343e558 | |||
| 68b293b880 | |||
| f867c0f53a | |||
| ee7e83d138 | |||
| 50df40c6c6 | |||
| 4d924fd33d | |||
| c953a726cb | |||
| 7c8c0b2417 | |||
| a834754d64 | |||
| 9957a0c733 | |||
| f6ce0b4141 | |||
| 120a9c440c | |||
| 59f72bdd98 | |||
| abc43302ac | |||
| c2725bdf83 | |||
| 66ad67693e | |||
| 43d4097556 | |||
| 1d030b4251 | |||
| d74fdf2f6c | |||
| a4dc1733e8 | |||
| a6fad000e4 | |||
| 0274330a09 | |||
| 119c7e6a3d | |||
| e74ee27256 | |||
| 4288a75d0f | |||
| fb761dfd9d | |||
| ada75a04a0 | |||
| f9ec748e91 | |||
| 3b59c79bf4 | |||
| 2cb637b79b | |||
| dff6595f27 | |||
| aac3975e5d | |||
| f69d070dd4 | |||
| d28798ee52 | |||
| 1760d6b8ed | |||
| 80b3436075 | |||
| 8ca0fc4cb0 | |||
| 53c4fd28bf | |||
| 90727a0578 | |||
| 56e8f8b10c | |||
| 1fa6298455 | |||
| e7e0fe4050 | |||
| a7e0d49235 | |||
| 8e85c00452 | |||
| 96a4fe1725 | |||
| 13fe966db3 | |||
| 2da5b4960a | |||
| 00d87b089b | |||
| 9ec30d8168 | |||
| cb21c325f6 | |||
| 35c2d018a5 | |||
| a9f749b4de | |||
| 4cecf7b8e3 | |||
| d661a31041 | |||
| f65b270d2a | |||
| de85a67f28 | |||
| 65e9da693d | |||
| 921333a7bc | |||
| fd7d1a4bd4 | |||
| bb9aa2280d | |||
| a38d2fa31e | |||
| 2b1266c647 | |||
| 901bf47610 | |||
| 80a6115537 | |||
| f94ca645bd | |||
| 2067644d30 | |||
| 0285e2f025 | |||
| 38a1ee4270 | |||
| 63459d5cd4 | |||
| 138691436e | |||
| b9c5b4593b | |||
| a0a3a24dc1 | |||
| 5959b41132 | |||
| 3d7229c9f2 | |||
| e1cfc3d93b | |||
| 5834540bec | |||
| f191d23e17 | |||
| 5114ab4218 | |||
| 9f5645ecac | |||
| 66befe5827 | |||
| c9de64f946 | |||
| dbfa3a0f4b | |||
| 02268eb7e8 | |||
| d2b8c8bf17 | |||
| d00f5efa77 | |||
| b8d7162daf | |||
| b0293e2d6a | |||
| e998eb8899 | |||
| 731a69ff86 | |||
| d2d34f89d5 | |||
| 0ad7bd747c | |||
| 3ad4dbd62e | |||
| b5c8f95713 | |||
| 1590c18041 | |||
| 405e8bdd0e | |||
| 06c74c3151 | |||
| 28304d1aa9 | |||
| a18d61ccb3 | |||
| 6350f47cd6 | |||
| e25a0d1305 | |||
| 31179cab5c | |||
| c85146457e | |||
| f1442ba7a5 | |||
| 33e50f7b05 | |||
| 865aacbfb1 | |||
| 2bc7ee5df0 | |||
| 7777668b86 | |||
| 96e8c7a73d | |||
| 89446b8e32 | |||
| b2fa8d3be1 | |||
| d8e0e84fcc | |||
| 443c630997 | |||
| 9831ef3580 | |||
| 460c52ea6c | |||
| 3d1e79c670 | |||
| 879b86cab9 | |||
| 941b1ff1ac | |||
| 4263801d16 | |||
| 52220ccab8 | |||
| cb7b7bf22a | |||
| 7a8be50b8f | |||
| 7c39b6b26a | |||
| a0625aa735 | |||
| 84d3381be6 | |||
| 8b01098dd1 | |||
| 1bfe4e74d2 | |||
| bcde7f50ee | |||
| c7cadc132b | |||
| abfb41006b | |||
| 5db20ee68d | |||
| b0487fa4c7 | |||
| b6355b69e7 | |||
| 9597ee5645 | |||
| 5ba1cb674e | |||
| f336d5ffc0 | |||
| 1d0ac8528b | |||
| 5c06c48c87 | |||
| 837d4e7fba | |||
| 7ded01a110 | |||
| 3f3b526ebd | |||
| b29607b8a9 | |||
| e641a25aab | |||
| b7e9f72023 | |||
| ab2c2eed4b | |||
| 6972101d7e | |||
| 3c0d3f1b2d | |||
| bc82fd98c9 | |||
| 6c986e6cfe | |||
| 36eb3471df | |||
| 4f3b4f429d | |||
| 837f992f69 | |||
| ad76736d26 | |||
| 3fe5a000f2 | |||
| d5a7e2f62e | |||
| 6e5315f754 | |||
| 3cb3e5de45 | |||
| 80f8760f68 | |||
| d75882a707 | |||
| 6925f39426 | |||
| d6cdfefb58 | |||
| dfa23feb31 | |||
| b0888e4424 | |||
| fd06e10646 | |||
| 6e051e0bdc | |||
| ae00cbe497 | |||
| 23ef513cce | |||
| 3aa29d898f | |||
| c9217d4738 | |||
| 16d4ab923f | |||
| 60d4a3108d | |||
| 7ad5eee176 | |||
| 209e9fa50f | |||
| 4aff5a1488 | |||
| 4f786d5533 | |||
| b4fd72f92f | |||
| df892030c5 | |||
| e31a516205 | |||
| c77afe06e2 | |||
| 921ffbe659 | |||
| 96ac0758b9 | |||
| dd8639b1f5 | |||
| 2572349938 | |||
| ac9a053f5b | |||
| bec51eb0bd | |||
| 12ccb3512f | |||
| 87a3a30bc5 | |||
| 6ae41a5e86 | |||
| 8ce016064f | |||
| 4e16b4313f | |||
| 5b493c17dc | |||
| 4b45726ada | |||
| 6653f5a557 | |||
| 4da8c6d8e7 | |||
| c5ce425924 | |||
| c74d4f7550 | |||
| 19f093e0cc | |||
| 2a4cca0402 | |||
| 32804c102c | |||
| 776f56fc37 | |||
| 866e911739 | |||
| a92cd23e62 | |||
| 4725001564 | |||
| a18595877f | |||
| 40e7c450d5 | |||
| 13a0b11d68 | |||
| 4fc69f608a | |||
| deb577d559 | |||
| c8161da3eb | |||
| 87733834d1 | |||
| b9aeea425f | |||
| 44b3039db6 | |||
| 6476270cf6 | |||
| e532f3ddda | |||
| 7aaab151e7 | |||
| 0353433919 | |||
| 40a6c495c7 | |||
| cfa6758911 | |||
| f6b29b1d8a | |||
| bd79a8468e | |||
| 83a1a09772 | |||
| 2c425421ec | |||
| 35dcc6906f | |||
| db7bc279ee | |||
| a7ea623266 | |||
| 724589c13a | |||
| db1fa098ad | |||
| be99db30c0 | |||
| 9456592dd7 | |||
| 20cd4275fc | |||
| 67697d5258 | |||
| 7d8e4de7ba | |||
| 2486120a38 | |||
| 43c8f2a913 | |||
| baf6410a1e | |||
| 4634338c7d | |||
| a2a8604c72 | |||
| dd8b57b666 | |||
| 72ed492a61 | |||
| c02482590f | |||
| f32e2d9554 | |||
| 40a6075022 | |||
| bf61814849 | |||
| 5741e79ade | |||
| 09419fd6be | |||
| a002f4b3b2 | |||
| 25c7ccb0d3 | |||
| 58a99a403b | |||
| dd8594b200 | |||
| 6df5368cb9 | |||
| 3a038a2cd2 | |||
| 9613e186a3 | |||
| 82b2362b2d | |||
| a6ee34602c | |||
| 05d29eabcd | |||
| f7e3534a79 | |||
| bd4972e466 | |||
| 0acf3beec2 | |||
| 081055be46 | |||
| c57ab7d090 | |||
| bd5a82dd86 | |||
| 942d409d72 | |||
| a17d3017b5 | |||
| b79f605ce2 | |||
| ea1c9b80e9 | |||
| 1d86f54a8b | |||
| fc909ff3ad | |||
| 7f54037246 | |||
| ec7164004e | |||
| a6314a9f80 | |||
| 560d23d3f2 | |||
| 1207291950 | |||
| 44e7b0e0be | |||
| 8c48d1ce82 | |||
| 8432e9ed9f | |||
| 1b014199c9 | |||
| 7e0c5c6323 | |||
| 9ada0901a6 | |||
| d60d32a5d8 | |||
| 667c00aa5a | |||
| cf0ca05843 | |||
| 3bcabacef4 | |||
| b5801ea847 | |||
| 1bb90a2f03 | |||
| cf55aef52b | |||
| c0f7538fdb | |||
| 395ab9b7cd | |||
| 80ecabb618 | |||
| ff291cc4d1 | |||
| 4183835d2b | |||
| 0e24d8db47 | |||
| f7bde54869 | |||
| bf8f027a97 | |||
| 60625ea4bf | |||
| a9f6ae1943 | |||
| cd01d7051b | |||
| bd126bb841 | |||
| f1b3fb3cd6 | |||
| d48f06db0e | |||
| 5567b06ed4 | |||
| e87b11179f | |||
| a17fef9d4b | |||
| 4c49bf7894 | |||
| bcb0ded784 | |||
| 54ba25e7f5 | |||
| 84a7f401a2 | |||
| 6afdf19ce4 | |||
| 4aa1cc47f4 | |||
| 166473596a | |||
| feb21b67d1 | |||
| dae7ec184f | |||
| cd170cae99 | |||
| d47d0a2e46 | |||
| f0d59db203 | |||
| f34a4e566b | |||
| 9c1ef177d1 | |||
| e1c7e86b66 | |||
| 384071aaeb | |||
| 79d1074d47 | |||
| 348ca93dcb | |||
| e70b3c099f | |||
| 8c9858a14c | |||
| 6e56cb3416 | |||
| 8761ae1e53 | |||
| d32be27b50 | |||
| 11b33215a2 | |||
| e8a31f1380 | |||
| 7cbfb22f1f | |||
| 40e6aac22b | |||
| 33cc601a1b | |||
| d50b0c9c2f | |||
| 77f415be61 | |||
| 1aa784e521 | |||
| ef8258da2f | |||
| 79692200b0 | |||
| 966912ccc5 | |||
| 1513dcad5c | |||
| 41050ac835 | |||
| fb8625756f | |||
| 886bab29f7 | |||
| 5bdf538d35 | |||
| 7d5040e283 | |||
| a49ed9a90e | |||
| 7a4e732f3b | |||
| 653ce82255 | |||
| 0b1df81945 | |||
| f65993c5b6 | |||
| df91b3d8aa | |||
| 4b58ab0ad9 | |||
| 9cbdfd2152 | |||
| 4a0a0088fe | |||
| dc37384ffd | |||
| 2a21e6c348 | |||
| 30011a5285 | |||
| 965b28e009 | |||
| 1b37007003 | |||
| 5cf0bcd399 | |||
| 0930d61dad | |||
| a181977ce9 | |||
| 3ab19f2f29 | |||
| 4b6f672e56 | |||
| c4a3830838 | |||
| d546c27462 | |||
| 7664e81aa2 | |||
| aa2ce7c770 | |||
| f63fc1ed74 | |||
| ac183f4e4b | |||
| 4f3c337cc7 | |||
| 1e24769061 | |||
| 02134eb822 | |||
| b436da841a | |||
| fa95e56b76 | |||
| 2a20853b92 | |||
| 09ad6301e7 | |||
| d8fa29e979 | |||
| ab91cc2def | |||
| cd4e2e29a3 | |||
| 0f454e897b | |||
| c1d53811f3 | |||
| dbe6269262 | |||
| 0c395c5bfa | |||
| 6760f38663 | |||
| b1cc8e5abf | |||
| 89d274dfc0 | |||
| dde776bde7 | |||
| edd2c796e1 | |||
| 07c64b9361 | |||
| 48b479b675 | |||
| d39a82d13f | |||
| 08844ff8d3 | |||
| 5f0a844df3 | |||
| 2ea58fe795 | |||
| e902fb3fc9 | |||
| c7be93e850 | |||
| fe8207fb1b | |||
| d1fbe96d6f | |||
| ccc89e6263 | |||
| d235de6ca2 | |||
| 4d98e07eb4 | |||
| 7c3f69a5bf | |||
| 846bb1109a | |||
| 8c5ba49068 | |||
| 8e18139ae9 | |||
| 410d42ccb3 | |||
| 2c35ba595c | |||
| 8f04afbff9 | |||
| 1fd5355290 | |||
| 65b6c48391 | |||
| 34c0960cc2 | |||
| d6175e8269 | |||
| c86823033d | |||
| 6c157b4e1f | |||
| 97b8d63547 | |||
| f0536be401 | |||
| 2c983ce39c | |||
| a3eb8ebb2d | |||
| bb98cb983e | |||
| ed5a0c03c8 | |||
| a67f7465c1 | |||
| 9b8ef3981a | |||
| 24a22eec82 | |||
| 405a2b2a5f | |||
| 17260b0272 | |||
| 2785baf7be | |||
| c4bc6b8d19 | |||
| a954d88d77 | |||
| d301a8f535 | |||
| 8ac14dc725 | |||
| 7d4ac28bd2 | |||
| f8ea0150fc | |||
| 0dc6dcbb34 | |||
| dbb48c82bf | |||
| 2a1e4da930 | |||
| dd481bcc2e | |||
| f68d1a4e2d | |||
| 9d56baba41 | |||
| 00041da2df | |||
| 1d251fef70 | |||
| 16a0cf0fd5 | |||
| 06c3729d51 | |||
| 3c7c352bfc | |||
| 66e6375942 | |||
| 8d3f66c2cb | |||
| 1fd540c231 | |||
| ac799dd00e | |||
| 699406b044 | |||
| 2f3333e87c | |||
| 6af4faef1f | |||
| 593f3ae353 | |||
| 3e0811fdb7 | |||
| 7f31e39ee3 | |||
| ed5ed001b9 | |||
| f8f14f3968 | |||
| dc41117078 | |||
| c339dbc8ae |
+18
-3
@@ -54,24 +54,23 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/FffGcodeWriter.cpp
|
||||
src/FffPolygonGenerator.cpp
|
||||
src/FffProcessor.cpp
|
||||
src/FuzzyWalls.cpp
|
||||
src/gcodeExport.cpp
|
||||
src/GCodePathConfig.cpp
|
||||
src/gcodePlanner.cpp
|
||||
src/infill.cpp
|
||||
src/WallsComputation.cpp
|
||||
src/layerPart.cpp
|
||||
src/LayerPlanBuffer.cpp
|
||||
src/MergeInfillLines.cpp
|
||||
src/mesh.cpp
|
||||
src/MeshGroup.cpp
|
||||
src/multiVolumes.cpp
|
||||
src/pathOrderOptimizer.cpp
|
||||
src/Preheat.cpp
|
||||
src/PrimeTower.cpp
|
||||
src/raft.cpp
|
||||
src/skin.cpp
|
||||
src/SkirtBrim.cpp
|
||||
src/sliceDataStorage.cpp
|
||||
src/slicer.cpp
|
||||
src/support.cpp
|
||||
src/timeEstimate.cpp
|
||||
src/WallsComputation.cpp
|
||||
@@ -84,9 +83,25 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/infill/ZigzagConnectorProcessorDisconnectedEndPieces.cpp
|
||||
src/infill/ZigzagConnectorProcessorEndPieces.cpp
|
||||
src/infill/ZigzagConnectorProcessorNoEndPieces.cpp
|
||||
src/infill/SubDivCube.cpp
|
||||
|
||||
src/slicer/LayerPart.cpp
|
||||
src/slicer/MultiVolumes.cpp
|
||||
src/slicer/SlicerLayer.cpp
|
||||
src/slicer/Slicer.cpp
|
||||
|
||||
src/textureProcessing/FaceNormalStorage.cpp
|
||||
src/textureProcessing/Material.cpp
|
||||
src/textureProcessing/MaterialBase.cpp
|
||||
src/textureProcessing/TexturedMesh.cpp
|
||||
src/textureProcessing/TextureBumpMapProcessor.cpp
|
||||
src/textureProcessing/TextureProximityProcessor.cpp
|
||||
|
||||
src/pathPlanning/Comb.cpp
|
||||
src/pathPlanning/GCodePath.cpp
|
||||
src/pathPlanning/LinePolygonsCrossings.cpp
|
||||
src/pathPlanning/NozzleTempInsert.cpp
|
||||
src/pathPlanning/TimeMaterialEstimates.cpp
|
||||
|
||||
src/progress/Progress.cpp
|
||||
src/progress/ProgressStageEstimator.cpp
|
||||
|
||||
+1
-1
@@ -832,7 +832,7 @@ EXAMPLE_RECURSIVE = NO
|
||||
# that contain images that are to be included in the documentation (see the
|
||||
# \image command).
|
||||
|
||||
IMAGE_PATH = documentation/assets
|
||||
IMAGE_PATH = docs/assets
|
||||
|
||||
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
||||
# invoke to filter for each input file. Doxygen will invoke the filter program
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 18 KiB Depois Largura: | Altura: | Tamanho: 18 KiB |
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 70 KiB |
|
Antes Largura: | Altura: | Tamanho: 20 KiB Depois Largura: | Altura: | Tamanho: 20 KiB |
@@ -1 +0,0 @@
|
||||
html/index.html
|
||||
@@ -44,7 +44,7 @@
|
||||
//#define use_xyz
|
||||
|
||||
//use_lines: Enables line clipping. Adds a very minor cost to performance.
|
||||
//#define use_lines
|
||||
#define use_lines
|
||||
|
||||
//use_deprecated: Enables temporary support for the obsolete functions
|
||||
//#define use_deprecated
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
https://github.com/nothings/stb
|
||||
|
||||
Thanks to Sean T. Barrett
|
||||
|
||||
license: public domain
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Command line setting defaults CuraEngine",
|
||||
"metadata":
|
||||
{
|
||||
"author": "Ultimaker B.V."
|
||||
},
|
||||
"settings": {
|
||||
"command_line_settings": {
|
||||
"label": "Command Line Settings",
|
||||
"type": "category",
|
||||
"children": {
|
||||
"center_object": {
|
||||
"description": "Whether to center the object on the middle of the build platform (0,0), instead of using the coordinate system in which the object was saved.",
|
||||
"type": "bool",
|
||||
"label": "Center object",
|
||||
"default_value": true
|
||||
},
|
||||
"machine_print_temp_wait": {
|
||||
"description": "Whether to wait for the nozzle temperature to be reached when preheating the nozzles at the start of the gcode.",
|
||||
"type": "bool",
|
||||
"label": "Machine print temp wait",
|
||||
"default_value": true
|
||||
},
|
||||
"mesh_position_x": {
|
||||
"description": "Offset applied to the object in the x direction.",
|
||||
"type": "float",
|
||||
"label": "Mesh position x",
|
||||
"default_value": 0
|
||||
},
|
||||
"mesh_position_y": {
|
||||
"description": "Offset applied to the object in the y direction.",
|
||||
"type": "float",
|
||||
"label": "Mesh position y",
|
||||
"default_value": 0
|
||||
},
|
||||
"mesh_position_z": {
|
||||
"description": "Offset applied to the object in the z direction. With this you can perform what was used to call 'Object Sink'.",
|
||||
"type": "float",
|
||||
"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",
|
||||
"label": "Prime tower direction outward",
|
||||
"default_value": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
#ifndef CONICAL_OVERHANG_H
|
||||
#define CONICAL_OVERHANG_H
|
||||
|
||||
#include "slicer.h"
|
||||
#include "slicer/Slicer.h"
|
||||
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -11,6 +11,7 @@ struct FanSpeedLayerTimeSettings
|
||||
public:
|
||||
double cool_min_layer_time;
|
||||
double cool_min_layer_time_fan_speed_max;
|
||||
double cool_fan_speed_0;
|
||||
double cool_fan_speed_min;
|
||||
double cool_fan_speed_max;
|
||||
double cool_min_speed;
|
||||
|
||||
+453
-199
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+86
-25
@@ -37,7 +37,7 @@ class FffGcodeWriter : public SettingsMessenger, NoCopy
|
||||
{
|
||||
friend class FffProcessor; // cause WireFrame2Gcode uses the member [gcode] (TODO)
|
||||
private:
|
||||
int max_object_height; //!< The maximal height of all previously sliced meshgroups, used to avoid collision when moving to the next meshgroup to print.
|
||||
coord_t max_object_height; //!< The maximal height of all previously sliced meshgroups, used to avoid collision when moving to the next meshgroup to print.
|
||||
|
||||
/*
|
||||
* Buffer for all layer plans (of type GCodePlanner)
|
||||
@@ -65,6 +65,13 @@ private:
|
||||
*/
|
||||
bool skirt_brim_is_processed[MAX_EXTRUDERS];
|
||||
|
||||
std::vector<std::vector<unsigned int>> mesh_order_per_extruder; //!< For each extruder, the cyclic order of the meshes (the first element is not the starting element per se)
|
||||
|
||||
/*!
|
||||
* For each extruder whether priming has already been planned
|
||||
*/
|
||||
bool extruder_prime_is_planned[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.
|
||||
|
||||
Point last_position_planned; //!< The position of the head before planning the next layer
|
||||
@@ -73,12 +80,13 @@ private:
|
||||
public:
|
||||
FffGcodeWriter(SettingsBase* settings_)
|
||||
: SettingsMessenger(settings_)
|
||||
, max_object_height(0)
|
||||
, layer_plan_buffer(this, gcode)
|
||||
, extruder_prime_is_planned {} // initialize all values in array with [false]
|
||||
, last_position_planned(no_point)
|
||||
, current_extruder_planned(0) // changed somewhere early in FffGcodeWriter::writeGCode
|
||||
, is_inside_mesh_layer_part(false)
|
||||
{
|
||||
max_object_height = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -175,15 +183,26 @@ private:
|
||||
* \param[out] storage The data storage to which to save the configurations.
|
||||
*/
|
||||
void initConfigs(SliceDataStorage& storage);
|
||||
|
||||
|
||||
/*!
|
||||
* Get the extruder with which to start the print.
|
||||
*
|
||||
* Generally this is the adhesion_extruder_nr, but in case the platform adhesion type is none,
|
||||
* the extruder with lowest number which is used on the first layer is used as initial extruder.
|
||||
*
|
||||
* \param[in] storage where to get settings from.
|
||||
*/
|
||||
unsigned int getStartExtruder(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Set temperatures and perform initial priming.
|
||||
*
|
||||
* Write a stub header if CuraEngine is in command line tool mode. (Cause writing the header afterwards would entail moving all gcode down.)
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param[in] start_extruder_nr The extruder with which to start the print.
|
||||
*/
|
||||
void processStartingCode(SliceDataStorage& storage);
|
||||
void processStartingCode(SliceDataStorage& storage, const unsigned int start_extruder_nr);
|
||||
|
||||
/*!
|
||||
* Move up and over the already printed meshgroups to print the next meshgroup.
|
||||
@@ -211,7 +230,22 @@ private:
|
||||
* \param total_layers The total number of layers.
|
||||
*/
|
||||
void processLayer(SliceDataStorage& storage, int layer_nr, unsigned int total_layers);
|
||||
|
||||
|
||||
/*!
|
||||
* Whether the extruders need to be primed separately just before they are used.
|
||||
*
|
||||
* \return whether the extruders need to be primed separately just before they are used
|
||||
*/
|
||||
bool getExtrudersNeedPrimeDuringFirstLayer();
|
||||
|
||||
/*!
|
||||
* Plan priming of all used extruders which haven't been primed yet
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param layer_plan The initial planning of the g-code of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*/
|
||||
void ensureAllExtrudersArePrimed(SliceDataStorage& storage, GCodePlanner& layer_plan, const int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add the skirt or the brim to the layer plan \p gcodeLayer.
|
||||
*
|
||||
@@ -239,16 +273,25 @@ private:
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr);
|
||||
|
||||
|
||||
/*!
|
||||
* Calculate in which order to print the meshes.
|
||||
* Calculate in which order to plan the extruders
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param current_extruder The current extruder with which we last printed
|
||||
* \return A vector of mesh indices ordered on print order.
|
||||
* \return A vector of pairs of extruder numbers coupled with the mesh indices ordered on print order for that extruder.
|
||||
*/
|
||||
std::vector<unsigned int> calculateMeshOrder(SliceDataStorage& storage, int current_extruder);
|
||||
|
||||
std::vector<int> calculateExtruderOrder(SliceDataStorage& storage, int current_extruder);
|
||||
|
||||
/*!
|
||||
* Calculate in which order to plan the meshes of a specific extruder
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param extruder_nr The extruder for which to determine the order
|
||||
* \return A vector of pairs of extruder numbers coupled with the mesh indices ordered on print order for that extruder.
|
||||
*/
|
||||
std::vector<unsigned int> calculateMeshOrder(SliceDataStorage& storage, int extruder_nr);
|
||||
|
||||
/*!
|
||||
* Add a single layer from a single mesh-volume to the layer plan \p gcodeLayer in mesh surface mode.
|
||||
*
|
||||
@@ -272,15 +315,27 @@ private:
|
||||
void addMeshOpenPolyLinesToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcode_layer, int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add a single layer from a single mesh-volume to the layer plan \p gcodeLayer.
|
||||
* Add a single layer from a single mesh-volume to the layer plan \p gcode_layer.
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param mesh The mesh to add to the layer plan \p gcodeLayer.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh to add to the layer plan \p gcode_layer.
|
||||
* \param gcode_layer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*
|
||||
*/
|
||||
void addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
void addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcode_layer, int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add a single part from a given layer of a mesh-volume to the layer plan \p gcode_layer.
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param mesh The mesh to add to the layer plan \p gcode_layer.
|
||||
* \param part The part to add
|
||||
* \param gcode_layer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*
|
||||
*/
|
||||
void addMeshPartToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, SliceLayerPart& part, GCodePlanner& gcode_layer, int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add thicker (multiple layers) sparse infill for a given part in a layer plan.
|
||||
@@ -314,44 +369,50 @@ private:
|
||||
* \param part The part for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param z_seam_type dir3ective for where to start the outer paerimeter of a part
|
||||
* \param z_seam_pos The location near where to start the outer inset in case \p z_seam_type is 'back'
|
||||
*/
|
||||
void processInsets(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, EZSeamType z_seam_type);
|
||||
void processInsets(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, EZSeamType z_seam_type, Point z_seam_pos);
|
||||
|
||||
|
||||
/*!
|
||||
* Add the gcode of the top/bottom skin of the given part.
|
||||
* Add the gcode of the top/bottom skin of the given part and of the perimeter gaps.
|
||||
*
|
||||
* Perimter gaps are generated for skin outlines and printed while the skin fill of the skin part is printed.
|
||||
* Perimeter gaps between the walls are added to the gcode afterwards.
|
||||
*
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh for which to add to the layer plan \p gcodeLayer.
|
||||
* \param part The part for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param skin_overlap The distance by which the skin overlaps with the wall insets.
|
||||
* \param skin_overlap The distance by which the skin overlaps with the wall insets and the distance by which the perimeter gaps overlap with adjacent print features.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
*/
|
||||
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int infill_angle);
|
||||
|
||||
void processSkinAndPerimeterGaps(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int infill_angle);
|
||||
|
||||
/*!
|
||||
* Add the support to the layer plan \p gcodeLayer of the current layer.
|
||||
* Add the support to the layer plan \p gcodeLayer of the current layer for all support parts with the given \p extruder_nr.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \param extruder_nr_before The extruder number at the start of the layer (before other print parts aka the rest)
|
||||
* \param before_rest Whether the function has been called before adding the rest to the layer plan \p gcodeLayer, or after.
|
||||
* \return whether any support was added to the layer plan
|
||||
*/
|
||||
void addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int extruder_nr_before, bool before_rest);
|
||||
bool addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int extruder_nr);
|
||||
/*!
|
||||
* Add the support lines/walls to the layer plan \p gcodeLayer of the current layer.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \return whether any support infill was added to the layer plan
|
||||
*/
|
||||
void addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
bool addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
/*!
|
||||
* Add the support skins to the layer plan \p gcodeLayer of the current layer.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \return whether any support skin was added to the layer plan
|
||||
*/
|
||||
void addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
bool addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
|
||||
/*!
|
||||
* Change to a new extruder, and add the prime tower instructions if the new extruder is different from the last.
|
||||
|
||||
+142
-131
@@ -2,16 +2,19 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <map> // multimap (ordered map allowing duplicate keys)
|
||||
#include <functional> // function
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "slicer/Slicer.h"
|
||||
#include "utils/algorithm.h"
|
||||
#include "slicer.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "support.h"
|
||||
#include "multiVolumes.h"
|
||||
#include "layerPart.h"
|
||||
#include "slicer/MultiVolumes.h"
|
||||
#include "slicer/LayerPart.h"
|
||||
#include "textureProcessing/TextureBumpMapProcessor.h"
|
||||
#include "textureProcessing/TextureProximityProcessor.h"
|
||||
#include "WallsComputation.h"
|
||||
#include "SkirtBrim.h"
|
||||
#include "skin.h"
|
||||
@@ -53,7 +56,7 @@ unsigned int FffPolygonGenerator::getDraftShieldLayerCount(const unsigned int to
|
||||
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);
|
||||
return std::max((coord_t)0, (getSettingInMicrons("draft_shield_height") - getSettingInMicrons("layer_height_0")) / getSettingInMicrons("layer_height") + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +88,28 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
return true; //This is NOT an error state!
|
||||
}
|
||||
|
||||
|
||||
storage.meshes.reserve(meshgroup->meshes.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated.
|
||||
for(unsigned int meshIdx=0; meshIdx < meshgroup->meshes.size(); meshIdx++)
|
||||
{
|
||||
// always make a new SliceMeshStorage, so that they have the same ordering / indexing as meshgroup.meshes
|
||||
// even make a mesh for a support mesh, which doesn't introduce any parts.
|
||||
storage.meshes.emplace_back(meshgroup->meshes[meshIdx], slice_layer_count); // new mesh in storage had settings from the Mes
|
||||
}
|
||||
// ^ needs to be set already for fuzzy wall texture map processing
|
||||
|
||||
std::vector<Slicer*> slicerList;
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < meshgroup->meshes.size(); mesh_idx++)
|
||||
{
|
||||
Mesh& mesh = meshgroup->meshes[mesh_idx];
|
||||
Slicer* slicer = new Slicer(&mesh, initial_slice_z, layer_thickness, slice_layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
|
||||
Mesh& mesh = *meshgroup->meshes[mesh_idx];
|
||||
if (mesh.getSettingBoolean("fuzz_map_enabled"))
|
||||
{
|
||||
TextureProximityProcessor::Settings texture_proximity_processor_settings(mesh.getSettingInMicrons("wall_line_width_0"));
|
||||
storage.meshes[mesh_idx].texture_proximity_processor = new TextureProximityProcessor(texture_proximity_processor_settings, slice_layer_count);
|
||||
}
|
||||
bool keep_open_polylines = mesh.getSettingBoolean("meshfix_keep_open_polygons");
|
||||
bool extensive_stitching = mesh.getSettingBoolean("meshfix_extensive_stitching");
|
||||
Slicer* slicer = new Slicer(&mesh, initial_slice_z, layer_thickness, slice_layer_count, keep_open_polylines, extensive_stitching, storage.meshes[mesh_idx].texture_proximity_processor);
|
||||
slicerList.push_back(slicer);
|
||||
/*
|
||||
for(SlicerLayer& layer : slicer->layers)
|
||||
@@ -107,8 +127,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
|
||||
for(unsigned int meshIdx=0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
|
||||
if (mesh.getSettingBoolean("conical_overhang_enabled"))
|
||||
Mesh& mesh = *storage.meshgroup->meshes[meshIdx];
|
||||
if (mesh.getSettingBoolean("conical_overhang_enabled") && !mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
ConicalOverhang::apply(slicerList[meshIdx], mesh.getSettingInAngleRadians("conical_overhang_angle"), layer_thickness);
|
||||
}
|
||||
@@ -116,19 +136,52 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
|
||||
|
||||
|
||||
if (storage.getSettingBoolean("carve_multiple_volumes"))
|
||||
{
|
||||
carveMultipleVolumes(slicerList);
|
||||
carveMultipleVolumes(slicerList, storage.getSettingBoolean("alternate_carve_order"));
|
||||
}
|
||||
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.
|
||||
storage.print_layer_count = 0;
|
||||
for (unsigned int meshIdx = 0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
Mesh& mesh = *storage.meshgroup->meshes[meshIdx];
|
||||
Slicer* slicer = slicerList[meshIdx];
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh") && !mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
storage.print_layer_count = std::max(storage.print_layer_count, (unsigned int)slicer->layers.size());
|
||||
}
|
||||
}
|
||||
storage.support.supportLayers.resize(storage.print_layer_count);
|
||||
|
||||
for (unsigned int meshIdx = 0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
Slicer* slicer = slicerList[meshIdx];
|
||||
storage.meshes.emplace_back(&meshgroup->meshes[meshIdx], slicer->layers.size()); // new mesh in storage had settings from the Mesh
|
||||
SliceMeshStorage& meshStorage = storage.meshes.back();
|
||||
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
|
||||
Mesh& mesh = *storage.meshgroup->meshes[meshIdx];
|
||||
|
||||
SliceMeshStorage& meshStorage = storage.meshes[meshIdx];
|
||||
|
||||
if (mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
for (unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
|
||||
SlicerLayer& slicer_layer = slicer->layers[layer_nr];
|
||||
support_layer.anti_overhang = support_layer.anti_overhang.unionPolygons(slicer_layer.polygons);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (mesh.getSettingBoolean("support_mesh"))
|
||||
{
|
||||
for (unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
|
||||
SlicerLayer& slicer_layer = slicer->layers[layer_nr];
|
||||
support_layer.support_mesh.add(slicer_layer.polygons);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
createLayerParts(meshStorage, slicer, mesh.getSettingBoolean("meshfix_union_all"), mesh.getSettingBoolean("meshfix_union_all_remove_holes"));
|
||||
delete slicerList[meshIdx];
|
||||
@@ -172,7 +225,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
unsigned int slice_layer_count = 0;
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (!mesh.getSettingBoolean("infill_mesh"))
|
||||
if (!mesh.getSettingBoolean("infill_mesh") && !mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
slice_layer_count = std::max<unsigned int>(slice_layer_count, mesh.layers.size());
|
||||
}
|
||||
@@ -201,11 +254,10 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
}
|
||||
for (unsigned int mesh_order_idx(0); mesh_order_idx < mesh_order.size(); ++mesh_order_idx)
|
||||
{
|
||||
processBasicWallsSkinInfill(storage, mesh_order_idx, mesh_order, slice_layer_count, inset_skin_progress_estimate);
|
||||
processBasicWallsSkinInfill(storage, mesh_order_idx, mesh_order, inset_skin_progress_estimate);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, mesh_order_idx + 1, storage.meshes.size());
|
||||
}
|
||||
|
||||
unsigned int print_layer_count = 0;
|
||||
for (unsigned int layer_nr = 0; layer_nr < slice_layer_count; layer_nr++)
|
||||
{
|
||||
SliceLayer* layer = nullptr;
|
||||
@@ -215,7 +267,6 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
if (int(layer_nr) <= mesh.layer_nr_max_filled_layer)
|
||||
{
|
||||
layer = &mesh.layers[layer_nr];
|
||||
print_layer_count = layer_nr + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -228,21 +279,21 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
}
|
||||
}
|
||||
|
||||
log("Layer count: %i\n", print_layer_count);
|
||||
log("Layer count: %i\n", storage.print_layer_count);
|
||||
|
||||
//layerparts2HTML(storage, "output/output.html");
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
|
||||
|
||||
AreaSupport::generateSupportAreas(storage, print_layer_count);
|
||||
AreaSupport::generateSupportAreas(storage, storage.print_layer_count);
|
||||
|
||||
// we need to remove empty layers after we have procesed the insets
|
||||
// processInsets might throw away parts if they have no wall at all (cause it doesn't fit)
|
||||
// brim depends on the first layer not being empty
|
||||
// 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)
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), storage.print_layer_count); // changes storage.print_layer_count!
|
||||
if (storage.print_layer_count == 0)
|
||||
{
|
||||
log("Stopping process because there are no non-empty layers.\n");
|
||||
return;
|
||||
@@ -251,7 +302,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
/*
|
||||
if (storage.support.generated)
|
||||
{
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
for (unsigned int layer_idx = 0; layer_idx < storage.print_layer_count; layer_idx++)
|
||||
{
|
||||
Polygons& support = storage.support.supportLayers[layer_idx].supportAreas;
|
||||
ExtruderTrain* infill_extr = storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("support_infill_extruder_nr"));
|
||||
@@ -263,13 +314,14 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
computePrintHeightStatistics(storage);
|
||||
|
||||
// handle helpers
|
||||
storage.primeTower.generatePaths(storage, print_layer_count);
|
||||
storage.primeTower.generatePaths(storage);
|
||||
storage.primeTower.subtractFromSupport(storage);
|
||||
|
||||
logDebug("Processing ooze shield\n");
|
||||
processOozeShield(storage);
|
||||
|
||||
logDebug("Processing draft shield\n");
|
||||
processDraftShield(storage, print_layer_count);
|
||||
processDraftShield(storage);
|
||||
|
||||
logDebug("Processing platform adhesion\n");
|
||||
processPlatformAdhesion(storage);
|
||||
@@ -277,17 +329,18 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
// meshes post processing
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
processDerivedWallsSkinInfill(mesh, print_layer_count);
|
||||
processDerivedWallsSkinInfill(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate)
|
||||
void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, ProgressStageEstimator& inset_skin_progress_estimate)
|
||||
{
|
||||
unsigned int mesh_idx = mesh_order[mesh_order_idx];
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
size_t mesh_layer_count = mesh.layers.size();
|
||||
if (mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
processInfillMesh(storage, mesh_order_idx, mesh_order, total_layers);
|
||||
processInfillMesh(storage, mesh_order_idx, mesh_order);
|
||||
}
|
||||
|
||||
// TODO: make progress more accurate!!
|
||||
@@ -297,20 +350,20 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
|
||||
inset_skin_progress_estimate.nextStage(mesh_inset_skin_progress_estimator); // the stage of this function call
|
||||
|
||||
ProgressEstimatorLinear* inset_estimator = new ProgressEstimatorLinear(total_layers);
|
||||
ProgressEstimatorLinear* inset_estimator = new ProgressEstimatorLinear(mesh_layer_count);
|
||||
mesh_inset_skin_progress_estimator->nextStage(inset_estimator);
|
||||
|
||||
|
||||
// walls
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
for (unsigned int layer_number = 0; layer_number < mesh.layers.size(); layer_number++)
|
||||
{
|
||||
logDebug("Processing insets for layer %i of %i\n", layer_number, total_layers);
|
||||
logDebug("Processing insets for layer %i of %i\n", layer_number, mesh_layer_count);
|
||||
processInsets(mesh, layer_number);
|
||||
double progress = inset_skin_progress_estimate.progress(layer_number);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100);
|
||||
}
|
||||
|
||||
ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(total_layers);
|
||||
ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(mesh_layer_count);
|
||||
mesh_inset_skin_progress_estimator->nextStage(skin_estimator);
|
||||
|
||||
bool process_infill = mesh.getSettingInMicrons("infill_line_distance") > 0;
|
||||
@@ -322,8 +375,8 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
SliceMeshStorage& other_mesh = storage.meshes[other_mesh_idx];
|
||||
if (other_mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
AABB3D aabb = storage.meshgroup->meshes[mesh_idx].getAABB();
|
||||
AABB3D other_aabb = storage.meshgroup->meshes[other_mesh_idx].getAABB();
|
||||
AABB3D aabb = storage.meshgroup->meshes[mesh_idx]->getAABB();
|
||||
AABB3D other_aabb = storage.meshgroup->meshes[other_mesh_idx]->getAABB();
|
||||
if (aabb.hit(other_aabb))
|
||||
{
|
||||
process_infill = true;
|
||||
@@ -338,9 +391,9 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
{
|
||||
mesh_max_bottom_layer_count = std::max(mesh_max_bottom_layer_count, mesh.getSettingAsCount("bottom_layers"));
|
||||
}
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
for (unsigned int layer_number = 0; layer_number < mesh.layers.size(); layer_number++)
|
||||
{
|
||||
logDebug("Processing skins and infill layer %i of %i\n", layer_number, total_layers);
|
||||
logDebug("Processing skins and infill layer %i of %i\n", layer_number, mesh_layer_count);
|
||||
if (!mesh.getSettingBoolean("magic_spiralize") || static_cast<int>(layer_number) < mesh_max_bottom_layer_count) //Only generate up/downskin and infill for the first X layers when spiralize is choosen.
|
||||
{
|
||||
processSkinsAndInfill(mesh, layer_number, process_infill);
|
||||
@@ -350,7 +403,7 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, size_t total_layers)
|
||||
void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order)
|
||||
{
|
||||
unsigned int mesh_idx = mesh_order[mesh_order_idx];
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
@@ -421,20 +474,20 @@ void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned
|
||||
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers)
|
||||
void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh)
|
||||
{
|
||||
// create gradual infill areas
|
||||
SkinInfillAreaComputation::generateGradualInfill(mesh, mesh.getSettingInMicrons("gradual_infill_step_height"), mesh.getSettingAsCount("gradual_infill_steps"));
|
||||
|
||||
// combine infill
|
||||
unsigned int combined_infill_layers = 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
|
||||
if (mesh.getSettingBoolean("magic_fuzzy_skin_enabled"))
|
||||
//SubDivCube Pre-compute Octree
|
||||
if (mesh.getSettingAsFillMethod("infill_pattern") == EFillMethod::CUBICSUBDIV)
|
||||
{
|
||||
processFuzzyWalls(mesh);
|
||||
SubDivCube::precomputeOctree(mesh);
|
||||
}
|
||||
|
||||
// combine infill
|
||||
unsigned int combined_infill_layers = std::max(1U, round_divide(mesh.getSettingInMicrons("infill_sparse_thickness"), std::max(getSettingInMicrons("layer_height"), (coord_t)1))); //How many infill layers to combine to obtain the requested sparse thickness.
|
||||
combineInfillLayers(mesh,combined_infill_layers);
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int layer_nr)
|
||||
@@ -443,12 +496,14 @@ void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int lay
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::SURFACE)
|
||||
{
|
||||
int inset_count = mesh.getSettingAsCount("wall_line_count");
|
||||
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
|
||||
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && ((layer_nr % 2) + 2) % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
|
||||
inset_count += 5;
|
||||
int line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
|
||||
int line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
|
||||
if (mesh.getSettingBoolean("alternate_extra_perimeter"))
|
||||
inset_count += layer_nr % 2;
|
||||
{
|
||||
inset_count += ((layer_nr % 2) + 2) % 2;
|
||||
}
|
||||
bool recompute_outline_based_on_outer_wall = mesh.getSettingBoolean("support_enable");
|
||||
WallsComputation walls_computation(mesh.getSettingInMicrons("wall_0_inset"), line_width_0, line_width_x, inset_count, recompute_outline_based_on_outer_wall);
|
||||
walls_computation.generateInsets(layer);
|
||||
@@ -457,16 +512,19 @@ void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int lay
|
||||
|
||||
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++)
|
||||
{
|
||||
bool layer_is_empty = true;
|
||||
if (storage.support.generated && layer_idx < storage.support.supportLayers.size())
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_idx];
|
||||
if (support_layer.supportAreas.size() > 0 || support_layer.skin.size() > 0)
|
||||
{
|
||||
layer_is_empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
@@ -500,6 +558,9 @@ void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, cons
|
||||
mesh.layer_nr_max_filled_layer -= n_empty_first_layers;
|
||||
}
|
||||
total_layers -= n_empty_first_layers;
|
||||
storage.support.layer_nr_max_filled_layer -= n_empty_first_layers;
|
||||
std::vector<SupportLayer>& support_layers = storage.support.supportLayers;
|
||||
support_layers.erase(support_layers.begin(), support_layers.begin() + n_empty_first_layers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,26 +594,36 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage
|
||||
|
||||
std::vector<int>& max_print_height_per_extruder = storage.max_print_height_per_extruder;
|
||||
assert(max_print_height_per_extruder.size() == 0 && "storage.max_print_height_per_extruder shouldn't have been initialized yet!");
|
||||
max_print_height_per_extruder.resize(extruder_count, -1); // unitialize all as -1
|
||||
max_print_height_per_extruder.resize(extruder_count, -1); //Initialize all as -1.
|
||||
{ // compute max_object_height_per_extruder
|
||||
//Height of the meshes themselves.
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
unsigned int extr_nr = mesh.getSettingAsIndex("extruder_nr");
|
||||
max_print_height_per_extruder[extr_nr] =
|
||||
std::max( max_print_height_per_extruder[extr_nr]
|
||||
, mesh.layer_nr_max_filled_layer );
|
||||
if (mesh.getSettingBoolean("anti_overhang_mesh") || mesh.getSettingBoolean("support_mesh"))
|
||||
{
|
||||
continue; //Special type of mesh that doesn't get printed.
|
||||
}
|
||||
const unsigned int extr_nr = mesh.getSettingAsIndex("extruder_nr");
|
||||
max_print_height_per_extruder[extr_nr] = std::max(max_print_height_per_extruder[extr_nr], mesh.layer_nr_max_filled_layer);
|
||||
}
|
||||
int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); // TODO: support extruder should be configurable per object
|
||||
|
||||
//Height of where the support reaches.
|
||||
const unsigned int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); // TODO: support extruder should be configurable per object
|
||||
max_print_height_per_extruder[support_infill_extruder_nr] =
|
||||
std::max( max_print_height_per_extruder[support_infill_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
int support_skin_extruder_nr = storage.getSettingAsIndex("support_interface_extruder_nr"); // TODO: support skin extruder should be configurable per object
|
||||
std::max(max_print_height_per_extruder[support_infill_extruder_nr],
|
||||
storage.support.layer_nr_max_filled_layer);
|
||||
const unsigned int support_skin_extruder_nr = storage.getSettingAsIndex("support_interface_extruder_nr"); // TODO: support skin extruder should be configurable per object
|
||||
max_print_height_per_extruder[support_skin_extruder_nr] =
|
||||
std::max( max_print_height_per_extruder[support_skin_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
int adhesion_extruder_nr = storage.getSettingAsIndex("adhesion_extruder_nr");
|
||||
max_print_height_per_extruder[adhesion_extruder_nr] =
|
||||
std::max(0, max_print_height_per_extruder[support_skin_extruder_nr]);
|
||||
std::max(max_print_height_per_extruder[support_skin_extruder_nr],
|
||||
storage.support.layer_nr_max_filled_layer);
|
||||
|
||||
//Height of where the platform adhesion reaches.
|
||||
if (storage.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
const unsigned int adhesion_extruder_nr = storage.getSettingAsIndex("adhesion_extruder_nr");
|
||||
max_print_height_per_extruder[adhesion_extruder_nr] =
|
||||
std::max(0, max_print_height_per_extruder[adhesion_extruder_nr]);
|
||||
}
|
||||
}
|
||||
|
||||
storage.max_print_height_order = order(max_print_height_per_extruder);
|
||||
@@ -603,9 +674,9 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage)
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned int total_layers)
|
||||
void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage)
|
||||
{
|
||||
const unsigned int draft_shield_layers = getDraftShieldLayerCount(total_layers);
|
||||
const unsigned int draft_shield_layers = getDraftShieldLayerCount(storage.print_layer_count);
|
||||
if (draft_shield_layers <= 0)
|
||||
{
|
||||
return;
|
||||
@@ -615,7 +686,7 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned
|
||||
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 < draft_shield_layers; layer_nr += layer_skip)
|
||||
for (unsigned int layer_nr = 0; layer_nr < storage.print_layer_count && layer_nr < draft_shield_layers; layer_nr += layer_skip)
|
||||
{
|
||||
draft_shield = draft_shield.unionPolygons(storage.getLayerOutlines(layer_nr, true));
|
||||
}
|
||||
@@ -641,68 +712,8 @@ void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
|
||||
case EPlatformAdhesion::RAFT:
|
||||
Raft::generate(storage, train->getSettingInMicrons("raft_margin"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh)
|
||||
{
|
||||
if (mesh.getSettingAsCount("wall_line_count") == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int64_t fuzziness = mesh.getSettingInMicrons("magic_fuzzy_skin_thickness");
|
||||
int64_t avg_dist_between_points = mesh.getSettingInMicrons("magic_fuzzy_skin_point_dist");
|
||||
int64_t min_dist_between_points = avg_dist_between_points * 3 / 4; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
|
||||
int64_t range_random_point_dist = avg_dist_between_points / 2;
|
||||
for (unsigned int layer_nr = 0; layer_nr < mesh.layers.size(); layer_nr++)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
Polygons results;
|
||||
Polygons& skin = (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)? part.outline : part.insets[0];
|
||||
for (PolygonRef poly : skin)
|
||||
{
|
||||
// generate points in between p0 and p1
|
||||
PolygonRef result = results.newPoly();
|
||||
|
||||
int64_t dist_left_over = rand() % (min_dist_between_points / 2); // the distance to be traversed on the line before making the first new point
|
||||
Point* p0 = &poly.back();
|
||||
for (Point& p1 : poly)
|
||||
{ // 'a' is the (next) new point between p0 and p1
|
||||
Point p0p1 = p1 - *p0;
|
||||
int64_t p0p1_size = vSize(p0p1);
|
||||
int64_t dist_last_point = dist_left_over + p0p1_size * 2; // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
|
||||
for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + rand() % range_random_point_dist)
|
||||
{
|
||||
int r = rand() % (fuzziness * 2) - fuzziness;
|
||||
Point perp_to_p0p1 = turn90CCW(p0p1);
|
||||
Point fuzz = normal(perp_to_p0p1, r);
|
||||
Point pa = *p0 + normal(p0p1, p0pa_dist) + fuzz;
|
||||
result.add(pa);
|
||||
dist_last_point = p0pa_dist;
|
||||
}
|
||||
dist_left_over = p0p1_size - dist_last_point;
|
||||
|
||||
p0 = &p1;
|
||||
}
|
||||
while (result.size() < 3 )
|
||||
{
|
||||
unsigned int point_idx = poly.size() - 2;
|
||||
result.add(poly[point_idx]);
|
||||
if (point_idx == 0) { break; }
|
||||
point_idx--;
|
||||
}
|
||||
if (result.size() < 3)
|
||||
{
|
||||
result.clear();
|
||||
for (Point& p : poly)
|
||||
result.add(p);
|
||||
}
|
||||
}
|
||||
skin = results;
|
||||
}
|
||||
case EPlatformAdhesion::NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,10 +84,9 @@ private:
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh_order_idx The index of the mesh_idx in \p mesh_order to process in the vector of meshes in \p storage
|
||||
* \param mesh_order The order in which the meshes are processed (used for infill meshes)
|
||||
* \param total_layers The total number of layers over all objects
|
||||
* \param inset_skin_progress_estimate The progress stage estimate calculator
|
||||
*/
|
||||
void processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate);
|
||||
void processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, ProgressStageEstimator& inset_skin_progress_estimate);
|
||||
|
||||
/*!
|
||||
* Process the mesh to be an infill mesh: limit all outlines to within the infill of normal meshes and subtract their volume from the infill of those meshes
|
||||
@@ -95,18 +94,16 @@ private:
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh_order_idx The index of the mesh_idx in \p mesh_order to process in the vector of meshes in \p storage
|
||||
* \param mesh_order The order in which the meshes are processed
|
||||
* \param total_layers The total number of layers over all objects
|
||||
*/
|
||||
void processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, size_t total_layers);
|
||||
void processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order);
|
||||
|
||||
/*!
|
||||
* Process features which are derived from the basic walls, skin, and infill:
|
||||
* fuzzy skin, infill combine
|
||||
*
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param total_layers The total number of layers over all objects
|
||||
*/
|
||||
void processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers);
|
||||
void processDerivedWallsSkinInfill(SliceMeshStorage& mesh);
|
||||
|
||||
/*!
|
||||
* Remove all bottom layers which are empty.
|
||||
@@ -151,9 +148,8 @@ private:
|
||||
* Generate the polygons where the draft screen should be.
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, unsigned int total_layers);
|
||||
void processDraftShield(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Generate the skirt/brim/raft areas/insets.
|
||||
|
||||
@@ -38,7 +38,7 @@ std::string FffProcessor::getAllSettingsString(MeshGroup& meshgroup, bool first_
|
||||
}
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < meshgroup.meshes.size(); mesh_idx++)
|
||||
{
|
||||
Mesh& mesh = meshgroup.meshes[mesh_idx];
|
||||
Mesh& mesh = *meshgroup.meshes[mesh_idx];
|
||||
sstream << " -e" << mesh.getSettingAsIndex("extruder_nr") << " -l \"" << mesh_idx << "\"" << mesh.getAllLocalSettingsString();
|
||||
}
|
||||
sstream << "\n";
|
||||
@@ -58,9 +58,9 @@ bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
gcode_writer.setParent(meshgroup);
|
||||
|
||||
bool empty = true;
|
||||
for (Mesh& mesh : meshgroup->meshes)
|
||||
for (Mesh* mesh : meshgroup->meshes)
|
||||
{
|
||||
if (!mesh.getSettingBoolean("infill_mesh"))
|
||||
if (!mesh->getSettingBoolean("infill_mesh") && !mesh->getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
empty = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#include "FuzzyWalls.h"
|
||||
|
||||
#define NORMAL_LENGTH 10000
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
FuzzyWalls::FuzzyWalls(const SliceMeshStorage& mesh)
|
||||
: settings(&mesh)
|
||||
{
|
||||
if (mesh.getSettingBoolean("fuzz_map_enabled"))
|
||||
{
|
||||
assert(mesh.texture_proximity_processor && "texture_proximity_processor should have been initialized");
|
||||
getAmplitude = [&mesh, this](const unsigned int layer_nr, const Point p)
|
||||
{
|
||||
assert(mesh.texture_proximity_processor && "When fuzz_map_enabled there has to be a texture proximity processor!");
|
||||
TextureProximityProcessor& texture_proximity_processor = *mesh.texture_proximity_processor;
|
||||
float color = texture_proximity_processor.getColor(p, layer_nr, settings.color_usage, 0.0); // TODO change default 0.0
|
||||
coord_t ret = color * settings.max_amplitude;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
getAmplitude = [this](const unsigned int layer_nr, const Point p)
|
||||
{
|
||||
return settings.max_amplitude;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Polygons FuzzyWalls::makeFuzzy(const SliceMeshStorage& mesh, const unsigned int layer_nr, const Polygons& in)
|
||||
{
|
||||
Polygons results;
|
||||
if (in.size() == 0)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
flows.reserve(in.size());
|
||||
for (const PolygonRef poly : const_cast<Polygons&>(in))
|
||||
{
|
||||
assert(poly.size() >= 3);
|
||||
// generate points in between p0 and p1
|
||||
PolygonRef result = results.newPoly();
|
||||
flows.emplace_back(); // keep flows aligned with the result
|
||||
flows.back().reserve(poly.size());
|
||||
|
||||
Point p0 = poly[poly.size() - 2];
|
||||
Point p1 = poly.back();
|
||||
for (int p0_idx = poly.size() - 2; p0_idx >= 0; p0_idx--)
|
||||
{ // p0 is the last point before p1 which is different from p1
|
||||
p0 = poly[p0_idx];
|
||||
}
|
||||
CarryOver carry_over;
|
||||
carry_over.dist_left_over = (settings.min_dist_between_points + rand() % settings.range_random_point_dist) / 2;
|
||||
carry_over.step_size = carry_over.dist_left_over;
|
||||
carry_over.offset_random = 0.0; // unused in the first iteration since carry_over.step_size = carry_over.dist_left_over; see makeCornerFuzzy
|
||||
carry_over.next_offset_random = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0 - 1.0;
|
||||
carry_over.p0p1_perp = turn90CCW(p1 - p0);
|
||||
// 'x' is the previous location from where a randomly offsetted new point between p-1 and p0 was created
|
||||
for (Point p2 : poly)
|
||||
{
|
||||
if (p2 == p1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
makeCornerFuzzy(layer_nr, p0, p1, p2, carry_over, result);
|
||||
makeSegmentFuzzy(layer_nr, p1, p2, result, carry_over);
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
}
|
||||
while (result.size() < 3 )
|
||||
{
|
||||
unsigned int point_idx = poly.size() - 2;
|
||||
result.add(poly[point_idx]);
|
||||
flows.back().push_back(1.0);
|
||||
if (point_idx == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
point_idx--;
|
||||
}
|
||||
if (result.size() > 0)
|
||||
{ // compute flow of the newly introduced segment
|
||||
const Point p0 = result.back();
|
||||
const Point p1 = result.back();
|
||||
const coord_t length = vSize(p1 - p0);
|
||||
const coord_t pxpa_dist = carry_over.step_size - carry_over.dist_left_over;
|
||||
const float flow_here = (length < 10 || std::abs(length - pxpa_dist) < 5)? 1.0 : std::min(1.0, INT2MM(pxpa_dist) / INT2MM(length));
|
||||
flows.back().push_back(flow_here);
|
||||
}
|
||||
if (result.size() < 3)
|
||||
{
|
||||
result.clear();
|
||||
flows.back().clear();
|
||||
for (const Point& p : poly)
|
||||
{
|
||||
result.add(p);
|
||||
flows.back().push_back(1.0);
|
||||
}
|
||||
}
|
||||
assert(result.size() == flows.back().size());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
void FuzzyWalls::makeCornerFuzzy(const unsigned int layer_nr, const Point p0, const Point p1, const Point p2, const CarryOver carry_over, PolygonRef result)
|
||||
{
|
||||
const Point p0p1_perp = carry_over.p0p1_perp;
|
||||
const Point p1p2 = p2 - p1;
|
||||
const Point p1p2_perp = turn90CCW(p1p2);
|
||||
const Point corner_normal = normal(p0p1_perp, NORMAL_LENGTH) + normal(p1p2_perp, NORMAL_LENGTH);
|
||||
|
||||
// x is the last point which was offsetted
|
||||
// a is the next point to be offsetted
|
||||
//
|
||||
// step_size
|
||||
// ^^^^^^^^^^^^^^^^^^^^
|
||||
// p1pa_dist
|
||||
// pxp1_dist ^^^^^^^^^
|
||||
// ^^^^^^^^^^
|
||||
// ┬ > amplitudes
|
||||
// |
|
||||
// |
|
||||
// ┬ |
|
||||
// ┥ | > previous random offset within amplitude
|
||||
// | ┥pr ┬ > corner offset computed by weighted average based on pxp0_dist, p0pa_dist and the amplitudes
|
||||
// | | |
|
||||
// -------x---------p1--------a-------
|
||||
// | | ┥ > next random offset within amplitude
|
||||
// | | ┴
|
||||
// | |
|
||||
// ┴ |
|
||||
// |
|
||||
// |
|
||||
// ┴
|
||||
//
|
||||
// assuming all amplitudes are the same and x, p1, a are on a straight line, pr will also be on a straight line between the previous and next offsetted points
|
||||
|
||||
const coord_t corner_amplitude = getAmplitude(layer_nr, p1);
|
||||
// randFloat = offset / amplitude
|
||||
// offset weighted by relative amplitudes and distance to p0
|
||||
assert(carry_over.step_size > 0);
|
||||
const coord_t pxp1_dist = (carry_over.step_size - carry_over.dist_left_over);
|
||||
assert(pxp1_dist >= 0);
|
||||
const coord_t p1pa_dist = carry_over.dist_left_over;
|
||||
const coord_t offset_contribution_0 = corner_amplitude * pxp1_dist * carry_over.offset_random;
|
||||
const coord_t offset_contribution_2 = corner_amplitude * p1pa_dist * carry_over.next_offset_random;
|
||||
const coord_t offset = (offset_contribution_0 + offset_contribution_2) / carry_over.step_size;
|
||||
|
||||
Point fuzz = normal(corner_normal, offset);
|
||||
Point pr = p1 + fuzz;
|
||||
if (result.size() > 0)
|
||||
{ // compute flow of the newly introduced segment
|
||||
const Point last = result.back();
|
||||
const coord_t length = vSize(last - pr);
|
||||
const float flow_here = (length < 10 || std::abs(length - pxp1_dist) < 5)? 1.0 : std::min(1.0, INT2MM(pxp1_dist) / INT2MM(length));
|
||||
// limit the flow to 1.0,
|
||||
// internal corners where the offset is negative could result in such a case,
|
||||
// but it is then better to not cause over extrusion there
|
||||
flows.back().push_back(flow_here);
|
||||
}
|
||||
result.add(pr);
|
||||
}
|
||||
|
||||
void FuzzyWalls::makeSegmentFuzzy(const unsigned int layer_nr, const Point p0, const Point p1, PolygonRef result, CarryOver& carry_over)
|
||||
{
|
||||
// 'a' is the (next) new point between p0 and p1, offsetted from the point
|
||||
// 'x', which is on the line segment p0p1
|
||||
const Point p0p1 = p1 - p0;
|
||||
carry_over.p0p1_perp = turn90CCW(p0p1);
|
||||
const int64_t p0p1_size = vSize(p0p1);
|
||||
coord_t dist_to_prev_point = carry_over.dist_left_over; // distance from the last introduced point to the newly introduced one
|
||||
int64_t dist_last_point = carry_over.dist_left_over - carry_over.step_size; // so that 'carry_over.step_size - (p0p1_size - dist_last_point)' evaulates to 'dist_left_over - p0p1_size'
|
||||
for (int64_t p0pa_dist = carry_over.dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += carry_over.step_size)
|
||||
{
|
||||
const Point px = p0 + normal(p0p1, p0pa_dist);
|
||||
coord_t amplitude = getAmplitude(layer_nr, px);
|
||||
if (amplitude == 0)
|
||||
{
|
||||
amplitude = 1;
|
||||
}
|
||||
carry_over.offset_random = carry_over.next_offset_random;
|
||||
carry_over.next_offset_random = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0 - 1.0;
|
||||
const coord_t offset = carry_over.offset_random * amplitude;
|
||||
Point fuzz = normal(carry_over.p0p1_perp, offset);
|
||||
Point pa = px + fuzz;
|
||||
if (result.size() > 0)
|
||||
{ // compute flow of the newly introduced segment
|
||||
const Point last = result.back();
|
||||
const coord_t length = vSize(last - pa);
|
||||
const float flow_here = (length < 10 || std::abs(length - dist_to_prev_point) < 5)? 1.0 : std::min(1.0, INT2MM(dist_to_prev_point) / INT2MM(length));
|
||||
flows.back().push_back(flow_here);
|
||||
}
|
||||
result.add(pa);
|
||||
dist_last_point = p0pa_dist;
|
||||
carry_over.step_size = settings.min_dist_between_points + rand() % settings.range_random_point_dist;
|
||||
dist_to_prev_point = carry_over.step_size;
|
||||
}
|
||||
carry_over.dist_left_over = carry_over.step_size - (p0p1_size - dist_last_point);
|
||||
assert(carry_over.dist_left_over >= 0);
|
||||
assert(carry_over.dist_left_over < carry_over.step_size);
|
||||
}
|
||||
|
||||
float FuzzyWalls::getFlow(const Polygons& from, unsigned int poly_idx, unsigned int from_point_idx, unsigned int to_point_idx)
|
||||
{
|
||||
assert(from.size() == flows.size());
|
||||
assert(poly_idx < flows.size());
|
||||
assert(from[poly_idx].size() == flows[poly_idx].size());
|
||||
assert((from_point_idx + 1) % flows[poly_idx].size() == to_point_idx);
|
||||
return flows[poly_idx][from_point_idx];
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,52 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef FUZZY_WALLS_H
|
||||
#define FUZZY_WALLS_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
#include "PolygonFlowAdjuster.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
class FuzzyWalls : public PolygonFlowAdjuster
|
||||
{
|
||||
public:
|
||||
struct Settings
|
||||
{
|
||||
coord_t max_amplitude;
|
||||
coord_t avg_dist_between_points;
|
||||
ColourUsage color_usage;
|
||||
coord_t min_dist_between_points;
|
||||
coord_t range_random_point_dist;
|
||||
Settings(const SettingsBaseVirtual* settings_base)
|
||||
: max_amplitude(settings_base->getSettingInMicrons("magic_fuzzy_skin_thickness"))
|
||||
, avg_dist_between_points(settings_base->getSettingInMicrons("magic_fuzzy_skin_point_dist"))
|
||||
, color_usage(settings_base->getSettingAsColourUsage("fuzz_map_texture_color"))
|
||||
, min_dist_between_points(avg_dist_between_points * 3 / 4) // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
|
||||
, range_random_point_dist(avg_dist_between_points / 2)
|
||||
{
|
||||
}
|
||||
};
|
||||
FuzzyWalls(const SliceMeshStorage& mesh);
|
||||
Polygons makeFuzzy(const SliceMeshStorage& mesh, const unsigned int layer_nr, const Polygons& in);
|
||||
float getFlow(const Polygons& from, unsigned int poly_idx, unsigned int from_point_idx, unsigned int to_point_idx);
|
||||
protected:
|
||||
struct CarryOver
|
||||
{
|
||||
coord_t dist_left_over;
|
||||
float offset_random; // [-1,1]
|
||||
float next_offset_random; // [-1,1]
|
||||
coord_t step_size;
|
||||
Point p0p1_perp;
|
||||
};
|
||||
Settings settings;
|
||||
std::function<coord_t (const unsigned int, const Point)> getAmplitude;
|
||||
|
||||
std::vector<std::vector<float>> flows; //!< The flow per segment per polygon in the input
|
||||
|
||||
void makeCornerFuzzy(const unsigned int layer_nr, const Point p0, const Point p1, const Point p2, const CarryOver carry_over, PolygonRef result);
|
||||
void makeSegmentFuzzy(const unsigned int layer_nr, const Point p0, const Point p1, PolygonRef result, CarryOver& carry_over);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//FUZZY_WALLS_H
|
||||
+191
-34
@@ -13,7 +13,7 @@ void LayerPlanBuffer::flush()
|
||||
{
|
||||
if (buffer.size() > 0)
|
||||
{
|
||||
insertPreheatCommands(); // insert preheat commands of the very last layer
|
||||
insertTempCommands(); // insert preheat commands of the very last layer
|
||||
}
|
||||
while (!buffer.empty())
|
||||
{
|
||||
@@ -38,41 +38,51 @@ void LayerPlanBuffer::insertPreheatCommand(ExtruderPlan& extruder_plan_before, d
|
||||
if (acc_time > time_after_extruder_plan_start)
|
||||
{
|
||||
const double time_before_path_end = acc_time - time_after_extruder_plan_start;
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, false, time_this_path - time_before_path_end);
|
||||
bool wait = false;
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, wait, time_this_path - time_before_path_end);
|
||||
return;
|
||||
}
|
||||
}
|
||||
extruder_plan_before.insertCommand(0, extruder, temp, false); // insert at start of extruder plan if time_after_extruder_plan_start > extruder_plan.time
|
||||
bool wait = false;
|
||||
unsigned int path_idx = 0;
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, wait); // insert at start of extruder plan if time_after_extruder_plan_start > extruder_plan.time
|
||||
}
|
||||
|
||||
Preheat::WarmUpResult LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
|
||||
double initial_print_temp = extruder_plan.initial_printing_temperature;
|
||||
|
||||
double in_between_time = 0.0;
|
||||
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{ // find a previous extruder plan where the same extruder is used to see what time this extruder wasn't used
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_before_idx];
|
||||
if (extruder_plan.extruder == extruder)
|
||||
ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
|
||||
if (extruder_plan_before.extruder == extruder)
|
||||
{
|
||||
Preheat::WarmUpResult warm_up = preheat_config.timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(in_between_time, extruder, required_temp);
|
||||
double temp_before = preheat_config.getFinalPrintTemp(extruder);
|
||||
if (temp_before == 0)
|
||||
{
|
||||
temp_before = extruder_plan_before.printing_temperature;
|
||||
}
|
||||
constexpr bool during_printing = false;
|
||||
Preheat::WarmUpResult warm_up = preheat_config.getWarmUpPointAfterCoolDown(in_between_time, extruder, temp_before, preheat_config.getStandbyTemp(extruder), initial_print_temp, during_printing);
|
||||
warm_up.heating_time = std::min(in_between_time, warm_up.heating_time + extra_preheat_time);
|
||||
return warm_up;
|
||||
}
|
||||
in_between_time += extruder_plan.estimates.getTotalTime();
|
||||
in_between_time += extruder_plan_before.estimates.getTotalTime();
|
||||
}
|
||||
// The last extruder plan with the same extruder falls outside of the buffer
|
||||
// assume the nozzle has cooled down to strandby temperature already.
|
||||
Preheat::WarmUpResult warm_up;
|
||||
warm_up.total_time_window = in_between_time;
|
||||
warm_up.lowest_temperature = preheat_config.getStandbyTemp(extruder);
|
||||
warm_up.heating_time = preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(warm_up.lowest_temperature, extruder, required_temp, false);
|
||||
constexpr bool during_printing = false;
|
||||
warm_up.heating_time = preheat_config.getTimeToGoFromTempToTemp(extruder, warm_up.lowest_temperature, initial_print_temp, during_printing);
|
||||
if (warm_up.heating_time > in_between_time)
|
||||
{
|
||||
warm_up.heating_time = in_between_time;
|
||||
warm_up.lowest_temperature = in_between_time / preheat_config.getTimeToHeatup1Degree(extruder);
|
||||
warm_up.lowest_temperature = in_between_time / preheat_config.getTimeToHeatup1Degree(extruder, during_printing);
|
||||
}
|
||||
warm_up.heating_time = warm_up.heating_time + extra_preheat_time;
|
||||
return warm_up;
|
||||
@@ -82,7 +92,8 @@ Preheat::WarmUpResult LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vecto
|
||||
void LayerPlanBuffer::insertPreheatCommand_singleExtrusion(ExtruderPlan& prev_extruder_plan, int extruder, double required_temp)
|
||||
{
|
||||
// time_before_extruder_plan_end is halved, so that at the layer change the temperature will be half way betewen the two requested temperatures
|
||||
double time_before_extruder_plan_end = 0.5 * preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(prev_extruder_plan.required_temp, extruder, required_temp, true);
|
||||
constexpr bool during_printing = true;
|
||||
double time_before_extruder_plan_end = 0.5 * preheat_config.getTimeToGoFromTempToTemp(extruder, prev_extruder_plan.printing_temperature, required_temp, during_printing);
|
||||
time_before_extruder_plan_end = std::min(prev_extruder_plan.estimates.getTotalTime(), time_before_extruder_plan_end);
|
||||
|
||||
insertPreheatCommand(prev_extruder_plan, time_before_extruder_plan_end, extruder, required_temp);
|
||||
@@ -111,13 +122,13 @@ void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<ExtruderPl
|
||||
{
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
double initial_print_temp = extruder_plan.initial_printing_temperature;
|
||||
|
||||
Preheat::WarmUpResult heating_time_and_from_temp = timeBeforeExtruderPlanToInsert(extruder_plans, extruder_plan_idx);
|
||||
|
||||
if (heating_time_and_from_temp.total_time_window < preheat_config.getMinimalTimeWindow(extruder))
|
||||
{
|
||||
handleStandbyTemp(extruder_plans, extruder_plan_idx, required_temp);
|
||||
handleStandbyTemp(extruder_plans, extruder_plan_idx, initial_print_temp);
|
||||
return; // don't insert preheat command and just stay on printing temperature
|
||||
}
|
||||
else
|
||||
@@ -125,30 +136,32 @@ void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<ExtruderPl
|
||||
handleStandbyTemp(extruder_plans, extruder_plan_idx, heating_time_and_from_temp.lowest_temperature);
|
||||
}
|
||||
|
||||
// handle preheat command
|
||||
double time_before_extruder_plan_to_insert = heating_time_and_from_temp.heating_time;
|
||||
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{
|
||||
ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
|
||||
assert (extruder_plan_before.extruder != extruder);
|
||||
|
||||
|
||||
double time_here = extruder_plan_before.estimates.getTotalTime();
|
||||
if (time_here >= time_before_extruder_plan_to_insert)
|
||||
{
|
||||
insertPreheatCommand(extruder_plan_before, time_before_extruder_plan_to_insert, extruder, required_temp);
|
||||
insertPreheatCommand(extruder_plan_before, time_before_extruder_plan_to_insert, extruder, initial_print_temp);
|
||||
return;
|
||||
}
|
||||
time_before_extruder_plan_to_insert -= time_here;
|
||||
}
|
||||
|
||||
|
||||
// time_before_extruder_plan_to_insert falls before all plans in the buffer
|
||||
extruder_plans[0]->insertCommand(0, extruder, required_temp, false); // insert preheat command at verfy beginning of buffer
|
||||
bool wait = false;
|
||||
unsigned int path_idx = 0;
|
||||
extruder_plans[0]->insertCommand(path_idx, extruder, initial_print_temp, wait); // insert preheat command at verfy beginning of buffer
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
void LayerPlanBuffer::insertTempCommands(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
|
||||
|
||||
ExtruderPlan* prev_extruder_plan = extruder_plans[extruder_plan_idx - 1];
|
||||
@@ -162,19 +175,154 @@ void LayerPlanBuffer::insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_
|
||||
|
||||
if (prev_extruder == extruder)
|
||||
{
|
||||
if (preheat_config.usesFlowDependentTemp(extruder))
|
||||
{
|
||||
insertPreheatCommand_singleExtrusion(*prev_extruder_plan, extruder, required_temp);
|
||||
}
|
||||
insertPreheatCommand_singleExtrusion(*prev_extruder_plan, extruder, extruder_plan.printing_temperature);
|
||||
prev_extruder_plan->printing_temperature_command = --prev_extruder_plan->inserts.end();
|
||||
}
|
||||
else
|
||||
{
|
||||
insertPreheatCommand_multiExtrusion(extruder_plans, extruder_plan_idx);
|
||||
insertFinalPrintTempCommand(extruder_plans, extruder_plan_idx - 1);
|
||||
insertPrintTempCommand(extruder_plan);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommands()
|
||||
void LayerPlanBuffer::insertPrintTempCommand(ExtruderPlan& extruder_plan)
|
||||
{
|
||||
unsigned int extruder = extruder_plan.extruder;
|
||||
double print_temp = extruder_plan.printing_temperature;
|
||||
|
||||
double heated_pre_travel_time = 0;
|
||||
if (preheat_config.getInitialPrintTemp(extruder) != 0)
|
||||
{ // handle heating from initial_print_temperature to printing_tempreature
|
||||
unsigned int path_idx;
|
||||
for (path_idx = 0; path_idx < extruder_plan.paths.size(); path_idx++)
|
||||
{
|
||||
GCodePath& path = extruder_plan.paths[path_idx];
|
||||
heated_pre_travel_time += path.estimates.getTotalTime();
|
||||
if (!path.isTravelPath())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool wait = false;
|
||||
extruder_plan.insertCommand(path_idx, extruder, print_temp, wait);
|
||||
}
|
||||
extruder_plan.heated_pre_travel_time = heated_pre_travel_time;
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int last_extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& last_extruder_plan = *extruder_plans[last_extruder_plan_idx];
|
||||
int extruder = last_extruder_plan.extruder;
|
||||
|
||||
double final_print_temp = preheat_config.getFinalPrintTemp(extruder);
|
||||
if (final_print_temp == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double heated_post_travel_time = 0; // The time after the last extrude move toward the end of the extruder plan during which the nozzle is stable at the final print temperature
|
||||
{ // compute heated_post_travel_time
|
||||
unsigned int path_idx;
|
||||
for (path_idx = last_extruder_plan.paths.size() - 1; int(path_idx) >= 0; path_idx--)
|
||||
{
|
||||
GCodePath& path = last_extruder_plan.paths[path_idx];
|
||||
if (!path.isTravelPath())
|
||||
{
|
||||
break;
|
||||
}
|
||||
heated_post_travel_time += path.estimates.getTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
double time_window = 0; // The time window within which the nozzle needs to heat from the initial print temp to the printing temperature and then back to the final print temp; i.e. from the first to the last extrusion move with this extruder
|
||||
double weighted_average_print_temp = 0; // The average of the normal printing temperatures of the extruder plans (which might be different due to flow dependent temp or due to initial layer temp) Weighted by time
|
||||
double initial_print_temp = -1; // The initial print temp of the first extruder plan with this extruder
|
||||
{ // compute time window and print temp statistics
|
||||
double heated_pre_travel_time = -1; // The time before the first extrude move from the start of the extruder plan during which the nozzle is stable at the initial print temperature
|
||||
for (unsigned int prev_extruder_plan_idx = last_extruder_plan_idx; (int)prev_extruder_plan_idx >= 0; prev_extruder_plan_idx--)
|
||||
{
|
||||
ExtruderPlan& prev_extruder_plan = *extruder_plans[prev_extruder_plan_idx];
|
||||
if (prev_extruder_plan.extruder != extruder)
|
||||
{
|
||||
break;
|
||||
}
|
||||
double prev_extruder_plan_time = prev_extruder_plan.estimates.getTotalTime();
|
||||
time_window += prev_extruder_plan_time;
|
||||
heated_pre_travel_time = prev_extruder_plan.heated_pre_travel_time;
|
||||
|
||||
if (prev_extruder_plan.estimates.getTotalUnretractedTime() > 0)
|
||||
{ // handle temp statistics
|
||||
assert(prev_extruder_plan.printing_temperature != -1 && "Previous extruder plan should already have a temperature planned");
|
||||
weighted_average_print_temp += prev_extruder_plan.printing_temperature * prev_extruder_plan_time;
|
||||
initial_print_temp = prev_extruder_plan.initial_printing_temperature;
|
||||
}
|
||||
}
|
||||
weighted_average_print_temp /= time_window;
|
||||
time_window -= heated_pre_travel_time + heated_post_travel_time;
|
||||
assert(heated_pre_travel_time != -1 && "heated_pre_travel_time must have been computed; there must have been an extruder plan!");
|
||||
}
|
||||
|
||||
assert((time_window >= 0 || last_extruder_plan.estimates.getMaterial() == 0) && "Time window should always be positive if we actually extrude");
|
||||
|
||||
// ,layer change .
|
||||
// : ,precool command ,layer change .
|
||||
// : ____: : ,precool command .
|
||||
// :/ \ _____:_____: .
|
||||
// _____/ \ / \ .
|
||||
// / \ / \ .
|
||||
// / / .
|
||||
// / / .
|
||||
// .
|
||||
// approximate ^ by ^ .
|
||||
// This approximation is quite ok since it only determines where to insert the precool temp command,
|
||||
// which means the stable temperature of the previous extruder plan and the stable temperature of the next extruder plan couldn't be reached
|
||||
constexpr bool during_printing = true;
|
||||
Preheat::CoolDownResult warm_cool_result = preheat_config.getCoolDownPointAfterWarmUp(time_window, extruder, initial_print_temp, weighted_average_print_temp, final_print_temp, during_printing);
|
||||
double cool_down_time = warm_cool_result.cooling_time;
|
||||
assert(cool_down_time >= 0);
|
||||
|
||||
// find extruder plan in which to insert cooling command
|
||||
ExtruderPlan* precool_extruder_plan = &last_extruder_plan;
|
||||
{
|
||||
for (unsigned int precool_extruder_plan_idx = last_extruder_plan_idx; (int)precool_extruder_plan_idx >= 0; precool_extruder_plan_idx--)
|
||||
{
|
||||
precool_extruder_plan = extruder_plans[precool_extruder_plan_idx];
|
||||
if (precool_extruder_plan->printing_temperature_command)
|
||||
{ // the precool command ends up before the command to go to the print temperature of the next extruder plan, so remove that print temp command
|
||||
precool_extruder_plan->inserts.erase(*precool_extruder_plan->printing_temperature_command);
|
||||
}
|
||||
double time_here = precool_extruder_plan->estimates.getTotalTime();
|
||||
if (cool_down_time < time_here)
|
||||
{
|
||||
break;
|
||||
}
|
||||
cool_down_time -= time_here;
|
||||
}
|
||||
}
|
||||
|
||||
// at this point cool_down_time is what time is left if cool down time of extruder plans after precool_extruder_plan (up until last_extruder_plan) are already taken into account
|
||||
|
||||
{ // insert temp command in precool_extruder_plan
|
||||
double extrusion_time_seen = 0;
|
||||
unsigned int path_idx;
|
||||
for (path_idx = precool_extruder_plan->paths.size() - 1; int(path_idx) >= 0; path_idx--)
|
||||
{
|
||||
GCodePath& path = precool_extruder_plan->paths[path_idx];
|
||||
extrusion_time_seen += path.estimates.getTotalTime();
|
||||
if (extrusion_time_seen >= cool_down_time)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool wait = false;
|
||||
double time_after_path_start = extrusion_time_seen - cool_down_time;
|
||||
precool_extruder_plan->insertCommand(path_idx, extruder, final_print_temp, wait, time_after_path_start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LayerPlanBuffer::insertTempCommands()
|
||||
{
|
||||
if (buffer.back().extruder_plans.size() == 0 || (buffer.back().extruder_plans.size() == 1 && buffer.back().extruder_plans[0].paths.size() == 0))
|
||||
{ // disregard empty layer
|
||||
@@ -197,17 +345,27 @@ void LayerPlanBuffer::insertPreheatCommands()
|
||||
GCodePlanner& layer_plan = buffer.back();
|
||||
for (unsigned int extruder_plan_idx = 0; extruder_plan_idx < layer_plan.extruder_plans.size(); extruder_plan_idx++)
|
||||
{
|
||||
unsigned int overall_extruder_plan_idx = extruder_plans.size() - layer_plan.extruder_plans.size() + extruder_plan_idx;
|
||||
ExtruderPlan& extruder_plan = layer_plan.extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double time = extruder_plan.estimates.getTotalUnretractedTime();
|
||||
if (time <= 0.0
|
||||
|| extruder_plan.estimates.getMaterial() == 0.0 // extruder plan only consists of moves (when an extruder switch occurs at the beginning of a layer)
|
||||
)
|
||||
if (time <= 0.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double avg_flow = extruder_plan.estimates.getMaterial() / time; // TODO: subtract retracted travel time
|
||||
extruder_plan.required_temp = preheat_config.getTemp(extruder_plan.extruder, avg_flow);
|
||||
double avg_flow = extruder_plan.estimates.getMaterial() / time;
|
||||
extruder_plan.printing_temperature = preheat_config.getTemp(extruder, avg_flow, extruder_plan.is_initial_layer);
|
||||
extruder_plan.initial_printing_temperature = preheat_config.getInitialPrintTemp(extruder);
|
||||
if (extruder_plan.initial_printing_temperature == 0
|
||||
|| !extruder_used_in_meshgroup[extruder]
|
||||
|| (overall_extruder_plan_idx > 0 && extruder_plans[overall_extruder_plan_idx - 1]->extruder == extruder)
|
||||
)
|
||||
{
|
||||
extruder_plan.initial_printing_temperature = extruder_plan.printing_temperature;
|
||||
extruder_used_in_meshgroup[extruder] = true;
|
||||
}
|
||||
assert(extruder_plan.printing_temperature != -1 && "extruder_plan.printing_temperature should now have been set");
|
||||
|
||||
if (buffer.size() == 1 && extruder_plan_idx == 0)
|
||||
{ // the very first extruder plan of the current meshgroup
|
||||
@@ -221,7 +379,7 @@ void LayerPlanBuffer::insertPreheatCommands()
|
||||
// see FffGcodeWriter::processStartingCode
|
||||
if (extruder_idx == extruder)
|
||||
{
|
||||
gcode.setInitialTemp(extruder_idx, extruder_plan.required_temp);
|
||||
gcode.setInitialTemp(extruder_idx, extruder_plan.printing_temperature);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -239,8 +397,7 @@ void LayerPlanBuffer::insertPreheatCommands()
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned int overall_extruder_plan_idx = extruder_plans.size() - layer_plan.extruder_plans.size() + extruder_plan_idx;
|
||||
insertPreheatCommand(extruder_plans, overall_extruder_plan_idx);
|
||||
insertTempCommands(extruder_plans, overall_extruder_plan_idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+42
-5
@@ -16,6 +16,19 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for buffering multiple layer plans (\ref GCodePlanner) / extruder plans within those layer plans, so that temperature commands can be inserted in earlier layer plans.
|
||||
*
|
||||
* This class handles where to insert temperature commands for:
|
||||
* - initial layer temperature
|
||||
* - flow dependent temperature
|
||||
* - starting to heat up from the standby temperature
|
||||
* - initial printing temperature | printing temperature | final printing temperature
|
||||
*
|
||||
* \image html assets/precool.png "Temperature Regulation" width=10cm
|
||||
* \image latex assets/precool.png "Temperature Regulation" width=10cm
|
||||
*
|
||||
*/
|
||||
class LayerPlanBuffer : SettingsMessenger
|
||||
{
|
||||
GCodeExport& gcode;
|
||||
@@ -27,12 +40,14 @@ class LayerPlanBuffer : SettingsMessenger
|
||||
|
||||
static constexpr const double extra_preheat_time = 1.0; //!< Time to start heating earlier than computed to avoid accummulative discrepancy between actual heating times and computed ones.
|
||||
|
||||
std::vector<bool> extruder_used_in_meshgroup; //!< For each extruder whether it has already been planned once in this meshgroup. This is used to see whether we should heat to the initial_print_temp or to the printing_temperature
|
||||
public:
|
||||
std::list<GCodePlanner> buffer; //!< The buffer containing several layer plans (GCodePlanner) before writing them to gcode.
|
||||
|
||||
LayerPlanBuffer(SettingsBaseVirtual* settings, GCodeExport& gcode)
|
||||
: SettingsMessenger(settings)
|
||||
, gcode(gcode)
|
||||
, extruder_used_in_meshgroup(MAX_EXTRUDERS, false)
|
||||
{ }
|
||||
|
||||
void setPreheatConfig(MeshGroup& settings)
|
||||
@@ -49,7 +64,7 @@ public:
|
||||
{
|
||||
if (buffer.size() > 0)
|
||||
{
|
||||
insertPreheatCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
|
||||
insertTempCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
|
||||
}
|
||||
buffer.emplace_back(constructor_args...);
|
||||
if (buffer.size() > buffer_size)
|
||||
@@ -68,7 +83,8 @@ public:
|
||||
* Write all remaining layer plans (GCodePlanner) to gcode and empty the buffer.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Insert the preheat command for @p extruder into @p extruder_plan_before
|
||||
*
|
||||
@@ -118,13 +134,34 @@ public:
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to generate the preheat command
|
||||
*/
|
||||
void insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
|
||||
void insertTempCommands(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
|
||||
|
||||
/*!
|
||||
* Insert the temperature command to heat from the initial print temperature to the printing temperature
|
||||
*
|
||||
* The temperature command is insert at the start of the very first extrusion move
|
||||
*
|
||||
* \param extruder_plan The extruder plan in which to insert the heat up command
|
||||
*/
|
||||
void insertPrintTempCommand(ExtruderPlan& extruder_plan);
|
||||
|
||||
/*!
|
||||
* Insert the temp command to start cooling from the printing temperature to the final print temp
|
||||
*
|
||||
* The print temp is inserted before the last extrusion move of the extruder plan corresponding to \p last_extruder_plan_idx
|
||||
*
|
||||
* The command is inserted at a timed offset before the end of the last extrusion move
|
||||
*
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param last_extruder_plan_idx The index of the last extruder plan in \p extruder_plans with the same extruder as previous extruder plans
|
||||
*/
|
||||
void insertFinalPrintTempCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int last_extruder_plan_idx);
|
||||
|
||||
/*!
|
||||
* Insert the preheat commands for the last added layer (unless that layer was empty)
|
||||
*/
|
||||
void insertPreheatCommands();
|
||||
private:
|
||||
void insertTempCommands();
|
||||
|
||||
/*!
|
||||
* Reconfigure the standby temperature during which we didn't print with this extruder.
|
||||
* Find the previous extruder plan with the same extruder as layers[layer_plan_idx].extruder_plans[extruder_plan_idx]
|
||||
|
||||
+175
-21
@@ -25,6 +25,10 @@ void* fgets_(char* ptr, size_t len, FILE* f)
|
||||
*ptr = '\0';
|
||||
return ptr;
|
||||
}
|
||||
else if (*ptr =='\0')
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
ptr++;
|
||||
len--;
|
||||
}
|
||||
@@ -45,6 +49,10 @@ MeshGroup::~MeshGroup()
|
||||
delete extruders[extruder];
|
||||
}
|
||||
}
|
||||
for (Mesh* mesh : meshes)
|
||||
{
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
|
||||
int MeshGroup::getExtruderCount() const
|
||||
@@ -58,6 +66,7 @@ int MeshGroup::getExtruderCount() const
|
||||
|
||||
ExtruderTrain* MeshGroup::createExtruderTrain(unsigned int extruder_nr)
|
||||
{
|
||||
assert((int)extruder_nr >= 0 && (int)extruder_nr < getSettingAsCount("machine_extruder_count") && "only valid extruder trains may be requested!");
|
||||
if (!extruders[extruder_nr])
|
||||
{
|
||||
extruders[extruder_nr] = new ExtruderTrain(this, extruder_nr);
|
||||
@@ -89,10 +98,10 @@ Point3 MeshGroup::min() const
|
||||
{
|
||||
return Point3(0, 0, 0);
|
||||
}
|
||||
Point3 ret = meshes[0].min();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
Point3 ret = meshes[0]->min();
|
||||
for (unsigned int i = 1; i < meshes.size(); i++)
|
||||
{
|
||||
Point3 v = meshes[i].min();
|
||||
Point3 v = meshes[i]->min();
|
||||
ret.x = std::min(ret.x, v.x);
|
||||
ret.y = std::min(ret.y, v.y);
|
||||
ret.z = std::min(ret.z, v.z);
|
||||
@@ -106,10 +115,10 @@ Point3 MeshGroup::max() const
|
||||
{
|
||||
return Point3(0, 0, 0);
|
||||
}
|
||||
Point3 ret = meshes[0].max();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
Point3 ret = meshes[0]->max();
|
||||
for (unsigned int i = 1; i < meshes.size(); i++)
|
||||
{
|
||||
Point3 v = meshes[i].max();
|
||||
Point3 v = meshes[i]->max();
|
||||
ret.x = std::max(ret.x, v.x);
|
||||
ret.y = std::max(ret.y, v.y);
|
||||
ret.z = std::max(ret.z, v.z);
|
||||
@@ -119,9 +128,9 @@ Point3 MeshGroup::max() const
|
||||
|
||||
void MeshGroup::clear()
|
||||
{
|
||||
for(Mesh& m : meshes)
|
||||
for (Mesh* m : meshes)
|
||||
{
|
||||
m.clear();
|
||||
m->clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,15 +142,15 @@ void MeshGroup::finalize()
|
||||
{
|
||||
createExtruderTrain(extruder_nr); // create it if it didn't exist yet
|
||||
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") == extruder_nr)
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") == extruder_nr && getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
getExtruderTrain(extruder_nr)->setIsUsed(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const Mesh& mesh : meshes)
|
||||
for (const Mesh* mesh : meshes)
|
||||
{
|
||||
if (mesh.getSettingBoolean("support_enable")
|
||||
if (mesh->getSettingBoolean("support_enable")
|
||||
&& (
|
||||
getSettingAsIndex("support_infill_extruder_nr") == extruder_nr
|
||||
|| getSettingAsIndex("support_extruder_nr_layer_0") == extruder_nr
|
||||
@@ -155,9 +164,14 @@ void MeshGroup::finalize()
|
||||
}
|
||||
}
|
||||
|
||||
for (const Mesh& mesh : meshes)
|
||||
for (const Mesh* mesh : meshes)
|
||||
{
|
||||
getExtruderTrain(mesh.getSettingAsIndex("extruder_nr"))->setIsUsed(true);
|
||||
if (!mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh->getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
getExtruderTrain(mesh->getSettingAsIndex("extruder_nr"))->setIsUsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
//If the machine settings have been supplied, offset the given position vertices to the center of vertices (0,0,0) is at the bed center.
|
||||
@@ -169,17 +183,17 @@ void MeshGroup::finalize()
|
||||
}
|
||||
|
||||
// If a mesh position was given, put the mesh at this position in 3D space.
|
||||
for(Mesh& mesh : meshes)
|
||||
for (Mesh* mesh : meshes)
|
||||
{
|
||||
Point3 mesh_offset(mesh.getSettingInMicrons("mesh_position_x"), mesh.getSettingInMicrons("mesh_position_y"), mesh.getSettingInMicrons("mesh_position_z"));
|
||||
if (mesh.getSettingBoolean("center_object"))
|
||||
Point3 mesh_offset(mesh->getSettingInMicrons("mesh_position_x"), mesh->getSettingInMicrons("mesh_position_y"), mesh->getSettingInMicrons("mesh_position_z"));
|
||||
if (mesh->getSettingBoolean("center_object"))
|
||||
{
|
||||
Point3 object_min = mesh.min();
|
||||
Point3 object_max = mesh.max();
|
||||
Point3 object_min = mesh->min();
|
||||
Point3 object_max = mesh->max();
|
||||
Point3 object_size = object_max - object_min;
|
||||
mesh_offset += Point3(-object_min.x - object_size.x / 2, -object_min.y - object_size.y / 2, -object_min.z);
|
||||
}
|
||||
mesh.offset(mesh_offset + meshgroup_offset);
|
||||
mesh->offset(mesh_offset + meshgroup_offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +337,128 @@ bool loadMeshSTL(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
|
||||
return loadMeshSTL_binary(mesh, filename, matrix);
|
||||
}
|
||||
|
||||
void loadMaterialBase(TexturedMesh* mesh, const char* filename)
|
||||
{
|
||||
FILE* f = fopen(filename, "rt");
|
||||
if (f == nullptr)
|
||||
{
|
||||
logError("ERROR: Couldn't load MTL file %s.\n", filename);
|
||||
return;
|
||||
}
|
||||
char buffer[1024];
|
||||
char mat_name [100];
|
||||
char mat_file [100];
|
||||
char map_type [10];
|
||||
Material* last_mat = nullptr;
|
||||
while(fgets_(buffer, sizeof(buffer), f))
|
||||
{
|
||||
if (buffer[0] == '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (sscanf(buffer, "map_%s %s", map_type, mat_file) == 2 // we don't care what type of map it specifies (currently)
|
||||
|| sscanf(buffer, "bump %s", mat_file) == 1
|
||||
|| sscanf(buffer, "disp %s", mat_file) == 1
|
||||
|| sscanf(buffer, "decal %s", mat_file) == 1
|
||||
|| sscanf(buffer, "refl %s", mat_file) == 1
|
||||
)
|
||||
{
|
||||
std::string parent_dir = std::string(filename).substr(0, std::string(filename).find_last_of("/\\"));
|
||||
std::string mtl_file = parent_dir + "/" + mat_file;
|
||||
if (last_mat)
|
||||
{
|
||||
last_mat->loadImage(mtl_file.c_str());
|
||||
}
|
||||
}
|
||||
else if (sscanf(buffer, "newmtl %s", mat_name) == 1)
|
||||
{
|
||||
last_mat = mesh->addMaterial(mat_name);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
bool loadMeshOBJ(TexturedMesh* mesh, const char* filename, const FMatrix3x3& matrix)
|
||||
{
|
||||
FILE* f = fopen(filename, "rt");
|
||||
if (f == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
char buffer[1024];
|
||||
FPoint3 vertex;
|
||||
int vertex_indices[3];
|
||||
float texture_x;
|
||||
float texture_y;
|
||||
float temp;
|
||||
char face_index_buffer_1 [100];
|
||||
char face_index_buffer_2 [100];
|
||||
char face_index_buffer_3 [100];
|
||||
char str_buffer [100];
|
||||
while(fgets_(buffer, sizeof(buffer), f))
|
||||
{
|
||||
if (buffer[0] == '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (sscanf(buffer, "v %f %f %f", &vertex.x, &vertex.y, &vertex.z) == 3)
|
||||
{
|
||||
Point3 v = matrix.apply(vertex);
|
||||
mesh->addVertex(v);
|
||||
}
|
||||
else if (sscanf(buffer, "vt %f %f", &texture_x, &texture_y) == 2)
|
||||
{
|
||||
mesh->addTextureCoord(texture_x, texture_y);
|
||||
}
|
||||
else if (sscanf(buffer, "f %s %s %s", face_index_buffer_1, face_index_buffer_2, face_index_buffer_3) == 3)
|
||||
{
|
||||
int normal_vector_index; // unused
|
||||
int texture_indices[3]; // becomes -1 if no texture data supplied
|
||||
int n_scanned_1 = sscanf(face_index_buffer_1, "%d/%d/%d", &vertex_indices[0], &texture_indices[0], &normal_vector_index);
|
||||
int n_scanned_2 = sscanf(face_index_buffer_2, "%d/%d/%d", &vertex_indices[1], &texture_indices[1], &normal_vector_index);
|
||||
int n_scanned_3 = sscanf(face_index_buffer_3, "%d/%d/%d", &vertex_indices[2], &texture_indices[2], &normal_vector_index);
|
||||
if (n_scanned_1 >= 2 && n_scanned_2 >= 2 && n_scanned_3 >= 2)
|
||||
{
|
||||
mesh->addFace(vertex_indices[0] - 1, vertex_indices[1] - 1, vertex_indices[2] - 1, texture_indices[0] - 1, texture_indices[1] - 1, texture_indices[2] - 1);
|
||||
// obj files count vertex indices starting from 1!
|
||||
}
|
||||
else if (n_scanned_1 >= 1 && n_scanned_2 >= 1 && n_scanned_3 >= 1)
|
||||
{
|
||||
mesh->addFace(vertex_indices[0] - 1, vertex_indices[1] - 1, vertex_indices[2] - 1);
|
||||
}
|
||||
}
|
||||
else if (sscanf(buffer, "mtllib %s", str_buffer) == 1)
|
||||
{
|
||||
std::string parent_dir = std::string(filename).substr(0, std::string(filename).find_last_of("/\\"));
|
||||
std::string mtl_file = parent_dir + "/" + str_buffer;
|
||||
loadMaterialBase(mesh, mtl_file.c_str());
|
||||
}
|
||||
else if (sscanf(buffer, "usemtl %s", str_buffer) == 1)
|
||||
{
|
||||
mesh->setMaterial(str_buffer);
|
||||
}
|
||||
else if (sscanf(buffer, "vn %f %f %f", &temp, &temp, &temp) == 3)
|
||||
{
|
||||
// do nothing with vertex normals
|
||||
}
|
||||
else if (sscanf(buffer, "g %s", str_buffer) == 1)
|
||||
{
|
||||
// do nothing with polygon groups
|
||||
}
|
||||
else if (buffer[0] == '\0')
|
||||
{
|
||||
// empty line, do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
logError("Cannot parse line \"%s\"\n", buffer);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
mesh->finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings)
|
||||
{
|
||||
TimeKeeper load_timer;
|
||||
@@ -330,14 +466,32 @@ bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMa
|
||||
const char* ext = strrchr(filename, '.');
|
||||
if (ext && (strcmp(ext, ".stl") == 0 || strcmp(ext, ".STL") == 0))
|
||||
{
|
||||
Mesh mesh = object_parent_settings ? Mesh(object_parent_settings) : Mesh(meshgroup); //If we have object_parent_settings, use them as parent settings. Otherwise, just use meshgroup.
|
||||
if(loadMeshSTL(&mesh,filename,transformation)) //Load it! If successful...
|
||||
Mesh* mesh = new Mesh(object_parent_settings ? object_parent_settings : meshgroup); //If we have object_parent_settings, use them as parent settings. Otherwise, just use meshgroup.
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
else if (ext && (strcmp(ext, ".obj") == 0 || strcmp(ext, ".OBJ") == 0))
|
||||
{
|
||||
TexturedMesh* mesh = new TexturedMesh(object_parent_settings ? object_parent_settings : meshgroup); //If we have object_parent_settings, use them as parent settings. Otherwise, just use meshgroup.
|
||||
if (loadMeshOBJ(mesh,filename,transformation)) //Load it! If successful...
|
||||
{
|
||||
meshgroup->meshes.push_back(mesh);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "utils/NoCopy.h"
|
||||
#include "mesh.h"
|
||||
#include "textureProcessing/TexturedMesh.h"
|
||||
#include "ExtruderTrain.h"
|
||||
|
||||
namespace cura
|
||||
@@ -35,7 +36,7 @@ public:
|
||||
|
||||
const ExtruderTrain* getExtruderTrain(unsigned int extruder_nr) const;
|
||||
|
||||
std::vector<Mesh> meshes;
|
||||
std::vector<Mesh*> meshes;
|
||||
|
||||
Point3 min() const; //! minimal corner of bounding box
|
||||
Point3 max() const; //! maximal corner of bounding box
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef POLYGON_FLOW_ADJUSTER_H
|
||||
#define POLYGON_FLOW_ADJUSTER_H
|
||||
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/polygon.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for computing and compensating the flow of line segments in a polygon.
|
||||
*
|
||||
*/
|
||||
class PolygonFlowAdjuster
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Compute the flow for a given line segment in the polygons
|
||||
*
|
||||
* \warning should only be called once for each line segment in a polygon!
|
||||
*
|
||||
* \param from the polygons from which to get the segment of a flow, which should be the same polygons as the ones which the PolygonFlowAdjuster was constructed with
|
||||
* \param poly_idx Index to the polygon in which to find the line segment
|
||||
* \param from_point_idx The index to the beginning of the line segment
|
||||
* \param to_point_idx The index to the ending of the line segment
|
||||
* \return a value between zero and one representing the reduced flow of the line segment
|
||||
*/
|
||||
virtual float getFlow(const Polygons& from, unsigned int poly_idx, unsigned int from_point_idx, unsigned int to_point_idx) = 0;
|
||||
|
||||
virtual ~PolygonFlowAdjuster()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
|
||||
#endif//POLYGON_FLOW_ADJUSTER_H
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "Preheat.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void Preheat::setConfig(const MeshGroup& meshgroup)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < meshgroup.getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
assert(meshgroup.getExtruderTrain(extruder_nr) != nullptr);
|
||||
const ExtruderTrain& extruder_train = *meshgroup.getExtruderTrain(extruder_nr);
|
||||
config_per_extruder.emplace_back();
|
||||
Config& config = config_per_extruder.back();
|
||||
double machine_nozzle_cool_down_speed = extruder_train.getSettingInSeconds("machine_nozzle_cool_down_speed");
|
||||
double machine_nozzle_heat_up_speed = extruder_train.getSettingInSeconds("machine_nozzle_heat_up_speed");
|
||||
double material_extrusion_cool_down_speed = extruder_train.getSettingInSeconds("material_extrusion_cool_down_speed");
|
||||
assert(material_extrusion_cool_down_speed < machine_nozzle_heat_up_speed && "The extrusion cooldown speed must be smaller than the heat up speed; otherwise the printing temperature cannot be reached!");
|
||||
config.time_to_cooldown_1_degree[0] = 1.0 / machine_nozzle_cool_down_speed;
|
||||
config.time_to_heatup_1_degree[0] = 1.0 / machine_nozzle_heat_up_speed;
|
||||
config.time_to_cooldown_1_degree[1] = 1.0 / (machine_nozzle_cool_down_speed + material_extrusion_cool_down_speed);
|
||||
config.time_to_heatup_1_degree[1] = 1.0 / (machine_nozzle_heat_up_speed - material_extrusion_cool_down_speed);
|
||||
config.standby_temp = extruder_train.getSettingInSeconds("material_standby_temperature");
|
||||
|
||||
config.min_time_window = extruder_train.getSettingInSeconds("machine_min_cool_heat_time_window");
|
||||
|
||||
config.material_print_temperature = extruder_train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
config.material_print_temperature_layer_0 = extruder_train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
config.material_initial_print_temperature = extruder_train.getSettingInDegreeCelsius("material_initial_print_temperature");
|
||||
config.material_final_print_temperature = extruder_train.getSettingInDegreeCelsius("material_final_print_temperature");
|
||||
|
||||
config.flow_dependent_temperature = extruder_train.getSettingBoolean("material_flow_dependent_temperature");
|
||||
|
||||
config.flow_temp_graph = extruder_train.getSettingAsFlowTempGraph("material_flow_temp_graph"); // [[0.1,180],[20,230]]
|
||||
}
|
||||
}
|
||||
|
||||
double Preheat::getTimeToGoFromTempToTemp(int extruder, double temp_before, double temp_after, bool during_printing)
|
||||
{
|
||||
Config& config = config_per_extruder[extruder];
|
||||
double time;
|
||||
if (temp_after > temp_before)
|
||||
{
|
||||
time = (temp_after - temp_before) * config.time_to_heatup_1_degree[during_printing];
|
||||
}
|
||||
else
|
||||
{
|
||||
time = (temp_before - temp_after) * config.time_to_cooldown_1_degree[during_printing];
|
||||
}
|
||||
return std::max(0.0, time);
|
||||
}
|
||||
|
||||
double Preheat::getTemp(unsigned int extruder, double flow, bool is_initial_layer)
|
||||
{
|
||||
if (is_initial_layer && config_per_extruder[extruder].material_print_temperature_layer_0 != 0)
|
||||
{
|
||||
return config_per_extruder[extruder].material_print_temperature_layer_0;
|
||||
}
|
||||
return config_per_extruder[extruder].flow_temp_graph.getTemp(flow, config_per_extruder[extruder].material_print_temperature, config_per_extruder[extruder].flow_dependent_temperature);
|
||||
}
|
||||
|
||||
Preheat::WarmUpResult Preheat::getWarmUpPointAfterCoolDown(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing)
|
||||
{
|
||||
WarmUpResult result;
|
||||
const Config& config = config_per_extruder[extruder];
|
||||
double time_to_cooldown_1_degree = config.time_to_cooldown_1_degree[during_printing];
|
||||
double time_to_heatup_1_degree = config.time_to_heatup_1_degree[during_printing];
|
||||
result.total_time_window = time_window;
|
||||
|
||||
// ,temp_end
|
||||
// / .
|
||||
// ,temp_start / .
|
||||
// \ ' ' ' ' '/ ' ' '> outer_temp .
|
||||
// \________/ .
|
||||
// "-> temp_mid
|
||||
// ^^^^^^^^^^
|
||||
// limited_time_window
|
||||
double outer_temp;
|
||||
double limited_time_window;
|
||||
if (temp_start < temp_end)
|
||||
{ // extra time needed during heating
|
||||
double extra_heatup_time = (temp_end - temp_start) * time_to_heatup_1_degree;
|
||||
result.heating_time = extra_heatup_time;
|
||||
limited_time_window = time_window - extra_heatup_time;
|
||||
outer_temp = temp_start;
|
||||
}
|
||||
else
|
||||
{
|
||||
double extra_cooldown_time = (temp_start - temp_end) * time_to_cooldown_1_degree;
|
||||
result.heating_time = 0;
|
||||
limited_time_window = time_window - extra_cooldown_time;
|
||||
outer_temp = temp_end;
|
||||
}
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.heating_time = 0.0;
|
||||
result.lowest_temperature = std::min(temp_start, temp_end);
|
||||
return result;
|
||||
}
|
||||
|
||||
double time_ratio_cooldown_heatup = time_to_cooldown_1_degree / time_to_heatup_1_degree;
|
||||
double time_to_heat_from_standby_to_print_temp = getTimeToGoFromTempToTemp(extruder, temp_mid, outer_temp, during_printing);
|
||||
double time_needed_to_reach_standby_temp = time_to_heat_from_standby_to_print_temp * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_standby_temp < limited_time_window)
|
||||
{
|
||||
result.heating_time += time_to_heat_from_standby_to_print_temp;
|
||||
result.lowest_temperature = temp_mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.heating_time += limited_time_window * time_to_heatup_1_degree / (time_to_cooldown_1_degree + time_to_heatup_1_degree);
|
||||
result.lowest_temperature = std::max(temp_mid, temp_end - result.heating_time / time_to_heatup_1_degree);
|
||||
}
|
||||
|
||||
if (result.heating_time > time_window || result.heating_time < 0.0)
|
||||
{
|
||||
logWarning("getWarmUpPointAfterCoolDown returns result outside of the time window!");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Preheat::CoolDownResult Preheat::getCoolDownPointAfterWarmUp(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing)
|
||||
{
|
||||
CoolDownResult result;
|
||||
const Config& config = config_per_extruder[extruder];
|
||||
double time_to_cooldown_1_degree = config.time_to_cooldown_1_degree[during_printing];
|
||||
double time_to_heatup_1_degree = config.time_to_heatup_1_degree[during_printing];
|
||||
|
||||
assert(temp_start != -1 && temp_mid != -1 && temp_end != -1 && "temperatures must be initialized!");
|
||||
|
||||
result.total_time_window = time_window;
|
||||
|
||||
// limited_time_window
|
||||
// :^^^^^^^^^^^^:
|
||||
// : ________. : . . .> temp_mid
|
||||
// : / \ : .
|
||||
// :/ . . . . .\:. . .> outer_temp .
|
||||
// ^temp_start \ .
|
||||
// \ .
|
||||
// ^temp_end
|
||||
double outer_temp;
|
||||
double limited_time_window;
|
||||
if (temp_start < temp_end)
|
||||
{ // extra time needed during heating
|
||||
double extra_heatup_time = (temp_end - temp_start) * time_to_heatup_1_degree;
|
||||
result.cooling_time = 0;
|
||||
limited_time_window = time_window - extra_heatup_time;
|
||||
outer_temp = temp_end;
|
||||
}
|
||||
else
|
||||
{
|
||||
double extra_cooldown_time = (temp_start - temp_end) * time_to_cooldown_1_degree;
|
||||
result.cooling_time = extra_cooldown_time;
|
||||
limited_time_window = time_window - extra_cooldown_time;
|
||||
outer_temp = temp_start;
|
||||
}
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.cooling_time = 0.0;
|
||||
result.highest_temperature = std::max(temp_start, temp_end);
|
||||
return result;
|
||||
}
|
||||
double time_ratio_cooldown_heatup = time_to_cooldown_1_degree / time_to_heatup_1_degree;
|
||||
double cool_down_time = getTimeToGoFromTempToTemp(extruder, temp_mid, outer_temp, during_printing);
|
||||
double time_needed_to_reach_temp1 = cool_down_time * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_temp1 < limited_time_window)
|
||||
{
|
||||
result.cooling_time += cool_down_time;
|
||||
result.highest_temperature = temp_mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.cooling_time += limited_time_window * time_to_heatup_1_degree / (time_to_cooldown_1_degree + time_to_heatup_1_degree);
|
||||
result.highest_temperature = std::min(temp_mid, temp_end + result.cooling_time / time_to_cooldown_1_degree);
|
||||
}
|
||||
|
||||
if (result.cooling_time > time_window || result.cooling_time < 0.0)
|
||||
{
|
||||
logWarning("getCoolDownPointAfterWarmUp returns result outside of the time window!");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
+87
-104
@@ -26,17 +26,21 @@ class Preheat
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
double time_to_heatup_1_degree; //!< average time it takes to heat up one degree (in the range of normal print temperatures and standby temperature)
|
||||
double time_to_cooldown_1_degree; //!< average time it takes to cool down one degree (in the range of normal print temperatures and standby temperature)
|
||||
|
||||
double heatup_cooldown_time_mod_while_printing; //!< The time to be added to Preheat::time_to_heatup_1_degree and subtracted from Preheat::time_to_cooldown_1_degree to get the timings while printing
|
||||
double time_to_heatup_1_degree[2]; //!< average time it takes to heat up one degree (in the range of normal print temperatures and standby temperature), while not-printing and while printing
|
||||
double time_to_cooldown_1_degree[2]; //!< average time it takes to cool down one degree (in the range of normal print temperatures and standby temperature), while not-printing and while printing
|
||||
|
||||
double standby_temp; //!< The temperature at which the nozzle rests when it is not printing.
|
||||
|
||||
double min_time_window; //!< Minimal time (in seconds) to allow an extruder to cool down and then warm up again.
|
||||
|
||||
double material_print_temperature; //!< default print temp (backward compatilibily)
|
||||
|
||||
|
||||
double material_print_temperature_layer_0; //!< initial layer print temp
|
||||
|
||||
double material_initial_print_temperature; //!< print temp when first starting to extrude after a layer switch
|
||||
|
||||
double material_final_print_temperature; //!< print temp at the end of all extrusion moves of an extruder to which it's cooled down just before - during the extrusion
|
||||
|
||||
bool flow_dependent_temperature; //!< Whether to make the temperature dependent on flow
|
||||
|
||||
FlowTempGraph flow_temp_graph; //!< The graph linking flows to corresponding temperatures
|
||||
@@ -54,6 +58,16 @@ public:
|
||||
double lowest_temperature; //!< The lower temperature from which heating starts.
|
||||
};
|
||||
|
||||
/*!
|
||||
* The type of result when computing when to start cooling down a nozzle before it's not going to be used again.
|
||||
*/
|
||||
struct CoolDownResult
|
||||
{
|
||||
double total_time_window; //!< The total time in which heating and cooling takes place.
|
||||
double cooling_time; //!< The total time needed to cool down to the required temperature.
|
||||
double highest_temperature; //!< The upper temperature from which cooling starts.
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the standby temperature of an extruder train
|
||||
* \param extruder the extruder train for which to get the standby tmep
|
||||
@@ -68,68 +82,51 @@ public:
|
||||
* Get the time it takes to heat up one degree celsius
|
||||
*
|
||||
* \param extruder the extruder train for which to get time it takes to heat up one degree celsius
|
||||
* \param during_printing whether the heating takes time during printing or when idle
|
||||
* \return the time it takes to heat up one degree celsius
|
||||
*/
|
||||
double getTimeToHeatup1Degree(int extruder)
|
||||
double getTimeToHeatup1Degree(int extruder, bool during_printing)
|
||||
{
|
||||
return config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
return config_per_extruder[extruder].time_to_heatup_1_degree[during_printing];
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the initial print temperature when starting to extrude.
|
||||
* \param during_printing whether the heating takes time during printing or when idle
|
||||
*/
|
||||
double getInitialPrintTemp(int extruder)
|
||||
{
|
||||
return config_per_extruder[extruder].material_initial_print_temperature;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the final print temperature at the end of all extrusion moves with the current extruder
|
||||
*/
|
||||
double getFinalPrintTemp(int extruder)
|
||||
{
|
||||
return config_per_extruder[extruder].material_final_print_temperature;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the nozzle and material temperature settings for each extruder train.
|
||||
* \param meshgroup Where to get settings from
|
||||
*/
|
||||
void setConfig(MeshGroup& settings)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < settings.getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
assert(settings.getExtruderTrain(extruder_nr) != nullptr);
|
||||
ExtruderTrain& extruder_train = *settings.getExtruderTrain(extruder_nr);
|
||||
config_per_extruder.emplace_back();
|
||||
Config& config = config_per_extruder.back();
|
||||
config.time_to_cooldown_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_cool_down_speed"); // 0.5
|
||||
config.time_to_heatup_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_heat_up_speed"); // 0.5
|
||||
config.heatup_cooldown_time_mod_while_printing = 1.0 / extruder_train.getSettingInSeconds("material_extrusion_cool_down_speed"); // 0.1
|
||||
config.standby_temp = extruder_train.getSettingInSeconds("material_standby_temperature"); // 150
|
||||
void setConfig(const MeshGroup& meshgroup);
|
||||
|
||||
config.min_time_window = extruder_train.getSettingInSeconds("machine_min_cool_heat_time_window");
|
||||
|
||||
config.material_print_temperature = extruder_train.getSettingInDegreeCelsius("material_print_temperature"); // 220
|
||||
|
||||
config.flow_dependent_temperature = extruder_train.getSettingBoolean("material_flow_dependent_temperature");
|
||||
|
||||
config.flow_temp_graph = extruder_train.getSettingAsFlowTempGraph("material_flow_temp_graph"); // [[0.1,180],[20,230]]
|
||||
}
|
||||
}
|
||||
|
||||
bool usesFlowDependentTemp(int extruder_nr)
|
||||
{
|
||||
return config_per_extruder[extruder_nr].flow_dependent_temperature;
|
||||
}
|
||||
private:
|
||||
/*!
|
||||
* Calculate time to heat up from standby temperature to a given temperature.
|
||||
* Assumes @p temp is higher than the standby temperature.
|
||||
* Get the optimal temperature corresponding to a given average flow,
|
||||
* or the initial layer temperature.
|
||||
*
|
||||
* \param extruder The extruder for which to get the time
|
||||
* \param temp The temperature to be reached
|
||||
*/
|
||||
double timeToHeatFromStandbyToPrintTemp(unsigned int extruder, double temp)
|
||||
{
|
||||
return (temp - config_per_extruder[extruder].standby_temp) * config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Get the optimal temperature corresponding to a given average flow.
|
||||
* \param extruder The extruder train
|
||||
* \param flow The flow for which to get the optimal temperature
|
||||
* \param is_initial_layer Whether the initial layer temperature should be returned instead of flow-based temperature
|
||||
* \return The corresponding optimal temperature
|
||||
*/
|
||||
double getTemp(unsigned int extruder, double flow)
|
||||
{
|
||||
return config_per_extruder[extruder].flow_temp_graph.getTemp(flow, config_per_extruder[extruder].material_print_temperature, config_per_extruder[extruder].flow_dependent_temperature);
|
||||
}
|
||||
double getTemp(unsigned int extruder, double flow, bool is_initial_layer);
|
||||
|
||||
/*!
|
||||
* Return the minimal time window of a specific extruder for letting an unused extruder cool down and warm up again
|
||||
@@ -142,76 +139,62 @@ public:
|
||||
}
|
||||
|
||||
/*!
|
||||
* Decide when to start warming up again after starting to cool down towards the standby temperature.
|
||||
* Decide when to start warming up again after starting to cool down towards \p temp_mid.
|
||||
* Two cases are considered:
|
||||
* the case where the standby temperature is reached \__/ .
|
||||
* and the case where it isn't \/ .
|
||||
*
|
||||
* IT is assumed that the printer is not printing during this cool down and warm up time.
|
||||
*
|
||||
* Assumes from_temp is approximately the same as @p temp
|
||||
* \warning it is assumed that \p temp_mid is lower than both \p temp_start and \p temp_end. If not somewhat weird results may follow.
|
||||
*
|
||||
// ,temp_end
|
||||
// / .
|
||||
// ,temp_start / .
|
||||
// \ / .
|
||||
// \________/ .
|
||||
// "-> temp_mid
|
||||
* \param window_time The time window within which the cooldown and heat up must take place.
|
||||
* \param extruder The extruder used
|
||||
* \param temp The temperature to which to heat
|
||||
* \param temp_start The temperature from which to start cooling down
|
||||
* \param temp_mid The temeprature to which we try to cool down
|
||||
* \param temp_end The temperature to which we need to have heated up at the end of the \p time_window
|
||||
* \param during_printing Whether the warming up and cooling down is performed during printing
|
||||
* \return The time before the end of the @p time_window to insert the preheat command and the temperature from which the heating starts
|
||||
*/
|
||||
WarmUpResult timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(double time_window, unsigned int extruder, double temp)
|
||||
{
|
||||
WarmUpResult result;
|
||||
const Config& config = config_per_extruder[extruder];
|
||||
result.total_time_window = time_window;
|
||||
double time_ratio_cooldown_heatup = config.time_to_cooldown_1_degree / config.time_to_heatup_1_degree;
|
||||
double time_to_heat_from_standby_to_print_temp = timeToHeatFromStandbyToPrintTemp(extruder, temp);
|
||||
double time_needed_to_reach_standby_temp = time_to_heat_from_standby_to_print_temp * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_standby_temp < time_window)
|
||||
{
|
||||
result.heating_time = time_to_heat_from_standby_to_print_temp;
|
||||
result.lowest_temperature = config.standby_temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.heating_time = time_window * config.time_to_heatup_1_degree / (config.time_to_cooldown_1_degree + config.time_to_heatup_1_degree);
|
||||
result.lowest_temperature = std::max(config.standby_temp, temp - result.heating_time / config.time_to_heatup_1_degree);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
WarmUpResult getWarmUpPointAfterCoolDown(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing);
|
||||
|
||||
/*!
|
||||
* Calculate time needed to warm up the nozzle from a given temp to a given temp.
|
||||
* If the printer is printing in the mean time the warming up will take longer.
|
||||
* Decide when to start cooling down again after starting to warm up towards the \p temp_mid
|
||||
* Two cases are considered:
|
||||
* the case where the temperature is reached /"""\ .
|
||||
* and the case where it isn't /\ .
|
||||
*
|
||||
* \warning it is assumed that \p temp_mid is higher than both \p temp_start and \p temp_end. If not somewhat weird results may follow.
|
||||
*
|
||||
* \param from_temp The temperature at which the nozzle was before
|
||||
// _> temp_mid
|
||||
// /""""""""\ .
|
||||
// / \ .
|
||||
// ^temp_start \ .
|
||||
// \ .
|
||||
// ^temp_end
|
||||
* \param window_time The time window within which the cooldown and heat up must take place.
|
||||
* \param extruder The extruder used
|
||||
* \param temp The temperature to which to heat
|
||||
* \param printing Whether the printer is printing in the time to heat up the nozzle
|
||||
* \return The time needed to reach the desired temperature (@p temp)
|
||||
* \param temp_start The temperature from which to start heating up
|
||||
* \param temp_mid The temeprature to which we try to heat up
|
||||
* \param temp_end The temperature to which we need to have cooled down after \p time_window time
|
||||
* \param during_printing Whether the warming up and cooling down is performed during printing
|
||||
* \return The time before the end of the \p time_window to insert the preheat command and the temperature from which the cooling starts
|
||||
*/
|
||||
double timeBeforeEndToInsertPreheatCommand_warmUp(double from_temp, unsigned int extruder, double temp, bool printing)
|
||||
{
|
||||
if (temp > from_temp)
|
||||
{
|
||||
if (printing)
|
||||
{
|
||||
return (temp - from_temp) * (config_per_extruder[extruder].time_to_heatup_1_degree + config_per_extruder[extruder].heatup_cooldown_time_mod_while_printing);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (temp - from_temp) * config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (printing)
|
||||
{
|
||||
return (from_temp - temp) * config_per_extruder[extruder].time_to_cooldown_1_degree;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (from_temp - temp) * std::max(0.0, config_per_extruder[extruder].time_to_cooldown_1_degree - config_per_extruder[extruder].heatup_cooldown_time_mod_while_printing);
|
||||
}
|
||||
}
|
||||
}
|
||||
CoolDownResult getCoolDownPointAfterWarmUp(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing);
|
||||
|
||||
/*!
|
||||
* Get the time to go from one temperature to another temperature
|
||||
* \param extruder The extruder number for which to perform the heatup / cooldown
|
||||
* \param temp_before The before temperature
|
||||
* \param temp_after The after temperature
|
||||
* \param during_printing Whether the planned cooldown / warmup occurs during printing or while in standby mode
|
||||
* \return The time needed
|
||||
*/
|
||||
double getTimeToGoFromTempToTemp(int extruder, double temp_before, double temp_after, bool during_printing);
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
+117
-62
@@ -12,13 +12,22 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
PrimeTower::PrimeTower()
|
||||
: current_pre_wipe_location_idx(0)
|
||||
PrimeTower::PrimeTower(const SliceDataStorage& storage)
|
||||
: is_hollow(false)
|
||||
, wipe_from_middle(false)
|
||||
, 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;
|
||||
}
|
||||
enabled = storage.getSettingBoolean("prime_tower_enable")
|
||||
&& storage.getSettingInMicrons("prime_tower_wall_thickness") > 10
|
||||
&& storage.getSettingInMicrons("prime_tower_size") > 10;
|
||||
if (enabled)
|
||||
{
|
||||
generateGroundpoly(storage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +49,6 @@ void PrimeTower::initConfigs(const MeshGroup* meshgroup)
|
||||
|
||||
void PrimeTower::setConfigs(const MeshGroup* meshgroup, const int layer_thickness)
|
||||
{
|
||||
|
||||
extruder_count = meshgroup->getExtruderCount();
|
||||
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
@@ -52,8 +60,17 @@ void PrimeTower::setConfigs(const MeshGroup* meshgroup, const int layer_thicknes
|
||||
|
||||
void PrimeTower::generateGroundpoly(const SliceDataStorage& storage)
|
||||
{
|
||||
extruder_count = storage.meshgroup->getExtruderCount();
|
||||
|
||||
int64_t prime_tower_wall_thickness = storage.getSettingInMicrons("prime_tower_wall_thickness");
|
||||
int64_t tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
|
||||
if (prime_tower_wall_thickness * 2 < tower_size)
|
||||
{
|
||||
is_hollow = true;
|
||||
}
|
||||
|
||||
PolygonRef p = ground_poly.newPoly();
|
||||
int tower_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
|
||||
@@ -61,14 +78,20 @@ void PrimeTower::generateGroundpoly(const SliceDataStorage& storage)
|
||||
p.add(Point(x + tower_distance, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance));
|
||||
middle = Point(x - tower_size / 2, y + tower_size / 2);
|
||||
|
||||
if (is_hollow)
|
||||
{
|
||||
ground_poly = ground_poly.difference(ground_poly.offset(-prime_tower_wall_thickness));
|
||||
}
|
||||
|
||||
post_wipe_point = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
|
||||
}
|
||||
|
||||
void PrimeTower::generatePaths(const SliceDataStorage& storage, unsigned int total_layers)
|
||||
void PrimeTower::generatePaths(const SliceDataStorage& storage)
|
||||
{
|
||||
extruder_count = storage.meshgroup->getExtruderCount();
|
||||
if (storage.max_print_height_second_to_last_extruder >= 0 && storage.getSettingBoolean("prime_tower_enable"))
|
||||
enabled &= storage.max_print_height_second_to_last_extruder >= 0; //Maybe it turns out that we don't need a prime tower after all because there are no layer switches.
|
||||
if (enabled)
|
||||
{
|
||||
generatePaths_denseInfill(storage);
|
||||
generateWipeLocations(storage);
|
||||
@@ -81,22 +104,22 @@ void PrimeTower::generatePaths_denseInfill(const SliceDataStorage& storage)
|
||||
int infill_overlap = 60; // so that it can't be zero; EDIT: wtf?
|
||||
int extra_infill_shift = 0;
|
||||
|
||||
generateGroundpoly(storage);
|
||||
|
||||
int64_t z = 0; // (TODO) because the prime tower stores the paths for each extruder for once instead of generating each layer, we don't know the z position
|
||||
|
||||
for (int extruder = 0; extruder < extruder_count; extruder++)
|
||||
{
|
||||
int line_width = storage.meshgroup->getExtruderTrain(extruder)->getSettingInMicrons("prime_tower_line_width");
|
||||
patterns_per_extruder.emplace_back(n_patterns);
|
||||
std::vector<Polygons>& patterns = patterns_per_extruder.back();
|
||||
std::vector<ExtrusionMoves>& patterns = patterns_per_extruder.back();
|
||||
patterns.resize(n_patterns);
|
||||
for (int pattern_idx = 0; pattern_idx < n_patterns; pattern_idx++)
|
||||
{
|
||||
Polygons result_polygons; // should remain empty, since we generate lines pattern!
|
||||
patterns[pattern_idx].polygons = ground_poly.offset(-line_width / 2);
|
||||
Polygons& result_lines = patterns[pattern_idx].lines;
|
||||
int outline_offset = -line_width;
|
||||
int line_distance = line_width;
|
||||
double fill_angle = 45 + pattern_idx * 90;
|
||||
Polygons& result_lines = patterns[pattern_idx];
|
||||
Polygons result_polygons; // should remain empty, since we generate lines pattern!
|
||||
Infill infill_comp(EFillMethod::LINES, ground_poly, outline_offset, line_width, line_distance, infill_overlap, fill_angle, z, extra_infill_shift);
|
||||
infill_comp.generate(result_polygons, result_lines);
|
||||
}
|
||||
@@ -104,9 +127,9 @@ void PrimeTower::generatePaths_denseInfill(const SliceDataStorage& storage)
|
||||
}
|
||||
|
||||
|
||||
void PrimeTower::addToGcode(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, bool wipe)
|
||||
void PrimeTower::addToGcode(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder)
|
||||
{
|
||||
if (!( storage.max_print_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0) )
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -125,43 +148,39 @@ void PrimeTower::addToGcode(const SliceDataStorage& storage, GCodePlanner& gcode
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
if (prev_extruder == gcodeLayer.getExtruder())
|
||||
bool pre_wipe = storage.meshgroup->getExtruderTrain(new_extruder)->getSettingBoolean("dual_pre_wipe");
|
||||
bool post_wipe = storage.meshgroup->getExtruderTrain(prev_extruder)->getSettingBoolean("prime_tower_wipe_enabled");
|
||||
|
||||
if (prev_extruder == new_extruder)
|
||||
{
|
||||
wipe = false;
|
||||
pre_wipe = false;
|
||||
post_wipe = false;
|
||||
}
|
||||
// pre-wipe:
|
||||
if (wipe)
|
||||
if (pre_wipe)
|
||||
{
|
||||
preWipe(storage, gcodeLayer, new_extruder);
|
||||
}
|
||||
|
||||
addToGcode_denseInfill(storage, gcodeLayer, gcode, layer_nr, prev_extruder);
|
||||
addToGcode_denseInfill(gcodeLayer, layer_nr, new_extruder);
|
||||
|
||||
// post-wipe:
|
||||
if (false && wipe) // TODO: make a separate setting for the post-wipe!
|
||||
if (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::addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder)
|
||||
void PrimeTower::addToGcode_denseInfill(GCodePlanner& gcodeLayer, const int layer_nr, const int extruder)
|
||||
{
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
ExtrusionMoves& pattern = patterns_per_extruder[extruder][((layer_nr % 2) + 2) % 2]; // +2) %2 to handle negative layer numbers
|
||||
|
||||
Polygons& pattern = patterns_per_extruder[new_extruder][layer_nr % 2];
|
||||
GCodePathConfig& config = config_per_extruder[extruder];
|
||||
|
||||
gcodeLayer.addPolygonsByOptimizer(pattern.polygons, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern.lines, &config, SpaceFillType::Lines);
|
||||
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
int start_idx = 0; // TODO: figure out which idx is closest to the far right corner
|
||||
|
||||
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;
|
||||
|
||||
CommandSocket::sendPolygons(PrintFeatureType::Support, pattern, config.getLineWidth());
|
||||
last_prime_tower_poly_printed[extruder] = layer_nr;
|
||||
}
|
||||
|
||||
Point PrimeTower::getLocationBeforePrimeTower(const SliceDataStorage& storage)
|
||||
@@ -194,37 +213,52 @@ Point PrimeTower::getLocationBeforePrimeTower(const SliceDataStorage& storage)
|
||||
|
||||
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
|
||||
wipe_from_middle = is_hollow;
|
||||
// only wipe from the middle of the prime tower if we have a z hop already on the first move after the layer switch
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
const ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
wipe_from_middle &= train.getSettingBoolean("retraction_hop_enabled")
|
||||
&& (!train.getSettingBoolean("retraction_hop_only_when_collides") || train.getSettingBoolean("retraction_hop_after_extruder_switch"));
|
||||
}
|
||||
|
||||
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)
|
||||
if (wipe_from_middle)
|
||||
{
|
||||
segment_start = prev;
|
||||
segment_end = closest_vert;
|
||||
// take the same start as end point so that the whole poly os covered.
|
||||
// find the inner polygon.
|
||||
segment_start = segment_end = PolygonUtils::findNearestVert(middle, ground_poly);
|
||||
}
|
||||
else
|
||||
{
|
||||
segment_start = closest_vert;
|
||||
segment_end = next;
|
||||
}
|
||||
// take the closer corner of the wipe tower and generate wipe locations on that side only:
|
||||
//
|
||||
// |
|
||||
// |
|
||||
// +-----
|
||||
// .
|
||||
// ^ nozzle switch location
|
||||
Point from = getLocationBeforePrimeTower(storage);
|
||||
|
||||
// TODO: come up with alternatives for better segments once the prime tower can be different shapes
|
||||
// find the single line segment closest to [from] pointing most toward [from]
|
||||
PolygonsPointIndex closest_vert = PolygonUtils::findNearestVert(from, ground_poly);
|
||||
PolygonsPointIndex prev = closest_vert.prev();
|
||||
PolygonsPointIndex next = closest_vert.next();
|
||||
int64_t prev_dot_score = dot(from - closest_vert.p(), turn90CCW(prev.p() - closest_vert.p()));
|
||||
int64_t next_dot_score = dot(from - closest_vert.p(), turn90CCW(closest_vert.p() - next.p()));
|
||||
if (prev_dot_score > next_dot_score)
|
||||
{
|
||||
segment_start = prev;
|
||||
segment_end = closest_vert;
|
||||
}
|
||||
else
|
||||
{
|
||||
segment_start = closest_vert;
|
||||
segment_end = next;
|
||||
}
|
||||
}
|
||||
|
||||
PolygonUtils::spreadDots(segment_start, segment_end, number_of_pre_wipe_locations, pre_wipe_locations);
|
||||
}
|
||||
@@ -240,16 +274,37 @@ void PrimeTower::preWipe(const SliceDataStorage& storage, GCodePlanner& gcode_la
|
||||
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
|
||||
if (wipe_from_middle)
|
||||
{
|
||||
// for hollow wipe tower:
|
||||
// start from above
|
||||
// go to wipe start
|
||||
// go to the Z height of the previous/current layer
|
||||
// wipe
|
||||
// go to normal layer height (automatically on the next extrusion move)...
|
||||
GCodePath& toward_middle = gcode_layer.addTravel(middle);
|
||||
toward_middle.perform_z_hop = true;
|
||||
gcode_layer.forceNewPathStart();
|
||||
GCodePath& toward_wipe_start = gcode_layer.addTravel_simple(start);
|
||||
toward_wipe_start.perform_z_hop = false;
|
||||
toward_wipe_start.retract = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.addTravel(start);
|
||||
}
|
||||
float flow = 0.0001; // force this path being interpreted as an extrusion path, so that no Z hop will occur (TODO: really separately handle travel and extrusion moves)
|
||||
gcode_layer.addExtrusionMove(end, &config_per_extruder[extruder_nr], SpaceFillType::None, flow);
|
||||
}
|
||||
|
||||
void PrimeTower::subtractFromSupport(SliceDataStorage& storage)
|
||||
{
|
||||
const Polygons outside_polygon = ground_poly.getOutsidePolygons();
|
||||
for(size_t layer = 0; layer <= (size_t)storage.max_print_height_second_to_last_extruder + 1 && layer < storage.support.supportLayers.size(); layer++)
|
||||
{
|
||||
storage.support.supportLayers[layer].supportAreas = storage.support.supportLayers[layer].supportAreas.difference(outside_polygon);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+48
-20
@@ -1,3 +1,6 @@
|
||||
//Copyright (c) 2016 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef PRIME_TOWER_H
|
||||
#define PRIME_TOWER_H
|
||||
|
||||
@@ -16,8 +19,6 @@ class SliceDataStorage;
|
||||
class GCodePlanner;
|
||||
class GCodeExport;
|
||||
|
||||
typedef std::vector<IntPoint> PolyLine;
|
||||
|
||||
/*!
|
||||
* Class for everything to do with the prime tower:
|
||||
* - generating the areas
|
||||
@@ -27,22 +28,41 @@ typedef std::vector<IntPoint> PolyLine;
|
||||
class PrimeTower
|
||||
{
|
||||
private:
|
||||
struct ExtrusionMoves
|
||||
{
|
||||
Polygons polygons;
|
||||
Polygons lines;
|
||||
};
|
||||
int extruder_count; //!< number of extruders
|
||||
std::vector<GCodePathConfig> config_per_extruder; //!< Path config for prime tower for each extruder
|
||||
|
||||
bool is_hollow; //!< Whether the prime tower is hollow
|
||||
|
||||
bool wipe_from_middle; //!< Whether to wipe on the inside of the hollow prime tower
|
||||
Point middle; //!< The middle of the prime tower
|
||||
|
||||
Point post_wipe_point; //!< location to post-wipe the unused nozzle off on
|
||||
|
||||
std::vector<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
|
||||
const unsigned int pre_wipe_location_skip = 13; //!< How big the steps are when stepping through \ref PrimeTower::wipe_locations
|
||||
const unsigned int number_of_pre_wipe_locations = 21; //!< The required size of \ref PrimeTower::wipe_locations
|
||||
// note that the above are two consecutive numbers in the Fibonacci sequence
|
||||
int current_pre_wipe_location_idx; //!< Index into \ref PrimeTower::wipe_locations of where to pre-wipe the nozzle
|
||||
|
||||
public:
|
||||
bool enabled; //!< Whether the prime tower is enabled.
|
||||
Polygons ground_poly; //!< The outline of the prime tower to be used for each layer
|
||||
|
||||
std::vector<std::vector<ExtrusionMoves>> patterns_per_extruder; //!< for each extruder a vector of patterns to alternate between, over the layers
|
||||
|
||||
/*!
|
||||
* \brief Creates a prime tower instance that will determine where and how
|
||||
* the prime tower gets printed.
|
||||
*
|
||||
* \param storage A storage where it retrieves the prime tower settings.
|
||||
*/
|
||||
PrimeTower(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Initialize \ref PrimeTower::config_per_extruder with speed and line width settings.
|
||||
*
|
||||
@@ -61,21 +81,19 @@ public:
|
||||
/*!
|
||||
* Generate the prime tower area to be used on each layer
|
||||
*
|
||||
* Fills \ref PrimeTower::ground_poly and sets \ref PrimeTower::middle
|
||||
*
|
||||
* \param storage Where to retrieve prime tower settings from
|
||||
*/
|
||||
void generateGroundpoly(const SliceDataStorage& storage);
|
||||
|
||||
std::vector<std::vector<Polygons>> patterns_per_extruder; //!< for each extruder a vector of patterns to alternate between, over the layers
|
||||
|
||||
/*!
|
||||
* 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(const SliceDataStorage& storage, unsigned int total_layers);
|
||||
|
||||
PrimeTower(); //!< basic constructor
|
||||
void generatePaths(const SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Add path plans for the prime tower to the \p gcode_layer
|
||||
@@ -84,9 +102,18 @@ public:
|
||||
* \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)
|
||||
* \param new_extruder The switched to extruder with which the prime tower paths should be generated.
|
||||
*/
|
||||
void addToGcode(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, bool wipe);
|
||||
void addToGcode(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder);
|
||||
|
||||
/*!
|
||||
* \brief Subtract the prime tower from the support areas in storage.
|
||||
*
|
||||
* \param storage The storage where to find the support from which to
|
||||
* subtract a prime tower.
|
||||
*/
|
||||
void subtractFromSupport(SliceDataStorage& storage);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Layer number of the last layer in which a prime tower has been printed per extruder train.
|
||||
@@ -114,7 +141,8 @@ private:
|
||||
/*!
|
||||
* \see WipeTower::generatePaths
|
||||
*
|
||||
* Generate the area where the prime tower should be.
|
||||
* Generate the extrude paths for each extruder on even and odd layers
|
||||
* Fill the ground poly with dense infill.
|
||||
*
|
||||
* \param storage where to get settings from
|
||||
* \param total_layers The total number of layers
|
||||
@@ -123,15 +151,15 @@ private:
|
||||
|
||||
/*!
|
||||
* \see PrimeTower::addToGcode
|
||||
*
|
||||
*
|
||||
* Add path plans for the prime tower to the \p gcode_layer
|
||||
*
|
||||
* \param storage where to get settings from; where to get the maximum height of the prime tower from
|
||||
*
|
||||
* \param[in,out] gcode_layer Where to get the current extruder from; where to store the generated layer paths
|
||||
* \param layer_nr The layer for which to generate the prime tower paths
|
||||
* \param prev_extruder The previous extruder with which paths were planned; from which extruder a switch was made
|
||||
* \param extruder The extruder we just switched to, with which the prime
|
||||
* tower paths should be drawn.
|
||||
*/
|
||||
void addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder);
|
||||
void addToGcode_denseInfill(GCodePlanner& gcode_layer, const int layer_nr, const int extruder);
|
||||
|
||||
/*!
|
||||
* Plan the moves for wiping the current nozzles oozed material before starting to print the prime tower.
|
||||
|
||||
+10
-3
@@ -1,4 +1,7 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
//Copyright (C) 2013 David Braam
|
||||
//Copyright (c) 2016 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "SkirtBrim.h"
|
||||
#include "support.h"
|
||||
|
||||
@@ -19,7 +22,7 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const unsigned i
|
||||
{ // 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
|
||||
first_layer_outline = first_layer_outline.unionPolygons(); //To guard against overlapping outlines, which would produce holes according to the even-odd rule.
|
||||
Polygons first_layer_empty_holes;
|
||||
if (outside_only)
|
||||
{
|
||||
@@ -35,7 +38,7 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const unsigned i
|
||||
// || || ||[]|| > 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
|
||||
Polygons model_brim_covered_area = first_layer_outline.offset(primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2), ClipperLib::jtRound); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides
|
||||
if (outside_only)
|
||||
{ // don't remove support within empty holes where no brim is generated.
|
||||
model_brim_covered_area.add(first_layer_empty_holes);
|
||||
@@ -45,6 +48,10 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const unsigned i
|
||||
first_layer_outline.add(support_layer.supportAreas);
|
||||
first_layer_outline.add(support_layer.skin);
|
||||
}
|
||||
if (storage.primeTower.enabled)
|
||||
{
|
||||
first_layer_outline.add(storage.primeTower.ground_poly); // don't remove parts of the prime tower, but make a brim for it
|
||||
}
|
||||
}
|
||||
constexpr int join_distance = 20;
|
||||
first_layer_outline = first_layer_outline.offset(join_distance).offset(-join_distance); // merge adjacent models into single polygon
|
||||
|
||||
+2
-2
@@ -23,9 +23,9 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
|
||||
std::vector<cura::Slicer*> slicerList;
|
||||
|
||||
for(Mesh& mesh : meshgroup->meshes)
|
||||
for (Mesh* mesh : meshgroup->meshes)
|
||||
{
|
||||
cura::Slicer* slicer = new cura::Slicer(&mesh, initial_layer_thickness, connectionHeight, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
|
||||
cura::Slicer* slicer = new cura::Slicer(mesh, initial_layer_thickness, connectionHeight, layer_count, mesh->getSettingBoolean("meshfix_keep_open_polygons"), mesh->getSettingBoolean("meshfix_extensive_stitching"), nullptr);
|
||||
slicerList.push_back(slicer);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
#include "settings/settings.h"
|
||||
|
||||
#include "MeshGroup.h"
|
||||
#include "slicer.h"
|
||||
#include "slicer/Slicer.h"
|
||||
|
||||
#include "utils/NoCopy.h"
|
||||
#include "utils/polygon.h"
|
||||
|
||||
+11
-10
@@ -18,8 +18,9 @@ void Wireframe2gcode::writeGCode()
|
||||
{
|
||||
|
||||
gcode.preSetup(wireFrame.meshgroup);
|
||||
|
||||
gcode.setInitialTemps(*wireFrame.meshgroup);
|
||||
|
||||
const unsigned int start_extruder_nr = getSettingAsIndex("adhesion_extruder_nr"); // TODO: figure out how Wireframe works with dual extrusion
|
||||
gcode.setInitialTemps(*wireFrame.meshgroup, start_extruder_nr);
|
||||
|
||||
if (CommandSocket::getInstance())
|
||||
CommandSocket::getInstance()->beginGCode();
|
||||
@@ -159,7 +160,7 @@ void Wireframe2gcode::writeGCode()
|
||||
|
||||
gcode.setZ(maxObjectHeight);
|
||||
|
||||
gcode.writeRetraction(&standard_retraction_config);
|
||||
gcode.writeRetraction(standard_retraction_config);
|
||||
|
||||
|
||||
gcode.updateTotalPrintTime();
|
||||
@@ -246,7 +247,7 @@ void Wireframe2gcode::strategy_retract(WeaveLayer& layer, WeaveConnectionPart& p
|
||||
retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
|
||||
|
||||
double top_retract_pause = 2.0;
|
||||
int retract_hop_dist = 1000;
|
||||
coord_t retract_hop_dist = 1000;
|
||||
bool after_retract_hop = false;
|
||||
//bool go_horizontal_first = true;
|
||||
bool lower_retract_start = true;
|
||||
@@ -259,7 +260,7 @@ void Wireframe2gcode::strategy_retract(WeaveLayer& layer, WeaveConnectionPart& p
|
||||
Point3 lowering = vec * retract_hop_dist / 2 / vec.vSize();
|
||||
Point3 lower = to - lowering;
|
||||
gcode.writeMove(lower, speedUp, extrusion_mm3_per_mm_connection);
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
gcode.writeRetraction(retraction_config);
|
||||
gcode.writeMove(to + lowering, speedUp, 0);
|
||||
gcode.writeDelay(top_retract_pause);
|
||||
if (after_retract_hop)
|
||||
@@ -268,7 +269,7 @@ void Wireframe2gcode::strategy_retract(WeaveLayer& layer, WeaveConnectionPart& p
|
||||
} else
|
||||
{
|
||||
gcode.writeMove(to, speedUp, extrusion_mm3_per_mm_connection);
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
gcode.writeRetraction(retraction_config);
|
||||
gcode.writeMove(to + Point3(0, 0, retract_hop_dist), speedFlat, 0);
|
||||
gcode.writeDelay(top_retract_pause);
|
||||
if (after_retract_hop)
|
||||
@@ -467,14 +468,14 @@ void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polyg
|
||||
void Wireframe2gcode::writeMoveWithRetract(Point3 to)
|
||||
{
|
||||
if ((gcode.getPosition() - to).vSize2() >= nozzle_top_diameter * nozzle_top_diameter * 2 * 2)
|
||||
gcode.writeRetraction(&standard_retraction_config);
|
||||
gcode.writeRetraction(standard_retraction_config);
|
||||
gcode.writeMove(to, moveSpeed, 0);
|
||||
}
|
||||
|
||||
void Wireframe2gcode::writeMoveWithRetract(Point to)
|
||||
{
|
||||
if (vSize2(gcode.getPositionXY() - to) >= nozzle_top_diameter * nozzle_top_diameter * 2 * 2)
|
||||
gcode.writeRetraction(&standard_retraction_config);
|
||||
gcode.writeRetraction(standard_retraction_config);
|
||||
gcode.writeMove(to, moveSpeed, 0);
|
||||
}
|
||||
|
||||
@@ -562,7 +563,7 @@ void Wireframe2gcode::processStartingCode()
|
||||
{
|
||||
if (getSettingBoolean("material_bed_temp_prepend"))
|
||||
{
|
||||
if (getSettingBoolean("machine_heated_bed") && getSettingInDegreeCelsius("material_bed_temperature") > 0)
|
||||
if (getSettingBoolean("machine_heated_bed") && getSettingInDegreeCelsius("material_bed_temperature") != 0)
|
||||
{
|
||||
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature"), getSettingBoolean("material_bed_temp_wait"));
|
||||
}
|
||||
@@ -603,7 +604,7 @@ void Wireframe2gcode::processStartingCode()
|
||||
constexpr bool wait = true;
|
||||
gcode.writeTemperatureCommand(start_extruder_nr, getSettingInDegreeCelsius("material_print_temperature"), wait);
|
||||
gcode.writePrimeTrain(getSettingInMillimetersPerSecond("speed_travel"));
|
||||
gcode.writeRetraction(&standard_retraction_config);
|
||||
gcode.writeRetraction(standard_retraction_config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -11,7 +11,7 @@
|
||||
#include "settings/settings.h"
|
||||
|
||||
#include "MeshGroup.h"
|
||||
#include "slicer.h"
|
||||
#include "slicer/Slicer.h"
|
||||
|
||||
#include "utils/polygon.h"
|
||||
#include "Weaver.h"
|
||||
@@ -29,37 +29,37 @@ private:
|
||||
static const int STRATEGY_KNOT = 1;
|
||||
static const int STRATEGY_RETRACT = 2;
|
||||
|
||||
int initial_layer_thickness;
|
||||
int filament_diameter;
|
||||
int line_width;
|
||||
coord_t initial_layer_thickness;
|
||||
coord_t filament_diameter;
|
||||
coord_t line_width;
|
||||
double flowConnection;
|
||||
double flowFlat;
|
||||
double extrusion_mm3_per_mm_connection;
|
||||
double extrusion_mm3_per_mm_flat;
|
||||
int nozzle_outer_diameter;
|
||||
int nozzle_head_distance;
|
||||
coord_t nozzle_outer_diameter;
|
||||
coord_t nozzle_head_distance;
|
||||
double nozzle_expansion_angle;
|
||||
int nozzle_clearance;
|
||||
int nozzle_top_diameter;
|
||||
coord_t nozzle_clearance;
|
||||
coord_t nozzle_top_diameter;
|
||||
double moveSpeed;
|
||||
double speedBottom;
|
||||
double speedUp;
|
||||
double speedDown;
|
||||
double speedFlat;
|
||||
int connectionHeight;
|
||||
int roof_inset;
|
||||
coord_t connectionHeight;
|
||||
coord_t roof_inset;
|
||||
double flat_delay;
|
||||
double bottom_delay;
|
||||
double top_delay;
|
||||
int up_dist_half_speed;
|
||||
int top_jump_dist;
|
||||
int fall_down;
|
||||
int drag_along;
|
||||
coord_t up_dist_half_speed;
|
||||
coord_t top_jump_dist;
|
||||
coord_t fall_down;
|
||||
coord_t drag_along;
|
||||
int strategy;
|
||||
double go_back_to_last_top;
|
||||
int straight_first_when_going_down;
|
||||
int roof_fall_down;
|
||||
int roof_drag_along;
|
||||
coord_t straight_first_when_going_down;
|
||||
coord_t roof_fall_down;
|
||||
coord_t roof_drag_along;
|
||||
double roof_outer_delay;
|
||||
|
||||
RetractionConfig standard_retraction_config; //!< The standard retraction settings used for moves between parts etc.
|
||||
|
||||
@@ -346,9 +346,9 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
continue;
|
||||
}
|
||||
const ExtruderTrain* settings_base = meshgroup->getExtruderTrain(extruder_nr); //The extruder train that the setting should fall back to.
|
||||
for (Mesh& mesh : meshgroup->meshes)
|
||||
for (Mesh* mesh : meshgroup->meshes)
|
||||
{
|
||||
mesh.setSettingInheritBase(setting_extruder.name(), *settings_base);
|
||||
mesh->setSettingInheritBase(setting_extruder.name(), *settings_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,8 +455,8 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list, const google
|
||||
}
|
||||
SettingsBase* extruder_train = meshgroup->getExtruderTrain(extruder_train_nr);
|
||||
|
||||
meshgroup->meshes.push_back(extruder_train); //Construct a new mesh (with the corresponding extruder train as settings parent object) and put it into MeshGroup's mesh list.
|
||||
Mesh& mesh = meshgroup->meshes.back();
|
||||
meshgroup->meshes.push_back(new Mesh(extruder_train)); //Construct a new mesh (with the corresponding extruder train as settings parent object) and put it into MeshGroup's mesh list.
|
||||
Mesh& mesh = *meshgroup->meshes.back();
|
||||
|
||||
for (int i = 0; i < face_count; ++i)
|
||||
{
|
||||
|
||||
+71
-31
@@ -11,6 +11,8 @@
|
||||
|
||||
namespace cura {
|
||||
|
||||
double layer_height; //!< report basic layer height in RepRap gcode file.
|
||||
|
||||
GCodeExport::GCodeExport()
|
||||
: output_stream(&std::cout)
|
||||
, currentPosition(0,0,MM2INT(20))
|
||||
@@ -34,6 +36,8 @@ GCodeExport::GCodeExport()
|
||||
initial_bed_temp = 0;
|
||||
|
||||
extruder_count = 0;
|
||||
|
||||
total_bounding_box = AABB3D();
|
||||
}
|
||||
|
||||
GCodeExport::~GCodeExport()
|
||||
@@ -47,24 +51,29 @@ void GCodeExport::preSetup(const MeshGroup* meshgroup)
|
||||
|
||||
extruder_count = meshgroup->getSettingAsCount("machine_extruder_count");
|
||||
|
||||
for (const Mesh& mesh : meshgroup->meshes)
|
||||
for (const Mesh* mesh : meshgroup->meshes)
|
||||
{
|
||||
extruder_attr[mesh.getSettingAsIndex("extruder_nr")].is_used = true;
|
||||
if (!mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh->getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
extruder_attr[mesh->getSettingAsIndex("extruder_nr")].is_used = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
const ExtruderTrain* train = meshgroup->getExtruderTrain(extruder_nr);
|
||||
|
||||
if (meshgroup->getSettingAsIndex("adhesion_extruder_nr") == int(extruder_nr))
|
||||
if (meshgroup->getSettingAsIndex("adhesion_extruder_nr") == int(extruder_nr) && meshgroup->getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
extruder_attr[extruder_nr].is_used = true;
|
||||
}
|
||||
for (const Mesh& mesh : meshgroup->meshes)
|
||||
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))
|
||||
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;
|
||||
@@ -90,6 +99,8 @@ void GCodeExport::preSetup(const MeshGroup* meshgroup)
|
||||
|
||||
machine_name = meshgroup->getSettingString("machine_name");
|
||||
|
||||
layer_height = meshgroup->getSettingInMillimeters("layer_height");
|
||||
|
||||
if (flavor == EGCodeFlavor::BFB)
|
||||
{
|
||||
new_line = "\r\n";
|
||||
@@ -102,17 +113,19 @@ void GCodeExport::preSetup(const MeshGroup* meshgroup)
|
||||
estimateCalculator.setFirmwareDefaults(meshgroup);
|
||||
}
|
||||
|
||||
void GCodeExport::setInitialTemps(const MeshGroup& settings)
|
||||
void GCodeExport::setInitialTemps(const MeshGroup& settings, const unsigned int start_extruder_nr)
|
||||
{
|
||||
for (unsigned int extr_nr = 0; extr_nr < extruder_count; extr_nr++)
|
||||
{
|
||||
const ExtruderTrain* extr_train = settings.getExtruderTrain(extr_nr);
|
||||
assert(extr_train);
|
||||
double temp = extr_train->getSettingInDegreeCelsius((extr_nr == 0)? "material_print_temperature" : "material_standby_temperature");
|
||||
const ExtruderTrain& train = *settings.getExtruderTrain(extr_nr);
|
||||
|
||||
double print_temp_0 = train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
double print_temp_here = (print_temp_0 != 0)? print_temp_0 : train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
double temp = (extr_nr == start_extruder_nr)? print_temp_here : train.getSettingInDegreeCelsius("material_standby_temperature");
|
||||
setInitialTemp(extr_nr, temp);
|
||||
}
|
||||
|
||||
initial_bed_temp = settings.getSettingInDegreeCelsius("material_bed_temperature");
|
||||
initial_bed_temp = settings.getSettingInDegreeCelsius("material_bed_temperature_layer_0");
|
||||
}
|
||||
|
||||
void GCodeExport::setInitialTemp(int extruder_nr, double temp)
|
||||
@@ -163,12 +176,12 @@ std::string GCodeExport::getFileHeader(const double* print_time, const std::vect
|
||||
prefix << ";PRINT.TIME:" << static_cast<int>(*print_time) << new_line;
|
||||
}
|
||||
|
||||
prefix << ";PRINT.SIZE.MIN.X:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Y:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Z:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.X:" << INT2MM(machine_dimensions.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Y:" << INT2MM(machine_dimensions.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Z:" << INT2MM(machine_dimensions.z) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.X:" << INT2MM(total_bounding_box.min.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Y:" << INT2MM(total_bounding_box.min.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Z:" << INT2MM(total_bounding_box.min.z) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.X:" << INT2MM(total_bounding_box.max.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Y:" << INT2MM(total_bounding_box.max.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Z:" << INT2MM(total_bounding_box.max.z) << new_line;
|
||||
prefix << ";END_OF_HEADER" << new_line;
|
||||
return prefix.str();
|
||||
default:
|
||||
@@ -182,6 +195,11 @@ std::string GCodeExport::getFileHeader(const double* print_time, const std::vect
|
||||
prefix << ";NOZZLE_DIAMETER:" << float(INT2MM(getNozzleSize(0))) << new_line;
|
||||
// TODO: the second nozzle size isn't always initiated! ";NOZZLE_DIAMETER2:"
|
||||
}
|
||||
else if (flavor == EGCodeFlavor::REPRAP)
|
||||
{
|
||||
prefix << ";Filament used: " << ((filament_used.size() >= 1)? filament_used[0] / (1000 * extruder_attr[0].filament_area) : 0) << "m" << new_line;
|
||||
prefix << ";Layer height: " << layer_height << new_line;
|
||||
}
|
||||
return prefix.str();
|
||||
}
|
||||
}
|
||||
@@ -538,6 +556,8 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(300)); // no crazy positions (this code should not be compiled for release)
|
||||
#endif //ASSERT_INSANE_OUTPUT
|
||||
|
||||
total_bounding_box.include(Point3(x, y, z));
|
||||
|
||||
if (extrusion_mm3_per_mm < 0)
|
||||
logWarning("Warning! Negative extrusion move!");
|
||||
|
||||
@@ -623,7 +643,7 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), speed);
|
||||
}
|
||||
|
||||
void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool extruder_switch)
|
||||
void GCodeExport::writeRetraction(const RetractionConfig& config, bool force, bool extruder_switch)
|
||||
{
|
||||
ExtruderTrainAttributes& extr_attr = extruder_attr[current_extruder];
|
||||
|
||||
@@ -640,7 +660,7 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool ext
|
||||
}
|
||||
|
||||
double old_retraction_e_amount = extr_attr.retraction_e_amount_current;
|
||||
double new_retraction_e_amount = mmToE(config->distance);
|
||||
double new_retraction_e_amount = mmToE(config.distance);
|
||||
double retraction_diff_e_amount = old_retraction_e_amount - new_retraction_e_amount;
|
||||
if (std::abs(retraction_diff_e_amount) < 0.000001)
|
||||
{
|
||||
@@ -650,23 +670,23 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool ext
|
||||
{ // handle retraction limitation
|
||||
double current_extruded_volume = getCurrentExtrudedVolume();
|
||||
std::deque<double>& extruded_volume_at_previous_n_retractions = extr_attr.extruded_volume_at_previous_n_retractions;
|
||||
while (int(extruded_volume_at_previous_n_retractions.size()) > config->retraction_count_max && !extruded_volume_at_previous_n_retractions.empty())
|
||||
while (int(extruded_volume_at_previous_n_retractions.size()) > config.retraction_count_max && !extruded_volume_at_previous_n_retractions.empty())
|
||||
{
|
||||
// extruder switch could have introduced data which falls outside the retraction window
|
||||
// also the retraction_count_max could have changed between the last retraction and this
|
||||
extruded_volume_at_previous_n_retractions.pop_back();
|
||||
}
|
||||
if (!force && config->retraction_count_max <= 0)
|
||||
if (!force && config.retraction_count_max <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!force && int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max
|
||||
&& current_extruded_volume < extruded_volume_at_previous_n_retractions.back() + config->retraction_extrusion_window * extr_attr.filament_area)
|
||||
if (!force && int(extruded_volume_at_previous_n_retractions.size()) == config.retraction_count_max
|
||||
&& current_extruded_volume < extruded_volume_at_previous_n_retractions.back() + config.retraction_extrusion_window * extr_attr.filament_area)
|
||||
{
|
||||
return;
|
||||
}
|
||||
extruded_volume_at_previous_n_retractions.push_front(current_extruded_volume);
|
||||
if (int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max + 1)
|
||||
if (int(extruded_volume_at_previous_n_retractions.size()) == config.retraction_count_max + 1)
|
||||
{
|
||||
extruded_volume_at_previous_n_retractions.pop_back();
|
||||
}
|
||||
@@ -689,17 +709,17 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool ext
|
||||
}
|
||||
else
|
||||
{
|
||||
double speed = ((retraction_diff_e_amount < 0.0)? config->speed : extr_attr.last_retraction_prime_speed) * 60;
|
||||
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" << 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;
|
||||
extr_attr.last_retraction_prime_speed = config.primeSpeed;
|
||||
}
|
||||
|
||||
extr_attr.retraction_e_amount_current = new_retraction_e_amount; // suppose that for UM2 the retraction amount in the firmware is equal to the provided amount
|
||||
extr_attr.prime_volume += config->prime_volume;
|
||||
extr_attr.prime_volume += config.prime_volume;
|
||||
|
||||
}
|
||||
|
||||
@@ -709,6 +729,16 @@ void GCodeExport::writeZhopStart(int hop_height)
|
||||
{
|
||||
isZHopped = hop_height;
|
||||
*output_stream << "G1 Z" << MMtoStream{currentPosition.z + isZHopped} << new_line;
|
||||
total_bounding_box.include(currentPosition + Point3(0, 0, isZHopped));
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeExport::writeZhopEnd()
|
||||
{
|
||||
if (isZHopped)
|
||||
{
|
||||
isZHopped = 0;
|
||||
*output_stream << "G1 Z" << MMtoStream{currentPosition.z} << new_line;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,7 +776,7 @@ void GCodeExport::switchExtruder(int new_extruder, const RetractionConfig& retra
|
||||
|
||||
bool force = true;
|
||||
bool extruder_switch = true;
|
||||
writeRetraction(&const_cast<RetractionConfig&>(retraction_config_old_extruder), force, extruder_switch);
|
||||
writeRetraction(const_cast<RetractionConfig&>(retraction_config_old_extruder), force, extruder_switch);
|
||||
|
||||
resetExtrusionValue(); // zero the E value on the old extruder, so that the current_e_value is registered on the old extruder
|
||||
|
||||
@@ -813,7 +843,12 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
|
||||
{
|
||||
if (!wait && extruder_attr[extruder].currentTemperature == temperature)
|
||||
return;
|
||||
|
||||
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE)
|
||||
{ // The UM2 family doesn't support temperature commands (they are fixed in the firmware)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait)
|
||||
*output_stream << "M109";
|
||||
else
|
||||
@@ -829,6 +864,11 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
|
||||
|
||||
void GCodeExport::writeBedTemperatureCommand(double temperature, bool wait)
|
||||
{
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE)
|
||||
{ // The UM2 family doesn't support temperature commands (they are fixed in the firmware)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait)
|
||||
*output_stream << "M190 S";
|
||||
else
|
||||
@@ -868,7 +908,7 @@ 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;
|
||||
*output_stream << "M203 Z" << PrecisionedDouble{2, max_z_feedrate} << new_line;
|
||||
current_max_z_feedrate = max_z_feedrate;
|
||||
estimateCalculator.setMaxZFeedrate(max_z_feedrate);
|
||||
}
|
||||
|
||||
+14
-2
@@ -99,6 +99,8 @@ private:
|
||||
double current_jerk; //!< The current jerk in the XY direction (in mm/s^3)
|
||||
double current_max_z_feedrate; //!< The current max z speed
|
||||
|
||||
AABB3D total_bounding_box; //!< The bounding box of all g-code.
|
||||
|
||||
/*!
|
||||
* The z position to be used on the next xy move, if the head wasn't in the correct z position yet.
|
||||
*
|
||||
@@ -259,7 +261,7 @@ private:
|
||||
*/
|
||||
void writeMoveBFB(int x, int y, int z, double speed, double extrusion_mm3_per_mm);
|
||||
public:
|
||||
void writeRetraction(RetractionConfig* config, bool force = false, bool extruder_switch = false);
|
||||
void writeRetraction(const RetractionConfig& config, bool force = false, bool extruder_switch = false);
|
||||
|
||||
/*!
|
||||
* Start a z hop with the given \p hop_height
|
||||
@@ -268,6 +270,12 @@ public:
|
||||
*/
|
||||
void writeZhopStart(int hop_height);
|
||||
|
||||
/*!
|
||||
* End a z hop: go back to the layer height
|
||||
*
|
||||
*/
|
||||
void writeZhopEnd();
|
||||
|
||||
/*!
|
||||
* Start the new_extruder:
|
||||
* - set new extruder
|
||||
@@ -341,13 +349,17 @@ public:
|
||||
* See FffGcodeWriter::processStartingCode
|
||||
*
|
||||
* \param settings The meshgroup to get the global bed temp from and to get the extruder trains from which to get the nozzle temperatures
|
||||
* \param start_extruder_nr The extruder with which to start this print
|
||||
*/
|
||||
void setInitialTemps(const MeshGroup& settings);
|
||||
void setInitialTemps(const MeshGroup& settings, const unsigned int start_extruder_nr);
|
||||
|
||||
/*!
|
||||
* Override or set an initial nozzle temperature as written by GCodeExport::setInitialTemps
|
||||
* This is used primarily during better specification of temperatures in LayerPlanBuffer::insertPreheatCommand
|
||||
*
|
||||
* \warning This function must be called before any of the layers in the meshgroup are written to file!
|
||||
* That's because it sets the current temperature in the gcode!
|
||||
*
|
||||
* \param extruder_nr The extruder number for which to better specify the temp
|
||||
* \param temp The temp at which the nozzle should be at startup
|
||||
*/
|
||||
|
||||
+102
-48
@@ -5,28 +5,19 @@
|
||||
#include "sliceDataStorage.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "MergeInfillLines.h"
|
||||
#include "raft.h" // getTotalExtraLayers
|
||||
|
||||
namespace cura {
|
||||
|
||||
TimeMaterialEstimates TimeMaterialEstimates::operator-(const TimeMaterialEstimates& other)
|
||||
{
|
||||
return TimeMaterialEstimates(extrude_time - other.extrude_time,unretracted_travel_time - other.unretracted_travel_time,retracted_travel_time - other.retracted_travel_time,material - other.material);
|
||||
}
|
||||
|
||||
TimeMaterialEstimates& TimeMaterialEstimates::operator-=(const TimeMaterialEstimates& other)
|
||||
{
|
||||
extrude_time -= other.extrude_time;
|
||||
unretracted_travel_time -= other.unretracted_travel_time;
|
||||
retracted_travel_time -= other.retracted_travel_time;
|
||||
material -= other.material;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ExtruderPlan::ExtruderPlan(int extruder, Point start_position, int layer_nr, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config)
|
||||
ExtruderPlan::ExtruderPlan(int extruder, Point start_position, int layer_nr, bool is_initial_layer, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config)
|
||||
: extruder(extruder)
|
||||
, required_temp(-1)
|
||||
, heated_pre_travel_time(0)
|
||||
, initial_printing_temperature(-1)
|
||||
, printing_temperature(-1)
|
||||
, start_position(start_position)
|
||||
, layer_nr(layer_nr)
|
||||
, is_initial_layer(is_initial_layer)
|
||||
, layer_thickness(layer_thickness)
|
||||
, fan_speed_layer_time_settings(fan_speed_layer_time_settings)
|
||||
, retraction_config(retraction_config)
|
||||
@@ -73,6 +64,7 @@ GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, SpaceF
|
||||
paths.emplace_back();
|
||||
GCodePath* ret = &paths.back();
|
||||
ret->retract = false;
|
||||
ret->perform_prime = false;
|
||||
ret->perform_z_hop = false;
|
||||
ret->config = config;
|
||||
ret->done = false;
|
||||
@@ -89,9 +81,10 @@ void GCodePlanner::forceNewPathStart()
|
||||
paths[paths.size()-1].done = true;
|
||||
}
|
||||
|
||||
GCodePlanner::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, bool is_inside_mesh, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
GCodePlanner::GCodePlanner(SliceDataStorage& storage, int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, bool is_inside_mesh, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
, layer_nr(layer_nr)
|
||||
, is_initial_layer(layer_nr == 0 - Raft::getTotalExtraLayers(storage))
|
||||
, z(z)
|
||||
, layer_thickness(layer_thickness)
|
||||
, start_position(last_position)
|
||||
@@ -102,7 +95,7 @@ GCodePlanner::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int
|
||||
, fan_speed_layer_time_settings_per_extruder(fan_speed_layer_time_settings_per_extruder)
|
||||
{
|
||||
extruder_plans.reserve(storage.meshgroup->getExtruderCount());
|
||||
extruder_plans.emplace_back(current_extruder, start_position, layer_nr, layer_thickness, fan_speed_layer_time_settings_per_extruder[current_extruder], storage.retraction_config_per_extruder[current_extruder]);
|
||||
extruder_plans.emplace_back(current_extruder, start_position, layer_nr, is_initial_layer, layer_thickness, fan_speed_layer_time_settings_per_extruder[current_extruder], storage.retraction_config_per_extruder[current_extruder]);
|
||||
comb = nullptr;
|
||||
was_inside = is_inside_mesh;
|
||||
is_inside = false; // assumes the next move will not be to inside a layer part (overwritten just before going into a layer part)
|
||||
@@ -202,7 +195,8 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
}
|
||||
else
|
||||
{
|
||||
extruder_plans.emplace_back(extruder, lastPosition, layer_nr, layer_thickness, fan_speed_layer_time_settings_per_extruder[extruder], storage.retraction_config_per_extruder[extruder]);
|
||||
extruder_plans.emplace_back(extruder, lastPosition, layer_nr, is_initial_layer, layer_thickness, fan_speed_layer_time_settings_per_extruder[extruder], storage.retraction_config_per_extruder[extruder]);
|
||||
assert((int)extruder_plans.size() <= storage.meshgroup->getExtruderCount() && "Never use the same extruder twice on one layer!");
|
||||
}
|
||||
last_planned_extruder_setting_base = storage.meshgroup->getExtruderTrain(extruder);
|
||||
|
||||
@@ -244,7 +238,7 @@ void GCodePlanner::moveInsideCombBoundary(int distance)
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::addTravel(Point p)
|
||||
GCodePath& GCodePlanner::addTravel(Point p)
|
||||
{
|
||||
GCodePath* path = nullptr;
|
||||
GCodePathConfig& travel_config = storage.travel_config_per_extruder[getExtruder()];
|
||||
@@ -330,11 +324,12 @@ void GCodePlanner::addTravel(Point p)
|
||||
}
|
||||
}
|
||||
|
||||
addTravel_simple(p, path);
|
||||
GCodePath& ret = addTravel_simple(p, path);
|
||||
was_inside = is_inside;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
GCodePath& GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
{
|
||||
if (path == nullptr)
|
||||
{
|
||||
@@ -342,8 +337,17 @@ void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
}
|
||||
path->points.push_back(p);
|
||||
lastPosition = p;
|
||||
return *path;
|
||||
}
|
||||
|
||||
void GCodePlanner::planPrime()
|
||||
{
|
||||
forceNewPathStart();
|
||||
GCodePath& prime_travel = addTravel_simple(lastPosition + Point(0, 100));
|
||||
prime_travel.retract = false;
|
||||
prime_travel.perform_prime = true;
|
||||
forceNewPathStart();
|
||||
}
|
||||
|
||||
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow, bool spiralize)
|
||||
{
|
||||
@@ -351,22 +355,51 @@ void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillT
|
||||
lastPosition = p;
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, bool spiralize)
|
||||
void GCodePlanner::addPolygon(Polygons& polygons, unsigned int poly_idx, int start_idx, GCodePathConfig* config, PolygonFlowAdjuster* wall_overlap_computation, coord_t wall_0_wipe_dist, bool spiralize)
|
||||
{
|
||||
Point p0 = polygon[startIdx];
|
||||
PolygonRef polygon = polygons[poly_idx];
|
||||
unsigned int p0_idx = start_idx;
|
||||
Point p0 = polygon[p0_idx];
|
||||
addTravel(p0);
|
||||
for(unsigned int i=1; i<polygon.size(); i++)
|
||||
for (unsigned int point_idx = 1; point_idx < polygon.size(); point_idx++)
|
||||
{
|
||||
Point p1 = polygon[(startIdx + i) % polygon.size()];
|
||||
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
|
||||
unsigned int p1_idx = (start_idx + point_idx) % polygon.size();
|
||||
Point p1 = polygon[p1_idx];
|
||||
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(polygons, poly_idx, p0_idx, p1_idx) : 1.0;
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
|
||||
p0 = p1;
|
||||
p0_idx = p1_idx;
|
||||
}
|
||||
if (polygon.size() > 2)
|
||||
{
|
||||
Point& p1 = polygon[startIdx];
|
||||
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
|
||||
Point& p1 = polygon[start_idx];
|
||||
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(polygons, poly_idx, p0_idx, start_idx) : 1.0;
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
|
||||
|
||||
if (wall_0_wipe_dist > 0)
|
||||
{ // apply outer wall wipe
|
||||
p0 = polygon[start_idx];
|
||||
int distance_traversed = 0;
|
||||
for (unsigned int point_idx = 1; ; point_idx++)
|
||||
{
|
||||
Point p1 = polygon[(start_idx + point_idx) % polygon.size()];
|
||||
int p0p1_dist = vSize(p1 - p0);
|
||||
if (distance_traversed + p0p1_dist >= wall_0_wipe_dist)
|
||||
{
|
||||
Point vector = p1 - p0;
|
||||
Point half_way = p0 + normal(vector, wall_0_wipe_dist - distance_traversed);
|
||||
addTravel_simple(half_way);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
addTravel_simple(p1);
|
||||
distance_traversed += p0p1_dist;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
forceNewPathStart();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -374,13 +407,13 @@ void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig*
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type, bool spiralize)
|
||||
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, PolygonFlowAdjuster* flow_adjuster, EZSeamType z_seam_type, Point z_seam_pos, coord_t wall_0_wipe_dist, bool spiralize)
|
||||
{
|
||||
if (polygons.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
PathOrderOptimizer orderOptimizer(lastPosition, z_seam_type);
|
||||
PathOrderOptimizer orderOptimizer(lastPosition, z_seam_pos, z_seam_type);
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
orderOptimizer.addPolygon(polygons[poly_idx]);
|
||||
@@ -388,7 +421,7 @@ void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* c
|
||||
orderOptimizer.optimize();
|
||||
for (unsigned int poly_idx : orderOptimizer.polyOrder)
|
||||
{
|
||||
addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config, wall_overlap_computation, spiralize);
|
||||
addPolygon(polygons, poly_idx, orderOptimizer.polyStart[poly_idx], config, flow_adjuster, wall_0_wipe_dist, spiralize);
|
||||
}
|
||||
}
|
||||
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist)
|
||||
@@ -563,22 +596,23 @@ void ExtruderPlan::processFanSpeedAndMinimalLayerTime(bool force_minimal_layer_t
|
||||
}
|
||||
/*
|
||||
Supposing no influence of minimal layer time; i.e. layer time > min layer time fan speed min:
|
||||
|
||||
max.. fan 'full' on layer
|
||||
| :
|
||||
| :
|
||||
^ min..|..:________________
|
||||
fan | /
|
||||
speed | /
|
||||
zero..|/__________________
|
||||
layer nr >
|
||||
|
||||
|
||||
|
||||
max.. fan 'full' on layer
|
||||
| :
|
||||
| :
|
||||
^ min..|..:________________
|
||||
fan | /
|
||||
speed | /
|
||||
speed_0..|/
|
||||
|
|
||||
|__________________
|
||||
layer nr >
|
||||
|
||||
*/
|
||||
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 * std::max(0, layer_nr) / fsml.cool_fan_full_layer;
|
||||
fan_speed = fsml.cool_fan_speed_0 + (fan_speed - fsml.cool_fan_speed_0) * std::max(0, layer_nr) / fsml.cool_fan_full_layer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,7 +647,13 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
gcode.setLayerNr(layer_nr);
|
||||
|
||||
gcode.writeLayerComment(layer_nr);
|
||||
|
||||
|
||||
if (layer_nr == 1 - Raft::getTotalExtraLayers(storage))
|
||||
{
|
||||
bool wait = false;
|
||||
gcode.writeBedTemperatureCommand(storage.getSettingInDegreeCelsius("material_bed_temperature"), wait);
|
||||
}
|
||||
|
||||
gcode.setZ(z);
|
||||
|
||||
|
||||
@@ -642,12 +682,12 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
|
||||
{ // require printing temperature to be met
|
||||
constexpr bool wait = true;
|
||||
gcode.writeTemperatureCommand(extruder, extruder_plan.required_temp, wait);
|
||||
gcode.writeTemperatureCommand(extruder, extruder_plan.initial_printing_temperature, wait);
|
||||
}
|
||||
|
||||
// prime extruder if it hadn't been used yet
|
||||
gcode.writePrimeTrain(storage.meshgroup->getExtruderTrain(extruder)->getSettingInMillimetersPerSecond("speed_travel"));
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
gcode.writeRetraction(retraction_config);
|
||||
|
||||
if (extruder_plan.prev_extruder_standby_temp)
|
||||
{ // turn off previous extruder
|
||||
@@ -661,6 +701,10 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
gcode.writeTemperatureCommand(prev_extruder, prev_extruder_temp, wait);
|
||||
}
|
||||
}
|
||||
else if (extruder_plan_idx == 0 && layer_nr != 0 && storage.meshgroup->getExtruderTrain(extruder)->getSettingBoolean("retract_at_layer_change"))
|
||||
{
|
||||
gcode.writeRetraction(retraction_config);
|
||||
}
|
||||
gcode.writeFanCommand(extruder_plan.getFanSpeed());
|
||||
std::vector<GCodePath>& paths = extruder_plan.paths;
|
||||
|
||||
@@ -683,6 +727,12 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
|
||||
GCodePath& path = paths[path_idx];
|
||||
|
||||
if (path.perform_prime)
|
||||
{
|
||||
gcode.writePrimeTrain(train->getSettingInMillimetersPerSecond("speed_travel"));
|
||||
gcode.writeRetraction(retraction_config);
|
||||
}
|
||||
|
||||
if (acceleration_enabled)
|
||||
{
|
||||
gcode.writeAcceleration(path.config->getAcceleration());
|
||||
@@ -694,11 +744,15 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
|
||||
if (path.retract)
|
||||
{
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
gcode.writeRetraction(retraction_config);
|
||||
if (path.perform_z_hop)
|
||||
{
|
||||
gcode.writeZhopStart(retraction_config.zHop);
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode.writeZhopEnd();
|
||||
}
|
||||
}
|
||||
if (!path.config->isTravelPath() && last_extrusion_config != path.config)
|
||||
{
|
||||
@@ -809,7 +863,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
{
|
||||
gcode.writeComment("Small layer, adding delay");
|
||||
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[gcode.getExtruderNr()];
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
gcode.writeRetraction(retraction_config);
|
||||
if (extruder_plan_idx == extruder_plans.size() - 1 || !train->getSettingBoolean("machine_extruder_end_pos_abs"))
|
||||
{ // only move the head if it's the last extruder plan; otherwise it's already at the switching bay area
|
||||
// or do it anyway when we switch extruder in-place
|
||||
|
||||
+41
-257
@@ -6,9 +6,12 @@
|
||||
|
||||
#include "gcodeExport.h"
|
||||
#include "pathPlanning/Comb.h"
|
||||
#include "pathPlanning/GCodePath.h"
|
||||
#include "pathPlanning/NozzleTempInsert.h"
|
||||
#include "pathPlanning/TimeMaterialEstimates.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "wallOverlap.h"
|
||||
#include "PolygonFlowAdjuster.h"
|
||||
#include "commandSocket.h"
|
||||
#include "FanSpeedLayerTime.h"
|
||||
#include "SpaceFillType.h"
|
||||
@@ -21,249 +24,6 @@ namespace cura
|
||||
|
||||
class SliceDataStorage;
|
||||
|
||||
/*!
|
||||
* A gcode command to insert before a specific path.
|
||||
*
|
||||
* Currently only used for preheat commands
|
||||
*/
|
||||
struct NozzleTempInsert
|
||||
{
|
||||
const unsigned int path_idx; //!< The path before which to insert this command
|
||||
double time_after_path_start; //!< The time after the start of the path, before which to insert the command // TODO: use this to insert command in between moves in a path!
|
||||
int extruder; //!< The extruder for which to set the temp
|
||||
double temperature; //!< The temperature of the temperature command to insert
|
||||
bool wait; //!< Whether to wait for the temperature to be reached
|
||||
NozzleTempInsert(unsigned int path_idx, int extruder, double temperature, bool wait, double time_after_path_start = 0.0)
|
||||
: path_idx(path_idx)
|
||||
, time_after_path_start(time_after_path_start)
|
||||
, extruder(extruder)
|
||||
, temperature(temperature)
|
||||
, wait(wait)
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Write the temperature command at the current position in the gcode.
|
||||
* \param gcode The actual gcode writer
|
||||
*/
|
||||
void write(GCodeExport& gcode)
|
||||
{
|
||||
gcode.writeTemperatureCommand(extruder, temperature, wait);
|
||||
}
|
||||
};
|
||||
|
||||
class ExtruderPlan; // forward declaration so that TimeMaterialEstimates can be a friend
|
||||
|
||||
|
||||
/*!
|
||||
* Time and material estimates for a portion of paths, e.g. layer, extruder plan, path.
|
||||
*/
|
||||
class TimeMaterialEstimates
|
||||
{
|
||||
friend class ExtruderPlan; // cause there the naive estimates are calculated
|
||||
private:
|
||||
double extrude_time; //!< Time in seconds occupied by extrusion
|
||||
double unretracted_travel_time; //!< Time in seconds occupied by non-retracted travel (non-extrusion)
|
||||
double retracted_travel_time; //!< Time in seconds occupied by retracted travel (non-extrusion)
|
||||
double material; //!< Material used (in mm^3)
|
||||
public:
|
||||
/*!
|
||||
* Basic contructor
|
||||
*
|
||||
* \param extrude_time Time in seconds occupied by extrusion
|
||||
* \param unretracted_travel_time Time in seconds occupied by non-retracted travel (non-extrusion)
|
||||
* \param retracted_travel_time Time in seconds occupied by retracted travel (non-extrusion)
|
||||
* \param material Material used (in mm^3)
|
||||
*/
|
||||
TimeMaterialEstimates(double extrude_time, double unretracted_travel_time, double retracted_travel_time, double material)
|
||||
: extrude_time(extrude_time)
|
||||
, unretracted_travel_time(unretracted_travel_time)
|
||||
, retracted_travel_time(retracted_travel_time)
|
||||
, material(material)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Basic constructor initializing all estimates to zero.
|
||||
*/
|
||||
TimeMaterialEstimates()
|
||||
: extrude_time(0.0)
|
||||
, unretracted_travel_time(0.0)
|
||||
, retracted_travel_time(0.0)
|
||||
, material(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set all estimates to zero.
|
||||
*/
|
||||
void reset()
|
||||
{
|
||||
extrude_time = 0.0;
|
||||
unretracted_travel_time = 0.0;
|
||||
retracted_travel_time = 0.0;
|
||||
material = 0.0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Pointwise addition of estimate stats
|
||||
*
|
||||
* \param other The estimates to add to these estimates.
|
||||
* \return The resulting estimates
|
||||
*/
|
||||
TimeMaterialEstimates operator+(const TimeMaterialEstimates& other)
|
||||
{
|
||||
return TimeMaterialEstimates(extrude_time+other.extrude_time, unretracted_travel_time+other.unretracted_travel_time, retracted_travel_time+other.retracted_travel_time, material+other.material);
|
||||
}
|
||||
|
||||
/*!
|
||||
* In place pointwise addition of estimate stats
|
||||
*
|
||||
* \param other The estimates to add to these estimates.
|
||||
* \return These estimates
|
||||
*/
|
||||
TimeMaterialEstimates& operator+=(const TimeMaterialEstimates& other)
|
||||
{
|
||||
extrude_time += other.extrude_time;
|
||||
unretracted_travel_time += other.unretracted_travel_time;
|
||||
retracted_travel_time += other.retracted_travel_time;
|
||||
material += other.material;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Subtracts the specified estimates from these estimates and returns
|
||||
* the result.
|
||||
*
|
||||
* Each of the estimates in this class are individually subtracted.
|
||||
*
|
||||
* \param other The estimates to subtract from these estimates.
|
||||
* \return These estimates with the specified estimates subtracted.
|
||||
*/
|
||||
TimeMaterialEstimates operator-(const TimeMaterialEstimates& other);
|
||||
|
||||
/*!
|
||||
* \brief Subtracts the specified elements from these estimates.
|
||||
*
|
||||
* This causes the estimates in this instance to change. Each of the
|
||||
* estimates in this class are individually subtracted.
|
||||
*
|
||||
* \param other The estimates to subtract from these estimates.
|
||||
* \return A reference to this instance.
|
||||
*/
|
||||
TimeMaterialEstimates& operator-=(const TimeMaterialEstimates& other);
|
||||
|
||||
/*!
|
||||
* Get total time estimate. The different time estimate member values added together.
|
||||
*
|
||||
* \return the total of all different time estimate values
|
||||
*/
|
||||
double getTotalTime() const
|
||||
{
|
||||
return extrude_time + unretracted_travel_time + retracted_travel_time;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the total time during which the head is not retracted.
|
||||
*
|
||||
* This includes extrusion time and non-retracted travel time
|
||||
*
|
||||
* \return the total time during which the head is not retracted.
|
||||
*/
|
||||
double getTotalUnretractedTime() const
|
||||
{
|
||||
return extrude_time + unretracted_travel_time;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the total travel time.
|
||||
*
|
||||
* This includes the retracted travel time as well as the unretracted travel time.
|
||||
*
|
||||
* \return the total travel time.
|
||||
*/
|
||||
double getTravelTime() const
|
||||
{
|
||||
return retracted_travel_time + unretracted_travel_time;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the extrusion time.
|
||||
*
|
||||
* \return extrusion time.
|
||||
*/
|
||||
double getExtrudeTime() const
|
||||
{
|
||||
return extrude_time;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the amount of material used in mm^3.
|
||||
*
|
||||
* \return amount of material
|
||||
*/
|
||||
double getMaterial() const
|
||||
{
|
||||
return material;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* A class for representing a planned path.
|
||||
*
|
||||
* A path consists of several segments of the same type of movement: retracted travel, infill extrusion, etc.
|
||||
*
|
||||
* This is a compact premature representation in which are line segments have the same config, i.e. the config of this path.
|
||||
*
|
||||
* In the final representation (gcode) each line segment may have different properties,
|
||||
* which are added when the generated GCodePaths are processed.
|
||||
*/
|
||||
class GCodePath
|
||||
{
|
||||
public:
|
||||
GCodePathConfig* config; //!< The configuration settings of the path.
|
||||
SpaceFillType space_fill_type; //!< The type of space filling of which this path is a part
|
||||
float flow; //!< A type-independent flow configuration (used for wall overlap compensation)
|
||||
bool retract; //!< Whether the path is a move path preceded by a retraction move; whether the path is a retracted move path.
|
||||
bool perform_z_hop; //!< Whether to perform a z_hop in this path, which is assumed to be a travel path.
|
||||
std::vector<Point> points; //!< The points constituting this path.
|
||||
bool done;//!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one.
|
||||
|
||||
bool spiralize; //!< Whether to gradually increment the z position during the printing of this path. A sequence of spiralized paths should start at the given layer height and end in one layer higher.
|
||||
|
||||
TimeMaterialEstimates estimates; //!< Naive time and material estimates
|
||||
|
||||
/*!
|
||||
* Whether this config is the config of a travel path.
|
||||
*
|
||||
* \return Whether this config is the config of a travel path.
|
||||
*/
|
||||
bool isTravelPath()
|
||||
{
|
||||
return config->isTravelPath();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the material flow in mm^3 per mm traversed.
|
||||
*
|
||||
* \warning Can only be called after the layer height has been set (which is done while writing the gcode!)
|
||||
*
|
||||
* \return The flow
|
||||
*/
|
||||
double getExtrusionMM3perMM()
|
||||
{
|
||||
return flow * config->getExtrusionMM3perMM();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the actual line width (modulated by the flow)
|
||||
* \return the actual line width as shown in layer view
|
||||
*/
|
||||
int getLineWidth()
|
||||
{
|
||||
return flow * config->getLineWidth() * config->getFlowPercentage() / 100.0;
|
||||
}
|
||||
};
|
||||
|
||||
class GCodePlanner; // forward declaration so that ExtruderPlan can be a friend
|
||||
class LayerPlanBuffer; // forward declaration so that ExtruderPlan can be a friend
|
||||
|
||||
@@ -281,7 +41,10 @@ protected:
|
||||
std::list<NozzleTempInsert> inserts; //!< The nozzle temperature command inserts, to be inserted in between paths
|
||||
|
||||
int extruder; //!< The extruder used for this paths in the current plan.
|
||||
double required_temp; //!< The required temperature at the start of this extruder plan.
|
||||
double heated_pre_travel_time; //!< The time at the start of this ExtruderPlan during which the head travels and has a temperature of initial_print_temperature
|
||||
double initial_printing_temperature; //!< The required temperature at the start of this extruder plan.
|
||||
double printing_temperature; //!< The normal temperature for printing this extruder plan. That start and end of this extruder plan may deviate because of the initial and final print temp
|
||||
std::optional<std::list<NozzleTempInsert>::iterator> printing_temperature_command; //!< The command to heat from the printing temperature of this extruder plan to the printing temperature of the next extruder plan (if it has the same extruder).
|
||||
std::optional<double> prev_extruder_standby_temp; //!< The temperature to which to set the previous extruder. Not used if the previous extruder plan was the same extruder.
|
||||
|
||||
TimeMaterialEstimates estimates; //!< Accumulated time and material estimates for all planned paths within this extruder plan.
|
||||
@@ -294,7 +57,7 @@ public:
|
||||
* \param extruder The extruder number for which this object is a plan.
|
||||
* \param start_position The position the head is when this extruder plan starts
|
||||
*/
|
||||
ExtruderPlan(int extruder, Point start_position, int layer_nr, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config);
|
||||
ExtruderPlan(int extruder, Point start_position, int layer_nr, bool is_initial_layer, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config);
|
||||
|
||||
/*!
|
||||
* Add a new Insert, constructed with the given arguments
|
||||
@@ -398,6 +161,8 @@ protected:
|
||||
Point start_position; //!< The position the print head was at at the start of this extruder plan
|
||||
|
||||
int layer_nr; //!< The layer number at which we are currently printing.
|
||||
bool is_initial_layer; //!< Whether this extruder plan is printed on the very first layer (which might be raft)
|
||||
|
||||
int layer_thickness; //!< The thickness of this layer in Z-direction
|
||||
|
||||
FanSpeedLayerTimeSettings& fan_speed_layer_time_settings; //!< The fan speed and layer time settings used to limit this extruder plan
|
||||
@@ -454,6 +219,7 @@ private:
|
||||
SliceDataStorage& storage; //!< The polygon data obtained from FffPolygonProcessor
|
||||
|
||||
int layer_nr; //!< The layer number of this layer plan
|
||||
int is_initial_layer; //!< Whether this is the first layer (which might be raft)
|
||||
|
||||
int z;
|
||||
|
||||
@@ -486,7 +252,8 @@ private:
|
||||
* \return A path with the given config which is now the last path in GCodePlanner::paths
|
||||
*/
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0, bool spiralize = false);
|
||||
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Force GCodePlanner::getLatestPathWithConfig to return a new path.
|
||||
*
|
||||
@@ -498,7 +265,7 @@ private:
|
||||
* - when changing extruder, the same travel config is used, but its extruder field is changed.
|
||||
*/
|
||||
void forceNewPathStart();
|
||||
public:
|
||||
|
||||
/*!
|
||||
*
|
||||
* \param fan_speed_layer_time_settings_per_extruder The fan speed and layer time settings for each extruder.
|
||||
@@ -507,7 +274,7 @@ public:
|
||||
* \param last_position The position of the head at the start of this gcode layer
|
||||
* \param combing_mode Whether combing is enabled and full or within infill only.
|
||||
*/
|
||||
GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_height, Point last_position, int current_extruder, bool is_inside_mesh, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
GCodePlanner(SliceDataStorage& storage, int layer_nr, int z, int layer_height, Point last_position, int current_extruder, bool is_inside_mesh, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
~GCodePlanner();
|
||||
|
||||
void overrideFanSpeeds(double speed);
|
||||
@@ -557,7 +324,13 @@ public:
|
||||
* Features like prime tower and support are considered outside.
|
||||
*/
|
||||
void setIsInside(bool going_to_comb);
|
||||
|
||||
|
||||
/*!
|
||||
* Plan a switch to a new extruder
|
||||
*
|
||||
* \param extruder The extruder number to which to switch
|
||||
* \return whether the extruder has changed
|
||||
*/
|
||||
bool setExtruder(int extruder);
|
||||
|
||||
/*!
|
||||
@@ -576,7 +349,7 @@ public:
|
||||
*
|
||||
* \param p The point to travel to
|
||||
*/
|
||||
void addTravel(Point p);
|
||||
GCodePath& addTravel(Point p);
|
||||
|
||||
/*!
|
||||
* Add a travel path to a certain point and retract if needed.
|
||||
@@ -586,7 +359,14 @@ public:
|
||||
* \param p The point to travel to
|
||||
* \param path (optional) The travel path to which to add the point \p p
|
||||
*/
|
||||
void addTravel_simple(Point p, GCodePath* path = nullptr);
|
||||
GCodePath& addTravel_simple(Point p, GCodePath* path = nullptr);
|
||||
|
||||
/*!
|
||||
* Plan a prime poop at the current location.
|
||||
*
|
||||
* \warning A nonretracted move is introduced so that the LayerPlanBuffer classifies this move as an extrusion move.
|
||||
*/
|
||||
void planPrime();
|
||||
|
||||
/*!
|
||||
* Add an extrusion move to a certain point, optionally with a different flow than the one in the \p config.
|
||||
@@ -601,13 +381,15 @@ public:
|
||||
|
||||
/*!
|
||||
* Add polygon to the gcode starting at vertex \p startIdx
|
||||
* \param polygon The polygon
|
||||
* \param polygon The polygons from which to get the polygon
|
||||
* \param polygon The index of the polygon in \p polygons
|
||||
* \param startIdx The index of the starting vertex of the \p polygon
|
||||
* \param config The config with which to print the polygon lines
|
||||
* \param wall_overlap_computation The wall overlap compensation calculator for each given segment (optionally nullptr)
|
||||
* \param flow_adjuster Construct yielding the flow of each segment added (optionally nullptr)
|
||||
* \param wall_0_wipe_dist The distance to travel along the polygon after it has been laid down, in order to wipe the start and end of the wall together
|
||||
* \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over this polygon
|
||||
*/
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, bool spiralize = false);
|
||||
void addPolygon(Polygons& polygons, unsigned int poly_idx, int startIdx, GCodePathConfig* config, PolygonFlowAdjuster* flow_adjuster = nullptr, coord_t wall_0_wipe_dist = 0, bool spiralize = false);
|
||||
|
||||
/*!
|
||||
* Add polygons to the gcode with optimized order.
|
||||
@@ -620,11 +402,13 @@ public:
|
||||
*
|
||||
* \param polygons The polygons
|
||||
* \param config The config with which to print the polygon lines
|
||||
* \param wall_overlap_computation The wall overlap compensation calculator for each given segment (optionally nullptr)
|
||||
* \param flow_adjuster Construct yielding the flow of each segment added (optionally nullptr)
|
||||
* \param z_seam_type The seam type / poly start optimizer
|
||||
* \param z_seam_pos The location near where to start each part in case \p z_seam_type is 'back'
|
||||
* \param wall_0_wipe_dist The distance to travel along each polygon after it has been laid down, in order to wipe the start and end of the wall together
|
||||
* \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over each polygon printed
|
||||
*/
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST, bool spiralize = false);
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, PolygonFlowAdjuster* flow_adjuster = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST, Point z_seam_pos = Point(0, 0), coord_t wall_0_wipe_dist = 0, bool spiralize = false);
|
||||
|
||||
/*!
|
||||
* Add lines to the gcode with optimized order.
|
||||
|
||||
+90
-28
@@ -17,11 +17,10 @@ int Infill::computeScanSegmentIdx(int x, int line_width)
|
||||
return x / line_width;
|
||||
}
|
||||
|
||||
void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
void Infill::generate(Polygons& result_polygons, Polygons& result_lines, SliceMeshStorage* mesh)
|
||||
{
|
||||
if (in_outline.size() == 0) return;
|
||||
if (line_distance == 0) return;
|
||||
const Polygons* outline = &in_outline;
|
||||
Polygons outline_offsetted;
|
||||
switch(pattern)
|
||||
{
|
||||
@@ -41,28 +40,75 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
generateTriangleInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC:
|
||||
outline_offsetted = in_outline.offset(outline_offset - infill_line_width / 2); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
outline = &outline_offsetted;
|
||||
generateConcentricInfill(*outline, result_polygons, line_distance);
|
||||
generateConcentricInfill(result_polygons, line_distance);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC_3D:
|
||||
generateConcentric3DInfill(result_polygons);
|
||||
break;
|
||||
case EFillMethod::ZIG_ZAG:
|
||||
generateZigZagInfill(result_lines, line_distance, fill_angle, connected_zigzags, use_endpieces);
|
||||
break;
|
||||
case EFillMethod::CUBICSUBDIV:
|
||||
if (!mesh)
|
||||
{
|
||||
logError("Cannot generate Cubic Subdivision infill without a mesh!\n");
|
||||
break;
|
||||
}
|
||||
generateCubicSubDivInfill(result_lines, *mesh);
|
||||
break;
|
||||
default:
|
||||
logError("Fill pattern has unknown value.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int inset_value)
|
||||
void Infill::generateConcentricInfill(Polygons& result, int inset_value)
|
||||
{
|
||||
while(outline.size() > 0)
|
||||
Polygons first_concentric_wall = in_outline.offset(outline_offset - line_distance + infill_line_width / 2); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
result.add(outline);
|
||||
outline = outline.offset(-inset_value);
|
||||
}
|
||||
const Polygons inner = first_concentric_wall.offset(infill_line_width / 2 + perimeter_gaps_extra_offset);
|
||||
const Polygons gaps_here = in_outline.difference(inner);
|
||||
perimeter_gaps->add(gaps_here);
|
||||
}
|
||||
generateConcentricInfill(first_concentric_wall, result, inset_value);
|
||||
}
|
||||
|
||||
void Infill::generateConcentricInfill(Polygons& first_concentric_wall, Polygons& result, int inset_value)
|
||||
{
|
||||
result.add(first_concentric_wall);
|
||||
Polygons* prev_inset = &first_concentric_wall;
|
||||
Polygons next_inset;
|
||||
while (prev_inset->size() > 0)
|
||||
{
|
||||
next_inset = prev_inset->offset(-inset_value);
|
||||
result.add(next_inset);
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
const Polygons outer = prev_inset->offset(-infill_line_width / 2 - perimeter_gaps_extra_offset);
|
||||
const Polygons inner = next_inset.offset(infill_line_width / 2);
|
||||
const Polygons gaps_here = outer.difference(inner);
|
||||
perimeter_gaps->add(gaps_here);
|
||||
}
|
||||
prev_inset = &next_inset;
|
||||
}
|
||||
}
|
||||
|
||||
void Infill::generateConcentric3DInfill(Polygons& result)
|
||||
{
|
||||
int period = line_distance * 2;
|
||||
int shift = int64_t(one_over_sqrt_2 * z) % period;
|
||||
shift = std::min(shift, period - shift); // symmetry due to the fact that we are applying the shift in both directions
|
||||
shift = std::min(shift, period / 2 - infill_line_width / 2); // don't put lines too close to each other
|
||||
shift = std::max(shift, infill_line_width / 2); // don't put lines too close to each other
|
||||
Polygons first_wall;
|
||||
// in contrast to concentric infill we dont do "- infill_line_width / 2" cause this is already handled by the max two lines above
|
||||
first_wall = in_outline.offset(outline_offset - shift);
|
||||
generateConcentricInfill(first_wall, result, period);
|
||||
first_wall = in_outline.offset(outline_offset - period + shift);
|
||||
generateConcentricInfill(first_wall, result, period);
|
||||
}
|
||||
|
||||
void Infill::generateGridInfill(Polygons& result)
|
||||
{
|
||||
@@ -80,14 +126,15 @@ void Infill::generateCubicInfill(Polygons& result)
|
||||
|
||||
void Infill::generateTetrahedralInfill(Polygons& result)
|
||||
{
|
||||
int shift = int64_t(one_over_sqrt_2 * z) % line_distance;
|
||||
shift = std::min(shift, line_distance - shift); // symmetry due to the fact that we are applying the shift in both directions
|
||||
shift = std::min(shift, line_distance / 2 - infill_line_width / 2); // don't put lines too close to each other
|
||||
int period = line_distance * 2;
|
||||
int shift = int64_t(one_over_sqrt_2 * z) % period;
|
||||
shift = std::min(shift, period - shift); // symmetry due to the fact that we are applying the shift in both directions
|
||||
shift = std::min(shift, period / 2 - infill_line_width / 2); // don't put lines too close to each other
|
||||
shift = std::max(shift, infill_line_width / 2); // don't put lines too close to each other
|
||||
generateLineInfill(result, line_distance, fill_angle, shift);
|
||||
generateLineInfill(result, line_distance, fill_angle, -shift);
|
||||
generateLineInfill(result, line_distance, fill_angle + 90, shift);
|
||||
generateLineInfill(result, line_distance, fill_angle + 90, -shift);
|
||||
generateLineInfill(result, period, fill_angle, shift);
|
||||
generateLineInfill(result, period, fill_angle, -shift);
|
||||
generateLineInfill(result, period, fill_angle + 90, shift);
|
||||
generateLineInfill(result, period, fill_angle + 90, -shift);
|
||||
}
|
||||
|
||||
void Infill::generateTriangleInfill(Polygons& result)
|
||||
@@ -97,15 +144,26 @@ void Infill::generateTriangleInfill(Polygons& result)
|
||||
generateLineInfill(result, line_distance, fill_angle + 120, 0);
|
||||
}
|
||||
|
||||
void Infill::generateCubicSubDivInfill(Polygons& result, SliceMeshStorage& mesh)
|
||||
{
|
||||
Polygons uncropped;
|
||||
mesh.base_subdiv_cube->generateSubdivisionLines(z, uncropped);
|
||||
addLineSegmentsInfill(result, uncropped);
|
||||
}
|
||||
|
||||
void Infill::addLineSegmentsInfill(Polygons& result, Polygons& input)
|
||||
{
|
||||
ClipperLib::PolyTree interior_segments_tree = in_outline.lineSegmentIntersection(input);
|
||||
ClipperLib::Paths interior_segments;
|
||||
ClipperLib::OpenPathsFromPolyTree(interior_segments_tree, interior_segments);
|
||||
for (uint64_t idx = 0; idx < interior_segments.size(); idx++)
|
||||
{
|
||||
result.addLine(interior_segments[idx][0], interior_segments[idx][1]);
|
||||
}
|
||||
}
|
||||
|
||||
void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list, int64_t shift)
|
||||
{
|
||||
auto addLine = [&](Point from, Point to)
|
||||
{
|
||||
PolygonRef p = result.newPoly();
|
||||
p.add(rotation_matrix.unapply(from));
|
||||
p.add(rotation_matrix.unapply(to));
|
||||
};
|
||||
|
||||
auto compare_int64_t = [](const void* a, const void* b)
|
||||
{
|
||||
int64_t n = (*(int64_t*)a) - (*(int64_t*)b);
|
||||
@@ -131,7 +189,7 @@ void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix,
|
||||
{ // segment is too short to create infill
|
||||
continue;
|
||||
}
|
||||
addLine(Point(x, crossings[crossing_idx]), Point(x, crossings[crossing_idx + 1]));
|
||||
result.addLine(rotation_matrix.unapply(Point(x, crossings[crossing_idx])), rotation_matrix.unapply(Point(x, crossings[crossing_idx + 1])));
|
||||
}
|
||||
scanline_idx += 1;
|
||||
}
|
||||
@@ -210,14 +268,18 @@ void Infill::generateLinearBasedInfill(const int outline_offset, Polygons& resul
|
||||
if (outline_offset != 0)
|
||||
{
|
||||
outline = in_outline.offset(outline_offset);
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
perimeter_gaps->add(in_outline.difference(outline.offset(infill_line_width / 2 + perimeter_gaps_extra_offset)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outline = in_outline;
|
||||
}
|
||||
|
||||
outline = outline.offset(infill_overlap);
|
||||
|
||||
|
||||
outline = outline.offset(infill_overlap);
|
||||
|
||||
if (outline.size() == 0)
|
||||
{
|
||||
return;
|
||||
|
||||
+62
-5
@@ -12,6 +12,7 @@
|
||||
#include "infill/ZigzagConnectorProcessorEndPieces.h"
|
||||
#include "infill/ZigzagConnectorProcessorConnectedEndPieces.h"
|
||||
#include "infill/ZigzagConnectorProcessorDisconnectedEndPieces.h"
|
||||
#include "infill/SubDivCube.h"
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/AABB.h"
|
||||
|
||||
@@ -20,6 +21,8 @@ namespace cura
|
||||
|
||||
class Infill
|
||||
{
|
||||
static constexpr int perimeter_gaps_extra_offset = 15; // extra offset so that the perimeter gaps aren't created everywhere due to rounding errors
|
||||
|
||||
EFillMethod pattern; //!< the space filling pattern of the infill to generate
|
||||
const Polygons& in_outline; //!< a reference polygon for getting the actual area within which to generate infill (see outline_offset)
|
||||
int outline_offset; //!< Offset from Infill::in_outline to get the actual area within which to generate infill
|
||||
@@ -29,12 +32,33 @@ class Infill
|
||||
double fill_angle; //!< for linear infill types: the angle of the infill lines (or the angle of the grid)
|
||||
int64_t z; //!< height of the layer for which we generate infill
|
||||
int64_t shift; //!< shift of the scanlines in the direction perpendicular to the fill_angle
|
||||
Polygons* perimeter_gaps; //!< (optional output) The areas in between consecutive insets when Concentric infill is used.
|
||||
bool connected_zigzags; //!< (ZigZag) Whether endpieces of zigzag infill should be connected to the nearest infill line on both sides of the zigzag connector
|
||||
bool use_endpieces; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself
|
||||
|
||||
static constexpr double one_over_sqrt_2 = 0.7071067811865475244008443621048490392848359376884740; //!< 1.0 / sqrt(2.0)
|
||||
public:
|
||||
Infill(EFillMethod pattern, const Polygons& in_outline, int outline_offset, int infill_line_width, int line_distance, int infill_overlap, double fill_angle, int64_t z, int64_t shift, bool connected_zigzags = false, bool use_endpieces = false)
|
||||
/*!
|
||||
* \warning If \p perimeter_gaps is given, then the difference between the \p in_outline
|
||||
* and the polygons which result from offsetting it by the \p outline_offset
|
||||
* and then expanding it again by half the \p infill_line_width
|
||||
* is added to the \p perimeter_gaps
|
||||
*
|
||||
* \param[out] perimeter_gaps (optional output) The areas in between consecutive insets when Concentric infill is used.
|
||||
*/
|
||||
Infill(EFillMethod pattern
|
||||
, const Polygons& in_outline
|
||||
, int outline_offset
|
||||
, int infill_line_width
|
||||
, int line_distance
|
||||
, int infill_overlap
|
||||
, double fill_angle
|
||||
, int64_t z
|
||||
, int64_t shift
|
||||
, Polygons* perimeter_gaps = nullptr
|
||||
, bool connected_zigzags = false
|
||||
, bool use_endpieces = false
|
||||
)
|
||||
: pattern(pattern)
|
||||
, in_outline(in_outline)
|
||||
, outline_offset(outline_offset)
|
||||
@@ -44,6 +68,7 @@ public:
|
||||
, fill_angle(fill_angle)
|
||||
, z(z)
|
||||
, shift(shift)
|
||||
, perimeter_gaps(perimeter_gaps)
|
||||
, connected_zigzags(connected_zigzags)
|
||||
, use_endpieces(use_endpieces)
|
||||
{
|
||||
@@ -53,8 +78,9 @@ public:
|
||||
*
|
||||
* \param result_polygons (output) The resulting polygons (from concentric infill)
|
||||
* \param result_lines (output) The resulting line segments (from linear infill types)
|
||||
* \param mesh The mesh for which to geenrate infill (should only be used for non-helper objects)
|
||||
*/
|
||||
void generate(Polygons& result_polygons, Polygons& result_lines);
|
||||
void generate(Polygons& result_polygons, Polygons& result_lines, SliceMeshStorage* mesh = nullptr);
|
||||
|
||||
private:
|
||||
/*!
|
||||
@@ -68,13 +94,30 @@ private:
|
||||
* \param line_distance the width of the scan segments
|
||||
*/
|
||||
static inline int computeScanSegmentIdx(int x, int line_distance);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill
|
||||
* \param outline The actual outline of the area within which to generate infill
|
||||
* Generate sparse concentric infill
|
||||
*
|
||||
* Also adds \ref Inifll::perimeter_gaps between \ref Infill::in_outline and the first wall
|
||||
*
|
||||
* \param result (output) The resulting polygons
|
||||
* \param inset_value The offset between each consecutive two polygons
|
||||
*/
|
||||
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value);
|
||||
void generateConcentricInfill(Polygons& result, int inset_value);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill starting from a specific outer wall
|
||||
* \param first_wall The outer wall from which to start
|
||||
* \param result (output) The resulting polygons
|
||||
* \param inset_value The offset between each consecutive two polygons
|
||||
*/
|
||||
void generateConcentricInfill(Polygons& first_wall, Polygons& result, int inset_value);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill
|
||||
* \param result (output) The resulting polygons
|
||||
*/
|
||||
void generateConcentric3DInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Generate a rectangular grid of infill lines
|
||||
@@ -99,6 +142,13 @@ private:
|
||||
* \param result (output) The resulting lines
|
||||
*/
|
||||
void generateTriangleInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Generate a 3d pattern of subdivided cubes on their points
|
||||
* \param[out] result The resulting lines
|
||||
* \param[in] mesh Where the Cubic Subdivision Infill precomputation is stored
|
||||
*/
|
||||
void generateCubicSubDivInfill(Polygons& result, SliceMeshStorage& mesh);
|
||||
|
||||
/*!
|
||||
* Convert a mapping from scanline to line_segment-scanline-intersections (\p cut_list) into line segments, using the even-odd rule
|
||||
@@ -112,6 +162,13 @@ private:
|
||||
*/
|
||||
void addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list, int64_t total_shift);
|
||||
|
||||
/*!
|
||||
* Crop line segments by the infill polygon using Clipper
|
||||
* \param result (output) The resulting lines
|
||||
* \param input The line segments to be cropped
|
||||
*/
|
||||
void addLineSegmentsInfill(Polygons& result, Polygons& input);
|
||||
|
||||
/*!
|
||||
* generate lines within the area of \p in_outline, at regular intervals of \p line_distance
|
||||
*
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
#include "SubDivCube.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "../utils/math.h"
|
||||
|
||||
#define ONE_OVER_SQRT_2 0.7071067811865475244008443621048490392848359376884740 //1 / sqrt(2)
|
||||
#define ONE_OVER_SQRT_3 0.577350269189625764509148780501957455647601751270126876018 //1 / sqrt(3)
|
||||
#define ONE_OVER_SQRT_6 0.408248290463863016366214012450981898660991246776111688072 //1 / sqrt(6)
|
||||
#define SQRT_TWO_THIRD 0.816496580927726032732428024901963797321982493552223376144 //sqrt(2 / 3)
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
std::vector<SubDivCube::CubeProperties> SubDivCube::cube_properties_per_recursion_step;
|
||||
double SubDivCube::radius_multiplier = 1;
|
||||
int32_t SubDivCube::radius_addition = 0;
|
||||
Point3Matrix SubDivCube::rotation_matrix;
|
||||
PointMatrix SubDivCube::infill_rotation_matrix;
|
||||
|
||||
SubDivCube::~SubDivCube()
|
||||
{
|
||||
for (int child_idx = 0; child_idx < 8; child_idx++)
|
||||
{
|
||||
if (children[child_idx])
|
||||
{
|
||||
delete children[child_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SubDivCube::precomputeOctree(SliceMeshStorage& mesh)
|
||||
{
|
||||
radius_multiplier = mesh.getSettingAsRatio("sub_div_rad_mult");
|
||||
radius_addition = mesh.getSettingInMicrons("sub_div_rad_add");
|
||||
double infill_angle = M_PI / 4.0;
|
||||
|
||||
coord_t furthest_dist_from_origin = std::sqrt(square(mesh.getSettingInMicrons("machine_height")) + square(mesh.getSettingInMicrons("machine_depth") / 2) + square(mesh.getSettingInMicrons("machine_width") / 2));
|
||||
coord_t max_side_length = furthest_dist_from_origin * 2;
|
||||
|
||||
int curr_recursion_depth = 0;
|
||||
const int64_t infill_line_distance = mesh.getSettingInMicrons("infill_line_distance");
|
||||
if (infill_line_distance > 0)
|
||||
{
|
||||
for (int64_t curr_side_length = infill_line_distance * 2; curr_side_length < max_side_length * 2; curr_side_length *= 2)
|
||||
{
|
||||
cube_properties_per_recursion_step.emplace_back();
|
||||
CubeProperties& cube_properties_here = cube_properties_per_recursion_step.back();
|
||||
cube_properties_here.side_length = curr_side_length;
|
||||
cube_properties_here.height = sqrt(3) * curr_side_length;
|
||||
cube_properties_here.square_height = sqrt(2) * curr_side_length;
|
||||
cube_properties_here.max_draw_z_diff = ONE_OVER_SQRT_3 * curr_side_length;
|
||||
cube_properties_here.max_line_offset = ONE_OVER_SQRT_6 * curr_side_length;
|
||||
curr_recursion_depth++;
|
||||
}
|
||||
}
|
||||
Point3 center(0, 0, 0);
|
||||
|
||||
Point3Matrix tilt; // rotation matrix to get from axis aligned cubes to cubes standing on their tip
|
||||
// The Z axis is transformed to go in positive Y direction
|
||||
//
|
||||
// cross section in a horizontal plane horizontal plane showing
|
||||
// looking down at the origin O positive X and positive Y
|
||||
// Z .
|
||||
// /:\ Y .
|
||||
// / : \ ^ .
|
||||
// / : \ | .
|
||||
// / .O. \ | .
|
||||
// /.~' '~.\ O---->X .
|
||||
// X """"""""""" Y .
|
||||
tilt.matrix[0] = -ONE_OVER_SQRT_2; tilt.matrix[1] = ONE_OVER_SQRT_2; tilt.matrix[2] = 0;
|
||||
tilt.matrix[3] = -ONE_OVER_SQRT_6; tilt.matrix[4] = -ONE_OVER_SQRT_6; tilt.matrix[5] = SQRT_TWO_THIRD ;
|
||||
tilt.matrix[6] = ONE_OVER_SQRT_3; tilt.matrix[7] = ONE_OVER_SQRT_3; tilt.matrix[8] = ONE_OVER_SQRT_3;
|
||||
|
||||
infill_rotation_matrix = PointMatrix(infill_angle);
|
||||
Point3Matrix infill_angle_mat(infill_rotation_matrix);
|
||||
|
||||
rotation_matrix = infill_angle_mat.compose(tilt);
|
||||
|
||||
mesh.base_subdiv_cube = new SubDivCube(mesh, center, curr_recursion_depth - 1);
|
||||
}
|
||||
|
||||
void SubDivCube::generateSubdivisionLines(int64_t z, Polygons& result)
|
||||
{
|
||||
if (cube_properties_per_recursion_step.empty()) //Infill is set to 0%.
|
||||
{
|
||||
return;
|
||||
}
|
||||
Polygons directional_line_groups[3];
|
||||
|
||||
generateSubdivisionLines(z, result, directional_line_groups);
|
||||
|
||||
for (int dir_idx = 0; dir_idx < 3; dir_idx++)
|
||||
{
|
||||
Polygons& line_group = directional_line_groups[dir_idx];
|
||||
for (unsigned int line_idx = 0; line_idx < line_group.size(); line_idx++)
|
||||
{
|
||||
result.addLine(line_group[line_idx][0], line_group[line_idx][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SubDivCube::generateSubdivisionLines(int64_t z, Polygons& result, Polygons (&directional_line_groups)[3])
|
||||
{
|
||||
CubeProperties cube_properties = cube_properties_per_recursion_step[depth];
|
||||
|
||||
int32_t z_diff = std::abs(z - center.z); //!< the difference between the cube center and the target layer.
|
||||
if (z_diff > cube_properties.height / 2) //!< this cube does not touch the target layer. Early exit.
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (z_diff < cube_properties.max_draw_z_diff) //!< this cube has lines that need to be drawn.
|
||||
{
|
||||
Point relative_a, relative_b; //!< relative coordinates of line endpoints around cube center
|
||||
Point a, b; //!< absolute coordinates of line endpoints
|
||||
relative_a.X = (cube_properties.square_height / 2) * (cube_properties.max_draw_z_diff - z_diff) / cube_properties.max_draw_z_diff;
|
||||
relative_b.X = -relative_a.X;
|
||||
relative_a.Y = cube_properties.max_line_offset - ((z - (center.z - cube_properties.max_draw_z_diff)) * ONE_OVER_SQRT_2);
|
||||
relative_b.Y = relative_a.Y;
|
||||
rotatePointInitial(relative_a);
|
||||
rotatePointInitial(relative_b);
|
||||
for (int dir_idx = 0; dir_idx < 3; dir_idx++)//!< draw the line, then rotate 120 degrees.
|
||||
{
|
||||
a.X = center.x + relative_a.X;
|
||||
a.Y = center.y + relative_a.Y;
|
||||
b.X = center.x + relative_b.X;
|
||||
b.Y = center.y + relative_b.Y;
|
||||
addLineAndCombine(directional_line_groups[dir_idx], a, b);
|
||||
if (dir_idx < 2)
|
||||
{
|
||||
rotatePoint120(relative_a);
|
||||
rotatePoint120(relative_b);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int idx = 0; idx < 8; idx++) //!< draws the eight children
|
||||
{
|
||||
if (children[idx] != nullptr)
|
||||
{
|
||||
children[idx]->generateSubdivisionLines(z, result, directional_line_groups);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubDivCube::SubDivCube(SliceMeshStorage& mesh, Point3& center, unsigned int depth)
|
||||
{
|
||||
this->depth = depth;
|
||||
this->center = center;
|
||||
|
||||
if (depth == 0) // lowest layer, no need for subdivision, exit.
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (depth >= cube_properties_per_recursion_step.size()) //Depth is out of bounds of what we pre-computed.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CubeProperties cube_properties = cube_properties_per_recursion_step[depth];
|
||||
Point3 child_center;
|
||||
coord_t radius = double(radius_multiplier * double(cube_properties.height)) / 4.0 + radius_addition;
|
||||
|
||||
int child_nr = 0;
|
||||
std::vector<Point3> rel_child_centers;
|
||||
rel_child_centers.emplace_back(1, 1, 1); // top
|
||||
rel_child_centers.emplace_back(-1, 1, 1); // top three
|
||||
rel_child_centers.emplace_back(1, -1, 1);
|
||||
rel_child_centers.emplace_back(1, 1, -1);
|
||||
rel_child_centers.emplace_back(-1, -1, -1); // bottom
|
||||
rel_child_centers.emplace_back(1, -1, -1); // bottom three
|
||||
rel_child_centers.emplace_back(-1, 1, -1);
|
||||
rel_child_centers.emplace_back(-1, -1, 1);
|
||||
for (Point3 rel_child_center : rel_child_centers)
|
||||
{
|
||||
child_center = center + rotation_matrix.apply(rel_child_center * coord_t(cube_properties.side_length / 4));
|
||||
if (isValidSubdivision(mesh, child_center, radius))
|
||||
{
|
||||
children[child_nr] = new SubDivCube(mesh, child_center, depth - 1);
|
||||
child_nr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SubDivCube::isValidSubdivision(SliceMeshStorage& mesh, Point3& center, int64_t radius)
|
||||
{
|
||||
int64_t distance2;
|
||||
long int sphere_slice_radius2;//!< squared radius of bounding sphere slice on target layer
|
||||
bool inside_somewhere = false;
|
||||
bool outside_somewhere = false;
|
||||
int inside;
|
||||
double part_dist;//what percentage of the radius the target layer is away from the center along the z axis. 0 - 1
|
||||
const long int layer_height = mesh.getSettingInMicrons("layer_height");
|
||||
long int bottom_layer = (center.z - radius) / layer_height;
|
||||
long int top_layer = (center.z + radius) / layer_height;
|
||||
for (long int test_layer = bottom_layer; test_layer <= top_layer; test_layer += 3) // steps of three. Low-hanging speed gain.
|
||||
{
|
||||
part_dist = (double)(test_layer * layer_height - center.z) / radius;
|
||||
sphere_slice_radius2 = radius * radius * (1.0 - (part_dist * part_dist));
|
||||
Point loc(center.x, center.y);
|
||||
|
||||
inside = distanceFromPointToMesh(mesh, test_layer, loc, &distance2);
|
||||
if (inside == 1)
|
||||
{
|
||||
inside_somewhere = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
outside_somewhere = true;
|
||||
}
|
||||
if (outside_somewhere && inside_somewhere)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if ((inside != 2) && distance2 < sphere_slice_radius2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int SubDivCube::distanceFromPointToMesh(SliceMeshStorage& mesh, long int layer_nr, Point& location, int64_t* distance2)
|
||||
{
|
||||
if (layer_nr < 0 || (unsigned long int)layer_nr >= mesh.layers.size()) //!< this layer is outside of valid range
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
Polygons collide;
|
||||
mesh.layers[layer_nr].getSecondOrInnermostWalls(collide);
|
||||
Point centerpoint = location;
|
||||
bool inside = collide.inside(centerpoint);
|
||||
ClosestPolygonPoint border_point = PolygonUtils::moveInside2(collide, centerpoint);
|
||||
Point diff = border_point.location - location;
|
||||
*distance2 = vSize2(diff);
|
||||
if (inside)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void SubDivCube::rotatePointInitial(Point& target)
|
||||
{
|
||||
target = infill_rotation_matrix.apply(target);
|
||||
}
|
||||
|
||||
void SubDivCube::rotatePoint120(Point& target)
|
||||
{
|
||||
constexpr double sqrt_three_fourths = 0.8660254037844386467637231707529361834714026269051903; //!< sqrt(3.0 / 4.0) = sqrt(3) / 2
|
||||
int64_t x;
|
||||
x = (-0.5) * target.X - sqrt_three_fourths * target.Y;
|
||||
target.Y = (-0.5)*target.Y + sqrt_three_fourths * target.X;
|
||||
target.X = x;
|
||||
}
|
||||
|
||||
void SubDivCube::addLineAndCombine(Polygons& group, Point from, Point to)
|
||||
{
|
||||
int epsilon = 10; // the smallest distance of two points which are viewed as coincident (dist > 0 due to rounding errors)
|
||||
for (unsigned int idx = 0; idx < group.size(); idx++)
|
||||
{
|
||||
if (std::abs(from.X - group[idx][1].X) < epsilon && std::abs(from.Y - group[idx][1].Y) < epsilon)
|
||||
{
|
||||
from = group[idx][0];
|
||||
group.remove(idx);
|
||||
idx--;
|
||||
continue;
|
||||
}
|
||||
if (std::abs(to.X - group[idx][0].X) < epsilon && std::abs(to.Y - group[idx][0].Y) < epsilon)
|
||||
{
|
||||
to = group[idx][1];
|
||||
group.remove(idx);
|
||||
idx--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
group.addLine(from, to);
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef INFILL_SUBDIVCUBE_H
|
||||
#define INFILL_SUBDIVCUBE_H
|
||||
|
||||
#include "../sliceDataStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class Infill;
|
||||
|
||||
class SubDivCube
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Constructor for SubDivCube. Recursively calls itself eight times to flesh out the octree.
|
||||
* \param mesh contains infill layer data and settings
|
||||
* \param my_center the center of the cube
|
||||
* \param depth the recursion depth of the cube (0 is most recursed)
|
||||
*/
|
||||
SubDivCube(SliceMeshStorage& mesh, Point3& center, unsigned int depth);
|
||||
|
||||
~SubDivCube(); //!< destructor (also destroys children
|
||||
|
||||
/*!
|
||||
* Precompute the octree of subdivided cubes
|
||||
* \param mesh contains infill layer data and settings
|
||||
*/
|
||||
static void precomputeOctree(SliceMeshStorage& mesh);
|
||||
/*!
|
||||
* Generates the lines of subdivision of the specific cube at the specific layer. It recursively calls itself, so it ends up drawing all the subdivision lines of sub-cubes too.
|
||||
* \param z the specified layer height
|
||||
* \param result (output) The resulting lines
|
||||
*/
|
||||
void generateSubdivisionLines(int64_t z, Polygons& result);
|
||||
private:
|
||||
/*!
|
||||
* Generates the lines of subdivision of the specific cube at the specific layer. It recursively calls itself, so it ends up drawing all the subdivision lines of sub-cubes too.
|
||||
* \param z the specified layer height
|
||||
* \param result (output) The resulting lines
|
||||
* \param directional_line_groups Array of 3 times a polylines. Used to keep track of line segments that are all pointing the same direction for line segment combining
|
||||
*/
|
||||
void generateSubdivisionLines(int64_t z, Polygons& result, Polygons (&directional_line_groups)[3]);
|
||||
struct CubeProperties
|
||||
{
|
||||
int64_t side_length; //!< side length of cubes
|
||||
int64_t height; //!< height of cubes based. This is the distance from one point of a cube to its 3d opposite.
|
||||
int64_t square_height; //!< square cut across lengths. This is the diagonal distance across a face of the cube.
|
||||
int64_t max_draw_z_diff; //!< maximum draw z differences. This is the maximum difference in z at which lines need to be drawn.
|
||||
int64_t max_line_offset; //!< maximum line offsets. This is the maximum distance at which subdivision lines should be drawn from the 2d cube center.
|
||||
};
|
||||
/*!
|
||||
* Rotates a point 120 degrees about the origin.
|
||||
* \param target the point to rotate.
|
||||
*/
|
||||
static void rotatePoint120(Point& target);
|
||||
/*!
|
||||
* Rotates a point to align it with the orientation of the infill.
|
||||
* \param target the point to rotate.
|
||||
*/
|
||||
static void rotatePointInitial(Point& target);
|
||||
/*!
|
||||
* Determines if a described theoretical cube should be subdivided based on if a sphere that encloses the cube touches the infill mesh.
|
||||
* \param mesh contains infill layer data and settings
|
||||
* \param center the center of the described cube
|
||||
* \param radius the radius of the enclosing sphere
|
||||
* \return the described cube should be subdivided
|
||||
*/
|
||||
static bool isValidSubdivision(SliceMeshStorage& mesh, Point3& center, int64_t radius);
|
||||
/*!
|
||||
* Finds the distance to the infill border at the specified layer from the specified point.
|
||||
* \param mesh contains infill layer data and settings
|
||||
* \param layer_nr the number of the specified layer
|
||||
* \param location the location of the specified point
|
||||
* \param[out] distance2 the squared distance to the infill border
|
||||
* \return Code 0: outside, 1: inside, 2: boundary does not exist at specified layer
|
||||
*/
|
||||
static int distanceFromPointToMesh(SliceMeshStorage& mesh, long int layer_nr, Point& location, int64_t* distance2);
|
||||
|
||||
/*!
|
||||
* Adds the defined line to the specified polygons. It assumes that the specified polygons are all parallel lines. Combines line segments with touching ends closer than epsilon.
|
||||
* \param[out] group the polygons to add the line to
|
||||
* \param from the first endpoint of the line
|
||||
* \param to the second endpoint of the line
|
||||
*/
|
||||
void addLineAndCombine(Polygons& group, Point from, Point to);
|
||||
|
||||
unsigned int depth; //!< the recursion depth of the cube (0 is most recursed)
|
||||
Point3 center; //!< center location of the cube in absolute coordinates
|
||||
SubDivCube* children[8] = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; //!< pointers to this cube's eight octree children
|
||||
static std::vector<CubeProperties> cube_properties_per_recursion_step; //!< precomputed array of basic properties of cubes based on recursion depth.
|
||||
static double radius_multiplier; //!< multiplier for the bounding radius when determining if a cube should be subdivided
|
||||
static Point3Matrix rotation_matrix; //!< The rotation matrix to get from axis aligned cubes to cubes standing on a corner point aligned with the infill_angle
|
||||
static PointMatrix infill_rotation_matrix; //!< Horizontal rotation applied to infill
|
||||
static int32_t radius_addition; //!< addition to the bounding radius when determining if a cube should be subdivided
|
||||
};
|
||||
|
||||
}
|
||||
#endif //INFILL_SUBDIVCUBE_H
|
||||
@@ -109,9 +109,7 @@ protected:
|
||||
*/
|
||||
void addLine(Point from, Point to)
|
||||
{
|
||||
PolygonRef line_poly = result.newPoly();
|
||||
line_poly.add(rotation_matrix.unapply(from));
|
||||
line_poly.add(rotation_matrix.unapply(to));
|
||||
result.addLine(rotation_matrix.unapply(from), rotation_matrix.unapply(to));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/string.h"
|
||||
@@ -210,7 +211,7 @@ void slice(int argc, char **argv)
|
||||
}
|
||||
else
|
||||
{
|
||||
last_settings_object = &(meshgroup->meshes.back()); // pointer is valid until a new object is added, so this is OK
|
||||
last_settings_object = meshgroup->meshes.back();
|
||||
}
|
||||
break;
|
||||
case 'o':
|
||||
|
||||
+26
-3
@@ -19,12 +19,21 @@ Mesh::Mesh(SettingsBaseVirtual* parent)
|
||||
{
|
||||
}
|
||||
|
||||
void Mesh::addFace(Point3& v0, Point3& v1, Point3& v2)
|
||||
bool Mesh::addFace(Point3& v0, Point3& v1, Point3& v2)
|
||||
{
|
||||
int vi0 = findIndexOfVertex(v0);
|
||||
int vi1 = findIndexOfVertex(v1);
|
||||
int vi2 = findIndexOfVertex(v2);
|
||||
if (vi0 == vi1 || vi1 == vi2 || vi0 == vi2) return; // the face has two vertices which get assigned the same location. Don't add the face.
|
||||
return addFace(vi0, vi1, vi2);
|
||||
}
|
||||
|
||||
bool Mesh::addFace(int vi0, int vi1, int vi2)
|
||||
{
|
||||
if (vi0 == vi1 || vi1 == vi2 || vi0 == vi2)
|
||||
{
|
||||
// the face has two vertices which get assigned the same location. Don't add the face.
|
||||
return false;
|
||||
}
|
||||
|
||||
int idx = faces.size(); // index of face to be added
|
||||
faces.emplace_back();
|
||||
@@ -35,6 +44,8 @@ void Mesh::addFace(Point3& v0, Point3& v1, Point3& v2)
|
||||
vertices[face.vertex_index[0]].connected_faces.push_back(idx);
|
||||
vertices[face.vertex_index[1]].connected_faces.push_back(idx);
|
||||
vertices[face.vertex_index[2]].connected_faces.push_back(idx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mesh::clear()
|
||||
@@ -74,10 +85,21 @@ AABB3D Mesh::getAABB() const
|
||||
}
|
||||
void Mesh::expandXY(int64_t offset)
|
||||
{
|
||||
aabb.expandXY(offset);
|
||||
if (offset)
|
||||
{
|
||||
aabb.expandXY(offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Mesh::addVertex(const Point3& v)
|
||||
{
|
||||
uint32_t hash = pointHash(v);
|
||||
vertex_hash_map[hash].push_back(vertices.size());
|
||||
vertices.emplace_back(v);
|
||||
aabb.include(v);
|
||||
}
|
||||
|
||||
int Mesh::findIndexOfVertex(const Point3& v)
|
||||
{
|
||||
uint32_t hash = pointHash(v);
|
||||
@@ -186,4 +208,5 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx, int notFaceVe
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+21
-1
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "settings/settings.h"
|
||||
#include "utils/AABB3D.h"
|
||||
#include "textureProcessing/MatSegment.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -65,7 +66,26 @@ public:
|
||||
|
||||
Mesh(SettingsBaseVirtual* parent); //!< initializes the settings
|
||||
|
||||
void addFace(Point3& v0, Point3& v1, Point3& v2); //!< add a face to the mesh without settings it's connected_faces.
|
||||
virtual ~Mesh() {} //!< Destructor
|
||||
|
||||
/*!
|
||||
* add a face to the mesh without settings it's connected_faces.
|
||||
*
|
||||
* Don't add a face when the surface is zero mm^2
|
||||
*
|
||||
* \return whether a face has been added
|
||||
*/
|
||||
bool addFace(Point3& v0, Point3& v1, Point3& v2);
|
||||
/*!
|
||||
* add a face to the mesh without settings it's connected_faces.
|
||||
*
|
||||
* Don't add a face when the surface is zero mm^2
|
||||
*
|
||||
* \return whether a face has been added
|
||||
*/
|
||||
bool addFace(int vi0, int vi1, int vi2);
|
||||
void addVertex(const Point3& v);
|
||||
|
||||
void clear(); //!< clears all data
|
||||
void finish(); //!< complete the model : set the connected_face_index fields of the faces.
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ int PathOrderOptimizer::getPolyStart(Point prev_point, int poly_idx)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EZSeamType::BACK: return getFarthestPointInPolygon(poly_idx);
|
||||
case EZSeamType::BACK: return getClosestPointInPolygon(z_seam_pos, poly_idx);
|
||||
case EZSeamType::RANDOM: return getRandomPointInPolygon(poly_idx);
|
||||
case EZSeamType::SHORTEST: return getClosestPointInPolygon(prev_point, poly_idx);
|
||||
default: return getClosestPointInPolygon(prev_point, poly_idx);
|
||||
@@ -129,24 +129,6 @@ int PathOrderOptimizer::getRandomPointInPolygon(int poly_idx)
|
||||
return rand() % polygons[poly_idx].size();
|
||||
}
|
||||
|
||||
|
||||
int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
|
||||
{
|
||||
PolygonRef poly = polygons[poly_idx];
|
||||
int best_point_idx = -1;
|
||||
float best_y = std::numeric_limits<float>::min();
|
||||
for(unsigned int point_idx=0 ; point_idx<poly.size() ; point_idx++)
|
||||
{
|
||||
if (poly[point_idx].Y > best_y)
|
||||
{
|
||||
best_point_idx = point_idx;
|
||||
best_y = poly[point_idx].Y;
|
||||
}
|
||||
}
|
||||
return best_point_idx;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -18,14 +18,16 @@ class PathOrderOptimizer
|
||||
{
|
||||
public:
|
||||
EZSeamType type;
|
||||
Point startPoint; //!< The location of the nozzle before starting to print the current layer
|
||||
Point startPoint; //!< A location near the prefered start location
|
||||
Point z_seam_pos; //!< The position near where to create the z_seam (if \ref PathOrderOptimizer::type == 'back')
|
||||
std::vector<PolygonRef> polygons; //!< the parts of the layer (in arbitrary order)
|
||||
std::vector<int> polyStart; //!< polygons[i][polyStart[i]] = point of polygon i which is to be the starting point in printing the polygon
|
||||
std::vector<int> polyOrder; //!< the optimized order as indices in #polygons
|
||||
|
||||
PathOrderOptimizer(Point startPoint, EZSeamType type = EZSeamType::SHORTEST)
|
||||
PathOrderOptimizer(Point startPoint, Point z_seam_pos = Point(0, 0), EZSeamType type = EZSeamType::SHORTEST)
|
||||
: type(type)
|
||||
, startPoint(startPoint)
|
||||
, z_seam_pos(z_seam_pos)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -43,9 +45,15 @@ public:
|
||||
void optimize(); //!< sets #polyStart and #polyOrder
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Get the starting vertex of a polygon, depending on the \ref PathOrderOptimizer::type
|
||||
* \param prev_point The previous planned location
|
||||
* \param poly_idx The index of the polygon in \ref PathOrderOptimizer::polygons
|
||||
* \return the index of the starting vertex in \ref PathOrderOptimizer::polygons[\p poly_idx]
|
||||
*/
|
||||
int getPolyStart(Point prev_point, int poly_idx);
|
||||
|
||||
int getClosestPointInPolygon(Point prev, int i_polygon); //!< returns the index of the closest point
|
||||
int getFarthestPointInPolygon(int poly_idx); //!< return the index to the point farthest from the front (highest y)
|
||||
int getRandomPointInPolygon(int poly_idx);
|
||||
|
||||
|
||||
|
||||
+107
-53
@@ -2,35 +2,25 @@
|
||||
#include "Comb.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional> // function
|
||||
#include <unordered_set>
|
||||
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/PolygonsPointIndex.h"
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "../utils/SVG.h"
|
||||
#include "../utils/linearAlg2D.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
// boundary_outside is only computed when it's needed!
|
||||
Polygons& Comb::getBoundaryOutside()
|
||||
LocToLineGrid& Comb::getOutsideLocToLine()
|
||||
{
|
||||
if (!boundary_outside)
|
||||
{
|
||||
boundary_outside = new Polygons();
|
||||
*boundary_outside = storage.getLayerOutlines(layer_nr, false).offset(offset_from_outlines_outside);
|
||||
}
|
||||
return *boundary_outside;
|
||||
}
|
||||
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& Comb::getOutsideLocToLine()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
Polygons& Comb::getBoundaryOutside()
|
||||
{
|
||||
return *boundary_outside;
|
||||
}
|
||||
|
||||
Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
@@ -41,23 +31,31 @@ Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_insi
|
||||
, offset_from_inside_to_outside(offset_from_outlines + offset_from_outlines_outside)
|
||||
, max_crossing_dist2(offset_from_inside_to_outside * offset_from_inside_to_outside * 2) // so max_crossing_dist = offset_from_inside_to_outside * sqrt(2) =approx 1.5 to allow for slightly diagonal crossings and slightly inaccurate crossing computation
|
||||
, avoid_other_parts(travel_avoid_other_parts)
|
||||
// , boundary_inside( boundary.offset(-offset_from_outlines) ) // TODO: make inside boundary configurable?
|
||||
, boundary_inside( comb_boundary_inside )
|
||||
, boundary_outside(nullptr)
|
||||
, outside_loc_to_line(nullptr)
|
||||
, partsView_inside( boundary_inside.splitIntoPartsView() ) // !! changes the order of boundary_inside !!
|
||||
, partsView_inside( boundary_inside.splitIntoPartsView() ) // WARNING !! changes the order of boundary_inside !!
|
||||
, inside_loc_to_line(PolygonUtils::createLocToLineGrid(boundary_inside, comb_boundary_offset))
|
||||
, boundary_outside(
|
||||
[&storage, layer_nr, travel_avoid_distance]()
|
||||
{
|
||||
return storage.getLayerOutlines(layer_nr, false).offset(travel_avoid_distance);
|
||||
}
|
||||
)
|
||||
, outside_loc_to_line(
|
||||
[](Comb* comber, const int64_t offset_from_inside_to_outside)
|
||||
{
|
||||
return PolygonUtils::createLocToLineGrid(comber->getBoundaryOutside(), offset_from_inside_to_outside * 3 / 2);
|
||||
}
|
||||
, this
|
||||
, offset_from_inside_to_outside
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
Comb::~Comb()
|
||||
{
|
||||
if (boundary_outside)
|
||||
if (inside_loc_to_line)
|
||||
{
|
||||
delete boundary_outside;
|
||||
}
|
||||
if (outside_loc_to_line)
|
||||
{
|
||||
delete outside_loc_to_line;
|
||||
delete inside_loc_to_line;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +82,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
{ // normal combing within part
|
||||
PolygonsPart part = partsView_inside.assemblePart(start_part_idx);
|
||||
combPaths.emplace_back();
|
||||
return LinePolygonsCrossings::comb(part, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
return LinePolygonsCrossings::comb(part, *inside_loc_to_line, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
}
|
||||
else
|
||||
{ // comb inside part to edge (if needed) >> move through air avoiding other parts >> comb inside end part upto the endpoint (if needed)
|
||||
@@ -99,31 +97,31 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
return false;
|
||||
}
|
||||
|
||||
Crossing start_crossing(startPoint, startInside, start_part_idx, start_part_boundary_poly_idx, boundary_inside);
|
||||
Crossing end_crossing(endPoint, endInside, end_part_idx, end_part_boundary_poly_idx, boundary_inside);
|
||||
Crossing start_crossing(startPoint, startInside, start_part_idx, start_part_boundary_poly_idx, boundary_inside, inside_loc_to_line);
|
||||
Crossing end_crossing(endPoint, endInside, end_part_idx, end_part_boundary_poly_idx, boundary_inside, inside_loc_to_line);
|
||||
|
||||
{ // find crossing over the in-between area between inside and outside
|
||||
start_crossing.findCrossingInOrMid(partsView_inside, endPoint);
|
||||
end_crossing.findCrossingInOrMid(partsView_inside, start_crossing.in_or_mid);
|
||||
}
|
||||
|
||||
bool avoid_other_parts_now = avoid_other_parts;
|
||||
if (avoid_other_parts_now && vSize2(start_crossing.in_or_mid - end_crossing.in_or_mid) < offset_from_inside_to_outside * offset_from_inside_to_outside * 4)
|
||||
bool skip_avoid_other_parts_path = false;
|
||||
if (skip_avoid_other_parts_path && vSize2(start_crossing.in_or_mid - end_crossing.in_or_mid) < offset_from_inside_to_outside * offset_from_inside_to_outside * 4)
|
||||
{ // parts are next to eachother, i.e. the direct crossing will always be smaller than two crossings via outside
|
||||
avoid_other_parts_now = false;
|
||||
skip_avoid_other_parts_path = true;
|
||||
}
|
||||
|
||||
if (avoid_other_parts_now)
|
||||
if (avoid_other_parts && !skip_avoid_other_parts_path)
|
||||
{ // compute the crossing points when moving through air
|
||||
Polygons& outside = getBoundaryOutside(); // comb through all air, since generally the outside consists of a single part
|
||||
// comb through all air, since generally the outside consists of a single part
|
||||
|
||||
bool success = start_crossing.findOutside(outside, end_crossing.in_or_mid, fail_on_unavoidable_obstacles, *this);
|
||||
bool success = start_crossing.findOutside(*boundary_outside, end_crossing.in_or_mid, fail_on_unavoidable_obstacles, *this);
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
success = end_crossing.findOutside(outside, start_crossing.out, fail_on_unavoidable_obstacles, *this);
|
||||
success = end_crossing.findOutside(*boundary_outside, start_crossing.out, fail_on_unavoidable_obstacles, *this);
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
@@ -136,7 +134,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
// start to boundary
|
||||
assert(start_crossing.dest_part.size() > 0 && "The part we start inside when combing should have been computed already!");
|
||||
combPaths.emplace_back();
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(start_crossing.dest_part, startPoint, start_crossing.in_or_mid, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(start_crossing.dest_part, *inside_loc_to_line, startPoint, start_crossing.in_or_mid, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{ // Couldn't comb between start point and computed crossing from the start part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon
|
||||
return false;
|
||||
@@ -144,7 +142,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
}
|
||||
|
||||
// throught air from boundary to boundary
|
||||
if (avoid_other_parts_now)
|
||||
if (avoid_other_parts && !skip_avoid_other_parts_path)
|
||||
{
|
||||
combPaths.emplace_back();
|
||||
combPaths.throughAir = true;
|
||||
@@ -155,7 +153,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
}
|
||||
else
|
||||
{
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(getBoundaryOutside(), start_crossing.out, end_crossing.out, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(*boundary_outside, *outside_loc_to_line, start_crossing.out, end_crossing.out, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{
|
||||
return false;
|
||||
@@ -166,10 +164,28 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
{ // directly through air (not avoiding other parts)
|
||||
combPaths.emplace_back();
|
||||
combPaths.throughAir = true;
|
||||
combPaths.back().cross_boundary = true; // TODO: calculate whether we cross a boundary!
|
||||
combPaths.back().cross_boundary = true; // note: we don't actually know whether this is cross boundary, but it might very well be
|
||||
combPaths.back().push_back(start_crossing.in_or_mid);
|
||||
combPaths.back().push_back(end_crossing.in_or_mid);
|
||||
}
|
||||
if (skip_avoid_other_parts_path)
|
||||
{
|
||||
if (startInside == endInside && start_part_idx == end_part_idx)
|
||||
{
|
||||
if (startInside)
|
||||
{ // both start and end are inside
|
||||
combPaths.back().cross_boundary = PolygonUtils::polygonCollidesWithLineSegment(startPoint, endPoint, *inside_loc_to_line);
|
||||
}
|
||||
else
|
||||
{ // both start and end are outside
|
||||
combPaths.back().cross_boundary = PolygonUtils::polygonCollidesWithLineSegment(startPoint, endPoint, *outside_loc_to_line);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
combPaths.back().cross_boundary = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (endInside)
|
||||
{
|
||||
@@ -177,7 +193,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
assert(end_crossing.dest_part.size() > 0 && "The part we end up inside when combing should have been computed already!");
|
||||
combPaths.emplace_back();
|
||||
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(end_crossing.dest_part, end_crossing.in_or_mid, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(end_crossing.dest_part, *inside_loc_to_line, end_crossing.in_or_mid, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{ // Couldn't comb between end point and computed crossing to the end part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon
|
||||
return false;
|
||||
@@ -188,21 +204,25 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
}
|
||||
}
|
||||
|
||||
Comb::Crossing::Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside)
|
||||
Comb::Crossing::Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside, const LocToLineGrid* inside_loc_to_line)
|
||||
: dest_is_inside(dest_is_inside)
|
||||
, dest_crossing_poly(boundary_inside[dest_part_boundary_crossing_poly_idx]) // initialize with most obvious poly, cause mostly a combing move will move outside the part, rather than inside a hole in the part
|
||||
, boundary_inside(boundary_inside)
|
||||
, inside_loc_to_line(inside_loc_to_line)
|
||||
, dest_point(dest_point)
|
||||
, dest_part_idx(dest_part_idx)
|
||||
{
|
||||
|
||||
if (dest_is_inside)
|
||||
{
|
||||
dest_crossing_poly = boundary_inside[dest_part_boundary_crossing_poly_idx]; // initialize with most obvious poly, cause mostly a combing move will move outside the part, rather than inside a hole in the part
|
||||
}
|
||||
}
|
||||
|
||||
bool Comb::moveInside(bool is_inside, Point& dest_point, unsigned int& inside_poly)
|
||||
{
|
||||
if (is_inside)
|
||||
{
|
||||
ClosestPolygonPoint cpp = PolygonUtils::ensureInsideOrOutside(boundary_inside, dest_point, offset_extra_start_end, max_moveInside_distance2);
|
||||
if (cpp.point_idx == NO_INDEX)
|
||||
ClosestPolygonPoint cpp = PolygonUtils::ensureInsideOrOutside(boundary_inside, dest_point, offset_extra_start_end, max_moveInside_distance2, &boundary_inside, inside_loc_to_line);
|
||||
if (!cpp.isValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -223,10 +243,43 @@ void Comb::Crossing::findCrossingInOrMid(const PartsView& partsView_inside, cons
|
||||
Point _dest_point(dest_point); // copy to local variable for lambda capture
|
||||
std::function<int(Point)> close_towards_start_penalty_function([_dest_point](Point candidate){ return vSize2((candidate - _dest_point) / 10); });
|
||||
dest_part = partsView_inside.assemblePart(dest_part_idx);
|
||||
Point result(close_to);
|
||||
|
||||
ClosestPolygonPoint boundary_crossing_point;
|
||||
{ // set [result] to a point on the destination part closest to close_to (but also a bit close to _dest_point)
|
||||
std::unordered_set<unsigned int> dest_part_poly_indices;
|
||||
for (unsigned int poly_idx : partsView_inside[dest_part_idx])
|
||||
{
|
||||
dest_part_poly_indices.emplace(poly_idx);
|
||||
}
|
||||
coord_t dist2_score = std::numeric_limits<coord_t>::max();
|
||||
std::function<bool (const PolygonsPointIndex&)> line_processor
|
||||
= [close_to, _dest_point, &boundary_crossing_point, &dist2_score, &dest_part_poly_indices](const PolygonsPointIndex& boundary_segment)
|
||||
{
|
||||
if (dest_part_poly_indices.find(boundary_segment.poly_idx) == dest_part_poly_indices.end())
|
||||
{ // we're not looking at a polygon from the dest_part
|
||||
return true; // a.k.a. continue;
|
||||
}
|
||||
Point closest_here = LinearAlg2D::getClosestOnLineSegment(close_to, boundary_segment.p(), boundary_segment.next().p());
|
||||
coord_t dist2_score_here = vSize2(close_to - closest_here) + vSize2(_dest_point - closest_here) / 10;
|
||||
if (dist2_score_here < dist2_score)
|
||||
{
|
||||
dist2_score = dist2_score_here;
|
||||
boundary_crossing_point = ClosestPolygonPoint(closest_here, boundary_segment.point_idx, boundary_segment.getPolygon(), boundary_segment.poly_idx);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
inside_loc_to_line->processLine(std::make_pair(dest_point, close_to), line_processor);
|
||||
}
|
||||
|
||||
Point result(boundary_crossing_point.p()); // the inside point of the crossing
|
||||
if (!boundary_crossing_point.isValid())
|
||||
{ // no point has been found in the sparse grid
|
||||
result = dest_point;
|
||||
}
|
||||
|
||||
int64_t max_dist2 = std::numeric_limits<int64_t>::max();
|
||||
ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::ensureInsideOrOutside(dest_part, result, offset_dist_to_get_from_on_the_polygon_to_outside, max_dist2, close_towards_start_penalty_function);
|
||||
if (crossing_1_in_cp.point_idx != NO_INDEX)
|
||||
ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::ensureInsideOrOutside(dest_part, result, boundary_crossing_point, offset_dist_to_get_from_on_the_polygon_to_outside, max_dist2, &boundary_inside, inside_loc_to_line, close_towards_start_penalty_function);
|
||||
if (crossing_1_in_cp.isValid())
|
||||
{
|
||||
dest_crossing_poly = crossing_1_in_cp.poly;
|
||||
in_or_mid = result;
|
||||
@@ -263,7 +316,8 @@ bool Comb::Crossing::findOutside(const Polygons& outside, const Point close_to,
|
||||
if (dest_is_inside && in_out_dist2_1 > comber.max_crossing_dist2) // moveInside moved too far
|
||||
{ // if move is too far over in_between
|
||||
// find crossing closer by
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(outside, dest_crossing_poly, dest_point, close_to, comber);
|
||||
assert(dest_crossing_poly && "destination crossing poly should have been instantiated!");
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(outside, *dest_crossing_poly, dest_point, close_to, comber);
|
||||
if (best)
|
||||
{
|
||||
in_or_mid = PolygonUtils::moveInside(best->first, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
|
||||
+23
-13
@@ -4,9 +4,11 @@
|
||||
|
||||
#include <memory> // shared_ptr
|
||||
|
||||
#include "../utils/optional.h"
|
||||
#include "../utils/polygon.h"
|
||||
#include "../utils/SparsePointGridInclusive.h"
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/LazyInitialization.h"
|
||||
|
||||
#include "LinePolygonsCrossings.h"
|
||||
#include "CombPath.h"
|
||||
@@ -30,7 +32,7 @@ class SliceDataStorage;
|
||||
* As an optimization, the combing paths inside are calculated on specifically those PolygonsParts within which to comb, while the coundary_outside isn't split into outside parts,
|
||||
* because generally there is only one outside part; encapsulated holes occur less often.
|
||||
*/
|
||||
class Comb
|
||||
class Comb
|
||||
{
|
||||
friend class LinePolygonsCrossings;
|
||||
private:
|
||||
@@ -46,7 +48,9 @@ private:
|
||||
Point in_or_mid; //!< The point on the inside boundary, or in between the inside and outside boundary if the start/end point isn't inside the inside boudary
|
||||
Point out; //!< The point on the outside boundary
|
||||
PolygonsPart dest_part; //!< The assembled inside-boundary PolygonsPart in which the dest_point lies. (will only be initialized when Crossing::dest_is_inside holds)
|
||||
PolygonRef dest_crossing_poly; //!< The polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon)
|
||||
std::optional<PolygonRef> dest_crossing_poly; //!< The polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon)
|
||||
const Polygons& boundary_inside; //!< The inside boundary as in \ref Comb::boundary_inside
|
||||
const LocToLineGrid* inside_loc_to_line; //!< The loc to line grid \ref Comb::inside_loc_to_line
|
||||
|
||||
/*!
|
||||
* Simple constructor
|
||||
@@ -57,7 +61,7 @@ private:
|
||||
* \param dest_part_boundary_crossing_poly_idx The index in \p boundary_inside of the polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon).
|
||||
* \param boundary_inside The boundary within which to comb.
|
||||
*/
|
||||
Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside);
|
||||
Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside, const LocToLineGrid* inside_loc_to_line);
|
||||
|
||||
/*!
|
||||
* Find the not-outside location (Combing::in_or_mid) of the crossing between to the outside boundary
|
||||
@@ -112,19 +116,20 @@ private:
|
||||
const bool avoid_other_parts; //!< Whether to perform inverse combing a.k.a. avoid parts.
|
||||
|
||||
Polygons& boundary_inside; //!< The boundary within which to comb.
|
||||
Polygons* boundary_outside; //!< The boundary outside of which to stay to avoid collision with other layer parts. This is a pointer cause we only compute it when we move outside the boundary (so not when there is only a single part in the layer)
|
||||
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.
|
||||
LocToLineGrid* inside_loc_to_line; //!< The SparsePointGridInclusive mapping locations to line segments of the inner boundary.
|
||||
LazyInitialization<Polygons> boundary_outside; //!< The boundary outside of which to stay to avoid collision with other layer parts. This is a pointer cause we only compute it when we move outside the boundary (so not when there is only a single part in the layer)
|
||||
LazyInitialization<LocToLineGrid, Comb*, const int64_t> outside_loc_to_line; //!< The SparsePointGridInclusive mapping locations to line segments of the outside boundary.
|
||||
|
||||
/*!
|
||||
* Get the boundary_outside, which is an offset from the outlines of all meshes in the layer. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
Polygons& getBoundaryOutside();
|
||||
|
||||
/*!
|
||||
* Get the SparsePointGridInclusive mapping locations to line segments of the outside boundary. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& getOutsideLocToLine();
|
||||
LocToLineGrid& getOutsideLocToLine();
|
||||
|
||||
/*!
|
||||
* Get the boundary_outside, which is an offset from the outlines of all meshes in the layer. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
Polygons& getBoundaryOutside();
|
||||
|
||||
/*!
|
||||
* Move the startPoint or endPoint inside when it should be inside
|
||||
@@ -138,6 +143,9 @@ private:
|
||||
public:
|
||||
/*!
|
||||
* Initializes the combing areas for every mesh in the layer (not support)
|
||||
*
|
||||
* \warning \ref Comb::calc changes the order of polygons in \p Comb::comb_boundary_inside
|
||||
*
|
||||
* \param storage Where the layer polygon data is stored
|
||||
* \param layer_nr The number of the layer for which to generate the combing areas.
|
||||
* \param comb_boundary_inside The comb boundary within which to comb within layer parts.
|
||||
@@ -146,12 +154,14 @@ public:
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
*/
|
||||
Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t offset_from_outlines, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
|
||||
|
||||
~Comb();
|
||||
|
||||
/*!
|
||||
* Calculate the comb paths (if any) - one for each polygon combed alternated with travel paths
|
||||
*
|
||||
* \warning Changes the order of polygons in \ref Comb::comb_boundary_inside
|
||||
*
|
||||
* \param startPoint Where to start moving from
|
||||
* \param endPoint Where to move to
|
||||
* \param combPoints Output parameter: The points along the combing path, excluding the \p startPoint (?) and \p endPoint
|
||||
@@ -160,7 +170,7 @@ public:
|
||||
* \param via_outside_makes_combing_fail When going through air is inavoidable, stop calculation early and return false.
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \return Whether combing has succeeded; otherwise a retraction is needed.
|
||||
*/
|
||||
*/
|
||||
bool calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside, bool endInside, int64_t max_comb_distance_ignored, bool via_outside_makes_combing_fail, bool fail_on_unavoidable_obstacles);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//Copyright (C) 2016 Ultimaker
|
||||
//Released under terms of the AGPLv3 License
|
||||
|
||||
#include "GCodePath.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
bool GCodePath::isTravelPath()
|
||||
{
|
||||
return config->isTravelPath();
|
||||
}
|
||||
|
||||
double GCodePath::getExtrusionMM3perMM()
|
||||
{
|
||||
return flow * config->getExtrusionMM3perMM();
|
||||
}
|
||||
|
||||
int GCodePath::getLineWidth()
|
||||
{
|
||||
return flow * config->getLineWidth() * config->getFlowPercentage() / 100.0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef PATH_PLANNING_G_CODE_PATH_H
|
||||
#define PATH_PLANNING_G_CODE_PATH_H
|
||||
|
||||
#include "../SpaceFillType.h"
|
||||
#include "../GCodePathConfig.h"
|
||||
|
||||
#include "TimeMaterialEstimates.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A class for representing a planned path.
|
||||
*
|
||||
* A path consists of several segments of the same type of movement: retracted travel, infill extrusion, etc.
|
||||
*
|
||||
* This is a compact premature representation in which are line segments have the same config, i.e. the config of this path.
|
||||
*
|
||||
* In the final representation (gcode) each line segment may have different properties,
|
||||
* which are added when the generated GCodePaths are processed.
|
||||
*/
|
||||
class GCodePath
|
||||
{
|
||||
public:
|
||||
GCodePathConfig* config; //!< The configuration settings of the path.
|
||||
SpaceFillType space_fill_type; //!< The type of space filling of which this path is a part
|
||||
float flow; //!< A type-independent flow configuration (used for wall overlap compensation)
|
||||
bool retract; //!< Whether the path is a move path preceded by a retraction move; whether the path is a retracted move path.
|
||||
bool perform_z_hop; //!< Whether to perform a z_hop in this path, which is assumed to be a travel path.
|
||||
bool perform_prime; //!< Whether this path is preceded by a prime (poop)
|
||||
std::vector<Point> points; //!< The points constituting this path.
|
||||
bool done;//!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one.
|
||||
|
||||
bool spiralize; //!< Whether to gradually increment the z position during the printing of this path. A sequence of spiralized paths should start at the given layer height and end in one layer higher.
|
||||
|
||||
TimeMaterialEstimates estimates; //!< Naive time and material estimates
|
||||
|
||||
/*!
|
||||
* Whether this config is the config of a travel path.
|
||||
*
|
||||
* \return Whether this config is the config of a travel path.
|
||||
*/
|
||||
bool isTravelPath();
|
||||
|
||||
/*!
|
||||
* Get the material flow in mm^3 per mm traversed.
|
||||
*
|
||||
* \warning Can only be called after the layer height has been set (which is done while writing the gcode!)
|
||||
*
|
||||
* \return The flow
|
||||
*/
|
||||
double getExtrusionMM3perMM();
|
||||
|
||||
/*!
|
||||
* Get the actual line width (modulated by the flow)
|
||||
* \return the actual line width as shown in layer view
|
||||
*/
|
||||
int getLineWidth();
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//PATH_PLANNING_G_CODE_PATH_H
|
||||
@@ -145,7 +145,7 @@ void LinePolygonsCrossings::getBasicCombingPath(CombPath& combPath)
|
||||
void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, CombPath& combPath)
|
||||
{
|
||||
PolygonRef poly = boundary[polyCrossings.poly_idx];
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x - dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x - std::abs(dist_to_move_boundary_point_outside), transformed_startPoint.Y)));
|
||||
if ( ( polyCrossings.max.point_idx - polyCrossings.min.point_idx + poly.size() ) % poly.size()
|
||||
< poly.size() / 2 )
|
||||
{ // follow the path in the same direction as the winding order of the boundary polygon
|
||||
@@ -166,7 +166,7 @@ void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, Co
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x + dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x + std::abs(dist_to_move_boundary_point_outside), transformed_startPoint.Y)));
|
||||
}
|
||||
|
||||
|
||||
@@ -194,9 +194,9 @@ bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimize
|
||||
continue;
|
||||
}
|
||||
Point& current_point = optimized_comb_path.back();
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx]))
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(current_point, comb_path[point_idx], loc_to_line_grid))
|
||||
{
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx - 1]))
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(current_point, comb_path[point_idx - 1], loc_to_line_grid))
|
||||
{
|
||||
comb_path.cross_boundary = true;
|
||||
}
|
||||
@@ -209,7 +209,7 @@ bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimize
|
||||
// TODO: add the below extra optimization? (+/- 7% extra computation time, +/- 2% faster print for Dual_extrusion_support_generation.stl)
|
||||
while (optimized_comb_path.size() > 1)
|
||||
{
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx]))
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx], loc_to_line_grid))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#define PATH_PLANNING_LINE_POLYGONS_CROSSINGS_H
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/SparseLineGrid.h"
|
||||
|
||||
#include "CombPath.h"
|
||||
|
||||
@@ -80,6 +82,7 @@ private:
|
||||
unsigned int max_crossing_idx; //!< The index into LinePolygonsCrossings::crossings to the crossing with the maximal PolyCrossings::max crossing of all PolyCrossings's.
|
||||
|
||||
Polygons& boundary; //!< The boundary not to cross during combing.
|
||||
LocToLineGrid& loc_to_line_grid; //!< Mapping from locations to line segments of \ref LinePolygonsCrossings::boundary
|
||||
Point startPoint; //!< The start point of the scanline.
|
||||
Point endPoint; //!< The end point of the scanline.
|
||||
|
||||
@@ -163,8 +166,12 @@ private:
|
||||
* \param end the end point
|
||||
* \param dist_to_move_boundary_point_outside Distance used to move a point from a boundary so that it doesn't intersect with it anymore. (Precision issue)
|
||||
*/
|
||||
LinePolygonsCrossings(Polygons& boundary, Point& start, Point& end, int64_t dist_to_move_boundary_point_outside)
|
||||
: boundary(boundary), startPoint(start), endPoint(end), dist_to_move_boundary_point_outside(dist_to_move_boundary_point_outside)
|
||||
LinePolygonsCrossings(Polygons& boundary, LocToLineGrid& loc_to_line_grid, Point& start, Point& end, int64_t dist_to_move_boundary_point_outside)
|
||||
: boundary(boundary)
|
||||
, loc_to_line_grid(loc_to_line_grid)
|
||||
, startPoint(start)
|
||||
, endPoint(end)
|
||||
, dist_to_move_boundary_point_outside(dist_to_move_boundary_point_outside)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -173,15 +180,16 @@ public:
|
||||
/*!
|
||||
* The main function of this class: calculate one combing path within the boundary.
|
||||
* \param boundary The polygons to follow when calculating the basic combing path
|
||||
* \param loc_to_line_grid A sparse grid mapping cells to all line segments of (at least) \p boundary in those cells
|
||||
* \param startPoint From where to start the combing move.
|
||||
* \param endPoint Where to end the combing move.
|
||||
* \param combPath Output parameter: the combing path generated.
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \return Whether combing succeeded, i.e. we didn't cross any gaps/other parts
|
||||
*/
|
||||
static bool comb(Polygons& boundary, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles)
|
||||
static bool comb(Polygons& boundary, LocToLineGrid& loc_to_line_grid, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles)
|
||||
{
|
||||
LinePolygonsCrossings linePolygonsCrossings(boundary, startPoint, endPoint, dist_to_move_boundary_point_outside);
|
||||
LinePolygonsCrossings linePolygonsCrossings(boundary, loc_to_line_grid, startPoint, endPoint, dist_to_move_boundary_point_outside);
|
||||
return linePolygonsCrossings.getCombingPath(combPath, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//Copyright (C) 2016 Ultimaker
|
||||
//Released under terms of the AGPLv3 License
|
||||
|
||||
#include "NozzleTempInsert.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
NozzleTempInsert::NozzleTempInsert(unsigned int path_idx, int extruder, double temperature, bool wait, double time_after_path_start)
|
||||
: path_idx(path_idx)
|
||||
, time_after_path_start(time_after_path_start)
|
||||
, extruder(extruder)
|
||||
, temperature(temperature)
|
||||
, wait(wait)
|
||||
{
|
||||
assert(temperature != 0 && temperature != -1 && "Temperature command must be set!");
|
||||
}
|
||||
|
||||
void NozzleTempInsert::write(GCodeExport& gcode)
|
||||
{
|
||||
gcode.writeTemperatureCommand(extruder, temperature, wait);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef PATH_PLANNING_NOZZLE_TEMP_INSERT_H
|
||||
#define PATH_PLANNING_NOZZLE_TEMP_INSERT_H
|
||||
|
||||
#include "../gcodeExport.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A gcode command to insert before a specific path.
|
||||
*
|
||||
* Currently only used for preheat commands
|
||||
*/
|
||||
struct NozzleTempInsert
|
||||
{
|
||||
const unsigned int path_idx; //!< The path before which to insert this command
|
||||
double time_after_path_start; //!< The time after the start of the path, before which to insert the command // TODO: use this to insert command in between moves in a path!
|
||||
int extruder; //!< The extruder for which to set the temp
|
||||
double temperature; //!< The temperature of the temperature command to insert
|
||||
bool wait; //!< Whether to wait for the temperature to be reached
|
||||
NozzleTempInsert(unsigned int path_idx, int extruder, double temperature, bool wait, double time_after_path_start = 0.0);
|
||||
|
||||
/*!
|
||||
* Write the temperature command at the current position in the gcode.
|
||||
* \param gcode The actual gcode writer
|
||||
*/
|
||||
void write(GCodeExport& gcode);
|
||||
};
|
||||
}//namespace cura
|
||||
|
||||
#endif//PATH_PLANNING_NOZZLE_TEMP_INSERT_H
|
||||
@@ -0,0 +1,84 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "TimeMaterialEstimates.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
TimeMaterialEstimates::TimeMaterialEstimates(double extrude_time, double unretracted_travel_time, double retracted_travel_time, double material)
|
||||
: extrude_time(extrude_time)
|
||||
, unretracted_travel_time(unretracted_travel_time)
|
||||
, retracted_travel_time(retracted_travel_time)
|
||||
, material(material)
|
||||
{
|
||||
}
|
||||
|
||||
TimeMaterialEstimates::TimeMaterialEstimates()
|
||||
: extrude_time(0.0)
|
||||
, unretracted_travel_time(0.0)
|
||||
, retracted_travel_time(0.0)
|
||||
, material(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
TimeMaterialEstimates TimeMaterialEstimates::operator-(const TimeMaterialEstimates& other)
|
||||
{
|
||||
return TimeMaterialEstimates(extrude_time - other.extrude_time,unretracted_travel_time - other.unretracted_travel_time,retracted_travel_time - other.retracted_travel_time,material - other.material);
|
||||
}
|
||||
|
||||
TimeMaterialEstimates& TimeMaterialEstimates::operator-=(const TimeMaterialEstimates& other)
|
||||
{
|
||||
extrude_time -= other.extrude_time;
|
||||
unretracted_travel_time -= other.unretracted_travel_time;
|
||||
retracted_travel_time -= other.retracted_travel_time;
|
||||
material -= other.material;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TimeMaterialEstimates TimeMaterialEstimates::operator+(const TimeMaterialEstimates& other)
|
||||
{
|
||||
return TimeMaterialEstimates(extrude_time+other.extrude_time, unretracted_travel_time+other.unretracted_travel_time, retracted_travel_time+other.retracted_travel_time, material+other.material);
|
||||
}
|
||||
|
||||
TimeMaterialEstimates& TimeMaterialEstimates::operator+=(const TimeMaterialEstimates& other)
|
||||
{
|
||||
extrude_time += other.extrude_time;
|
||||
unretracted_travel_time += other.unretracted_travel_time;
|
||||
retracted_travel_time += other.retracted_travel_time;
|
||||
material += other.material;
|
||||
return *this;
|
||||
}
|
||||
|
||||
double TimeMaterialEstimates::getExtrudeTime() const
|
||||
{
|
||||
return extrude_time;
|
||||
}
|
||||
|
||||
double TimeMaterialEstimates::getMaterial() const
|
||||
{
|
||||
return material;
|
||||
}
|
||||
|
||||
double TimeMaterialEstimates::getTotalTime() const
|
||||
{
|
||||
return extrude_time + unretracted_travel_time + retracted_travel_time;
|
||||
}
|
||||
|
||||
double TimeMaterialEstimates::getTotalUnretractedTime() const
|
||||
{
|
||||
return extrude_time + unretracted_travel_time;
|
||||
}
|
||||
|
||||
double TimeMaterialEstimates::getTravelTime() const
|
||||
{
|
||||
return retracted_travel_time + unretracted_travel_time;
|
||||
}
|
||||
|
||||
void TimeMaterialEstimates::reset()
|
||||
{
|
||||
extrude_time = 0.0;
|
||||
unretracted_travel_time = 0.0;
|
||||
retracted_travel_time = 0.0;
|
||||
material = 0.0;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,124 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef PATH_PLANNING_TIME_MATERIAL_ESTIMATES_H
|
||||
#define PATH_PLANNING_TIME_MATERIAL_ESTIMATES_H
|
||||
|
||||
#include "../gcodeExport.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class ExtruderPlan; // forward declaration so that TimeMaterialEstimates can be a friend
|
||||
|
||||
/*!
|
||||
* Time and material estimates for a portion of paths, e.g. layer, extruder plan, path.
|
||||
*/
|
||||
class TimeMaterialEstimates
|
||||
{
|
||||
friend class ExtruderPlan; // cause there the naive estimates are calculated
|
||||
private:
|
||||
double extrude_time; //!< Time in seconds occupied by extrusion
|
||||
double unretracted_travel_time; //!< Time in seconds occupied by non-retracted travel (non-extrusion)
|
||||
double retracted_travel_time; //!< Time in seconds occupied by retracted travel (non-extrusion)
|
||||
double material; //!< Material used (in mm^3)
|
||||
public:
|
||||
/*!
|
||||
* Basic contructor
|
||||
*
|
||||
* \param extrude_time Time in seconds occupied by extrusion
|
||||
* \param unretracted_travel_time Time in seconds occupied by non-retracted travel (non-extrusion)
|
||||
* \param retracted_travel_time Time in seconds occupied by retracted travel (non-extrusion)
|
||||
* \param material Material used (in mm^3)
|
||||
*/
|
||||
TimeMaterialEstimates(double extrude_time, double unretracted_travel_time, double retracted_travel_time, double material);
|
||||
|
||||
/*!
|
||||
* Basic constructor initializing all estimates to zero.
|
||||
*/
|
||||
TimeMaterialEstimates();
|
||||
|
||||
/*!
|
||||
* Set all estimates to zero.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/*!
|
||||
* Pointwise addition of estimate stats
|
||||
*
|
||||
* \param other The estimates to add to these estimates.
|
||||
* \return The resulting estimates
|
||||
*/
|
||||
TimeMaterialEstimates operator+(const TimeMaterialEstimates& other);
|
||||
|
||||
/*!
|
||||
* In place pointwise addition of estimate stats
|
||||
*
|
||||
* \param other The estimates to add to these estimates.
|
||||
* \return These estimates
|
||||
*/
|
||||
TimeMaterialEstimates& operator+=(const TimeMaterialEstimates& other);
|
||||
|
||||
/*!
|
||||
* \brief Subtracts the specified estimates from these estimates and returns
|
||||
* the result.
|
||||
*
|
||||
* Each of the estimates in this class are individually subtracted.
|
||||
*
|
||||
* \param other The estimates to subtract from these estimates.
|
||||
* \return These estimates with the specified estimates subtracted.
|
||||
*/
|
||||
TimeMaterialEstimates operator-(const TimeMaterialEstimates& other);
|
||||
|
||||
/*!
|
||||
* \brief Subtracts the specified elements from these estimates.
|
||||
*
|
||||
* This causes the estimates in this instance to change. Each of the
|
||||
* estimates in this class are individually subtracted.
|
||||
*
|
||||
* \param other The estimates to subtract from these estimates.
|
||||
* \return A reference to this instance.
|
||||
*/
|
||||
TimeMaterialEstimates& operator-=(const TimeMaterialEstimates& other);
|
||||
|
||||
/*!
|
||||
* Get total time estimate. The different time estimate member values added together.
|
||||
*
|
||||
* \return the total of all different time estimate values
|
||||
*/
|
||||
double getTotalTime() const;
|
||||
|
||||
/*!
|
||||
* Get the total time during which the head is not retracted.
|
||||
*
|
||||
* This includes extrusion time and non-retracted travel time
|
||||
*
|
||||
* \return the total time during which the head is not retracted.
|
||||
*/
|
||||
double getTotalUnretractedTime() const;
|
||||
|
||||
/*!
|
||||
* Get the total travel time.
|
||||
*
|
||||
* This includes the retracted travel time as well as the unretracted travel time.
|
||||
*
|
||||
* \return the total travel time.
|
||||
*/
|
||||
double getTravelTime() const;
|
||||
|
||||
/*!
|
||||
* Get the extrusion time.
|
||||
*
|
||||
* \return extrusion time.
|
||||
*/
|
||||
double getExtrudeTime() const;
|
||||
|
||||
/*!
|
||||
* Get the amount of material used in mm^3.
|
||||
*
|
||||
* \return amount of material
|
||||
*/
|
||||
double getMaterial() const;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//PATH_PLANNING_TIME_MATERIAL_ESTIMATES_H
|
||||
+5
-1
@@ -43,7 +43,7 @@ int Raft::getZdiffBetweenRaftAndLayer1(const SliceDataStorage& storage)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
const int64_t airgap = std::max(0, train.getSettingInMicrons("raft_airgap"));
|
||||
const int64_t airgap = std::max((coord_t)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");
|
||||
@@ -75,6 +75,10 @@ int Raft::getFillerLayerHeight(const SliceDataStorage& storage)
|
||||
int Raft::getTotalExtraLayers(const SliceDataStorage& storage)
|
||||
{
|
||||
const ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("adhesion_extruder_nr"));
|
||||
if (train.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::RAFT)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 2 + train.getSettingAsCount("raft_surface_layers") + getFillerLayerCount(storage);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,11 @@ bool SettingRegistry::getDefinitionFile(const std::string machine_id, std::strin
|
||||
|
||||
int SettingRegistry::loadExtruderJSONsettings(unsigned int extruder_nr, SettingsBase* settings_base)
|
||||
{
|
||||
if (extruder_train_ids.empty()) //... Tough luck, buddy.
|
||||
{
|
||||
logError("Couldn't find any extruder trains!\n");
|
||||
return -1;
|
||||
}
|
||||
if (extruder_nr >= extruder_train_ids.size())
|
||||
{
|
||||
logWarning("Couldn't load extruder.def.json file for extruder %i. Index out of bounds.\n Loading first extruder definition instead.\n", extruder_nr);
|
||||
@@ -220,8 +225,7 @@ int SettingRegistry::loadJSONsettingsFromDoc(rapidjson::Document& json_document,
|
||||
|
||||
if (json_document.HasMember("settings"))
|
||||
{
|
||||
std::list<std::string> path;
|
||||
handleChildren(json_document["settings"], path, settings_base, warn_duplicates);
|
||||
handleChildren(json_document["settings"], settings_base, warn_duplicates);
|
||||
}
|
||||
|
||||
if (json_document.HasMember("overrides"))
|
||||
@@ -243,7 +247,7 @@ int SettingRegistry::loadJSONsettingsFromDoc(rapidjson::Document& json_document,
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SettingRegistry::handleChildren(const rapidjson::Value& settings_list, std::list<std::string>& path, SettingsBase* settings_base, bool warn_duplicates)
|
||||
void SettingRegistry::handleChildren(const rapidjson::Value& settings_list, SettingsBase* settings_base, bool warn_duplicates)
|
||||
{
|
||||
if (!settings_list.IsObject())
|
||||
{
|
||||
@@ -252,12 +256,10 @@ void SettingRegistry::handleChildren(const rapidjson::Value& settings_list, std:
|
||||
}
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = settings_list.MemberBegin(); setting_iterator != settings_list.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
handleSetting(setting_iterator, path, settings_base, warn_duplicates);
|
||||
handleSetting(setting_iterator, settings_base, warn_duplicates);
|
||||
if (setting_iterator->value.HasMember("children"))
|
||||
{
|
||||
std::list<std::string> path_here = path;
|
||||
path_here.push_back(setting_iterator->name.GetString());
|
||||
handleChildren(setting_iterator->value["children"], path_here, settings_base, warn_duplicates);
|
||||
handleChildren(setting_iterator->value["children"], settings_base, warn_duplicates);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,7 +277,7 @@ bool SettingRegistry::settingIsUsedByEngine(const rapidjson::Value& setting)
|
||||
}
|
||||
|
||||
|
||||
void SettingRegistry::handleSetting(const rapidjson::Value::ConstMemberIterator& json_setting_it, std::list<std::string>& path, SettingsBase* settings_base, bool warn_duplicates)
|
||||
void SettingRegistry::handleSetting(const rapidjson::Value::ConstMemberIterator& json_setting_it, SettingsBase* settings_base, bool warn_duplicates)
|
||||
{
|
||||
const rapidjson::Value& json_setting = json_setting_it->value;
|
||||
if (!json_setting.IsObject())
|
||||
|
||||
@@ -174,17 +174,16 @@ private:
|
||||
* \param settings_base The settings base where to store the default values.
|
||||
* \param warn_duplicates whether to warn for duplicate setting definitions
|
||||
*/
|
||||
void handleChildren(const rapidjson::Value& settings_list, std::list<std::string>& path, SettingsBase* settings_base, bool warn_duplicates);
|
||||
void handleChildren(const rapidjson::Value& settings_list, SettingsBase* settings_base, bool warn_duplicates);
|
||||
|
||||
/*!
|
||||
* Handle a json object for a setting.
|
||||
*
|
||||
* \param json_setting_it Iterator for the setting which contains the key (setting name) and attributes info
|
||||
* \param path The path of (internal) setting names traversed to get to this object
|
||||
* \param settings_base The settings base where to store the default values.
|
||||
* \param warn_duplicates whether to warn for duplicate setting definitions
|
||||
*/
|
||||
void handleSetting(const rapidjson::Value::ConstMemberIterator& json_setting_it, std::list<std::string>& path, SettingsBase* settings_base, bool warn_duplicates);
|
||||
void handleSetting(const rapidjson::Value::ConstMemberIterator& json_setting_it, SettingsBase* settings_base, bool warn_duplicates);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -42,7 +42,7 @@ std::string toString(EGCodeFlavor flavor)
|
||||
}
|
||||
|
||||
SettingsBaseVirtual::SettingsBaseVirtual()
|
||||
: parent(NULL)
|
||||
: parent(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ SettingsBaseVirtual::SettingsBaseVirtual(SettingsBaseVirtual* parent)
|
||||
}
|
||||
|
||||
SettingsBase::SettingsBase()
|
||||
: SettingsBaseVirtual(NULL)
|
||||
: SettingsBaseVirtual(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ double SettingsBaseVirtual::getSettingInMillimeters(std::string key) const
|
||||
return atof(value.c_str());
|
||||
}
|
||||
|
||||
int SettingsBaseVirtual::getSettingInMicrons(std::string key) const
|
||||
coord_t SettingsBaseVirtual::getSettingInMicrons(std::string key) const
|
||||
{
|
||||
return getSettingInMillimeters(key) * 1000.0;
|
||||
}
|
||||
@@ -210,6 +210,12 @@ double SettingsBaseVirtual::getSettingInPercentage(std::string key) const
|
||||
return std::max(0.0, atof(value.c_str()));
|
||||
}
|
||||
|
||||
double SettingsBaseVirtual::getSettingAsRatio(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return atof(value.c_str()) / 100.0;
|
||||
}
|
||||
|
||||
double SettingsBaseVirtual::getSettingInSeconds(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
@@ -345,12 +351,16 @@ EFillMethod SettingsBaseVirtual::getSettingAsFillMethod(std::string key) const
|
||||
return EFillMethod::GRID;
|
||||
if (value == "cubic")
|
||||
return EFillMethod::CUBIC;
|
||||
if (value == "cubicsubdiv")
|
||||
return EFillMethod::CUBICSUBDIV;
|
||||
if (value == "tetrahedral")
|
||||
return EFillMethod::TETRAHEDRAL;
|
||||
if (value == "triangles")
|
||||
return EFillMethod::TRIANGLES;
|
||||
if (value == "concentric")
|
||||
return EFillMethod::CONCENTRIC;
|
||||
if (value == "concentric_3d")
|
||||
return EFillMethod::CONCENTRIC_3D;
|
||||
if (value == "zigzag")
|
||||
return EFillMethod::ZIG_ZAG;
|
||||
return EFillMethod::NONE;
|
||||
@@ -363,6 +373,8 @@ EPlatformAdhesion SettingsBaseVirtual::getSettingAsPlatformAdhesion(std::string
|
||||
return EPlatformAdhesion::BRIM;
|
||||
if (value == "raft")
|
||||
return EPlatformAdhesion::RAFT;
|
||||
if (value == "none")
|
||||
return EPlatformAdhesion::NONE;
|
||||
return EPlatformAdhesion::SKIRT;
|
||||
}
|
||||
|
||||
@@ -400,7 +412,21 @@ ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key) const
|
||||
return ESurfaceMode::NORMAL;
|
||||
}
|
||||
|
||||
CombingMode SettingsBaseVirtual::getSettingAsCombingMode(std::string key)
|
||||
FillPerimeterGapMode SettingsBaseVirtual::getSettingAsFillPerimeterGapMode(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "nowhere")
|
||||
{
|
||||
return FillPerimeterGapMode::NOWHERE;
|
||||
}
|
||||
if (value == "everywhere")
|
||||
{
|
||||
return FillPerimeterGapMode::EVERYWHERE;
|
||||
}
|
||||
return FillPerimeterGapMode::NOWHERE;
|
||||
}
|
||||
|
||||
CombingMode SettingsBaseVirtual::getSettingAsCombingMode(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "off")
|
||||
@@ -418,7 +444,7 @@ CombingMode SettingsBaseVirtual::getSettingAsCombingMode(std::string key)
|
||||
return CombingMode::ALL;
|
||||
}
|
||||
|
||||
SupportDistPriority SettingsBaseVirtual::getSettingAsSupportDistPriority(std::string key)
|
||||
SupportDistPriority SettingsBaseVirtual::getSettingAsSupportDistPriority(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "xy_overrides_z")
|
||||
@@ -432,6 +458,32 @@ SupportDistPriority SettingsBaseVirtual::getSettingAsSupportDistPriority(std::st
|
||||
return SupportDistPriority::XY_OVERRIDES_Z;
|
||||
}
|
||||
|
||||
ColourUsage SettingsBaseVirtual::getSettingAsColourUsage(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "red")
|
||||
{
|
||||
return ColourUsage::RED;
|
||||
}
|
||||
if (value == "green")
|
||||
{
|
||||
return ColourUsage::GREEN;
|
||||
}
|
||||
if (value == "blue")
|
||||
{
|
||||
return ColourUsage::BLUE;
|
||||
}
|
||||
if (value == "alpha")
|
||||
{
|
||||
return ColourUsage::ALPHA;
|
||||
}
|
||||
if (value == "grey")
|
||||
{
|
||||
return ColourUsage::GREY;
|
||||
}
|
||||
return ColourUsage::GREY;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
@@ -105,9 +105,11 @@ enum class EFillMethod
|
||||
LINES,
|
||||
GRID,
|
||||
CUBIC,
|
||||
CUBICSUBDIV,
|
||||
TETRAHEDRAL,
|
||||
TRIANGLES,
|
||||
CONCENTRIC,
|
||||
CONCENTRIC_3D,
|
||||
ZIG_ZAG,
|
||||
NONE
|
||||
};
|
||||
@@ -119,7 +121,8 @@ enum class EPlatformAdhesion
|
||||
{
|
||||
SKIRT,
|
||||
BRIM,
|
||||
RAFT
|
||||
RAFT,
|
||||
NONE
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -146,6 +149,12 @@ enum class ESurfaceMode
|
||||
BOTH
|
||||
};
|
||||
|
||||
enum class FillPerimeterGapMode
|
||||
{
|
||||
NOWHERE,
|
||||
EVERYWHERE
|
||||
};
|
||||
|
||||
enum class CombingMode
|
||||
{
|
||||
OFF,
|
||||
@@ -168,6 +177,18 @@ enum class SupportDistPriority
|
||||
Z_OVERRIDES_XY
|
||||
};
|
||||
|
||||
/*!
|
||||
* Which color(s) of a texture to use
|
||||
*/
|
||||
enum class ColourUsage
|
||||
{
|
||||
RED = 0,
|
||||
GREEN = 1,
|
||||
BLUE = 2,
|
||||
ALPHA = 3,
|
||||
GREY // use red, green and blue channels
|
||||
};
|
||||
|
||||
#define MAX_EXTRUDERS 16
|
||||
|
||||
//Maximum number of infill layers that can be combined into a single infill extrusion area.
|
||||
@@ -222,12 +243,13 @@ public:
|
||||
double getSettingInAngleDegrees(std::string key) const;
|
||||
double getSettingInAngleRadians(std::string key) const;
|
||||
double getSettingInMillimeters(std::string key) const;
|
||||
int getSettingInMicrons(std::string key) const;
|
||||
coord_t getSettingInMicrons(std::string key) const;
|
||||
bool getSettingBoolean(std::string key) const;
|
||||
double getSettingInDegreeCelsius(std::string key) const;
|
||||
double getSettingInMillimetersPerSecond(std::string key) const;
|
||||
double getSettingInCubicMillimeters(std::string key) const;
|
||||
double getSettingInPercentage(std::string key) const;
|
||||
double getSettingAsRatio(std::string key) const; //!< For settings which are provided in percentage
|
||||
double getSettingInSeconds(std::string key) const;
|
||||
|
||||
FlowTempGraph getSettingAsFlowTempGraph(std::string key) const;
|
||||
@@ -240,8 +262,10 @@ public:
|
||||
ESupportType getSettingAsSupportType(std::string key) const;
|
||||
EZSeamType getSettingAsZSeamType(std::string key) const;
|
||||
ESurfaceMode getSettingAsSurfaceMode(std::string key) const;
|
||||
CombingMode getSettingAsCombingMode(std::string key);
|
||||
SupportDistPriority getSettingAsSupportDistPriority(std::string key);
|
||||
FillPerimeterGapMode getSettingAsFillPerimeterGapMode(std::string key) const;
|
||||
CombingMode getSettingAsCombingMode(std::string key) const;
|
||||
SupportDistPriority getSettingAsSupportDistPriority(std::string key) const;
|
||||
ColourUsage getSettingAsColourUsage(std::string key) const;
|
||||
};
|
||||
|
||||
class SettingRegistry;
|
||||
|
||||
+11
-1
@@ -143,13 +143,23 @@ void generateInfill(int layerNr, SliceMeshStorage& mesh, const int innermost_wal
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layerNr];
|
||||
|
||||
int extra_offset = 0;
|
||||
EFillMethod fill_pattern = mesh.getSettingAsFillMethod("infill_pattern");
|
||||
if ((fill_pattern == EFillMethod::CONCENTRIC || fill_pattern == EFillMethod::CONCENTRIC_3D)
|
||||
&& mesh.getSettingBoolean("alternate_extra_perimeter")
|
||||
&& layerNr % 2 == 0
|
||||
&& mesh.getSettingInMicrons("infill_line_distance") > mesh.getSettingInMicrons("infill_line_width") * 2)
|
||||
{
|
||||
extra_offset = -innermost_wall_line_width;
|
||||
}
|
||||
|
||||
for(SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
if (int(part.insets.size()) < wall_line_count)
|
||||
{
|
||||
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_line_width / 2 - infill_skin_overlap);
|
||||
Polygons infill = part.insets.back().offset(extra_offset - innermost_wall_line_width / 2 - infill_skin_overlap);
|
||||
|
||||
for(SliceLayerPart& part2 : layer.parts)
|
||||
{
|
||||
|
||||
+171
-14
@@ -1,6 +1,11 @@
|
||||
//Copyright (c) 2016 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
#include "FffProcessor.h" //To create a mesh group with if none is provided.
|
||||
#include "infill/SubDivCube.h" // For the destructor
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -67,6 +72,48 @@ void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls) const
|
||||
}
|
||||
}
|
||||
|
||||
SliceMeshStorage::SliceMeshStorage(SettingsBaseVirtual* settings, unsigned int slice_layer_count)
|
||||
: SettingsMessenger(settings)
|
||||
, layer_nr_max_filled_layer(0)
|
||||
, inset0_config(PrintFeatureType::OuterWall)
|
||||
, insetX_config(PrintFeatureType::InnerWall)
|
||||
, skin_config(PrintFeatureType::Skin)
|
||||
, base_subdiv_cube(nullptr)
|
||||
, texture_proximity_processor(nullptr)
|
||||
{
|
||||
layers.resize(slice_layer_count);
|
||||
infill_config.reserve(MAX_INFILL_COMBINE);
|
||||
for(int n=0; n<MAX_INFILL_COMBINE; n++)
|
||||
infill_config.emplace_back(PrintFeatureType::Infill);
|
||||
}
|
||||
|
||||
|
||||
SliceMeshStorage::SliceMeshStorage(SliceMeshStorage&& old)
|
||||
: SettingsMessenger(SettingsBaseVirtual::parent)
|
||||
, layers(old.layers)
|
||||
, layer_nr_max_filled_layer(old.layer_nr_max_filled_layer)
|
||||
, inset0_config(old.inset0_config)
|
||||
, insetX_config(old.insetX_config)
|
||||
, skin_config(old.skin_config)
|
||||
, base_subdiv_cube(old.base_subdiv_cube)
|
||||
, texture_proximity_processor(old.texture_proximity_processor)
|
||||
{
|
||||
old.base_subdiv_cube = nullptr;
|
||||
old.texture_proximity_processor = nullptr;
|
||||
}
|
||||
|
||||
SliceMeshStorage::~SliceMeshStorage()
|
||||
{
|
||||
if (base_subdiv_cube)
|
||||
{
|
||||
delete base_subdiv_cube;
|
||||
}
|
||||
if (texture_proximity_processor)
|
||||
{
|
||||
delete texture_proximity_processor;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RetractionConfig> SliceDataStorage::initializeRetractionConfigs()
|
||||
{
|
||||
std::vector<RetractionConfig> ret;
|
||||
@@ -96,6 +143,7 @@ std::vector<GCodePathConfig> SliceDataStorage::initializeSkirtBrimConfigs()
|
||||
|
||||
SliceDataStorage::SliceDataStorage(MeshGroup* meshgroup) : SettingsMessenger(meshgroup),
|
||||
meshgroup(meshgroup != nullptr ? meshgroup : new MeshGroup(FffProcessor::getInstance())), //If no mesh group is provided, we roll our own.
|
||||
print_layer_count(0),
|
||||
retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
extruder_switch_retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
travel_config_per_extruder(initializeTravelConfigs()),
|
||||
@@ -105,10 +153,16 @@ SliceDataStorage::SliceDataStorage(MeshGroup* meshgroup) : SettingsMessenger(mes
|
||||
raft_surface_config(PrintFeatureType::SupportInterface),
|
||||
support_config(PrintFeatureType::Support),
|
||||
support_skin_config(PrintFeatureType::SupportInterface),
|
||||
max_print_height_second_to_last_extruder(-1)
|
||||
max_print_height_second_to_last_extruder(-1),
|
||||
primeTower(*this)
|
||||
{
|
||||
}
|
||||
|
||||
SliceDataStorage::~SliceDataStorage()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only) const
|
||||
{
|
||||
if (layer_nr < 0 && layer_nr < -Raft::getFillerLayerCount(*this))
|
||||
@@ -142,7 +196,7 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
|
||||
{
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
if (mesh.getSettingBoolean("infill_mesh"))
|
||||
if (mesh.getSettingBoolean("infill_mesh") || mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -161,7 +215,10 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
|
||||
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);
|
||||
if (primeTower.enabled)
|
||||
{
|
||||
total.add(primeTower.ground_poly);
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
@@ -202,27 +259,33 @@ Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool inc
|
||||
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);
|
||||
if (primeTower.enabled)
|
||||
{
|
||||
total.add(primeTower.ground_poly);
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
std::vector<bool> SliceDataStorage::getExtrudersUsed() const
|
||||
{
|
||||
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt_brim[extr_nr].size() > 0)
|
||||
if (getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
if (skirt_brim[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,9 +299,103 @@ std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
ret[getSettingAsIndex("support_interface_extruder_nr")] = true;
|
||||
|
||||
// all meshes are presupposed to actually have content
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh.getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<bool> SliceDataStorage::getExtrudersUsed(int layer_nr) const
|
||||
{
|
||||
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
|
||||
bool include_adhesion = true;
|
||||
bool include_helper_parts = true;
|
||||
bool include_models = true;
|
||||
if (layer_nr < 0)
|
||||
{
|
||||
include_models = false;
|
||||
if (layer_nr < -Raft::getFillerLayerCount(*this))
|
||||
{
|
||||
include_helper_parts = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
layer_nr = 0; // because the helper parts are copied from the initial layer in the filler layer
|
||||
include_adhesion = false;
|
||||
}
|
||||
}
|
||||
else if (layer_nr > 0 || getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT)
|
||||
{ // only include adhesion only for layers where platform adhesion actually occurs
|
||||
// i.e. layers < 0 are for raft, layer 0 is for brim/skirt
|
||||
include_adhesion = false;
|
||||
}
|
||||
if (include_adhesion)
|
||||
{
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt_brim[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ooze shield, draft shield ..?
|
||||
|
||||
if (include_helper_parts)
|
||||
{
|
||||
// support
|
||||
if (layer_nr < int(support.supportLayers.size()))
|
||||
{
|
||||
const SupportLayer& support_layer = support.supportLayers[layer_nr];
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
if (support_layer.supportAreas.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (support_layer.supportAreas.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_infill_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
if (support_layer.skin.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_interface_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (include_models)
|
||||
{
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
if (layer_nr >= int(mesh.layers.size()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
if (layer.parts.size() > 0)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
+36
-17
@@ -12,6 +12,7 @@
|
||||
#include "MeshGroup.h"
|
||||
#include "PrimeTower.h"
|
||||
#include "GCodePathConfig.h"
|
||||
#include "textureProcessing/TextureProximityProcessor.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -125,6 +126,8 @@ class SupportLayer
|
||||
public:
|
||||
Polygons supportAreas; //!< normal support areas
|
||||
Polygons skin; //!< the support areas which are to be printed as denser roofs and/or bottoms. Note that the roof/bottom areas and support areas should be mutually exclusive.
|
||||
Polygons support_mesh; //!< Areas from support meshes
|
||||
Polygons anti_overhang; //!< Areas where no overhang should be detected.
|
||||
};
|
||||
|
||||
class SupportStorage
|
||||
@@ -141,7 +144,15 @@ public:
|
||||
};
|
||||
/******************/
|
||||
|
||||
class SliceMeshStorage : public SettingsMessenger // passes on settings from a Mesh object
|
||||
class SubDivCube; // forward declaration to prevent dependency loop
|
||||
|
||||
/*!
|
||||
*
|
||||
* passes on settings from a Mesh object
|
||||
*
|
||||
* Cannot be copied due to \ref SliceMeshStorage::texture_proximity_processor being governed by this object alone
|
||||
*/
|
||||
class SliceMeshStorage : public SettingsMessenger, public NoCopy
|
||||
{
|
||||
public:
|
||||
std::vector<SliceLayer> layers;
|
||||
@@ -153,18 +164,18 @@ public:
|
||||
GCodePathConfig skin_config;
|
||||
std::vector<GCodePathConfig> infill_config;
|
||||
|
||||
SliceMeshStorage(SettingsBaseVirtual* settings, unsigned int slice_layer_count)
|
||||
: SettingsMessenger(settings)
|
||||
, layer_nr_max_filled_layer(0)
|
||||
, inset0_config(PrintFeatureType::OuterWall)
|
||||
, insetX_config(PrintFeatureType::InnerWall)
|
||||
, skin_config(PrintFeatureType::Skin)
|
||||
{
|
||||
layers.reserve(slice_layer_count);
|
||||
infill_config.reserve(MAX_INFILL_COMBINE);
|
||||
for(int n=0; n<MAX_INFILL_COMBINE; n++)
|
||||
infill_config.emplace_back(PrintFeatureType::Infill);
|
||||
}
|
||||
SubDivCube* base_subdiv_cube;
|
||||
|
||||
TextureProximityProcessor* texture_proximity_processor; //!< TextureProximityProcessor per layer per mesh (if that mesh needs a proximity processor)
|
||||
|
||||
SliceMeshStorage(SettingsBaseVirtual* settings, unsigned int slice_layer_count);
|
||||
|
||||
/*!
|
||||
* Move constructor
|
||||
*/
|
||||
SliceMeshStorage(SliceMeshStorage&& old);
|
||||
|
||||
virtual ~SliceMeshStorage();
|
||||
};
|
||||
|
||||
class SliceDataStorage : public SettingsMessenger, NoCopy
|
||||
@@ -172,6 +183,8 @@ class SliceDataStorage : public SettingsMessenger, NoCopy
|
||||
public:
|
||||
MeshGroup* meshgroup; // needed to pass on the per extruder settings.. (TODO: put this somewhere else? Put the per object settings here directly, or a pointer only to the per object settings.)
|
||||
|
||||
unsigned int print_layer_count; //!< The total number of layers (except the raft and filler layers)
|
||||
|
||||
Point3 model_size, model_min, model_max;
|
||||
std::vector<SliceMeshStorage> meshes;
|
||||
|
||||
@@ -231,9 +244,7 @@ public:
|
||||
*/
|
||||
SliceDataStorage(MeshGroup* meshgroup);
|
||||
|
||||
~SliceDataStorage()
|
||||
{
|
||||
}
|
||||
~SliceDataStorage();
|
||||
|
||||
/*!
|
||||
* Get all outlines within a given layer.
|
||||
@@ -259,7 +270,15 @@ public:
|
||||
*
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed();
|
||||
std::vector<bool> getExtrudersUsed() const;
|
||||
|
||||
/*!
|
||||
* Get the extruders used on a particular layer.
|
||||
*
|
||||
* \param layer_nr the layer for which to check
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed(int layer_nr) const;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_CLOSE_POLYGON_RESULT_H
|
||||
#define SLICER_CLOSE_POLYGON_RESULT_H
|
||||
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class ClosePolygonResult
|
||||
{ //The result of trying to find a point on a closed polygon line. This gives back the point index, the polygon index, and the point of the connection.
|
||||
//The line on which the point lays is between pointIdx-1 and pointIdx
|
||||
public:
|
||||
Point intersectionPoint;
|
||||
int polygonIdx = -1;
|
||||
unsigned int pointIdx = -1;
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // SLICER_CLOSE_POLYGON_RESULT_H
|
||||
@@ -0,0 +1,22 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_GAP_CLOSER_RESULT_H
|
||||
#define SLICER_GAP_CLOSER_RESULT_H
|
||||
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class GapCloserResult
|
||||
{
|
||||
public:
|
||||
int64_t len = -1;
|
||||
int polygonIdx = -1;
|
||||
unsigned int pointIdxA = -1;
|
||||
unsigned int pointIdxB = -1;
|
||||
bool AtoB = false;
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // SLICER_GAP_CLOSER_RESULT_H
|
||||
@@ -1,10 +1,10 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "layerPart.h"
|
||||
#include "settings/settings.h"
|
||||
#include "progress/Progress.h"
|
||||
#include "LayerPart.h"
|
||||
#include "../settings/settings.h"
|
||||
#include "../progress/Progress.h"
|
||||
|
||||
#include "utils/SVG.h" // debug output
|
||||
#include "../utils/SVG.h" // debug output
|
||||
|
||||
/*
|
||||
The layer-part creation step is the first step in creating actual useful data for 3D printing.
|
||||
@@ -46,7 +46,6 @@ void createLayerParts(SliceMeshStorage& mesh, Slicer* slicer, bool union_layers,
|
||||
{
|
||||
for(unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
mesh.layers.push_back(SliceLayer());
|
||||
mesh.layers[layer_nr].sliceZ = slicer->layers[layer_nr].z;
|
||||
mesh.layers[layer_nr].printZ = slicer->layers[layer_nr].z;
|
||||
createLayerWithParts(mesh.layers[layer_nr], &slicer->layers[layer_nr], union_layers, union_all_remove_holes);
|
||||
@@ -1,10 +1,10 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef LAYERPART_H
|
||||
#define LAYERPART_H
|
||||
#ifndef SLICER_LAYERPART_H
|
||||
#define SLICER_LAYERPART_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
#include "slicer.h"
|
||||
#include "commandSocket.h"
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "Slicer.h"
|
||||
#include "../commandSocket.h"
|
||||
|
||||
/*
|
||||
The layer-part creation step is the first step in creating actual useful data for 3D printing.
|
||||
@@ -28,4 +28,4 @@ void layerparts2HTML(SliceDataStorage& mesh, const char* filename, bool all_laye
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//LAYERPART_H
|
||||
#endif//SLICER_LAYERPART_H
|
||||
@@ -1,22 +1,28 @@
|
||||
#include "multiVolumes.h"
|
||||
#include "MultiVolumes.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes, bool alternate_carve_order)
|
||||
{
|
||||
//Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas.
|
||||
for (unsigned int volume_1_idx = 0; volume_1_idx < volumes.size(); volume_1_idx++)
|
||||
for (unsigned int volume_1_idx = 1; volume_1_idx < volumes.size(); volume_1_idx++)
|
||||
{
|
||||
Slicer& volume_1 = *volumes[volume_1_idx];
|
||||
if (volume_1.mesh->getSettingBoolean("infill_mesh"))
|
||||
if (volume_1.mesh->getSettingBoolean("infill_mesh")
|
||||
|| volume_1.mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| volume_1.mesh->getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (unsigned int volume_2_idx = 0; volume_2_idx < volume_1_idx; volume_2_idx++)
|
||||
{
|
||||
Slicer& volume_2 = *volumes[volume_2_idx];
|
||||
if (volume_2.mesh->getSettingBoolean("infill_mesh"))
|
||||
if (volume_2.mesh->getSettingBoolean("infill_mesh")
|
||||
|| volume_2.mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| volume_2.mesh->getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -28,7 +34,14 @@ void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
SlicerLayer& layer1 = volume_1.layers[layerNr];
|
||||
SlicerLayer& layer2 = volume_2.layers[layerNr];
|
||||
layer1.polygons = layer1.polygons.difference(layer2.polygons);
|
||||
if (alternate_carve_order && layerNr % 2 == 0)
|
||||
{
|
||||
layer2.polygons = layer2.polygons.difference(layer1.polygons);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer1.polygons = layer1.polygons.difference(layer2.polygons);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +61,8 @@ void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
int overlap = volume->mesh->getSettingInMicrons("multiple_mesh_overlap");
|
||||
if (volume->mesh->getSettingBoolean("infill_mesh")
|
||||
|| volume->mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| volume->mesh->getSettingBoolean("support_mesh")
|
||||
|| overlap == 0)
|
||||
{
|
||||
continue;
|
||||
@@ -60,6 +75,8 @@ void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes)
|
||||
for (Slicer* other_volume : volumes)
|
||||
{
|
||||
if (other_volume->mesh->getSettingBoolean("infill_mesh")
|
||||
|| other_volume->mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| other_volume->mesh->getSettingBoolean("support_mesh")
|
||||
|| !other_volume->mesh->getAABB().hit(aabb)
|
||||
|| other_volume == volume
|
||||
)
|
||||
@@ -1,13 +1,17 @@
|
||||
#ifndef MULTIVOLUMES_H
|
||||
#define MULTIVOLUMES_H
|
||||
#ifndef SLICER_MULTIVOLUMES_H
|
||||
#define SLICER_MULTIVOLUMES_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
#include "slicer.h"
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "Slicer.h"
|
||||
|
||||
/* This file contains code to help fixing up and changing layers that are build from multiple volumes. */
|
||||
namespace cura {
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &meshes);
|
||||
/*!
|
||||
*
|
||||
* \param alternate_carve_order Whether to switch which model carves out of which with every layer
|
||||
*/
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &meshes, bool alternate_carve_order);
|
||||
|
||||
/*!
|
||||
* Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
@@ -17,4 +21,4 @@ void generateMultipleVolumesOverlap(std::vector<Slicer*> &meshes);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//MULTIVOLUMES_H
|
||||
#endif//SLICER_MULTIVOLUMES_H
|
||||
@@ -0,0 +1,180 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../utils/gettime.h"
|
||||
#include "../utils/logoutput.h"
|
||||
#include "../textureProcessing/MatCoord.h"
|
||||
#include "../textureProcessing/FaceNormalStorage.h"
|
||||
|
||||
#include "Slicer.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
void Slicer::project2D(unsigned int face_idx, const Point3 p[3], unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, int32_t layer_nr, SlicerSegment& seg)
|
||||
{
|
||||
const Point3& p0 = p[idx_shared];
|
||||
const Point3& p1 = p[idx_first];
|
||||
const Point3& p2 = p[idx_second];
|
||||
|
||||
seg.start.X = interpolate(z, p0.z, p1.z, p0.x, p1.x);
|
||||
seg.start.Y = interpolate(z, p0.z, p1.z, p0.y, p1.y);
|
||||
seg.end .X = interpolate(z, p0.z, p2.z, p0.x, p2.x);
|
||||
seg.end .Y = interpolate(z, p0.z, p2.z, p0.y, p2.y);
|
||||
if (textured_mesh)
|
||||
{
|
||||
MatSegment mat_segment;
|
||||
bool got_texture_coords = textured_mesh->sliceFaceTexture(face_idx, idx_shared, idx_first, idx_second, z, seg.start, seg.end, mat_segment);
|
||||
SlicerLayer& layer = layers[layer_nr];
|
||||
if (got_texture_coords)
|
||||
{
|
||||
if (layer.texture_bump_map)
|
||||
{
|
||||
layer.texture_bump_map->registerTexturedFaceSlice(seg, mat_segment);
|
||||
}
|
||||
if (texture_proximity_processor)
|
||||
{
|
||||
texture_proximity_processor->registerTexturedFaceSlice(seg, mat_segment, layer_nr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Slicer::Slicer(Mesh* mesh, int initial, int thickness, unsigned int slice_layer_count, bool keep_none_closed, bool extensive_stitching, TextureProximityProcessor* texture_proximity_processor)
|
||||
: mesh(mesh)
|
||||
, textured_mesh(dynamic_cast<TexturedMesh*>(mesh))
|
||||
, texture_proximity_processor(texture_proximity_processor)
|
||||
{
|
||||
assert((int) slice_layer_count > 0);
|
||||
|
||||
TimeKeeper slice_timer;
|
||||
|
||||
std::optional<TextureBumpMapProcessor::Settings> bump_map_settings;
|
||||
FaceNormalStorage* face_normal_storage = nullptr;
|
||||
if (mesh->getSettingBoolean("bump_map_enabled"))
|
||||
{
|
||||
bump_map_settings.emplace(mesh);
|
||||
if (mesh->getSettingAsRatio("bump_map_face_angle_correction") != 0.0)
|
||||
{
|
||||
face_normal_storage = new FaceNormalStorage(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
layers.reserve(slice_layer_count);
|
||||
for (uint32_t layer_nr = 0; layer_nr < slice_layer_count; layer_nr++)
|
||||
{ // initialize all layers
|
||||
layers.emplace_back(layer_nr, mesh, bump_map_settings, face_normal_storage);
|
||||
assert(&layers.back() == &layers[layer_nr] && "We should just have emplaced the last layer!");
|
||||
layers[layer_nr].z = initial + thickness * layer_nr;
|
||||
}
|
||||
|
||||
bool bump_map_alternate = mesh->getSettingBoolean("bump_map_alternate");
|
||||
int extruder_nr = mesh->getSettingAsIndex("extruder_nr");
|
||||
|
||||
for(unsigned int face_idx = 0; face_idx < mesh->faces.size(); face_idx++)
|
||||
{
|
||||
const MeshFace& face = mesh->faces[face_idx];
|
||||
const MeshVertex& v0 = mesh->vertices[face.vertex_index[0]];
|
||||
const MeshVertex& v1 = mesh->vertices[face.vertex_index[1]];
|
||||
const MeshVertex& v2 = mesh->vertices[face.vertex_index[2]];
|
||||
Point3 p[3] =
|
||||
{ mesh->vertices[face.vertex_index[0]].p
|
||||
, mesh->vertices[face.vertex_index[1]].p
|
||||
, mesh->vertices[face.vertex_index[2]].p };
|
||||
Point3& p0 = p[0];
|
||||
Point3& p1 = p[1];
|
||||
Point3& p2 = p[2];
|
||||
int32_t minZ = p0.z;
|
||||
int32_t maxZ = p0.z;
|
||||
if (p1.z < minZ) minZ = p1.z;
|
||||
if (p2.z < minZ) minZ = p2.z;
|
||||
if (p1.z > maxZ) maxZ = p1.z;
|
||||
if (p2.z > maxZ) maxZ = p2.z;
|
||||
int32_t layer_max = (maxZ - initial) / thickness;
|
||||
int32_t layer_min = (minZ - initial + thickness - 1) / thickness; // + thickness - 1 to get the first layer above or at minZ
|
||||
for (int32_t layer_nr = layer_min; layer_nr <= layer_max; layer_nr++)
|
||||
{
|
||||
if (bump_map_alternate && layer_nr % 2 == extruder_nr) // TODO only works for the first two extruders!
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int32_t z = layer_nr * thickness + initial;
|
||||
if (z < minZ) continue;
|
||||
if (layer_nr < 0) continue;
|
||||
|
||||
SlicerSegment s;
|
||||
s.endVertex = nullptr;
|
||||
s.faceIndex = face_idx;
|
||||
assert(face_idx >= 0);
|
||||
s.addedToPolygon = false;
|
||||
if (p0.z < z && p1.z >= z && p2.z >= z)
|
||||
{
|
||||
s.endOtherFaceIdx = face.connected_face_index[0];
|
||||
if (p1.z == z)
|
||||
{
|
||||
s.endVertex = &v1;
|
||||
}
|
||||
project2D(face_idx, p, 0, 2, 1, z, layer_nr, s);
|
||||
}
|
||||
else if (p0.z > z && p1.z < z && p2.z < z)
|
||||
{
|
||||
s.endOtherFaceIdx = face.connected_face_index[2];
|
||||
project2D(face_idx, p, 0, 1, 2, z, layer_nr, s);
|
||||
|
||||
}
|
||||
|
||||
else if (p1.z < z && p0.z >= z && p2.z >= z)
|
||||
{
|
||||
s.endOtherFaceIdx = face.connected_face_index[1];
|
||||
if (p2.z == z)
|
||||
{
|
||||
s.endVertex = &v2;
|
||||
}
|
||||
project2D(face_idx, p, 1, 0, 2, z, layer_nr, s);
|
||||
}
|
||||
else if (p1.z > z && p0.z < z && p2.z < z)
|
||||
{
|
||||
s.endOtherFaceIdx = face.connected_face_index[0];
|
||||
project2D(face_idx, p, 1, 2, 0, z, layer_nr, s);
|
||||
|
||||
}
|
||||
|
||||
else if (p2.z < z && p1.z >= z && p0.z >= z)
|
||||
{
|
||||
s.endOtherFaceIdx = face.connected_face_index[2];
|
||||
if (p0.z == z)
|
||||
{
|
||||
s.endVertex = &v0;
|
||||
}
|
||||
project2D(face_idx, p, 2, 1, 0, z, layer_nr, s);
|
||||
}
|
||||
else if (p2.z > z && p1.z < z && p0.z < z)
|
||||
{
|
||||
s.endOtherFaceIdx = face.connected_face_index[1];
|
||||
project2D(face_idx, p, 2, 0, 1, z, layer_nr, s);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Not all cases create a segment, because a point of a face could create just a dot, and two touching faces
|
||||
// on the slice would create two segments
|
||||
continue;
|
||||
}
|
||||
layers[layer_nr].face_idx_to_segment_idx.insert(std::make_pair(face_idx, layers[layer_nr].segments.size()));
|
||||
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());
|
||||
|
||||
if (face_normal_storage)
|
||||
{
|
||||
delete face_normal_storage;
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,75 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_SLICER_H
|
||||
#define SLICER_SLICER_H
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "../mesh.h"
|
||||
#include "../utils/polygon.h"
|
||||
|
||||
#include "SlicerSegment.h"
|
||||
#include "ClosePolygonResult.h"
|
||||
#include "SlicerLayer.h"
|
||||
|
||||
#include "../textureProcessing/MatSegment.h"
|
||||
#include "../textureProcessing/TextureProximityProcessor.h"
|
||||
|
||||
/*
|
||||
The Slicer creates layers of polygons from an optimized 3D model.
|
||||
The result of the Slicer is a list of polygons without any order or structure.
|
||||
*/
|
||||
namespace cura {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Slicer
|
||||
{
|
||||
public:
|
||||
std::vector<SlicerLayer> layers;
|
||||
|
||||
const Mesh* mesh = nullptr; //!< The sliced mesh
|
||||
|
||||
const TexturedMesh* textured_mesh; //!< Pointer to the textured mesh if \ref Slicer::mesh is a TexturedMesh
|
||||
|
||||
TextureProximityProcessor* texture_proximity_processor; //!< Containers for each layer for fast lookup of textures being defined in the proximity of the lookup point
|
||||
|
||||
/*!
|
||||
*
|
||||
* \param texture_proximity_processors (optional) A TextureProximityProcessor for all layers in the mesh
|
||||
*/
|
||||
Slicer(Mesh* mesh, int initial, int thickness, unsigned int slice_layer_count, bool keepNoneClosed, bool extensiveStitching, TextureProximityProcessor* texture_proximity_processors);
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* 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)
|
||||
*
|
||||
* \param p The face vertice locations in the order the vertices are given in the face
|
||||
*/
|
||||
int64_t interpolate(int64_t x, int64_t x0, int64_t x1, int64_t y0, int64_t y1) const
|
||||
{
|
||||
int64_t dx_01 = x1 - x0;
|
||||
int64_t num = (y1 - y0) * (x - x0);
|
||||
num += num > 0 ? dx_01/2 : -dx_01/2; // add in offset to round result
|
||||
int64_t y = y0 + num / dx_01;
|
||||
return y;
|
||||
}
|
||||
|
||||
/*!
|
||||
*
|
||||
* \warning this function requires result.faceIndex to be correctly set already
|
||||
*
|
||||
* \p result where to store the start and end of the sliced segment
|
||||
*/
|
||||
void project2D(unsigned int face_idx, const Point3 p[3], unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, int32_t layer_nr, SlicerSegment& result);
|
||||
|
||||
void dumpSegmentsToHTML(const char* filename);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//SLICER_SLICER_H
|
||||
@@ -1,21 +1,29 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include <stdio.h>
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include <algorithm> // remove_if
|
||||
#include "SlicerLayer.h"
|
||||
#include "../textureProcessing/TextureBumpMapProcessor.h"
|
||||
#include "../utils/SparsePointGridInclusive.h"
|
||||
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/SparsePointGridInclusive.h"
|
||||
|
||||
#include "slicer.h"
|
||||
|
||||
|
||||
namespace cura {
|
||||
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
|
||||
|
||||
|
||||
SlicerLayer::SlicerLayer(unsigned int layer_nr, Mesh* mesh, std::optional<TextureBumpMapProcessor::Settings> bump_map_settings, FaceNormalStorage* face_normal_storage)
|
||||
: layer_nr(layer_nr)
|
||||
{
|
||||
if (bump_map_settings)
|
||||
{
|
||||
TexturedMesh* textured_mesh = dynamic_cast<TexturedMesh*>(mesh);
|
||||
assert(textured_mesh && "we should only have bump map settings when there is a texture");
|
||||
texture_bump_map.emplace(textured_mesh, *bump_map_settings, face_normal_storage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SlicerLayer::makeBasicPolygonLoops(const Mesh* mesh, Polygons& open_polylines)
|
||||
{
|
||||
for(unsigned int start_segment_idx = 0; start_segment_idx < segments.size(); start_segment_idx++)
|
||||
@@ -759,7 +767,7 @@ void SlicerLayer::makePolygons(const Mesh* mesh, bool keep_none_closed, bool ext
|
||||
for (PolygonRef polyline : open_polylines)
|
||||
{
|
||||
if (polyline.size() > 0)
|
||||
openPolylines.add(polyline);
|
||||
polygons.add(polyline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,6 +782,11 @@ void SlicerLayer::makePolygons(const Mesh* mesh, bool keep_none_closed, bool ext
|
||||
auto it = std::remove_if(polygons.begin(), polygons.end(), [snapDistance](PolygonRef poly) { return poly.shorterThan(snapDistance); });
|
||||
polygons.erase(it, polygons.end());
|
||||
|
||||
if (texture_bump_map)
|
||||
{
|
||||
texture_bump_map->processBumpMap(polygons, layer_nr);
|
||||
}
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
polygons.simplify();
|
||||
|
||||
@@ -786,113 +799,4 @@ void SlicerLayer::makePolygons(const Mesh* mesh, bool keep_none_closed, bool ext
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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];
|
||||
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;
|
||||
if (p2.z < minZ) minZ = p2.z;
|
||||
if (p1.z > maxZ) maxZ = p1.z;
|
||||
if (p2.z > maxZ) maxZ = p2.z;
|
||||
int32_t layer_max = (maxZ - initial) / thickness;
|
||||
for(int32_t layer_nr = (minZ - initial) / thickness; layer_nr <= layer_max; layer_nr++)
|
||||
{
|
||||
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
|
||||
// on the slice would create two segments
|
||||
continue;
|
||||
}
|
||||
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
|
||||
} // namespace cura
|
||||
@@ -1,58 +1,46 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_H
|
||||
#define SLICER_H
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_SLICER_LAYER_H
|
||||
#define SLICER_SLICER_LAYER_H
|
||||
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "mesh.h"
|
||||
#include "utils/polygon.h"
|
||||
/*
|
||||
The Slicer creates layers of polygons from an optimized 3D model.
|
||||
The result of the Slicer is a list of polygons without any order or structure.
|
||||
*/
|
||||
namespace cura {
|
||||
#include "../utils/optional.h"
|
||||
#include "../mesh.h"
|
||||
#include "../utils/intpoint.h"
|
||||
#include "../utils/polygon.h"
|
||||
|
||||
class SlicerSegment
|
||||
#include "SlicerSegment.h"
|
||||
#include "GapCloserResult.h"
|
||||
#include "ClosePolygonResult.h"
|
||||
|
||||
#include "../textureProcessing/MatSegment.h"
|
||||
#include "../textureProcessing/TextureBumpMapProcessor.h"
|
||||
#include "../textureProcessing/FaceNormalStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
public:
|
||||
Point start, end;
|
||||
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
|
||||
{ //The result of trying to find a point on a closed polygon line. This gives back the point index, the polygon index, and the point of the connection.
|
||||
//The line on which the point lays is between pointIdx-1 and pointIdx
|
||||
public:
|
||||
Point intersectionPoint;
|
||||
int polygonIdx = -1;
|
||||
unsigned int pointIdx = -1;
|
||||
};
|
||||
class GapCloserResult
|
||||
{
|
||||
public:
|
||||
int64_t len = -1;
|
||||
int polygonIdx = -1;
|
||||
unsigned int pointIdxA = -1;
|
||||
unsigned int pointIdxB = -1;
|
||||
bool AtoB = false;
|
||||
};
|
||||
|
||||
class SlicerLayer
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \param mesh For which mesh this layer is sliced
|
||||
* \param bump_map_settings The settings with which to create a TextureBumpMapProcessor - if provided
|
||||
* \param face_normal_storage The face normal statistics to be used in the \p bump_map_settings - if provided
|
||||
*/
|
||||
SlicerLayer(unsigned int layer_nr, Mesh* mesh, std::optional<TextureBumpMapProcessor::Settings> bump_map_settings, FaceNormalStorage* face_normal_storage);
|
||||
|
||||
std::vector<SlicerSegment> segments;
|
||||
std::unordered_map<int, int> face_idx_to_segment_idx; // topology
|
||||
|
||||
int z = -1;
|
||||
unsigned int layer_nr;
|
||||
|
||||
Polygons polygons;
|
||||
Polygons openPolylines;
|
||||
|
||||
std::optional<TextureBumpMapProcessor> texture_bump_map; //!< the bump map to apply to the outlines - if any
|
||||
|
||||
/*!
|
||||
* Connect the segments into polygons for this layer of this \p mesh
|
||||
*
|
||||
@@ -483,44 +471,6 @@ private:
|
||||
bool allow_reverse);
|
||||
};
|
||||
|
||||
class Slicer
|
||||
{
|
||||
public:
|
||||
std::vector<SlicerLayer> layers;
|
||||
} // namespace cura
|
||||
|
||||
const Mesh* mesh = nullptr; //!< The sliced mesh
|
||||
|
||||
Slicer(Mesh* mesh, int initial, int thickness, int slice_layer_count, bool keepNoneClosed, bool extensiveStitching);
|
||||
|
||||
/*!
|
||||
* Linear interpolation
|
||||
*
|
||||
* Get the Y of a point with X \p x in the line through (\p x0, \p y0) and (\p x1, \p y1)
|
||||
*/
|
||||
int64_t interpolate(int64_t x, int64_t x0, int64_t x1, int64_t y0, int64_t y1) const
|
||||
{
|
||||
int64_t dx_01 = x1 - x0;
|
||||
int64_t num = (y1 - y0) * (x - x0);
|
||||
num += num > 0 ? dx_01/2 : -dx_01/2; // add in offset to round result
|
||||
int64_t y = y0 + num / dx_01;
|
||||
return y;
|
||||
}
|
||||
|
||||
SlicerSegment project2D(Point3& p0, Point3& p1, Point3& p2, int32_t z) const
|
||||
{
|
||||
SlicerSegment seg;
|
||||
|
||||
seg.start.X = interpolate(z, p0.z, p1.z, p0.x, p1.x);
|
||||
seg.start.Y = interpolate(z, p0.z, p1.z, p0.y, p1.y);
|
||||
seg.end .X = interpolate(z, p0.z, p2.z, p0.x, p2.x);
|
||||
seg.end .Y = interpolate(z, p0.z, p2.z, p0.y, p2.y);
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
void dumpSegmentsToHTML(const char* filename);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//SLICER_H
|
||||
#endif // SLICER_SLICER_LAYER_H
|
||||
@@ -0,0 +1,59 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_SLICER_SEGMENT_H
|
||||
#define SLICER_SLICER_SEGMENT_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
#include "../mesh.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class SlicerSegment
|
||||
{
|
||||
public:
|
||||
Point start, end;
|
||||
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;
|
||||
|
||||
SlicerSegment() //!< non-initializing constructor
|
||||
{}
|
||||
SlicerSegment(Point start, Point end) //!< partially initializing constructor
|
||||
: start(start)
|
||||
, end(end)
|
||||
{}
|
||||
/*!
|
||||
* equivalence testing irrespective of start/end order
|
||||
*/
|
||||
bool operator==(const SlicerSegment& b) const
|
||||
{
|
||||
return (start == b.start && end == b.end) || (start == b.end && end == b.start);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
namespace std
|
||||
{
|
||||
/*!
|
||||
* hash function irrespective of start/end order
|
||||
*/
|
||||
template<> struct hash<cura::SlicerSegment>
|
||||
{
|
||||
typedef std::size_t result_type;
|
||||
result_type operator()(cura::SlicerSegment const& s) const
|
||||
{
|
||||
return std::hash<cura::Point>()(cura::operator+(s.start, s.end));
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
|
||||
#endif // SLICER_SLICER_SEGMENT_H
|
||||
+81
-27
@@ -1,4 +1,7 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
//Copyright (C) 2013 David Braam
|
||||
//Copyright (c) 2016 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <cmath> // sqrt
|
||||
#include <utility> // pair
|
||||
#include <deque>
|
||||
@@ -65,14 +68,32 @@ Polygons AreaSupport::join(Polygons& supportLayer_up, Polygons& supportLayer_thi
|
||||
|
||||
void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count)
|
||||
{
|
||||
int max_layer_nr_support_mesh_filled;
|
||||
for (max_layer_nr_support_mesh_filled = storage.support.supportLayers.size() - 1; max_layer_nr_support_mesh_filled >= 0; max_layer_nr_support_mesh_filled--)
|
||||
{
|
||||
const SupportLayer& support_layer = storage.support.supportLayers[max_layer_nr_support_mesh_filled];
|
||||
if (support_layer.supportAreas.size() > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, max_layer_nr_support_mesh_filled);
|
||||
for (int layer_nr = 0; layer_nr < max_layer_nr_support_mesh_filled; layer_nr++)
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[max_layer_nr_support_mesh_filled];
|
||||
support_layer.support_mesh = support_layer.support_mesh.unionPolygons();
|
||||
}
|
||||
|
||||
// initialization of supportAreasPerLayer
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count ; layer_idx++)
|
||||
storage.support.supportLayers.emplace_back();
|
||||
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
if (layer_count > storage.support.supportLayers.size())
|
||||
{ // there might alsready be anti_overhang_area data in the supportLayers
|
||||
storage.support.supportLayers.resize(layer_count);
|
||||
}
|
||||
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
{
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
if (mesh.getSettingBoolean("infill_mesh"))
|
||||
if (mesh.getSettingBoolean("infill_mesh") || mesh.getSettingBoolean("anti_overhang_mesh"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -212,7 +233,6 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
basic_and_full_overhang_above.push_front(computeBasicAndFullOverhang(storage, mesh, layer_idx, max_dist_from_lower_layer));
|
||||
}
|
||||
|
||||
bool still_in_upper_empty_layers = true;
|
||||
int overhang_points_pos = overhang_points.size() - 1;
|
||||
Polygons supportLayer_last;
|
||||
std::vector<Polygons> towerRoofs;
|
||||
@@ -247,8 +267,9 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
{ // join with support from layer up
|
||||
supportLayer_this = AreaSupport::join(supportLayer_last, supportLayer_this, join_distance, smoothing_distance, max_smoothing_angle, conical_support, conical_support_offset, conical_smallest_breadth);
|
||||
}
|
||||
|
||||
|
||||
|
||||
supportLayer_this = supportLayer_this.unionPolygons(storage.support.supportLayers[layer_idx].support_mesh);
|
||||
|
||||
// move up from model
|
||||
if (layerZdistanceBottom > 0 && layer_idx >= layerZdistanceBottom)
|
||||
{
|
||||
@@ -256,8 +277,8 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
int bottomLayer = ((layer_idx - layerZdistanceBottom) / stepHeight) * stepHeight;
|
||||
supportLayer_this = supportLayer_this.difference(storage.getLayerOutlines(bottomLayer, false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
supportLayer_last = supportLayer_this;
|
||||
|
||||
|
||||
@@ -277,18 +298,12 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
}
|
||||
else
|
||||
{
|
||||
supportLayer_this = supportLayer_this.difference(storage.getLayerOutlines(layer_idx, false).offset(supportXYDistance));
|
||||
supportLayer_this = supportLayer_this.difference(outlines.offset(supportXYDistance));
|
||||
}
|
||||
}
|
||||
|
||||
supportAreas[layer_idx] = supportLayer_this;
|
||||
|
||||
if (still_in_upper_empty_layers && supportLayer_this.size() > 0)
|
||||
{
|
||||
storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, (int)layer_idx);
|
||||
still_in_upper_empty_layers = false;
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::SUPPORT, storage.meshes.size() * mesh_idx + support_layer_count - layer_idx, support_layer_count * storage.meshes.size());
|
||||
}
|
||||
|
||||
@@ -326,6 +341,28 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
}
|
||||
}
|
||||
|
||||
//Enforce top Z distance.
|
||||
if (layerZdistanceTop > 0)
|
||||
{
|
||||
// this is performed after the main support generation loop above, because it affects the joining of polygons
|
||||
// if this would be performed in the main loop then some support would not have been generated under the overhangs and consequently no support is generated for that,
|
||||
// meaning almost no support would be generated in some cases which definitely need support.
|
||||
for (size_t layer_idx = 0; layer_idx < storage.support.supportLayers.size() && layer_idx < support_layer_count - layerZdistanceTop; layer_idx++)
|
||||
{
|
||||
supportAreas[layer_idx] = supportAreas[layer_idx].difference(storage.getLayerOutlines(layer_idx + layerZdistanceTop, false));
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int layer_idx = supportAreas.size() - 1; layer_idx != (unsigned int) std::max(-1, storage.support.layer_nr_max_filled_layer) ; layer_idx--)
|
||||
{
|
||||
const Polygons& support_here = supportAreas[layer_idx];
|
||||
if (support_here.size() > 0)
|
||||
{
|
||||
storage.support.layer_nr_max_filled_layer = layer_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
storage.support.generated = true;
|
||||
}
|
||||
|
||||
@@ -349,6 +386,12 @@ std::pair<Polygons, Polygons> AreaSupport::computeBasicAndFullOverhang(const Sli
|
||||
Polygons supportLayer_supported = supportLayer_supporter.offset(max_dist_from_lower_layer);
|
||||
Polygons basic_overhang = supportLayer_supportee.difference(supportLayer_supported);
|
||||
|
||||
const SupportLayer& support_layer = storage.support.supportLayers[layer_idx];
|
||||
if (support_layer.anti_overhang.size())
|
||||
{
|
||||
basic_overhang = basic_overhang.difference(support_layer.anti_overhang);
|
||||
}
|
||||
|
||||
// Polygons support_extension = basic_overhang.offset(max_dist_from_lower_layer);
|
||||
// support_extension = support_extension.intersection(supportLayer_supported);
|
||||
// support_extension = support_extension.intersection(supportLayer_supportee);
|
||||
@@ -388,12 +431,17 @@ void AreaSupport::detectOverhangPoints(
|
||||
|
||||
if (part_poly.size() > 0)
|
||||
{
|
||||
Polygons part_poly_recomputed = part_poly.difference(storage.support.supportLayers[layer_idx].anti_overhang);
|
||||
if (part_poly_recomputed.size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (overhang_points.size() > 0 && overhang_points.back().first == layer_idx)
|
||||
overhang_points.back().second.push_back(part_poly);
|
||||
overhang_points.back().second.push_back(part_poly_recomputed);
|
||||
else
|
||||
{
|
||||
std::vector<Polygons> small_part_polys;
|
||||
small_part_polys.push_back(part_poly);
|
||||
small_part_polys.push_back(part_poly_recomputed);
|
||||
overhang_points.emplace_back<std::pair<int, std::vector<Polygons>>>(std::make_pair(layer_idx, small_part_polys));
|
||||
}
|
||||
}
|
||||
@@ -444,15 +492,21 @@ void AreaSupport::handleTowers(
|
||||
}
|
||||
|
||||
// make tower roofs
|
||||
//for (Polygons& tower_roof : towerRoofs)
|
||||
for (unsigned int r = 0; r < towerRoofs.size(); r++)
|
||||
for (unsigned int roof_idx = 0; roof_idx < towerRoofs.size(); roof_idx++)
|
||||
{
|
||||
supportLayer_this = supportLayer_this.unionPolygons(towerRoofs[r]);
|
||||
|
||||
Polygons& tower_roof = towerRoofs[r];
|
||||
if (tower_roof.size() > 0 && tower_roof[0].area() < supportTowerDiameter * supportTowerDiameter)
|
||||
Polygons& tower_roof = towerRoofs[roof_idx];
|
||||
if (tower_roof.size() > 0)
|
||||
{
|
||||
towerRoofs[r] = tower_roof.offset(towerRoofExpansionDistance);
|
||||
supportLayer_this = supportLayer_this.unionPolygons(tower_roof);
|
||||
|
||||
if (tower_roof[0].area() < supportTowerDiameter * supportTowerDiameter)
|
||||
{
|
||||
tower_roof = tower_roof.offset(towerRoofExpansionDistance);
|
||||
}
|
||||
else
|
||||
{
|
||||
tower_roof.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ public:
|
||||
* \param layer_count total number of layers
|
||||
*/
|
||||
static void generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Generate support polygons over all layers for one object.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include <limits> // numeric_limits
|
||||
#include <cmath> // isnan
|
||||
|
||||
#include "FaceNormalStorage.h"
|
||||
|
||||
#include <math.h> // debug
|
||||
|
||||
#define NORMAL_LENGTH 10000
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
FaceNormalStorage::FaceNormalStorage(Mesh* mesh)
|
||||
{
|
||||
face_normal_vertical_component.reserve(mesh->faces.size());
|
||||
for (MeshFace& face : mesh->faces)
|
||||
{
|
||||
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;
|
||||
face_normal_vertical_component.emplace_back(computeFaceTanAngle(p0, p1, p2));
|
||||
}
|
||||
}
|
||||
|
||||
float FaceNormalStorage::computeFaceTanAngle(const Point3 p0, const Point3 p1, const Point3 p2) const
|
||||
{
|
||||
Point3 v01 = p1 - p0;
|
||||
Point3 v01_n = v01.normal(NORMAL_LENGTH);
|
||||
Point3 v02 = p2 - p0;
|
||||
Point3 v02_n = v02.normal(NORMAL_LENGTH);
|
||||
Point3 normal_dir = v01_n.cross(v02_n);
|
||||
coord_t z_component = normal_dir.z;
|
||||
coord_t xy_component = vSize(Point(normal_dir.x, normal_dir.y));
|
||||
if (xy_component > -2 && xy_component < 2)
|
||||
{
|
||||
if (z_component > 0)
|
||||
{
|
||||
return std::numeric_limits<float>::infinity();
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1 * std::numeric_limits<float>::infinity();
|
||||
}
|
||||
}
|
||||
float ret = (float) z_component / (float) xy_component;
|
||||
assert(!std::isnan(ret));
|
||||
assert(!std::isnan(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
float FaceNormalStorage::getFaceTanAngle(unsigned int face_idx)
|
||||
{
|
||||
return face_normal_vertical_component[face_idx];
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,42 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSING_FACE_NORMAL_STORAGE_H
|
||||
#define TEXTURE_PROCESSING_FACE_NORMAL_STORAGE_H
|
||||
|
||||
#include "../mesh.h"
|
||||
#include "../utils/NoCopy.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* helper class for storing mesh face data to be used by each TextureBumpMapProcessor of one mesh
|
||||
*/
|
||||
class FaceNormalStorage : NoCopy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Constructor to compute the tan angle for all faces in the model.
|
||||
*/
|
||||
FaceNormalStorage(Mesh* mesh);
|
||||
|
||||
/*!
|
||||
* Get the horizontal component of the face normal
|
||||
*
|
||||
* returns a negative amount for faces angling downward
|
||||
* (TODO verify above sentence)
|
||||
* \return the ratio between the vertical and the horizontal aspect of the normal of the face with index \p face_index (in the list of faes in the \ref Mesh)
|
||||
*/
|
||||
float getFaceTanAngle(unsigned int face_idx);
|
||||
protected:
|
||||
|
||||
/*!
|
||||
* compute the tan angle of one face
|
||||
* \p p0, \p p1 and \p p2 should be in CCW order
|
||||
*/
|
||||
float computeFaceTanAngle(const Point3 p0, const Point3 p1, const Point3 p2) const;
|
||||
std::vector<float> face_normal_vertical_component; //!< for each face the horizontal component of the normal angle
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSING_FACE_NORMAL_STORAGE_H
|
||||
@@ -0,0 +1,44 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSING_MAT_COORD_H
|
||||
#define TEXTURE_PROCESSING_MAT_COORD_H
|
||||
|
||||
#include "../utils/FPoint.h"
|
||||
|
||||
#include "Material.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Coordinates in a specific texture bitmap
|
||||
*/
|
||||
struct MatCoord
|
||||
{
|
||||
FPoint coords;
|
||||
const Material* mat; //!< Material id
|
||||
MatCoord() //!< non-initializing constructor
|
||||
{}
|
||||
MatCoord(FPoint coords, const Material& mat) //!< constructor
|
||||
: coords(coords)
|
||||
, mat(&mat)
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Get the color of the material to which this coordinate is pointing
|
||||
*/
|
||||
float getColor(ColourUsage color) const
|
||||
{
|
||||
if (mat)
|
||||
{
|
||||
return mat->getColor(coords.x, coords.y, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSING_MAT_COORD_H
|
||||
@@ -0,0 +1,27 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSING_MAT_SEGMENT_H
|
||||
#define TEXTURE_PROCESSING_MAT_SEGMENT_H
|
||||
|
||||
#include "MatCoord.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Coordinates in a specific texture bitmap
|
||||
*/
|
||||
struct MatSegment
|
||||
{
|
||||
MatCoord start;
|
||||
MatCoord end;
|
||||
MatSegment() //!< non-initializing constructor
|
||||
{}
|
||||
MatSegment(MatCoord start, MatCoord end)
|
||||
: start(start)
|
||||
, end(end)
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSING_MAT_SEGMENT_H
|
||||
@@ -0,0 +1,178 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include <limits> // numeric limits
|
||||
#include <algorithm> // min max
|
||||
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
#include "Material.h"
|
||||
|
||||
#define STBI_FAILURE_USERMSG // enable user friendly bug messages for STB lib
|
||||
#define STB_IMAGE_IMPLEMENTATION // needed in order to enable the implementation of libs/std_image.h
|
||||
#include "stb/stb_image.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
/*!
|
||||
* custom destructor for the data to be used by the shared_pointer
|
||||
*/
|
||||
struct ArrayDeleter
|
||||
{
|
||||
void operator ()(unsigned char* p)
|
||||
{
|
||||
stbi_image_free(p);
|
||||
}
|
||||
};
|
||||
|
||||
Material::Material()
|
||||
: data(nullptr, ArrayDeleter())
|
||||
, width(0)
|
||||
, height(0)
|
||||
, depth(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Material::~Material()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void Material::loadImage(const char* filename)
|
||||
{
|
||||
int w, h, d;
|
||||
// in RGBA order
|
||||
int desired_channel_count = 0; // keep original amount of channels
|
||||
unsigned char* data = stbi_load(filename, &w, &h, &d, desired_channel_count);
|
||||
if (data)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
depth = d;
|
||||
this->data = std::shared_ptr<unsigned char>(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* reason = "[unknown reason]";
|
||||
if (stbi_failure_reason())
|
||||
{
|
||||
reason = stbi_failure_reason();
|
||||
}
|
||||
logError("Cannot load image %s: '%s'.\n", filename, reason);
|
||||
std::exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
float Material::getColor(float x, float y, ColourUsage color) const
|
||||
{
|
||||
if (!data)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
assert(x >= 0.0f && x <= 1.0f);
|
||||
assert(y >= 0.0f && y <= 1.0f);
|
||||
switch (color)
|
||||
{
|
||||
case ColourUsage::RED:
|
||||
case ColourUsage::GREEN:
|
||||
case ColourUsage::BLUE:
|
||||
case ColourUsage::ALPHA:
|
||||
{
|
||||
assert((int)color >= 0 && (unsigned int)color < depth && "Z out of bounds!");
|
||||
return getColorData(x, y, (unsigned int) color);
|
||||
}
|
||||
case ColourUsage::GREY:
|
||||
default:
|
||||
{
|
||||
float r = getColorData(x, y, (unsigned int) ColourUsage::RED);
|
||||
float g = getColorData(x, y, (unsigned int) ColourUsage::GREEN);
|
||||
float b = getColorData(x, y, (unsigned int) ColourUsage::BLUE);
|
||||
return (r + g + b) / 3.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float Material::getColorData(float x, float y, unsigned int z) const
|
||||
{
|
||||
unsigned int x_idx = (unsigned int) (x * (width - 1) + 0.5);
|
||||
assert(x_idx >= 0 && x_idx < width && "requested X is out of bounds!");
|
||||
unsigned int y_idx = (unsigned int) (y * (height - 1) + 0.5);
|
||||
assert(y_idx >= 0 && y_idx < height && "requested Y is out of bounds!");
|
||||
|
||||
unsigned char col = data.get()[((height - y_idx - 1) * width + x_idx) * depth + z];
|
||||
return (float) col / std::numeric_limits<unsigned char>::max();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Material::debugOutput(bool dw) const
|
||||
{
|
||||
std::cerr << "\nImage size: " << width << " x " << height << " (" << depth << "channels)\n";
|
||||
std::cerr << '+';
|
||||
for (unsigned int i = 0; i < width; i++)
|
||||
{
|
||||
std::cerr << ((dw)? "--" : "-");
|
||||
}
|
||||
std::cerr << "+\n";
|
||||
for (unsigned int y = 0; y < height; y++)
|
||||
{
|
||||
std::cerr << "|";
|
||||
for (unsigned int x = 0; x < width; x++)
|
||||
{
|
||||
int val = (data.get()[((height - y) * width + x) * depth] * 10 / 256);
|
||||
|
||||
switch (val)
|
||||
{
|
||||
case 0:
|
||||
std::cerr << ((dw)? " " : " ");
|
||||
break;
|
||||
case 1:
|
||||
std::cerr << ((dw)? ".." : ".");
|
||||
break;
|
||||
case 2:
|
||||
std::cerr << ((dw)? ",," : ",");
|
||||
break;
|
||||
case 3:
|
||||
std::cerr << ((dw)? "::" : ":");
|
||||
break;
|
||||
case 4:
|
||||
std::cerr << ((dw)? ";;" : ";");
|
||||
break;
|
||||
case 5:
|
||||
std::cerr << ((dw)? "++" : "+");
|
||||
break;
|
||||
case 6:
|
||||
std::cerr << ((dw)? "░░" : "░");
|
||||
break;
|
||||
case 7:
|
||||
std::cerr << ((dw)? "▒▒" : "▒");
|
||||
break;
|
||||
case 8:
|
||||
std::cerr << ((dw)? "▓▓" : "▓");
|
||||
break;
|
||||
default:
|
||||
if (val > 8)
|
||||
{
|
||||
std::cerr << ((dw)? "██" : "█");
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << ((dw)? " " : " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cerr << "|\n";
|
||||
}
|
||||
std::cerr << '+';
|
||||
for (unsigned int i = 0; i < width; i++)
|
||||
{
|
||||
std::cerr << ((dw)? "--" : "-");
|
||||
}
|
||||
std::cerr << "+\n";
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,71 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSING_MATERIAL_H
|
||||
#define TEXTURE_PROCESSING_MATERIAL_H
|
||||
|
||||
#include <memory> // shared_ptr
|
||||
|
||||
#include "../settings/settings.h" // ColourUsage
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* The material used in a texture.
|
||||
*
|
||||
* This class just holds the image data and has some nice utility functions.
|
||||
*/
|
||||
class Material
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* non-initializing constructor
|
||||
*/
|
||||
Material();
|
||||
|
||||
/*!
|
||||
* Destructor
|
||||
*
|
||||
* deletes the image data
|
||||
*/
|
||||
~Material();
|
||||
|
||||
/*!
|
||||
* Load an image from file.
|
||||
*
|
||||
* Crash if this doesn't work. (unsupported file type, IO exception, etc.)
|
||||
*/
|
||||
void loadImage(const char* filename);
|
||||
|
||||
/*!
|
||||
* get the color value at a particular place in the image
|
||||
*
|
||||
* \param x place in the horizontal direction left to right (value between zero and one)
|
||||
* \param y place in the vertical direction top to bottom (value between zero and one)
|
||||
* \param color The color channel to check
|
||||
* \return a value between zero and one
|
||||
*/
|
||||
float getColor(float x, float y, ColourUsage color) const;
|
||||
|
||||
/*!
|
||||
* print out something which looks like the picture through std::cerr
|
||||
* \param double_width Whether to double each character being written, so that the width is visually similar to the height of each pixel.
|
||||
*/
|
||||
void debugOutput(bool double_width = true) const;
|
||||
protected:
|
||||
|
||||
std::shared_ptr<unsigned char> data; //!< pixel data in rgb-row-first (or bgr-row first ?)
|
||||
unsigned int width, height, depth; //!< image dimensions
|
||||
|
||||
/*!
|
||||
* Get a color value from the data
|
||||
* \param x place in the horizontal direction left to right (value between zero and one)
|
||||
* \param y place in the vertical direction top to bottom (value between zero and one)
|
||||
* \return the color data (0-256)
|
||||
*/
|
||||
float getColorData(float x, float y, unsigned int z) const;
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSING_MATERIAL_H
|
||||
@@ -0,0 +1,42 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "MaterialBase.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
Material* MaterialBase::add(std::string name)
|
||||
{
|
||||
name_to_mat_idx[name] = materials.size();
|
||||
materials.emplace_back();
|
||||
return &materials.back();
|
||||
}
|
||||
|
||||
const Material* MaterialBase::getMat(unsigned int id) const
|
||||
{
|
||||
if (id < materials.size())
|
||||
{
|
||||
return &materials[id];
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int MaterialBase::getMatId(std::string name) const
|
||||
{
|
||||
auto it = name_to_mat_idx.find(name);
|
||||
if (it == name_to_mat_idx.end())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,27 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSING_MATERIAL_BASE_H
|
||||
#define TEXTURE_PROCESSING_MATERIAL_BASE_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Material.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class MaterialBase
|
||||
{
|
||||
public:
|
||||
int getMatId(std::string name) const;
|
||||
Material* add(std::string name);
|
||||
const Material* getMat(unsigned int id) const;
|
||||
protected:
|
||||
std::unordered_map<std::string, int> name_to_mat_idx;
|
||||
std::vector<Material> materials;
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSING_MATERIAL_BASE_H
|
||||
@@ -0,0 +1,289 @@
|
||||
#include "TextureBumpMapProcessor.h"
|
||||
|
||||
#include <algorithm> // swap
|
||||
#include <cmath> // fabs
|
||||
|
||||
#include "../utils/optional.h"
|
||||
#include "../utils/linearAlg2D.h"
|
||||
#include "../slicer/SlicerSegment.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
#define SLICE_SEGMENT_SNAP_GAP 20
|
||||
|
||||
TextureBumpMapProcessor::TextureBumpMapProcessor(TexturedMesh* mesh, const TextureBumpMapProcessor::Settings settings, FaceNormalStorage* face_normal_storage)
|
||||
: mesh(mesh)
|
||||
, settings(settings)
|
||||
, face_normal_storage(face_normal_storage)
|
||||
, loc_to_slice(SLICE_SEGMENT_SNAP_GAP)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void TextureBumpMapProcessor::registerTexturedFaceSlice(SlicerSegment face_segment, MatSegment texture_segment)
|
||||
{
|
||||
assert(face_segment.faceIndex >= 0);
|
||||
TexturedFaceSlice slice{face_segment, texture_segment};
|
||||
loc_to_slice.insert(face_segment.start, slice);
|
||||
loc_to_slice.insert(face_segment.end, slice);
|
||||
}
|
||||
|
||||
std::optional<TextureBumpMapProcessor::TexturedFaceSlice> TextureBumpMapProcessor::getTexturedFaceSlice(Point p0, Point p1)
|
||||
{
|
||||
std::vector<TexturedFaceSlice> nearby_slices = loc_to_slice.getNearby(p0, SLICE_SEGMENT_SNAP_GAP);
|
||||
std::optional<TexturedFaceSlice> best;
|
||||
coord_t best_dist_score = std::numeric_limits<coord_t>::max();
|
||||
|
||||
for (TexturedFaceSlice& slice : nearby_slices)
|
||||
{
|
||||
coord_t dist_score = std::min(
|
||||
vSize2(slice.face_segment.start - p0) + vSize2(slice.face_segment.end - p1)
|
||||
, vSize2(slice.face_segment.end - p0) + vSize2(slice.face_segment.start - p1)
|
||||
);
|
||||
if (dist_score < best_dist_score)
|
||||
{
|
||||
best = slice;
|
||||
best_dist_score = dist_score;
|
||||
}
|
||||
}
|
||||
if (best_dist_score > SLICE_SEGMENT_SNAP_GAP * SLICE_SEGMENT_SNAP_GAP * 4) // TODO: this condition doesn't follow exactly from using SLICE_SEGMENT_SNAP_GAP and the quadratic dist score
|
||||
{
|
||||
return std::optional<TextureBumpMapProcessor::TexturedFaceSlice>();
|
||||
}
|
||||
if (vSize2(best->face_segment.start - p0) > vSize2(best->face_segment.start - p1))
|
||||
{
|
||||
std::swap(best->face_segment.start, best->face_segment.end);
|
||||
}
|
||||
assert(best->face_segment.faceIndex >= 0);
|
||||
return best;
|
||||
}
|
||||
|
||||
coord_t TextureBumpMapProcessor::getOffset(const float color, const int face_idx)
|
||||
{
|
||||
coord_t extra_offset = 0;
|
||||
if (face_normal_storage)
|
||||
{
|
||||
assert(face_idx >= 0 && "we must know for which face we are getting the color");
|
||||
float tan_angle = face_normal_storage->getFaceTanAngle(face_idx);
|
||||
float abs_tan_angle = std::fabs(tan_angle);
|
||||
abs_tan_angle = std::min(abs_tan_angle, settings.max_tan_correction_angle);
|
||||
extra_offset = settings.face_angle_correction * (color - 0.5) * abs_tan_angle * settings.layer_height;
|
||||
// (color - 0.5) so that the color causes either an outset or an inset which is
|
||||
// within the range [-0.5, 0.5] so that when at max it will coincide with the min on the previous layer:
|
||||
//
|
||||
// for a black mesh
|
||||
// bridged gap = 4 applied offset = 2 and -2
|
||||
// ^^^^ ^^
|
||||
// ____ ______^^
|
||||
// :_______ :_____
|
||||
// : : : will become : : :
|
||||
}
|
||||
return color * (settings.amplitude * 2) - settings.amplitude + settings.offset + extra_offset;
|
||||
}
|
||||
|
||||
coord_t TextureBumpMapProcessor::getCornerOffset(std::optional<TextureBumpMapProcessor::TexturedFaceSlice>& textured_face_slice, std::optional<TextureBumpMapProcessor::TexturedFaceSlice>& next_textured_face_slice)
|
||||
{
|
||||
coord_t offset0 = 0; // where no texture is present, no offset is applied
|
||||
coord_t offset1 = 0;
|
||||
if (textured_face_slice)
|
||||
{
|
||||
const float color0 = textured_face_slice->mat_segment.end.getColor(settings.color_usage);
|
||||
const int face_0_idx = textured_face_slice->face_segment.faceIndex;
|
||||
offset0 = getOffset(color0, face_0_idx);
|
||||
}
|
||||
if (next_textured_face_slice)
|
||||
{
|
||||
const float color1 = next_textured_face_slice->mat_segment.start.getColor(settings.color_usage);
|
||||
const int face_1_idx = next_textured_face_slice->face_segment.faceIndex;
|
||||
offset1 = getOffset(color1, face_1_idx);
|
||||
}
|
||||
coord_t offset = (offset0 + offset1) / 2;
|
||||
return offset;
|
||||
}
|
||||
|
||||
coord_t TextureBumpMapProcessor::getCornerDisregard(Point p0, Point p1, Point p2, std::optional<TextureBumpMapProcessor::TexturedFaceSlice>& textured_face_slice, std::optional<TextureBumpMapProcessor::TexturedFaceSlice>& next_textured_face_slice)
|
||||
{
|
||||
coord_t offset = getCornerOffset(textured_face_slice, next_textured_face_slice);
|
||||
if ((LinearAlg2D::pointIsLeftOfLine(p1, p0, p2) < 0) == (offset > 0))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Point v01 = p1 - p0;
|
||||
Point v12 = p2 - p1;
|
||||
assert(p0 != p1 && "Code below depends on v01 not being of zer o size");
|
||||
assert(p1 != p2 && "This function assumes the three points are different");
|
||||
Point n01 = normal(turn90CCW(v01), -1000);
|
||||
Point n12 = normal(turn90CCW(v12), -1000);
|
||||
Point corner_normal = n01 + n12;
|
||||
coord_t corner_normal_size2 = vSize2(corner_normal);
|
||||
coord_t normal_aspect = dot(corner_normal, v01) / vSize(v01); // The aspect of the corner normal along v01 (might be negative)
|
||||
coord_t dist_aspect = sqrt(std::max((coord_t)1, corner_normal_size2 - normal_aspect * normal_aspect)); // The distance of the end of the normal vector to v01 or v12
|
||||
// ^ due to rounding errors 'corner_normal_size2 - normal_aspect^2' may be smaller than zero; because of division on line below should be at least 1
|
||||
coord_t disregard = std::abs(offset * normal_aspect) / dist_aspect;
|
||||
assert(disregard >= 0);
|
||||
return disregard;
|
||||
}
|
||||
|
||||
|
||||
void TextureBumpMapProcessor::processSegmentBumpMap(unsigned int layer_nr, const SlicerSegment& slicer_segment, const MatSegment& mat, const Point p0, const Point p1, coord_t& dist_left_over, coord_t corner_disregard_p1, PolygonRef result)
|
||||
{
|
||||
assert(mat.start.mat == mat.end.mat && "texture across face must be from one material!");
|
||||
|
||||
Point p0p1 = p1 - p0;
|
||||
int64_t p0p1_size = vSize(p0p1);
|
||||
if (dist_left_over >= p0p1_size - corner_disregard_p1)
|
||||
{
|
||||
dist_left_over -= p0p1_size;
|
||||
return;
|
||||
}
|
||||
|
||||
Point perp_to_p0p1 = turn90CCW(p0p1);
|
||||
int64_t dist_last_point = -1; // p0p1_size * 2 - dist_left_over; // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
|
||||
for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size - corner_disregard_p1; p0pa_dist += settings.point_distance)
|
||||
{
|
||||
assert(p0pa_dist >= 0);
|
||||
assert(p0pa_dist <= p0p1_size);
|
||||
MatCoord mat_coord_now = mat.start;
|
||||
mat_coord_now.coords = mat.start.coords + (mat.end.coords - mat.start.coords) * p0pa_dist / p0p1_size;
|
||||
float val = mat_coord_now.getColor(settings.color_usage);
|
||||
int offset = getOffset(val, slicer_segment.faceIndex);
|
||||
Point fuzz = normal(perp_to_p0p1, offset);
|
||||
Point pa = p0 + normal(p0p1, p0pa_dist) - fuzz;
|
||||
result.add(pa);
|
||||
dist_last_point = p0pa_dist;
|
||||
}
|
||||
assert(dist_last_point >= 0 && "above loop should have run at least once!");
|
||||
assert(p0p1_size > dist_last_point);
|
||||
dist_left_over = p0p1_size - dist_last_point;
|
||||
assert(dist_left_over <= settings.point_distance + corner_disregard_p1);
|
||||
}
|
||||
|
||||
|
||||
void TextureBumpMapProcessor::processBumpMap(Polygons& layer_polygons, unsigned int layer_nr)
|
||||
{
|
||||
if (layer_polygons.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Polygons preprocessed;
|
||||
for (PolygonRef poly : layer_polygons)
|
||||
{ // remove duplicate points
|
||||
PolygonRef preprocessed_poly = preprocessed.newPoly();
|
||||
Point p0 = poly.back();
|
||||
for (const Point p1 : poly)
|
||||
{
|
||||
if (p1 == p0)
|
||||
continue;
|
||||
preprocessed_poly.add(p1);
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
|
||||
Polygons results;
|
||||
for (PolygonRef poly : preprocessed)
|
||||
{
|
||||
if (poly.size() < 3)
|
||||
{
|
||||
results.add(poly);
|
||||
continue;
|
||||
}
|
||||
PolygonRef result = results.newPoly();
|
||||
|
||||
std::vector<std::optional<TexturedFaceSlice>> texture_poly;
|
||||
{
|
||||
Point p0 = poly.back();
|
||||
for (Point& p1 : poly)
|
||||
{
|
||||
texture_poly.emplace_back(getTexturedFaceSlice(p0, p1));
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
|
||||
coord_t corner_disregard_p0 = getCornerDisregard(poly[poly.size() - 2], poly.back(), poly[0], texture_poly.back(), texture_poly[0]);; // TODO
|
||||
coord_t dist_left_over = (settings.point_distance / 2); // the distance to be traversed on the line before making the first new point
|
||||
Point* p0 = &poly.back();
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{ // 'a' is the (next) new point between p0 and p1
|
||||
Point& p1 = poly[point_idx];
|
||||
unsigned int next_point_idx = (point_idx + 1 == poly.size())? 0 : point_idx + 1;
|
||||
Point& p2 = poly[next_point_idx];
|
||||
if (*p0 == p1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::optional<TexturedFaceSlice>& textured_face_slice = texture_poly[point_idx];
|
||||
std::optional<TexturedFaceSlice>& next_textured_face_slice = texture_poly[next_point_idx];
|
||||
|
||||
coord_t corner_disregard_p1 = getCornerDisregard(*p0, p1, p2, textured_face_slice, next_textured_face_slice); // TODO
|
||||
if (dist_left_over < corner_disregard_p0)
|
||||
{
|
||||
dist_left_over = corner_disregard_p0;
|
||||
}
|
||||
|
||||
if (textured_face_slice)
|
||||
{
|
||||
processSegmentBumpMap(layer_nr, textured_face_slice->face_segment, textured_face_slice->mat_segment, *p0, p1, dist_left_over, corner_disregard_p1, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
coord_t p0p1_size2 = vSize2(p1 - *p0);
|
||||
if (p0p1_size2 < dist_left_over * dist_left_over)
|
||||
{
|
||||
dist_left_over -= sqrt(p0p1_size2);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.emplace_back(*p0);
|
||||
result.emplace_back(p1);
|
||||
dist_left_over = settings.point_distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (corner_disregard_p1 == 0
|
||||
&& (textured_face_slice || next_textured_face_slice)
|
||||
&& (textured_face_slice || !shorterThen(p1 - *p0, SLICE_SEGMENT_SNAP_GAP)) // don't introduce corner points for gap closer poly segments
|
||||
&& (next_textured_face_slice || !shorterThen(p2 - p1, SLICE_SEGMENT_SNAP_GAP)) // don't introduce corner points for gap closer poly segments
|
||||
)
|
||||
{ // add point for outward corner
|
||||
// TODO: remove code duplication with getCornerDisregard
|
||||
coord_t offset = getCornerOffset(textured_face_slice, next_textured_face_slice);
|
||||
Point v01 = p1 - *p0;
|
||||
Point v12 = p2 - p1;
|
||||
Point n01 = normal(turn90CCW(v01), -1000);
|
||||
Point n12 = normal(turn90CCW(v12), -1000);
|
||||
Point corner_normal = normal(n01 + n12, offset);
|
||||
result.add(p1 + corner_normal);
|
||||
}
|
||||
p0 = &p1;
|
||||
corner_disregard_p0 = corner_disregard_p1;
|
||||
}
|
||||
while (result.size() < 3 )
|
||||
{
|
||||
unsigned int point_idx = poly.size() - 2;
|
||||
result.add(poly[point_idx]);
|
||||
if (point_idx == 0) { break; }
|
||||
point_idx--;
|
||||
}
|
||||
if (result.size() < 3)
|
||||
{
|
||||
result.clear();
|
||||
for (Point& p : poly)
|
||||
result.add(p);
|
||||
}
|
||||
}
|
||||
// a negative offset on two sides of a corner, may introduce complexities in the model which should be removed:
|
||||
// ^↘
|
||||
// ^ ↘
|
||||
// <<<<<<<<^<<<< should become <<<<<<<<
|
||||
// ^ ^
|
||||
// ^ ^
|
||||
// ^ ^
|
||||
layer_polygons = results.removeComplexParts();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,150 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSING_TEXTURE_BUMP_MAP_PROCESSOR_H
|
||||
#define TEXTURE_PROCESSING_TEXTURE_BUMP_MAP_PROCESSOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <math.h> // tan
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "../utils/optional.h"
|
||||
#include "../utils/SparsePointGrid.h"
|
||||
|
||||
#include "../settings/settings.h"
|
||||
|
||||
#include "../slicer/SlicerSegment.h"
|
||||
#include "TexturedMesh.h"
|
||||
#include "FaceNormalStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class TextureBumpMapProcessor
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Helper class to retrieve and store texture to bump map settings
|
||||
*/
|
||||
struct Settings
|
||||
{
|
||||
coord_t layer_height;
|
||||
coord_t point_distance;
|
||||
coord_t amplitude;
|
||||
coord_t offset;
|
||||
bool alternate;
|
||||
float face_angle_correction;
|
||||
float max_tan_correction_angle;
|
||||
ColourUsage color_usage;
|
||||
Settings(SettingsBaseVirtual* settings_base)
|
||||
: layer_height(settings_base->getSettingInMicrons("layer_height"))
|
||||
, point_distance(settings_base->getSettingInMicrons("bump_map_point_dist"))
|
||||
, amplitude(settings_base->getSettingInMicrons("bump_map_amplitude"))
|
||||
, offset(settings_base->getSettingInMicrons("bump_map_offset"))
|
||||
, alternate(settings_base->getSettingBoolean("bump_map_alternate"))
|
||||
, face_angle_correction(settings_base->getSettingAsRatio("bump_map_face_angle_correction"))
|
||||
, max_tan_correction_angle(std::tan(0.5 * M_PI - settings_base->getSettingInAngleRadians("bump_map_angle_correction_min")))
|
||||
, color_usage(settings_base->getSettingAsColourUsage("bump_map_texture_color"))
|
||||
{
|
||||
}
|
||||
};
|
||||
/*!
|
||||
* default constructor
|
||||
*
|
||||
* initializes the \ref SparseGrid::cell_size of \ref TextureBumpMapProcessor::loc_to_slice
|
||||
*
|
||||
* \param settings The settings with which to \ref TextureBumpMapProcessor::processBumpMap
|
||||
*/
|
||||
TextureBumpMapProcessor(TexturedMesh* mesh, const Settings settings, FaceNormalStorage* face_normal_storage);
|
||||
|
||||
/*!
|
||||
* Process the texture bump map.
|
||||
* Change the polygons in a layer
|
||||
*
|
||||
* \warning Where no texture is present, no offset is applied to the outer boundary!
|
||||
* Such segments are copied to the result as is
|
||||
*
|
||||
* \param[in,out] layer_polygons The polygons to be offsetted by texture color values
|
||||
* \param layer_nr The layer nr for which we are processing the bump map
|
||||
*/
|
||||
void processBumpMap(Polygons& layer_polygons, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Register that a particular face was sliced to a particular texture segment.
|
||||
* \param face_segment The geometrical segment of the face
|
||||
* \param texture_segment The corresponding texture coordinates
|
||||
*/
|
||||
void registerTexturedFaceSlice(SlicerSegment face_segment, MatSegment texture_segment);
|
||||
protected:
|
||||
/*!
|
||||
* A sliced segment in combination with the corresponding texture slice.
|
||||
*/
|
||||
struct TexturedFaceSlice
|
||||
{
|
||||
SlicerSegment face_segment;
|
||||
MatSegment mat_segment;
|
||||
};
|
||||
|
||||
TexturedMesh* mesh;
|
||||
|
||||
/*!
|
||||
* The settings with which to \ref TextureBumpMapProcessor::processBumpMap
|
||||
*/
|
||||
Settings settings;
|
||||
|
||||
/*!
|
||||
* The face normal statistics to correct offsets for slanted faces - if provided
|
||||
*
|
||||
* This is stored as a pointer so that the default assignment operator = can be defined automatically.
|
||||
*/
|
||||
FaceNormalStorage* face_normal_storage;
|
||||
|
||||
/*!
|
||||
* A grid to efficiently look op which texture segment best fits the slicer segment.
|
||||
*/
|
||||
SparseGrid<TexturedFaceSlice> loc_to_slice;
|
||||
|
||||
/*!
|
||||
* Get the offset to be applied at a given location
|
||||
*/
|
||||
coord_t getOffset(const float color, const int face_idx);
|
||||
|
||||
/*!
|
||||
* Get the offset to be applied at a given corner
|
||||
*
|
||||
* Computes the average offset from the end of \p textured_face_slice and start of \p next_textured_face_slice
|
||||
* If either of those is not present, the \ref TextureBumpMapProcessor::Settings::default_color is used for that segment
|
||||
*
|
||||
* \warning Where no texture is present, no offset is applied to the outer boundary!
|
||||
*
|
||||
* \param textured_face_slice From which to determine the offset at the end of the line segment - or default to zero
|
||||
* \param next_textured_face_slice From which to determine the offset at the start of the line segment - or default to zero
|
||||
*/
|
||||
coord_t getCornerOffset(std::optional<TextureBumpMapProcessor::TexturedFaceSlice>& textured_face_slice, std::optional<TextureBumpMapProcessor::TexturedFaceSlice>& next_textured_face_slice);
|
||||
|
||||
/*!
|
||||
* Get how much of a corner to skip generating offsetted indices for inner corners,
|
||||
* because those points would be removed by the offset itseld
|
||||
*/
|
||||
coord_t getCornerDisregard(Point p0, Point p1, Point p2, std::optional<TexturedFaceSlice>& textured_face_slice, std::optional<TexturedFaceSlice>& next_textured_face_slice);
|
||||
|
||||
/*!
|
||||
* Get the TexturedFaceSlice corresponding to an outline segment
|
||||
*
|
||||
* Note that due to snapping in the \ref Slicer::makePolygons function, an outline segment may be a bit different from the originally sliced SlicerSegment
|
||||
*
|
||||
* \param p0 The start of the segment
|
||||
* \param p1 The end of the segment
|
||||
*/
|
||||
std::optional<TexturedFaceSlice> getTexturedFaceSlice(Point p0, Point p1);
|
||||
|
||||
/*!
|
||||
*
|
||||
* \param layer_nr The layer number for which we process the bump map
|
||||
* \param slicer_segment The segment closest matching \p p0 - \p p1
|
||||
* \param corner_disregard_p1 The distance at the end of p0p1 in which not to place offsetted points
|
||||
*/
|
||||
void processSegmentBumpMap(unsigned int layer_nr, const SlicerSegment& slicer_segment, const MatSegment& mat, const Point p0, const Point p1, coord_t& dist_left_over, coord_t corner_disregard_p1, PolygonRef result);
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSING_TEXTURE_BUMP_MAP_PROCESSOR_H
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "TextureProximityProcessor.h"
|
||||
|
||||
#include <algorithm> // swap
|
||||
#include <functional> // function
|
||||
|
||||
#include "../utils/optional.h"
|
||||
#include "../utils/linearAlg2D.h"
|
||||
#include "../slicer/SlicerSegment.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
TextureProximityProcessor::TextureProximityProcessor(const TextureProximityProcessor::Settings settings, unsigned int slice_layer_count)
|
||||
: settings(settings)
|
||||
{
|
||||
loc_to_slice.resize(slice_layer_count, SparseLineGrid<TexturedFaceSlice, TexturedFaceSliceLocator>(settings.proximity));
|
||||
}
|
||||
|
||||
|
||||
void TextureProximityProcessor::registerTexturedFaceSlice(SlicerSegment face_segment, MatSegment texture_segment, unsigned int layer_nr)
|
||||
{
|
||||
TexturedFaceSlice slice{face_segment, texture_segment};
|
||||
assert((int)layer_nr >= 0 && layer_nr < loc_to_slice.size());
|
||||
loc_to_slice[layer_nr].insert(slice);
|
||||
}
|
||||
|
||||
float TextureProximityProcessor::getColor(const Point location, const unsigned int layer_nr, ColourUsage color, float default_color)
|
||||
{
|
||||
assert((int)layer_nr >= 0 && layer_nr < loc_to_slice.size());
|
||||
SparseLineGrid<TexturedFaceSlice, TexturedFaceSliceLocator> grid = loc_to_slice[layer_nr];
|
||||
|
||||
coord_t best_dist2 = std::numeric_limits<coord_t>::max();
|
||||
std::optional<TexturedFaceSlice> best;
|
||||
std::function<bool (const TexturedFaceSlice& in)> process_func = [location, &best_dist2, &best](const TexturedFaceSlice& in)
|
||||
{
|
||||
coord_t dist2 = LinearAlg2D::getDist2FromLineSegment(in.face_segment.start, location, in.face_segment.end);
|
||||
if (dist2 < best_dist2)
|
||||
{
|
||||
best_dist2 = dist2;
|
||||
best = in;
|
||||
}
|
||||
return true; // keep going, we're not sure whether we have found the best yet
|
||||
};
|
||||
|
||||
grid.processNearby(location, settings.proximity, process_func);
|
||||
|
||||
if (best_dist2 > settings.proximity * settings.proximity * 4)
|
||||
{
|
||||
return default_color;
|
||||
}
|
||||
assert(best && "given that dist2 != max int this variable should have been innitialized");
|
||||
const Point p0 = best->face_segment.start;
|
||||
const Point p1 = best->face_segment.end;
|
||||
const Point x = location;
|
||||
// Point r = resulting point on the nearest segment, nearest to [location]
|
||||
const MatSegment mat_segment = best->mat_segment;
|
||||
|
||||
const Point v01 = p1 - p0;
|
||||
const Point v0x = x - p0;
|
||||
const coord_t v01_length2 = vSize2(v01);
|
||||
if (v01_length2 <= 4)
|
||||
{
|
||||
return mat_segment.start.getColor(color);
|
||||
}
|
||||
|
||||
const coord_t dot_prod = dot(v0x, v01);
|
||||
const int64_t v0r_length2 = dot_prod * dot_prod / v01_length2;
|
||||
if (v0r_length2 <= 0)
|
||||
{
|
||||
return mat_segment.start.getColor(color);
|
||||
}
|
||||
if (v0r_length2 >= v01_length2)
|
||||
{
|
||||
return mat_segment.end.getColor(color);
|
||||
}
|
||||
const coord_t v0r_length = sqrt(v0r_length2);
|
||||
const coord_t v01_length = sqrt(v01_length2);
|
||||
MatCoord mat_in_between = mat_segment.start;
|
||||
mat_in_between.coords = mat_segment.start.coords + (mat_segment.end.coords - mat_segment.start.coords) * v0r_length / v01_length;
|
||||
return mat_in_between.getColor(color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,93 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSING_TEXTURE_PROXIMITY_PROCESSOR_H
|
||||
#define TEXTURE_PROCESSING_TEXTURE_PROXIMITY_PROCESSOR_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../utils/intpoint.h"
|
||||
#include "../utils/SparseLineGrid.h"
|
||||
|
||||
#include "../settings/settings.h"
|
||||
|
||||
#include "../slicer/SlicerSegment.h"
|
||||
#include "TexturedMesh.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for recording texture coordinates at places where textures are defined, for later looking in the proximity of a texture.
|
||||
*/
|
||||
class TextureProximityProcessor
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Helper class to retrieve and store texture to bump map settings
|
||||
*/
|
||||
struct Settings
|
||||
{
|
||||
coord_t proximity; //!< The distance within which to search for nearby texture
|
||||
Settings(coord_t proximity)
|
||||
: proximity(proximity)
|
||||
{
|
||||
}
|
||||
};
|
||||
/*!
|
||||
* default constructor
|
||||
*
|
||||
* initializes the \ref SparseGrid::cell_size of \ref TextureProximityProcessor::loc_to_slice
|
||||
*
|
||||
* \param settings The settings with which to \ref TextureProximityProcessor::processBumpMap
|
||||
*/
|
||||
TextureProximityProcessor(const Settings settings, unsigned int slice_layer_count);
|
||||
|
||||
/*!
|
||||
* Register that a particular face was sliced to a particular texture segment.
|
||||
* \param face_segment The geometrical segment of the face
|
||||
* \param texture_segment The corresponding texture coordinates
|
||||
* \param layer_nr The layer for which to register a face being sliced
|
||||
*/
|
||||
void registerTexturedFaceSlice(SlicerSegment face_segment, MatSegment texture_segment, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
*
|
||||
* \param default_color Default color where no texture is present
|
||||
*/
|
||||
float getColor(const Point location, const unsigned int layer_nr, ColourUsage color, float default_color);
|
||||
protected:
|
||||
/*!
|
||||
* A sliced segment in combination with the corresponding texture slice.
|
||||
*/
|
||||
struct TexturedFaceSlice
|
||||
{
|
||||
SlicerSegment face_segment;
|
||||
MatSegment mat_segment;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Locator to find the line segment of a \ref TexturedFaceSlice
|
||||
*/
|
||||
struct TexturedFaceSliceLocator
|
||||
{
|
||||
std::pair<Point, Point> operator()(const TexturedFaceSlice& elem) const
|
||||
{
|
||||
return std::make_pair(elem.face_segment.start, elem.face_segment.end);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* The settings with which to \ref TextureBumpMapProcessor::processBumpMap
|
||||
*/
|
||||
Settings settings;
|
||||
|
||||
/*!
|
||||
* A grid to efficiently look op which texture segment best fits the slicer segment.
|
||||
*
|
||||
* A vector of elements for each layer
|
||||
*/
|
||||
std::vector<SparseLineGrid<TexturedFaceSlice, TexturedFaceSliceLocator>> loc_to_slice;
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSING_TEXTURE_PROXIMITY_PROCESSOR_H
|
||||
@@ -0,0 +1,144 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "TexturedMesh.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <math.h>
|
||||
|
||||
#include "../utils/logoutput.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
TexturedMesh::TexturedMesh(SettingsBaseVirtual* sb)
|
||||
: Mesh(sb)
|
||||
, current_mat(-1) // not set yet
|
||||
{
|
||||
}
|
||||
|
||||
void TexturedMesh::addTextureCoord(float x, float y)
|
||||
{
|
||||
// some textures use wrapping for some unholy reason
|
||||
// unwrap for texture coordinates to fall within [0,1]
|
||||
if (x > 1.0f || x < 0.0f)
|
||||
{ // only apply fmod when more than 1.0
|
||||
x = fmod(x, 1.0f);
|
||||
if (x < 0.0)
|
||||
{
|
||||
x += 1.0f;
|
||||
}
|
||||
}
|
||||
if (y > 1.0f || y < 0.0f)
|
||||
{ // only apply fmod when more than 1.0
|
||||
y = fmod(y, 1.0f);
|
||||
if (y < 0.0)
|
||||
{
|
||||
y += 1.0f;
|
||||
}
|
||||
}
|
||||
texture_coords.emplace_back(x, y);
|
||||
}
|
||||
|
||||
void TexturedMesh::addFace(int vi0, int vi1, int vi2, int ti0, int ti1, int ti2)
|
||||
{
|
||||
if (vi0 < -1)
|
||||
{
|
||||
vi0 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (vi1 < -1)
|
||||
{
|
||||
vi1 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (vi2 < -1)
|
||||
{
|
||||
vi2 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (ti0 < -1)
|
||||
{
|
||||
ti0 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (ti1 < -1)
|
||||
{
|
||||
ti1 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (ti2 < -1)
|
||||
{
|
||||
ti2 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
bool made_new_face = Mesh::addFace(vi0, vi1, vi2);
|
||||
if (made_new_face)
|
||||
{
|
||||
face_texture_indices.emplace_back(ti0, ti1, ti2, current_mat);
|
||||
assert(Mesh::faces.size() == face_texture_indices.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool TexturedMesh::setMaterial(std::string name)
|
||||
{
|
||||
current_mat = material_base.getMatId(name);
|
||||
return current_mat >= 0;
|
||||
}
|
||||
|
||||
Material* TexturedMesh::addMaterial(std::string name)
|
||||
{
|
||||
return material_base.add(name);
|
||||
}
|
||||
|
||||
|
||||
bool TexturedMesh::getFaceEdgeMatCoord(unsigned int face_idx, int64_t z, unsigned int p0_idx, unsigned int p1_idx, MatCoord& result) const
|
||||
{
|
||||
if (face_idx >= face_texture_indices.size() || face_idx >= faces.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FaceTextureCoordIndices texture_idxs = face_texture_indices[face_idx];
|
||||
if (texture_idxs.index[0] < 0 || texture_idxs.index[1] < 0 || texture_idxs.index[2] < 0 || texture_idxs.mat_id < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const MeshFace& face = faces[face_idx];
|
||||
Point3 p0(vertices[face.vertex_index[p0_idx]].p);
|
||||
Point3 p1(vertices[face.vertex_index[p1_idx]].p);
|
||||
|
||||
float dzp0 = z - p0.z;
|
||||
float dp0p1 = p1.z - p0.z;
|
||||
|
||||
if (dzp0 * dp0p1 < 0)
|
||||
{ // z doesn't lie between p0 and p1
|
||||
return false;
|
||||
}
|
||||
if (dzp0 == 0)
|
||||
{ // edge is not cut by horizontal plane!
|
||||
return false;
|
||||
}
|
||||
float ratio = INT2MM(dzp0) / INT2MM(dp0p1);
|
||||
|
||||
FPoint t0 = texture_coords[texture_idxs.index[p0_idx]];
|
||||
FPoint t1 = texture_coords[texture_idxs.index[p1_idx]];
|
||||
|
||||
result.mat = material_base.getMat(texture_idxs.mat_id);
|
||||
result.coords.x = t0.x + (t1.x - t0.x) * ratio;
|
||||
result.coords.y = t0.y + (t1.y - t0.y) * ratio;
|
||||
|
||||
if (result.coords.x > 1.001 || result.coords.x < -0.001 || result.coords.y > 1.001 || result.coords.y < -0.001)
|
||||
{
|
||||
logError("WARNING: wrapping material to outside image!\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TexturedMesh::sliceFaceTexture(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result) const
|
||||
{
|
||||
if (!getFaceEdgeMatCoord(face_idx, z, idx_shared, idx_first, result.start))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!getFaceEdgeMatCoord(face_idx, z, idx_shared, idx_second, result.end))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,77 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSING_TEXTURED_MESH_H
|
||||
#define TEXTURE_PROCESSING_TEXTURED_MESH_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "MaterialBase.h"
|
||||
|
||||
#include "../mesh.h"
|
||||
#include "../utils/intpoint.h"
|
||||
#include "MatSegment.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A mesh with bitmap textures to it.
|
||||
*
|
||||
* material coordinates are defined separately, and can be reused for different bitmap textures
|
||||
*/
|
||||
class TexturedMesh : public Mesh
|
||||
{
|
||||
public:
|
||||
TexturedMesh(SettingsBaseVirtual* sb);
|
||||
|
||||
|
||||
/*!
|
||||
*
|
||||
*/
|
||||
struct FaceTextureCoordIndices
|
||||
{
|
||||
int index[3]; //!< indices into texture_coords or -1 if no texture data available
|
||||
int mat_id; //!< Material id
|
||||
FaceTextureCoordIndices(int i1, int i2, int i3, int mat_id)
|
||||
: mat_id(mat_id)
|
||||
{
|
||||
index[0] = i1;
|
||||
index[1] = i2;
|
||||
index[2] = i3;
|
||||
}
|
||||
};
|
||||
void addTextureCoord(float x, float y);
|
||||
void addFace(int vi0, int vi1, int vi2, int ti0, int ti1, int ti2);
|
||||
using Mesh::addFace; // otherwise above addFace would shadow the parent addFace
|
||||
|
||||
bool setMaterial(std::string name); //!< set the material to be used in the comming data to be loaded
|
||||
Material* addMaterial(std::string name);
|
||||
|
||||
/*!
|
||||
* \return Whether a texture line segment has been created
|
||||
*/
|
||||
bool sliceFaceTexture(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result) const;
|
||||
|
||||
protected:
|
||||
std::vector<FPoint> texture_coords; //!< all texture coordinates by all faces
|
||||
std::vector<FaceTextureCoordIndices> face_texture_indices; //!< for each face the corresponding texture coordinates in TexturedMesh::texture_coords
|
||||
// TODO clean up above lists when super class clear() is called
|
||||
// TODO when to clean up below material base?
|
||||
MaterialBase material_base;
|
||||
/*!
|
||||
* Get the material coordinate corresponding to the point on a plane cutting a given edge of the face.
|
||||
* \param face_idx The face for which to get the material coord
|
||||
* \param z The z of the horizontal plane cutting the face
|
||||
* \param p0_idx The index into the first vert of the edge
|
||||
* \param p1_idx The index into the second vert of the edge
|
||||
* \param result The resulting material Coordinates
|
||||
* \return Whether a Material coordinate is defined at the given location
|
||||
*/
|
||||
bool getFaceEdgeMatCoord(unsigned int face_idx, int64_t z, unsigned int p0_idx, unsigned int p1_idx, MatCoord& result) const;
|
||||
private:
|
||||
int current_mat; //!< material currently used in loading the face material info
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSING_TEXTURED_MESH_H
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
double max_feedrate[NUM_AXIS] = {600, 600, 40, 25};
|
||||
double max_feedrate[NUM_AXIS] = {600, 600, 40, 25}; // mm/s
|
||||
double minimumfeedrate = 0.01;
|
||||
double acceleration = 3000;
|
||||
double max_acceleration[NUM_AXIS] = {9000, 9000, 100, 10000};
|
||||
|
||||
@@ -15,8 +15,8 @@ AABB3D::AABB3D()
|
||||
|
||||
bool AABB3D::hit(const AABB3D& other) const
|
||||
{
|
||||
if ( max.x < other.min.y
|
||||
|| min.x > other.max.y
|
||||
if ( max.x < other.min.x
|
||||
|| min.x > other.max.x
|
||||
|| max.y < other.min.y
|
||||
|| min.y > other.max.y
|
||||
|| max.z < other.min.z
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_F_POINT_H
|
||||
#define UTILS_F_POINT_H
|
||||
|
||||
#include <cmath> // sqrt
|
||||
#include <iostream> // auto-serialization / auto-toString() '<<'
|
||||
|
||||
namespace cura
|
||||
{
|
||||
/*!
|
||||
* 2D coordinates represented by floats
|
||||
*/
|
||||
class FPoint
|
||||
{
|
||||
public:
|
||||
float x, y; //!< Coordinates
|
||||
FPoint() //!< non-initializing constructor
|
||||
{}
|
||||
FPoint(float x, float y) //!< constructor
|
||||
: x(x)
|
||||
, y(y)
|
||||
{}
|
||||
|
||||
FPoint operator+(const FPoint& p) const { return FPoint(x+p.x, y+p.y); }
|
||||
FPoint operator-(const FPoint& p) const { return FPoint(x-p.x, y-p.y); }
|
||||
FPoint operator/(const float i) const { return FPoint(x/i, y/i); }
|
||||
FPoint operator*(const float i) const { return FPoint(x*i, y*i); }
|
||||
|
||||
FPoint& operator += (const FPoint& p) { x += p.x; y += p.y; return *this; }
|
||||
FPoint& operator -= (const FPoint& p) { x -= p.x; y -= p.y; return *this; }
|
||||
|
||||
bool operator==(const FPoint& p) const { return x == p.x && y == p.y; }
|
||||
bool operator!=(const FPoint& p) const { return x != p.x || y != p.y; }
|
||||
|
||||
/*!
|
||||
* output to string stream in standard format
|
||||
*/
|
||||
template<class CharT, class TraitsT>
|
||||
friend
|
||||
std::basic_ostream<CharT, TraitsT>&
|
||||
operator <<(std::basic_ostream<CharT, TraitsT>& os, const FPoint& p)
|
||||
{
|
||||
return os << "(" << p.x << ", " << p.y << ")";
|
||||
}
|
||||
|
||||
/*!
|
||||
* squared vector size
|
||||
*/
|
||||
float vSize2() const
|
||||
{
|
||||
return x * x + y * y;
|
||||
}
|
||||
|
||||
/*!
|
||||
* vector size
|
||||
*/
|
||||
float vSize() const
|
||||
{
|
||||
return sqrt(vSize2());
|
||||
}
|
||||
|
||||
/*!
|
||||
* dot product
|
||||
*/
|
||||
float dot(const FPoint& p) const
|
||||
{
|
||||
return x * p.x + y * p.y;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // UTILS_F_POINT_H
|
||||
@@ -0,0 +1,129 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_LAZY_INITIALIZATION_H
|
||||
#define UTILS_LAZY_INITIALIZATION_H
|
||||
|
||||
#include <functional> // bind, function
|
||||
|
||||
#include "optional.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for initializing an object only when it's requested
|
||||
*
|
||||
* Credits to Johannes Goller
|
||||
*
|
||||
* \tparam T The type of the object to instantiate lazily
|
||||
* \tparam Args The types of the arguments to the constructor or constructor function object
|
||||
*/
|
||||
template <typename T, typename... Args>
|
||||
class LazyInitialization : public std::optional<T>
|
||||
{
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Delayed constructor call of T class
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the constructor at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[args...]()
|
||||
{
|
||||
return new T(args...);
|
||||
}
|
||||
)
|
||||
{ }
|
||||
|
||||
/*!
|
||||
* Delayed function call for creating a T object
|
||||
*
|
||||
* Performs a copy from the return value of the function on the stack to the heap.
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the function object at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(const std::function<T (Args...)>& f, Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[f, args...]()
|
||||
{
|
||||
return new T(f(args...));
|
||||
}
|
||||
)
|
||||
{ }
|
||||
|
||||
/*!
|
||||
* Delayed function call for creating a T object
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the function object at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(const std::function<T* (Args...)>& f, Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[f, args...]()
|
||||
{
|
||||
return f(args...);
|
||||
}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
LazyInitialization(LazyInitialization<T, Args...>& other) //!< copy constructor
|
||||
: std::optional<T>(other)
|
||||
, constructor(other.constructor)
|
||||
{
|
||||
}
|
||||
|
||||
LazyInitialization(LazyInitialization<T, Args...>&& other) //!< move constructor
|
||||
: std::optional<T>(other)
|
||||
{
|
||||
constructor = std::move(other.constructor);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Dereference this lazy object
|
||||
*
|
||||
* Calls constructor if object isn't constructed yet.
|
||||
*/
|
||||
T& operator*()
|
||||
{
|
||||
if (!std::optional<T>::instance)
|
||||
{
|
||||
std::optional<T>::instance = constructor();
|
||||
}
|
||||
return std::optional<T>::operator*();
|
||||
}
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
if (!std::optional<T>::instance)
|
||||
{
|
||||
std::optional<T>::instance = constructor();
|
||||
}
|
||||
return std::optional<T>::operator->();
|
||||
}
|
||||
|
||||
LazyInitialization<T, Args...>& operator=(LazyInitialization<T, Args...>&& other)
|
||||
{
|
||||
std::optional<T>::operator=(other);
|
||||
constructor = other.constructor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(LazyInitialization<T, Args...>& other)
|
||||
{
|
||||
std::optional<T>::swap(other);
|
||||
std::swap(constructor, other.constructor);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<T* ()> constructor;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif // UTILS_LAZY_INITIALIZATION_H
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário