Comparar commits
378 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| d949e29d4c | |||
| fe4bce9d29 | |||
| ab313b8992 | |||
| 7779bb9604 | |||
| 1576d1d19e | |||
| 615ed0b384 | |||
| ee09c6da92 | |||
| 325345a2ce | |||
| 29e17d8bdf | |||
| 1f1bfadf9e | |||
| 5aca2bde45 | |||
| ae6426cb6e | |||
| 7271693169 | |||
| d5bbe57571 | |||
| 8423d3ed4a | |||
| b1f717ad9b | |||
| 00eba89494 | |||
| 76e5b8b94b | |||
| 80e09a05ef | |||
| e96bd68324 | |||
| 9ba6239219 | |||
| af099e6896 | |||
| bcdcc25a51 | |||
| 6b9c98914d | |||
| e1d508ade0 | |||
| 919c0ea16d | |||
| 865b22063b | |||
| 4bfd48efa7 | |||
| 9e2b269c8b | |||
| 2e78d60c13 | |||
| caf1fd5582 | |||
| 8f99483d2c | |||
| fc31b13a5e | |||
| 2ea1bc0e59 | |||
| 102fb24d74 | |||
| 381bf1c3d2 | |||
| 03b188aca5 | |||
| d584b19078 | |||
| df1b0372ff | |||
| 73de8ac75b | |||
| 48464e9d13 | |||
| 51a9d1b44f | |||
| 481142ef4a | |||
| 2eb3d3438a | |||
| eedda865bc | |||
| d9195c276f | |||
| 5accc6adf8 | |||
| f7fde50e73 | |||
| 765b8be00b | |||
| 756af8601c | |||
| 21661f2f4b | |||
| 1ffa57f740 | |||
| 158b944b76 | |||
| cce2efd419 | |||
| 1853a41117 | |||
| 69412eda52 | |||
| 567c385e69 | |||
| e35f21ef7b | |||
| ee8b2ed39c | |||
| 7d93688b9b | |||
| 8daa32f757 | |||
| cf05eadfaa | |||
| 70093fdb5a | |||
| 1a7dec654e | |||
| 24f41fe419 | |||
| 97e89e1a94 | |||
| 913f955de8 | |||
| a2fa27603b | |||
| 9d313910f4 | |||
| eacc87d435 | |||
| 57f276d07e | |||
| dd638c32e5 | |||
| e02ed7c928 | |||
| c0007b156f | |||
| 2c1f4cf50e | |||
| 28612e060c | |||
| 094370db73 | |||
| ecb6b7c914 | |||
| 51b2d390c5 | |||
| be157338ad | |||
| 2c54394cd0 | |||
| 141f29bc18 | |||
| 28b27c971e | |||
| 7efc41e218 | |||
| 7145023b6f | |||
| 8797157351 | |||
| f999e1b75f | |||
| 5876964d41 | |||
| 61ff30772c | |||
| e6453082bf | |||
| c2038f7408 | |||
| ff5a1d82f8 | |||
| 1b13dfca97 | |||
| fed536b079 | |||
| 3cf74b5624 | |||
| 52a57b1499 | |||
| 831266db31 | |||
| 5fdb56966f | |||
| eae4c507a4 | |||
| 92b68c5745 | |||
| 9ade99a9c7 | |||
| 097c390fab | |||
| f982202aa4 | |||
| c3f474e229 | |||
| 125af2915f | |||
| 7b26d08d25 | |||
| a2b631b61b | |||
| e3df22dab9 | |||
| 7ad1b32ef3 | |||
| 98a7c65d66 | |||
| b60ad09882 | |||
| 0f35450440 | |||
| 0adc1b44e7 | |||
| 8d084e727a | |||
| 916005b6ad | |||
| c7ecc23053 | |||
| 9d36987124 | |||
| 0fa3cab356 | |||
| 31226c0b68 | |||
| ca23a77ad0 | |||
| b052cd431c | |||
| f00cb00f52 | |||
| f236969909 | |||
| 70dde36d85 | |||
| 52f8a7500e | |||
| 710138f487 | |||
| f7e3682c35 | |||
| 4fca97352d | |||
| 10773b688e | |||
| aae2aad42c | |||
| 79d36d9a49 | |||
| 96d64b325a | |||
| a316302314 | |||
| 93b0aad9f0 | |||
| 1df22b3cab | |||
| 0a3da66539 | |||
| 45674d0b1c | |||
| 834a778f93 | |||
| 160612f0dc | |||
| 0cd463820f | |||
| 73f3995443 | |||
| 72762d9db5 | |||
| 22cbeb7c0a | |||
| 819206afff | |||
| 9121c5440d | |||
| 587121c58d | |||
| 5d5912f8c2 | |||
| efd88d4c84 | |||
| c994d830fa | |||
| 186bdb50da | |||
| 2b418030bb | |||
| 874465b450 | |||
| 7089a2d72a | |||
| 3d7309bb3d | |||
| 2f355d1263 | |||
| 478c667655 | |||
| 0d07f3b9b0 | |||
| d7d3fd47f7 | |||
| 244a90e4eb | |||
| ec5680e2ea | |||
| f9e562e020 | |||
| e7379efaf3 | |||
| 576d90806d | |||
| dcedf97493 | |||
| 76af340433 | |||
| 24fdcd0355 | |||
| 3f6f35bd95 | |||
| b41288c63d | |||
| 75b13046cf | |||
| 2433c8521e | |||
| 613e65ecba | |||
| 6a7d9a7c18 | |||
| 6e52f81840 | |||
| 7984932307 | |||
| 871fdc3a01 | |||
| 807e734fa4 | |||
| b59ebe074d | |||
| 81a3b14999 | |||
| a1a3c079bb | |||
| 33d5c91227 | |||
| d43774c664 | |||
| 6feb5bbe8c | |||
| ed15bdcc25 | |||
| 95bbf211ba | |||
| bb2138b060 | |||
| debe1c5129 | |||
| 157f0c37f8 | |||
| cac5ee612c | |||
| a931f2f964 | |||
| 6450379a1c | |||
| 24ece43700 | |||
| ff59738246 | |||
| b3de3b7643 | |||
| 1523350501 | |||
| 1a5fd99d7e | |||
| 55ee8f687b | |||
| 265adb1a5d | |||
| 235c561bb5 | |||
| 1753bd566e | |||
| 5e8c73a0a2 | |||
| 5752b358f2 | |||
| 10d4785659 | |||
| 9beaee7161 | |||
| c79e052c17 | |||
| 5b88fda464 | |||
| 52cd83bac3 | |||
| a47adb2114 | |||
| e0001ffa5b | |||
| 58734899d7 | |||
| 60b0f43838 | |||
| 4bed0e3ad1 | |||
| 0e27cbfd4f | |||
| 94c29cf0af | |||
| 7d1d60b422 | |||
| 7fca86e27a | |||
| faeaf2816f | |||
| 9943279361 | |||
| 58b2d42596 | |||
| c42df2e53e | |||
| 3607a88f0a | |||
| 8625365cf0 | |||
| 941354c740 | |||
| 9ba97a7cd7 | |||
| f9bf42f610 | |||
| ce4a375780 | |||
| f0675c88df | |||
| 0d0dd7292a | |||
| 735f50875e | |||
| f0fd1a654e | |||
| 82532a3ddf | |||
| 7075ca34d4 | |||
| 8c9625f578 | |||
| 7c4064aa5f | |||
| 6d82284545 | |||
| fe512222e5 | |||
| a18cfd9af1 | |||
| 7469a48449 | |||
| 9c8bac7a99 | |||
| 9baaa93159 | |||
| 9b92c42947 | |||
| 0a77aaa385 | |||
| a54e70e48f | |||
| d3e41f3ce2 | |||
| a9b4aa7640 | |||
| 95ff26940e | |||
| e5f87bcbc7 | |||
| 210636e205 | |||
| 028c8c6e42 | |||
| 73f5691651 | |||
| ddc2c1e574 | |||
| fcbcf380ae | |||
| eccda61b55 | |||
| 0f8b52d1a0 | |||
| 35373a5b1a | |||
| 094d0e84a2 | |||
| a5beedf4f6 | |||
| a77f0bf728 | |||
| 8a75b638b7 | |||
| 0815a834d8 | |||
| 93c9864f17 | |||
| 2444667a70 | |||
| 0ec956c653 | |||
| ccd1c44315 | |||
| bed85c7e68 | |||
| 4e40b2bc09 | |||
| 6fbe9c7de8 | |||
| 5b70a57700 | |||
| 1ce42b6b93 | |||
| e5b79ea847 | |||
| 70168cde07 | |||
| 7dad3d5018 | |||
| 87fbaf51f9 | |||
| 5cf401cde7 | |||
| 136aea0c15 | |||
| 8e43531e10 | |||
| 7ac77ba24b | |||
| 1406c8f2ea | |||
| c0715fd490 | |||
| 267e0b1fa9 | |||
| dcf56c5a8e | |||
| bf23ca38ba | |||
| b851be6a06 | |||
| 84b3176ac3 | |||
| b9f8325534 | |||
| 9fa902ad6d | |||
| 68888fc0b7 | |||
| 136e199225 | |||
| 295e51309f | |||
| 5f09352a24 | |||
| 94faff843f | |||
| 2efce629f7 | |||
| 6964c7122b | |||
| 6b97779ac8 | |||
| 5d9c64a140 | |||
| 5ca046fec2 | |||
| 5657577938 | |||
| 224fec0b79 | |||
| ae2b67ef69 | |||
| 3b8ad5e55c | |||
| abd2c586db | |||
| 0a8b508b4c | |||
| df97f36315 | |||
| cc3ce8ae6b | |||
| ba2f47aa67 | |||
| e516682744 | |||
| f43f4285a8 | |||
| 99c756aa90 | |||
| a8a2d69a5f | |||
| f7ac889bb4 | |||
| a326e73e4a | |||
| 2796103a2b | |||
| 14c4ac55f1 | |||
| 831b9d94f8 | |||
| 5fa793ece7 | |||
| 4065e6f7e8 | |||
| 7ff00dfbb0 | |||
| 84909e2e77 | |||
| 9d75cdf0f7 | |||
| c0c842649f | |||
| e8d117d62f | |||
| 87ae0dc870 | |||
| e26ee1a113 | |||
| 9d31a525c9 | |||
| ffb08f9f8d | |||
| 294dec007a | |||
| 93a19422e8 | |||
| c44adab86d | |||
| 7bb5cbe98e | |||
| 0b0db9f864 | |||
| 64ddaf2ff5 | |||
| a901e88aaa | |||
| fe03259704 | |||
| cf209a157b | |||
| 0f547ae354 | |||
| 2ebfbe837d | |||
| af87a00eaa | |||
| 09543636c7 | |||
| f5a56a5048 | |||
| 16185bd88c | |||
| 47479a9fd5 | |||
| fce31b0c32 | |||
| d44c68a28b | |||
| 5f793943f2 | |||
| 3d81f84c0f | |||
| 9f59447a81 | |||
| 67c9b3dd23 | |||
| f1028b55a2 | |||
| 4cb11122dc | |||
| 58438bf56e | |||
| e93ba3d37f | |||
| 9566e60037 | |||
| b0b06635ac | |||
| 6dfacb7d2c | |||
| f1774665df | |||
| 45c8439446 | |||
| 1d0fded682 | |||
| 31a81c5476 | |||
| 229d3b1a2f | |||
| 796ced3508 | |||
| c91137cc03 | |||
| d061420750 | |||
| 50ef87d41f | |||
| c4952dd28f | |||
| 6c5c5c2e34 | |||
| 1520e010e7 | |||
| 30bd8b5c67 | |||
| 8cc54c60de | |||
| a4a5e22b50 | |||
| 4529585acb | |||
| d1bc5f8002 | |||
| ab68ee508e | |||
| db7eb35e34 | |||
| 216ab79521 | |||
| 2a74c39642 | |||
| eeaa53c118 | |||
| effff44843 | |||
| 99ea16e251 | |||
| 574a89ad93 |
+12
-8
@@ -15,8 +15,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libstdc++")
|
||||
elseif(APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
endif()
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR} libs)
|
||||
@@ -27,32 +25,39 @@ set(engine_SRCS
|
||||
src/bridge.cpp
|
||||
src/comb.cpp
|
||||
src/commandSocket.cpp
|
||||
src/FffGcodeWriter.cpp
|
||||
src/FffPolygonGenerator.cpp
|
||||
src/FffProcessor.cpp
|
||||
src/gcodeExport.cpp
|
||||
src/gcodePlanner.cpp
|
||||
src/infill.cpp
|
||||
src/inset.cpp
|
||||
src/layerPart.cpp
|
||||
src/main.cpp
|
||||
src/MergeInfillLines.cpp
|
||||
src/mesh.cpp
|
||||
src/MeshGroup.cpp
|
||||
src/multiVolumes.cpp
|
||||
src/pathOrderOptimizer.cpp
|
||||
src/polygonOptimizer.cpp
|
||||
src/PrimeTower.cpp
|
||||
src/Progress.cpp
|
||||
src/raft.cpp
|
||||
src/settingRegistry.cpp
|
||||
src/settings.cpp
|
||||
src/skin.cpp
|
||||
src/skirt.cpp
|
||||
src/sliceDataStorage.cpp
|
||||
src/slicer.cpp
|
||||
src/support.cpp
|
||||
src/timeEstimate.cpp
|
||||
src/wallOverlap.cpp
|
||||
src/Weaver.cpp
|
||||
src/Wireframe2gcode.cpp
|
||||
|
||||
src/modelFile/modelFile.cpp
|
||||
|
||||
src/utils/gettime.cpp
|
||||
src/utils/logoutput.cpp
|
||||
src/utils/polygonUtils.cpp
|
||||
src/utils/polygon.cpp
|
||||
)
|
||||
|
||||
protobuf_generate_cpp(engine_PB_SRCS engine_PB_HEADERS Cura.proto)
|
||||
@@ -60,12 +65,11 @@ protobuf_generate_cpp(engine_PB_SRCS engine_PB_HEADERS Cura.proto)
|
||||
add_executable(CuraEngine ${engine_SRCS} ${engine_PB_SRCS})
|
||||
target_link_libraries(CuraEngine clipper Arcus)
|
||||
|
||||
add_executable(Test src/test.cpp)
|
||||
target_link_libraries(Test clipper)
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(CuraEngine pthread)
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS CuraEngine DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
include(CPackConfig.cmake)
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
set(CPACK_PACKAGE_VENDOR "Ultimaker")
|
||||
set(CPACK_PACKAGE_CONTACT "Arjen Hiemstra <a.hiemstra@ultimaker.com>")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cura Engine")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR 15)
|
||||
set(CPACK_PACKAGE_VERSION_MINOR 05)
|
||||
set(CPACK_PACKAGE_VERSION_PATCH 90)
|
||||
set(CPACK_GENERATOR "DEB;RPM")
|
||||
|
||||
set(RPM_REQUIRES
|
||||
"arcus >= 15.05.90"
|
||||
"protobuf >= 3.0.0"
|
||||
"libstdc++6 >= 4.9.0"
|
||||
"libgcc1 >= 4.9.0"
|
||||
)
|
||||
string(REPLACE ";" "," RPM_REQUIRES "${RPM_REQUIRES}")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES ${RPM_REQUIRES})
|
||||
|
||||
set(DEB_DEPENDS
|
||||
"arcus (>= 15.05.90)"
|
||||
"protobuf (>= 3.0.0)"
|
||||
"libstdc++6 (>= 4.9.0)"
|
||||
"libgcc1 (>= 4.9.0)"
|
||||
)
|
||||
string(REPLACE ";" ", " DEB_DEPENDS "${DEB_DEPENDS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS ${DEB_DEPENDS})
|
||||
|
||||
include(CPack)
|
||||
@@ -0,0 +1,14 @@
|
||||
Changelog CuraEngine
|
||||
====================
|
||||
|
||||
|
||||
|
||||
|
||||
- Feature: infill_wipe_dist. Add a travel move after an infill line in order to let it stick better to the walls.
|
||||
- Feature: Draft Protection Screen. A shell similar to the ooze shield providing protection from gusts of wind and acting similar to a heated chamber
|
||||
|
||||
|
||||
Release 15.06.01
|
||||
-----
|
||||
|
||||
- [Not documented]
|
||||
+19
-7
@@ -1,13 +1,22 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package Cura;
|
||||
package cura.proto;
|
||||
|
||||
// typeid 1
|
||||
message ObjectList {
|
||||
|
||||
message ObjectList
|
||||
{
|
||||
repeated Object objects = 1;
|
||||
repeated Setting settings = 2;
|
||||
}
|
||||
|
||||
message Object {
|
||||
// typeid 1
|
||||
message Slice
|
||||
{
|
||||
repeated ObjectList object_lists = 1;
|
||||
}
|
||||
|
||||
message Object
|
||||
{
|
||||
int64 id = 1;
|
||||
bytes vertices = 2; //An array of 3 floats.
|
||||
bytes normals = 3; //An array of 3 floats.
|
||||
@@ -16,16 +25,19 @@ message Object {
|
||||
}
|
||||
|
||||
// typeid 3
|
||||
message Progress {
|
||||
message Progress
|
||||
{
|
||||
float amount = 1;
|
||||
}
|
||||
|
||||
// typeid 2
|
||||
message SlicedObjectList {
|
||||
message SlicedObjectList
|
||||
{
|
||||
repeated SlicedObject objects = 1;
|
||||
}
|
||||
|
||||
message SlicedObject {
|
||||
message SlicedObject
|
||||
{
|
||||
int64 id = 1;
|
||||
|
||||
repeated Layer layers = 2;
|
||||
|
||||
+3
-2
@@ -58,7 +58,8 @@ PROJECT_LOGO =
|
||||
# entered, it will be relative to the location where doxygen was started. If
|
||||
# left blank the current directory will be used.
|
||||
|
||||
OUTPUT_DIRECTORY = documentation
|
||||
#LEAVE THIS DIRECTORY EMPTY IF THIS DOCUMENTATION NEEDS TO BE PUBLISHED AUTOMATICALLY TO GITHUB PAGES!
|
||||
OUTPUT_DIRECTORY = docs
|
||||
|
||||
# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
|
||||
# directories (in 2 levels) under the output directory of each output format and
|
||||
@@ -2049,7 +2050,7 @@ HIDE_UNDOC_RELATIONS = YES
|
||||
# set to NO
|
||||
# The default value is: NO.
|
||||
|
||||
HAVE_DOT = NO
|
||||
HAVE_DOT = YES
|
||||
|
||||
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
|
||||
# to run in parallel. When set to 0 doxygen will base this on the number of
|
||||
|
||||
+9
-8
@@ -3,9 +3,9 @@ CuraEngine
|
||||
The CuraEngine is a C++ console application for 3D printing GCode generation. It has been made as a better and faster alternative to the old Skeinforge engine.
|
||||
|
||||
The CuraEngine is pure C++ and uses Clipper from http://www.angusj.com/delphi/clipper.php
|
||||
There are no external dependences and Clipper is included in the source code without modifications.
|
||||
Furthermore it depends on libArcus by Ultimaker, which can be found at http://github.com/Ultimaker/libArcus
|
||||
|
||||
This is just a console application for GCode generation. For a full graphical application look at https://github.com/daid/Cura which is the graphical frontend for CuraEngine.
|
||||
This is just a console application for GCode generation. For a full graphical application look at https://github.com/Ultimaker/Cura which is the graphical frontend for CuraEngine.
|
||||
|
||||
The CuraEngine can be used seperately or in other applications. Feel free to add it to your application. But please take note of the License.
|
||||
|
||||
@@ -24,6 +24,7 @@ How to Install
|
||||
|
||||
In order to compile CuraEngine, either use CMake or start a project in your preferred IDE.
|
||||
CMake compilation:
|
||||
|
||||
1. Navigate to the CuraEngine directory and execute the following commands
|
||||
2. $ mkdir build && cd build
|
||||
3. $ cmake ..
|
||||
@@ -48,16 +49,16 @@ Installing Protobuf
|
||||
|
||||
Running
|
||||
=======
|
||||
Other than running CuraEngine from a frontend, such as PluggableCura, one can run CuraEngine from the command line.
|
||||
Other than running CuraEngine from a frontend, such as Ultimaker/Cura, one can run CuraEngine from the command line.
|
||||
For that one needs a settings JSON file, which can be found in the Ultimaker/Cura repository.
|
||||
An example run for an UM2 machine looks as follows:
|
||||
* Navigate to the CuraEngine directory and execute the following
|
||||
```
|
||||
./build/CuraEngine -v -j fdmprinter.json -s machine_start_gcode=";FLAVOR:UltiGCode
|
||||
;TIME:10000000
|
||||
;MATERIAL:2000
|
||||
;MATERIAL2:0" -s mesh_position_x=115.0 -s mesh_position_y=112.5 -s mesh_position_z=0 -s material_diameter=1.128 -o "output/test.gcode" "/path/to/model.stl"
|
||||
./build/CuraEngine slice -v -j ../Cura/resources/machines/dual_extrusion_printer.json -o "output/test.gcode" -e1 -s infill_line_distance=0 -e0 -l "/model_1.stl" -e1 -l "fully_filled_model.stl"
|
||||
```
|
||||
|
||||
Run `CuraEngine help` for a general description of how to use the CuraEngine tool.
|
||||
|
||||
Internals
|
||||
=========
|
||||
|
||||
@@ -119,4 +120,4 @@ The GCode generation is quite a large bit of code. As a lot is going on here. Im
|
||||
* PathOrderOptimizer: This piece of code needs to solve a TravelingSalesmanProblem. Given a list of polygons/lines it tries to find the best order in which to print them. It currently does this by finding the closest next polygon to print.
|
||||
* Infill: This code generates a group of lines from an area. This is the code that generates the actuall infill pattern. There is also a concentric infill function, which is currently not used.
|
||||
* Comb: The combing code is the code that tries to avoid holes when moving the head around without printing. This code also detects when it fails. The final GCode generator uses the combing code while generating the final GCode. So they interact closely.
|
||||
* GCodeExport: The GCode export is a 2 step process. First it collects all the paths for a layer that it needs to print, this includes all moves, prints, extrusion widths. And then it generates the final GCode. This is the only piece of code that has knowledge about GCode keywords and syntax;meshmdhfdhfdhf to generate a different flavor of GCode it will be the only piece that needs adjustment. All volumatric calculations also happen here.
|
||||
* GCodeExport: The GCode export is a 2 step process. First it collects all the paths for a layer that it needs to print, this includes all moves, prints, extrusion widths. And then it generates the final GCode. This is the only piece of code that has knowledge about GCode keywords and syntax to generate a different flavor of GCode it will be the only piece that needs adjustment. All volumatric calculations also happen here.
|
||||
|
||||
@@ -112,7 +112,7 @@ enum class EnumExample
|
||||
{
|
||||
ELEM0 = 0,
|
||||
ELEM1 = 1
|
||||
}
|
||||
};
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Illegal syntax
|
||||
|
||||
@@ -4,6 +4,7 @@ Glossary
|
||||
|
||||
Term/Synonyms | Meaning
|
||||
--- | ---
|
||||
Extruder Train | The whole of a feeder, bowden tube and a nozzle
|
||||
Island/Part | isolated/unconnected part in 2D slice
|
||||
Inset | perimeter, the perimeters which are laid down around the infill
|
||||
Slicing | The act of extracting the contours of the object at a certain height (not the whole process which would also include gcode generation etc.)
|
||||
|
||||
-1325
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -0,0 +1,57 @@
|
||||
Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
|
||||
Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
|
||||
|
||||
If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License.
|
||||
If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON.
|
||||
A copy of the MIT License is included in this file.
|
||||
|
||||
Other dependencies and licenses:
|
||||
|
||||
Open Source Software Licensed Under the BSD License:
|
||||
--------------------------------------------------------------------
|
||||
|
||||
The msinttypes r29
|
||||
Copyright (c) 2006-2013 Alexander Chemeris
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Open Source Software Licensed Under the JSON License:
|
||||
--------------------------------------------------------------------
|
||||
|
||||
json.org
|
||||
Copyright (c) 2002 JSON.org
|
||||
All Rights Reserved.
|
||||
|
||||
JSON_checker
|
||||
Copyright (c) 2002 JSON.org
|
||||
All Rights Reserved.
|
||||
|
||||
|
||||
Terms of the JSON License:
|
||||
---------------------------------------------------
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
The Software shall be used for Good, not Evil.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Terms of the MIT License:
|
||||
--------------------------------------------------------------------
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,130 @@
|
||||
[ problem ]
|
||||
gantry_height introduced by Jaime while there already was machine_nozzle_gantry_distance
|
||||
|
||||
|
||||
[ RENAMES ]
|
||||
raft_base_linewidth > raft_base_line_width
|
||||
raft_interface_linewidth > raft_interface_line_width
|
||||
fill_overlap > infill_overlap
|
||||
fill_pattern > infill_pattern
|
||||
fill_sparse_combine > infill_sparse_combine
|
||||
fill_sparse_density > infill_sparse_density
|
||||
fill_sparse_thickness > infill_sparse_thickness
|
||||
support_fill_rate > support_infill_rate
|
||||
|
||||
|
||||
[ SPLITS ]
|
||||
raft_line_spacing > raft_base_line_spacing (, raft_interface_line_spacing, raft_surface_line_spacing)
|
||||
wall_overlap_avoid_enabled > remove_overlapping_walls_enabled (, remove_overlapping_walls_0_enabled, remove_overlapping_walls_x_enabled)
|
||||
|
||||
retraction_minimal_extrusion > retraction_extrusion_window (+ retraction_count_max = 1)
|
||||
|
||||
magic_mesh_surface_mode : false >> "Normal"
|
||||
magic_mesh_surface_mode : true >> "Surface"
|
||||
|
||||
|
||||
[ NEW ]
|
||||
alternate_extra_perimeter
|
||||
|
||||
coasting_enable
|
||||
coasting_min_volume
|
||||
coasting_min_volume_move
|
||||
coasting_min_volume_retract
|
||||
coasting_speed
|
||||
coasting_speed_move
|
||||
coasting_speed_retract
|
||||
coasting_volume
|
||||
coasting_volume_move
|
||||
coasting_volume_retract
|
||||
|
||||
fill_perimeter_gaps
|
||||
|
||||
draft_shield_dist
|
||||
draft_shield_enabled
|
||||
draft_shield_height
|
||||
draft_shield_height_limitation
|
||||
|
||||
infill_wipe_dist
|
||||
|
||||
line_width (was wall_line_width)
|
||||
|
||||
machine_extruder_count
|
||||
|
||||
machine_head_polygon
|
||||
machine_head_with_fans_polygon
|
||||
machine_heat_zone_length
|
||||
|
||||
magic_mesh_surface_mode
|
||||
|
||||
meshfix_extensive_stitching
|
||||
meshfix_keep_open_polygons
|
||||
meshfix_union_all
|
||||
meshfix_union_all_remove_holes
|
||||
|
||||
print_sequence
|
||||
|
||||
raft_base_line_spacing (from raft_line_spacing)
|
||||
raft_base_line_width
|
||||
raft_fan_speed
|
||||
raft_interface_fan_speed
|
||||
raft_interface_line_spacing
|
||||
raft_interface_speed
|
||||
raft_speed
|
||||
raft_surface_fan_speed
|
||||
raft_surface_line_spacing
|
||||
raft_surface_line_width
|
||||
raft_surface_speed
|
||||
raft_surface_thickness
|
||||
remove_overlapping_walls_0_enabled
|
||||
remove_overlapping_walls_enabled
|
||||
remove_overlapping_walls_x_enabled
|
||||
|
||||
retraction_count_max
|
||||
retraction_extrusion_window (from retraction_minimal_extrusion)
|
||||
retraction_extra_prime_amount
|
||||
|
||||
skin_alternate_rotation
|
||||
|
||||
speed_support_lines
|
||||
speed_support_roof
|
||||
|
||||
support_conical_angle
|
||||
support_conical_enabled
|
||||
support_conical_min_width
|
||||
support_offset
|
||||
support_roof_enable
|
||||
support_roof_height
|
||||
support_roof_line_width
|
||||
|
||||
travel_avoid_distance
|
||||
travel_avoid_other_parts
|
||||
travel_compensate_overlapping_walls_enabled
|
||||
|
||||
z_seam_type
|
||||
|
||||
|
||||
[ DUAL EXTRUSION ]
|
||||
extruder_nr
|
||||
machine_use_extruder_offset_to_offset_coords
|
||||
machine_nozzle_offset_x
|
||||
machine_nozzle_offset_y
|
||||
machine_extruder_start_code
|
||||
machine_extruder_start_pos_abs
|
||||
machine_extruder_start_pos_x
|
||||
machine_extruder_start_pos_y
|
||||
machine_extruder_end_pos_abs
|
||||
machine_extruder_end_pos_x
|
||||
machine_extruder_end_pos_y
|
||||
machine_extruder_end_code
|
||||
prime_tower_enable
|
||||
prime_tower_size
|
||||
prime_tower_position_x
|
||||
prime_tower_position_y
|
||||
prime_tower_flow
|
||||
prime_tower_wipe_enabled
|
||||
ooze_shield_enabled
|
||||
ooze_shield_angle
|
||||
ooze_shield_dist
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef EXTRUDER_TRAIN_H
|
||||
#define EXTRUDER_TRAIN_H
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class ExtruderTrain : public SettingsBase
|
||||
{
|
||||
int extruder_nr;
|
||||
public:
|
||||
int getExtruderNr() { return extruder_nr; }
|
||||
|
||||
ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr)
|
||||
: SettingsBase(settings)
|
||||
, extruder_nr(extruder_nr)
|
||||
{ }
|
||||
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif // EXTRUDER_TRAIN_H
|
||||
@@ -0,0 +1,938 @@
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "FffGcodeWriter.h"
|
||||
#include "FffProcessor.h"
|
||||
#include "Progress.h"
|
||||
#include "wallOverlap.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keeper)
|
||||
{
|
||||
PrimeTower primetower();
|
||||
|
||||
gcode.preSetup(storage.meshgroup);
|
||||
|
||||
gcode.resetTotalPrintTimeAndFilament();
|
||||
|
||||
if (command_socket)
|
||||
command_socket->beginGCode();
|
||||
|
||||
setConfigCoasting(storage);
|
||||
|
||||
setConfigRetraction(storage);
|
||||
|
||||
if (meshgroup_number == 1)
|
||||
{
|
||||
processStartingCode(storage);
|
||||
}
|
||||
else
|
||||
{
|
||||
processNextMeshGroupCode(storage);
|
||||
}
|
||||
meshgroup_number++;
|
||||
|
||||
unsigned int total_layers = storage.meshes[0].layers.size();
|
||||
//gcode.writeComment("Layer count: %d", totalLayers);
|
||||
|
||||
bool has_raft = getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT;
|
||||
if (has_raft)
|
||||
{
|
||||
processRaft(storage, total_layers);
|
||||
}
|
||||
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
last_prime_tower_poly_printed[extruder] = -1; // layer 0 has its prime tower printed during the brim (?)
|
||||
|
||||
for(unsigned int layer_nr=0; layer_nr<total_layers; layer_nr++)
|
||||
{
|
||||
processLayer(storage, layer_nr, total_layers, has_raft);
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::FINISH, &time_keeper, command_socket);
|
||||
|
||||
gcode.writeFanCommand(0);
|
||||
|
||||
//Store the object height for when we are printing multiple objects, as we need to clear every one of them when moving to the next position.
|
||||
max_object_height = std::max(max_object_height, storage.model_max.z);
|
||||
|
||||
if (command_socket)
|
||||
{
|
||||
finalize();
|
||||
command_socket->sendGCodeLayer();
|
||||
command_socket->endSendSlicedObject();
|
||||
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
|
||||
{
|
||||
std::ostringstream prefix;
|
||||
prefix << ";FLAVOR:UltiGCode\n";
|
||||
prefix << ";TIME:" << int(gcode.getTotalPrintTime()) << "\n";
|
||||
prefix << ";MATERIAL:" << int(gcode.getTotalFilamentUsed(0)) << "\n";
|
||||
prefix << ";MATERIAL2:" << int(gcode.getTotalFilamentUsed(1)) << "\n";
|
||||
command_socket->sendGCodePrefix(prefix.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FffGcodeWriter::setConfigCoasting(SliceDataStorage& storage)
|
||||
{
|
||||
for (int extr = 0; extr < storage.meshgroup->getExtruderCount(); extr++)
|
||||
{
|
||||
storage.coasting_config.emplace_back();
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extr);
|
||||
CoastingConfig& coasting_config = storage.coasting_config.back();
|
||||
coasting_config.coasting_enable = train->getSettingBoolean("coasting_enable");
|
||||
coasting_config.coasting_volume_move = train->getSettingInCubicMillimeters("coasting_volume_move");
|
||||
coasting_config.coasting_min_volume_move = train->getSettingInCubicMillimeters("coasting_min_volume_move");
|
||||
coasting_config.coasting_speed_move = train->getSettingInPercentage("coasting_speed_move");
|
||||
coasting_config.coasting_volume_retract = train->getSettingInCubicMillimeters("coasting_volume_retract");
|
||||
coasting_config.coasting_min_volume_retract = train->getSettingInCubicMillimeters("coasting_min_volume_retract");
|
||||
coasting_config.coasting_speed_retract = train->getSettingInPercentage("coasting_speed_retract");
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setConfigRetraction(SliceDataStorage& storage)
|
||||
{
|
||||
storage.retraction_config.amount = (storage.getSettingBoolean("retraction_enable"))? INT2MM(getSettingInMicrons("retraction_amount")) : 0;
|
||||
storage.retraction_config.primeAmount = INT2MM(getSettingInMicrons("retraction_extra_prime_amount"));
|
||||
storage.retraction_config.speed = getSettingInMillimetersPerSecond("retraction_retract_speed");
|
||||
storage.retraction_config.primeSpeed = getSettingInMillimetersPerSecond("retraction_prime_speed");
|
||||
storage.retraction_config.zHop = getSettingInMicrons("retraction_hop");
|
||||
storage.retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
|
||||
storage.retraction_config.retraction_extrusion_window = getSettingInMicrons("retraction_extrusion_window");
|
||||
storage.retraction_config.retraction_count_max = getSettingInMicrons("retraction_count_max");
|
||||
|
||||
int extruder_count = storage.meshgroup->getExtruderCount();
|
||||
for (int extruder = 0; extruder < extruder_count; extruder++)
|
||||
{
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
storage.retraction_config_per_extruder[extruder].amount = (train->getSettingBoolean("retraction_enable"))? INT2MM(train->getSettingInMicrons("retraction_amount")) : 0;
|
||||
storage.retraction_config_per_extruder[extruder].primeAmount = INT2MM(train->getSettingInMicrons("retraction_extra_prime_amount"));
|
||||
storage.retraction_config_per_extruder[extruder].speed = train->getSettingInMillimetersPerSecond("retraction_retract_speed");
|
||||
storage.retraction_config_per_extruder[extruder].primeSpeed = train->getSettingInMillimetersPerSecond("retraction_prime_speed");
|
||||
storage.retraction_config_per_extruder[extruder].zHop = train->getSettingInMicrons("retraction_hop");
|
||||
storage.retraction_config_per_extruder[extruder].retraction_min_travel_distance = train->getSettingInMicrons("retraction_min_travel");
|
||||
storage.retraction_config_per_extruder[extruder].retraction_extrusion_window = train->getSettingInMicrons("retraction_extrusion_window");
|
||||
storage.retraction_config_per_extruder[extruder].retraction_count_max = train->getSettingInMicrons("retraction_count_max");
|
||||
}
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
mesh.retraction_config.amount = (mesh.getSettingBoolean("retraction_enable"))? INT2MM(mesh.getSettingInMicrons("retraction_amount")) : 0;
|
||||
mesh.retraction_config.primeAmount = INT2MM(mesh.getSettingInMicrons("retraction_extra_prime_amount"));
|
||||
mesh.retraction_config.speed = mesh.getSettingInMillimetersPerSecond("retraction_retract_speed");
|
||||
mesh.retraction_config.primeSpeed = mesh.getSettingInMillimetersPerSecond("retraction_prime_speed");
|
||||
mesh.retraction_config.zHop = mesh.getSettingInMicrons("retraction_hop");
|
||||
mesh.retraction_config.retraction_min_travel_distance = mesh.getSettingInMicrons("retraction_min_travel");
|
||||
mesh.retraction_config.retraction_extrusion_window = mesh.getSettingInMicrons("retraction_extrusion_window");
|
||||
mesh.retraction_config.retraction_count_max = mesh.getSettingInMicrons("retraction_count_max");
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setConfigSkirt(SliceDataStorage& storage, int layer_thickness)
|
||||
{
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
storage.skirt_config[extruder].setSpeed(train->getSettingInMillimetersPerSecond("skirt_speed"));
|
||||
storage.skirt_config[extruder].setLineWidth(train->getSettingInMicrons("skirt_line_width"));
|
||||
storage.skirt_config[extruder].setFlow(train->getSettingInPercentage("material_flow"));
|
||||
storage.skirt_config[extruder].setLayerHeight(layer_thickness);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setConfigSupport(SliceDataStorage& storage, int layer_thickness)
|
||||
{
|
||||
storage.support_config.setLineWidth(getSettingInMicrons("support_line_width"));
|
||||
storage.support_config.setSpeed(getSettingInMillimetersPerSecond("speed_support_lines"));
|
||||
storage.support_config.setFlow(storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_extruder_nr"))->getSettingInPercentage("material_flow"));
|
||||
storage.support_config.setLayerHeight(layer_thickness);
|
||||
|
||||
storage.support_roof_config.setLineWidth(getSettingInMicrons("support_roof_line_width"));
|
||||
storage.support_roof_config.setSpeed(getSettingInMillimetersPerSecond("speed_support_roof"));
|
||||
storage.support_roof_config.setFlow(storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_roof_extruder_nr"))->getSettingInPercentage("material_flow"));
|
||||
storage.support_roof_config.setLayerHeight(layer_thickness);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setConfigInsets(SliceMeshStorage& mesh, int layer_thickness)
|
||||
{
|
||||
mesh.inset0_config.setLineWidth(mesh.getSettingInMicrons("wall_line_width_0"));
|
||||
mesh.inset0_config.setSpeed(mesh.getSettingInMillimetersPerSecond("speed_wall_0"));
|
||||
mesh.inset0_config.setFlow(mesh.getSettingInPercentage("material_flow"));
|
||||
mesh.inset0_config.setLayerHeight(layer_thickness);
|
||||
|
||||
mesh.insetX_config.setLineWidth(mesh.getSettingInMicrons("wall_line_width_x"));
|
||||
mesh.insetX_config.setSpeed(mesh.getSettingInMillimetersPerSecond("speed_wall_x"));
|
||||
mesh.insetX_config.setFlow(mesh.getSettingInPercentage("material_flow"));
|
||||
mesh.insetX_config.setLayerHeight(layer_thickness);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setConfigSkin(SliceMeshStorage& mesh, int layer_thickness)
|
||||
{
|
||||
mesh.skin_config.setLineWidth(mesh.getSettingInMicrons("skin_line_width"));
|
||||
mesh.skin_config.setSpeed(mesh.getSettingInMillimetersPerSecond("speed_topbottom"));
|
||||
mesh.skin_config.setFlow(mesh.getSettingInPercentage("material_flow"));
|
||||
mesh.skin_config.setLayerHeight(layer_thickness);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setConfigInfill(SliceMeshStorage& mesh, int layer_thickness)
|
||||
{
|
||||
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
|
||||
{
|
||||
mesh.infill_config[idx].setLineWidth(mesh.getSettingInMicrons("infill_line_width") * (idx + 1));
|
||||
mesh.infill_config[idx].setSpeed(mesh.getSettingInMillimetersPerSecond("speed_infill"));
|
||||
mesh.infill_config[idx].setFlow(mesh.getSettingInPercentage("material_flow"));
|
||||
mesh.infill_config[idx].setLayerHeight(layer_thickness);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
{
|
||||
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
|
||||
{
|
||||
if (!command_socket)
|
||||
{
|
||||
gcode.writeCode(";FLAVOR:UltiGCode\n;TIME:666\n;MATERIAL:666\n;MATERIAL2:-1\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getSettingBoolean("machine_heated_bed") && getSettingInDegreeCelsius("material_bed_temperature") > 0)
|
||||
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature"), true);
|
||||
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
if (mesh.getSettingInDegreeCelsius("material_print_temperature") > 0)
|
||||
gcode.writeTemperatureCommand(mesh.getSettingAsIndex("extruder_nr"), mesh.getSettingInDegreeCelsius("material_print_temperature"));
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
if (mesh.getSettingInDegreeCelsius("material_print_temperature") > 0)
|
||||
gcode.writeTemperatureCommand(mesh.getSettingAsIndex("extruder_nr"), mesh.getSettingInDegreeCelsius("material_print_temperature"), true);
|
||||
}
|
||||
|
||||
gcode.writeCode(getSettingString("machine_start_gcode").c_str());
|
||||
|
||||
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
|
||||
if (gcode.getFlavor() == EGCodeFlavor::BFB)
|
||||
{
|
||||
gcode.writeComment("enable auto-retraction");
|
||||
std::ostringstream tmp;
|
||||
tmp << "M227 S" << (getSettingInMicrons("retraction_amount") * 2560 / 1000) << " P" << (getSettingInMicrons("retraction_amount") * 2560 / 1000);
|
||||
gcode.writeLine(tmp.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processNextMeshGroupCode(SliceDataStorage& storage)
|
||||
{
|
||||
gcode.writeFanCommand(0);
|
||||
gcode.resetExtrusionValue();
|
||||
gcode.setZ(max_object_height + 5000);
|
||||
gcode.writeMove(gcode.getPositionXY(), getSettingInMillimetersPerSecond("speed_travel"), 0);
|
||||
gcode.writeMove(Point(storage.model_min.x, storage.model_min.y), getSettingInMillimetersPerSecond("speed_travel"), 0);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int totalLayers)
|
||||
{
|
||||
int extruder_nr = getSettingAsIndex("adhesion_extruder_nr");
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
|
||||
GCodePathConfig raft_base_config(&storage.retraction_config_per_extruder[extruder_nr], "SUPPORT");
|
||||
raft_base_config.setSpeed(getSettingInMillimetersPerSecond("raft_base_speed"));
|
||||
raft_base_config.setLineWidth(getSettingInMicrons("raft_base_line_width"));
|
||||
raft_base_config.setLayerHeight(getSettingInMicrons("raft_base_thickness"));
|
||||
raft_base_config.setFlow(train->getSettingInPercentage("material_flow"));
|
||||
GCodePathConfig raft_interface_config(&storage.retraction_config_per_extruder[extruder_nr], "SUPPORT");
|
||||
raft_interface_config.setSpeed(getSettingInMillimetersPerSecond("raft_interface_speed"));
|
||||
raft_interface_config.setLineWidth(getSettingInMicrons("raft_interface_line_width"));
|
||||
raft_interface_config.setLayerHeight(getSettingInMicrons("raft_base_thickness"));
|
||||
raft_interface_config.setFlow(train->getSettingInPercentage("material_flow"));
|
||||
GCodePathConfig raft_surface_config(&storage.retraction_config_per_extruder[extruder_nr], "SUPPORT");
|
||||
raft_surface_config.setSpeed(getSettingInMillimetersPerSecond("raft_surface_speed"));
|
||||
raft_surface_config.setLineWidth(getSettingInMicrons("raft_surface_line_width"));
|
||||
raft_surface_config.setLayerHeight(getSettingInMicrons("raft_base_thickness"));
|
||||
raft_surface_config.setFlow(train->getSettingInPercentage("material_flow"));
|
||||
|
||||
bool retraction_combing = false; // the raft isn't added to the parts to avoid
|
||||
|
||||
{ // raft base layer
|
||||
gcode.writeLayerComment(-3);
|
||||
gcode.writeComment("RAFT");
|
||||
GCodePlanner gcode_layer(gcode, storage, &storage.retraction_config_per_extruder[extruder_nr], train->getSettingInMillimetersPerSecond("speed_travel"), retraction_combing, 0, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
gcode_layer.setCombing(false);
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") > 0)
|
||||
gcode_layer.setExtruder(extruder_nr);
|
||||
gcode.setZ(getSettingInMicrons("raft_base_thickness"));
|
||||
gcode_layer.addPolygonsByOptimizer(storage.raftOutline, &raft_base_config);
|
||||
|
||||
Polygons raftLines;
|
||||
int offset_from_poly_outline = 0;
|
||||
generateLineInfill(storage.raftOutline, offset_from_poly_outline, raftLines, train->getSettingInMicrons("raft_base_line_width"), train->getSettingInMicrons("raft_base_line_spacing"), train->getSettingInPercentage("infill_overlap"), 0);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &raft_base_config);
|
||||
|
||||
gcode.writeFanCommand(train->getSettingInPercentage("raft_base_fan_speed"));
|
||||
gcode_layer.writeGCode(false, train->getSettingInMicrons("raft_base_thickness"));
|
||||
}
|
||||
|
||||
{ // raft interface layer
|
||||
gcode.writeLayerComment(-2);
|
||||
gcode.writeComment("RAFT");
|
||||
GCodePlanner gcode_layer(gcode, storage, &storage.retraction_config_per_extruder[extruder_nr], train->getSettingInMillimetersPerSecond("speed_travel"), retraction_combing, 0, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
gcode_layer.setCombing(false);
|
||||
gcode.setZ(train->getSettingInMicrons("raft_base_thickness") + train->getSettingInMicrons("raft_interface_thickness"));
|
||||
|
||||
Polygons raftLines;
|
||||
int offset_from_poly_outline = 0;
|
||||
generateLineInfill(storage.raftOutline, offset_from_poly_outline, raftLines, train->getSettingInMicrons("raft_interface_line_width"), train->getSettingInMicrons("raft_interface_line_spacing"), train->getSettingInPercentage("infill_overlap"), train->getSettingAsCount("raft_surface_layers") > 0 ? 45 : 90);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &raft_interface_config);
|
||||
|
||||
gcode_layer.writeGCode(false, train->getSettingInMicrons("raft_interface_thickness"));
|
||||
}
|
||||
|
||||
for (int raftSurfaceLayer=1; raftSurfaceLayer<=train->getSettingAsCount("raft_surface_layers"); raftSurfaceLayer++)
|
||||
{ // raft surface layers
|
||||
gcode.writeLayerComment(-1);
|
||||
gcode.writeComment("RAFT");
|
||||
GCodePlanner gcode_layer(gcode, storage, &storage.retraction_config_per_extruder[extruder_nr], train->getSettingInMillimetersPerSecond("speed_travel"), retraction_combing, 0, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
gcode_layer.setCombing(false);
|
||||
gcode.setZ(train->getSettingInMicrons("raft_base_thickness") + train->getSettingInMicrons("raft_interface_thickness") + train->getSettingInMicrons("raft_surface_thickness")*raftSurfaceLayer);
|
||||
|
||||
Polygons raft_lines;
|
||||
int offset_from_poly_outline = 0;
|
||||
generateLineInfill(storage.raftOutline, offset_from_poly_outline, raft_lines, train->getSettingInMicrons("raft_surface_line_width"), train->getSettingInMicrons("raft_surface_line_spacing"), train->getSettingInPercentage("infill_overlap"), 90 * raftSurfaceLayer);
|
||||
gcode_layer.addLinesByOptimizer(raft_lines, &raft_surface_config);
|
||||
|
||||
gcode_layer.writeGCode(false, train->getSettingInMicrons("raft_interface_thickness"));
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_nr, unsigned int total_layers, bool has_raft)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::EXPORT, layer_nr+1, total_layers, command_socket);
|
||||
|
||||
int layer_thickness = getSettingInMicrons("layer_height");
|
||||
if (layer_nr == 0 && !has_raft)
|
||||
{
|
||||
layer_thickness = getSettingInMicrons("layer_height_0");
|
||||
}
|
||||
|
||||
|
||||
|
||||
setConfigSkirt(storage, layer_thickness);
|
||||
|
||||
setConfigSupport(storage, layer_thickness);
|
||||
|
||||
storage.primeTower.setConfigs(storage.meshgroup, storage.retraction_config_per_extruder, layer_thickness);
|
||||
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
setConfigInsets(mesh, layer_thickness);
|
||||
setConfigSkin(mesh, layer_thickness);
|
||||
setConfigInfill(mesh, layer_thickness);
|
||||
}
|
||||
|
||||
processInitialLayersSpeedup(storage, layer_nr);
|
||||
|
||||
gcode.writeLayerComment(layer_nr);
|
||||
|
||||
int64_t comb_offset_from_outlines = storage.meshgroup->getExtruderTrain(gcode.getExtruderNr())->getSettingInMicrons("machine_nozzle_size") * 2; // TODO: only used when there is no second wall.
|
||||
GCodePlanner gcode_layer(gcode, storage, &storage.retraction_config, getSettingInMillimetersPerSecond("speed_travel"), getSettingBoolean("retraction_combing"), layer_nr, comb_offset_from_outlines, getSettingBoolean("travel_avoid_other_parts"), getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
int z = storage.meshes[0].layers[layer_nr].printZ;
|
||||
gcode.setZ(z);
|
||||
gcode.resetStartPosition();
|
||||
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
int start_extruder = 0; // TODO: make settable
|
||||
gcode_layer.setExtruder(start_extruder);
|
||||
processSkirt(storage, gcode_layer, start_extruder);
|
||||
}
|
||||
|
||||
int extruder_nr_before = gcode_layer.getExtruder();
|
||||
addSupportToGCode(storage, gcode_layer, layer_nr, extruder_nr_before, true);
|
||||
|
||||
processOozeShield(storage, gcode_layer, layer_nr);
|
||||
|
||||
processDraftShield(storage, gcode_layer, layer_nr);
|
||||
|
||||
//Figure out in which order to print the meshes, do this by looking at the current extruder and preferer the meshes that use that extruder.
|
||||
std::vector<unsigned int> mesh_order = calculateMeshOrder(storage, gcode_layer.getExtruder());
|
||||
gcode_layer.setCombing(true);
|
||||
for(unsigned int mesh_idx : mesh_order)
|
||||
{
|
||||
SliceMeshStorage* mesh = &storage.meshes[mesh_idx];
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
{
|
||||
gcode_layer.setCombing(false);
|
||||
addMeshLayerToGCode_meshSurfaceMode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.setCombing(true); // needed when the last mesh was spiralized
|
||||
addMeshLayerToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
gcode_layer.setCombing(false);
|
||||
|
||||
addSupportToGCode(storage, gcode_layer, layer_nr, extruder_nr_before, false);
|
||||
|
||||
{ // add prime tower if it hasn't already been added
|
||||
// print the prime tower if it hasn't been printed yet
|
||||
int prev_extruder = gcode_layer.getExtruder(); // most likely the same extruder as we are extruding with now
|
||||
addPrimeTower(storage, gcode_layer, layer_nr, prev_extruder);
|
||||
}
|
||||
processFanSpeedAndMinimalLayerTime(storage, gcode_layer, layer_nr);
|
||||
|
||||
gcode_layer.writeGCode(getSettingBoolean("cool_lift_head"), layer_nr > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
|
||||
if (command_socket)
|
||||
command_socket->sendGCodeLayer();
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processInitialLayersSpeedup(SliceDataStorage& storage, unsigned int layer_nr)
|
||||
{
|
||||
double initial_speedup_layers = getSettingAsCount("speed_slowdown_layers");
|
||||
if (static_cast<int>(layer_nr) < initial_speedup_layers)
|
||||
{
|
||||
double initial_layer_speed = getSettingInMillimetersPerSecond("speed_layer_0");
|
||||
storage.support_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
initial_layer_speed = mesh.getSettingInMillimetersPerSecond("speed_layer_0");
|
||||
mesh.inset0_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
mesh.insetX_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
mesh.skin_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
|
||||
{
|
||||
mesh.infill_config[idx].smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processSkirt(SliceDataStorage& storage, GCodePlanner& gcode_layer, unsigned int extruder_nr)
|
||||
{
|
||||
gcode_layer.setCombing(false);
|
||||
Polygons& skirt = storage.skirt[extruder_nr];
|
||||
if (skirt.size() > 0)
|
||||
{
|
||||
gcode_layer.addTravel(skirt[skirt.size()-1].closestPointTo(gcode.getPositionXY()));
|
||||
}
|
||||
gcode_layer.addPolygonsByOptimizer(skirt, &storage.skirt_config[extruder_nr]);
|
||||
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processOozeShield(SliceDataStorage& storage, GCodePlanner& gcode_layer, unsigned int layer_nr)
|
||||
{
|
||||
if (storage.oozeShield.size() > 0)
|
||||
{
|
||||
gcode_layer.setCombing(false);
|
||||
gcode_layer.addPolygonsByOptimizer(storage.oozeShield[layer_nr], &storage.skirt_config[0]); // TODO: skirt config idx should correspond to ooze shield extruder nr
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processDraftShield(SliceDataStorage& storage, GCodePlanner& gcode_layer, unsigned int layer_nr)
|
||||
{
|
||||
if (storage.draft_protection_shield.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int draft_shield_height = getSettingInMicrons("draft_shield_height");
|
||||
int layer_height_0 = getSettingInMicrons("layer_height_0");
|
||||
int layer_height = getSettingInMicrons("layer_height");
|
||||
|
||||
int max_screen_layer = (draft_shield_height - layer_height_0) / layer_height + 1;
|
||||
|
||||
if (int(layer_nr) > max_screen_layer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gcode_layer.setCombing(false);
|
||||
gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, &storage.skirt_config[0]); // TODO: skirt config idx should correspond to draft shield extruder nr
|
||||
|
||||
}
|
||||
|
||||
std::vector<unsigned int> FffGcodeWriter::calculateMeshOrder(SliceDataStorage& storage, int current_extruder)
|
||||
{
|
||||
std::vector<unsigned int> ret;
|
||||
std::list<unsigned int> add_list;
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
add_list.push_back(mesh_idx);
|
||||
|
||||
int add_extruder_nr = current_extruder;
|
||||
while(add_list.size() > 0)
|
||||
{
|
||||
for(auto add_it = add_list.begin(); add_it != add_list.end(); )
|
||||
{
|
||||
if (storage.meshes[*add_it].getSettingAsIndex("extruder_nr") == add_extruder_nr)
|
||||
{
|
||||
ret.push_back(*add_it);
|
||||
add_it = add_list.erase(add_it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++add_it;
|
||||
}
|
||||
}
|
||||
if (add_list.size() > 0)
|
||||
add_extruder_nr = storage.meshes[*add_list.begin()].getSettingAsIndex("extruder_nr");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcode_layer, int layer_nr)
|
||||
{
|
||||
if (layer_nr > mesh->layer_nr_max_filled_layer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, mesh->getSettingAsIndex("extruder_nr"));
|
||||
|
||||
SliceLayer* layer = &mesh->layers[layer_nr];
|
||||
|
||||
|
||||
Polygons polygons;
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
{
|
||||
polygons.add(layer->parts[partNr].outline);
|
||||
}
|
||||
if (mesh->getSettingBoolean("magic_spiralize"))
|
||||
mesh->inset0_config.spiralize = true;
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(polygons, &mesh->inset0_config);
|
||||
|
||||
addMeshOpenPolyLinesToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addMeshOpenPolyLinesToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcode_layer, int layer_nr)
|
||||
{
|
||||
SliceLayer* layer = &mesh->layers[layer_nr];
|
||||
|
||||
Polygons lines;
|
||||
for(PolygonRef polyline : layer->openPolyLines)
|
||||
{
|
||||
for(unsigned int point_idx = 1; point_idx<polyline.size(); point_idx++)
|
||||
{
|
||||
Polygon p;
|
||||
p.add(polyline[point_idx-1]);
|
||||
p.add(polyline[point_idx]);
|
||||
lines.add(p);
|
||||
}
|
||||
}
|
||||
gcode_layer.addLinesByOptimizer(lines, &mesh->inset0_config);
|
||||
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcode_layer, int layer_nr)
|
||||
{
|
||||
if (layer_nr > mesh->layer_nr_max_filled_layer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SliceLayer* layer = &mesh->layers[layer_nr];
|
||||
|
||||
if (layer->parts.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, mesh->getSettingAsIndex("extruder_nr"));
|
||||
|
||||
|
||||
EZSeamType z_seam_type = mesh->getSettingAsZSeamType("z_seam_type");
|
||||
PathOrderOptimizer part_order_optimizer(gcode.getStartPositionXY(), z_seam_type);
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
{
|
||||
part_order_optimizer.addPolygon(layer->parts[partNr].insets[0][0]);
|
||||
}
|
||||
part_order_optimizer.optimize();
|
||||
|
||||
bool skin_alternate_rotation = mesh->getSettingBoolean("skin_alternate_rotation") && ( mesh->getSettingAsCount("top_layers") >= 4 || mesh->getSettingAsCount("bottom_layers") >= 4 );
|
||||
|
||||
for(int order_idx : part_order_optimizer.polyOrder)
|
||||
{
|
||||
SliceLayerPart& part = layer->parts[order_idx];
|
||||
|
||||
int infill_angle = 45;
|
||||
if (layer_nr & 1)
|
||||
infill_angle += 90;
|
||||
int extrusion_width = mesh->infill_config[0].getLineWidth();
|
||||
|
||||
int infill_line_distance = mesh->getSettingInMicrons("infill_line_distance");
|
||||
double infill_overlap = mesh->getSettingInPercentage("infill_overlap");
|
||||
|
||||
if (mesh->getSettingBoolean("infill_before_walls"))
|
||||
{
|
||||
processMultiLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, extrusion_width);
|
||||
processSingleLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, extrusion_width);
|
||||
}
|
||||
|
||||
processInsets(gcode_layer, mesh, part, layer_nr, z_seam_type);
|
||||
|
||||
if (!mesh->getSettingBoolean("infill_before_walls"))
|
||||
{
|
||||
processMultiLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, extrusion_width);
|
||||
processSingleLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, extrusion_width);
|
||||
}
|
||||
|
||||
if (skin_alternate_rotation && ( layer_nr / 2 ) & 1)
|
||||
infill_angle -= 45;
|
||||
|
||||
int64_t skin_overlap = 0;
|
||||
processSkin(gcode_layer, mesh, part, layer_nr, skin_overlap, infill_angle, extrusion_width);
|
||||
|
||||
//After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter.
|
||||
if (!mesh->getSettingBoolean("magic_spiralize") || static_cast<int>(layer_nr) < mesh->getSettingAsCount("bottom_layers"))
|
||||
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons("machine_nozzle_size") * 1);
|
||||
}
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
{
|
||||
addMeshOpenPolyLinesToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int infill_angle, int extrusion_width)
|
||||
{
|
||||
if (infill_line_distance > 0)
|
||||
{
|
||||
//Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers)
|
||||
for(unsigned int n=1; n<part.infill_area.size(); n++)
|
||||
{
|
||||
Infill infill_comp(mesh->getSettingAsFillMethod("infill_pattern"), part.infill_area[n], 0, false, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
|
||||
Polygons infill_polygons;
|
||||
Polygons infill_lines;
|
||||
infill_comp.generate(infill_polygons, infill_lines, nullptr);
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[n]);
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[n]);
|
||||
sendPolygons(InfillType, layer_nr, infill_lines, extrusion_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int infill_angle, int extrusion_width)
|
||||
{
|
||||
|
||||
if (infill_line_distance == 0 || part.infill_area.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Combine the 1 layer thick infill with the top/bottom skin and print that as one thing.
|
||||
Polygons infill_polygons;
|
||||
Polygons infill_lines;
|
||||
|
||||
EFillMethod pattern = mesh->getSettingAsFillMethod("infill_pattern");
|
||||
Infill infill_comp(pattern, part.infill_area[0], 0, false, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
|
||||
infill_comp.generate(infill_polygons, infill_lines, nullptr);
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[0]);
|
||||
if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[0], mesh->getSettingInMicrons("infill_wipe_dist"));
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[0]);
|
||||
}
|
||||
sendPolygons(InfillType, layer_nr, infill_lines, extrusion_width);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processInsets(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, EZSeamType z_seam_type)
|
||||
{
|
||||
bool compensate_overlap = mesh->getSettingBoolean("travel_compensate_overlapping_walls_enabled");
|
||||
if (mesh->getSettingAsCount("wall_line_count") > 0)
|
||||
{
|
||||
if (mesh->getSettingBoolean("magic_spiralize"))
|
||||
{
|
||||
if (static_cast<int>(layer_nr) >= mesh->getSettingAsCount("bottom_layers"))
|
||||
mesh->inset0_config.spiralize = true;
|
||||
if (static_cast<int>(layer_nr) == mesh->getSettingAsCount("bottom_layers") && part.insets.size() > 0)
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->insetX_config);
|
||||
}
|
||||
for(int inset_number=part.insets.size()-1; inset_number>-1; inset_number--)
|
||||
{
|
||||
if (inset_number == 0)
|
||||
{
|
||||
if (!compensate_overlap)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->inset0_config, nullptr, z_seam_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
Polygons& outer_wall = part.insets[0];
|
||||
WallOverlapComputation wall_overlap_computation(outer_wall, mesh->getSettingInMicrons("wall_line_width_0"));
|
||||
gcode_layer.addPolygonsByOptimizer(outer_wall, &mesh->inset0_config, &wall_overlap_computation, z_seam_type);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[inset_number], &mesh->insetX_config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, double infill_overlap, int infill_angle, int extrusion_width)
|
||||
{
|
||||
Polygons skin_polygons;
|
||||
Polygons skin_lines;
|
||||
for(SkinPart& skin_part : part.skin_parts) // TODO: optimize parts order
|
||||
{
|
||||
EFillMethod pattern = mesh->getSettingAsFillMethod("top_bottom_pattern");
|
||||
int bridge = -1;
|
||||
if (layer_nr > 0)
|
||||
bridge = bridgeAngle(skin_part.outline, &mesh->layers[layer_nr-1]);
|
||||
if (bridge > -1)
|
||||
{
|
||||
pattern = EFillMethod::LINES;
|
||||
}
|
||||
Polygons* inner_skin_outline = nullptr;
|
||||
int offset_from_inner_skin_outline = 0;
|
||||
if (pattern == EFillMethod::CONCENTRIC)
|
||||
{
|
||||
offset_from_inner_skin_outline = -extrusion_width/2;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Polygons& skin_perimeter : skin_part.insets)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(skin_perimeter, &mesh->skin_config); // add polygons to gcode in inward order
|
||||
}
|
||||
if (skin_part.insets.size() > 0)
|
||||
{
|
||||
inner_skin_outline = &skin_part.insets.back();
|
||||
offset_from_inner_skin_outline = -extrusion_width/2;
|
||||
if (mesh->getSettingString("fill_perimeter_gaps") != "Nowhere")
|
||||
{
|
||||
generateLineInfill(skin_part.perimeterGaps, 0, skin_lines, extrusion_width, extrusion_width, 0, infill_angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inner_skin_outline == nullptr)
|
||||
{
|
||||
inner_skin_outline = &skin_part.outline;
|
||||
}
|
||||
|
||||
Infill infill_comp(pattern, *inner_skin_outline, offset_from_inner_skin_outline, mesh->getSettingBoolean("remove_overlapping_walls_x_enabled"), extrusion_width, extrusion_width, infill_overlap, infill_angle, false, false);
|
||||
infill_comp.generate(skin_polygons, skin_lines, &part.perimeterGaps);
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(skin_polygons, &mesh->skin_config);
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config);
|
||||
}
|
||||
|
||||
// handle gaps between perimeters etc.
|
||||
if (mesh->getSettingString("fill_perimeter_gaps") != "Nowhere")
|
||||
{
|
||||
generateLineInfill(part.perimeterGaps, 0, skin_lines, extrusion_width, extrusion_width, 0, infill_angle);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr_before, bool before_rest)
|
||||
{
|
||||
if (!storage.support.generated || layer_nr > storage.support.layer_nr_max_filled_layer)
|
||||
return;
|
||||
|
||||
int support_roof_extruder_nr = getSettingAsIndex("support_roof_extruder_nr");
|
||||
int support_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_extruder_nr");
|
||||
|
||||
bool print_support_before_rest = support_extruder_nr == extruder_nr_before
|
||||
|| support_roof_extruder_nr == extruder_nr_before;
|
||||
// TODO: always print support after rest when only one nozzle is used for the whole meshgroup
|
||||
|
||||
if (print_support_before_rest != before_rest)
|
||||
return;
|
||||
|
||||
gcode_layer.setCombing(false);
|
||||
|
||||
int current_extruder_nr = gcode_layer.getExtruder();
|
||||
|
||||
if (storage.support.supportLayers[layer_nr].roofs.size() > 0)
|
||||
{
|
||||
if (support_roof_extruder_nr != support_extruder_nr && support_roof_extruder_nr == current_extruder_nr)
|
||||
{
|
||||
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addSupportLinesToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
|
||||
{
|
||||
if (!storage.support.generated
|
||||
|| layer_nr > storage.support.layer_nr_max_filled_layer
|
||||
|| storage.support.supportLayers[layer_nr].supportAreas.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int support_line_distance = getSettingInMicrons("support_line_distance");
|
||||
int extrusion_width = storage.support_config.getLineWidth();
|
||||
EFillMethod support_pattern = getSettingAsFillMethod("support_pattern");
|
||||
if (layer_nr == 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG)) { support_pattern = EFillMethod::GRID; }
|
||||
|
||||
int support_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_extruder_nr");
|
||||
|
||||
double infill_overlap = storage.meshgroup->getExtruderTrain(support_extruder_nr)->getSettingInPercentage("infill_overlap");
|
||||
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_extruder_nr);
|
||||
|
||||
Polygons& support = storage.support.supportLayers[layer_nr].supportAreas;
|
||||
|
||||
std::vector<PolygonsPart> support_islands = support.splitIntoParts();
|
||||
|
||||
PathOrderOptimizer island_order_optimizer(gcode.getPositionXY());
|
||||
for(unsigned int n=0; n<support_islands.size(); n++)
|
||||
{
|
||||
island_order_optimizer.addPolygon(support_islands[n][0]);
|
||||
}
|
||||
island_order_optimizer.optimize();
|
||||
|
||||
for(unsigned int n=0; n<support_islands.size(); n++)
|
||||
{
|
||||
PolygonsPart& island = support_islands[island_order_optimizer.polyOrder[n]];
|
||||
|
||||
int offset_from_outline = 0;
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, false, extrusion_width, support_line_distance, infill_overlap, 0, getSettingBoolean("support_connect_zigzags"), true);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines, nullptr);
|
||||
|
||||
if (support_pattern == EFillMethod::GRID || support_pattern == EFillMethod::TRIANGLES)
|
||||
gcode_layer.addPolygonsByOptimizer(island, &storage.support_config);
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_config);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
|
||||
{
|
||||
if (!storage.support.generated
|
||||
|| layer_nr > storage.support.layer_nr_max_filled_layer
|
||||
|| storage.support.supportLayers[layer_nr].roofs.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EFillMethod pattern = getSettingAsFillMethod("support_roof_pattern");
|
||||
int support_line_distance = getSettingInMicrons("support_roof_line_distance");
|
||||
|
||||
int roof_extruder_nr = getSettingAsIndex("support_roof_extruder_nr");
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, roof_extruder_nr);
|
||||
|
||||
double fillAngle;
|
||||
if (getSettingInMicrons("support_roof_height") < 2 * getSettingInMicrons("layer_height"))
|
||||
{
|
||||
fillAngle = 90; // perpendicular to support lines
|
||||
}
|
||||
else
|
||||
{
|
||||
fillAngle = 45 + (layer_nr % 2) * 90; // alternate between the two kinds of diagonal: / and \ .
|
||||
}
|
||||
double infill_overlap = 0;
|
||||
int outline_offset = 0;
|
||||
|
||||
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].roofs, outline_offset, false, storage.support_roof_config.getLineWidth(), support_line_distance, infill_overlap, fillAngle, false, true);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines, nullptr);
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_config);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr)
|
||||
{
|
||||
if (extruder_nr == -1) // an object with extruder_nr==-1 means it will be printed with any current nozzle
|
||||
return;
|
||||
|
||||
int previous_extruder = gcode_layer.getExtruder();
|
||||
if (previous_extruder == extruder_nr) { return; }
|
||||
bool extruder_changed = gcode_layer.setExtruder(extruder_nr);
|
||||
|
||||
if (extruder_changed)
|
||||
{
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
processSkirt(storage, gcode_layer, extruder_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addPrimeTower(storage, gcode_layer, layer_nr, previous_extruder);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int prev_extruder)
|
||||
{
|
||||
|
||||
if (getSettingInMicrons("prime_tower_size") < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool prime_tower_dir_outward = getSettingBoolean("prime_tower_dir_outward");
|
||||
bool wipe = getSettingBoolean("prime_tower_wipe_enabled");
|
||||
|
||||
storage.primeTower.addToGcode(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processFanSpeedAndMinimalLayerTime(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr)
|
||||
{
|
||||
double travelTime;
|
||||
double extrudeTime;
|
||||
gcodeLayer.getNaiveTimeEstimates(travelTime, extrudeTime);
|
||||
gcodeLayer.forceMinimalLayerTime(getSettingInSeconds("cool_min_layer_time"), getSettingInMillimetersPerSecond("cool_min_speed"), travelTime, extrudeTime);
|
||||
|
||||
// interpolate fan speed (for cool_fan_full_layer and for cool_min_layer_time_fan_speed_max)
|
||||
double fanSpeed = getSettingInPercentage("cool_fan_speed_min");
|
||||
double totalLayerTime = travelTime + extrudeTime;
|
||||
if (totalLayerTime < getSettingInSeconds("cool_min_layer_time"))
|
||||
{
|
||||
fanSpeed = getSettingInPercentage("cool_fan_speed_max");
|
||||
}
|
||||
else if (totalLayerTime < getSettingInSeconds("cool_min_layer_time_fan_speed_max"))
|
||||
{
|
||||
// when forceMinimalLayerTime didn't change the extrusionSpeedFactor, we adjust the fan speed
|
||||
double minTime = (getSettingInSeconds("cool_min_layer_time"));
|
||||
double maxTime = (getSettingInSeconds("cool_min_layer_time_fan_speed_max"));
|
||||
double fanSpeedMin = getSettingInPercentage("cool_fan_speed_min");
|
||||
double fanSpeedMax = getSettingInPercentage("cool_fan_speed_max");
|
||||
fanSpeed = fanSpeedMax - (fanSpeedMax-fanSpeedMin) * (totalLayerTime - minTime) / (maxTime - minTime);
|
||||
}
|
||||
if (static_cast<int>(layer_nr) < getSettingAsCount("cool_fan_full_layer"))
|
||||
{
|
||||
//Slow down the fan on the layers below the [cool_fan_full_layer], where layer 0 is speed 0.
|
||||
fanSpeed = fanSpeed * layer_nr / getSettingAsCount("cool_fan_full_layer");
|
||||
}
|
||||
gcode.writeFanCommand(fanSpeed);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::finalize()
|
||||
{
|
||||
gcode.finalize(max_object_height, getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
|
||||
for(int e=0; e<getSettingAsCount("machine_extruder_count"); e++)
|
||||
gcode.writeTemperatureCommand(e, 0, false);
|
||||
gcode.writeCode("M25 ;Stop reading from this point on.");
|
||||
gcode.writeComment("Cura profile string:");
|
||||
gcode.writeComment(FffProcessor::getInstance()->getAllLocalSettingsString() + FffProcessor::getInstance()->getProfileString());
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,320 @@
|
||||
#ifndef GCODE_WRITER_H
|
||||
#define GCODE_WRITER_H
|
||||
|
||||
|
||||
#include <fstream>
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "raft.h"
|
||||
#include "infill.h"
|
||||
#include "bridge.h"
|
||||
#include "pathOrderOptimizer.h"
|
||||
#include "gcodePlanner.h"
|
||||
#include "gcodeExport.h"
|
||||
#include "commandSocket.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "PrimeTower.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Secondary stage in Fused Filament Fabrication processing: The generated polygons are used in the gcode generation.
|
||||
* Some polygons in the SliceDataStorage signify areas which are to be filled with parallel lines,
|
||||
* while other polygons signify the contours which should be printed.
|
||||
*
|
||||
* The main function of this class is FffGcodeWriter::writeGCode().
|
||||
*/
|
||||
class FffGcodeWriter : public SettingsMessenger
|
||||
{
|
||||
friend class FffProcessor; // cause WireFrame2Gcode uses the member [gcode] (TODO)
|
||||
private:
|
||||
int max_object_height;
|
||||
int meshgroup_number; //!< used for sequential printing of objects
|
||||
GCodeExport gcode;
|
||||
CommandSocket* command_socket;
|
||||
std::ofstream output_file;
|
||||
|
||||
/*!
|
||||
* Layer number of the last layer in which a prime tower has been printed per extruder train.
|
||||
*
|
||||
* This is recorded per extruder to account for a prime tower per extruder, instead of the mixed prime tower.
|
||||
*/
|
||||
int last_prime_tower_poly_printed[MAX_EXTRUDERS];
|
||||
public:
|
||||
FffGcodeWriter(SettingsBase* settings_)
|
||||
: SettingsMessenger(settings_)
|
||||
{
|
||||
meshgroup_number = 1;
|
||||
max_object_height = 0;
|
||||
command_socket = NULL;
|
||||
}
|
||||
void resetFileNumber()
|
||||
{
|
||||
meshgroup_number = 1;
|
||||
}
|
||||
|
||||
void setCommandSocket(CommandSocket* socket)
|
||||
{
|
||||
command_socket = socket;
|
||||
}
|
||||
|
||||
void sendPolygons(PolygonType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
{
|
||||
if (command_socket)
|
||||
command_socket->sendPolygons(type, layer_nr, polygons, line_width);
|
||||
}
|
||||
|
||||
bool setTargetFile(const char* filename)
|
||||
{
|
||||
output_file.open(filename);
|
||||
if (output_file.is_open())
|
||||
{
|
||||
gcode.setOutputStream(&output_file);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setTargetStream(std::ostream* stream)
|
||||
{
|
||||
gcode.setOutputStream(stream);
|
||||
}
|
||||
|
||||
double getTotalFilamentUsed(int e)
|
||||
{
|
||||
return gcode.getTotalFilamentUsed(e);
|
||||
}
|
||||
|
||||
double getTotalPrintTime()
|
||||
{
|
||||
return gcode.getTotalPrintTime();
|
||||
}
|
||||
|
||||
void writeGCode(SliceDataStorage& storage, TimeKeeper& timeKeeper);
|
||||
|
||||
private:
|
||||
void setConfigCoasting(SliceDataStorage& storage);
|
||||
|
||||
//Setup the retraction parameters.
|
||||
void setConfigRetraction(SliceDataStorage& storage);
|
||||
|
||||
void setConfigSkirt(SliceDataStorage& storage, int layer_thickness);
|
||||
|
||||
void setConfigSupport(SliceDataStorage& storage, int layer_thickness);
|
||||
|
||||
void setConfigInsets(SliceMeshStorage& mesh, int layer_thickness);
|
||||
|
||||
void setConfigSkin(SliceMeshStorage& mesh, int layer_thickness);
|
||||
|
||||
void setConfigInfill(SliceMeshStorage& mesh, int layer_thickness);
|
||||
|
||||
/*!
|
||||
* Set temperatures and perform initial priming.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
*/
|
||||
void processStartingCode(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Move up and over the just printed model to print the next model.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
*/
|
||||
void processNextMeshGroupCode(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Add raft gcode.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
* \param totalLayers The total number of layers.
|
||||
*/
|
||||
void processRaft(SliceDataStorage& storage, unsigned int totalLayers);
|
||||
|
||||
/*!
|
||||
* Add a layer to the gcode.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \param totalLayers The total number of layers.
|
||||
* \param has_raft Whether a raft is used for this print.
|
||||
*/
|
||||
void processLayer(SliceDataStorage& storage, unsigned int layer_nr, unsigned int totalLayers, bool has_raft);
|
||||
|
||||
/*!
|
||||
* Interpolate between the initial layer speeds and the eventual speeds.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*/
|
||||
void processInitialLayersSpeedup(SliceDataStorage& storage, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add the skirt to the gcode.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param extruder_nr The extrudewr train for which to process the skirt
|
||||
*/
|
||||
void processSkirt(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int extruder_nr);
|
||||
|
||||
/*!
|
||||
* Adds the ooze shield to the print.
|
||||
* \param storage Input: 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.
|
||||
*/
|
||||
void processOozeShield(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Adds the draft protection screen to the print.
|
||||
* \param storage Input: 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.
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Calculate in which order to print the meshes.
|
||||
* \param storage Input: 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.
|
||||
*/
|
||||
std::vector<unsigned int> calculateMeshOrder(SliceDataStorage& storage, int current_extruder);
|
||||
|
||||
/*!
|
||||
* Add a single layer from a single mesh-volume to the GCode in mesh surface mode.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
* \param mesh The mesh to add to the gcode.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*
|
||||
*/
|
||||
void addMeshLayerToGCode_meshSurfaceMode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add the open polylines from a single layer from a single mesh-volume to the GCode for mesh surface mode.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
* \param mesh The mesh for which to add to the gcode.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*
|
||||
*/
|
||||
void addMeshOpenPolyLinesToGCode(SliceDataStorage& storage, SliceMeshStorage* mesh, GCodePlanner& gcode_layer, int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add a single layer from a single mesh-volume to the GCode.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
* \param mesh The mesh to add to the gcode.
|
||||
* \param gcodeLayer 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);
|
||||
|
||||
/*!
|
||||
* Add thicker (multiple layers) sparse infill for a given part in a layer.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh for which to add to the gcode.
|
||||
* \param part The part for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param infill_line_distance The distance between the infill lines
|
||||
* \param infill_overlap The fraction of the extrusion width by which the infill overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
void processMultiLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int fillAngle, int extrusionWidth);
|
||||
|
||||
/*!
|
||||
* Add normal sparse infill for a given part in a layer.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh for which to add to the gcode.
|
||||
* \param part The part for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param infill_line_distance The distance between the infill lines
|
||||
* \param infill_overlap The fraction of the extrusion width by which the infill overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
void processSingleLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int fillAngle, int extrusionWidth);
|
||||
|
||||
/*!
|
||||
* Generate the insets for the walls of a given layer part.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh for which to add to the gcode.
|
||||
* \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
|
||||
*/
|
||||
void processInsets(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, EZSeamType z_seam_type);
|
||||
|
||||
|
||||
/*!
|
||||
* Add the gcode of the top/bottom skin of the given part.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh for which to add to the gcode.
|
||||
* \param part The part for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param infill_overlap The fraction of the extrusion width by which the infill overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, double infill_overlap, int infill_angle, int extrusion_width);
|
||||
|
||||
/*!
|
||||
* Add the support to the gcode of the current layer.
|
||||
* \param storage Input: 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 gcode, or after.
|
||||
*/
|
||||
void addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int extruder_nr_before, bool before_rest);
|
||||
/*!
|
||||
* Add the support lines/walls to the gcode of the current layer.
|
||||
* \param storage Input: 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.
|
||||
*/
|
||||
void addSupportLinesToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
/*!
|
||||
* Add the support roofs to the gcode of the current layer.
|
||||
* \param storage Input: 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.
|
||||
*/
|
||||
void 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.
|
||||
*
|
||||
* On layer 0 this function adds the skirt for the nozzle it switches to, instead of the prime tower.
|
||||
*
|
||||
* \param storage Input: 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 The extruder to which to switch
|
||||
*/
|
||||
void setExtruder_addPrime(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr);
|
||||
|
||||
/*!
|
||||
* Add the prime tower gcode for the current layer.
|
||||
* \param storage Input: 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 prev_extruder The current extruder with which we last printed.
|
||||
*/
|
||||
void addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int prev_extruder);
|
||||
|
||||
/*!
|
||||
* Finish the layer by applying speed corrections for minimal layer times and determine the fanSpeed.
|
||||
* \param storage Input: 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.
|
||||
*/
|
||||
void processFanSpeedAndMinimalLayerTime(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add the end gcode and set all temperatures to zero.
|
||||
*/
|
||||
void finalize();
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif // GCODE_WRITER_H
|
||||
@@ -0,0 +1,584 @@
|
||||
#include "FffPolygonGenerator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random> // for bulging effect?
|
||||
#include <functional> // for bugling?
|
||||
#include <cmath> // for bulging?
|
||||
|
||||
#include "slicer.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "support.h"
|
||||
#include "multiVolumes.h"
|
||||
#include "layerPart.h"
|
||||
#include "inset.h"
|
||||
#include "skirt.h"
|
||||
#include "skin.h"
|
||||
#include "infill.h"
|
||||
#include "raft.h"
|
||||
#include "debug.h"
|
||||
#include "Progress.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper)
|
||||
{
|
||||
if (commandSocket)
|
||||
commandSocket->beginSendSlicedObject();
|
||||
|
||||
if (!sliceModel(meshgroup, timeKeeper, storage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
slices2polygons(storage, timeKeeper);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeeper, SliceDataStorage& storage) /// slices the model
|
||||
{
|
||||
Progress::messageProgressStage(Progress::Stage::SLICING, &timeKeeper, commandSocket);
|
||||
|
||||
storage.model_min = meshgroup->min();
|
||||
storage.model_max = meshgroup->max();
|
||||
storage.model_size = storage.model_max - storage.model_min;
|
||||
|
||||
log("Slicing model...\n");
|
||||
int initial_layer_thickness = meshgroup->getSettingInMicrons("layer_height_0");
|
||||
int layer_thickness = meshgroup->getSettingInMicrons("layer_height");
|
||||
if (meshgroup->getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT)
|
||||
{
|
||||
initial_layer_thickness = layer_thickness;
|
||||
}
|
||||
int initial_slice_z = initial_layer_thickness - layer_thickness / 2;
|
||||
int layer_count = (storage.model_max.z - initial_slice_z) / layer_thickness + 1;
|
||||
|
||||
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, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
|
||||
slicerList.push_back(slicer);
|
||||
/*
|
||||
for(SlicerLayer& layer : slicer->layers)
|
||||
{
|
||||
//Reporting the outline here slows down the engine quite a bit, so only do so when debugging.
|
||||
//sendPolygons("outline", layer_nr, layer.z, layer.polygonList);
|
||||
//sendPolygons("openoutline", layer_nr, layer.openPolygonList);
|
||||
}
|
||||
*/
|
||||
Progress::messageProgress(Progress::Stage::SLICING, mesh_idx + 1, meshgroup->meshes.size(), commandSocket);
|
||||
}
|
||||
|
||||
log("Layer count: %i\n", layer_count);
|
||||
|
||||
meshgroup->clear();///Clear the mesh face and vertex data, it is no longer needed after this point, and it saves a lot of memory.
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper, commandSocket);
|
||||
|
||||
bulgeWalls(slicerList, meshgroup);
|
||||
|
||||
//carveMultipleVolumes(storage.meshes);
|
||||
generateMultipleVolumesOverlap(slicerList, getSettingInMicrons("multiple_mesh_overlap"));
|
||||
|
||||
storage.meshes.reserve(slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated.
|
||||
for(unsigned int meshIdx=0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
storage.meshes.emplace_back(&meshgroup->meshes[meshIdx]); // new mesh in storage had settings from the Mesh
|
||||
SliceMeshStorage& meshStorage = storage.meshes.back();
|
||||
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
|
||||
createLayerParts(meshStorage, slicerList[meshIdx], mesh.getSettingBoolean("meshfix_union_all"), mesh.getSettingBoolean("meshfix_union_all_remove_holes"));
|
||||
delete slicerList[meshIdx];
|
||||
|
||||
bool has_raft = meshStorage.getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT;
|
||||
//Add the raft offset to each layer.
|
||||
for(unsigned int layer_nr=0; layer_nr<meshStorage.layers.size(); layer_nr++)
|
||||
{
|
||||
SliceLayer& layer = meshStorage.layers[layer_nr];
|
||||
if (has_raft)
|
||||
{
|
||||
layer.printZ +=
|
||||
meshStorage.getSettingInMicrons("raft_base_thickness")
|
||||
+ meshStorage.getSettingInMicrons("raft_interface_thickness")
|
||||
+ meshStorage.getSettingAsCount("raft_surface_layers") * getSettingInMicrons("layer_height") //raft_surface_thickness")
|
||||
+ meshStorage.getSettingInMicrons("raft_airgap")
|
||||
- initial_slice_z;
|
||||
}
|
||||
else
|
||||
{
|
||||
meshStorage.layers[layer_nr].printZ +=
|
||||
meshStorage.getSettingInMicrons("layer_height_0")
|
||||
- initial_slice_z;
|
||||
}
|
||||
|
||||
|
||||
if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) )
|
||||
{
|
||||
meshStorage.layer_nr_max_filled_layer = layer_nr; // last set by the highest non-empty layer
|
||||
}
|
||||
|
||||
if (commandSocket)
|
||||
{
|
||||
commandSocket->sendLayerInfo(layer_nr, layer.printZ, layer_nr == 0 && !has_raft? meshStorage.getSettingInMicrons("layer_height_0") : meshStorage.getSettingInMicrons("layer_height"));
|
||||
}
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::PARTS, meshIdx + 1, slicerList.size(), commandSocket);
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::INSET, &timeKeeper, commandSocket);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& time_keeper)
|
||||
{
|
||||
// const
|
||||
unsigned int total_layers = storage.meshes.at(0).layers.size();
|
||||
//layerparts2HTML(storage, "output/output.html");
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
{
|
||||
processInsets(storage, layer_number);
|
||||
|
||||
Progress::messageProgress(Progress::Stage::INSET, layer_number+1, total_layers, commandSocket);
|
||||
}
|
||||
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), total_layers);
|
||||
|
||||
if (total_layers < 1)
|
||||
{
|
||||
log("Stopping process because there are no layers.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper, commandSocket);
|
||||
|
||||
AreaSupport::generateSupportAreas(storage, total_layers, commandSocket);
|
||||
if (storage.support.generated)
|
||||
{
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
{
|
||||
Polygons& support = storage.support.supportLayers[layer_idx].supportAreas;
|
||||
sendPolygons(SupportType, layer_idx, support, getSettingInMicrons("support_line_width"));
|
||||
}
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper, commandSocket);
|
||||
int mesh_max_bottom_layer_count = 0;
|
||||
if (getSettingBoolean("magic_spiralize"))
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
if (!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.
|
||||
{
|
||||
processSkins(storage, layer_number);
|
||||
}
|
||||
Progress::messageProgress(Progress::Stage::SKIN, layer_number+1, total_layers, commandSocket);
|
||||
}
|
||||
|
||||
for(unsigned int layer_number = total_layers-1; layer_number > 0; layer_number--)
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
combineInfillLayers(layer_number, mesh, mesh.getSettingAsCount("infill_sparse_combine"));
|
||||
}
|
||||
|
||||
storage.primeTower.computePrimeTowerMax(storage);
|
||||
storage.primeTower.generatePaths(storage, total_layers);
|
||||
|
||||
processOozeShield(storage, total_layers);
|
||||
|
||||
processDraftShield(storage, total_layers);
|
||||
|
||||
processPlatformAdhesion(storage);
|
||||
|
||||
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (mesh.getSettingBoolean("magic_fuzzy_skin_enabled"))
|
||||
{
|
||||
processFuzzySkin(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processInsets(SliceDataStorage& storage, unsigned int layer_nr)
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
SliceLayer* layer = &mesh.layers[layer_nr];
|
||||
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.
|
||||
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;
|
||||
generateInsets(layer, mesh.getSettingInMicrons("machine_nozzle_size"), line_width_0, line_width_x, inset_count, mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
|
||||
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
{
|
||||
if (layer->parts[partNr].insets.size() > 0)
|
||||
{
|
||||
sendPolygons(Inset0Type, layer_nr, layer->parts[partNr].insets[0], line_width_0);
|
||||
for(unsigned int inset=1; inset<layer->parts[partNr].insets.size(); inset++)
|
||||
sendPolygons(InsetXType, layer_nr, layer->parts[partNr].insets[inset], line_width_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // only send polygon data
|
||||
SliceLayer* layer = &mesh.layers[layer_nr];
|
||||
for(SliceLayerPart& part : layer->parts)
|
||||
{
|
||||
sendPolygons(Inset0Type, layer_nr, part.outline, mesh.getSettingInMicrons("wall_line_width_0"));
|
||||
}
|
||||
}
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
{
|
||||
for (PolygonRef polyline : layer->openPolyLines)
|
||||
{
|
||||
Polygons segments;
|
||||
for (unsigned int point_idx = 1; point_idx < polyline.size(); point_idx++)
|
||||
{
|
||||
PolygonRef segment = segments.newPoly();
|
||||
segment.add(polyline[point_idx-1]);
|
||||
segment.add(polyline[point_idx]);
|
||||
}
|
||||
sendPolygons(Inset0Type, layer_nr, segments, mesh.getSettingInMicrons("wall_line_width_0"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, int layer_height, unsigned int totalLayers)
|
||||
{
|
||||
int n_empty_first_layers = 0;
|
||||
for (unsigned int layer_idx = 0; layer_idx < totalLayers; layer_idx++)
|
||||
{
|
||||
bool layer_is_empty = true;
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) )
|
||||
{
|
||||
layer_is_empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (layer_is_empty)
|
||||
{
|
||||
n_empty_first_layers++;
|
||||
} else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (n_empty_first_layers > 0)
|
||||
{
|
||||
log("Removing %d layers because they are empty\n", n_empty_first_layers);
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
std::vector<SliceLayer>& layers = mesh.layers;
|
||||
layers.erase(layers.begin(), layers.begin() + n_empty_first_layers);
|
||||
for (SliceLayer& layer : layers)
|
||||
{
|
||||
layer.printZ -= n_empty_first_layers * layer_height;
|
||||
}
|
||||
}
|
||||
totalLayers -= n_empty_first_layers;
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processSkins(SliceDataStorage& storage, unsigned int layer_nr)
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { continue; }
|
||||
|
||||
int extrusionWidth = mesh.getSettingInMicrons("wall_line_width_x");
|
||||
int extrusionWidth_infill = mesh.getSettingInMicrons("infill_line_width");
|
||||
generateSkins(layer_nr, mesh, extrusionWidth, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
|
||||
if (mesh.getSettingInMicrons("infill_line_distance") > 0)
|
||||
{
|
||||
int infill_skin_overlap = 0;
|
||||
if (mesh.getSettingInMicrons("infill_line_distance") > mesh.getSettingInMicrons("infill_line_width") + 10)
|
||||
{
|
||||
infill_skin_overlap = extrusionWidth / 2;
|
||||
}
|
||||
generateInfill(layer_nr, mesh, extrusionWidth_infill, infill_skin_overlap);
|
||||
if (mesh.getSettingString("fill_perimeter_gaps") == "Skin")
|
||||
{
|
||||
generatePerimeterGaps(layer_nr, mesh, extrusionWidth, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"));
|
||||
}
|
||||
else if (mesh.getSettingString("fill_perimeter_gaps") == "Everywhere")
|
||||
{
|
||||
generatePerimeterGaps(layer_nr, mesh, extrusionWidth, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
for(SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
// sendPolygons(InfillType, layer_nr, part.infill_area[0], extrusionWidth_infill); // sends the outline, not the actual infill
|
||||
for (SkinPart& skin_part : part.skin_parts)
|
||||
{
|
||||
sendPolygons(SkinType, layer_nr, skin_part.outline, extrusionWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage, unsigned int totalLayers)
|
||||
{
|
||||
if (!getSettingBoolean("ooze_shield_enabled"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int ooze_shield_dist = getSettingInMicrons("ooze_shield_dist");
|
||||
|
||||
for(unsigned int layer_nr=0; layer_nr<totalLayers; layer_nr++)
|
||||
{
|
||||
storage.oozeShield.push_back(storage.getLayerOutlines(layer_nr, true).offset(ooze_shield_dist));
|
||||
}
|
||||
|
||||
int largest_printed_radius = MM2INT(1.0); // TODO: make var a parameter, and perhaps even a setting?
|
||||
for(unsigned int layer_nr=0; layer_nr<totalLayers; layer_nr++)
|
||||
{
|
||||
storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].offset(-largest_printed_radius).offset(largest_printed_radius);
|
||||
}
|
||||
int allowed_angle_offset = tan(getSettingInAngleRadians("ooze_shield_angle")) * getSettingInMicrons("layer_height");//Allow for a 60deg angle in the oozeShield.
|
||||
for(unsigned int layer_nr=1; layer_nr<totalLayers; layer_nr++)
|
||||
{
|
||||
storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].unionPolygons(storage.oozeShield[layer_nr-1].offset(-allowed_angle_offset));
|
||||
}
|
||||
for(unsigned int layer_nr=totalLayers-1; layer_nr>0; layer_nr--)
|
||||
{
|
||||
storage.oozeShield[layer_nr-1] = storage.oozeShield[layer_nr-1].unionPolygons(storage.oozeShield[layer_nr].offset(-allowed_angle_offset));
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned int totalLayers)
|
||||
{
|
||||
int draft_shield_height = getSettingInMicrons("draft_shield_height");
|
||||
int draft_shield_dist = getSettingInMicrons("draft_shield_dist");
|
||||
int layer_height_0 = getSettingInMicrons("layer_height_0");
|
||||
int layer_height = getSettingInMicrons("layer_height");
|
||||
|
||||
if (draft_shield_height < layer_height_0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int max_screen_layer = (draft_shield_height - layer_height_0) / layer_height + 1;
|
||||
|
||||
int layer_skip = 500 / layer_height + 1;
|
||||
|
||||
Polygons& draft_shield = storage.draft_protection_shield;
|
||||
for (unsigned int layer_nr = 0; layer_nr < totalLayers && layer_nr < max_screen_layer; layer_nr += layer_skip)
|
||||
{
|
||||
draft_shield = draft_shield.unionPolygons(storage.getLayerOutlines(layer_nr, true));
|
||||
}
|
||||
|
||||
storage.draft_protection_shield = draft_shield.convexHull(draft_shield_dist);
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
|
||||
{
|
||||
switch(getSettingAsPlatformAdhesion("adhesion_type"))
|
||||
{
|
||||
case EPlatformAdhesion::SKIRT:
|
||||
if (getSettingInMicrons("draft_shield_height") == 0)
|
||||
{ // draft screen replaces skirt
|
||||
generateSkirt(storage, getSettingInMicrons("skirt_gap"), getSettingAsCount("skirt_line_count"), getSettingInMicrons("skirt_minimal_length"));
|
||||
}
|
||||
break;
|
||||
case EPlatformAdhesion::BRIM:
|
||||
generateSkirt(storage, 0, getSettingAsCount("brim_line_count"), getSettingInMicrons("skirt_minimal_length"));
|
||||
break;
|
||||
case EPlatformAdhesion::RAFT:
|
||||
generateRaft(storage, getSettingInMicrons("raft_margin"));
|
||||
break;
|
||||
}
|
||||
|
||||
Polygons skirt_sent = storage.skirt[0];
|
||||
for (int extruder = 1; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
skirt_sent.add(storage.skirt[extruder]);
|
||||
sendPolygons(SkirtType, 0, skirt_sent, getSettingInMicrons("skirt_line_width"));
|
||||
}
|
||||
|
||||
|
||||
void FffPolygonGenerator::processFuzzySkin(SliceMeshStorage& mesh)
|
||||
{
|
||||
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 (SliceLayer& layer : mesh.layers)
|
||||
{
|
||||
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 = crossZ(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::bulgeWalls(std::vector< Slicer* > slicerList, MeshGroup* meshgroup)
|
||||
{
|
||||
|
||||
assert(slicerList.size() == meshgroup->meshes.size());
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < slicerList.size(); mesh_idx++)
|
||||
{
|
||||
Slicer* slicer = slicerList[mesh_idx];
|
||||
Mesh& mesh = meshgroup->meshes[mesh_idx];
|
||||
|
||||
if (!mesh.getSettingBoolean("magic_bulge_walls"))
|
||||
{
|
||||
// continue; // TODO
|
||||
}
|
||||
|
||||
auto getBulging = [](Point xy, int z)
|
||||
{
|
||||
std::hash<int> hash_fn;
|
||||
int cell_size = MM2INT(0.2);
|
||||
int cell_dim = 5; // surrounding taken into account
|
||||
double result = 0.0;
|
||||
int bulging = MM2INT(10.0);
|
||||
Point3 middle(xy.X / cell_size, xy.Y / cell_size, z / cell_size);
|
||||
double total_weight = 0.0;
|
||||
for (int x = middle.x - cell_dim; x < middle.x + cell_dim; x++)
|
||||
{
|
||||
for (int y = middle.y - cell_dim; y < middle.y + cell_dim; y++)
|
||||
{
|
||||
for (int z = middle.z - cell_dim; z < middle.z + cell_dim; z++)
|
||||
{
|
||||
srand(x ^ (y << 8) ^ (z << 16)); // set seed
|
||||
int h = rand();
|
||||
// int h = hash_fn(x ^ (y << 8) ^ (z << 16));
|
||||
double r = (double(h % 200000 - 100000))/100000.0; // between -1 and 1
|
||||
double weight = sqrt(1.0 / (1.0 + static_cast<double>(((Point3(xy.X, xy.Y, z) - Point3(x,y,z)* cell_size)).vSize()) * 4));
|
||||
total_weight += weight;
|
||||
result += r * weight ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<int>(result / total_weight * bulging);
|
||||
// return rand() % (bulging*2) - bulging;
|
||||
};
|
||||
|
||||
int64_t avg_dist_between_points = MM2INT(0.5); // 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;
|
||||
|
||||
int layer_height = mesh.getSettingInMicrons("layer_height");
|
||||
|
||||
for (unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
SlicerLayer& layer = slicer->layers[layer_nr];
|
||||
Polygons& outlines = layer.polygonList;
|
||||
Polygons results;
|
||||
|
||||
int z_approx = layer_nr * layer_height;
|
||||
|
||||
for (PolygonRef poly : outlines)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
Point in_between = *p0 + normal(p0p1, p0pa_dist);
|
||||
int r = getBulging(in_between, z_approx);
|
||||
Point perp_to_p0p1 = crossZ(p0p1);
|
||||
Point fuzz = normal(perp_to_p0p1, r);
|
||||
Point pa = in_between + 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);
|
||||
}
|
||||
}
|
||||
|
||||
outlines = results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,147 @@
|
||||
#ifndef FFF_AREA_GENERATOR_H
|
||||
#define FFF_AREA_GENERATOR_H
|
||||
|
||||
|
||||
#include "MeshGroup.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "settings.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "commandSocket.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class Slicer; // forward declaration
|
||||
|
||||
/*!
|
||||
* Primary stage in Fused Filament Fabrication processing: Polygons are generated.
|
||||
* The model is sliced and each slice consists of polygons representing the outlines: the boundaries between inside and outside the object.
|
||||
* After slicing, the layers are processed; for example the wall insets are generated, and the areas which are to be filled with support and infill, which are all represented by polygons.
|
||||
* In this stage nothing other than areas and circular paths are generated, which are both represented by polygons.
|
||||
* No infill lines or support pattern etc. is generated.
|
||||
*
|
||||
* The main function of this class is FffPolygonGenerator::generateAreas().
|
||||
*/
|
||||
class FffPolygonGenerator : public SettingsMessenger
|
||||
{
|
||||
private:
|
||||
CommandSocket* commandSocket;
|
||||
public:
|
||||
/*!
|
||||
* Basic constructor; doesn't set the FffAreaGenerator::commandSocket .
|
||||
*/
|
||||
FffPolygonGenerator(SettingsBase* settings_)
|
||||
: SettingsMessenger(settings_)
|
||||
, commandSocket(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the FffAreaGenerator::commandSocket
|
||||
*/
|
||||
void setCommandSocket(CommandSocket* socket)
|
||||
{
|
||||
commandSocket = socket;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Slice the \p object, process the outline information into inset perimeter polygons, support area polygons, etc.
|
||||
*
|
||||
* \param object The object to slice.
|
||||
* \param timeKeeper Object which keeps track of timings of each stage.
|
||||
* \param storage Output parameter: where the outlines are stored. See SliceLayerPart::outline.
|
||||
*/
|
||||
bool generateAreas(SliceDataStorage& storage, MeshGroup* object, TimeKeeper& timeKeeper);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Send polygons over the command socket, if there is one.
|
||||
* \param type The type of polygon to send
|
||||
* \param layer_nr The layer number at which the polygons occur
|
||||
* \param polygons The polygons to be sent
|
||||
*/
|
||||
void sendPolygons(PolygonType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
{
|
||||
if (commandSocket)
|
||||
commandSocket->sendPolygons(type, layer_nr, polygons, line_width);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Slice the \p object and store the outlines in the \p storage.
|
||||
*
|
||||
* \param object The object to slice.
|
||||
* \param timeKeeper Object which keeps track of timings of each stage.
|
||||
* \param storage Output parameter: where the outlines are stored. See SliceLayerPart::outline.
|
||||
*
|
||||
* \return Whether the process succeeded (always true).
|
||||
*/
|
||||
bool sliceModel(MeshGroup* object, TimeKeeper& timeKeeper, SliceDataStorage& storage); /// slices the model
|
||||
|
||||
/*!
|
||||
* Processes the outline information as stored in the \p storage: generates inset perimeter polygons, support area polygons, etc.
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param timeKeeper Object which keeps track of timings of each stage.
|
||||
*/
|
||||
void slices2polygons(SliceDataStorage& storage, TimeKeeper& timeKeeper);
|
||||
|
||||
/*!
|
||||
* Remove all bottom layers which are empty.
|
||||
* \param storage Input and Ouput parameter: stores all layers
|
||||
* \param layer_height The height of each layer
|
||||
* \param totalLayers The total number of layers
|
||||
*/
|
||||
void removeEmptyFirstLayers(SliceDataStorage& storage, int layer_height, unsigned int totalLayers);
|
||||
|
||||
/*!
|
||||
* Generate the inset polygons which form the walls.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param layer_nr The layer for which to generate the insets.
|
||||
*/
|
||||
void processInsets(SliceDataStorage& storage, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Generate the outline of the ooze shield.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param totalLayers The total number of layers
|
||||
*/
|
||||
void processOozeShield(SliceDataStorage& storage, unsigned int totalLayers);
|
||||
|
||||
/*!
|
||||
* Generate the skin areas.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param layer_nr The layer for which to generate the skin areas.
|
||||
*/
|
||||
void processSkins(SliceDataStorage& storage, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* 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 totalLayers The total number of layers
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, unsigned int totalLayers);
|
||||
/*!
|
||||
* Generate the skirt/brim/raft areas/insets.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
*/
|
||||
void processPlatformAdhesion(SliceDataStorage& storage);
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* Special mode: Make the outer wall 'fuzzy'
|
||||
*/
|
||||
void processFuzzySkin(SliceMeshStorage& mesh);
|
||||
|
||||
|
||||
/*!
|
||||
* Special mode: bulge the outer walls
|
||||
*/
|
||||
void bulgeWalls(std::vector< Slicer* > slicerList, MeshGroup* meshgroup);
|
||||
|
||||
};
|
||||
}//namespace cura
|
||||
#endif // FFF_AREA_GENERATOR_H
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "FffProcessor.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
FffProcessor FffProcessor::instance; // definition must be in cpp
|
||||
|
||||
|
||||
std::string FffProcessor::getAllSettingsString(MeshGroup& meshgroup, bool first_meshgroup)
|
||||
{
|
||||
std::stringstream sstream;
|
||||
if (first_meshgroup)
|
||||
{
|
||||
sstream << " -g";
|
||||
}
|
||||
else
|
||||
{
|
||||
sstream << " --next";
|
||||
}
|
||||
sstream << meshgroup.getAllLocalSettingsString();
|
||||
for (int extruder_nr = 0; extruder_nr < meshgroup.getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
ExtruderTrain* train = meshgroup.getExtruderTrain(extruder_nr);
|
||||
sstream << " -e" << extruder_nr << train->getAllLocalSettingsString();
|
||||
}
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < meshgroup.meshes.size(); mesh_idx++)
|
||||
{
|
||||
Mesh& mesh = meshgroup.meshes[mesh_idx];
|
||||
sstream << " -e" << mesh.getSettingAsCount("extruder_nr") << " -l \"" << mesh_idx << "\"" << mesh.getAllLocalSettingsString();
|
||||
}
|
||||
sstream << "\n";
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
bool FffProcessor::processFiles(const std::vector< std::string >& files)
|
||||
{
|
||||
time_keeper.restart();
|
||||
MeshGroup* meshgroup = new MeshGroup(this);
|
||||
|
||||
for(std::string filename : files)
|
||||
{
|
||||
log("Loading %s from disk...\n", filename.c_str());
|
||||
|
||||
FMatrix3x3 matrix;
|
||||
if (!loadMeshIntoMeshGroup(meshgroup, filename.c_str(), matrix))
|
||||
{
|
||||
logError("Failed to load model: %s\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
meshgroup->finalize();
|
||||
|
||||
log("Loaded from disk in %5.3fs\n", time_keeper.restart());
|
||||
return processMeshGroup(meshgroup);
|
||||
}
|
||||
|
||||
bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
{
|
||||
if (SHOW_ALL_SETTINGS) { logWarning(getAllSettingsString(*meshgroup, first_meshgroup).c_str()); }
|
||||
time_keeper.restart();
|
||||
if (!meshgroup)
|
||||
return false;
|
||||
|
||||
TimeKeeper time_keeper_total;
|
||||
|
||||
if (meshgroup->getSettingBoolean("wireframe_enabled"))
|
||||
{
|
||||
log("starting Neith Weaver...\n");
|
||||
|
||||
Weaver w(this);
|
||||
w.weave(meshgroup, command_socket);
|
||||
|
||||
log("starting Neith Gcode generation...\n");
|
||||
Wireframe2gcode gcoder(w, gcode_writer.gcode, this);
|
||||
gcoder.writeGCode(command_socket);
|
||||
log("finished Neith Gcode generation...\n");
|
||||
|
||||
} else
|
||||
{
|
||||
SliceDataStorage storage(meshgroup);
|
||||
|
||||
if (!polygon_generator.generateAreas(storage, meshgroup, time_keeper))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
gcode_writer.setCommandSocket(command_socket);
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::EXPORT, &time_keeper, command_socket);
|
||||
gcode_writer.writeGCode(storage, time_keeper);
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::FINISH, 1, 1, command_socket); //Report the GUI that a file has been fully processed.
|
||||
log("Total time elapsed %5.2fs.\n", time_keeper_total.restart());
|
||||
|
||||
profile_string += getAllSettingsString(*meshgroup, first_meshgroup);
|
||||
first_meshgroup = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,97 @@
|
||||
#ifndef FFF_PROCESSOR_H
|
||||
#define FFF_PROCESSOR_H
|
||||
|
||||
#include "settings.h"
|
||||
#include "FffGcodeWriter.h"
|
||||
#include "FffPolygonGenerator.h"
|
||||
#include "commandSocket.h"
|
||||
#include "Weaver.h"
|
||||
#include "Wireframe2gcode.h"
|
||||
#include "Progress.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/NoCopy.h"
|
||||
|
||||
#define SHOW_ALL_SETTINGS true
|
||||
|
||||
namespace cura {
|
||||
|
||||
//FusedFilamentFabrication processor. Singleton class
|
||||
class FffProcessor : public SettingsBase , NoCopy
|
||||
{
|
||||
private:
|
||||
static FffProcessor instance;
|
||||
|
||||
FffProcessor()
|
||||
: polygon_generator(this)
|
||||
, gcode_writer(this)
|
||||
, first_meshgroup(true)
|
||||
{
|
||||
command_socket = NULL;
|
||||
}
|
||||
public:
|
||||
static FffProcessor* getInstance()
|
||||
{
|
||||
return &instance;
|
||||
}
|
||||
|
||||
private:
|
||||
FffPolygonGenerator polygon_generator;
|
||||
FffGcodeWriter gcode_writer;
|
||||
CommandSocket* command_socket;
|
||||
|
||||
bool first_meshgroup;
|
||||
|
||||
std::string profile_string = "";
|
||||
|
||||
std::string getAllSettingsString(MeshGroup& meshgroup, bool first_meshgroup);
|
||||
|
||||
public:
|
||||
std::string getProfileString() { return profile_string; }
|
||||
|
||||
TimeKeeper time_keeper; // TODO: use singleton time keeper
|
||||
|
||||
void resetFileNumber()
|
||||
{
|
||||
gcode_writer.resetFileNumber();
|
||||
}
|
||||
|
||||
void setCommandSocket(CommandSocket* socket)
|
||||
{
|
||||
command_socket = socket;
|
||||
gcode_writer.setCommandSocket(socket);
|
||||
polygon_generator.setCommandSocket(socket);
|
||||
}
|
||||
|
||||
bool setTargetFile(const char* filename)
|
||||
{
|
||||
return gcode_writer.setTargetFile(filename);
|
||||
}
|
||||
|
||||
void setTargetStream(std::ostream* stream)
|
||||
{
|
||||
return gcode_writer.setTargetStream(stream);
|
||||
}
|
||||
|
||||
double getTotalFilamentUsed(int e)
|
||||
{
|
||||
return gcode_writer.getTotalFilamentUsed(e);
|
||||
}
|
||||
|
||||
double getTotalPrintTime()
|
||||
{
|
||||
return gcode_writer.getTotalPrintTime();
|
||||
}
|
||||
|
||||
void finalize()
|
||||
{
|
||||
gcode_writer.finalize();
|
||||
}
|
||||
|
||||
bool processFiles(const std::vector<std::string> &files);
|
||||
|
||||
bool processMeshGroup(MeshGroup* meshgroup);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//FFF_PROCESSOR_H
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "MergeInfillLines.h"
|
||||
|
||||
#include <algorithm> // min
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void MergeInfillLines::writeCompensatedMove(Point& to, double speed, GCodePath& last_path, int64_t new_line_width)
|
||||
{
|
||||
double old_line_width = INT2MM(last_path.config->getLineWidth());
|
||||
double new_line_width_mm = INT2MM(new_line_width);
|
||||
double speed_mod = old_line_width / new_line_width_mm;
|
||||
double extrusion_mod = new_line_width_mm / old_line_width;
|
||||
double new_speed = std::min(speed * speed_mod, 150.0); // TODO: hardcoded value: max extrusion speed is 150 mm/s = 9000 mm/min
|
||||
gcode.writeMove(to, new_speed, last_path.getExtrusionMM3perMM() * extrusion_mod);
|
||||
}
|
||||
|
||||
bool MergeInfillLines::mergeInfillLines(double speed, unsigned int& path_idx)
|
||||
{ //Check for lots of small moves and combine them into one large line
|
||||
Point prev_middle;
|
||||
Point last_middle;
|
||||
int64_t line_width;
|
||||
|
||||
MergeInfillLines merger(gcode, paths, travelConfig, nozzle_size);
|
||||
|
||||
if (merger.isConvertible(path_idx, prev_middle, last_middle, line_width, false))
|
||||
{
|
||||
// path_idx + 3 is the index of the second extrusion move to be converted in combination with the first
|
||||
{
|
||||
GCodePath& move_path = paths[path_idx];
|
||||
for(unsigned int point_idx = 0; point_idx < move_path.points.size() - 1; point_idx++)
|
||||
{
|
||||
gcode.writeMove(move_path.points[point_idx], speed, move_path.getExtrusionMM3perMM());
|
||||
}
|
||||
gcode.writeMove(prev_middle, travelConfig.getSpeed(), 0);
|
||||
GCodePath& last_path = paths[path_idx + 3];
|
||||
|
||||
writeCompensatedMove(last_middle, speed, last_path, line_width);
|
||||
}
|
||||
|
||||
path_idx += 2;
|
||||
for (; merger.isConvertible(path_idx, prev_middle, last_middle, line_width, true); path_idx += 2)
|
||||
{
|
||||
GCodePath& last_path = paths[path_idx + 3];
|
||||
writeCompensatedMove(last_middle, speed, last_path, line_width);
|
||||
}
|
||||
path_idx = path_idx + 1; // means that the next path considered is the travel path after the converted extrusion path corresponding to the updated path_idx
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool MergeInfillLines::isConvertible(unsigned int path_idx_first_move, Point& first_middle, Point& second_middle, int64_t& line_width, bool use_second_middle_as_first)
|
||||
{
|
||||
int64_t max_line_width = nozzle_size * 3 / 2;
|
||||
|
||||
|
||||
unsigned int idx = path_idx_first_move;
|
||||
if (idx + 3 > paths.size()-1) return false;
|
||||
if (paths[idx+0].config != &travelConfig) return false;
|
||||
if (paths[idx+1].points.size() > 1) return false;
|
||||
if (paths[idx+1].config == &travelConfig) return false;
|
||||
// if (paths[idx+2].points.size() > 1) return false;
|
||||
if (paths[idx+2].config != &travelConfig) return false;
|
||||
if (paths[idx+3].points.size() > 1) return false;
|
||||
if (paths[idx+3].config == &travelConfig) return false;
|
||||
|
||||
Point& a = paths[idx+0].points.back(); // first extruded line from
|
||||
Point& b = paths[idx+1].points.back(); // first extruded line to
|
||||
Point& c = paths[idx+2].points.back(); // second extruded line from
|
||||
Point& d = paths[idx+3].points.back(); // second extruded line to
|
||||
Point ab = b - a;
|
||||
Point cd = d - c;
|
||||
|
||||
int64_t prod = dot(ab,cd);
|
||||
if (std::abs(prod) + 400 < vSize(ab) * vSize(cd)) // 400 = 20*20, where 20 micron is the allowed inaccuracy in the dot product, introduced by the inaccurate point locations of a,b,c,d
|
||||
return false; // extrusion moves not in the same or opposite diraction
|
||||
if (prod < 0) { ab = ab * -1; }
|
||||
|
||||
|
||||
Point infill_vector = (cd + ab) / 2;
|
||||
|
||||
if (!shorterThen(infill_vector, 5 * nozzle_size)) return false; // infill lines too far apart
|
||||
|
||||
first_middle = (use_second_middle_as_first)?
|
||||
second_middle :
|
||||
(a + b) / 2;
|
||||
second_middle = (c + d) / 2;
|
||||
|
||||
Point dir_vector_perp = crossZ(second_middle - first_middle);
|
||||
int64_t dir_vector_perp_length = vSize(dir_vector_perp); // == dir_vector_length
|
||||
if (dir_vector_perp_length == 0) return false;
|
||||
if (dir_vector_perp_length > 5 * nozzle_size) return false; // infill lines too far apart
|
||||
|
||||
|
||||
line_width = std::abs( dot(dir_vector_perp, infill_vector) / dir_vector_perp_length );
|
||||
if (line_width > max_line_width) return false; // combined lines would be too wide
|
||||
if (line_width == 0) return false; // dot is zero, so lines are in each others extension, not next to eachother
|
||||
|
||||
{ // check whether the two lines are adjacent
|
||||
Point ca = first_middle - c;
|
||||
double ca_size = vSizeMM(ca);
|
||||
double cd_size = vSizeMM(cd);
|
||||
double prod = INT2MM(dot(ca, cd));
|
||||
double fraction = prod / ( ca_size * cd_size );
|
||||
int64_t line2line_dist = MM2INT(cd_size * std::sqrt(1.0 - fraction * fraction));
|
||||
|
||||
if (line2line_dist + 20 > paths[idx+1].config->getLineWidth()) return false; // there is a gap between the two lines
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/*
|
||||
void MergeInfillLines::merge(Point& from, Point& p0, Point& p1)
|
||||
{ //Check for lots of small moves and combine them into one large line
|
||||
if (path->points.size() == 1 && path->config != &travelConfig); // && shorterThen(from - path->points[0], path->config->getLineWidth() * 2))
|
||||
{
|
||||
Point p0 = path->points[0];
|
||||
unsigned int path_idx_last = path_idx + 1; // index of the last short move
|
||||
while(path_idx_last < paths.size() && paths[path_idx_last].points.size() == 1 && shorterThen(p0 - paths[path_idx_last].points[0], path->config->getLineWidth() * 2))
|
||||
{
|
||||
p0 = paths[path_idx_last].points[0];
|
||||
path_idx_last ++;
|
||||
}
|
||||
if (paths[path_idx_last-1].config == &travelConfig)
|
||||
path_idx_last --;
|
||||
|
||||
if (path_idx_last > path_idx + 2)
|
||||
{
|
||||
p0 = from;
|
||||
for(unsigned int path_idx_short = path_idx; path_idx_short < path_idx_last-1; path_idx_short+=2)
|
||||
{
|
||||
int64_t oldLen = vSize(p0 - paths[path_idx_short].points[0]);
|
||||
Point newPoint = (paths[path_idx_short].points[0] + paths[path_idx_short+1].points[0]) / 2;
|
||||
int64_t newLen = vSize(from - newPoint);
|
||||
if (newLen > 0)
|
||||
{
|
||||
if (oldLen > 0)
|
||||
gcode.writeMove(newPoint, speed * oldLen / newLen, path->getExtrusionMM3perMM() * newLen / oldLen);
|
||||
else
|
||||
gcode.writeMove(newPoint, speed, path->getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
gcode.writeMove(paths[path_idx_last-1].points[0], speed, path->getExtrusionMM3perMM());
|
||||
path_idx = path_idx_last - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef MERGE_INFILL_LINES_H
|
||||
#define MERGE_INFILL_LINES_H
|
||||
|
||||
#include "utils/intpoint.h"
|
||||
#include "gcodeExport.h"
|
||||
#include "gcodePlanner.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class MergeInfillLines
|
||||
{
|
||||
// void merge(Point& from, Point& p0, Point& p1);
|
||||
GCodeExport& gcode; //!< Where to write the combined line to
|
||||
std::vector<GCodePath>& paths; //!< The paths currently under consideration
|
||||
GCodePathConfig& travelConfig; //!< The travel settings used to see whether a path is a travel path or an extrusion path
|
||||
int64_t nozzle_size; //!< The diameter of the hole in the nozzle
|
||||
|
||||
|
||||
/*!
|
||||
* Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
|
||||
* \param path_idx_first_move Index into MergeInfillLines::paths to the travel before the two extrusion moves udner consideration
|
||||
* \param first_middle Output parameter: the middle of the first extrusion move
|
||||
* \param second_middle Input/Output parameter: outputs the middle of the second extrusion move; inputs \p first_middle so we don't have to compute it
|
||||
* \param line_width Output parameter: The width of the resulting combined line (the average length of the lines combined)
|
||||
* \param use_second_middle_as_first Whether to use \p second_middle as input parameter for \p first_middle
|
||||
* \return Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
|
||||
*/
|
||||
bool isConvertible(unsigned int path_idx_first_move, Point& first_middle, Point& second_middle, int64_t& line_width, bool use_second_middle_as_first);
|
||||
|
||||
/*!
|
||||
* Write an extrusion move with compensated width and compensated speed so that the material flow will be the same.
|
||||
*
|
||||
* \param to The point to move to
|
||||
* \param speed The original speed
|
||||
* \param old_path The original path
|
||||
* \param new_line_width The width of the convewrted line (approximately the length of the original line)
|
||||
*/
|
||||
void writeCompensatedMove(Point& to, double speed, GCodePath& old_path, int64_t new_line_width);
|
||||
public:
|
||||
/*!
|
||||
* Simple constructor only used by MergeInfillLines::isConvertible to easily convey the environment
|
||||
*/
|
||||
MergeInfillLines(GCodeExport& gcode, std::vector<GCodePath>& paths, GCodePathConfig& travelConfig, int64_t nozzle_size)
|
||||
: gcode(gcode), paths(paths), travelConfig(travelConfig), nozzle_size(nozzle_size) { }
|
||||
|
||||
/*!
|
||||
* Check for lots of small moves and combine them into one large line.
|
||||
* Updates \p path_idx to the next path which is not combined.
|
||||
*
|
||||
* \param gcode Where to write the combined line to
|
||||
* \param paths The paths currently under consideration
|
||||
* \param travelConfig The travel settings used to see whether a path is a travel path or an extrusion path
|
||||
* \param nozzle_size The diameter of the hole in the nozzle
|
||||
* \param speed A factor used to scale the movement speed
|
||||
* \param path_idx Input/Output parameter: The current index in \p paths where to start combining and the current index after combining as output parameter.
|
||||
* \return Whether lines have been merged and normal path-to-gcode generation can be skipped for the current resulting \p path_idx .
|
||||
*/
|
||||
bool mergeInfillLines(double speed, unsigned int& path_idx);
|
||||
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif // MERGE_INFILL_LINES_H
|
||||
@@ -3,9 +3,12 @@
|
||||
#include <strings.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "modelFile.h"
|
||||
#include "../utils/logoutput.h"
|
||||
#include "../utils/string.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/string.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
FILE* binaryMeshBlob = nullptr;
|
||||
|
||||
@@ -25,7 +28,7 @@ void* fgets_(char* ptr, size_t len, FILE* f)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool loadModelSTL_ascii(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
bool loadMeshSTL_ascii(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
{
|
||||
FILE* f = fopen(filename, "rt");
|
||||
char buffer[1024];
|
||||
@@ -58,7 +61,7 @@ bool loadModelSTL_ascii(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadModelSTL_binary(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
bool loadMeshSTL_binary(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
{
|
||||
FILE* f = fopen(filename, "rb");
|
||||
char buffer[80];
|
||||
@@ -77,35 +80,29 @@ bool loadModelSTL_binary(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
}
|
||||
//For each face read:
|
||||
//float(x,y,z) = normal, float(X,Y,Z)*3 = vertexes, uint16_t = flags
|
||||
// Every Face is 50 Bytes: Normal(3*float), Vertices(9*float), 2 Bytes Spacer
|
||||
mesh->faces.reserve(faceCount);
|
||||
mesh->vertices.reserve(faceCount);
|
||||
for(unsigned int i=0;i<faceCount;i++)
|
||||
{
|
||||
if (fread(buffer, sizeof(float) * 3, 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
float v[9];
|
||||
if (fread(v, sizeof(float) * 9, 1, f) != 1)
|
||||
if (fread(buffer, 50, 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
float *v= ((float*)buffer)+3;
|
||||
|
||||
Point3 v0 = matrix.apply(FPoint3(v[0], v[1], v[2]));
|
||||
Point3 v1 = matrix.apply(FPoint3(v[3], v[4], v[5]));
|
||||
Point3 v2 = matrix.apply(FPoint3(v[6], v[7], v[8]));
|
||||
mesh->addFace(v0, v1, v2);
|
||||
if (fread(buffer, sizeof(uint16_t), 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
mesh->finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadModelSTL(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
bool loadMeshSTL(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
{
|
||||
FILE* f = fopen(filename, "r");
|
||||
char buffer[6];
|
||||
@@ -122,7 +119,7 @@ bool loadModelSTL(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
buffer[5] = '\0';
|
||||
if (stringcasecompare(buffer, "solid") == 0)
|
||||
{
|
||||
bool load_success = loadModelSTL_ascii(mesh, filename, matrix);
|
||||
bool load_success = loadMeshSTL_ascii(mesh, filename, matrix);
|
||||
if (!load_success)
|
||||
return false;
|
||||
|
||||
@@ -131,20 +128,29 @@ bool loadModelSTL(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
if (mesh->faces.size() < 1)
|
||||
{
|
||||
mesh->clear();
|
||||
return loadModelSTL_binary(mesh, filename, matrix);
|
||||
return loadMeshSTL_binary(mesh, filename, matrix);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return loadModelSTL_binary(mesh, filename, matrix);
|
||||
return loadMeshSTL_binary(mesh, filename, matrix);
|
||||
}
|
||||
|
||||
bool loadMeshFromFile(PrintObject* object, const char* filename, FMatrix3x3& matrix)
|
||||
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings)
|
||||
{
|
||||
const char* ext = strrchr(filename, '.');
|
||||
if (ext && (strcmp(ext, ".stl") == 0 || strcmp(ext, ".STL") == 0))
|
||||
{
|
||||
object->meshes.emplace_back(object);
|
||||
return loadModelSTL(&object->meshes[object->meshes.size()-1], filename, matrix);
|
||||
if (object_parent_settings)
|
||||
{
|
||||
meshgroup->meshes.emplace_back(object_parent_settings); // make new mesh with [object_parent_settings] as parent settings object
|
||||
}
|
||||
else
|
||||
{
|
||||
meshgroup->meshes.emplace_back(meshgroup); // make new mesh with [meshgroup] as parent settings object
|
||||
}
|
||||
return loadMeshSTL(&meshgroup->meshes[meshgroup->meshes.size()-1], filename, transformation);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,137 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef MESH_GROUP_H
|
||||
#define MESH_GROUP_H
|
||||
|
||||
#include "mesh.h"
|
||||
#include "ExtruderTrain.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A MeshGroup is a collection with 1 or more 3D meshes.
|
||||
*
|
||||
* One MeshGroup is a whole which is printed at once.
|
||||
* Generally there is one single MeshGroup, though when using one-at-a-time printing, multiple MeshGroups are processed consecutively.
|
||||
*/
|
||||
class MeshGroup : public SettingsBase
|
||||
{
|
||||
ExtruderTrain* extruders[MAX_EXTRUDERS] = {nullptr};
|
||||
int extruder_count;
|
||||
public:
|
||||
int getExtruderCount()
|
||||
{
|
||||
if (extruder_count == -1)
|
||||
{
|
||||
extruder_count = getSettingAsCount("machine_extruder_count");
|
||||
}
|
||||
return extruder_count;
|
||||
}
|
||||
|
||||
MeshGroup(SettingsBaseVirtual* settings_base)
|
||||
: SettingsBase(settings_base)
|
||||
, extruder_count(-1)
|
||||
{}
|
||||
|
||||
~MeshGroup()
|
||||
{
|
||||
for (unsigned int extruder = 0; extruder < MAX_EXTRUDERS; extruder++)
|
||||
{
|
||||
if (extruders[extruder])
|
||||
{
|
||||
delete extruders[extruder];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExtruderTrain* getExtruderTrain(unsigned int extruder_nr)
|
||||
{
|
||||
if (!extruders[extruder_nr])
|
||||
{
|
||||
extruders[extruder_nr] = new ExtruderTrain(this, extruder_nr);
|
||||
}
|
||||
return extruders[extruder_nr];
|
||||
}
|
||||
|
||||
std::vector<Mesh> meshes;
|
||||
|
||||
Point3 min() //! minimal corner of bounding box
|
||||
{
|
||||
if (meshes.size() < 1)
|
||||
{
|
||||
return Point3(0, 0, 0);
|
||||
}
|
||||
Point3 ret = meshes[0].min();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
{
|
||||
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);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Point3 max() //! maximal corner of bounding box
|
||||
{
|
||||
if (meshes.size() < 1)
|
||||
{
|
||||
return Point3(0, 0, 0);
|
||||
}
|
||||
Point3 ret = meshes[0].max();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
{
|
||||
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);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for(Mesh& m : meshes)
|
||||
{
|
||||
m.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void finalize()
|
||||
{
|
||||
//If the machine settings have been supplied, offset the given position vertices to the center of vertices (0,0,0) is at the bed center.
|
||||
Point3 meshgroup_offset(0, 0, 0);
|
||||
if (!getSettingBoolean("machine_center_is_zero"))
|
||||
{
|
||||
meshgroup_offset.x = getSettingInMicrons("machine_width") / 2;
|
||||
meshgroup_offset.y = getSettingInMicrons("machine_depth") / 2;
|
||||
}
|
||||
|
||||
// If a mesh position was given, put the mesh at this position in 3D space.
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Load a Mesh from file and store it in the \p meshgroup.
|
||||
*
|
||||
* \param meshgroup The meshgroup where to store the mesh
|
||||
* \param filename The filename of the mesh file
|
||||
* \param transformation The transformation applied to all vertices
|
||||
* \param object_parent_settings (optional) The parent settings object of the new mesh. Defaults to \p meshgroup if none is given.
|
||||
* \return whether the file could be loaded
|
||||
*/
|
||||
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings = nullptr);
|
||||
|
||||
}//namespace cura
|
||||
#endif//MESH_GROUP_H
|
||||
@@ -0,0 +1,287 @@
|
||||
#include "PrimeTower.h"
|
||||
|
||||
#include "ExtruderTrain.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "gcodeExport.h"
|
||||
#include "gcodePlanner.h"
|
||||
#include "infill.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
PrimeTower::PrimeTower()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrimeTower::setConfigs(MeshGroup* meshgroup, std::vector<RetractionConfig>& retraction_config_per_extruder, int layer_thickness)
|
||||
{
|
||||
|
||||
extruder_count = meshgroup->getSettingAsCount("machine_extruder_count");
|
||||
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
{
|
||||
ExtruderTrain* train = meshgroup->getExtruderTrain(extr);
|
||||
config_per_extruder.emplace_back(&retraction_config_per_extruder[extr], "WALL-INNER");// so that visualization in the old Cura still works (TODO)
|
||||
GCodePathConfig& conf = config_per_extruder.back();
|
||||
|
||||
conf.setSpeed(train->getSettingInMillimetersPerSecond("speed_prime_tower"));
|
||||
conf.setLineWidth(train->getSettingInMicrons("prime_tower_line_width"));
|
||||
conf.setFlow(train->getSettingInPercentage("prime_tower_flow"));
|
||||
conf.setLayerHeight(layer_thickness);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrimeTower::computePrimeTowerMax(SliceDataStorage& storage)
|
||||
{ // compute storage.max_object_height_second_to_last_extruder, which is used to determine the highest point in the prime tower
|
||||
|
||||
extruder_count = storage.getSettingAsCount("machine_extruder_count");
|
||||
|
||||
int max_object_height_per_extruder[extruder_count];
|
||||
{ // compute max_object_height_per_extruder
|
||||
memset(max_object_height_per_extruder, -1, sizeof(max_object_height_per_extruder));
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
max_object_height_per_extruder[mesh.getSettingAsIndex("extruder_nr")] =
|
||||
std::max( max_object_height_per_extruder[mesh.getSettingAsIndex("extruder_nr")]
|
||||
, mesh.layer_nr_max_filled_layer );
|
||||
}
|
||||
int support_extruder_nr = storage.getSettingAsIndex("support_extruder_nr"); // TODO: support extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_extruder_nr] =
|
||||
std::max( max_object_height_per_extruder[support_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
int support_roof_extruder_nr = storage.getSettingAsIndex("support_roof_extruder_nr"); // TODO: support roof extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_roof_extruder_nr] =
|
||||
std::max( max_object_height_per_extruder[support_roof_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
}
|
||||
{ // // compute max_object_height_second_to_last_extruder
|
||||
int extruder_max_object_height = 0;
|
||||
for (int extruder_nr = 1; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
if (max_object_height_per_extruder[extruder_nr] > max_object_height_per_extruder[extruder_max_object_height])
|
||||
{
|
||||
extruder_max_object_height = extruder_nr;
|
||||
}
|
||||
}
|
||||
int extruder_second_max_object_height = -1;
|
||||
for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
if (extruder_nr == extruder_max_object_height) { continue; }
|
||||
if (max_object_height_per_extruder[extruder_nr] > max_object_height_per_extruder[extruder_second_max_object_height])
|
||||
{
|
||||
extruder_second_max_object_height = extruder_nr;
|
||||
}
|
||||
}
|
||||
if (extruder_second_max_object_height < 0)
|
||||
{
|
||||
storage.max_object_height_second_to_last_extruder = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
storage.max_object_height_second_to_last_extruder = max_object_height_per_extruder[extruder_second_max_object_height];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PrimeTower::generateGroundpoly(SliceDataStorage& storage)
|
||||
{
|
||||
PolygonRef p = storage.primeTower.ground_poly.newPoly();
|
||||
int tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
int tower_distance = 0; //storage.getSettingInMicrons("prime_tower_distance");
|
||||
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
|
||||
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
|
||||
p.add(Point(x + tower_distance, y + tower_distance));
|
||||
p.add(Point(x + tower_distance, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance));
|
||||
|
||||
storage.wipePoint = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
|
||||
}
|
||||
|
||||
void PrimeTower::generatePaths(SliceDataStorage& storage, unsigned int totalLayers)
|
||||
{
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0
|
||||
// && storage.getSettingInMicrons("prime_tower_distance") > 0
|
||||
&& storage.getSettingInMicrons("prime_tower_size") > 0)
|
||||
{
|
||||
generatePaths3(storage);
|
||||
}
|
||||
}
|
||||
void PrimeTower::generatePaths_OLD(SliceDataStorage& storage, unsigned int totalLayers)
|
||||
{
|
||||
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_distance") > 0 && storage.getSettingInMicrons("prime_tower_size") > 0)
|
||||
{
|
||||
PolygonRef p = storage.primeTower.ground_poly.newPoly();
|
||||
int tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
int tower_distance = 0; //storage.getSettingInMicrons("prime_tower_distance");
|
||||
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
|
||||
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
|
||||
p.add(Point(x + tower_distance, y + tower_distance));
|
||||
p.add(Point(x + tower_distance, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance));
|
||||
|
||||
storage.wipePoint = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PrimeTower::generatePaths2(SliceDataStorage& storage) // half baked attempt at spiral shaped prime tower pattern
|
||||
{
|
||||
// extruder_count = storage.getSettingAsCount("machine_extruder_count");
|
||||
//
|
||||
// int64_t line_dists[extruder_count + 1]; // distance between the lines of different extruders, and half the line dist for beginning and ending
|
||||
// int64_t total_width = 0;
|
||||
// {
|
||||
// int64_t last_line_width = 0;
|
||||
// for (int extr = 0; extr < extruder_count; extr++)
|
||||
// {
|
||||
// int64_t line_width = config_per_extruder[extr].getLineWidth();
|
||||
// line_dists[extr] = (line_width + last_line_width) / 2;
|
||||
// total_width += line_width;
|
||||
// last_line_width = line_width;
|
||||
// }
|
||||
// line_dists[extruder_count] = last_line_width / 2;
|
||||
// }
|
||||
//
|
||||
|
||||
|
||||
}
|
||||
|
||||
void PrimeTower::generatePaths3(SliceDataStorage& storage)
|
||||
{
|
||||
|
||||
int n_patterns = 2; // alternating patterns between layers
|
||||
double infill_overlap = 15; // so that it can't be zero
|
||||
|
||||
generateGroundpoly(storage);
|
||||
|
||||
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();
|
||||
for (int pattern_idx = 0; pattern_idx < n_patterns; pattern_idx++)
|
||||
{
|
||||
generateLineInfill(ground_poly, -line_width/2, patterns[pattern_idx], line_width, line_width, infill_overlap, 45 + pattern_idx*90);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrimeTower::addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
{
|
||||
if (!( storage.max_object_height_second_to_last_extruder >= 0
|
||||
// && storage.getSettingInMicrons("prime_tower_distance") > 0
|
||||
&& storage.getSettingInMicrons("prime_tower_size") > 0) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool prime_tower_added = false;
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount() && !prime_tower_added; extruder++)
|
||||
{
|
||||
prime_tower_added = last_prime_tower_poly_printed[extruder] == int(layer_nr);
|
||||
}
|
||||
if (prime_tower_added)
|
||||
{ // don't print the prime tower if it has been printed already
|
||||
return;
|
||||
}
|
||||
|
||||
if (prev_extruder == gcodeLayer.getExtruder())
|
||||
{
|
||||
wipe = false;
|
||||
}
|
||||
addToGcode3(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed);
|
||||
}
|
||||
|
||||
void PrimeTower::addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
{
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
|
||||
|
||||
Polygons& pattern = patterns_per_extruder[new_extruder][layer_nr % 2];
|
||||
|
||||
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
int start_idx = 0; // TODO: figure out which idx is closest to the far right corner
|
||||
gcodeLayer.addPolygon(ground_poly.back(), start_idx, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern, &config);
|
||||
|
||||
last_prime_tower_poly_printed[new_extruder] = layer_nr;
|
||||
|
||||
if (wipe)
|
||||
{ //Make sure we wipe the old extruder on the prime tower.
|
||||
gcodeLayer.addTravel(storage.wipePoint - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
|
||||
}
|
||||
}
|
||||
|
||||
void PrimeTower::addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
{
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
|
||||
int64_t offset = -config_per_extruder[new_extruder].getLineWidth();
|
||||
if (layer_nr > 0)
|
||||
offset *= 2;
|
||||
|
||||
//If we changed extruder, print the wipe/prime tower for this nozzle;
|
||||
std::vector<Polygons> insets;
|
||||
{ // generate polygons
|
||||
if ((layer_nr % 2) == 1)
|
||||
insets.push_back(storage.primeTower.ground_poly.offset(offset / 2));
|
||||
else
|
||||
insets.push_back(storage.primeTower.ground_poly);
|
||||
while(true)
|
||||
{
|
||||
Polygons new_inset = insets[insets.size() - 1].offset(offset);
|
||||
if (new_inset.size() < 1)
|
||||
break;
|
||||
insets.push_back(new_inset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for(unsigned int n=0; n<insets.size(); n++)
|
||||
{
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
gcodeLayer.addPolygonsByOptimizer(insets[(prime_tower_dir_outward)? insets.size() - 1 - n : n], &config);
|
||||
}
|
||||
last_prime_tower_poly_printed[new_extruder] = layer_nr;
|
||||
|
||||
if (wipe)
|
||||
{ //Make sure we wipe the old extruder on the prime tower.
|
||||
gcodeLayer.addTravel(storage.wipePoint - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef PRIME_TOWER_H
|
||||
#define PRIME_TOWER_H
|
||||
|
||||
#include "gcodeExport.h" // GCodePathConfig
|
||||
#include "MeshGroup.h"
|
||||
#include "utils/polygon.h" // Polygons
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
class SliceDataStorage;
|
||||
class GCodePlanner;
|
||||
class GCodeExport;
|
||||
|
||||
typedef std::vector<IntPoint> PolyLine;
|
||||
|
||||
class PrimeTower
|
||||
{
|
||||
private:
|
||||
int extruder_count;
|
||||
std::vector<GCodePathConfig> config_per_extruder;
|
||||
|
||||
class WallInfill
|
||||
{
|
||||
|
||||
};
|
||||
public:
|
||||
void setConfigs(MeshGroup* configs, std::vector<RetractionConfig>& retraction_config_per_extruder, int layer_thickness);
|
||||
|
||||
Polygons ground_poly;
|
||||
|
||||
std::vector<PolyLine> extruder_paths;
|
||||
|
||||
|
||||
void generateGroundpoly(SliceDataStorage& storage);
|
||||
|
||||
std::vector<std::vector<Polygons>> patterns_per_extruder; //!< for each extruder a vector of patterns to alternate between, over the layers
|
||||
|
||||
void generatePaths3(SliceDataStorage& storage);
|
||||
|
||||
void generatePaths2(SliceDataStorage& storage);
|
||||
/*!
|
||||
* Generate the area where the prime tower should be.
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param totalLayers The total number of layers
|
||||
*/
|
||||
void generatePaths(SliceDataStorage& storage, unsigned int totalLayers);
|
||||
void generatePaths_OLD(SliceDataStorage& storage, unsigned int totalLayers);
|
||||
|
||||
void computePrimeTowerMax(SliceDataStorage& storage);
|
||||
|
||||
PrimeTower();
|
||||
|
||||
void addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
void addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
void addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif // PRIME_TOWER_H
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef PRINT_FEATURE
|
||||
#define PRINT_FEATURE
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
enum class EPrintFeature : unsigned int
|
||||
{
|
||||
OUTER_WALL,
|
||||
INNER_WALLS,
|
||||
INFILL,
|
||||
SKIN,
|
||||
HELPERS,
|
||||
UNCLASSIFIED,
|
||||
ENUM_COUNT
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // PRINT_FEATURE
|
||||
@@ -0,0 +1,101 @@
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "Progress.h"
|
||||
|
||||
#include "commandSocket.h"
|
||||
#include "utils/gettime.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
double Progress::times [] =
|
||||
{
|
||||
0.0,
|
||||
5.269,
|
||||
1.533,
|
||||
22.953,
|
||||
51.009,
|
||||
48.858,
|
||||
154.62,
|
||||
0.1
|
||||
};
|
||||
std::string Progress::names [] =
|
||||
{
|
||||
"start",
|
||||
"slice",
|
||||
"layerparts",
|
||||
"inset",
|
||||
"support",
|
||||
"skin",
|
||||
"export",
|
||||
"process"
|
||||
};
|
||||
|
||||
|
||||
double Progress::accumulated_times [N_PROGRESS_STAGES] = {-1};
|
||||
double Progress::total_timing = -1;
|
||||
|
||||
/*
|
||||
const Progress::Stage Progress::stages[] =
|
||||
{
|
||||
Progress::Stage::START,
|
||||
Progress::Stage::SLICING,
|
||||
Progress::Stage::PARTS,
|
||||
Progress::Stage::INSET,
|
||||
Progress::Stage::SUPPORT,
|
||||
Progress::Stage::SKIN,
|
||||
Progress::Stage::EXPORT,
|
||||
Progress::Stage::FINISH
|
||||
};
|
||||
*/
|
||||
|
||||
float Progress::calcOverallProgress(Stage stage, float stage_progress)
|
||||
{
|
||||
return ( accumulated_times[(int)stage] + stage_progress * times[(int)stage] ) / total_timing;
|
||||
}
|
||||
|
||||
|
||||
void Progress::init()
|
||||
{
|
||||
double accumulated_time = 0;
|
||||
for (int stage = 0; stage < N_PROGRESS_STAGES; stage++)
|
||||
{
|
||||
accumulated_times[(int)stage] = accumulated_time;
|
||||
accumulated_time += times[(int)stage];
|
||||
}
|
||||
total_timing = accumulated_time;
|
||||
}
|
||||
|
||||
void Progress::messageProgress(Progress::Stage stage, int progress_in_stage, int progress_in_stage_max, CommandSocket* command_socket)
|
||||
{
|
||||
float percentage = calcOverallProgress(stage, float(progress_in_stage) / float(progress_in_stage_max));
|
||||
if (command_socket)
|
||||
{
|
||||
command_socket->sendProgress(percentage);
|
||||
}
|
||||
|
||||
logProgress(names[(int)stage].c_str(), progress_in_stage, progress_in_stage_max, percentage);
|
||||
}
|
||||
|
||||
void Progress::messageProgressStage(Progress::Stage stage, TimeKeeper* time_keeper, CommandSocket* command_socket)
|
||||
{
|
||||
if (command_socket)
|
||||
{
|
||||
command_socket->sendProgressStage(stage);
|
||||
}
|
||||
|
||||
if (time_keeper)
|
||||
{
|
||||
if ((int)stage > 0)
|
||||
{
|
||||
log("Progress: %s accomplished in %5.3fs\n", names[(int)stage-1].c_str(), time_keeper->restart());
|
||||
}
|
||||
else
|
||||
{
|
||||
time_keeper->restart();
|
||||
}
|
||||
|
||||
if ((int)stage < (int)Stage::FINISH)
|
||||
log("Starting %s...\n", names[(int)stage].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace cura
|
||||
@@ -0,0 +1,75 @@
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef PROGRESS_H
|
||||
#define PROGRESS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/gettime.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
class CommandSocket;
|
||||
|
||||
#define N_PROGRESS_STAGES 8
|
||||
|
||||
/*!
|
||||
* Class for handling the progress bar and the progress logging.
|
||||
*
|
||||
* The progress bar is based on a single slicing of a rather large model which needs some complex support;
|
||||
* the relative timing of each stage is currently based on that of the slicing of dragon_65_tilted_large.stl
|
||||
*/
|
||||
class Progress
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The stage in the whole slicing process
|
||||
*/
|
||||
enum class Stage : unsigned int
|
||||
{
|
||||
START = 0,
|
||||
SLICING = 1,
|
||||
PARTS = 2,
|
||||
INSET = 3,
|
||||
SUPPORT = 4,
|
||||
SKIN = 5,
|
||||
EXPORT = 6,
|
||||
FINISH = 7
|
||||
};
|
||||
private:
|
||||
static double times [N_PROGRESS_STAGES]; //!< Time estimates per stage
|
||||
static std::string names[N_PROGRESS_STAGES]; //!< name of each stage
|
||||
static double accumulated_times [N_PROGRESS_STAGES]; //!< Time past before each stage
|
||||
static double total_timing; //!< An estimate of the total time
|
||||
/*!
|
||||
* Give an estimate between 0 and 1 of how far the process is.
|
||||
*
|
||||
* \param stage The current stage of processing
|
||||
* \param stage_process How far we currently are in the \p stage
|
||||
* \return An estimate of the overall progress.
|
||||
*/
|
||||
static float calcOverallProgress(Stage stage, float stage_progress);
|
||||
public:
|
||||
static void init(); //!< Initialize some values needed in a fast computation of the progress
|
||||
/*!
|
||||
* Message progress over the \p commandSocket and to the terminal (if the command line arg '-p' is provided).
|
||||
*
|
||||
* \param stage The current stage of processing
|
||||
* \param progress_in_stage Any number giving the progress within the stage
|
||||
* \param progress_in_stage_max The maximal value of \p progress_in_stage
|
||||
* \param commandSocket The command socket over which to communicate the progress.
|
||||
*/
|
||||
static void messageProgress(Stage stage, int progress_in_stage, int progress_in_stage_max, CommandSocket* commandSocket);
|
||||
/*!
|
||||
* Message the progress stage over the command socket.
|
||||
*
|
||||
* \param stage The current stage
|
||||
* \param timeKeeper The stapwatch keeping track of the timings for each stage (optional)
|
||||
* \param commandSocket The command socket over which to communicate (optional)
|
||||
*/
|
||||
static void messageProgressStage(Stage stage, TimeKeeper* timeKeeper, CommandSocket* commandSocket);
|
||||
};
|
||||
|
||||
|
||||
} // name space cura
|
||||
#endif//PROGRESS_H
|
||||
+23
-18
@@ -4,14 +4,17 @@
|
||||
#include <fstream> // debug IO
|
||||
#include <unistd.h>
|
||||
|
||||
#include "Progress.h"
|
||||
#include "weaveDataStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void Weaver::weave(PrintObject* object, CommandSocket* commandSocket)
|
||||
{
|
||||
int maxz = object->max().z;
|
||||
void Weaver::weave(MeshGroup* meshgroup, CommandSocket* commandSocket)
|
||||
{
|
||||
wireFrame.meshgroup = meshgroup;
|
||||
|
||||
int maxz = meshgroup->max().z;
|
||||
|
||||
int layer_count = (maxz - initial_layer_thickness) / connectionHeight + 1;
|
||||
|
||||
@@ -19,7 +22,7 @@ void Weaver::weave(PrintObject* object, CommandSocket* commandSocket)
|
||||
|
||||
std::vector<cura::Slicer*> slicerList;
|
||||
|
||||
for(Mesh& mesh : object->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"));
|
||||
slicerList.push_back(slicer);
|
||||
@@ -59,11 +62,12 @@ void Weaver::weave(PrintObject* object, CommandSocket* commandSocket)
|
||||
if (wireFrame.bottom_outline.size() > 0)
|
||||
starting_point_in_layer = (wireFrame.bottom_outline.max() + wireFrame.bottom_outline.min()) / 2;
|
||||
else
|
||||
starting_point_in_layer = (Point(0,0) + object->max() + object->min()) / 2;
|
||||
starting_point_in_layer = (Point(0,0) + meshgroup->max() + meshgroup->min()) / 2;
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::INSET, nullptr, commandSocket);
|
||||
for (int layer_idx = starting_layer_idx + 1; layer_idx < layer_count; layer_idx++)
|
||||
{
|
||||
logProgress("inset", layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
|
||||
Progress::messageProgress(Progress::Stage::INSET, layer_idx+1, layer_count, commandSocket); // abuse the progress system of the normal mode of CuraEngine
|
||||
|
||||
Polygons parts1;
|
||||
for (cura::Slicer* slicer : slicerList)
|
||||
@@ -97,9 +101,10 @@ void Weaver::weave(PrintObject* object, CommandSocket* commandSocket)
|
||||
{
|
||||
Polygons* lower_top_parts = &wireFrame.bottom_outline;
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SKIN, nullptr, commandSocket);
|
||||
for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++)
|
||||
{
|
||||
logProgress("skin", layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
|
||||
Progress::messageProgress(Progress::Stage::SKIN, layer_idx+1, wireFrame.layers.size(), commandSocket); // abuse the progress system of the normal mode of CuraEngine
|
||||
|
||||
WeaveLayer& layer = wireFrame.layers[layer_idx];
|
||||
|
||||
@@ -151,7 +156,7 @@ void Weaver::createHorizontalFill(Polygons& lower_top_parts, WeaveLayer& layer,
|
||||
{
|
||||
int64_t bridgable_dist = connectionHeight;
|
||||
|
||||
Polygons& polys_below = lower_top_parts;
|
||||
// Polygons& polys_below = lower_top_parts;
|
||||
Polygons& polys_here = layer.supported;
|
||||
Polygons& polys_above = layer_above;
|
||||
|
||||
@@ -191,8 +196,8 @@ void Weaver::fillRoofs(Polygons& supporting, Polygons& to_be_supported, int dire
|
||||
Polygons roof_outlines;
|
||||
Polygons roof_holes;
|
||||
{ // split roofs into outlines and holes
|
||||
std::vector<Polygons> roof_parts = roofs.splitIntoParts();
|
||||
for (Polygons& roof_part : roof_parts)
|
||||
std::vector<PolygonsPart> roof_parts = roofs.splitIntoParts();
|
||||
for (PolygonsPart& roof_part : roof_parts)
|
||||
{
|
||||
roof_outlines.add(roof_part[0]);
|
||||
for (unsigned int hole_idx = 1; hole_idx < roof_part.size(); hole_idx++)
|
||||
@@ -206,8 +211,8 @@ void Weaver::fillRoofs(Polygons& supporting, Polygons& to_be_supported, int dire
|
||||
|
||||
Polygons supporting_outlines;
|
||||
|
||||
std::vector<Polygons> supporting_parts = supporting.splitIntoParts();
|
||||
for (Polygons& supporting_part : supporting_parts)
|
||||
std::vector<PolygonsPart> supporting_parts = supporting.splitIntoParts();
|
||||
for (PolygonsPart& supporting_part : supporting_parts)
|
||||
supporting_outlines.add(supporting_part[0]); // only add outlines, not the holes
|
||||
|
||||
|
||||
@@ -253,11 +258,11 @@ void Weaver::fillFloors(Polygons& supporting, Polygons& to_be_supported, int dir
|
||||
if (floors.size() == 0) return;
|
||||
|
||||
|
||||
std::vector<Polygons> floor_parts = floors.splitIntoParts();
|
||||
std::vector<PolygonsPart> floor_parts = floors.splitIntoParts();
|
||||
|
||||
Polygons floor_outlines;
|
||||
Polygons floor_holes;
|
||||
for (Polygons& floor_part : floor_parts)
|
||||
for (PolygonsPart& floor_part : floor_parts)
|
||||
{
|
||||
floor_outlines.add(floor_part[0]);
|
||||
for (unsigned int hole_idx = 1; hole_idx < floor_part.size(); hole_idx++)
|
||||
@@ -376,7 +381,7 @@ void Weaver::chainify_polygons(Polygons& parts1, Point start_close_to, Polygons&
|
||||
{
|
||||
const PolygonRef upperPart = parts1[prt];
|
||||
|
||||
ClosestPolygonPoint closestInPoly = findClosest(start_close_to, upperPart);
|
||||
ClosestPolygonPoint closestInPoly = PolygonUtils::findClosest(start_close_to, upperPart);
|
||||
|
||||
|
||||
PolygonRef part_top = result.newPoly();
|
||||
@@ -387,7 +392,7 @@ void Weaver::chainify_polygons(Polygons& parts1, Point start_close_to, Polygons&
|
||||
|
||||
for (Point upper_point = upperPart[closestInPoly.pos]; found; upper_point = next_upper.location)
|
||||
{
|
||||
found = getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.pos, next_upper);
|
||||
found = PolygonUtils::getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.pos, next_upper);
|
||||
|
||||
|
||||
if (!found)
|
||||
@@ -437,7 +442,7 @@ void Weaver::connect_polygons(Polygons& supporting, int z0, Polygons& supported,
|
||||
for (const Point& upper_point : upperPart)
|
||||
{
|
||||
|
||||
ClosestPolygonPoint lowerPolyPoint = findClosest(upper_point, supporting);
|
||||
ClosestPolygonPoint lowerPolyPoint = PolygonUtils::findClosest(upper_point, supporting);
|
||||
Point& lower = lowerPolyPoint.location;
|
||||
|
||||
Point3 lower3 = Point3(lower.X, lower.Y, z0);
|
||||
@@ -469,5 +474,5 @@ void Weaver::connect_polygons(Polygons& supporting, int z0, Polygons& supported,
|
||||
|
||||
|
||||
|
||||
} // namespace cura
|
||||
}//namespace cura
|
||||
|
||||
|
||||
+5
-6
@@ -5,7 +5,7 @@
|
||||
#include "commandSocket.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include "modelFile/modelFile.h" // PrintObject
|
||||
#include "MeshGroup.h"
|
||||
#include "slicer.h"
|
||||
|
||||
#include "utils/polygon.h"
|
||||
@@ -19,7 +19,7 @@ namespace cura
|
||||
/*!
|
||||
* The main weaver / WirePrint / wireframe printing class, which computes the basic paths to be followed.
|
||||
*/
|
||||
class Weaver : public SettingsBase
|
||||
class Weaver : public SettingsMessenger
|
||||
{
|
||||
friend class Wireframe2gcode;
|
||||
private:
|
||||
@@ -40,8 +40,7 @@ private:
|
||||
|
||||
|
||||
public:
|
||||
|
||||
Weaver(SettingsBase* settings_base) : SettingsBase(settings_base)
|
||||
Weaver(SettingsBase* settings_base) : SettingsMessenger(settings_base)
|
||||
{
|
||||
|
||||
initial_layer_thickness = getSettingInMicrons("layer_height_0");
|
||||
@@ -60,10 +59,10 @@ public:
|
||||
* This is the main function for Neith / Weaving / WirePrinting / Webbed printing.
|
||||
* Creates a wireframe for the model consisting of horizontal 'flat' parts and connections between consecutive flat parts consisting of UP moves and diagonally DOWN moves.
|
||||
*
|
||||
* \param object The object for which to create a wireframe print
|
||||
* \param objects The objects for which to create a wireframe print
|
||||
* \param commandSocket the commandSocket
|
||||
*/
|
||||
void weave(PrintObject* object, CommandSocket* commandSocket);
|
||||
void weave(MeshGroup* objects, CommandSocket* commandSocket);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
+81
-34
@@ -4,36 +4,27 @@
|
||||
#include <fstream> // debug IO
|
||||
|
||||
#include "weaveDataStorage.h"
|
||||
#include "Progress.h"
|
||||
|
||||
#include "pathOrderOptimizer.h" // for skirt
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
void Wireframe2gcode::writeGCode(CommandSocket* commandSocket, int& maxObjectHeight)
|
||||
void Wireframe2gcode::writeGCode(CommandSocket* commandSocket)
|
||||
{
|
||||
|
||||
gcode.preSetup(wireFrame.meshgroup);
|
||||
|
||||
if (commandSocket)
|
||||
commandSocket->beginGCode();
|
||||
|
||||
maxObjectHeight = wireFrame.layers.back().z1;
|
||||
processStartingCode(commandSocket);
|
||||
|
||||
{ // starting Gcode
|
||||
if (hasSetting("material_bed_temperature") && getSettingInDegreeCelsius("material_bed_temperature") > 0)
|
||||
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature"), true);
|
||||
if (hasSetting("material_print_temperature") && getSettingInDegreeCelsius("material_print_temperature") > 0)
|
||||
gcode.writeTemperatureCommand(getSettingAsIndex("extruder_nr"), getSettingInDegreeCelsius("material_print_temperature"));
|
||||
|
||||
gcode.writeCode(getSettingString("machine_start_gcode").c_str());
|
||||
if (gcode.getFlavor() == GCODE_FLAVOR_BFB)
|
||||
{
|
||||
gcode.writeComment("enable auto-retraction");
|
||||
std::ostringstream tmp;
|
||||
tmp << "M227 S" << (getSettingInMicrons("retraction_amount") * 2560 / 1000) << " P" << (getSettingInMicrons("retraction_amount") * 2560 / 1000); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
|
||||
gcode.writeLine(tmp.str().c_str());
|
||||
}
|
||||
}
|
||||
int maxObjectHeight = wireFrame.layers.back().z1;
|
||||
|
||||
processSkirt(commandSocket);
|
||||
|
||||
|
||||
unsigned int totalLayers = wireFrame.layers.size();
|
||||
@@ -77,18 +68,16 @@ void Wireframe2gcode::writeGCode(CommandSocket* commandSocket, int& maxObjectHei
|
||||
gcode.writeMove(segment.to, speedBottom, extrusion_per_mm_flat);
|
||||
}
|
||||
);
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::EXPORT, nullptr, commandSocket);
|
||||
for (unsigned int layer_nr = 0; layer_nr < wireFrame.layers.size(); layer_nr++)
|
||||
{
|
||||
|
||||
logProgress("export", layer_nr+1, totalLayers); // abuse the progress system of the normal mode of CuraEngine
|
||||
if (commandSocket) commandSocket->sendProgress(2.0/3.0 + 1.0/3.0 * float(layer_nr) / float(totalLayers));
|
||||
Progress::messageProgress(Progress::Stage::EXPORT, layer_nr+1, totalLayers, commandSocket); // abuse the progress system of the normal mode of CuraEngine
|
||||
|
||||
WeaveLayer& layer = wireFrame.layers[layer_nr];
|
||||
|
||||
gcode.writeLayerComment(layer_nr+1);
|
||||
|
||||
int fanSpeed = getSettingInPercentage("cool_fan_speed_max");
|
||||
double fanSpeed = getSettingInPercentage("cool_fan_speed_max");
|
||||
if (layer_nr == 0)
|
||||
fanSpeed = getSettingInPercentage("cool_fan_speed_min");
|
||||
gcode.writeFanCommand(fanSpeed);
|
||||
@@ -168,12 +157,10 @@ void Wireframe2gcode::writeGCode(CommandSocket* commandSocket, int& maxObjectHei
|
||||
|
||||
gcode.writeFanCommand(0);
|
||||
|
||||
finalize(maxObjectHeight);
|
||||
|
||||
if (commandSocket)
|
||||
{
|
||||
gcode.finalize(maxObjectHeight, getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
|
||||
for(int e=0; e<MAX_EXTRUDERS; e++)
|
||||
gcode.writeTemperatureCommand(e, 0, false);
|
||||
|
||||
commandSocket->sendGCodeLayer();
|
||||
commandSocket->endSendSlicedObject();
|
||||
}
|
||||
@@ -244,7 +231,7 @@ void Wireframe2gcode::strategy_retract(WeaveLayer& layer, WeaveConnectionPart& p
|
||||
|
||||
RetractionConfig retraction_config;
|
||||
// TODO: get these from the settings!
|
||||
retraction_config.amount = 500; //INT2MM(getSettingInt("retractionAmount"))
|
||||
retraction_config.amount = 500; //INT2MM(getSettingInt("retraction_amount"))
|
||||
retraction_config.primeAmount = 0;//INT2MM(getSettingInt("retractionPrime
|
||||
retraction_config.speed = 20; // 40;
|
||||
retraction_config.primeSpeed = 15; // 30;
|
||||
@@ -406,16 +393,16 @@ void Wireframe2gcode::handle_roof_segment(WeaveRoofPart& inset, WeaveConnectionP
|
||||
|
||||
|
||||
|
||||
void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& fill_insets, Polygons& roof_outlines
|
||||
void Wireframe2gcode::writeFill(std::vector<WeaveRoofPart>& infill_insets, Polygons& roof_outlines
|
||||
, std::function<void (Wireframe2gcode& thiss, WeaveRoofPart& inset, WeaveConnectionPart& part, unsigned int segment_idx)> connectionHandler
|
||||
, std::function<void (Wireframe2gcode& thiss, WeaveConnectionSegment& p)> flatHandler)
|
||||
{
|
||||
|
||||
// bottom:
|
||||
gcode.writeTypeComment("FILL");
|
||||
for (unsigned int inset_idx = 0; inset_idx < fill_insets.size(); inset_idx++)
|
||||
for (unsigned int inset_idx = 0; inset_idx < infill_insets.size(); inset_idx++)
|
||||
{
|
||||
WeaveRoofPart& inset = fill_insets[inset_idx];
|
||||
WeaveRoofPart& inset = infill_insets[inset_idx];
|
||||
|
||||
|
||||
for (unsigned int inset_part_nr = 0; inset_part_nr < inset.connections.size(); inset_part_nr++)
|
||||
@@ -484,7 +471,7 @@ void Wireframe2gcode::writeMoveWithRetract(Point to)
|
||||
}
|
||||
|
||||
Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBase* settings_base)
|
||||
: SettingsBase(settings_base)
|
||||
: SettingsMessenger(settings_base)
|
||||
, gcode(gcode)
|
||||
{
|
||||
wireFrame = weaver.wireFrame;
|
||||
@@ -500,8 +487,8 @@ Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBas
|
||||
|
||||
double filament_area = /* M_PI * */ (INT2MM(filament_diameter) / 2.0) * (INT2MM(filament_diameter) / 2.0);
|
||||
double lineArea = /* M_PI * */ (INT2MM(extrusionWidth) / 2.0) * (INT2MM(extrusionWidth) / 2.0);
|
||||
extrusion_per_mm_connection = lineArea / filament_area * double(flowConnection) / 100.0;
|
||||
extrusion_per_mm_flat = lineArea / filament_area * double(flowFlat) / 100.0;
|
||||
extrusion_per_mm_connection = lineArea / filament_area * flowConnection / 100.0;
|
||||
extrusion_per_mm_flat = lineArea / filament_area * flowFlat / 100.0;
|
||||
|
||||
nozzle_outer_diameter = getSettingInMicrons("machine_nozzle_tip_outer_diameter"); // ___ ___ .
|
||||
nozzle_head_distance = getSettingInMicrons("machine_nozzle_head_distance"); // | | .
|
||||
@@ -551,5 +538,65 @@ Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBas
|
||||
|
||||
}
|
||||
|
||||
void Wireframe2gcode::processStartingCode(CommandSocket* command_socket)
|
||||
{
|
||||
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
|
||||
{
|
||||
if (!command_socket)
|
||||
{
|
||||
gcode.writeCode(";FLAVOR:UltiGCode\n;TIME:666\n;MATERIAL:666\n;MATERIAL2:-1\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getSettingBoolean("machine_heated_bed") && getSettingInDegreeCelsius("material_bed_temperature") > 0)
|
||||
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature"), true);
|
||||
|
||||
if (getSettingInDegreeCelsius("material_print_temperature") > 0)
|
||||
{
|
||||
gcode.writeTemperatureCommand(getSettingAsIndex("extruder_nr"), getSettingInDegreeCelsius("material_print_temperature"));
|
||||
gcode.writeTemperatureCommand(getSettingAsIndex("extruder_nr"), getSettingInDegreeCelsius("material_print_temperature"), true);
|
||||
}
|
||||
|
||||
}
|
||||
gcode.writeCode(getSettingString("machine_start_gcode").c_str());
|
||||
|
||||
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
|
||||
if (gcode.getFlavor() == EGCodeFlavor::BFB)
|
||||
{
|
||||
gcode.writeComment("enable auto-retraction");
|
||||
std::ostringstream tmp;
|
||||
tmp << "M227 S" << (getSettingInMicrons("retraction_amount") * 2560 / 1000) << " P" << (getSettingInMicrons("retraction_amount") * 2560 / 1000);
|
||||
gcode.writeLine(tmp.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
|
||||
void Wireframe2gcode::processSkirt(CommandSocket* commandSocket)
|
||||
{
|
||||
Polygons skirt = wireFrame.bottom_outline.offset(100000+5000).offset(-100000);
|
||||
PathOrderOptimizer order(gcode.getStartPositionXY());
|
||||
order.addPolygons(skirt);
|
||||
order.optimize();
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < skirt.size(); poly_idx++)
|
||||
{
|
||||
unsigned int actual_poly_idx = order.polyOrder[poly_idx];
|
||||
PolygonRef poly = skirt[actual_poly_idx];
|
||||
gcode.writeMove(poly[order.polyStart[actual_poly_idx]], getSettingInMillimetersPerSecond("speed_travel"), 0);
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
Point& p = poly[(point_idx + order.polyStart[actual_poly_idx] + 1) % poly.size()];
|
||||
gcode.writeMove(p, getSettingInMillimetersPerSecond("skirt_speed"), getSettingInMillimetersPerSecond("skirt_line_width"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Wireframe2gcode::finalize(int maxObjectHeight)
|
||||
{
|
||||
gcode.finalize(maxObjectHeight, getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
|
||||
for(int e=0; e<getSettingAsCount("machine_extruder_count"); e++)
|
||||
gcode.writeTemperatureCommand(e, 0, false);
|
||||
}
|
||||
}//namespace cura
|
||||
|
||||
+48
-33
@@ -8,7 +8,7 @@
|
||||
#include "commandSocket.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include "modelFile/modelFile.h" // PrintObject
|
||||
#include "MeshGroup.h"
|
||||
#include "slicer.h"
|
||||
|
||||
#include "utils/polygon.h"
|
||||
@@ -22,7 +22,7 @@ namespace cura
|
||||
/*!
|
||||
* Export class for exporting wireframe print gcode / weaver gcode / wireprint gcode.
|
||||
*/
|
||||
class Wireframe2gcode : public SettingsBase
|
||||
class Wireframe2gcode : public SettingsMessenger
|
||||
{
|
||||
private:
|
||||
static const int STRATEGY_COMPENSATE = 0;
|
||||
@@ -32,35 +32,35 @@ private:
|
||||
int initial_layer_thickness;
|
||||
int filament_diameter;
|
||||
int extrusionWidth;
|
||||
int flowConnection;// = getSettingInt("wireframeFlowConnection");
|
||||
int flowFlat; // = getSettingInt("wireframeFlowFlat");
|
||||
double extrusion_per_mm_connection; // = lineArea / filament_area * double(flowConnection) / 100.0;
|
||||
double extrusion_per_mm_flat; // = lineArea / filament_area * double(flowFlat) / 100.0;
|
||||
int nozzle_outer_diameter; // = getSettingInt("machineNozzleTipOuterDiameter"); // ___ ___ .
|
||||
int nozzle_head_distance; // = getSettingInt("machineNozzleHeadDistance"); // | | .
|
||||
int nozzle_expansion_angle; // = getSettingInt("machineNozzleExpansionAngle"); // \_U_/ .
|
||||
int nozzle_clearance; // = getSettingInt("wireframeNozzleClearance"); // at least line width
|
||||
int nozzle_top_diameter; // = tan(static_cast<double>(nozzle_expansion_angle)/180.0 * M_PI) * connectionHeight + nozzle_outer_diameter + nozzle_clearance;
|
||||
int moveSpeed; // = 40;
|
||||
int speedBottom; // = getSettingInt("wireframePrintspeedBottom");
|
||||
int speedUp; // = getSettingInt("wireframePrintspeedUp");
|
||||
int speedDown; // = getSettingInt("wireframePrintspeedDown");
|
||||
int speedFlat; // = getSettingInt("wireframePrintspeedFlat");
|
||||
int connectionHeight; // = getSettingInt("wireframeConnectionHeight");
|
||||
int roof_inset; // = getSettingInt("wireframeRoofInset");
|
||||
double flat_delay; // = getSettingInt("wireframeFlatDelay")/100.0;
|
||||
double bottom_delay; // = getSettingInt("wireframeBottomDelay")/100.0;
|
||||
double top_delay; // = getSettingInt("wireframeTopDelay")/100.0;
|
||||
int up_dist_half_speed; // = getSettingInt("wireframeUpDistHalfSpeed");
|
||||
int top_jump_dist; // = getSettingInt("wireframeTopJump");
|
||||
int fall_down; // = getSettingInt("wireframeFallDown");
|
||||
int drag_along; // = getSettingInt("wireframeDragAlong");
|
||||
int strategy; // = getSettingInt("wireframeStrategy"); // HIGHER_BEND_NO_STRAIGHTEN; // RETRACT_TO_STRAIGHTEN; // MOVE_TO_STRAIGHTEN; //
|
||||
double go_back_to_last_top; // = false;
|
||||
int straight_first_when_going_down; // = getSettingInt("wireframeStraightBeforeDown"); // %
|
||||
int roof_fall_down; // = getSettingInt("wireframeRoofFallDown");
|
||||
int roof_drag_along; // = getSettingInt("wireframeRoofDragAlong");
|
||||
double roof_outer_delay; // = getSettingInt("wireframeRoofOuterDelay")/100.0;
|
||||
double flowConnection;
|
||||
double flowFlat;
|
||||
double extrusion_per_mm_connection;
|
||||
double extrusion_per_mm_flat;
|
||||
int nozzle_outer_diameter;
|
||||
int nozzle_head_distance;
|
||||
double nozzle_expansion_angle;
|
||||
int nozzle_clearance;
|
||||
int nozzle_top_diameter;
|
||||
double moveSpeed;
|
||||
double speedBottom;
|
||||
double speedUp;
|
||||
double speedDown;
|
||||
double speedFlat;
|
||||
int connectionHeight;
|
||||
int 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;
|
||||
int strategy;
|
||||
double go_back_to_last_top;
|
||||
int straight_first_when_going_down;
|
||||
int roof_fall_down;
|
||||
int roof_drag_along;
|
||||
double roof_outer_delay;
|
||||
|
||||
RetractionConfig standard_retraction_config; //!< The standard retraction settings used for moves between parts etc.
|
||||
|
||||
@@ -69,13 +69,28 @@ public:
|
||||
|
||||
Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBase* settings_base);
|
||||
|
||||
void writeGCode(CommandSocket* commandSocket, int& maxObjectHeight);
|
||||
void writeGCode(CommandSocket* commandSocket);
|
||||
|
||||
|
||||
private:
|
||||
WireFrame wireFrame;
|
||||
|
||||
void writeFill(std::vector<WeaveRoofPart>& fill_insets, Polygons& outlines
|
||||
/*!
|
||||
* Startup gcode: nozzle temp up, retraction settings, bed temp
|
||||
*/
|
||||
void processStartingCode(CommandSocket* command_socket);
|
||||
|
||||
/*!
|
||||
* Lay down a skirt
|
||||
*/
|
||||
void processSkirt(CommandSocket* commandSocket);
|
||||
|
||||
/*!
|
||||
* End gcode: nozzle temp down
|
||||
*/
|
||||
void finalize(int maxObjectHeight);
|
||||
|
||||
void writeFill(std::vector<WeaveRoofPart>& infill_insets, Polygons& outlines
|
||||
, std::function<void (Wireframe2gcode& thiss, WeaveRoofPart& inset, WeaveConnectionPart& part, unsigned int segment_idx)> connectionHandler
|
||||
, std::function<void (Wireframe2gcode& thiss, WeaveConnectionSegment& p)> flatHandler);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "bridge.h"
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
int bridgeAngle(Polygons outline, SliceLayer* prevLayer)
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
#ifndef BRIDGE_H
|
||||
#define BRIDGE_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
namespace cura {
|
||||
class Polygons;
|
||||
class SliceLayer;
|
||||
|
||||
int bridgeAngle(Polygons outline, SliceLayer* prevLayer);
|
||||
|
||||
|
||||
+334
-186
@@ -1,236 +1,384 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "comb.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
bool Comb::preTest(Point startPoint, Point endPoint)
|
||||
bool Comb::moveInsideBoundary(Point* p, int distance)
|
||||
{
|
||||
return collisionTest(startPoint, endPoint);
|
||||
return PolygonUtils::moveInside(boundary_inside, *p, distance) != NO_INDEX;
|
||||
}
|
||||
|
||||
bool Comb::collisionTest(Point startPoint, Point endPoint)
|
||||
Polygons Comb::getLayerSecondWalls()
|
||||
{
|
||||
Polygons layer_walls;
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
for (SliceLayerPart& part : mesh.layers[layer_nr].parts)
|
||||
{
|
||||
if (part.insets.size() >= 2)
|
||||
{
|
||||
layer_walls.add(part.insets[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer_walls.add(part.outline.offset(-offset_from_outlines));
|
||||
}
|
||||
}
|
||||
}
|
||||
return layer_walls;
|
||||
}
|
||||
|
||||
// boundary_outside is only computed when it's needed!
|
||||
Polygons* Comb::getBoundaryOutside()
|
||||
{
|
||||
if (!boundary_outside)
|
||||
{
|
||||
boundary_outside = new Polygons();
|
||||
*boundary_outside = storage.getLayerOutlines(layer_nr, false).offset(offset_from_outlines_outside);
|
||||
}
|
||||
return boundary_outside;
|
||||
}
|
||||
|
||||
Comb::Comb(SliceDataStorage& storage, unsigned int layer_nr, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
, layer_nr(layer_nr)
|
||||
, offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls
|
||||
, max_moveInside_distance2(offset_from_outlines * 2 * offset_from_outlines * 2)
|
||||
, offset_from_outlines_outside(travel_avoid_distance)
|
||||
, avoid_other_parts(travel_avoid_other_parts)
|
||||
// , boundary_inside( boundary.offset(-offset_from_outlines) ) // TODO: make inside boundary configurable?
|
||||
, boundary_inside( getLayerSecondWalls() )
|
||||
, boundary_outside(nullptr)
|
||||
, partsView_inside( boundary_inside.splitIntoPartsView() ) // !! changes the order of boundary_inside !!
|
||||
{
|
||||
}
|
||||
|
||||
Comb::~Comb()
|
||||
{
|
||||
if (boundary_outside)
|
||||
delete boundary_outside;
|
||||
}
|
||||
|
||||
bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside, bool endInside)
|
||||
{
|
||||
if (shorterThen(endPoint - startPoint, max_comb_distance_ignored))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Move start and end point inside the comb boundary
|
||||
unsigned int start_inside_poly = NO_INDEX;
|
||||
if (startInside)
|
||||
{
|
||||
start_inside_poly = PolygonUtils::moveInside(boundary_inside, startPoint, offset_extra_start_end, max_moveInside_distance2);
|
||||
if (!inside(start_inside_poly) || start_inside_poly == NO_INDEX)
|
||||
{
|
||||
if (start_inside_poly != NO_INDEX)
|
||||
{ // if not yet inside because of overshoot, try again
|
||||
start_inside_poly = PolygonUtils::moveInside(boundary_inside, startPoint, offset_extra_start_end, max_moveInside_distance2);
|
||||
}
|
||||
if (start_inside_poly == NO_INDEX) //If we fail to move the point inside the comb boundary we need to retract.
|
||||
{
|
||||
startInside = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
unsigned int end_inside_poly = NO_INDEX;
|
||||
if (endInside)
|
||||
{
|
||||
end_inside_poly = PolygonUtils::moveInside(boundary_inside, endPoint, offset_extra_start_end, max_moveInside_distance2);
|
||||
if (!inside(endPoint) || end_inside_poly == NO_INDEX)
|
||||
{
|
||||
if (end_inside_poly != NO_INDEX)
|
||||
{ // if not yet inside because of overshoot, try again
|
||||
end_inside_poly = PolygonUtils::moveInside(boundary_inside, endPoint, offset_extra_start_end, max_moveInside_distance2);
|
||||
}
|
||||
if (end_inside_poly == NO_INDEX) //If we fail to move the point inside the comb boundary we need to retract.
|
||||
{
|
||||
endInside = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned int start_part_boundary_poly_idx;
|
||||
unsigned int end_part_boundary_poly_idx;
|
||||
unsigned int start_part_idx = (start_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(start_inside_poly, &start_part_boundary_poly_idx);
|
||||
unsigned int end_part_idx = (end_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside.getPartContaining(end_inside_poly, &end_part_boundary_poly_idx);
|
||||
|
||||
if (startInside && endInside && start_part_idx == end_part_idx)
|
||||
{ // normal combing within part
|
||||
PolygonsPart part = partsView_inside.assemblePart(start_part_idx);
|
||||
combPaths.emplace_back();
|
||||
LinePolygonsCrossings::comb(part, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{ // comb inside part to edge (if needed) >> move through air avoiding other parts >> comb inside end part upto the endpoint (if needed)
|
||||
Point middle_from;
|
||||
Point middle_to;
|
||||
|
||||
if (startInside && endInside)
|
||||
{
|
||||
ClosestPolygonPoint middle_from_cp = PolygonUtils::findClosest(endPoint, boundary_inside[start_part_boundary_poly_idx]);
|
||||
ClosestPolygonPoint middle_to_cp = PolygonUtils::findClosest(middle_from_cp.location, boundary_inside[end_part_boundary_poly_idx]);
|
||||
// walkToNearestSmallestConnection(middle_from_cp, middle_to_cp); // TODO: perform this optimization?
|
||||
middle_from = middle_from_cp.location;
|
||||
middle_to = middle_to_cp.location;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!startInside && !endInside)
|
||||
{
|
||||
middle_from = startPoint;
|
||||
middle_to = endPoint;
|
||||
}
|
||||
else if (!startInside && endInside)
|
||||
{
|
||||
middle_from = startPoint;
|
||||
ClosestPolygonPoint middle_to_cp = PolygonUtils::findClosest(middle_from, boundary_inside[end_part_boundary_poly_idx]);
|
||||
middle_to = middle_to_cp.location;
|
||||
}
|
||||
else if (startInside && !endInside)
|
||||
{
|
||||
middle_to = endPoint;
|
||||
ClosestPolygonPoint middle_from_cp = PolygonUtils::findClosest(middle_to, boundary_inside[start_part_boundary_poly_idx]);
|
||||
middle_from = middle_from_cp.location;
|
||||
}
|
||||
}
|
||||
|
||||
if (startInside)
|
||||
{
|
||||
// start to boundary
|
||||
PolygonsPart part_begin = partsView_inside.assemblePart(start_part_idx); // comb through the starting part only
|
||||
combPaths.emplace_back();
|
||||
LinePolygonsCrossings::comb(part_begin, startPoint, middle_from, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
|
||||
// throught air from boundary to boundary
|
||||
if (avoid_other_parts)
|
||||
{
|
||||
Polygons& middle = *getBoundaryOutside(); // comb through all air, since generally the outside consists of a single part
|
||||
Point from_outside = middle_from;
|
||||
if (startInside || middle.inside(from_outside, true))
|
||||
{ // move outside
|
||||
PolygonUtils::moveInside(middle, from_outside, -offset_extra_start_end, max_moveInside_distance2);
|
||||
}
|
||||
Point to_outside = middle_to;
|
||||
if (endInside || middle.inside(to_outside, true))
|
||||
{ // move outside
|
||||
PolygonUtils::moveInside(middle, to_outside, -offset_extra_start_end, max_moveInside_distance2);
|
||||
}
|
||||
combPaths.emplace_back();
|
||||
combPaths.back().throughAir = true;
|
||||
if ( vSize(middle_from - middle_to) < vSize(middle_from - from_outside) + vSize(middle_to - to_outside) )
|
||||
{ // via outside is a detour
|
||||
combPaths.back().push_back(middle_from);
|
||||
combPaths.back().push_back(middle_to);
|
||||
}
|
||||
else
|
||||
{
|
||||
LinePolygonsCrossings::comb(middle, from_outside, to_outside, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // directly through air (not avoiding other parts)
|
||||
combPaths.emplace_back();
|
||||
combPaths.back().throughAir = true;
|
||||
combPaths.back().cross_boundary = true; // TODO: calculate whether we cross a boundary!
|
||||
combPaths.back().push_back(middle_from);
|
||||
combPaths.back().push_back(middle_to);
|
||||
}
|
||||
|
||||
if (endInside)
|
||||
{
|
||||
// boundary to end
|
||||
PolygonsPart part_end = partsView_inside.assemblePart(end_part_idx); // comb through end part only
|
||||
combPaths.emplace_back();
|
||||
LinePolygonsCrossings::comb(part_end, middle_to, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void LinePolygonsCrossings::calcScanlineCrossings()
|
||||
{
|
||||
|
||||
min_crossing_idx = NO_INDEX;
|
||||
max_crossing_idx = NO_INDEX;
|
||||
|
||||
for(unsigned int poly_idx = 0; poly_idx < boundary.size(); poly_idx++)
|
||||
{
|
||||
PolyCrossings minMax(poly_idx);
|
||||
PolygonRef poly = boundary[poly_idx];
|
||||
Point p0 = transformation_matrix.apply(poly.back());
|
||||
for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
Point p1 = transformation_matrix.apply(poly[point_idx]);
|
||||
if ((p0.Y > transformed_startPoint.Y && p1.Y < transformed_startPoint.Y) || (p1.Y > transformed_startPoint.Y && p0.Y < transformed_startPoint.Y))
|
||||
{
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
|
||||
|
||||
if (x >= transformed_startPoint.X && x <= transformed_endPoint.X)
|
||||
{
|
||||
if (x < minMax.min.x) { minMax.min.x = x; minMax.min.point_idx = point_idx; }
|
||||
if (x > minMax.max.x) { minMax.max.x = x; minMax.max.point_idx = point_idx; }
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
|
||||
if (minMax.min.point_idx != NO_INDEX)
|
||||
{ // then also max.point_idx != -1
|
||||
if (min_crossing_idx == NO_INDEX || minMax.min.x < crossings[min_crossing_idx].min.x) { min_crossing_idx = crossings.size(); }
|
||||
if (max_crossing_idx == NO_INDEX || minMax.max.x > crossings[max_crossing_idx].max.x) { max_crossing_idx = crossings.size(); }
|
||||
crossings.push_back(minMax);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool LinePolygonsCrossings::lineSegmentCollidesWithBoundary()
|
||||
{
|
||||
Point diff = endPoint - startPoint;
|
||||
|
||||
matrix = PointMatrix(diff);
|
||||
sp = matrix.apply(startPoint);
|
||||
ep = matrix.apply(endPoint);
|
||||
|
||||
for(unsigned int n=0; n<boundery.size(); n++)
|
||||
transformation_matrix = PointMatrix(diff);
|
||||
transformed_startPoint = transformation_matrix.apply(startPoint);
|
||||
transformed_endPoint = transformation_matrix.apply(endPoint);
|
||||
|
||||
for(PolygonRef poly : boundary)
|
||||
{
|
||||
if (boundery[n].size() < 1)
|
||||
continue;
|
||||
Point p0 = matrix.apply(boundery[n][boundery[n].size()-1]);
|
||||
for(unsigned int i=0; i<boundery[n].size(); i++)
|
||||
Point p0 = transformation_matrix.apply(poly.back());
|
||||
for(Point p1_ : poly)
|
||||
{
|
||||
Point p1 = matrix.apply(boundery[n][i]);
|
||||
if ((p0.Y > sp.Y && p1.Y < sp.Y) || (p1.Y > sp.Y && p0.Y < sp.Y))
|
||||
Point p1 = transformation_matrix.apply(p1_);
|
||||
if ((p0.Y > transformed_startPoint.Y && p1.Y < transformed_startPoint.Y) || (p1.Y > transformed_startPoint.Y && p0.Y < transformed_startPoint.Y))
|
||||
{
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (sp.Y - p0.Y) / (p1.Y - p0.Y);
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
|
||||
|
||||
if (x > sp.X && x < ep.X)
|
||||
if (x > transformed_startPoint.X && x < transformed_endPoint.X)
|
||||
return true;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Comb::calcMinMax()
|
||||
|
||||
void LinePolygonsCrossings::getCombingPath(CombPath& combPath)
|
||||
{
|
||||
for(unsigned int n=0; n<boundery.size(); n++)
|
||||
if (shorterThen(endPoint - startPoint, Comb::max_comb_distance_ignored) || !lineSegmentCollidesWithBoundary())
|
||||
{
|
||||
minX[n] = INT64_MAX;
|
||||
maxX[n] = INT64_MIN;
|
||||
Point p0 = matrix.apply(boundery[n][boundery[n].size()-1]);
|
||||
for(unsigned int i=0; i<boundery[n].size(); i++)
|
||||
{
|
||||
Point p1 = matrix.apply(boundery[n][i]);
|
||||
if ((p0.Y > sp.Y && p1.Y < sp.Y) || (p1.Y > sp.Y && p0.Y < sp.Y))
|
||||
{
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (sp.Y - p0.Y) / (p1.Y - p0.Y);
|
||||
|
||||
if (x >= sp.X && x <= ep.X)
|
||||
{
|
||||
if (x < minX[n]) { minX[n] = x; minIdx[n] = i; }
|
||||
if (x > maxX[n]) { maxX[n] = x; maxIdx[n] = i; }
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
//We're not crossing any boundaries. So skip the comb generation.
|
||||
combPath.push_back(startPoint);
|
||||
combPath.push_back(endPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
calcScanlineCrossings();
|
||||
|
||||
CombPath basicPath;
|
||||
getBasicCombingPath(basicPath);
|
||||
optimizePath(basicPath, combPath);
|
||||
}
|
||||
|
||||
unsigned int Comb::getPolygonAbove(int64_t x)
|
||||
|
||||
void LinePolygonsCrossings::getBasicCombingPath(CombPath& combPath)
|
||||
{
|
||||
int64_t min = POINT_MAX;
|
||||
unsigned int ret = NO_INDEX;
|
||||
for(unsigned int n=0; n<boundery.size(); n++)
|
||||
for (PolyCrossings* crossing = getNextPolygonAlongScanline(transformed_startPoint.X)
|
||||
; crossing != nullptr
|
||||
; crossing = getNextPolygonAlongScanline(crossing->max.x))
|
||||
{
|
||||
if (minX[n] > x && minX[n] < min)
|
||||
getBasicCombingPath(*crossing, combPath);
|
||||
}
|
||||
combPath.push_back(endPoint);
|
||||
}
|
||||
|
||||
void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, CombPath& combPath)
|
||||
{
|
||||
PolygonRef poly = boundary[polyCrossings.poly_idx];
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x, transformed_startPoint.Y)));
|
||||
if ( ( polyCrossings.max.point_idx - polyCrossings.min.point_idx + poly.size() ) % poly.size()
|
||||
< poly.size() / 2 )
|
||||
{ // follow the path in the same direction as the winding order of the boundary polygon
|
||||
for(unsigned int point_idx = polyCrossings.min.point_idx
|
||||
; point_idx != polyCrossings.max.point_idx
|
||||
; point_idx = (point_idx < poly.size() - 1) ? (point_idx + 1) : (0))
|
||||
{
|
||||
min = minX[n];
|
||||
ret = n;
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // follow the path in the opposite direction of the winding order of the boundary polygon
|
||||
unsigned int min_idx = (polyCrossings.min.point_idx == 0)? poly.size() - 1: polyCrossings.min.point_idx - 1;
|
||||
unsigned int max_idx = (polyCrossings.max.point_idx == 0)? poly.size() - 1: polyCrossings.max.point_idx - 1;
|
||||
|
||||
for(unsigned int point_idx = min_idx; point_idx != max_idx; point_idx = (point_idx > 0) ? (point_idx - 1) : (poly.size() - 1))
|
||||
{
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x, transformed_startPoint.Y)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
LinePolygonsCrossings::PolyCrossings* LinePolygonsCrossings::getNextPolygonAlongScanline(int64_t x)
|
||||
{
|
||||
PolyCrossings* ret = nullptr;
|
||||
for(PolyCrossings& crossing : crossings)
|
||||
{
|
||||
if (crossing.min.x > x && (ret == nullptr || crossing.min.x < ret->min.x) )
|
||||
{
|
||||
ret = &crossing;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Point Comb::getBounderyPointWithOffset(unsigned int polygonNr, unsigned int idx)
|
||||
{
|
||||
Point p0 = boundery[polygonNr][(idx > 0) ? (idx - 1) : (boundery[polygonNr].size() - 1)];
|
||||
Point p1 = boundery[polygonNr][idx];
|
||||
Point p2 = boundery[polygonNr][(idx < (boundery[polygonNr].size() - 1)) ? (idx + 1) : (0)];
|
||||
|
||||
Point off0 = crossZ(normal(p1 - p0, MM2INT(1.0)));
|
||||
Point off1 = crossZ(normal(p2 - p1, MM2INT(1.0)));
|
||||
Point n = normal(off0 + off1, MM2INT(0.2));
|
||||
|
||||
return p1 + n;
|
||||
}
|
||||
|
||||
Comb::Comb(Polygons& _boundery)
|
||||
: boundery(_boundery)
|
||||
{
|
||||
minX = new int64_t[boundery.size()];
|
||||
maxX = new int64_t[boundery.size()];
|
||||
minIdx = new unsigned int[boundery.size()];
|
||||
maxIdx = new unsigned int[boundery.size()];
|
||||
}
|
||||
|
||||
Comb::~Comb()
|
||||
{
|
||||
delete[] minX;
|
||||
delete[] maxX;
|
||||
delete[] minIdx;
|
||||
delete[] maxIdx;
|
||||
}
|
||||
|
||||
bool Comb::moveInside(Point* p, int distance)
|
||||
{
|
||||
Point ret = *p;
|
||||
int64_t bestDist = MM2INT(2.0) * MM2INT(2.0);
|
||||
for(unsigned int n=0; n<boundery.size(); n++)
|
||||
bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimized_comb_path)
|
||||
{
|
||||
optimized_comb_path.push_back(startPoint);
|
||||
for(unsigned int point_idx = 1; point_idx<comb_path.size(); point_idx++)
|
||||
{
|
||||
if (boundery[n].size() < 1)
|
||||
continue;
|
||||
Point p0 = boundery[n][boundery[n].size()-1];
|
||||
for(unsigned int i=0; i<boundery[n].size(); i++)
|
||||
Point& current_point = optimized_comb_path.back();
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx]))
|
||||
{
|
||||
Point p1 = boundery[n][i];
|
||||
|
||||
//Q = A + Normal( B - A ) * ((( B - A ) dot ( P - A )) / VSize( A - B ));
|
||||
Point pDiff = p1 - p0;
|
||||
int64_t lineLength = vSize(pDiff);
|
||||
int64_t distOnLine = dot(pDiff, *p - p0) / lineLength;
|
||||
if (distOnLine < 10)
|
||||
distOnLine = 10;
|
||||
if (distOnLine > lineLength - 10)
|
||||
distOnLine = lineLength - 10;
|
||||
Point q = p0 + pDiff * distOnLine / lineLength;
|
||||
|
||||
int64_t dist = vSize2(q - *p);
|
||||
if (dist < bestDist)
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx - 1]))
|
||||
{
|
||||
bestDist = dist;
|
||||
ret = q + crossZ(normal(p1 - p0, distance));
|
||||
comb_path.cross_boundary = true;
|
||||
}
|
||||
|
||||
p0 = p1;
|
||||
optimized_comb_path.push_back(comb_path[point_idx - 1]);
|
||||
}
|
||||
}
|
||||
if (bestDist < MM2INT(2.0) * MM2INT(2.0))
|
||||
{
|
||||
*p = ret;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Comb::calc(Point startPoint, Point endPoint, std::vector<Point>& combPoints)
|
||||
{
|
||||
if (shorterThen(endPoint - startPoint, MM2INT(1.5)))
|
||||
return true;
|
||||
|
||||
bool addEndpoint = false;
|
||||
//Check if we are inside the comb boundaries
|
||||
if (!boundery.inside(startPoint))
|
||||
{
|
||||
if (!moveInside(&startPoint)) //If we fail to move the point inside the comb boundary we need to retract.
|
||||
return false;
|
||||
combPoints.push_back(startPoint);
|
||||
}
|
||||
if (!boundery.inside(endPoint))
|
||||
{
|
||||
if (!moveInside(&endPoint)) //If we fail to move the point inside the comb boundary we need to retract.
|
||||
return false;
|
||||
addEndpoint = true;
|
||||
}
|
||||
|
||||
//Check if we are crossing any bounderies, and pre-calculate some values.
|
||||
if (!preTest(startPoint, endPoint))
|
||||
{
|
||||
//We're not crossing any boundaries. So skip the comb generation.
|
||||
if (!addEndpoint && combPoints.size() == 0) //Only skip if we didn't move the start and end point.
|
||||
return true;
|
||||
}
|
||||
|
||||
//Calculate the minimum and maximum positions where we cross the comb boundary
|
||||
calcMinMax();
|
||||
|
||||
int64_t x = sp.X;
|
||||
std::vector<Point> pointList;
|
||||
//Now walk trough the crossings, for every boundary we cross, find the initial cross point and the exit point. Then add all the points in between
|
||||
// to the pointList and continue with the next boundary we will cross, until there are no more boundaries to cross.
|
||||
// This gives a path from the start to finish curved around the holes that it encounters.
|
||||
while(true)
|
||||
{
|
||||
unsigned int n = getPolygonAbove(x);
|
||||
if (n == NO_INDEX) break;
|
||||
|
||||
pointList.push_back(matrix.unapply(Point(minX[n] - MM2INT(0.2), sp.Y)));
|
||||
if ( (minIdx[n] - maxIdx[n] + boundery[n].size()) % boundery[n].size() > (maxIdx[n] - minIdx[n] + boundery[n].size()) % boundery[n].size())
|
||||
else
|
||||
{
|
||||
for(unsigned int i=minIdx[n]; i != maxIdx[n]; i = (i < boundery[n].size() - 1) ? (i + 1) : (0))
|
||||
{
|
||||
pointList.push_back(getBounderyPointWithOffset(n, i));
|
||||
}
|
||||
}else{
|
||||
if (minIdx[n] == 0)
|
||||
minIdx[n] = boundery[n].size() - 1;
|
||||
else
|
||||
minIdx[n]--;
|
||||
if (maxIdx[n] == 0)
|
||||
maxIdx[n] = boundery[n].size() - 1;
|
||||
else
|
||||
maxIdx[n]--;
|
||||
// : dont add the newest point
|
||||
|
||||
for(unsigned int i=minIdx[n]; i != maxIdx[n]; i = (i > 0) ? (i - 1) : (boundery[n].size() - 1))
|
||||
// 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)
|
||||
{
|
||||
pointList.push_back(getBounderyPointWithOffset(n, i));
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
optimized_comb_path.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
pointList.push_back(matrix.unapply(Point(maxX[n] + MM2INT(0.2), sp.Y)));
|
||||
|
||||
x = maxX[n];
|
||||
}
|
||||
pointList.push_back(endPoint);
|
||||
|
||||
//Optimize the pointList, skip each point we could already reach by not crossing a boundary. This smooths out the path and makes it skip any unneeded corners.
|
||||
Point p0 = startPoint;
|
||||
for(unsigned int n=1; n<pointList.size(); n++)
|
||||
{
|
||||
if (collisionTest(p0, pointList[n]))
|
||||
{
|
||||
if (collisionTest(p0, pointList[n-1]))
|
||||
return false;
|
||||
p0 = pointList[n-1];
|
||||
combPoints.push_back(p0);
|
||||
}
|
||||
}
|
||||
if (addEndpoint)
|
||||
combPoints.push_back(endPoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+254
-23
@@ -4,39 +4,270 @@
|
||||
|
||||
#include "utils/polygon.h"
|
||||
|
||||
namespace cura {
|
||||
namespace cura
|
||||
{
|
||||
|
||||
struct CombPath : public std::vector<Point> //!< A single path either inside or outise the parts
|
||||
{
|
||||
bool throughAir = false; //!< Whether the path is one which moves through air.
|
||||
bool cross_boundary = false; //!< Whether the path crosses a boundary.
|
||||
};
|
||||
struct CombPaths : public std::vector<CombPath> //!< A list of paths alternating between inside a part and outside a part
|
||||
{
|
||||
};
|
||||
|
||||
class Comb
|
||||
/*!
|
||||
* Class for generating a combing move action from point a to point b and avoiding collision with other parts when moving through air.
|
||||
* See LinePolygonsCrossings::comb.
|
||||
*
|
||||
* The general implementation is by rotating everything such that the the line segment from a to b is aligned with the x-axis.
|
||||
* We call the line on which a and b lie the 'scanline'.
|
||||
*
|
||||
* The basic path is generated by following the scanline until it hits a polygon, then follow the polygon until the last point where it hits the scanline,
|
||||
* follow the scanline again, etc.
|
||||
* The path is offsetted from the polygons, so that it doesn't intersect with them.
|
||||
*
|
||||
* Next the basic path is optimized by taking shortcuts where possible. Only shortcuts which skip a single point are considered, in order to reduce computational complexity.
|
||||
*/
|
||||
class LinePolygonsCrossings
|
||||
{
|
||||
private:
|
||||
Polygons& boundery;
|
||||
|
||||
int64_t* minX;
|
||||
int64_t* maxX;
|
||||
unsigned int* minIdx;
|
||||
unsigned int* maxIdx;
|
||||
|
||||
PointMatrix matrix;
|
||||
Point sp;
|
||||
Point ep;
|
||||
|
||||
bool preTest(Point startPoint, Point endPoint);
|
||||
bool collisionTest(Point startPoint, Point endPoint);
|
||||
|
||||
void calcMinMax();
|
||||
|
||||
unsigned int getPolygonAbove(int64_t x);
|
||||
/*!
|
||||
* A Crossing holds data on a single point where a polygon crosses the scanline.
|
||||
*/
|
||||
struct Crossing
|
||||
{
|
||||
int64_t x; //!< x coordinate of crossings between the polygon and the scanline.
|
||||
unsigned int point_idx; //!< The index of the first point of the line segment which crosses the scanline
|
||||
|
||||
/*!
|
||||
* Creates a Crossing with minimal initialization
|
||||
* \param x The x-coordinate in transformed space
|
||||
* \param point_idx The index of the first point of the line segment which crosses the scanline
|
||||
*/
|
||||
Crossing(int64_t x, unsigned int point_idx)
|
||||
: x(x), point_idx(point_idx)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
Point getBounderyPointWithOffset(unsigned int polygonNr, unsigned int idx);
|
||||
/*!
|
||||
* A PolyCrossings holds data on where a polygon crosses the scanline. Only the Crossing with lowest Crossing::x and highest are recorded.
|
||||
*/
|
||||
struct PolyCrossings
|
||||
{
|
||||
unsigned int poly_idx; //!< The index of the polygon which crosses the scanline
|
||||
Crossing min; //!< The point where the polygon first crosses the scanline.
|
||||
Crossing max; //!< The point where the polygon last crosses the scanline.
|
||||
/*!
|
||||
* Create a PolyCrossings with minimal initialization. PolyCrossings::min and PolyCrossings::max are not yet computed.
|
||||
* \param poly_idx The index of the polygon in LinePolygonsCrossings::boundary
|
||||
*/
|
||||
PolyCrossings(unsigned int poly_idx)
|
||||
: poly_idx(poly_idx)
|
||||
, min(INT64_MAX, NO_INDEX), max(INT64_MIN, NO_INDEX)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* A PolyCrossings list: for every polygon a PolyCrossings.
|
||||
*/
|
||||
struct PartCrossings : public std::vector<PolyCrossings>
|
||||
{
|
||||
//unsigned int part_idx;
|
||||
};
|
||||
|
||||
|
||||
PartCrossings crossings; //!< All crossings of polygons in the LinePolygonsCrossings::boundary with the scanline.
|
||||
unsigned int min_crossing_idx; //!< The index into LinePolygonsCrossings::crossings to the crossing with the minimal PolyCrossings::min crossing of all PolyCrossings's.
|
||||
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.
|
||||
Point startPoint; //!< The start point of the scanline.
|
||||
Point endPoint; //!< The end point of the scanline.
|
||||
|
||||
int64_t dist_to_move_boundary_point_outside; //!< The distance used to move outside or inside so that a boundary point doesn't intersect with the boundary anymore. Neccesary due to computational rounding problems. Use negative value for insicde combing.
|
||||
|
||||
PointMatrix transformation_matrix; //!< The transformation which rotates everything such that the scanline is aligned with the x-axis.
|
||||
Point transformed_startPoint; //!< The LinePolygonsCrossings::startPoint as transformed by Comb::transformation_matrix
|
||||
Point transformed_endPoint; //!< The LinePolygonsCrossings::endPoint as transformed by Comb::transformation_matrix
|
||||
|
||||
|
||||
/*!
|
||||
* Check if we are crossing the boundaries, and pre-calculate some values.
|
||||
*
|
||||
* Sets Comb::transformation_matrix, Comb::transformed_startPoint and Comb::transformed_endPoint
|
||||
* \return Whether the line segment from LinePolygonsCrossings::startPoint to LinePolygonsCrossings::endPoint collides with the boundary
|
||||
*/
|
||||
bool lineSegmentCollidesWithBoundary();
|
||||
|
||||
/*!
|
||||
* Calculate Comb::crossings, Comb::min_crossing_idx and Comb::max_crossing_idx.
|
||||
*/
|
||||
void calcScanlineCrossings();
|
||||
|
||||
/*!
|
||||
* Get the basic combing path and optimize it.
|
||||
*
|
||||
* \param combPath Output parameter: the points along the combing path.
|
||||
*/
|
||||
void getCombingPath(CombPath& combPath);
|
||||
|
||||
/*!
|
||||
* Get the basic combing path, without shortcuts. The path goes straight toward the endPoint and follows the boundary when it hits it, until it passes the scanline again.
|
||||
*
|
||||
* Walk trough the crossings, for every boundary we cross, find the initial cross point and the exit point. Then add all the points in between
|
||||
* to the \p combPath and continue with the next boundary we will cross, until there are no more boundaries to cross.
|
||||
* This gives a path from the start to finish curved around the holes that it encounters.
|
||||
*
|
||||
* \param combPath Output parameter: the points along the combing path.
|
||||
*/
|
||||
void getBasicCombingPath(CombPath& combPath);
|
||||
|
||||
/*!
|
||||
* Get the basic combing path, following a single boundary polygon when it hits it, until it passes the scanline again.
|
||||
*
|
||||
* Find the initial cross point and the exit point. Then add all the points in between
|
||||
* to the \p combPath and continue with the next boundary we will cross, until there are no more boundaries to cross.
|
||||
* This gives a path from the start to finish curved around the polygon that it encounters.
|
||||
*
|
||||
* \param combPath Output parameter: where to add the points along the combing path.
|
||||
*/
|
||||
void getBasicCombingPath(PolyCrossings& crossings, CombPath& combPath);
|
||||
|
||||
/*!
|
||||
* Find the first polygon cutting the scanline after \p x.
|
||||
*
|
||||
* Note that this function only looks at the first segment cutting the scanline (see Comb::minX)!
|
||||
* It doesn't return the next polygon which crosses the scanline, but the first polygon crossing the scanline for the first time.
|
||||
*
|
||||
* \param x The point on the scanline from where to look.
|
||||
* \return The next PolyCrossings fully beyond \p x or one with PolyCrossings::poly_idx set to NO_INDEX if there's none left.
|
||||
*/
|
||||
PolyCrossings* getNextPolygonAlongScanline(int64_t x);
|
||||
|
||||
/*!
|
||||
* Optimize the \p comb_path: skip each point we could already reach by not crossing a boundary. This smooths out the path and makes it skip some unneeded corners.
|
||||
*
|
||||
* \param comb_path The unoptimized combing path.
|
||||
* \param optimized_comb_path Output parameter: The points of optimized combing path
|
||||
* \return Whether it turns out that the basic comb path already crossed a boundary
|
||||
*/
|
||||
bool optimizePath(CombPath& comb_path, CombPath& optimized_comb_path);
|
||||
|
||||
/*!
|
||||
* Create a LinePolygonsCrossings with minimal initialization.
|
||||
* \param boundary The boundary which not to cross during combing
|
||||
* \param start the starting point
|
||||
* \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)
|
||||
{
|
||||
}
|
||||
|
||||
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 startPoint From where to start the combing move.
|
||||
* \param endPoint Where to end the combing move.
|
||||
* \param combPath Output parameter: the combing path generated.
|
||||
*/
|
||||
static void comb(Polygons& boundary, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside)
|
||||
{
|
||||
LinePolygonsCrossings linePolygonsCrossings(boundary, startPoint, endPoint, dist_to_move_boundary_point_outside);
|
||||
linePolygonsCrossings.getCombingPath(combPath);
|
||||
};
|
||||
};
|
||||
|
||||
class SliceDataStorage;
|
||||
|
||||
/*!
|
||||
* Class for generating a full combing actions from a travel move from a start point to an end point.
|
||||
* A single Comb object is used for each layer.
|
||||
*
|
||||
* Comb::calc is the main function of this class.
|
||||
*
|
||||
* Typical output: A combing path to the boundary of the polygon + a move through air avoiding other parts in the layer + a combing path from the boundary of the ending polygon to the end point.
|
||||
* Each of these three is a CombPath; the first and last are within Comb::boundary_inside while the middle is outside of Comb::boundary_outside.
|
||||
* Between these there is a little gap where the nozzle crosses the boundary of an object approximately perpendicular to its boundary.
|
||||
*
|
||||
* As an optimization, the combing paths inside are calculated on specifically those PolygonsParts within which to comb, while the coundary_outside isn't split into outside parts,
|
||||
* because generally there is only one outside part; encapsulated holes occur less often.
|
||||
*/
|
||||
class Comb
|
||||
{
|
||||
friend class LinePolygonsCrossings;
|
||||
private:
|
||||
SliceDataStorage& storage; //!< The storage from which to compute the outside boundary, when needed.
|
||||
unsigned int layer_nr; //!< The layer number for the layer for which to compute the outside boundary, when needed.
|
||||
|
||||
int64_t offset_from_outlines; //!< Offset from the boundary of a part to the comb path. (nozzle width / 2)
|
||||
int64_t max_moveInside_distance2; //!< Maximal distance of a point to the Comb::boundary_inside which is still to be considered inside. (very sharp corners not allowed :S)
|
||||
int64_t offset_from_outlines_outside; //!< Offset from the boundary of a part to a travel path which avoids it by this distance.
|
||||
static const int64_t max_moveOutside_distance2 = INT64_MAX; //!< Any point which is not inside should be considered outside.
|
||||
static const int64_t offset_dist_to_get_from_on_the_polygon_to_outside = 40; //!< in order to prevent on-boundary vs crossing boundary confusions (precision thing)
|
||||
static const int64_t max_comb_distance_ignored = MM2INT(1.5); //!< If the direct path from start point to end point is shorter than this, go directly without any combing.
|
||||
static const int64_t offset_extra_start_end = 100; //!< Distance to move start point and end point toward eachother to extra avoid collision with the boundaries.
|
||||
|
||||
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)
|
||||
PartsView partsView_inside; //!< Structured indices onto boundary_inside which shows which polygons belong to which part.
|
||||
|
||||
/*!
|
||||
* Collects the inner most walls for every mesh in the layer (not support) or computes them from the outlines using Comb::offset_from_outlines.
|
||||
*/
|
||||
Polygons getLayerSecondWalls();
|
||||
|
||||
/*!
|
||||
* 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();
|
||||
|
||||
public:
|
||||
Comb(Polygons& _boundery);
|
||||
/*!
|
||||
* Initializes the combing areas for every mesh in the layer (not support)
|
||||
* \param storage Where the layer polygon data is stored
|
||||
* \param layer_nr The number of the layer for which to generate the combing areas.
|
||||
* \param offset_from_outlines The offset from the outline polygon, to create the combing boundary in case there is no second wall.
|
||||
* \param travel_avoid_other_parts Whether to avoid other layer parts when traveling through air.
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
*/
|
||||
Comb(SliceDataStorage& storage, unsigned int layer_nr, int64_t offset_from_outlines, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
|
||||
~Comb();
|
||||
|
||||
bool inside(const Point p) { return boundery.inside(p); }
|
||||
bool moveInside(Point* p, int distance = 100);
|
||||
//! Utility function for `boundary_inside.inside(p)`.
|
||||
bool inside(const Point p) { return boundary_inside.inside(p); }
|
||||
|
||||
/*!
|
||||
* Calculate the comb paths (if any) - one for each polygon combed alternated with travel paths
|
||||
*
|
||||
* \param startPoint Where to start moving from
|
||||
* \param endPoint Where to move to
|
||||
* \param combPoints Output parameter: The points along the combing path, excluding the \p startPoint (?) and \p endPoint
|
||||
* \param startInside Whether we want to start inside the comb boundary
|
||||
* \param endInside Whether we want to end up inside the comb boundary
|
||||
* \return Whether combing has succeeded; otherwise a retraction is needed.
|
||||
*/
|
||||
bool calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside = false, bool endInside = false);
|
||||
|
||||
/*!
|
||||
* Move \p p to inside the inner comb boundary with a \p distance from the boundary.
|
||||
*
|
||||
* \param p the point to change/move
|
||||
* \param distance the distance from the resulting point to the boundary on the inside
|
||||
* \return whether the point has been moved inside
|
||||
*/
|
||||
bool moveInsideBoundary(Point* p, int distance);
|
||||
|
||||
bool calc(Point startPoint, Point endPoint, std::vector<Point>& combPoints);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+135
-95
@@ -1,6 +1,7 @@
|
||||
#include "utils/logoutput.h"
|
||||
#include "commandSocket.h"
|
||||
#include "fffProcessor.h"
|
||||
#include "FffProcessor.h"
|
||||
#include "Progress.h"
|
||||
|
||||
#include <thread>
|
||||
#include <cinttypes>
|
||||
@@ -16,118 +17,143 @@ namespace cura {
|
||||
#define BYTES_PER_FLOAT 4
|
||||
#define FLOATS_PER_VECTOR 3
|
||||
#define VECTORS_PER_FACE 3
|
||||
|
||||
|
||||
class CommandSocket::Private
|
||||
{
|
||||
public:
|
||||
Private()
|
||||
: processor(nullptr)
|
||||
, socket(nullptr)
|
||||
: socket(nullptr)
|
||||
, object_count(0)
|
||||
, current_object_number(0)
|
||||
, currentSlicedObject(nullptr)
|
||||
, slicedObjects(0)
|
||||
, current_sliced_object(nullptr)
|
||||
, sliced_objects(0)
|
||||
{ }
|
||||
|
||||
Cura::Layer* getLayerById(int id);
|
||||
|
||||
fffProcessor* processor;
|
||||
cura::proto::Layer* getLayerById(int id);
|
||||
|
||||
Arcus::Socket* socket;
|
||||
|
||||
|
||||
// Number of objects that need to be sliced
|
||||
int object_count;
|
||||
int current_object_number;
|
||||
|
||||
// Message that holds a list of sliced objects
|
||||
std::shared_ptr<cura::proto::SlicedObjectList> sliced_object_list;
|
||||
|
||||
// Message that holds the currently sliced object (to be added to sliced_object_list)
|
||||
cura::proto::SlicedObject* current_sliced_object;
|
||||
|
||||
// Number of sliced objects for this sliced object list
|
||||
int sliced_objects;
|
||||
|
||||
// Ids of the sliced objects
|
||||
std::vector<int64_t> object_ids;
|
||||
|
||||
std::shared_ptr<Cura::SlicedObjectList> slicedObjectList;
|
||||
Cura::SlicedObject* currentSlicedObject;
|
||||
int slicedObjects;
|
||||
std::vector<int64_t> objectIds;
|
||||
|
||||
std::string tempGCodeFile;
|
||||
std::string temp_gcode_file;
|
||||
std::ostringstream gcode_output_stream;
|
||||
|
||||
std::shared_ptr<PrintObject> objectToSlice;
|
||||
|
||||
// Print object that olds one or more meshes that need to be sliced.
|
||||
std::vector< std::shared_ptr<MeshGroup> > objects_to_slice;
|
||||
};
|
||||
|
||||
CommandSocket::CommandSocket(fffProcessor* processor)
|
||||
CommandSocket::CommandSocket()
|
||||
: d(new Private)
|
||||
{
|
||||
d->processor = processor;
|
||||
d->processor->setCommandSocket(this);
|
||||
FffProcessor::getInstance()->setCommandSocket(this);
|
||||
}
|
||||
|
||||
void CommandSocket::connect(const std::string& ip, int port)
|
||||
{
|
||||
d->socket = new Arcus::Socket();
|
||||
d->socket->registerMessageType(1, &Cura::ObjectList::default_instance());
|
||||
d->socket->registerMessageType(2, &Cura::SlicedObjectList::default_instance());
|
||||
d->socket->registerMessageType(3, &Cura::Progress::default_instance());
|
||||
d->socket->registerMessageType(4, &Cura::GCodeLayer::default_instance());
|
||||
d->socket->registerMessageType(5, &Cura::ObjectPrintTime::default_instance());
|
||||
d->socket->registerMessageType(6, &Cura::SettingList::default_instance());
|
||||
d->socket->registerMessageType(7, &Cura::GCodePrefix::default_instance());
|
||||
//d->socket->registerMessageType(1, &Cura::ObjectList::default_instance());
|
||||
d->socket->registerMessageType(1, &cura::proto::Slice::default_instance());
|
||||
d->socket->registerMessageType(2, &cura::proto::SlicedObjectList::default_instance());
|
||||
d->socket->registerMessageType(3, &cura::proto::Progress::default_instance());
|
||||
d->socket->registerMessageType(4, &cura::proto::GCodeLayer::default_instance());
|
||||
d->socket->registerMessageType(5, &cura::proto::ObjectPrintTime::default_instance());
|
||||
d->socket->registerMessageType(6, &cura::proto::SettingList::default_instance());
|
||||
d->socket->registerMessageType(7, &cura::proto::GCodePrefix::default_instance());
|
||||
|
||||
d->socket->connect(ip, port);
|
||||
|
||||
|
||||
// Start & continue listening as long as socket is not closed and there is no error.
|
||||
while(d->socket->state() != Arcus::SocketState::Closed && d->socket->state() != Arcus::SocketState::Error)
|
||||
{
|
||||
if(d->objectToSlice)
|
||||
//If there is an object to slice, do so.
|
||||
if(d->objects_to_slice.size())
|
||||
{
|
||||
//TODO: Support all-at-once/one-at-a-time printing
|
||||
d->processor->processModel(d->objectToSlice.get());
|
||||
d->objectToSlice.reset();
|
||||
d->processor->resetFileNumber();
|
||||
|
||||
for(auto object : d->objects_to_slice)
|
||||
{
|
||||
FffProcessor::getInstance()->processMeshGroup(object.get());
|
||||
}
|
||||
d->objects_to_slice.clear();
|
||||
sendPrintTime();
|
||||
}
|
||||
//TODO: Support all-at-once/one-at-a-time printing
|
||||
//d->processor->processModel(d->object_to_slice.get());
|
||||
//d->object_to_slice.reset();
|
||||
//d->processor->resetFileNumber();
|
||||
|
||||
//sendPrintTime();
|
||||
}
|
||||
|
||||
// Actually start handling messages.
|
||||
Arcus::MessagePtr message = d->socket->takeNextMessage();
|
||||
|
||||
Cura::SettingList* settingList = dynamic_cast<Cura::SettingList*>(message.get());
|
||||
if(settingList)
|
||||
cura::proto::SettingList* setting_list = dynamic_cast<cura::proto::SettingList*>(message.get());
|
||||
if(setting_list)
|
||||
{
|
||||
handleSettingList(settingList);
|
||||
handleSettingList(setting_list);
|
||||
}
|
||||
|
||||
Cura::ObjectList* objectList = dynamic_cast<Cura::ObjectList*>(message.get());
|
||||
if(objectList)
|
||||
/*cura::proto::ObjectList* object_list = dynamic_cast<cura::proto::ObjectList*>(message.get());
|
||||
if(object_list)
|
||||
{
|
||||
handleObjectList(objectList);
|
||||
handleObjectList(object_list);
|
||||
}*/
|
||||
|
||||
cura::proto::Slice* slice = dynamic_cast<cura::proto::Slice*>(message.get());
|
||||
if(slice)
|
||||
{
|
||||
// Reset object counts
|
||||
d->object_count = 0;
|
||||
d->object_ids.clear();
|
||||
for(auto object : slice->object_lists())
|
||||
{
|
||||
handleObjectList(&object);
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
|
||||
if(!d->socket->errorString().empty()) {
|
||||
if(!d->socket->errorString().empty())
|
||||
{
|
||||
logError("%s\n", d->socket->errorString().data());
|
||||
d->socket->clearError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandSocket::handleObjectList(Cura::ObjectList* list)
|
||||
void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
{
|
||||
FMatrix3x3 matrix;
|
||||
d->object_count = 0;
|
||||
d->objectIds.clear();
|
||||
|
||||
d->objectToSlice = std::make_shared<PrintObject>(d->processor);
|
||||
//d->object_count = 0;
|
||||
//d->object_ids.clear();
|
||||
d->objects_to_slice.push_back(std::make_shared<MeshGroup>(FffProcessor::getInstance()));
|
||||
MeshGroup* object_to_slice = d->objects_to_slice.back().get();
|
||||
for(auto object : list->objects())
|
||||
{
|
||||
d->objectToSlice->meshes.emplace_back(d->objectToSlice.get()); //Construct a new mesh and put it into printObject's mesh list.
|
||||
Mesh& mesh = d->objectToSlice->meshes.back();
|
||||
object_to_slice->meshes.push_back(object_to_slice); //Construct a new mesh (with object_to_slice as settings parent object) and put it into MeshGroup's mesh list.
|
||||
Mesh& mesh = object_to_slice->meshes.back();
|
||||
|
||||
int bytesPerFace = BYTES_PER_FLOAT * FLOATS_PER_VECTOR * VECTORS_PER_FACE;
|
||||
int faceCount = object.vertices().size() / bytesPerFace;
|
||||
for(int i = 0; i < faceCount; ++i)
|
||||
int bytes_per_face = BYTES_PER_FLOAT * FLOATS_PER_VECTOR * VECTORS_PER_FACE;
|
||||
int face_count = object.vertices().size() / bytes_per_face;
|
||||
for(int i = 0; i < face_count; ++i)
|
||||
{
|
||||
//TODO: Apply matrix
|
||||
std::string data = object.vertices().substr(i * bytesPerFace, bytesPerFace);
|
||||
const FPoint3* floatVerts = reinterpret_cast<const FPoint3*>(data.data());
|
||||
std::string data = object.vertices().substr(i * bytes_per_face, bytes_per_face);
|
||||
const FPoint3* float_vertices = reinterpret_cast<const FPoint3*>(data.data());
|
||||
|
||||
Point3 verts[3];
|
||||
verts[0] = matrix.apply(floatVerts[0]);
|
||||
verts[1] = matrix.apply(floatVerts[1]);
|
||||
verts[2] = matrix.apply(floatVerts[2]);
|
||||
verts[0] = matrix.apply(float_vertices[0]);
|
||||
verts[1] = matrix.apply(float_vertices[1]);
|
||||
verts[2] = matrix.apply(float_vertices[2]);
|
||||
mesh.addFace(verts[0], verts[1], verts[2]);
|
||||
}
|
||||
|
||||
@@ -136,47 +162,53 @@ void CommandSocket::handleObjectList(Cura::ObjectList* list)
|
||||
mesh.setSetting(setting.name(), setting.value());
|
||||
}
|
||||
|
||||
d->objectIds.push_back(object.id());
|
||||
d->object_ids.push_back(object.id());
|
||||
mesh.finish();
|
||||
}
|
||||
|
||||
for(auto setting : list->settings())
|
||||
{
|
||||
object_to_slice->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
|
||||
d->object_count++;
|
||||
d->objectToSlice->finalize();
|
||||
object_to_slice->finalize();
|
||||
}
|
||||
|
||||
void CommandSocket::handleSettingList(Cura::SettingList* list)
|
||||
void CommandSocket::handleSettingList(cura::proto::SettingList* list)
|
||||
{
|
||||
for(auto setting : list->settings())
|
||||
{
|
||||
d->processor->setSetting(setting.name(), setting.value());
|
||||
FffProcessor::getInstance()->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandSocket::sendLayerInfo(int layer_nr, int32_t z, int32_t height)
|
||||
{
|
||||
if(!d->currentSlicedObject)
|
||||
if(!d->current_sliced_object)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Cura::Layer* layer = d->getLayerById(layer_nr);
|
||||
|
||||
cura::proto::Layer* layer = d->getLayerById(layer_nr);
|
||||
layer->set_height(z);
|
||||
layer->set_thickness(height);
|
||||
}
|
||||
|
||||
void CommandSocket::sendPolygons(PolygonType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
{
|
||||
if(!d->currentSlicedObject)
|
||||
if(!d->current_sliced_object)
|
||||
return;
|
||||
|
||||
if (polygons.size() == 0)
|
||||
return;
|
||||
|
||||
Cura::Layer* layer = d->getLayerById(layer_nr);
|
||||
cura::proto::Layer* layer = d->getLayerById(layer_nr);
|
||||
|
||||
for(unsigned int i = 0; i < polygons.size(); ++i)
|
||||
{
|
||||
Cura::Polygon* p = layer->add_polygons();
|
||||
p->set_type(static_cast<Cura::Polygon_Type>(type));
|
||||
cura::proto::Polygon* p = layer->add_polygons();
|
||||
p->set_type(static_cast<cura::proto::Polygon_Type>(type));
|
||||
std::string polydata;
|
||||
polydata.append(reinterpret_cast<const char*>(polygons[i].data()), polygons[i].size() * sizeof(Point));
|
||||
p->set_points(polydata);
|
||||
@@ -186,16 +218,23 @@ void CommandSocket::sendPolygons(PolygonType type, int layer_nr, Polygons& polyg
|
||||
|
||||
void CommandSocket::sendProgress(float amount)
|
||||
{
|
||||
auto message = std::make_shared<Cura::Progress>();
|
||||
auto message = std::make_shared<cura::proto::Progress>();
|
||||
amount /= d->object_count;
|
||||
amount += d->sliced_objects * (1. / d->object_count);
|
||||
message->set_amount(amount);
|
||||
d->socket->sendMessage(message);
|
||||
}
|
||||
|
||||
void CommandSocket::sendProgressStage(Progress::Stage stage)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void CommandSocket::sendPrintTime()
|
||||
{
|
||||
auto message = std::make_shared<Cura::ObjectPrintTime>();
|
||||
message->set_time(d->processor->getTotalPrintTime());
|
||||
message->set_material_amount(d->processor->getTotalFilamentUsed(0));
|
||||
auto message = std::make_shared<cura::proto::ObjectPrintTime>();
|
||||
message->set_time(FffProcessor::getInstance()->getTotalPrintTime());
|
||||
message->set_material_amount(FffProcessor::getInstance()->getTotalFilamentUsed(0));
|
||||
d->socket->sendMessage(message);
|
||||
}
|
||||
|
||||
@@ -210,36 +249,37 @@ void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float
|
||||
|
||||
void CommandSocket::beginSendSlicedObject()
|
||||
{
|
||||
if(!d->slicedObjectList)
|
||||
if(!d->sliced_object_list)
|
||||
{
|
||||
d->slicedObjectList = std::make_shared<Cura::SlicedObjectList>();
|
||||
d->sliced_object_list = std::make_shared<cura::proto::SlicedObjectList>();
|
||||
}
|
||||
|
||||
d->currentSlicedObject = d->slicedObjectList->add_objects();
|
||||
d->currentSlicedObject->set_id(d->objectIds[d->slicedObjects]);
|
||||
d->current_sliced_object = d->sliced_object_list->add_objects();
|
||||
d->current_sliced_object->set_id(d->object_ids[d->sliced_objects]);
|
||||
}
|
||||
|
||||
void CommandSocket::endSendSlicedObject()
|
||||
{
|
||||
d->slicedObjects++;
|
||||
if(d->slicedObjects >= d->object_count)
|
||||
d->sliced_objects++;
|
||||
std::cout << "End sliced object called. sliced objects " << d->sliced_objects << " object count: " << d->object_count << std::endl;
|
||||
if(d->sliced_objects >= d->object_count)
|
||||
{
|
||||
d->socket->sendMessage(d->slicedObjectList);
|
||||
d->slicedObjects = 0;
|
||||
d->slicedObjectList.reset();
|
||||
d->currentSlicedObject = nullptr;
|
||||
d->socket->sendMessage(d->sliced_object_list);
|
||||
d->sliced_objects = 0;
|
||||
d->sliced_object_list.reset();
|
||||
d->current_sliced_object = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandSocket::beginGCode()
|
||||
{
|
||||
d->processor->setTargetStream(&d->gcode_output_stream);
|
||||
FffProcessor::getInstance()->setTargetStream(&d->gcode_output_stream);
|
||||
}
|
||||
|
||||
void CommandSocket::sendGCodeLayer()
|
||||
{
|
||||
auto message = std::make_shared<Cura::GCodeLayer>();
|
||||
message->set_id(d->objectIds[0]);
|
||||
auto message = std::make_shared<cura::proto::GCodeLayer>();
|
||||
message->set_id(d->object_ids[0]);
|
||||
message->set_data(d->gcode_output_stream.str());
|
||||
d->socket->sendMessage(message);
|
||||
|
||||
@@ -248,23 +288,23 @@ void CommandSocket::sendGCodeLayer()
|
||||
|
||||
void CommandSocket::sendGCodePrefix(std::string prefix)
|
||||
{
|
||||
auto message = std::make_shared<Cura::GCodePrefix>();
|
||||
auto message = std::make_shared<cura::proto::GCodePrefix>();
|
||||
message->set_data(prefix);
|
||||
d->socket->sendMessage(message);
|
||||
}
|
||||
|
||||
Cura::Layer* CommandSocket::Private::getLayerById(int id)
|
||||
cura::proto::Layer* CommandSocket::Private::getLayerById(int id)
|
||||
{
|
||||
auto itr = std::find_if(currentSlicedObject->mutable_layers()->begin(), currentSlicedObject->mutable_layers()->end(), [id](Cura::Layer& l) { return l.id() == id; });
|
||||
auto itr = std::find_if(current_sliced_object->mutable_layers()->begin(), current_sliced_object->mutable_layers()->end(), [id](cura::proto::Layer& l) { return l.id() == id; });
|
||||
|
||||
Cura::Layer* layer = nullptr;
|
||||
if(itr != currentSlicedObject->mutable_layers()->end())
|
||||
cura::proto::Layer* layer = nullptr;
|
||||
if(itr != current_sliced_object->mutable_layers()->end())
|
||||
{
|
||||
layer = &(*itr);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer = currentSlicedObject->add_layers();
|
||||
layer = current_sliced_object->add_layers();
|
||||
layer->set_id(id);
|
||||
}
|
||||
|
||||
|
||||
+45
-6
@@ -4,6 +4,7 @@
|
||||
#include "utils/socket.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "settings.h"
|
||||
#include "Progress.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -11,21 +12,59 @@
|
||||
|
||||
namespace cura {
|
||||
|
||||
class fffProcessor;
|
||||
|
||||
class CommandSocket
|
||||
{
|
||||
public:
|
||||
CommandSocket(fffProcessor* processor);
|
||||
|
||||
CommandSocket();
|
||||
/*!
|
||||
* Connect with the GUI
|
||||
* This creates and initialises the arcus socket and then continues listening for messages.
|
||||
* \param ip string containing the ip to connect with
|
||||
* \param port int of the port to connect with.
|
||||
*/
|
||||
void connect(const std::string& ip, int port);
|
||||
|
||||
void handleObjectList(Cura::ObjectList* list);
|
||||
void handleSettingList(Cura::SettingList* list);
|
||||
|
||||
/*!
|
||||
* Handler for ObjectList message.
|
||||
* Loads all objects from the message and starts the slicing process
|
||||
*/
|
||||
void handleObjectList(cura::proto::ObjectList* list);
|
||||
|
||||
/*!
|
||||
* Handler for SettingList message.
|
||||
* This simply sets all the settings by using key value pair
|
||||
*/
|
||||
void handleSettingList(cura::proto::SettingList* list);
|
||||
|
||||
/*!
|
||||
* Does nothing at the moment
|
||||
*/
|
||||
void sendLayerInfo(int layer_nr, int32_t z, int32_t height);
|
||||
|
||||
/*!
|
||||
* Send a polygon to the engine. This is used for the layerview in the GUI
|
||||
*/
|
||||
void sendPolygons(cura::PolygonType type, int layer_nr, cura::Polygons& polygons, int line_width);
|
||||
|
||||
/*!
|
||||
* Send progress to GUI
|
||||
*/
|
||||
void sendProgress(float amount);
|
||||
|
||||
/*!
|
||||
* Send the current stage of the process to the GUI (starting, slicing infill, etc)
|
||||
*/
|
||||
void sendProgressStage(Progress::Stage stage);
|
||||
|
||||
/*!
|
||||
* Send time estimate of how long print would take.
|
||||
*/
|
||||
void sendPrintTime();
|
||||
|
||||
/*!
|
||||
* Does nothing at the moment
|
||||
*/
|
||||
void sendPrintMaterialForObject(int index, int extruder_nr, float material_amount);
|
||||
|
||||
void beginSendSlicedObject();
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
#define DEBUG_SHOW_LINE 0
|
||||
#define DEBUG_SHOW_LINE 1
|
||||
|
||||
#if DEBUG_SHOW_LINE == 1
|
||||
#define DEBUG_FILE_LINE __FILE_NAME__ << "." << __LINE__ << ": "
|
||||
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+123
-91
@@ -11,25 +11,17 @@ GCodeExport::GCodeExport()
|
||||
: output_stream(&std::cout), currentPosition(0,0,0), startPosition(INT32_MIN,INT32_MIN,0)
|
||||
{
|
||||
extrusion_amount = 0;
|
||||
retraction_extrusion_window = 0.0;
|
||||
extruderSwitchRetraction = 14.5;
|
||||
current_extruder = 0;
|
||||
currentFanSpeed = -1;
|
||||
|
||||
totalPrintTime = 0.0;
|
||||
for(unsigned int e=0; e<MAX_EXTRUDERS; e++)
|
||||
{
|
||||
totalFilament[e] = 0.0;
|
||||
currentTemperature[e] = 0;
|
||||
filament_diameter[e] = 0;
|
||||
}
|
||||
|
||||
currentSpeed = 1;
|
||||
retractionPrimeSpeed = 1;
|
||||
isRetracted = false;
|
||||
isZHopped = false;
|
||||
setFlavor(GCODE_FLAVOR_REPRAP);
|
||||
memset(extruderOffset, 0, sizeof(extruderOffset));
|
||||
last_coasted_amount_mm3 = 0;
|
||||
setFlavor(EGCodeFlavor::REPRAP);
|
||||
}
|
||||
|
||||
GCodeExport::~GCodeExport()
|
||||
@@ -42,36 +34,32 @@ void GCodeExport::setOutputStream(std::ostream* stream)
|
||||
*output_stream << std::fixed;
|
||||
}
|
||||
|
||||
void GCodeExport::setExtruderOffset(int id, Point p)
|
||||
{
|
||||
extruderOffset[id] = p;
|
||||
}
|
||||
|
||||
Point GCodeExport::getExtruderOffset(int id)
|
||||
{
|
||||
return extruderOffset[id];
|
||||
return extruder_attr[id].nozzle_offset;
|
||||
}
|
||||
|
||||
void GCodeExport::setSwitchExtruderCode(int id, std::string preSwitchExtruderCode, std::string postSwitchExtruderCode)
|
||||
Point GCodeExport::getGcodePos(int64_t x, int64_t y, int extruder_train)
|
||||
{
|
||||
this->preSwitchExtruderCode[id] = preSwitchExtruderCode;
|
||||
this->postSwitchExtruderCode[id] = postSwitchExtruderCode;
|
||||
if (use_extruder_offset_to_offset_coords) { return Point(x,y) - getExtruderOffset(extruder_train); }
|
||||
else { return Point(x,y); }
|
||||
}
|
||||
|
||||
|
||||
void GCodeExport::setFlavor(EGCodeFlavor flavor)
|
||||
{
|
||||
this->flavor = flavor;
|
||||
if (flavor == GCODE_FLAVOR_MACH3)
|
||||
if (flavor == EGCodeFlavor::MACH3)
|
||||
for(int n=0; n<MAX_EXTRUDERS; n++)
|
||||
extruderCharacter[n] = 'A' + n;
|
||||
extruder_attr[n].extruderCharacter = 'A' + n;
|
||||
else
|
||||
for(int n=0; n<MAX_EXTRUDERS; n++)
|
||||
extruderCharacter[n] = 'E';
|
||||
if (flavor == GCODE_FLAVOR_ULTIGCODE || flavor == GCODE_FLAVOR_REPRAP_VOLUMATRIC)
|
||||
extruder_attr[n].extruderCharacter = 'E';
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE || flavor == EGCodeFlavor::REPRAP_VOLUMATRIC)
|
||||
{
|
||||
is_volumatric = true;
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
is_volumatric = false;
|
||||
}
|
||||
@@ -82,15 +70,6 @@ EGCodeFlavor GCodeExport::getFlavor()
|
||||
return this->flavor;
|
||||
}
|
||||
|
||||
void GCodeExport::setRetractionSettings(int extruderSwitchRetraction, int extruderSwitchRetractionSpeed, int extruderSwitchPrimeSpeed, int retraction_extrusion_window, int retraction_count_max)
|
||||
{
|
||||
this->extruderSwitchRetraction = INT2MM(extruderSwitchRetraction);
|
||||
this->extruderSwitchRetractionSpeed = extruderSwitchRetractionSpeed;
|
||||
this->extruderSwitchPrimeSpeed = extruderSwitchPrimeSpeed;
|
||||
this->retraction_extrusion_window = INT2MM(retraction_extrusion_window);
|
||||
this->retraction_count_max = retraction_count_max;
|
||||
}
|
||||
|
||||
void GCodeExport::setZ(int z)
|
||||
{
|
||||
this->zPos = z;
|
||||
@@ -126,16 +105,16 @@ int GCodeExport::getExtruderNr()
|
||||
return current_extruder;
|
||||
}
|
||||
|
||||
double GCodeExport::getFilamentArea(unsigned int extruder)
|
||||
void GCodeExport::setFilamentDiameter(unsigned int extruder, int diameter)
|
||||
{
|
||||
double r = INT2MM(filament_diameter[extruder]) / 2.0;
|
||||
double filament_area = M_PI * r * r;
|
||||
return filament_area;
|
||||
double r = INT2MM(diameter) / 2.0;
|
||||
double area = M_PI * r * r;
|
||||
extruder_attr[extruder].filament_area = area;
|
||||
}
|
||||
|
||||
void GCodeExport::setFilamentDiameter(unsigned int n, int diameter)
|
||||
double GCodeExport::getFilamentArea(unsigned int extruder)
|
||||
{
|
||||
filament_diameter[n] = diameter;
|
||||
return extruder_attr[extruder].filament_area;
|
||||
}
|
||||
|
||||
double GCodeExport::getExtrusionAmountMM3(unsigned int extruder)
|
||||
@@ -154,10 +133,14 @@ double GCodeExport::getExtrusionAmountMM3(unsigned int extruder)
|
||||
double GCodeExport::getTotalFilamentUsed(int e)
|
||||
{
|
||||
if (e == current_extruder)
|
||||
return totalFilament[e] + getExtrusionAmountMM3(e);
|
||||
return totalFilament[e];
|
||||
return extruder_attr[e].totalFilament + getExtrusionAmountMM3(e);
|
||||
return extruder_attr[e].totalFilament;
|
||||
}
|
||||
|
||||
double GCodeExport::getTotalPrintTime(EPrintFeature print_feature)
|
||||
{
|
||||
return total_print_time_per_feature[(unsigned int)print_feature];
|
||||
}
|
||||
double GCodeExport::getTotalPrintTime()
|
||||
{
|
||||
return totalPrintTime;
|
||||
@@ -166,18 +149,24 @@ double GCodeExport::getTotalPrintTime()
|
||||
void GCodeExport::resetTotalPrintTimeAndFilament()
|
||||
{
|
||||
totalPrintTime = 0;
|
||||
for (unsigned int feat_idx = 0; feat_idx < (unsigned int)EPrintFeature::ENUM_COUNT; feat_idx++)
|
||||
{
|
||||
total_print_time_per_feature[feat_idx] = 0.0;
|
||||
}
|
||||
for(unsigned int e=0; e<MAX_EXTRUDERS; e++)
|
||||
{
|
||||
totalFilament[e] = 0.0;
|
||||
currentTemperature[e] = 0;
|
||||
extruder_attr[e].totalFilament = 0.0;
|
||||
extruder_attr[e].currentTemperature = 0;
|
||||
}
|
||||
extrusion_amount = 0.0;
|
||||
estimateCalculator.reset();
|
||||
}
|
||||
|
||||
void GCodeExport::updateTotalPrintTime()
|
||||
void GCodeExport::updateTotalPrintTime(EPrintFeature print_feature)
|
||||
{
|
||||
totalPrintTime += estimateCalculator.calculate();
|
||||
double time = estimateCalculator.calculate();
|
||||
totalPrintTime += time;
|
||||
total_print_time_per_feature[(unsigned int)print_feature] += time;
|
||||
estimateCalculator.reset();
|
||||
}
|
||||
|
||||
@@ -202,10 +191,10 @@ void GCodeExport::writeLine(const char* line)
|
||||
|
||||
void GCodeExport::resetExtrusionValue()
|
||||
{
|
||||
if (extrusion_amount != 0.0 && flavor != GCODE_FLAVOR_MAKERBOT && flavor != GCODE_FLAVOR_BFB)
|
||||
if (extrusion_amount != 0.0 && flavor != EGCodeFlavor::MAKERBOT && flavor != EGCodeFlavor::BFB)
|
||||
{
|
||||
*output_stream << "G92 " << extruderCharacter[current_extruder] << "0\n";
|
||||
totalFilament[current_extruder] += getExtrusionAmountMM3(current_extruder);
|
||||
*output_stream << "G92 " << extruder_attr[current_extruder].extruderCharacter << "0\n";
|
||||
extruder_attr[current_extruder].totalFilament += getExtrusionAmountMM3(current_extruder);
|
||||
for (unsigned int i = 0; i < extrusion_amount_at_previous_n_retractions.size(); i++)
|
||||
extrusion_amount_at_previous_n_retractions[i] -= extrusion_amount;
|
||||
extrusion_amount = 0.0;
|
||||
@@ -218,27 +207,36 @@ void GCodeExport::writeDelay(double timeAmount)
|
||||
totalPrintTime += timeAmount;
|
||||
}
|
||||
|
||||
void GCodeExport::writeMove(Point p, int speed, double extrusion_mm3_per_mm)
|
||||
void GCodeExport::writeMove(Point p, double speed, double extrusion_mm3_per_mm)
|
||||
{
|
||||
writeMove(p.X, p.Y, zPos, speed, extrusion_mm3_per_mm);
|
||||
}
|
||||
|
||||
void GCodeExport::writeMove(Point3 p, int speed, double extrusion_mm3_per_mm)
|
||||
void GCodeExport::writeMove(Point3 p, double speed, double extrusion_mm3_per_mm)
|
||||
{
|
||||
writeMove(p.x, p.y, p.z, speed, extrusion_mm3_per_mm);
|
||||
}
|
||||
void GCodeExport::writeMove(int x, int y, int z, int speed, double extrusion_mm3_per_mm)
|
||||
|
||||
void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_mm3_per_mm)
|
||||
{
|
||||
if (currentPosition.x == x && currentPosition.y == y && currentPosition.z == z)
|
||||
return;
|
||||
|
||||
assert(speed*60 < 10000 && speed*60 > 100); // normal F values occurring in UM2 gcode (this code should not be compiled for release)
|
||||
assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(300)); // no crazy positions (this code should not be compiled for release)
|
||||
|
||||
if (extrusion_mm3_per_mm < 0)
|
||||
logWarning("Warning! Negative extrusion move!");
|
||||
|
||||
double extrusion_per_mm = extrusion_mm3_per_mm;
|
||||
if (!is_volumatric)
|
||||
{
|
||||
extrusion_per_mm = extrusion_mm3_per_mm / getFilamentArea(current_extruder);
|
||||
}
|
||||
|
||||
Point gcode_pos = getGcodePos(x,y, current_extruder);
|
||||
|
||||
if (flavor == GCODE_FLAVOR_BFB)
|
||||
if (flavor == EGCodeFlavor::BFB)
|
||||
{
|
||||
//For Bits From Bytes machines, we need to handle this completely differently. As they do not use E values but RPM values.
|
||||
float fspeed = speed * 60;
|
||||
@@ -249,12 +247,12 @@ void GCodeExport::writeMove(int x, int y, int z, int speed, double extrusion_mm3
|
||||
{
|
||||
if (isRetracted)
|
||||
{
|
||||
if (currentSpeed != int(rpm * 10))
|
||||
if (currentSpeed != double(rpm))
|
||||
{
|
||||
//fprintf(f, "; %f e-per-mm %d mm-width %d mm/s\n", extrusion_per_mm, lineWidth, speed);
|
||||
//fprintf(f, "M108 S%0.1f\r\n", rpm);
|
||||
*output_stream << "M108 S" << std::setprecision(1) << rpm << "\r\n";
|
||||
currentSpeed = int(rpm * 10);
|
||||
currentSpeed = double(rpm);
|
||||
}
|
||||
//Add M101 or M201 to enable the proper extruder.
|
||||
*output_stream << "M" << int((current_extruder + 1) * 100 + 1) << "\r\n";
|
||||
@@ -276,9 +274,15 @@ void GCodeExport::writeMove(int x, int y, int z, int speed, double extrusion_mm3
|
||||
isRetracted = true;
|
||||
}
|
||||
}
|
||||
*output_stream << std::setprecision(3) << "G1 X" << INT2MM(x - extruderOffset[current_extruder].X) << " Y" << INT2MM(y - extruderOffset[current_extruder].Y) << " Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << "\r\n";
|
||||
}else{
|
||||
*output_stream << std::setprecision(3) <<
|
||||
"G1 X" << INT2MM(gcode_pos.X) <<
|
||||
" Y" << INT2MM(gcode_pos.Y) <<
|
||||
" Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << "\r\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
//Normal E handling.
|
||||
|
||||
if (extrusion_mm3_per_mm > 0.000001)
|
||||
{
|
||||
Point3 diff = Point3(x,y,z) - getPosition();
|
||||
@@ -287,15 +291,20 @@ void GCodeExport::writeMove(int x, int y, int z, int speed, double extrusion_mm3
|
||||
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z) << "\n";
|
||||
isZHopped = false;
|
||||
}
|
||||
extrusion_amount += (is_volumatric) ? last_coasted_amount_mm3 : last_coasted_amount_mm3 / getFilamentArea(current_extruder);
|
||||
if (isRetracted)
|
||||
{
|
||||
if (flavor == GCODE_FLAVOR_ULTIGCODE || flavor == GCODE_FLAVOR_REPRAP_VOLUMATRIC)
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE || flavor == EGCodeFlavor::REPRAP_VOLUMATRIC)
|
||||
{
|
||||
*output_stream << "G11\n";
|
||||
//Assume default UM2 retraction settings.
|
||||
if (last_coasted_amount_mm3 > 0)
|
||||
{
|
||||
*output_stream << "G1 F" << (retractionPrimeSpeed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount << "\n";
|
||||
}
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), 25.0);
|
||||
}else{
|
||||
*output_stream << "G1 F" << (retractionPrimeSpeed * 60) << " " << extruderCharacter[current_extruder] << std::setprecision(5) << extrusion_amount << "\n";
|
||||
*output_stream << "G1 F" << (retractionPrimeSpeed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount << "\n";
|
||||
currentSpeed = retractionPrimeSpeed;
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), currentSpeed);
|
||||
}
|
||||
@@ -303,6 +312,15 @@ void GCodeExport::writeMove(int x, int y, int z, int speed, double extrusion_mm3
|
||||
resetExtrusionValue();
|
||||
isRetracted = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (last_coasted_amount_mm3 > 0)
|
||||
{
|
||||
*output_stream << "G1 F" << (retractionPrimeSpeed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount << "\n";
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount), currentSpeed);
|
||||
}
|
||||
}
|
||||
last_coasted_amount_mm3 = 0;
|
||||
extrusion_amount += extrusion_per_mm * diff.vSizeMM();
|
||||
*output_stream << "G1";
|
||||
}else{
|
||||
@@ -315,11 +333,13 @@ void GCodeExport::writeMove(int x, int y, int z, int speed, double extrusion_mm3
|
||||
currentSpeed = speed;
|
||||
}
|
||||
|
||||
*output_stream << std::setprecision(3) << " X" << INT2MM(x - extruderOffset[current_extruder].X) << " Y" << INT2MM(y - extruderOffset[current_extruder].Y);
|
||||
*output_stream << std::setprecision(3) <<
|
||||
" X" << INT2MM(gcode_pos.X) <<
|
||||
" Y" << INT2MM(gcode_pos.Y);
|
||||
if (z != currentPosition.z)
|
||||
*output_stream << " Z" << INT2MM(z);
|
||||
if (extrusion_mm3_per_mm > 0.000001)
|
||||
*output_stream << " " << extruderCharacter[current_extruder] << std::setprecision(5) << extrusion_amount;
|
||||
*output_stream << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount;
|
||||
*output_stream << "\n";
|
||||
}
|
||||
|
||||
@@ -330,29 +350,31 @@ void GCodeExport::writeMove(int x, int y, int z, int speed, double extrusion_mm3
|
||||
|
||||
void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
|
||||
{
|
||||
if (flavor == GCODE_FLAVOR_BFB)//BitsFromBytes does automatic retraction.
|
||||
if (flavor == EGCodeFlavor::BFB)//BitsFromBytes does automatic retraction.
|
||||
return;
|
||||
if (isRetracted)
|
||||
return;
|
||||
if (config->amount <= 0)
|
||||
return;
|
||||
|
||||
if (!force && retraction_count_max > 0 && int(extrusion_amount_at_previous_n_retractions.size()) == retraction_count_max - 1
|
||||
&& extrusion_amount < extrusion_amount_at_previous_n_retractions.back() + retraction_extrusion_window)
|
||||
if (!force && config->retraction_count_max > 0 && int(extrusion_amount_at_previous_n_retractions.size()) == config->retraction_count_max - 1
|
||||
&& extrusion_amount < extrusion_amount_at_previous_n_retractions.back() + config->retraction_extrusion_window)
|
||||
return;
|
||||
|
||||
if (config->primeAmount > 0)
|
||||
{
|
||||
extrusion_amount += config->primeAmount;
|
||||
}
|
||||
retractionPrimeSpeed = config->primeSpeed;
|
||||
|
||||
if (flavor == GCODE_FLAVOR_ULTIGCODE || flavor == GCODE_FLAVOR_REPRAP_VOLUMATRIC)
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE || flavor == EGCodeFlavor::REPRAP_VOLUMATRIC)
|
||||
{
|
||||
*output_stream << "G10\n";
|
||||
//Assume default UM2 retraction settings.
|
||||
double retraction_distance = 4.5;
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount - retraction_distance), 25); // TODO: hardcoded values!
|
||||
}else{
|
||||
*output_stream << "G1 F" << (config->speed * 60) << " " << extruderCharacter[current_extruder] << std::setprecision(5) << extrusion_amount - config->amount << "\n";
|
||||
*output_stream << "G1 F" << (config->speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << extrusion_amount - config->amount << "\n";
|
||||
currentSpeed = config->speed;
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), extrusion_amount - config->amount), currentSpeed);
|
||||
}
|
||||
@@ -362,19 +384,18 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
|
||||
isZHopped = true;
|
||||
}
|
||||
extrusion_amount_at_previous_n_retractions.push_front(extrusion_amount);
|
||||
if (int(extrusion_amount_at_previous_n_retractions.size()) == retraction_count_max)
|
||||
if (int(extrusion_amount_at_previous_n_retractions.size()) == config->retraction_count_max)
|
||||
{
|
||||
extrusion_amount_at_previous_n_retractions.pop_back();
|
||||
}
|
||||
isRetracted = true;
|
||||
}
|
||||
|
||||
void GCodeExport::switchExtruder(int newExtruder)
|
||||
void GCodeExport::writeRetraction_extruderSwitch()
|
||||
{
|
||||
if (current_extruder == newExtruder)
|
||||
return;
|
||||
|
||||
if (flavor == GCODE_FLAVOR_BFB)
|
||||
if (isRetracted) { return; }
|
||||
|
||||
if (flavor == EGCodeFlavor::BFB)
|
||||
{
|
||||
if (!isRetracted)
|
||||
*output_stream << "M103\r\n";
|
||||
@@ -382,26 +403,38 @@ void GCodeExport::switchExtruder(int newExtruder)
|
||||
isRetracted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
resetExtrusionValue();
|
||||
if (flavor == GCODE_FLAVOR_ULTIGCODE || flavor == GCODE_FLAVOR_REPRAP_VOLUMATRIC)
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE || flavor == EGCodeFlavor::REPRAP_VOLUMATRIC)
|
||||
{
|
||||
*output_stream << "G10 S1\n";
|
||||
}else{
|
||||
*output_stream << "G1 F" << (extruderSwitchRetractionSpeed * 60) << " " << extruderCharacter[current_extruder] << std::setprecision(5) << (extrusion_amount - extruderSwitchRetraction) << "\n";
|
||||
currentSpeed = extruderSwitchRetractionSpeed;
|
||||
*output_stream << "G1 F" << (extruder_attr[current_extruder].extruderSwitchRetractionSpeed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << (extrusion_amount - extruder_attr[current_extruder].extruderSwitchRetraction) << "\n";
|
||||
currentSpeed = extruder_attr[current_extruder].extruderSwitchRetractionSpeed;
|
||||
}
|
||||
isRetracted = true;
|
||||
}
|
||||
|
||||
current_extruder = newExtruder;
|
||||
if (flavor == GCODE_FLAVOR_MACH3)
|
||||
void GCodeExport::switchExtruder(int new_extruder)
|
||||
{
|
||||
if (current_extruder == new_extruder)
|
||||
return;
|
||||
|
||||
if (!isRetracted) // assumes the last retraction already was an extruder switch retraction
|
||||
{
|
||||
writeRetraction_extruderSwitch();
|
||||
}
|
||||
|
||||
int old_extruder = current_extruder;
|
||||
current_extruder = new_extruder;
|
||||
if (flavor == EGCodeFlavor::MACH3)
|
||||
resetExtrusionValue();
|
||||
isRetracted = true;
|
||||
writeCode(preSwitchExtruderCode[current_extruder].c_str());
|
||||
if (flavor == GCODE_FLAVOR_MAKERBOT)
|
||||
writeCode(extruder_attr[old_extruder].end_code.c_str());
|
||||
if (flavor == EGCodeFlavor::MAKERBOT)
|
||||
*output_stream << "M135 T" << current_extruder << "\n";
|
||||
else
|
||||
*output_stream << "T" << current_extruder << "\n";
|
||||
writeCode(postSwitchExtruderCode[current_extruder].c_str());
|
||||
writeCode(extruder_attr[new_extruder].start_code.c_str());
|
||||
|
||||
//Change the Z position so it gets re-writting again. We do not know if the switch code modified the Z position.
|
||||
currentPosition.z += 1;
|
||||
@@ -410,26 +443,26 @@ void GCodeExport::switchExtruder(int newExtruder)
|
||||
void GCodeExport::writeCode(const char* str)
|
||||
{
|
||||
*output_stream << str;
|
||||
if (flavor == GCODE_FLAVOR_BFB)
|
||||
if (flavor == EGCodeFlavor::BFB)
|
||||
*output_stream << "\r\n";
|
||||
else
|
||||
*output_stream << "\n";
|
||||
}
|
||||
|
||||
void GCodeExport::writeFanCommand(int speed)
|
||||
void GCodeExport::writeFanCommand(double speed)
|
||||
{
|
||||
if (currentFanSpeed == speed)
|
||||
return;
|
||||
if (speed > 0)
|
||||
{
|
||||
if (flavor == GCODE_FLAVOR_MAKERBOT)
|
||||
if (flavor == EGCodeFlavor::MAKERBOT)
|
||||
*output_stream << "M126 T0\n"; //value = speed * 255 / 100 // Makerbot cannot set fan speed...;
|
||||
else
|
||||
*output_stream << "M106 S" << (speed * 255 / 100) << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flavor == GCODE_FLAVOR_MAKERBOT)
|
||||
if (flavor == EGCodeFlavor::MAKERBOT)
|
||||
*output_stream << "M127 T0\n";
|
||||
else
|
||||
*output_stream << "M107\n";
|
||||
@@ -437,9 +470,9 @@ void GCodeExport::writeFanCommand(int speed)
|
||||
currentFanSpeed = speed;
|
||||
}
|
||||
|
||||
void GCodeExport::writeTemperatureCommand(int extruder, int temperature, bool wait)
|
||||
void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool wait)
|
||||
{
|
||||
if (!wait && currentTemperature[extruder] == temperature)
|
||||
if (!wait && extruder_attr[extruder].currentTemperature == temperature)
|
||||
return;
|
||||
|
||||
if (wait)
|
||||
@@ -449,10 +482,10 @@ void GCodeExport::writeTemperatureCommand(int extruder, int temperature, bool wa
|
||||
if (extruder != current_extruder)
|
||||
*output_stream << " T" << extruder;
|
||||
*output_stream << " S" << temperature << "\n";
|
||||
currentTemperature[extruder] = temperature;
|
||||
extruder_attr[extruder].currentTemperature = temperature;
|
||||
}
|
||||
|
||||
void GCodeExport::writeBedTemperatureCommand(int temperature, bool wait)
|
||||
void GCodeExport::writeBedTemperatureCommand(double temperature, bool wait)
|
||||
{
|
||||
if (wait)
|
||||
*output_stream << "M190 S";
|
||||
@@ -461,9 +494,8 @@ void GCodeExport::writeBedTemperatureCommand(int temperature, bool wait)
|
||||
*output_stream << temperature << "\n";
|
||||
}
|
||||
|
||||
void GCodeExport::finalize(int maxObjectHeight, int moveSpeed, const char* endCode)
|
||||
void GCodeExport::finalize(int maxObjectHeight, double moveSpeed, const char* endCode)
|
||||
{
|
||||
std::cerr << "maxObjectHeight : " << maxObjectHeight << std::endl;
|
||||
writeFanCommand(0);
|
||||
setZ(maxObjectHeight + 5000);
|
||||
writeMove(Point3(0,0,maxObjectHeight + 5000) + getPositionXY(), moveSpeed, 0);
|
||||
|
||||
+108
-44
@@ -4,41 +4,59 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <deque> // for extrusionAmountAtPreviousRetractions
|
||||
#include <sstream> // for stream.str()
|
||||
|
||||
#include "settings.h"
|
||||
#include "utils/intpoint.h"
|
||||
#include "timeEstimate.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
struct CoastingConfig
|
||||
{
|
||||
bool coasting_enable;
|
||||
double coasting_volume_move;
|
||||
double coasting_speed_move;
|
||||
double coasting_min_volume_move;
|
||||
|
||||
double coasting_volume_retract;
|
||||
double coasting_speed_retract;
|
||||
double coasting_min_volume_retract;
|
||||
};
|
||||
|
||||
class RetractionConfig
|
||||
{
|
||||
public:
|
||||
double amount; //!< The amount
|
||||
int speed;
|
||||
int primeSpeed;
|
||||
double primeAmount;
|
||||
int zHop;
|
||||
double amount; //!< The amount retracted
|
||||
double speed; //!< The speed with which to retract
|
||||
double primeSpeed; //!< the speed with which to unretract
|
||||
double primeAmount; //!< the amount of material primed after unretracting
|
||||
int zHop; //!< the amount with which to lift the head during a retraction-travel
|
||||
int retraction_min_travel_distance; //!<
|
||||
double retraction_extrusion_window;
|
||||
int retraction_count_max;
|
||||
};
|
||||
|
||||
//The GCodePathConfig is the configuration for moves/extrusion actions. This defines at which width the line is printed and at which speed.
|
||||
class GCodePathConfig
|
||||
{
|
||||
private:
|
||||
int speed;
|
||||
int line_width;
|
||||
int flow;
|
||||
int layer_thickness;
|
||||
double extrusion_mm3_per_mm;
|
||||
double speed; //!< movement speed
|
||||
int line_width; //!< width of the line extruded
|
||||
double flow; //!< extrusion flow in %
|
||||
int layer_thickness; //!< layer height
|
||||
double extrusion_mm3_per_mm;//!< mm^3 filament moved per mm line extruded
|
||||
public:
|
||||
const char* name;
|
||||
bool spiralize;
|
||||
RetractionConfig* retraction_config;
|
||||
RetractionConfig *const retraction_config;
|
||||
|
||||
GCodePathConfig() : speed(0), line_width(0), extrusion_mm3_per_mm(0.0), name(nullptr), spiralize(false), retraction_config(nullptr) {}
|
||||
// GCodePathConfig() : speed(0), line_width(0), extrusion_mm3_per_mm(0.0), name(nullptr), spiralize(false), retraction_config(nullptr) {}
|
||||
GCodePathConfig(RetractionConfig* retraction_config, const char* name) : speed(0), line_width(0), extrusion_mm3_per_mm(0.0), name(name), spiralize(false), retraction_config(retraction_config) {}
|
||||
|
||||
void setSpeed(int speed)
|
||||
void setSpeed(double speed)
|
||||
{
|
||||
this->speed = speed;
|
||||
}
|
||||
@@ -54,14 +72,14 @@ public:
|
||||
this->layer_thickness = layer_height;
|
||||
calculateExtrusion();
|
||||
}
|
||||
|
||||
void setFlow(int flow)
|
||||
|
||||
void setFlow(double flow)
|
||||
{
|
||||
this->flow = flow;
|
||||
calculateExtrusion();
|
||||
}
|
||||
|
||||
void smoothSpeed(int min_speed, int layer_nr, int max_speed_layer)
|
||||
void smoothSpeed(double min_speed, int layer_nr, double max_speed_layer)
|
||||
{
|
||||
speed = (speed*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
|
||||
}
|
||||
@@ -71,7 +89,7 @@ public:
|
||||
return extrusion_mm3_per_mm;
|
||||
}
|
||||
|
||||
int getSpeed()
|
||||
double getSpeed()
|
||||
{
|
||||
return speed;
|
||||
}
|
||||
@@ -93,33 +111,55 @@ private:
|
||||
class GCodeExport
|
||||
{
|
||||
private:
|
||||
struct ExtruderTrainAttributes
|
||||
{
|
||||
Point nozzle_offset;
|
||||
char extruderCharacter;
|
||||
std::string start_code;
|
||||
std::string end_code;
|
||||
double filament_area; //!< in mm^2 for non-volumetric, cylindrical filament
|
||||
|
||||
double extruderSwitchRetraction;
|
||||
int extruderSwitchRetractionSpeed;
|
||||
int extruderSwitchPrimeSpeed;
|
||||
|
||||
double totalFilament; //!< total filament used per extruder in mm^3
|
||||
int currentTemperature;
|
||||
|
||||
ExtruderTrainAttributes()
|
||||
: nozzle_offset(0,0)
|
||||
, extruderCharacter(0)
|
||||
, start_code("")
|
||||
, end_code("")
|
||||
, filament_area(0)
|
||||
, extruderSwitchRetraction(0.0)
|
||||
, extruderSwitchRetractionSpeed(0)
|
||||
, extruderSwitchPrimeSpeed(0)
|
||||
, totalFilament(0)
|
||||
, currentTemperature(0)
|
||||
{ }
|
||||
};
|
||||
ExtruderTrainAttributes extruder_attr[MAX_EXTRUDERS];
|
||||
bool use_extruder_offset_to_offset_coords;
|
||||
|
||||
std::ostream* output_stream;
|
||||
double extrusion_amount; // in mm or mm^3
|
||||
double extruderSwitchRetraction;
|
||||
int extruderSwitchRetractionSpeed;
|
||||
int extruderSwitchPrimeSpeed;
|
||||
double retraction_extrusion_window;
|
||||
int retraction_count_max;
|
||||
std::deque<double> extrusion_amount_at_previous_n_retractions; // in mm or mm^3
|
||||
Point3 currentPosition;
|
||||
Point3 startPosition;
|
||||
Point extruderOffset[MAX_EXTRUDERS];
|
||||
char extruderCharacter[MAX_EXTRUDERS];
|
||||
int currentTemperature[MAX_EXTRUDERS];
|
||||
int currentSpeed;
|
||||
double currentSpeed;
|
||||
int zPos;
|
||||
bool isRetracted;
|
||||
bool isZHopped;
|
||||
int retractionPrimeSpeed;
|
||||
|
||||
double last_coasted_amount_mm3; //!< The coasted amount of filament to be primed on the first next extrusion. (same type as GCodeExport::extrusion_amount)
|
||||
double retractionPrimeSpeed;
|
||||
int current_extruder;
|
||||
int currentFanSpeed;
|
||||
EGCodeFlavor flavor;
|
||||
std::string preSwitchExtruderCode[MAX_EXTRUDERS];
|
||||
std::string postSwitchExtruderCode[MAX_EXTRUDERS];
|
||||
|
||||
double totalFilament[MAX_EXTRUDERS]; // in mm^3
|
||||
double filament_diameter[MAX_EXTRUDERS]; // in mm^3
|
||||
|
||||
double totalPrintTime;
|
||||
double total_print_time_per_feature[(unsigned int)EPrintFeature::ENUM_COUNT];
|
||||
TimeEstimateCalculator estimateCalculator;
|
||||
|
||||
bool is_volumatric;
|
||||
@@ -130,17 +170,17 @@ public:
|
||||
|
||||
void setOutputStream(std::ostream* stream);
|
||||
|
||||
void setExtruderOffset(int id, Point p);
|
||||
Point getExtruderOffset(int id);
|
||||
void setSwitchExtruderCode(int id, std::string preSwitchExtruderCode, std::string postSwitchExtruderCode);
|
||||
|
||||
Point getGcodePos(int64_t x, int64_t y, int extruder_train);
|
||||
|
||||
void setFlavor(EGCodeFlavor flavor);
|
||||
EGCodeFlavor getFlavor();
|
||||
|
||||
void setRetractionSettings(int extruderSwitchRetraction, int extruderSwitchRetractionSpeed, int extruderSwitchPrimeSpeed, int minimalExtrusionBeforeRetraction, int retraction_count_max);
|
||||
|
||||
void setZ(int z);
|
||||
|
||||
void setLastCoastedAmountMM3(double last_coasted_amount) { this->last_coasted_amount_mm3 = last_coasted_amount; }
|
||||
|
||||
Point3 getPosition();
|
||||
|
||||
Point getPositionXY();
|
||||
@@ -161,7 +201,8 @@ public:
|
||||
double getTotalFilamentUsed(int e);
|
||||
|
||||
double getTotalPrintTime();
|
||||
void updateTotalPrintTime();
|
||||
double getTotalPrintTime(EPrintFeature print_feature);
|
||||
void updateTotalPrintTime(EPrintFeature print_feature = EPrintFeature::UNCLASSIFIED);
|
||||
void resetTotalPrintTimeAndFilament();
|
||||
|
||||
void writeComment(std::string comment);
|
||||
@@ -174,24 +215,47 @@ public:
|
||||
|
||||
void writeDelay(double timeAmount);
|
||||
|
||||
void writeMove(Point p, int speed, double extrusion_per_mm);
|
||||
void writeMove(Point p, double speed, double extrusion_per_mm);
|
||||
|
||||
void writeMove(Point3 p, int speed, double extrusion_per_mm);
|
||||
void writeMove(Point3 p, double speed, double extrusion_per_mm);
|
||||
private:
|
||||
void writeMove(int x, int y, int z, int speed, double extrusion_per_mm);
|
||||
void writeMove(int x, int y, int z, double speed, double extrusion_per_mm);
|
||||
public:
|
||||
void writeRetraction(RetractionConfig* config, bool force=false);
|
||||
|
||||
void writeRetraction_extruderSwitch();
|
||||
|
||||
void switchExtruder(int newExtruder);
|
||||
|
||||
void writeCode(const char* str);
|
||||
|
||||
void writeFanCommand(int speed);
|
||||
void writeFanCommand(double speed);
|
||||
|
||||
void writeTemperatureCommand(int extruder, int temperature, bool wait = false);
|
||||
void writeBedTemperatureCommand(int temperature, bool wait = false);
|
||||
void writeTemperatureCommand(int extruder, double temperature, bool wait = false);
|
||||
void writeBedTemperatureCommand(double temperature, bool wait = false);
|
||||
|
||||
void preSetup(MeshGroup* settings)
|
||||
{
|
||||
for(int n=0; n<settings->getSettingAsCount("machine_extruder_count"); n++)
|
||||
{
|
||||
ExtruderTrain* train = settings->getExtruderTrain(n);
|
||||
setFilamentDiameter(n, train->getSettingInMicrons("material_diameter"));
|
||||
|
||||
extruder_attr[n].nozzle_offset = Point(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
||||
|
||||
extruder_attr[n].start_code = train->getSettingString("machine_extruder_start_code");
|
||||
extruder_attr[n].end_code = train->getSettingString("machine_extruder_end_code");
|
||||
|
||||
extruder_attr[n].extruderSwitchRetraction = INT2MM(train->getSettingInMicrons("switch_extruder_retraction_amount"));
|
||||
extruder_attr[n].extruderSwitchRetractionSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_retraction_speed");
|
||||
extruder_attr[n].extruderSwitchPrimeSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_prime_speed");
|
||||
}
|
||||
|
||||
setFlavor(settings->getSettingAsGCodeFlavor("machine_gcode_flavor"));
|
||||
use_extruder_offset_to_offset_coords = settings->getSettingBoolean("machine_use_extruder_offset_to_offset_coords");
|
||||
}
|
||||
void finalize(int maxObjectHeight, double moveSpeed, const char* endCode);
|
||||
|
||||
void finalize(int maxObjectHeight, int moveSpeed, const char* endCode);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+430
-128
@@ -1,11 +1,15 @@
|
||||
#include "gcodePlanner.h"
|
||||
#include "pathOrderOptimizer.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include <cstring>
|
||||
#include "debug.h" // debugging
|
||||
#include "MergeInfillLines.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config)
|
||||
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, float flow)
|
||||
{
|
||||
if (paths.size() > 0 && paths[paths.size()-1].config == config && !paths[paths.size()-1].done)
|
||||
if (paths.size() > 0 && paths[paths.size()-1].config == config && !paths[paths.size()-1].done && paths[paths.size()-1].flow == flow)
|
||||
return &paths[paths.size()-1];
|
||||
paths.push_back(GCodePath());
|
||||
GCodePath* ret = &paths[paths.size()-1];
|
||||
@@ -13,6 +17,11 @@ GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config)
|
||||
ret->config = config;
|
||||
ret->extruder = currentExtruder;
|
||||
ret->done = false;
|
||||
ret->flow = flow;
|
||||
if (config != &travelConfig)
|
||||
{
|
||||
last_retraction_config = config->retraction_config;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -22,110 +31,189 @@ void GCodePlanner::forceNewPathStart()
|
||||
paths[paths.size()-1].done = true;
|
||||
}
|
||||
|
||||
GCodePlanner::GCodePlanner(GCodeExport& gcode, RetractionConfig* retraction_config, int travelSpeed, int retractionMinimalDistance)
|
||||
: gcode(gcode), travelConfig(retraction_config, "MOVE")
|
||||
GCodePlanner::GCodePlanner(GCodeExport& gcode, SliceDataStorage& storage, RetractionConfig* retraction_config_travel, double travelSpeed, bool retraction_combing, unsigned int layer_nr, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: gcode(gcode), storage(storage)
|
||||
, travelConfig(retraction_config_travel, "MOVE")
|
||||
{
|
||||
lastPosition = gcode.getPositionXY();
|
||||
travelConfig.setSpeed(travelSpeed);
|
||||
comb = nullptr;
|
||||
extrudeSpeedFactor = 100;
|
||||
travelSpeedFactor = 100;
|
||||
last_retraction_config = &storage.retraction_config; // start with general config
|
||||
setExtrudeSpeedFactor(1.0);
|
||||
setTravelSpeedFactor(1.0);
|
||||
extraTime = 0.0;
|
||||
totalPrintTime = 0.0;
|
||||
forceRetraction = false;
|
||||
alwaysRetract = false;
|
||||
currentExtruder = gcode.getExtruderNr();
|
||||
this->retractionMinimalDistance = retractionMinimalDistance;
|
||||
if (retraction_combing)
|
||||
{
|
||||
was_combing = true; // means it will try to get inside the comb boundary first
|
||||
is_going_to_comb = true; // means it will try to get inside the comb boundary
|
||||
comb = new Comb(storage, layer_nr, comb_boundary_offset, travel_avoid_other_parts, travel_avoid_distance);
|
||||
}
|
||||
else
|
||||
comb = nullptr;
|
||||
}
|
||||
|
||||
GCodePlanner::~GCodePlanner()
|
||||
{
|
||||
if (comb)
|
||||
delete comb;
|
||||
}
|
||||
|
||||
void GCodePlanner::addTravel(Point p)
|
||||
void GCodePlanner::setCombing(bool going_to_comb)
|
||||
{
|
||||
GCodePath* path = getLatestPathWithConfig(&travelConfig);
|
||||
if (forceRetraction)
|
||||
{
|
||||
if (!shorterThen(lastPosition - p, retractionMinimalDistance))
|
||||
{
|
||||
path->retract = true;
|
||||
}
|
||||
forceRetraction = false;
|
||||
}else if (comb != nullptr)
|
||||
{
|
||||
std::vector<Point> pointList;
|
||||
if (comb->calc(lastPosition, p, pointList))
|
||||
{
|
||||
for(unsigned int n=0; n<pointList.size(); n++)
|
||||
{
|
||||
path->points.push_back(pointList[n]);
|
||||
}
|
||||
}else{
|
||||
if (!shorterThen(lastPosition - p, retractionMinimalDistance))
|
||||
path->retract = true;
|
||||
}
|
||||
}else if (alwaysRetract)
|
||||
{
|
||||
if (!shorterThen(lastPosition - p, retractionMinimalDistance))
|
||||
path->retract = true;
|
||||
}
|
||||
path->points.push_back(p);
|
||||
lastPosition = p;
|
||||
is_going_to_comb = going_to_comb;
|
||||
}
|
||||
|
||||
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config)
|
||||
|
||||
bool GCodePlanner::setExtruder(int extruder)
|
||||
{
|
||||
getLatestPathWithConfig(config)->points.push_back(p);
|
||||
lastPosition = p;
|
||||
if (extruder == currentExtruder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(currentExtruder);
|
||||
bool end_pos_absolute = train->getSettingBoolean("machine_extruder_end_pos_abs");
|
||||
Point end_pos(train->getSettingInMicrons("machine_extruder_end_pos_x"), train->getSettingInMicrons("machine_extruder_end_pos_y"));
|
||||
addTravel((end_pos_absolute)? end_pos : lastPosition + end_pos);
|
||||
|
||||
currentExtruder = extruder; // the extruder switch
|
||||
|
||||
forceNewPathStart();
|
||||
|
||||
train = storage.meshgroup->getExtruderTrain(currentExtruder);
|
||||
bool start_pos_absolute = train->getSettingBoolean("machine_extruder_start_pos_abs");
|
||||
Point start_pos(train->getSettingInMicrons("machine_extruder_start_pos_x"), train->getSettingInMicrons("machine_extruder_start_pos_y"));
|
||||
lastPosition = (start_pos_absolute)? start_pos : lastPosition + start_pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GCodePlanner::moveInsideCombBoundary(int distance)
|
||||
{
|
||||
if (!comb || comb->inside(lastPosition)) return;
|
||||
if (!comb) return;
|
||||
Point p = lastPosition;
|
||||
if (comb->moveInside(&p, distance))
|
||||
if (comb->moveInsideBoundary(&p, distance))
|
||||
{
|
||||
//Move inside again, so we move out of tight 90deg corners
|
||||
comb->moveInside(&p, distance);
|
||||
comb->moveInsideBoundary(&p, distance);
|
||||
if (comb->inside(p))
|
||||
{
|
||||
addTravel(p);
|
||||
addTravel_simple(p);
|
||||
//Make sure the that any retraction happens after this move, not before it by starting a new move path.
|
||||
forceNewPathStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config)
|
||||
void GCodePlanner::addTravel(Point p)
|
||||
{
|
||||
GCodePath* path = nullptr;
|
||||
|
||||
if (comb != nullptr && lastPosition != Point(0,0))
|
||||
{
|
||||
CombPaths combPaths;
|
||||
if (comb->calc(lastPosition, p, combPaths, was_combing, is_going_to_comb))
|
||||
{
|
||||
bool retract = combPaths.size() > 1;
|
||||
{ // check whether we want to retract
|
||||
if (!retract && combPaths.size() == 1 && combPaths[0].throughAir && combPaths[0].size() > 2)
|
||||
{ // retract when avoiding obstacles through air
|
||||
retract = true;
|
||||
}
|
||||
|
||||
for (unsigned int path_idx = 0; path_idx < combPaths.size() && !retract; path_idx++)
|
||||
{ // retract when path moves through a boundary
|
||||
if (combPaths[path_idx].cross_boundary) { retract = true; }
|
||||
}
|
||||
}
|
||||
|
||||
if (retract && last_retraction_config->zHop > 0)
|
||||
{ // TODO: stop comb calculation early! (as soon as we see we don't end in the same part as we began)
|
||||
path = getLatestPathWithConfig(&travelConfig);
|
||||
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
|
||||
{
|
||||
path->retract = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (CombPath& combPath : combPaths)
|
||||
{ // add all comb paths (don't do anything special for paths which are moving through air)
|
||||
if (combPath.size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
path = getLatestPathWithConfig(&travelConfig);
|
||||
path->retract = retract;
|
||||
for (Point& combPoint : combPath)
|
||||
{
|
||||
path->points.push_back(combPoint);
|
||||
}
|
||||
lastPosition = combPath.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
path = getLatestPathWithConfig(&travelConfig);
|
||||
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
|
||||
{
|
||||
path->retract = true;
|
||||
}
|
||||
}
|
||||
was_combing = is_going_to_comb;
|
||||
}
|
||||
|
||||
addTravel_simple(p, path);
|
||||
}
|
||||
|
||||
void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
{
|
||||
if (path == nullptr)
|
||||
{
|
||||
path = getLatestPathWithConfig(&travelConfig);
|
||||
}
|
||||
path->points.push_back(p);
|
||||
lastPosition = p;
|
||||
}
|
||||
|
||||
|
||||
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, float flow)
|
||||
{
|
||||
getLatestPathWithConfig(config, flow)->points.push_back(p);
|
||||
lastPosition = p;
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation)
|
||||
{
|
||||
Point p0 = polygon[startIdx];
|
||||
addTravel(p0);
|
||||
for(unsigned int i=1; i<polygon.size(); i++)
|
||||
{
|
||||
Point p1 = polygon[(startIdx + i) % polygon.size()];
|
||||
addExtrusionMove(p1, config);
|
||||
addExtrusionMove(p1, config, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
|
||||
p0 = p1;
|
||||
}
|
||||
if (polygon.size() > 2)
|
||||
addExtrusionMove(polygon[startIdx], config);
|
||||
{
|
||||
Point& p1 = polygon[startIdx];
|
||||
addExtrusionMove(p1, config, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config)
|
||||
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type)
|
||||
{
|
||||
//log("addPolygonsByOptimizer");
|
||||
PathOrderOptimizer orderOptimizer(lastPosition);
|
||||
PathOrderOptimizer orderOptimizer(lastPosition, z_seam_type);
|
||||
for(unsigned int i=0;i<polygons.size();i++)
|
||||
orderOptimizer.addPolygon(polygons[i]);
|
||||
orderOptimizer.optimize();
|
||||
for(unsigned int i=0;i<orderOptimizer.polyOrder.size();i++)
|
||||
{
|
||||
int nr = orderOptimizer.polyOrder[i];
|
||||
addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config);
|
||||
addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config, wall_overlap_computation);
|
||||
}
|
||||
}
|
||||
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config)
|
||||
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, int wipe_dist)
|
||||
{
|
||||
LineOrderOptimizer orderOptimizer(lastPosition);
|
||||
for(unsigned int i=0;i<polygons.size();i++)
|
||||
@@ -134,11 +222,26 @@ void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* conf
|
||||
for(unsigned int i=0;i<orderOptimizer.polyOrder.size();i++)
|
||||
{
|
||||
int nr = orderOptimizer.polyOrder[i];
|
||||
addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config);
|
||||
// addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config);
|
||||
PolygonRef polygon = polygons[nr];
|
||||
int start = orderOptimizer.polyStart[nr];
|
||||
int end = 1 - start;
|
||||
Point& p0 = polygon[start];
|
||||
addTravel(p0);
|
||||
Point& p1 = polygon[end];
|
||||
addExtrusionMove(p1, config);
|
||||
if (wipe_dist != 0)
|
||||
{
|
||||
int line_width = config->getLineWidth();
|
||||
if (vSize2(p1-p0) > line_width * line_width * 4)
|
||||
{ // otherwise line will get optimized by combining multiple into a single extrusion move
|
||||
addExtrusionMove(p1 + normal(p1-p0, wipe_dist), config, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::forceMinimalLayerTime(double minTime, int minimalSpeed, double travelTime, double extrudeTime)
|
||||
void GCodePlanner::forceMinimalLayerTime(double minTime, double minimalSpeed, double travelTime, double extrudeTime)
|
||||
{
|
||||
double totalTime = travelTime + extrudeTime;
|
||||
if (totalTime < minTime && extrudeTime > 0.0)
|
||||
@@ -150,18 +253,18 @@ void GCodePlanner::forceMinimalLayerTime(double minTime, int minimalSpeed, doubl
|
||||
for(unsigned int n=0; n<paths.size(); n++)
|
||||
{
|
||||
GCodePath* path = &paths[n];
|
||||
if (path->config->getExtrusionMM3perMM() == 0)
|
||||
if (path->getExtrusionMM3perMM() == 0)
|
||||
continue;
|
||||
int speed = path->config->getSpeed() * factor;
|
||||
double speed = path->config->getSpeed() * factor;
|
||||
if (speed < minimalSpeed)
|
||||
factor = double(minimalSpeed) / double(path->config->getSpeed());
|
||||
factor = minimalSpeed / path->config->getSpeed();
|
||||
}
|
||||
|
||||
//Only slow down with the minimal time if that will be slower then a factor already set. First layer slowdown also sets the speed factor.
|
||||
if (factor * 100 < getExtrudeSpeedFactor())
|
||||
setExtrudeSpeedFactor(factor * 100);
|
||||
if (factor < getExtrudeSpeedFactor())
|
||||
setExtrudeSpeedFactor(factor);
|
||||
else
|
||||
factor = getExtrudeSpeedFactor() / 100.0;
|
||||
factor = getExtrudeSpeedFactor();
|
||||
|
||||
if (minTime - (extrudeTime / factor) - travelTime > 0.1)
|
||||
{
|
||||
@@ -173,7 +276,7 @@ void GCodePlanner::forceMinimalLayerTime(double minTime, int minimalSpeed, doubl
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::getTimes(double& travelTime, double& extrudeTime)
|
||||
void GCodePlanner::getNaiveTimeEstimates(double& travelTime, double& extrudeTime)
|
||||
{
|
||||
travelTime = 0.0;
|
||||
extrudeTime = 0.0;
|
||||
@@ -183,8 +286,8 @@ void GCodePlanner::getTimes(double& travelTime, double& extrudeTime)
|
||||
GCodePath* path = &paths[n];
|
||||
for(unsigned int i=0; i<path->points.size(); i++)
|
||||
{
|
||||
double thisTime = vSizeMM(p0 - path->points[i]) / double(path->config->getSpeed());
|
||||
if (path->config->getExtrusionMM3perMM() != 0)
|
||||
double thisTime = vSizeMM(p0 - path->points[i]) / path->config->getSpeed();
|
||||
if (path->getExtrusionMM3perMM() != 0)
|
||||
extrudeTime += thisTime;
|
||||
else
|
||||
travelTime += thisTime;
|
||||
@@ -195,104 +298,117 @@ void GCodePlanner::getTimes(double& travelTime, double& extrudeTime)
|
||||
|
||||
void GCodePlanner::writeGCode(bool liftHeadIfNeeded, int layerThickness)
|
||||
{
|
||||
GCodePathConfig* lastConfig = nullptr;
|
||||
GCodePathConfig* last_extrusion_config = nullptr;
|
||||
int extruder = gcode.getExtruderNr();
|
||||
|
||||
for(unsigned int n=0; n<paths.size(); n++)
|
||||
|
||||
for(unsigned int path_idx = 0; path_idx < paths.size(); path_idx++)
|
||||
{
|
||||
GCodePath* path = &paths[n];
|
||||
if (extruder != path->extruder)
|
||||
GCodePath& path = paths[path_idx];
|
||||
if (extruder != path.extruder)
|
||||
{
|
||||
extruder = path->extruder;
|
||||
extruder = path.extruder;
|
||||
gcode.switchExtruder(extruder);
|
||||
}else if (path->retract)
|
||||
}else if (path.retract)
|
||||
{
|
||||
gcode.writeRetraction(path->config->retraction_config);
|
||||
writeRetraction(path_idx);
|
||||
}
|
||||
if (path->config != &travelConfig && lastConfig != path->config)
|
||||
if (path.config != &travelConfig && last_extrusion_config != path.config)
|
||||
{
|
||||
gcode.writeTypeComment(path->config->name);
|
||||
lastConfig = path->config;
|
||||
gcode.writeTypeComment(path.config->name);
|
||||
last_extrusion_config = path.config;
|
||||
}
|
||||
int speed = path->config->getSpeed();
|
||||
double speed = path.config->getSpeed();
|
||||
|
||||
if (path->config->getExtrusionMM3perMM() != 0)// Only apply the extrudeSpeedFactor to extrusion moves
|
||||
speed = speed * extrudeSpeedFactor / 100;
|
||||
if (path.getExtrusionMM3perMM() != 0)// Only apply the extrudeSpeed to extrusion moves
|
||||
speed *= getExtrudeSpeedFactor();
|
||||
else
|
||||
speed = speed * travelSpeedFactor / 100;
|
||||
speed *= getExtrudeSpeedFactor();
|
||||
|
||||
if (path->points.size() == 1 && path->config != &travelConfig && shorterThen(gcode.getPositionXY() - path->points[0], path->config->getLineWidth() * 2))
|
||||
{
|
||||
//Check for lots of small moves and combine them into one large line
|
||||
Point p0 = path->points[0];
|
||||
unsigned int i = n + 1;
|
||||
while(i < paths.size() && paths[i].points.size() == 1 && shorterThen(p0 - paths[i].points[0], path->config->getLineWidth() * 2))
|
||||
{
|
||||
p0 = paths[i].points[0];
|
||||
i ++;
|
||||
}
|
||||
if (paths[i-1].config == &travelConfig)
|
||||
i --;
|
||||
if (i > n + 2)
|
||||
{
|
||||
p0 = gcode.getPositionXY();
|
||||
for(unsigned int x=n; x<i-1; x+=2)
|
||||
{
|
||||
int64_t new_width = vSize(p0 - paths[x].points[0]); // = old_length
|
||||
Point newPoint = (paths[x].points[0] + paths[x+1].points[0]) / 2;
|
||||
int64_t old_width = path->config->getLineWidth();
|
||||
if (old_width > 0)
|
||||
{
|
||||
if (new_width > 0)
|
||||
gcode.writeMove(newPoint, speed * old_width / new_width, path->config->getExtrusionMM3perMM() * new_width / old_width);
|
||||
else
|
||||
gcode.writeMove(newPoint, speed, path->config->getExtrusionMM3perMM());
|
||||
}
|
||||
p0 = paths[x+1].points[0];
|
||||
}
|
||||
gcode.writeMove(paths[i-1].points[0], speed, path->config->getExtrusionMM3perMM());
|
||||
n = i - 1;
|
||||
continue;
|
||||
}
|
||||
int64_t nozzle_size = 400; // TODO allow the machine settings to be passed on everywhere :: depends on which nozzle!
|
||||
|
||||
if (MergeInfillLines(gcode, paths, travelConfig, nozzle_size).mergeInfillLines(speed, path_idx)) // !! has effect on path_idx !!
|
||||
{ // !! has effect on path_idx !!
|
||||
// works when path_idx is the index of the travel move BEFORE the infill lines to be merged
|
||||
continue;
|
||||
}
|
||||
|
||||
bool spiralize = path->config->spiralize;
|
||||
|
||||
if (path.config == &travelConfig)
|
||||
{ // early comp for travel paths, which are handled more simply
|
||||
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
bool spiralize = path.config->spiralize;
|
||||
if (spiralize)
|
||||
{
|
||||
//Check if we are the last spiralize path in the list, if not, do not spiralize.
|
||||
for(unsigned int m=n+1; m<paths.size(); m++)
|
||||
for(unsigned int m=path_idx+1; m<paths.size(); m++)
|
||||
{
|
||||
if (paths[m].config->spiralize)
|
||||
spiralize = false;
|
||||
}
|
||||
}
|
||||
if (spiralize)
|
||||
{
|
||||
if (!spiralize) // normal (extrusion) move (with coasting
|
||||
{
|
||||
CoastingConfig& coasting_config = storage.coasting_config[extruder];
|
||||
bool coasting = coasting_config.coasting_enable;
|
||||
if (coasting)
|
||||
{
|
||||
coasting = writePathWithCoasting(path_idx, layerThickness
|
||||
, coasting_config.coasting_volume_move, coasting_config.coasting_speed_move, coasting_config.coasting_min_volume_move
|
||||
, coasting_config.coasting_volume_retract, coasting_config.coasting_speed_retract, coasting_config.coasting_min_volume_retract);
|
||||
}
|
||||
if (! coasting) // not same as 'else', cause we might have changed coasting in the line above...
|
||||
{ // normal path to gcode algorithm
|
||||
if ( // change |||||| to /\/\/\/\/ ...
|
||||
false &&
|
||||
path_idx + 2 < paths.size() // has a next move
|
||||
&& paths[path_idx+1].points.size() == 1 // is single extruded line
|
||||
&& paths[path_idx+1].config != &travelConfig // next move is extrusion
|
||||
&& paths[path_idx+2].config == &travelConfig // next next move is travel
|
||||
&& shorterThen(path.points.back() - gcode.getPositionXY(), 2 * nozzle_size) // preceding extrusion is close by
|
||||
&& shorterThen(paths[path_idx+1].points.back() - path.points.back(), 2 * nozzle_size) // extrusion move is small
|
||||
&& shorterThen(paths[path_idx+2].points.back() - paths[path_idx+1].points.back(), 2 * nozzle_size) // consecutive extrusion is close by
|
||||
)
|
||||
{
|
||||
gcode.writeMove(paths[path_idx+2].points.back(), speed, paths[path_idx+1].getExtrusionMM3perMM());
|
||||
path_idx += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // SPIRALIZE
|
||||
//If we need to spiralize then raise the head slowly by 1 layer as this path progresses.
|
||||
float totalLength = 0.0;
|
||||
int z = gcode.getPositionZ();
|
||||
Point p0 = gcode.getPositionXY();
|
||||
for(unsigned int i=0; i<path->points.size(); i++)
|
||||
for(unsigned int i=0; i<path.points.size(); i++)
|
||||
{
|
||||
Point p1 = path->points[i];
|
||||
Point p1 = path.points[i];
|
||||
totalLength += vSizeMM(p0 - p1);
|
||||
p0 = p1;
|
||||
}
|
||||
|
||||
float length = 0.0;
|
||||
p0 = gcode.getPositionXY();
|
||||
for(unsigned int i=0; i<path->points.size(); i++)
|
||||
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
Point p1 = path->points[i];
|
||||
Point p1 = path.points[point_idx];
|
||||
length += vSizeMM(p0 - p1);
|
||||
p0 = p1;
|
||||
gcode.setZ(z + layerThickness * length / totalLength);
|
||||
gcode.writeMove(path->points[i], speed, path->config->getExtrusionMM3perMM());
|
||||
}
|
||||
}else{
|
||||
for(unsigned int i=0; i<path->points.size(); i++)
|
||||
{
|
||||
gcode.writeMove(path->points[i], speed, path->config->getExtrusionMM3perMM());
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,8 +417,11 @@ void GCodePlanner::writeGCode(bool liftHeadIfNeeded, int layerThickness)
|
||||
if (liftHeadIfNeeded && extraTime > 0.0)
|
||||
{
|
||||
gcode.writeComment("Small layer, adding delay");
|
||||
if (lastConfig)
|
||||
gcode.writeRetraction(lastConfig->retraction_config, true);
|
||||
if (last_extrusion_config)
|
||||
{
|
||||
bool extruder_switch_retract = false;// TODO: check whether we should do a retractoin_extruderSwitch; is the next path with a different extruder?
|
||||
writeRetraction(extruder_switch_retract, last_extrusion_config->retraction_config);
|
||||
}
|
||||
gcode.setZ(gcode.getPositionZ() + MM2INT(3.0));
|
||||
gcode.writeMove(gcode.getPositionXY(), travelConfig.getSpeed(), 0);
|
||||
gcode.writeMove(gcode.getPositionXY() - Point(-MM2INT(20.0), 0), travelConfig.getSpeed(), 0);
|
||||
@@ -310,4 +429,187 @@ void GCodePlanner::writeGCode(bool liftHeadIfNeeded, int layerThickness)
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::writeRetraction(unsigned int path_idx_travel_after)
|
||||
{
|
||||
if (makeRetractSwitchRetract(path_idx_travel_after))
|
||||
{
|
||||
gcode.writeRetraction_extruderSwitch();
|
||||
}
|
||||
else
|
||||
{
|
||||
RetractionConfig* extrusion_retraction_config = nullptr;
|
||||
for(int extrusion_path_idx = int(path_idx_travel_after) - 1; extrusion_path_idx >= 0; extrusion_path_idx--)
|
||||
{ // backtrack to find the last extrusion path
|
||||
if (paths[extrusion_path_idx].config != &travelConfig)
|
||||
{
|
||||
extrusion_retraction_config = paths[extrusion_path_idx].config->retraction_config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
writeRetraction(false, extrusion_retraction_config);
|
||||
}
|
||||
}
|
||||
void GCodePlanner::writeRetraction(bool extruder_switch_retract, RetractionConfig* retraction_config)
|
||||
{
|
||||
if (extruder_switch_retract)
|
||||
{
|
||||
gcode.writeRetraction_extruderSwitch();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (retraction_config)
|
||||
{
|
||||
gcode.writeRetraction(retraction_config);
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode.writeRetraction(travelConfig.retraction_config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool GCodePlanner::makeRetractSwitchRetract(unsigned int path_idx)
|
||||
{
|
||||
for (unsigned int path_idx2 = path_idx + 1; path_idx2 < paths.size(); path_idx2++)
|
||||
{
|
||||
if (paths[path_idx2].getExtrusionMM3perMM() > 0)
|
||||
{
|
||||
if (paths[path_idx2].extruder != gcode.getExtruderNr())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GCodePlanner::writePathWithCoasting(unsigned int path_idx, int64_t layerThickness, double coasting_volume_move, double coasting_speed_move, double coasting_min_volume_move, double coasting_volume_retract, double coasting_speed_retract, double coasting_min_volume_retract)
|
||||
{
|
||||
GCodePath& path = paths[path_idx];
|
||||
if (path_idx + 1 >= paths.size()
|
||||
||
|
||||
! (path.getExtrusionMM3perMM() > 0.0 && paths[path_idx + 1].config->getExtrusionMM3perMM() == 0.0)
|
||||
||
|
||||
path.points.size() < 2
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
GCodePath& path_next = paths[path_idx + 1];
|
||||
|
||||
if (path_next.retract)
|
||||
{
|
||||
if (coasting_volume_retract <= 0) { return false; }
|
||||
return writePathWithCoasting(path, path_next, layerThickness, coasting_volume_retract, coasting_speed_retract, coasting_min_volume_retract, makeRetractSwitchRetract(path_idx));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (coasting_volume_move <= 0) { return false; }
|
||||
return writePathWithCoasting(path, path_next, layerThickness, coasting_volume_move, coasting_speed_move, coasting_min_volume_move);
|
||||
}
|
||||
}
|
||||
bool GCodePlanner::writePathWithCoasting(GCodePath& path, GCodePath& path_next, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume, bool extruder_switch_retract)
|
||||
{
|
||||
|
||||
int64_t coasting_min_dist_considered = 100; // hardcoded setting for when to not perform coasting
|
||||
|
||||
|
||||
double extrude_speed = path.config->getSpeed() * getExtrudeSpeedFactor(); // travel speed
|
||||
|
||||
int64_t coasting_dist = MM2INT(MM2_2INT(coasting_volume) / layerThickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues
|
||||
int64_t coasting_min_dist = MM2INT(MM2_2INT(coasting_min_volume) / layerThickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues
|
||||
|
||||
|
||||
std::vector<int64_t> accumulated_dist_per_point; // the first accumulated dist is that of the last point! (that of the last point is always zero...)
|
||||
accumulated_dist_per_point.push_back(0);
|
||||
|
||||
int64_t accumulated_dist = 0;
|
||||
|
||||
bool length_is_less_than_min_dist = true;
|
||||
|
||||
unsigned int acc_dist_idx_gt_coast_dist = NO_INDEX; // the index of the first point with accumulated_dist more than coasting_dist (= index into accumulated_dist_per_point)
|
||||
// == the point printed BEFORE the start point for coasting
|
||||
|
||||
|
||||
Point* last = &path.points[path.points.size() - 1];
|
||||
for (unsigned int backward_point_idx = 1; backward_point_idx < path.points.size(); backward_point_idx++)
|
||||
{
|
||||
Point& point = path.points[path.points.size() - 1 - backward_point_idx];
|
||||
int64_t dist = vSize(point - *last);
|
||||
accumulated_dist += dist;
|
||||
accumulated_dist_per_point.push_back(accumulated_dist);
|
||||
|
||||
if (acc_dist_idx_gt_coast_dist == NO_INDEX && accumulated_dist >= coasting_dist)
|
||||
{
|
||||
acc_dist_idx_gt_coast_dist = backward_point_idx; // the newly added point
|
||||
}
|
||||
|
||||
if (accumulated_dist >= coasting_min_dist)
|
||||
{
|
||||
length_is_less_than_min_dist = false;
|
||||
break;
|
||||
}
|
||||
|
||||
last = &point;
|
||||
}
|
||||
|
||||
if (accumulated_dist < coasting_min_dist_considered)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int64_t actual_coasting_dist = coasting_dist;
|
||||
if (length_is_less_than_min_dist)
|
||||
{
|
||||
// in this case accumulated_dist is the length of the whole path
|
||||
actual_coasting_dist = accumulated_dist * coasting_dist / coasting_min_dist;
|
||||
for (acc_dist_idx_gt_coast_dist = 0 ; acc_dist_idx_gt_coast_dist < accumulated_dist_per_point.size() ; acc_dist_idx_gt_coast_dist++)
|
||||
{ // search for the correct coast_dist_idx
|
||||
if (accumulated_dist_per_point[acc_dist_idx_gt_coast_dist] > actual_coasting_dist)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (acc_dist_idx_gt_coast_dist == NO_INDEX)
|
||||
{ // something has gone wrong; coasting_min_dist < coasting_dist ?
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int point_idx_before_start = path.points.size() - 1 - acc_dist_idx_gt_coast_dist;
|
||||
|
||||
Point start;
|
||||
{ // computation of begin point of coasting
|
||||
int64_t residual_dist = actual_coasting_dist - accumulated_dist_per_point[acc_dist_idx_gt_coast_dist - 1];
|
||||
Point& a = path.points[point_idx_before_start];
|
||||
Point& b = path.points[point_idx_before_start + 1];
|
||||
start = b + normal(a-b, residual_dist);
|
||||
}
|
||||
|
||||
{ // write normal extrude path:
|
||||
for(unsigned int point_idx = 0; point_idx <= point_idx_before_start; point_idx++)
|
||||
{
|
||||
gcode.writeMove(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
gcode.writeMove(start, extrude_speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
|
||||
if (path_next.retract)
|
||||
{
|
||||
writeRetraction(extruder_switch_retract, path.config->retraction_config);
|
||||
}
|
||||
|
||||
for (unsigned int point_idx = point_idx_before_start + 1; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
gcode.writeMove(path.points[point_idx], coasting_speed * path.config->getSpeed(), 0);
|
||||
}
|
||||
|
||||
gcode.setLastCoastedAmountMM3(path.getExtrusionMM3perMM() * INT2MM(actual_coasting_dist));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+156
-62
@@ -7,118 +7,212 @@
|
||||
#include "comb.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "wallOverlap.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class SliceDataStorage;
|
||||
|
||||
class GCodePath
|
||||
{
|
||||
public:
|
||||
GCodePathConfig* config;
|
||||
bool retract;
|
||||
int extruder;
|
||||
std::vector<Point> points;
|
||||
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.
|
||||
GCodePathConfig* config; //!< The configuration settings of the path.
|
||||
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.
|
||||
int extruder; //!< The extruder used for this 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.
|
||||
|
||||
double getExtrusionMM3perMM()
|
||||
{
|
||||
return flow * config->getExtrusionMM3perMM();
|
||||
}
|
||||
};
|
||||
|
||||
//The GCodePlanner class stores multiple moves that are planned.
|
||||
// It facilitates the combing to keep the head inside the print.
|
||||
// It also keeps track of the print time estimate for this planning so speed adjustments can be made for the minimal-layer-time.
|
||||
/*!
|
||||
* The GCodePlanner class stores multiple moves that are planned.
|
||||
* It facilitates the combing to keep the head inside the print.
|
||||
* It also keeps track of the print time estimate for this planning so speed adjustments can be made for the minimal-layer-time.
|
||||
*/
|
||||
class GCodePlanner
|
||||
{
|
||||
private:
|
||||
GCodeExport& gcode;
|
||||
SliceDataStorage& storage;
|
||||
|
||||
Point lastPosition;
|
||||
std::vector<GCodePath> paths;
|
||||
|
||||
bool was_combing;
|
||||
bool is_going_to_comb;
|
||||
Comb* comb;
|
||||
|
||||
GCodePathConfig travelConfig;
|
||||
int extrudeSpeedFactor;
|
||||
int travelSpeedFactor;
|
||||
RetractionConfig* last_retraction_config;
|
||||
|
||||
GCodePathConfig travelConfig; //!< The config used for travel moves (only the speed and retraction config are set!)
|
||||
double extrudeSpeedFactor;
|
||||
double travelSpeedFactor; // TODO: remove this unused var?
|
||||
int currentExtruder;
|
||||
int retractionMinimalDistance;
|
||||
bool forceRetraction;
|
||||
bool alwaysRetract;
|
||||
|
||||
double extraTime;
|
||||
double totalPrintTime;
|
||||
|
||||
private:
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config);
|
||||
/*!
|
||||
* Either create a new path with the given config or return the last path if it already had that config.
|
||||
* If GCodePlanner::forceNewPathStart has been called a new path will always be returned.
|
||||
*
|
||||
* \param config The config used for the path returned
|
||||
* \param flow (optional) A ratio for the extrusion speed
|
||||
* \return A path with the given config which is now the last path in GCodePlanner::paths
|
||||
*/
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, float flow = 1.0);
|
||||
|
||||
/*!
|
||||
* Force GCodePlanner::getLatestPathWithConfig to return a new path.
|
||||
*
|
||||
* This function is introduced because in some cases
|
||||
* GCodePlanner::getLatestPathWithConfig is called consecutively with the same config pointer,
|
||||
* though the content of the config has changed.
|
||||
*
|
||||
* Example cases:
|
||||
* - when changing extruder, the same travel config is used, but its extruder field is changed.
|
||||
*/
|
||||
void forceNewPathStart();
|
||||
public:
|
||||
GCodePlanner(GCodeExport& gcode, RetractionConfig* retraction_config, int travelSpeed, int retractionMinimalDistance);
|
||||
/*
|
||||
*
|
||||
* \param travel_avoid_other_parts Whether to avoid other layer parts when travaeling through air.
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
*/
|
||||
GCodePlanner(GCodeExport& gcode, SliceDataStorage& storage, RetractionConfig* retraction_config_travel, double travelSpeed, bool retraction_combing, unsigned int layer_nr, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
~GCodePlanner();
|
||||
|
||||
bool setExtruder(int extruder)
|
||||
{
|
||||
if (extruder == currentExtruder)
|
||||
return false;
|
||||
currentExtruder = extruder;
|
||||
return true;
|
||||
}
|
||||
void setCombing(bool going_to_comb);
|
||||
|
||||
bool setExtruder(int extruder);
|
||||
|
||||
int getExtruder()
|
||||
{
|
||||
return currentExtruder;
|
||||
}
|
||||
|
||||
void setCombBoundary(Polygons* polygons)
|
||||
void setExtrudeSpeedFactor(double speedFactor)
|
||||
{
|
||||
if (comb)
|
||||
delete comb;
|
||||
if (polygons)
|
||||
comb = new Comb(*polygons);
|
||||
else
|
||||
comb = nullptr;
|
||||
}
|
||||
|
||||
void setAlwaysRetract(bool alwaysRetract)
|
||||
{
|
||||
this->alwaysRetract = alwaysRetract;
|
||||
}
|
||||
|
||||
void forceRetract()
|
||||
{
|
||||
forceRetraction = true;
|
||||
}
|
||||
|
||||
void setExtrudeSpeedFactor(int speedFactor)
|
||||
{
|
||||
if (speedFactor < 1) speedFactor = 1;
|
||||
if (speedFactor < 1) speedFactor = 1.0;
|
||||
this->extrudeSpeedFactor = speedFactor;
|
||||
}
|
||||
int getExtrudeSpeedFactor()
|
||||
double getExtrudeSpeedFactor()
|
||||
{
|
||||
return this->extrudeSpeedFactor;
|
||||
}
|
||||
void setTravelSpeedFactor(int speedFactor)
|
||||
void setTravelSpeedFactor(double speedFactor)
|
||||
{
|
||||
if (speedFactor < 1) speedFactor = 1;
|
||||
if (speedFactor < 1) speedFactor = 1.0;
|
||||
this->travelSpeedFactor = speedFactor;
|
||||
}
|
||||
int getTravelSpeedFactor()
|
||||
double getTravelSpeedFactor()
|
||||
{
|
||||
return this->travelSpeedFactor;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Whether the current retracted path is to be an extruder switch retraction.
|
||||
* This function is used to avoid a G10 S1 after a G10.
|
||||
*
|
||||
* \param path_idx The index of the current retracted path
|
||||
* \return Whether the path should be an extgruder switch retracted path
|
||||
*/
|
||||
bool makeRetractSwitchRetract(unsigned int path_idx);
|
||||
|
||||
/*!
|
||||
* Add a travel path to a certain point, retract if needed and when avoiding boundary crossings:
|
||||
* avoiding obstacles and comb along the boundary of parts.
|
||||
*
|
||||
* \param p The point to travel to
|
||||
*/
|
||||
void addTravel(Point p);
|
||||
|
||||
void addExtrusionMove(Point p, GCodePathConfig* config);
|
||||
|
||||
void moveInsideCombBoundary(int distance);
|
||||
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config);
|
||||
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config);
|
||||
|
||||
void addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config);
|
||||
|
||||
void forceMinimalLayerTime(double minTime, int minimalSpeed, double travelTime, double extrusionTime);
|
||||
|
||||
void getTimes(double& travelTime, double& extrudeTime);
|
||||
/*!
|
||||
* Add a travel path to a certain point and retract if needed.
|
||||
*
|
||||
* No combing is performed.
|
||||
*
|
||||
* \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);
|
||||
|
||||
void addExtrusionMove(Point p, GCodePathConfig* config, float flow = 1.0);
|
||||
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr);
|
||||
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST);
|
||||
|
||||
/*!
|
||||
* Add lines to the gcode with optimized order.
|
||||
* \param polygons The lines
|
||||
* \param config The config of the lines
|
||||
* \param wipe_dist (optional) the distance wiped without extruding after laying down a line.
|
||||
*/
|
||||
void addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, int wipe_dist = 0);
|
||||
|
||||
void forceMinimalLayerTime(double minTime, double minimalSpeed, double travelTime, double extrusionTime);
|
||||
|
||||
void getNaiveTimeEstimates(double& travelTime, double& extrudeTime);
|
||||
|
||||
/*!
|
||||
* Writes a path to GCode and performs coasting, or returns false if it did nothing.
|
||||
*
|
||||
* Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines.
|
||||
*
|
||||
* \param path_idx The index into GCodePlanner::paths for the next path to be written to GCode.
|
||||
* \param layerThickness The height of the current layer.
|
||||
* \param coasting_volume_move The volume otherwise leaked during a normal move.
|
||||
* \param coasting_speed_move The speed at which to move during move-coasting.
|
||||
* \param coasting_min_volume_move The minimal volume a path should have which builds up enough pressure to ooze as much as \p coasting_volume_move.
|
||||
* \param coasting_volume_retract The volume otherwise leaked during a retract move.
|
||||
* \param coasting_speed_retract The speed at which to move during retract-coasting.
|
||||
* \param coasting_min_volume_retract The minimal volume a path should have which builds up enough pressure to ooze as much as \p coasting_volume_retract.
|
||||
* \return Whether any GCode has been written for the path.
|
||||
*/
|
||||
bool writePathWithCoasting(unsigned int path_idx, int64_t layerThickness, double coasting_volume_move, double coasting_speed_move, double coasting_min_volume_move, double coasting_volume_retract, double coasting_speed_retract, double coasting_min_volume_retract);
|
||||
|
||||
/*!
|
||||
* Writes a path to GCode and performs coasting, or returns false if it did nothing.
|
||||
*
|
||||
* Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines.
|
||||
*
|
||||
* Paths shorter than \p coasting_min_volume will use less \p coasting_volume linearly.
|
||||
*
|
||||
* \param path The extrusion path to be written to GCode.
|
||||
* \param path_next The next travel path to be written to GCode.
|
||||
* \param layerThickness The height of the current layer.
|
||||
* \param coasting_volume The volume otherwise leaked.
|
||||
* \param coasting_speed The speed at which to move during coasting.
|
||||
* \param coasting_min_volume The minimal volume a path should have which builds up enough pressure to ooze as much as \p coasting_volume.
|
||||
* \param extruder_switch_retract (optional) For a coasted path followed by a retraction: whether to retract normally, or do an extruder switch retraction.
|
||||
* \return Whether any GCode has been written for the path.
|
||||
*/
|
||||
bool writePathWithCoasting(GCodePath& path, GCodePath& path_next, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume, bool extruder_switch_retract = false);
|
||||
|
||||
/*!
|
||||
* Write a retraction: either an extruder switch retraction or a normal retraction based on the last extrusion paths retraction config.
|
||||
* \param path_idx_travel_after Index in GCodePlanner::paths to the travel move before which to do the retraction
|
||||
*/
|
||||
void writeRetraction(unsigned int path_idx_travel_after);
|
||||
|
||||
/*!
|
||||
* Write a retraction: either an extruder switch retraction or a normal retraction based on the given retraction config.
|
||||
* \param extruder_switch_retract Whether to write an extruder switch retract
|
||||
* \param retraction_config The config used.
|
||||
*/
|
||||
void writeRetraction(bool extruder_switch_retract, RetractionConfig* retraction_config);
|
||||
|
||||
void writeGCode(bool liftHeadIfNeeded, int layerThickness);
|
||||
void moveInsideCombBoundary(int arg1);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+60
-10
@@ -2,8 +2,57 @@
|
||||
#include "infill.h"
|
||||
#include "functional"
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "utils/AABB.h"
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
void Infill::generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between)
|
||||
{
|
||||
if (in_outline.size() == 0) return;
|
||||
if (line_distance == 0) return;
|
||||
const Polygons* outline = &in_outline;
|
||||
Polygons outline_offsetted;
|
||||
switch(pattern)
|
||||
{
|
||||
case EFillMethod::GRID:
|
||||
generateGridInfill(in_outline, outlineOffset, result_lines, extrusion_width, line_distance * 2, infill_overlap, fill_angle);
|
||||
break;
|
||||
case EFillMethod::LINES:
|
||||
generateLineInfill(in_outline, outlineOffset, result_lines, extrusion_width, line_distance, infill_overlap, fill_angle);
|
||||
break;
|
||||
case EFillMethod::TRIANGLES:
|
||||
generateTriangleInfill(in_outline, outlineOffset, result_lines, extrusion_width, line_distance * 3, infill_overlap, fill_angle);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC:
|
||||
if (outlineOffset != 0)
|
||||
{
|
||||
PolygonUtils::offsetSafe(in_outline, outlineOffset, extrusion_width, outline_offsetted, avoidOverlappingPerimeters);
|
||||
outline = &outline_offsetted;
|
||||
}
|
||||
if (abs(extrusion_width - line_distance) < 10)
|
||||
{
|
||||
generateConcentricInfillDense(*outline, result_polygons, in_between, extrusion_width, avoidOverlappingPerimeters);
|
||||
}
|
||||
else
|
||||
{
|
||||
generateConcentricInfill(*outline, result_polygons, line_distance);
|
||||
}
|
||||
break;
|
||||
case EFillMethod::ZIG_ZAG:
|
||||
if (outlineOffset != 0)
|
||||
{
|
||||
PolygonUtils::offsetSafe(in_outline, outlineOffset, extrusion_width, outline_offsetted, avoidOverlappingPerimeters);
|
||||
outline = &outline_offsetted;
|
||||
}
|
||||
generateZigZagInfill(*outline, result_lines, extrusion_width, line_distance, infill_overlap, fill_angle, connect_zigzags, use_endPieces);
|
||||
break;
|
||||
default:
|
||||
logError("Fill pattern has unknown value.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, int extrusionWidth, bool avoidOverlappingPerimeters)
|
||||
@@ -16,7 +65,7 @@ void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons*
|
||||
result.add(r);
|
||||
}
|
||||
Polygons next_outline;
|
||||
offsetExtrusionWidth(outline, true, extrusionWidth, next_outline, in_between, avoidOverlappingPerimeters);
|
||||
PolygonUtils::offsetExtrusionWidth(outline, true, extrusionWidth, next_outline, in_between, avoidOverlappingPerimeters);
|
||||
outline = next_outline;
|
||||
}
|
||||
|
||||
@@ -37,7 +86,7 @@ void generateConcentricInfill(Polygons outline, Polygons& result, int inset_valu
|
||||
|
||||
|
||||
void generateGridInfill(const Polygons& in_outline, int outlineOffset, Polygons& result,
|
||||
int extrusionWidth, int lineSpacing, int infillOverlap,
|
||||
int extrusionWidth, int lineSpacing, double infillOverlap,
|
||||
double rotation)
|
||||
{
|
||||
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
|
||||
@@ -47,7 +96,7 @@ void generateGridInfill(const Polygons& in_outline, int outlineOffset, Polygons&
|
||||
}
|
||||
|
||||
void generateTriangleInfill(const Polygons& in_outline, int outlineOffset, Polygons& result,
|
||||
int extrusionWidth, int lineSpacing, int infillOverlap,
|
||||
int extrusionWidth, int lineSpacing, double infillOverlap,
|
||||
double rotation)
|
||||
{
|
||||
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
|
||||
@@ -107,10 +156,11 @@ void addLineInfill(Polygons& result, PointMatrix matrix, int scanline_min_idx, i
|
||||
* and connect them using the even-odd rule
|
||||
*
|
||||
*/
|
||||
void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation)
|
||||
void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation)
|
||||
{
|
||||
if (lineSpacing == 0) return;
|
||||
if (in_outline.size() == 0) return;
|
||||
Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 + outlineOffset);
|
||||
Polygons outline = ((outlineOffset)? in_outline.offset(outlineOffset) : in_outline).offset(extrusionWidth * infillOverlap / 100);
|
||||
if (outline.size() == 0) return;
|
||||
|
||||
PointMatrix matrix(rotation);
|
||||
@@ -164,10 +214,10 @@ void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons&
|
||||
}
|
||||
|
||||
|
||||
void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, bool connect_zigzags, bool use_endPieces)
|
||||
void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags, bool use_endPieces)
|
||||
{
|
||||
if (use_endPieces) return generateZigZagInfill_endPieces(in_outline, result, extrusionWidth, lineSpacing, infillOverlap, rotation, connect_zigzags);
|
||||
else return generateZigZagInfill_noEndPieces(in_outline, result, extrusionWidth, lineSpacing, infillOverlap, rotation);
|
||||
if (use_endPieces) return generateZigZagIninfill_endPieces(in_outline, result, extrusionWidth, lineSpacing, infillOverlap, rotation, connect_zigzags);
|
||||
else return generateZigZagIninfill_noEndPieces(in_outline, result, extrusionWidth, lineSpacing, infillOverlap, rotation);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -219,7 +269,7 @@ void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extr
|
||||
* ^ ^ ^ scanlines
|
||||
* ^ disconnected end piece
|
||||
*/
|
||||
void generateZigZagInfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, bool connect_zigzags)
|
||||
void generateZigZagIninfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags)
|
||||
{
|
||||
// if (in_outline.size() == 0) return;
|
||||
// Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2);
|
||||
@@ -353,7 +403,7 @@ void generateZigZagInfill_endPieces(const Polygons& in_outline, Polygons& result
|
||||
}
|
||||
|
||||
|
||||
void generateZigZagInfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation)
|
||||
void generateZigZagIninfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation)
|
||||
{
|
||||
if (in_outline.size() == 0) return;
|
||||
Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2);
|
||||
|
||||
+38
-6
@@ -3,17 +3,49 @@
|
||||
#define INFILL_H
|
||||
|
||||
#include "utils/polygon.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
class Infill
|
||||
{
|
||||
EFillMethod pattern;
|
||||
const Polygons& in_outline;
|
||||
int outlineOffset;
|
||||
bool avoidOverlappingPerimeters;
|
||||
int extrusion_width;
|
||||
int line_distance;
|
||||
double infill_overlap;
|
||||
double fill_angle;
|
||||
bool connect_zigzags;
|
||||
bool use_endPieces;
|
||||
|
||||
public:
|
||||
Infill(EFillMethod pattern, const Polygons& in_outline, int outlineOffset, bool avoidOverlappingPerimeters, int extrusion_width, int line_distance, double infill_overlap, double fill_angle, bool connect_zigzags, bool use_endPieces)
|
||||
: pattern(pattern)
|
||||
, in_outline(in_outline)
|
||||
, outlineOffset(outlineOffset)
|
||||
, avoidOverlappingPerimeters(avoidOverlappingPerimeters)
|
||||
, extrusion_width(extrusion_width)
|
||||
, line_distance(line_distance)
|
||||
, infill_overlap(infill_overlap)
|
||||
, fill_angle(fill_angle)
|
||||
, connect_zigzags(connect_zigzags)
|
||||
, use_endPieces(use_endPieces)
|
||||
{
|
||||
}
|
||||
void generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between);
|
||||
};
|
||||
|
||||
void generateInfill(EFillMethod pattern, const Polygons& in_outline, int outlineOffset, Polygons& result_polygons, Polygons& result_lines, Polygons* in_between, bool avoidOverlappingPerimeters, int extrusion_width, int line_distance, double infill_overlap, double fill_angle, bool connect_zigzags, bool use_endPieces);
|
||||
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value);
|
||||
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, int extrusionWidth, bool avoidOverlappingPerimeters);
|
||||
void generateGridInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation);
|
||||
void generateTriangleInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation);
|
||||
void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation);
|
||||
void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, bool connect_zigzags, bool use_endPieces);
|
||||
void generateZigZagInfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, bool connect_zigzags);
|
||||
void generateZigZagInfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation);
|
||||
void generateGridInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation);
|
||||
void generateTriangleInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation);
|
||||
void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation);
|
||||
void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags, bool use_endPieces);
|
||||
void generateZigZagIninfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags);
|
||||
void generateZigZagIninfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation);
|
||||
}//namespace cura
|
||||
|
||||
#endif//INFILL_H
|
||||
|
||||
+26
-12
@@ -1,13 +1,10 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "inset.h"
|
||||
#include "polygonOptimizer.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
namespace cura {
|
||||
|
||||
void generateInsets(SliceLayerPart* part, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters)
|
||||
void generateInsets(SliceLayerPart* part, int nozzle_width, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
int combBoundaryInset = line_width_x/2; // hard coded value
|
||||
part->combBoundery = part->outline.offset(-combBoundaryInset);
|
||||
if (insetCount == 0)
|
||||
{
|
||||
part->insets.push_back(part->outline);
|
||||
@@ -19,16 +16,34 @@ void generateInsets(SliceLayerPart* part, int line_width_0, int line_width_x, in
|
||||
part->insets.push_back(Polygons());
|
||||
if (i == 0)
|
||||
{
|
||||
offsetSafe(part->outline, - line_width_x/2, line_width_x, part->insets[i], avoidOverlappingPerimeters);
|
||||
if (line_width_0 < nozzle_width)
|
||||
{
|
||||
PolygonUtils::offsetSafe(part->outline, - nozzle_width/2, line_width_0, part->insets[0], avoidOverlappingPerimeters_0);
|
||||
}
|
||||
else
|
||||
{
|
||||
PolygonUtils::offsetSafe(part->outline, - line_width_0/2, line_width_0, part->insets[0], avoidOverlappingPerimeters_0);
|
||||
}
|
||||
} else if (i == 1)
|
||||
{
|
||||
offsetExtrusionWidth(part->insets[i-1], true, line_width_0, part->insets[i], &part->perimeterGaps, avoidOverlappingPerimeters);
|
||||
if (line_width_0 < nozzle_width)
|
||||
{
|
||||
int offset_from_first_boundary_for_edge_of_outer_wall = -nozzle_width/2;
|
||||
// ideally this /\ should be: nozzle_width/2 - line_width_0; however, factually, the nozzle will fill up part of the perimeter gaps
|
||||
PolygonUtils::offsetSafe(part->insets[0], nozzle_width/2 - line_width_0 - line_width_x/2, offset_from_first_boundary_for_edge_of_outer_wall, line_width_x, part->insets[1], &part->perimeterGaps, avoidOverlappingPerimeters);
|
||||
}
|
||||
else
|
||||
{
|
||||
PolygonUtils::offsetSafe(part->insets[0], -line_width_0/2 - line_width_x/2, -line_width_0/2, line_width_x, part->insets[1], &part->perimeterGaps, avoidOverlappingPerimeters);
|
||||
}
|
||||
} else
|
||||
{
|
||||
offsetExtrusionWidth(part->insets[i-1], true, line_width_x, part->insets[i], &part->perimeterGaps, avoidOverlappingPerimeters);
|
||||
PolygonUtils::offsetExtrusionWidth(part->insets[i-1], true, line_width_x, part->insets[i], &part->perimeterGaps, avoidOverlappingPerimeters);
|
||||
}
|
||||
|
||||
optimizePolygons(part->insets[i]);
|
||||
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
part->insets[i].simplify();
|
||||
if (part->insets[i].size() < 1)
|
||||
{
|
||||
part->insets.pop_back();
|
||||
@@ -37,12 +52,11 @@ void generateInsets(SliceLayerPart* part, int line_width_0, int line_width_x, in
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void generateInsets(SliceLayer* layer, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters)
|
||||
void generateInsets(SliceLayer* layer, int nozzle_width, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
for(unsigned int partNr = 0; partNr < layer->parts.size(); partNr++)
|
||||
{
|
||||
generateInsets(&layer->parts[partNr], line_width_0, line_width_x, insetCount, avoidOverlappingPerimeters);
|
||||
generateInsets(&layer->parts[partNr], nozzle_width, line_width_0, line_width_x, insetCount, avoidOverlappingPerimeters_0, avoidOverlappingPerimeters);
|
||||
}
|
||||
|
||||
//Remove the parts which did not generate an inset. As these parts are too small to print,
|
||||
|
||||
+13
-6
@@ -11,23 +11,30 @@ namespace cura
|
||||
* Generates the insets / perimeters for a single layer part.
|
||||
*
|
||||
* \param part The part for which to generate the insets.
|
||||
* \param line_width_0 Line width of the outer wall
|
||||
* \param line_width_x Line width of other walls
|
||||
* \param nozzle_width The diameter of the hole in the nozzle
|
||||
* \param line_width_0 line width of the outer wall
|
||||
* \param line_width_x line width of other walls
|
||||
* \param insetCount The number of insets to to generate
|
||||
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
|
||||
*/
|
||||
void generateInsets(SliceLayerPart* part, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters);
|
||||
void generateInsets(SliceLayerPart* part, int nozzle_width, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
|
||||
/*!
|
||||
* Generates the insets / perimeters for all parts in a layer.
|
||||
*
|
||||
* Note that the second inset gets offsetted by \p line_width_0 instead of the first,
|
||||
* which leads to better results for a smaller \p line_width_0 than \p line_width_x and when printing the outer wall last.
|
||||
*
|
||||
* \param layer The layer for which to generate the insets.
|
||||
* \param line_width_0 Line width of the outer wall
|
||||
* \param line_width_x Line width of other walls
|
||||
* \param nozzle_width The diameter of the hole in the nozzle
|
||||
* \param line_width_0 line width of the outer wall
|
||||
* \param line_width_x line width of other walls
|
||||
* \param insetCount The number of insets to to generate
|
||||
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
|
||||
*/
|
||||
void generateInsets(SliceLayer* layer, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters);
|
||||
void generateInsets(SliceLayer* layer, int nozzle_width, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
+32
-24
@@ -1,8 +1,10 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include <stdio.h>
|
||||
|
||||
#include "layerPart.h"
|
||||
#include "settings.h"
|
||||
#include "Progress.h"
|
||||
|
||||
#include "utils/SVG.h" // debug output
|
||||
|
||||
/*
|
||||
The layer-part creation step is the first step in creating actual useful data for 3D printing.
|
||||
@@ -11,7 +13,7 @@ each of these groups is called a "part", which sometimes are also known as "isla
|
||||
isolated areas in the 2D layer with possible holes.
|
||||
|
||||
Creating "parts" is an important step, as all elements in a single part should be printed before going to another part.
|
||||
And all every bit inside a single part can be printed without the nozzle leaving the boundery of this part.
|
||||
And all every bit inside a single part can be printed without the nozzle leaving the boundary of this part.
|
||||
|
||||
It's also the first step that stores the result in the "data storage" so all other steps can access it.
|
||||
*/
|
||||
@@ -20,7 +22,7 @@ namespace cura {
|
||||
|
||||
void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool union_layers, bool union_all_remove_holes)
|
||||
{
|
||||
storageLayer.openLines = layer->openPolygons;
|
||||
storageLayer.openPolyLines = layer->openPolylines;
|
||||
|
||||
if (union_all_remove_holes)
|
||||
{
|
||||
@@ -31,16 +33,15 @@ void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool uni
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Polygons> result;
|
||||
std::vector<PolygonsPart> result;
|
||||
result = layer->polygonList.splitIntoParts(union_layers || union_all_remove_holes);
|
||||
for(unsigned int i=0; i<result.size(); i++)
|
||||
{
|
||||
storageLayer.parts.push_back(SliceLayerPart());
|
||||
storageLayer.parts.emplace_back();
|
||||
storageLayer.parts[i].outline = result[i];
|
||||
storageLayer.parts[i].boundaryBox.calculate(storageLayer.parts[i].outline);
|
||||
}
|
||||
}
|
||||
|
||||
void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_layers, bool union_all_remove_holes)
|
||||
{
|
||||
for(unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
@@ -49,41 +50,48 @@ void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_laye
|
||||
storage.layers[layer_nr].sliceZ = slicer->layers[layer_nr].z;
|
||||
storage.layers[layer_nr].printZ = slicer->layers[layer_nr].z;
|
||||
createLayerWithParts(storage.layers[layer_nr], &slicer->layers[layer_nr], union_layers, union_all_remove_holes);
|
||||
|
||||
logProgress("layerparts", layer_nr + 1, slicer->layers.size());
|
||||
}
|
||||
}
|
||||
|
||||
void dumpLayerparts(SliceDataStorage& storage, const char* filename)
|
||||
void layerparts2HTML(SliceDataStorage& storage, const char* filename, bool all_layers, int layer_nr)
|
||||
{
|
||||
|
||||
FILE* out = fopen(filename, "w");
|
||||
fprintf(out, "<!DOCTYPE html><html><body>");
|
||||
Point3 modelSize = storage.model_size;
|
||||
Point3 modelMin = storage.model_min;
|
||||
|
||||
Point model_min_2d = Point(modelMin.x, modelMin.y);
|
||||
Point model_max_2d = Point(modelSize.x, modelSize.y) + model_min_2d;
|
||||
AABB aabb(model_min_2d, model_max_2d);
|
||||
|
||||
SVG svg(filename, aabb);
|
||||
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
for(SliceLayer& layer : mesh.layers)
|
||||
for(unsigned int layer_idx = 0; layer_idx < mesh.layers.size(); layer_idx++)
|
||||
{
|
||||
fprintf(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width: 500px; height:500px\">\n");
|
||||
if (!(all_layers || int(layer_idx) == layer_nr)) { continue; }
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
// fprintf(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width: 500px; height:500px\">\n");
|
||||
for(SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
for(unsigned int j=0;j<part.outline.size();j++)
|
||||
{
|
||||
fprintf(out, "<polygon points=\"");
|
||||
for(unsigned int k=0;k<part.outline[j].size();k++)
|
||||
fprintf(out, "%f,%f ", float(part.outline[j][k].X - modelMin.x)/modelSize.x*500, float(part.outline[j][k].Y - modelMin.y)/modelSize.y*500);
|
||||
if (j == 0)
|
||||
fprintf(out, "\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n");
|
||||
else
|
||||
fprintf(out, "\" style=\"fill:red; stroke:black;stroke-width:1\" />\n");
|
||||
}
|
||||
svg.writeAreas(part.outline);
|
||||
svg.writePoints(part.outline);
|
||||
// for(unsigned int j=0;j<part.outline.size();j++)
|
||||
// {
|
||||
// fprintf(out, "<polygon points=\"");
|
||||
// for(unsigned int k=0;k<part.outline[j].size();k++)
|
||||
// fprintf(out, "%f,%f ", float(part.outline[j][k].X - modelMin.x)/modelSize.x*500, float(part.outline[j][k].Y - modelMin.y)/modelSize.y*500);
|
||||
// if (j == 0)
|
||||
// fprintf(out, "\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n");
|
||||
// else
|
||||
// fprintf(out, "\" style=\"fill:red; stroke:black;stroke-width:1\" />\n");
|
||||
// }
|
||||
}
|
||||
fprintf(out, "</svg>\n");
|
||||
// fprintf(out, "</svg>\n");
|
||||
}
|
||||
}
|
||||
fprintf(out, "</body></html>");
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+3
-2
@@ -4,6 +4,7 @@
|
||||
|
||||
#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.
|
||||
@@ -12,7 +13,7 @@ each of these groups is called a "part", which sometimes are also known as "isla
|
||||
isolated areas in the 2D layer with possible holes.
|
||||
|
||||
Creating "parts" is an important step, as all elements in a single part should be printed before going to another part.
|
||||
And all every bit inside a single part can be printed without the nozzle leaving the boundery of this part.
|
||||
And all every bit inside a single part can be printed without the nozzle leaving the boundary of this part.
|
||||
|
||||
It's also the first step that stores the result in the "data storage" so all other steps can access it.
|
||||
*/
|
||||
@@ -23,7 +24,7 @@ void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool uni
|
||||
|
||||
void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_layers, bool union_all_remove_holes);
|
||||
|
||||
void dumpLayerparts(SliceDataStorage& storage, const char* filename);
|
||||
void layerparts2HTML(SliceDataStorage& storage, const char* filename, bool all_layers = true, int layer_nr = -1);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
+257
-138
@@ -14,30 +14,38 @@
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/string.h"
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
#include "modelFile/modelFile.h"
|
||||
#include "settings.h"
|
||||
#include "FffProcessor.h"
|
||||
#include "settingRegistry.h"
|
||||
#include "multiVolumes.h"
|
||||
#include "polygonOptimizer.h"
|
||||
#include "slicer.h"
|
||||
#include "layerPart.h"
|
||||
#include "inset.h"
|
||||
#include "skin.h"
|
||||
#include "infill.h"
|
||||
#include "bridge.h"
|
||||
#include "support.h"
|
||||
#include "pathOrderOptimizer.h"
|
||||
#include "skirt.h"
|
||||
#include "raft.h"
|
||||
#include "comb.h"
|
||||
#include "gcodeExport.h"
|
||||
#include "fffProcessor.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void print_usage()
|
||||
{
|
||||
cura::logError("usage: CuraEngine [-h] [-v] [-m 3x3matrix] [-c <config file>] [-s <settingkey>=<value>] -o <output.gcode> <model.stl>\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("usage:\n");
|
||||
cura::logError("CuraEngine help\n");
|
||||
cura::logError("\tShow this help message\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("CuraEngine connect <host>[:<port>] [-j <settings.json>]\n");
|
||||
cura::logError(" --connect <host>[:<port>]\n\tConnect to <host> via a command socket, \n\tinstead of passing information via the command line\n");
|
||||
cura::logError(" -j\n\tLoad settings.json file to register all settings and their defaults\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("CuraEngine slice [-v] [-p] [-j <settings.json>] [-s <settingkey>=<value>] [-g] [-e] [-o <output.gcode>] [-l <model.stl>] [--next]\n");
|
||||
cura::logError(" -v\n\tIncrease the verbose level (show log messages).\n");
|
||||
cura::logError(" -p\n\tLog progress information.\n");
|
||||
cura::logError(" -j\n\tLoad settings.json file to register all settings and their defaults.\n");
|
||||
cura::logError(" -s <setting>=<value>\n\tSet a setting to a value for the last supplied object, \n\textruder train, or general settings.\n");
|
||||
cura::logError(" -l <model_file>\n\tLoad an STL model. \n");
|
||||
cura::logError(" -g\n\tSwitch setting focus to the current mesh group only.\n\tUsed for one-at-a-time printing.\n");
|
||||
cura::logError(" -e\n\tAdd a new extruder train.\n");
|
||||
cura::logError(" --next\n\tGenerate gcode for the previously supplied mesh group and append that to \n\tthe gcode of further models for one-at-a-time printing.\n");
|
||||
cura::logError(" -o <output_file>\n\tSpecify a file to which to write the generated gcode.\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("The settings are appended to the last supplied object:\n");
|
||||
cura::logError("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e [extruder train settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object settings] \n\t--next [next group settings]\n\t... etc.\n");
|
||||
cura::logError("\n");
|
||||
}
|
||||
|
||||
//Signal handler for a "floating point exception", which can also be integer division by zero errors.
|
||||
@@ -48,6 +56,218 @@ void signal_FPE(int n)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void print_call(int argc, char **argv)
|
||||
{
|
||||
cura::logError("Command called:\n");
|
||||
for (int idx= 0; idx < argc; idx++)
|
||||
cura::logError("%s ", argv[idx]);
|
||||
cura::logError("\n");
|
||||
}
|
||||
|
||||
void connect(int argc, char **argv)
|
||||
{
|
||||
CommandSocket* commandSocket = new CommandSocket();
|
||||
std::string ip;
|
||||
int port = 49674;
|
||||
|
||||
std::string ip_port(argv[2]);
|
||||
if (ip_port.find(':') != std::string::npos)
|
||||
{
|
||||
ip = ip_port.substr(0, ip_port.find(':'));
|
||||
port = std::stoi(ip_port.substr(ip_port.find(':') + 1).data());
|
||||
}
|
||||
|
||||
|
||||
for(int argn = 3; argn < argc; argn++)
|
||||
{
|
||||
char* str = argv[argn];
|
||||
if (str[0] == '-')
|
||||
{
|
||||
for(str++; *str; str++)
|
||||
{
|
||||
switch(*str)
|
||||
{
|
||||
case 'v':
|
||||
cura::increaseVerboseLevel();
|
||||
break;
|
||||
case 'j':
|
||||
argn++;
|
||||
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn]))
|
||||
{
|
||||
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
cura::logError("Unknown option: %c\n", *str);
|
||||
print_call(argc, argv);
|
||||
print_usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commandSocket->connect(ip, port);
|
||||
}
|
||||
|
||||
void slice(int argc, char **argv)
|
||||
{
|
||||
FffProcessor::getInstance()->time_keeper.restart();
|
||||
|
||||
FMatrix3x3 transformation; // the transformation applied to a model when loaded
|
||||
|
||||
MeshGroup meshgroup(FffProcessor::getInstance());
|
||||
|
||||
int extruder_train_nr = 0;
|
||||
|
||||
SettingsBase* last_extruder_train = meshgroup.getExtruderTrain(0);
|
||||
SettingsBase* last_settings_object = FffProcessor::getInstance();
|
||||
for(int argn = 2; argn < argc; argn++)
|
||||
{
|
||||
char* str = argv[argn];
|
||||
if (str[0] == '-')
|
||||
{
|
||||
if (str[1] == '-')
|
||||
{
|
||||
if (stringcasecompare(str, "--next") == 0)
|
||||
{
|
||||
try {
|
||||
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
|
||||
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
|
||||
meshgroup.finalize();
|
||||
log("Loaded from disk in %5.3fs\n", FffProcessor::getInstance()->time_keeper.restart());
|
||||
|
||||
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
meshgroup.getExtruderTrain(extruder_nr)->setExtruderTrainDefaults(extruder_nr); // also initializes yet uninitialized extruder trains
|
||||
}
|
||||
//start slicing
|
||||
FffProcessor::getInstance()->processMeshGroup(&meshgroup);
|
||||
|
||||
// initialize loading of new meshes
|
||||
FffProcessor::getInstance()->time_keeper.restart();
|
||||
meshgroup = MeshGroup(FffProcessor::getInstance());
|
||||
last_settings_object = &meshgroup;
|
||||
}catch(...){
|
||||
cura::logError("Unknown exception\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
}else{
|
||||
cura::logError("Unknown option: %s\n", str);
|
||||
}
|
||||
}else{
|
||||
for(str++; *str; str++)
|
||||
{
|
||||
switch(*str)
|
||||
{
|
||||
case 'v':
|
||||
cura::increaseVerboseLevel();
|
||||
break;
|
||||
case 'p':
|
||||
cura::enableProgressLogging();
|
||||
break;
|
||||
case 'j':
|
||||
argn++;
|
||||
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn]))
|
||||
{
|
||||
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
str++;
|
||||
extruder_train_nr = int(*str - '0'); // TODO: parse int instead (now "-e10"="-e:" , "-e11"="-e;" , "-e12"="-e<" .. etc)
|
||||
last_settings_object = meshgroup.getExtruderTrain(extruder_train_nr);
|
||||
last_extruder_train = meshgroup.getExtruderTrain(extruder_train_nr);
|
||||
break;
|
||||
case 'l':
|
||||
argn++;
|
||||
|
||||
log("Loading %s from disk...\n", argv[argn]);
|
||||
// transformation = // TODO: get a transformation from somewhere
|
||||
|
||||
if (!loadMeshIntoMeshGroup(&meshgroup, argv[argn], transformation, last_extruder_train))
|
||||
{
|
||||
logError("Failed to load model: %s\n", argv[argn]);
|
||||
}
|
||||
else
|
||||
{
|
||||
last_settings_object = &(meshgroup.meshes.back()); // pointer is valid until a new object is added, so this is OK
|
||||
}
|
||||
break;
|
||||
case 'o':
|
||||
argn++;
|
||||
if (!FffProcessor::getInstance()->setTargetFile(argv[argn]))
|
||||
{
|
||||
cura::logError("Failed to open %s for output.\n", argv[argn]);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
last_settings_object = &meshgroup;
|
||||
case 's':
|
||||
{
|
||||
//Parse the given setting and store it.
|
||||
argn++;
|
||||
char* valuePtr = strchr(argv[argn], '=');
|
||||
if (valuePtr)
|
||||
{
|
||||
*valuePtr++ = '\0';
|
||||
|
||||
last_settings_object->setSetting(argv[argn], valuePtr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
cura::logError("Unknown option: %c\n", *str);
|
||||
print_call(argc, argv);
|
||||
print_usage();
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
cura::logError("Unknown option: %s\n", argv[argn]);
|
||||
print_call(argc, argv);
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (extruder_train_nr = 0; extruder_train_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_train_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
meshgroup.getExtruderTrain(extruder_train_nr)->setExtruderTrainDefaults(extruder_train_nr); // also initializes yet uninitialized extruder trains
|
||||
}
|
||||
|
||||
|
||||
#ifndef DEBUG
|
||||
try {
|
||||
#endif
|
||||
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
|
||||
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
|
||||
meshgroup.finalize();
|
||||
log("Loaded from disk in %5.3fs\n", FffProcessor::getInstance()->time_keeper.restart());
|
||||
|
||||
//start slicing
|
||||
FffProcessor::getInstance()->processMeshGroup(&meshgroup);
|
||||
|
||||
#ifndef DEBUG
|
||||
}catch(...){
|
||||
cura::logError("Unknown exception\n");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
//Finalize the processor, this adds the end.gcode. And reports statistics.
|
||||
FffProcessor::getInstance()->finalize();
|
||||
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
using namespace cura;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -62,9 +282,10 @@ int main(int argc, char **argv)
|
||||
signal(SIGFPE, signal_FPE);
|
||||
#endif
|
||||
|
||||
fffProcessor processor;
|
||||
std::vector<std::string> files;
|
||||
|
||||
Progress::init();
|
||||
|
||||
|
||||
logCopyright("\n");
|
||||
logCopyright("Cura_SteamEngine version %s\n", VERSION);
|
||||
logCopyright("Copyright (C) 2014 David Braam\n");
|
||||
logCopyright("\n");
|
||||
@@ -81,130 +302,28 @@ int main(int argc, char **argv)
|
||||
logCopyright("You should have received a copy of the GNU Affero General Public License\n");
|
||||
logCopyright("along with this program. If not, see <http://www.gnu.org/licenses/>.\n");
|
||||
|
||||
CommandSocket* commandSocket = NULL;
|
||||
std::string ip;
|
||||
int port = 49674;
|
||||
|
||||
for(int argn = 1; argn < argc; argn++)
|
||||
if (argc < 2)
|
||||
{
|
||||
char* str = argv[argn];
|
||||
if (str[0] == '-')
|
||||
{
|
||||
if (str[1] == '-')
|
||||
{
|
||||
if (stringcasecompare(str, "--connect") == 0)
|
||||
{
|
||||
commandSocket = new CommandSocket(&processor);
|
||||
|
||||
std::string ip_port(argv[argn + 1]);
|
||||
if (ip_port.find(':') != std::string::npos)
|
||||
{
|
||||
ip = ip_port.substr(0, ip_port.find(':'));
|
||||
port = std::stoi(ip_port.substr(ip_port.find(':') + 1).data());
|
||||
}
|
||||
|
||||
argn += 1;
|
||||
}
|
||||
else if (stringcasecompare(str, "--") == 0)
|
||||
{
|
||||
try {
|
||||
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
|
||||
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
|
||||
if (files.size() > 0)
|
||||
processor.processFiles(files);
|
||||
files.clear();
|
||||
}catch(...){
|
||||
cura::logError("Unknown exception\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
}else{
|
||||
cura::logError("Unknown option: %s\n", str);
|
||||
}
|
||||
}else{
|
||||
for(str++; *str; str++)
|
||||
{
|
||||
switch(*str)
|
||||
{
|
||||
case 'h':
|
||||
print_usage();
|
||||
exit(1);
|
||||
case 'v':
|
||||
cura::increaseVerboseLevel();
|
||||
break;
|
||||
case 'j':
|
||||
argn++;
|
||||
if (!SettingRegistry::getInstance()->loadJSON(argv[argn]))
|
||||
{
|
||||
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
cura::enableProgressLogging();
|
||||
break;
|
||||
case 'o':
|
||||
argn++;
|
||||
if (!processor.setTargetFile(argv[argn]))
|
||||
{
|
||||
cura::logError("Failed to open %s for output.\n", argv[argn]);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
{
|
||||
//Parse the given setting and store it.
|
||||
argn++;
|
||||
char* valuePtr = strchr(argv[argn], '=');
|
||||
if (valuePtr)
|
||||
{
|
||||
*valuePtr++ = '\0';
|
||||
|
||||
processor.setSetting(argv[argn], valuePtr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
cura::logError("Unknown option: %c\n", *str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
files.push_back(argv[argn]);
|
||||
}
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!SettingRegistry::getInstance()->settingsLoaded())
|
||||
|
||||
if (stringcasecompare(argv[1], "connect") == 0)
|
||||
{
|
||||
//If no json file has been loaded, try to load the default.
|
||||
if (!SettingRegistry::getInstance()->loadJSON("fdmprinter.json"))
|
||||
{
|
||||
logError("ERROR: Failed to load json file: fdmprinter.json\n");
|
||||
}
|
||||
}
|
||||
|
||||
if(commandSocket)
|
||||
connect(argc, argv);
|
||||
}
|
||||
else if (stringcasecompare(argv[1], "slice") == 0)
|
||||
{
|
||||
commandSocket->connect(ip, port);
|
||||
slice(argc, argv);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef DEBUG
|
||||
try {
|
||||
#endif
|
||||
//Catch all exceptions, this prevents the "something went wrong" dialog on windows to pop up on a thrown exception.
|
||||
// Only ClipperLib currently throws exceptions. And only in case that it makes an internal error.
|
||||
if (files.size() > 0)
|
||||
processor.processFiles(files);
|
||||
#ifndef DEBUG
|
||||
}catch(...){
|
||||
cura::logError("Unknown exception\n");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
//Finalize the processor, this adds the end.gcode. And reports statistics.
|
||||
processor.finalize();
|
||||
cura::logError("Unknown command: %s\n", argv[1]);
|
||||
print_call(argc, argv);
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+10
-21
@@ -1,6 +1,8 @@
|
||||
#include "mesh.h"
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
const int vertex_meld_distance = MM2INT(0.03);
|
||||
static inline uint32_t pointHash(Point3& p)
|
||||
@@ -8,7 +10,7 @@ static inline uint32_t pointHash(Point3& p)
|
||||
return ((p.x + vertex_meld_distance/2) / vertex_meld_distance) ^ (((p.y + vertex_meld_distance/2) / vertex_meld_distance) << 10) ^ (((p.z + vertex_meld_distance/2) / vertex_meld_distance) << 20);
|
||||
}
|
||||
|
||||
Mesh::Mesh(SettingsBase* parent)
|
||||
Mesh::Mesh(SettingsBaseVirtual* parent)
|
||||
: SettingsBase(parent)
|
||||
{
|
||||
}
|
||||
@@ -55,29 +57,11 @@ void Mesh::finish()
|
||||
|
||||
Point3 Mesh::min()
|
||||
{
|
||||
if (vertices.size() < 1)
|
||||
return Point3(0, 0, 0);
|
||||
Point3 ret = vertices[0].p;
|
||||
for(unsigned int i=0; i<vertices.size(); i++)
|
||||
{
|
||||
ret.x = std::min(ret.x, vertices[i].p.x);
|
||||
ret.y = std::min(ret.y, vertices[i].p.y);
|
||||
ret.z = std::min(ret.z, vertices[i].p.z);
|
||||
}
|
||||
return ret;
|
||||
return aabb.min;
|
||||
}
|
||||
Point3 Mesh::max()
|
||||
{
|
||||
if (vertices.size() < 1)
|
||||
return Point3(0, 0, 0);
|
||||
Point3 ret = vertices[0].p;
|
||||
for(unsigned int i=0; i<vertices.size(); i++)
|
||||
{
|
||||
ret.x = std::max(ret.x, vertices[i].p.x);
|
||||
ret.y = std::max(ret.y, vertices[i].p.y);
|
||||
ret.z = std::max(ret.z, vertices[i].p.z);
|
||||
}
|
||||
return ret;
|
||||
return aabb.max;
|
||||
}
|
||||
|
||||
int Mesh::findIndexOfVertex(Point3& v)
|
||||
@@ -93,6 +77,9 @@ int Mesh::findIndexOfVertex(Point3& v)
|
||||
}
|
||||
vertex_hash_map[hash].push_back(vertices.size());
|
||||
vertices.emplace_back(v);
|
||||
|
||||
aabb.include(v);
|
||||
|
||||
return vertices.size() - 1;
|
||||
}
|
||||
|
||||
@@ -191,3 +178,5 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx)
|
||||
if (bestIdx < 0) cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx);
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
+20
-4
@@ -2,7 +2,10 @@
|
||||
#define MESH_H
|
||||
|
||||
#include "settings.h"
|
||||
#include "utils/AABB.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
/*!
|
||||
Vertex type to be used in a Mesh.
|
||||
|
||||
@@ -14,7 +17,7 @@ public:
|
||||
Point3 p; //!< location of the vertex
|
||||
std::vector<uint32_t> connected_faces; //!< list of the indices of connected faces
|
||||
|
||||
MeshVertex(Point3 p) : p(p) {} //!< doesn't set connected_faces
|
||||
MeshVertex(Point3 p) : p(p) {connected_faces.reserve(8);} //!< doesn't set connected_faces
|
||||
};
|
||||
|
||||
/*! A MeshFace is a 3 dimensional model triangle with 3 points. These points are already converted to integers
|
||||
@@ -54,12 +57,13 @@ See MeshFace for the specifics of how/when faces are connected.
|
||||
class Mesh : public SettingsBase // inherits settings
|
||||
{
|
||||
//! The vertex_hash_map stores a index reference of each vertex for the hash of that location. Allows for quick retrieval of points with the same location.
|
||||
std::map<uint32_t, std::vector<uint32_t> > vertex_hash_map;
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t> > vertex_hash_map;
|
||||
AABB3D aabb;
|
||||
public:
|
||||
std::vector<MeshVertex> vertices;//!< list of all vertices in the mesh
|
||||
std::vector<MeshFace> faces; //!< list of all faces in the mesh
|
||||
|
||||
Mesh(SettingsBase* parent); //!< initializes the settings
|
||||
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.
|
||||
void clear(); //!< clears all data
|
||||
@@ -67,6 +71,18 @@ public:
|
||||
|
||||
Point3 min(); //!< min (in x,y and z) vertex of the bounding box
|
||||
Point3 max(); //!< max (in x,y and z) vertex of the bounding box
|
||||
|
||||
/*!
|
||||
* Offset the whole mesh (all vertices and the bounding box).
|
||||
* \param offset The offset byu which to offset the whole mesh.
|
||||
*/
|
||||
void offset(Point3 offset)
|
||||
{
|
||||
if (offset == Point3(0,0,0)) { return; }
|
||||
for(MeshVertex& v : vertices)
|
||||
v.p += offset;
|
||||
aabb.offset(offset);
|
||||
}
|
||||
|
||||
private:
|
||||
int findIndexOfVertex(Point3& v); //!< find index of vertex close to the given point, or create a new vertex and return its index.
|
||||
@@ -77,6 +93,6 @@ private:
|
||||
int getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx);
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
#endif//MESH_H
|
||||
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef MODELFILE_H
|
||||
#define MODELFILE_H
|
||||
/**
|
||||
modelFile contains the model loaders for the slicer. The model loader turns any format that it can read into a list of triangles with 3 X/Y/Z points.
|
||||
|
||||
The format returned is a Model class with an array of faces, which have integer points with a resolution of 1 micron. Giving a maximum object size of 4 meters.
|
||||
**/
|
||||
|
||||
#include "../mesh.h"
|
||||
|
||||
//A PrintObject is a 3D model with 1 or more 3D meshes.
|
||||
class PrintObject : public SettingsBase
|
||||
{
|
||||
public:
|
||||
std::vector<Mesh> meshes;
|
||||
|
||||
PrintObject(SettingsBase* settings_base)
|
||||
: SettingsBase(settings_base)
|
||||
{
|
||||
}
|
||||
|
||||
Point3 min() //! minimal corner of bounding box
|
||||
{
|
||||
if (meshes.size() < 1)
|
||||
return Point3(0, 0, 0);
|
||||
Point3 ret = meshes[0].min();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
{
|
||||
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);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Point3 max() //! maximal corner of bounding box
|
||||
{
|
||||
if (meshes.size() < 1)
|
||||
return Point3(0, 0, 0);
|
||||
Point3 ret = meshes[0].max();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
{
|
||||
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);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for(Mesh& m : meshes)
|
||||
m.clear();
|
||||
}
|
||||
|
||||
void offset(Point3 offset)
|
||||
{
|
||||
for(Mesh& m : meshes)
|
||||
for(MeshVertex& v : m.vertices)
|
||||
v.p += offset;
|
||||
}
|
||||
|
||||
void finalize()
|
||||
{
|
||||
// If a mesh position was given, put the mesh at this position in 3D space.
|
||||
if (hasSetting("mesh_position_x") || hasSetting("mesh_position_y") || hasSetting("mesh_position_z"))
|
||||
{
|
||||
Point3 object_min = min();
|
||||
Point3 object_max = max();
|
||||
Point3 object_size = object_max - object_min;
|
||||
Point3 object_offset = Point3(-object_min.x - object_size.x / 2, -object_min.y - object_size.y / 2, -object_min.z);
|
||||
if (hasSetting("mesh_position_x"))
|
||||
object_offset.x += getSettingInMicrons("mesh_position_x");
|
||||
if (hasSetting("mesh_position_y"))
|
||||
object_offset.y += getSettingInMicrons("mesh_position_y");
|
||||
if (hasSetting("mesh_position_z"))
|
||||
object_offset.z += getSettingInMicrons("mesh_position_z");
|
||||
offset(object_offset);
|
||||
}
|
||||
|
||||
//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.
|
||||
if (hasSetting("machine_center_is_zero") && !getSettingBoolean("machine_center_is_zero"))
|
||||
{
|
||||
Point3 object_offset = Point3(0, 0, 0);
|
||||
if (hasSetting("machine_width"))
|
||||
object_offset.x = getSettingInMicrons("machine_width") / 2;
|
||||
if (hasSetting("machine_depth"))
|
||||
object_offset.y = getSettingInMicrons("machine_depth") / 2;
|
||||
offset(object_offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool loadMeshFromFile(PrintObject* object, const char* filename, FMatrix3x3& matrix);
|
||||
|
||||
#endif//MODELFILE_H
|
||||
+33
-43
@@ -1,58 +1,48 @@
|
||||
#include "multiVolumes.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
void carveMultipleVolumes(std::vector<SliceMeshStorage> &volumes)
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
//Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas.
|
||||
for(unsigned int idx=0; idx < volumes.size(); idx++)
|
||||
{
|
||||
for(unsigned int idx2=0; idx2<idx; idx2++)
|
||||
{
|
||||
for(unsigned int layerNr=0; layerNr < volumes[idx].layers.size(); layerNr++)
|
||||
for(unsigned int layerNr=0; layerNr < volumes[idx]->layers.size(); layerNr++)
|
||||
{
|
||||
SliceLayer* layer1 = &volumes[idx].layers[layerNr];
|
||||
SliceLayer* layer2 = &volumes[idx2].layers[layerNr];
|
||||
for(unsigned int p1 = 0; p1 < layer1->parts.size(); p1++)
|
||||
{
|
||||
for(unsigned int p2 = 0; p2 < layer2->parts.size(); p2++)
|
||||
{
|
||||
layer1->parts[p1].outline = layer1->parts[p1].outline.difference(layer2->parts[p2].outline);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
//This generates some overlap in dual extrusion, for better bonding in touching parts.
|
||||
void generateMultipleVolumesOverlap(std::vector<SliceMeshStorage> &volumes, int overlap)
|
||||
{
|
||||
if (volumes.size() < 2 || overlap <= 0) return;
|
||||
|
||||
for(unsigned int layerNr=0; layerNr < volumes[0].layers.size(); layerNr++)
|
||||
{
|
||||
Polygons fullLayer;
|
||||
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
|
||||
{
|
||||
SliceLayer* layer1 = &volumes[volIdx].layers[layerNr];
|
||||
for(unsigned int p1 = 0; p1 < layer1->parts.size(); p1++)
|
||||
{
|
||||
fullLayer = fullLayer.unionPolygons(layer1->parts[p1].outline.offset(20)); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
|
||||
}
|
||||
}
|
||||
fullLayer = fullLayer.offset(-20); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
|
||||
|
||||
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
|
||||
{
|
||||
SliceLayer* layer1 = &volumes[volIdx].layers[layerNr];
|
||||
for(unsigned int p1 = 0; p1 < layer1->parts.size(); p1++)
|
||||
{
|
||||
layer1->parts[p1].outline = fullLayer.intersection(layer1->parts[p1].outline.offset(overlap / 2));
|
||||
SlicerLayer& layer1 = volumes[idx]->layers[layerNr];
|
||||
SlicerLayer& layer2 = volumes[idx2]->layers[layerNr];
|
||||
layer1.polygonList = layer1.polygonList.difference(layer2.polygonList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
//This generates some overlap in dual extrusion, for better bonding in touching parts.
|
||||
void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes, int overlap)
|
||||
{
|
||||
if (volumes.size() < 2 || overlap <= 0) return;
|
||||
|
||||
for(unsigned int layerNr=0; layerNr < volumes[0]->layers.size(); layerNr++)
|
||||
{
|
||||
Polygons fullLayer;
|
||||
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
|
||||
{
|
||||
SlicerLayer& layer1 = volumes[volIdx]->layers[layerNr];
|
||||
fullLayer = fullLayer.unionPolygons(layer1.polygonList.offset(20)); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
|
||||
}
|
||||
fullLayer = fullLayer.offset(-20); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
|
||||
|
||||
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
|
||||
{
|
||||
SlicerLayer& layer1 = volumes[volIdx]->layers[layerNr];
|
||||
layer1.polygonList = fullLayer.intersection(layer1.polygonList.offset(overlap / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
#define MULTIVOLUMES_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<SliceMeshStorage> &meshes);
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &meshes);
|
||||
|
||||
//Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
//This generates some overlap in dual extrusion, for better bonding in touching parts.
|
||||
void generateMultipleVolumesOverlap(std::vector<SliceMeshStorage> &meshes, int overlap);
|
||||
/*!
|
||||
* Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
* This generates some overlap in dual extrusion, for better bonding in touching parts.
|
||||
*/
|
||||
void generateMultipleVolumesOverlap(std::vector<Slicer*> &meshes, int overlap);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include <map>
|
||||
|
||||
#include "pathOrderOptimizer.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/BucketGrid2D.h"
|
||||
@@ -78,18 +76,30 @@ void PathOrderOptimizer::optimize()
|
||||
prev_point = startPoint;
|
||||
for(unsigned int n=0; n<polyOrder.size(); n++) /// decide final starting points in each polygon
|
||||
{
|
||||
int i_polygon = polyOrder[n];
|
||||
int best = getClosestPointInPolygon(prev_point, i_polygon);
|
||||
polyStart[i_polygon] = best;
|
||||
prev_point = polygons[i_polygon][best];
|
||||
int poly_idx = polyOrder[n];
|
||||
int point_idx = getPolyStart(prev_point, poly_idx);
|
||||
polyStart[poly_idx] = point_idx;
|
||||
prev_point = polygons[poly_idx][point_idx];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inline int PathOrderOptimizer::getClosestPointInPolygon(Point prev_point, int i_polygon)
|
||||
int PathOrderOptimizer::getPolyStart(Point prev_point, int poly_idx)
|
||||
{
|
||||
PolygonRef poly = polygons[i_polygon];
|
||||
int best = -1;
|
||||
switch (type)
|
||||
{
|
||||
case EZSeamType::BACK: return getFarthestPointInPolygon(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int PathOrderOptimizer::getClosestPointInPolygon(Point prev_point, int poly_idx)
|
||||
{
|
||||
PolygonRef poly = polygons[poly_idx];
|
||||
int best_point_idx = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
bool orientation = poly.orientation();
|
||||
for(unsigned int i_point=0 ; i_point<poly.size() ; i_point++)
|
||||
@@ -102,11 +112,33 @@ inline int PathOrderOptimizer::getClosestPointInPolygon(Point prev_point, int i_
|
||||
dot_score = -dot_score;
|
||||
if (dist + dot_score < bestDist)
|
||||
{
|
||||
best = i_point;
|
||||
best_point_idx = i_point;
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
return best_point_idx;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,25 +4,29 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include "utils/polygon.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
//! Parts order optimization class.
|
||||
|
||||
/*!
|
||||
* Utility class for optimizing the path order by minimizing the distance traveled between printing different parts in the layer.
|
||||
* The order of polygons is optimized and the startingpoint within each polygon is chosen.
|
||||
*/
|
||||
* Parts order optimization class.
|
||||
*
|
||||
* Utility class for optimizing the path order by minimizing the distance traveled between printing different parts in the layer.
|
||||
* The order of polygons is optimized and the startingpoint within each polygon is chosen.
|
||||
*/
|
||||
class PathOrderOptimizer
|
||||
{
|
||||
public:
|
||||
EZSeamType type;
|
||||
Point startPoint; //!< The location of the nozzle before starting to print the current layer
|
||||
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)
|
||||
PathOrderOptimizer(Point startPoint, EZSeamType type = EZSeamType::SHORTEST)
|
||||
: type(type)
|
||||
, startPoint(startPoint)
|
||||
{
|
||||
this->startPoint = startPoint;
|
||||
}
|
||||
|
||||
void addPolygon(PolygonRef polygon)
|
||||
@@ -38,8 +42,11 @@ public:
|
||||
|
||||
void optimize(); //!< sets #polyStart and #polyOrder
|
||||
|
||||
private:
|
||||
int getClosestPointInPolygon(Point prev, int i_polygon); //!< returns the index of the closest point
|
||||
private:
|
||||
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);
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "polygonOptimizer.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
void optimizePolygon(PolygonRef poly)
|
||||
{
|
||||
Point p0 = poly[poly.size()-1];
|
||||
for(unsigned int i=0;i<poly.size();i++)
|
||||
{
|
||||
Point p1 = poly[i];
|
||||
if (shorterThen(p0 - p1, MICRON2INT(10)))
|
||||
{
|
||||
poly.remove(i);
|
||||
i --;
|
||||
}else if (shorterThen(p0 - p1, MICRON2INT(500)))
|
||||
{
|
||||
Point p2;
|
||||
if (i < poly.size() - 1)
|
||||
p2 = poly[i+1];
|
||||
else
|
||||
p2 = poly[0];
|
||||
|
||||
Point diff0 = normal(p1 - p0, 10000000);
|
||||
Point diff2 = normal(p1 - p2, 10000000);
|
||||
|
||||
int64_t d = dot(diff0, diff2);
|
||||
if (d < -99999999999999LL)
|
||||
{
|
||||
poly.remove(i);
|
||||
i --;
|
||||
}else{
|
||||
p0 = p1;
|
||||
}
|
||||
}else{
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void optimizePolygons(Polygons& polys)
|
||||
{
|
||||
for(unsigned int n=0;n<polys.size();n++)
|
||||
{
|
||||
optimizePolygon(polys[n]);
|
||||
if (polys[n].size() < 3)
|
||||
{
|
||||
polys.remove(n);
|
||||
n--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,15 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef POLYGON_OPTIMIZER_H
|
||||
#define POLYGON_OPTIMIZER_H
|
||||
|
||||
#include "utils/polygon.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
void optimizePolygon(PolygonRef poly);
|
||||
|
||||
void optimizePolygons(Polygons& polys);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//POLYGON_OPTIMIZER_H
|
||||
+10
-11
@@ -6,19 +6,18 @@ namespace cura {
|
||||
|
||||
void generateRaft(SliceDataStorage& storage, int distance)
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
if (storage.draft_protection_shield.size() > 0)
|
||||
{
|
||||
if (mesh.layers.size() < 1) continue;
|
||||
SliceLayer* layer = &mesh.layers[0];
|
||||
for(SliceLayerPart& part : layer->parts)
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(part.outline.offset(distance));
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(storage.draft_protection_shield.offset(distance));
|
||||
}
|
||||
else if (storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0)
|
||||
{
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(storage.oozeShield[0].offset(distance));
|
||||
}
|
||||
else
|
||||
{
|
||||
storage.raftOutline = storage.getLayerOutlines(0, true);
|
||||
}
|
||||
|
||||
Polygons support;
|
||||
if (storage.support.generated)
|
||||
support = storage.support.supportAreasPerLayer[0];
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(support.offset(distance));
|
||||
storage.raftOutline = storage.raftOutline.unionPolygons(storage.wipeTower.offset(distance));
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+220
-122
@@ -1,27 +1,80 @@
|
||||
#include "settingRegistry.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream> // debug IO
|
||||
#include <libgen.h> // dirname
|
||||
#include <string>
|
||||
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
#include "rapidjson/rapidjson.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/error/en.h"
|
||||
#include "rapidjson/filereadstream.h"
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
SettingRegistry SettingRegistry::instance; // define settingRegistry
|
||||
|
||||
std::string SettingRegistry::toString(rapidjson::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case rapidjson::Type::kNullType: return "null";
|
||||
case rapidjson::Type::kFalseType: return "false";
|
||||
case rapidjson::Type::kTrueType: return "true";
|
||||
case rapidjson::Type::kObjectType: return "object";
|
||||
case rapidjson::Type::kArrayType: return "array";
|
||||
case rapidjson::Type::kStringType: return "string";
|
||||
case rapidjson::Type::kNumberType: return "number";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SettingContainer::SettingContainer(std::string key, std::string label)
|
||||
: key(key)
|
||||
, label(label)
|
||||
{
|
||||
}
|
||||
|
||||
SettingConfig* SettingContainer::addChild(std::string key, std::string label)
|
||||
{
|
||||
children.emplace_back(key, label, nullptr);
|
||||
return &children.back();
|
||||
}
|
||||
|
||||
SettingConfig::SettingConfig(std::string key, std::string label, SettingContainer* parent)
|
||||
: SettingContainer(key, label)
|
||||
, parent(parent)
|
||||
{
|
||||
// std::cerr << key << std::endl; // debug output to show all frontend registered settings...
|
||||
}
|
||||
|
||||
bool SettingRegistry::settingExists(std::string key) const
|
||||
{
|
||||
return settings.find(key) != settings.end();
|
||||
}
|
||||
|
||||
const SettingConfig* SettingRegistry::getSettingConfig(std::string key)
|
||||
SettingConfig* SettingRegistry::getSettingConfig(std::string key)
|
||||
{
|
||||
if (settings.find(key) == settings.end())
|
||||
auto it = settings.find(key);
|
||||
if (it == settings.end())
|
||||
return nullptr;
|
||||
return settings[key];
|
||||
return it->second;
|
||||
}
|
||||
|
||||
SettingContainer* SettingRegistry::getCategory(std::string key)
|
||||
{
|
||||
for (SettingContainer& cat : categories)
|
||||
if (cat.getKey().compare(key) == 0)
|
||||
return &cat;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
SettingRegistry::SettingRegistry()
|
||||
{
|
||||
}
|
||||
@@ -31,104 +84,160 @@ bool SettingRegistry::settingsLoaded()
|
||||
return settings.size() > 0;
|
||||
}
|
||||
|
||||
bool SettingRegistry::loadJSON(std::string filename)
|
||||
int SettingRegistry::loadJSON(std::string filename, rapidjson::Document& json_document)
|
||||
{
|
||||
FILE* f = fopen(filename.c_str(), "rb");
|
||||
if (!f)
|
||||
{
|
||||
cura::logError("Couldn't open JSON file.\n");
|
||||
return 1;
|
||||
}
|
||||
char read_buffer[4096];
|
||||
rapidjson::FileReadStream reader_stream(f, read_buffer, sizeof(read_buffer));
|
||||
json_document.ParseStream(reader_stream);
|
||||
fclose(f);
|
||||
if (json_document.HasParseError())
|
||||
{
|
||||
cura::logError("Error parsing JSON(offset %u): %s\n", (unsigned)json_document.GetErrorOffset(), GetParseError_En(json_document.GetParseError()));
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SettingRegistry::loadJSONsettings(std::string filename)
|
||||
{
|
||||
rapidjson::Document json_document;
|
||||
|
||||
{
|
||||
FILE* f = fopen(filename.c_str(), "rb");
|
||||
if (!f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
char read_buffer[4096];
|
||||
rapidjson::FileReadStream reader_stream(f, read_buffer, sizeof(read_buffer));
|
||||
json_document.ParseStream(reader_stream);
|
||||
fclose(f);
|
||||
if (json_document.HasParseError())
|
||||
{
|
||||
cura::logError("Error(offset %u): %s\n", (unsigned)json_document.GetErrorOffset(), GetParseError_En(json_document.GetParseError()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!json_document.IsObject())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!json_document.HasMember("categories"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
categories.emplace_back("machine_settings", "Machine Settings");
|
||||
SettingCategory* category_machine_settings = &categories.back();
|
||||
_addSettingsToCategory(category_machine_settings, json_document["machine_settings"], NULL);
|
||||
int err = loadJSON(filename, json_document);
|
||||
if (err) { return err; }
|
||||
|
||||
categories.emplace_back("mesh_settings", "TEMPORARY");
|
||||
SettingCategory* category_mesh_settings = &categories.back();
|
||||
if (json_document.HasMember("inherits"))
|
||||
{
|
||||
SettingConfig* config = category_mesh_settings->addChild("mesh_position_x", "mesh_position_x");
|
||||
config->setDefault("0");
|
||||
if (settingExists(config->getKey()))
|
||||
{
|
||||
cura::logError("Duplicate definition of setting: %s\n", config->getKey().c_str());
|
||||
}
|
||||
settings[config->getKey()] = config;
|
||||
std::string filename_copy = std::string(filename.c_str()); // copy the string because dirname(.) changes the input string!!!
|
||||
char* filename_cstr = (char*)filename_copy.c_str();
|
||||
int err = loadJSONsettings(std::string(dirname(filename_cstr)) + std::string("/") + json_document["inherits"].GetString());
|
||||
if (err) { return err; }
|
||||
return loadJSONsettingsFromDoc(json_document, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingConfig* config = category_mesh_settings->addChild("mesh_position_y", "mesh_position_y");
|
||||
config->setDefault("0");
|
||||
if (settingExists(config->getKey()))
|
||||
{
|
||||
cura::logError("Duplicate definition of setting: %s\n", config->getKey().c_str());
|
||||
}
|
||||
settings[config->getKey()] = config;
|
||||
}
|
||||
{
|
||||
SettingConfig* config = category_mesh_settings->addChild("mesh_position_z", "mesh_position_z");
|
||||
config->setDefault("0");
|
||||
if (settingExists(config->getKey()))
|
||||
{
|
||||
cura::logError("Duplicate definition of setting: %s\n", config->getKey().c_str());
|
||||
}
|
||||
settings[config->getKey()] = config;
|
||||
return loadJSONsettingsFromDoc(json_document, true);
|
||||
}
|
||||
|
||||
|
||||
for (rapidjson::Value::ConstMemberIterator category_iterator = json_document["categories"].MemberBegin(); category_iterator != json_document["categories"].MemberEnd(); ++category_iterator)
|
||||
{
|
||||
if (!category_iterator->value.IsObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!category_iterator->value.HasMember("label") || !category_iterator->value["label"].IsString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!category_iterator->value.HasMember("settings") || !category_iterator->value["settings"].IsObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
categories.emplace_back(category_iterator->name.GetString(), category_iterator->value["label"].GetString());
|
||||
SettingCategory* category = &categories.back();
|
||||
|
||||
_addSettingsToCategory(category, category_iterator->value["settings"], NULL);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SettingRegistry::_addSettingsToCategory(SettingCategory* category, const rapidjson::Value& json_object, SettingConfig* parent)
|
||||
int SettingRegistry::loadJSONsettingsFromDoc(rapidjson::Document& json_document, bool warn_duplicates)
|
||||
{
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object.MemberBegin(); setting_iterator != json_object.MemberEnd(); ++setting_iterator)
|
||||
|
||||
if (!json_document.IsObject())
|
||||
{
|
||||
const rapidjson::Value& data = setting_iterator->value;
|
||||
cura::logError("JSON file is not an object.\n");
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (json_document.HasMember("machine_extruder_trains"))
|
||||
{
|
||||
categories.emplace_back("machine_extruder_trains", "Extruder Trains Settings Objects");
|
||||
SettingContainer* category_trains = &categories.back();
|
||||
const rapidjson::Value& trains = json_document["machine_extruder_trains"];
|
||||
if (trains.IsArray())
|
||||
{
|
||||
if (trains.Size() > 0 && trains[0].IsObject())
|
||||
{
|
||||
unsigned int idx = 0;
|
||||
for (auto it = trains.Begin(); it != trains.End(); ++it)
|
||||
{
|
||||
SettingConfig* child = category_trains->addChild(std::to_string(idx), std::to_string(idx));
|
||||
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = it->MemberBegin(); setting_iterator != it->MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(child, setting_iterator, warn_duplicates, false);
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logError("Error: JSON machine_extruder_trains is not an array!\n");
|
||||
}
|
||||
}
|
||||
if (json_document.HasMember("machine_settings"))
|
||||
{
|
||||
categories.emplace_back("machine_settings", "Machine Settings");
|
||||
SettingContainer* category_machine_settings = &categories.back();
|
||||
|
||||
const rapidjson::Value& json_object_container = json_document["machine_settings"];
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(category_machine_settings, setting_iterator, warn_duplicates);
|
||||
}
|
||||
}
|
||||
|
||||
if (json_document.HasMember("categories"))
|
||||
{
|
||||
for (rapidjson::Value::ConstMemberIterator category_iterator = json_document["categories"].MemberBegin(); category_iterator != json_document["categories"].MemberEnd(); ++category_iterator)
|
||||
{
|
||||
if (!category_iterator->value.IsObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!category_iterator->value.HasMember("label") || !category_iterator->value["label"].IsString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!category_iterator->value.HasMember("settings") || !category_iterator->value["settings"].IsObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
categories.emplace_back(category_iterator->name.GetString(), category_iterator->value["label"].GetString());
|
||||
SettingContainer* category = &categories.back();
|
||||
|
||||
const rapidjson::Value& json_object_container = category_iterator->value["settings"];
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(category, setting_iterator, warn_duplicates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (false && json_document.HasMember("overrides"))
|
||||
{
|
||||
const rapidjson::Value& json_object_container = json_document["overrides"];
|
||||
for (rapidjson::Value::ConstMemberIterator override_iterator = json_object_container.MemberBegin(); override_iterator != json_object_container.MemberEnd(); ++override_iterator)
|
||||
{
|
||||
SettingConfig* conf = getSettingConfig(override_iterator->name.GetString());
|
||||
_addSettingToContainer(conf, override_iterator, false);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
void SettingRegistry::_addSettingToContainer(SettingContainer* parent, rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings)
|
||||
{
|
||||
const rapidjson::Value& data = json_object_it->value;
|
||||
|
||||
if (data.HasMember("type") && data["type"].IsString() &&
|
||||
(data["type"].GetString() == std::string("polygon") || data["type"].GetString() == std::string("polygons")))
|
||||
{
|
||||
logWarning("Loading polygon setting %s not implemented...\n", json_object_it->name.GetString());
|
||||
/// When this setting has children, add those children to the parent setting.
|
||||
if (data.HasMember("children") && data["children"].IsObject())
|
||||
{
|
||||
const rapidjson::Value& json_object_container = data["children"];
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(parent, setting_iterator, warn_duplicates, add_to_settings);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::string label;
|
||||
if (!setting_iterator->value.HasMember("label") || !data["label"].IsString())
|
||||
if (!json_object_it->value.HasMember("label") || !data["label"].IsString())
|
||||
{
|
||||
label = "N/A";
|
||||
}
|
||||
@@ -138,11 +247,7 @@ void SettingRegistry::_addSettingsToCategory(SettingCategory* category, const ra
|
||||
}
|
||||
|
||||
/// Create the new setting config object.
|
||||
SettingConfig* config;
|
||||
if (parent)
|
||||
config = parent->addChild(setting_iterator->name.GetString(), label);
|
||||
else
|
||||
config = category->addChild(setting_iterator->name.GetString(), label);
|
||||
SettingConfig* config = parent->addChild(json_object_it->name.GetString(), label);
|
||||
|
||||
|
||||
/// Fill the setting config object with data we have in the json file.
|
||||
@@ -152,23 +257,28 @@ void SettingRegistry::_addSettingsToCategory(SettingCategory* category, const ra
|
||||
}
|
||||
if (data.HasMember("default"))
|
||||
{
|
||||
if (data["default"].IsString())
|
||||
const rapidjson::Value& dflt = data["default"];
|
||||
if (dflt.IsString())
|
||||
{
|
||||
config->setDefault(data["default"].GetString());
|
||||
config->setDefault(dflt.GetString());
|
||||
}
|
||||
else if (data["default"].IsTrue())
|
||||
else if (dflt.IsTrue())
|
||||
{
|
||||
config->setDefault("true");
|
||||
}
|
||||
else if (data["default"].IsFalse())
|
||||
else if (dflt.IsFalse())
|
||||
{
|
||||
config->setDefault("false");
|
||||
}
|
||||
else if (data["default"].IsNumber())
|
||||
else if (dflt.IsNumber())
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << data["default"].GetDouble();
|
||||
ss << dflt.GetDouble();
|
||||
config->setDefault(ss.str());
|
||||
} // arrays are ignored because machine_extruder_trains needs to be handled separately
|
||||
else
|
||||
{
|
||||
logError("Unrecognized data type in JSON: %s has type %s\n", json_object_it->name.GetString(), toString(dflt.GetType()).c_str());
|
||||
}
|
||||
}
|
||||
if (data.HasMember("unit") && data["unit"].IsString())
|
||||
@@ -177,38 +287,26 @@ void SettingRegistry::_addSettingsToCategory(SettingCategory* category, const ra
|
||||
}
|
||||
|
||||
/// Register the setting in the settings map lookup.
|
||||
if (settingExists(config->getKey()))
|
||||
if (warn_duplicates && settingExists(config->getKey()))
|
||||
{
|
||||
cura::logError("Duplicate definition of setting: %s\n", config->getKey().c_str());
|
||||
cura::logError("Duplicate definition of setting: %s a.k.a. \"%s\" was already claimed by \"%s\"\n", config->getKey().c_str(), config->getLabel().c_str(), getSettingConfig(config->getKey())->getLabel().c_str());
|
||||
}
|
||||
|
||||
if (add_to_settings)
|
||||
{
|
||||
settings[config->getKey()] = config;
|
||||
}
|
||||
settings[config->getKey()] = config;
|
||||
|
||||
/// When this setting has children, add those children to this setting.
|
||||
if (data.HasMember("children") && data["children"].IsObject())
|
||||
{
|
||||
_addSettingsToCategory(category, data["children"], config);
|
||||
const rapidjson::Value& json_object_container = data["children"];
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(parent, setting_iterator, warn_duplicates, add_to_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingCategory::SettingCategory(std::string key, std::string label)
|
||||
: label(label), key(key)
|
||||
{
|
||||
}
|
||||
|
||||
SettingConfig* SettingCategory::addChild(std::string key, std::string label)
|
||||
{
|
||||
children.emplace_back(key, label, nullptr);
|
||||
return &children.back();
|
||||
}
|
||||
|
||||
SettingConfig::SettingConfig(std::string key, std::string label, SettingConfig* parent)
|
||||
: label(label), key(key), parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
SettingConfig* SettingConfig::addChild(std::string key, std::string label)
|
||||
{
|
||||
children.emplace_back(key, label, this);
|
||||
return &children.back();
|
||||
}
|
||||
}//namespace cura
|
||||
+98
-20
@@ -3,11 +3,15 @@
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
#include "utils/NoCopy.h"
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
// Forward declaration
|
||||
class SettingConfig;
|
||||
|
||||
@@ -15,16 +19,39 @@ class SettingConfig;
|
||||
* Setting category.
|
||||
* Filled from the fdmprinter.json file. Contains one or more children settings.
|
||||
*/
|
||||
class SettingCategory
|
||||
class SettingContainer
|
||||
{
|
||||
friend class SettingConfig;
|
||||
private:
|
||||
std::string label;
|
||||
std::string key;
|
||||
std::string label;
|
||||
std::list<SettingConfig> children;
|
||||
public:
|
||||
SettingCategory(std::string key, std::string label);
|
||||
std::string getKey() const { return key; }
|
||||
std::string getLabel() const { return label; }
|
||||
SettingContainer(std::string key, std::string label);
|
||||
|
||||
SettingConfig* addChild(std::string key, std::string label);
|
||||
|
||||
/*!
|
||||
* Get the \p idx th child.
|
||||
*
|
||||
* This is used to get a specific extruder train in Settingsbase::setExtruderTrainDefaults
|
||||
*
|
||||
* \param idx The index in the list of children
|
||||
* \return The \p idx th child
|
||||
*/
|
||||
const SettingConfig* getChild(unsigned int idx) const
|
||||
{
|
||||
if (idx < children.size())
|
||||
{
|
||||
auto it = children.begin();
|
||||
while (idx > 0) { ++it; idx--; }
|
||||
return &*it;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -32,20 +59,24 @@ public:
|
||||
* Filled from the fdmprinter.json file. Can contain child settings, and is registered in the
|
||||
* setting registry with it's key.
|
||||
*/
|
||||
class SettingConfig
|
||||
class SettingConfig : public SettingContainer
|
||||
{
|
||||
private:
|
||||
std::string label;
|
||||
std::string key;
|
||||
std::string type;
|
||||
std::string default_value;
|
||||
std::string unit;
|
||||
SettingConfig* parent;
|
||||
std::list<SettingConfig> children;
|
||||
SettingContainer* parent;
|
||||
public:
|
||||
SettingConfig(std::string key, std::string label, SettingConfig* parent);
|
||||
|
||||
SettingConfig* addChild(std::string key, std::string label);
|
||||
SettingConfig(std::string key, std::string label, SettingContainer* parent);
|
||||
|
||||
/*!
|
||||
* Get the SettingConfig::children.
|
||||
*
|
||||
* This is used to get the extruder trains; see Settingsbase::setExtruderTrainDefaults
|
||||
*
|
||||
* \return SettingConfig::children
|
||||
*/
|
||||
const std::list<SettingConfig>& getChildren() const { return children; }
|
||||
|
||||
std::string getKey() const
|
||||
{
|
||||
@@ -89,25 +120,72 @@ public:
|
||||
* This registry contains all known setting keys.
|
||||
* The registry also contains the settings categories to build up the setting hiarcy from the json file.
|
||||
*/
|
||||
class SettingRegistry
|
||||
class SettingRegistry : NoCopy
|
||||
{
|
||||
private:
|
||||
static SettingRegistry instance;
|
||||
|
||||
std::map<std::string, SettingConfig*> settings;
|
||||
std::list<SettingCategory> categories;
|
||||
SettingRegistry();
|
||||
|
||||
std::unordered_map<std::string, SettingConfig*> settings;
|
||||
std::list<SettingContainer> categories;
|
||||
public:
|
||||
/*!
|
||||
* Get the SettingRegistry.
|
||||
*
|
||||
* This is a singleton class.
|
||||
*
|
||||
* \return The SettingRegistry
|
||||
*/
|
||||
static SettingRegistry* getInstance() { return &instance; }
|
||||
|
||||
bool settingExists(std::string key) const;
|
||||
const SettingConfig* getSettingConfig(std::string key);
|
||||
SettingConfig* getSettingConfig(std::string key);
|
||||
|
||||
/*!
|
||||
* Return the first category with the given key as name, or a null pointer.
|
||||
*
|
||||
* \param key the key as it is in the JSON file
|
||||
* \return The first category in the list having the \p key
|
||||
*/
|
||||
SettingContainer* getCategory(std::string key);
|
||||
|
||||
bool settingsLoaded();
|
||||
bool loadJSON(std::string filename);
|
||||
private:
|
||||
SettingRegistry();
|
||||
/*!
|
||||
* Load settings from a json file and all the parents it inherits from.
|
||||
*
|
||||
* Uses recursion to load the parent json file.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadJSONsettings(std::string filename);
|
||||
|
||||
void _addSettingsToCategory(SettingCategory* category, const rapidjson::Value& json_object, SettingConfig* parent);
|
||||
private:
|
||||
std::string toString(rapidjson::Type type);
|
||||
/*!
|
||||
* Load a json document.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \param json_document (output) the document to be loaded
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadJSON(std::string filename, rapidjson::Document& json_document);
|
||||
|
||||
/*!
|
||||
* Load settings from a single json file.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \param warn_duplicates whether to warn for duplicate definitions
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadJSONsettingsFromDoc(rapidjson::Document& json_document, bool warn_duplicates);
|
||||
|
||||
/*!
|
||||
* \param warn_duplicates whether to warn for duplicate definitions
|
||||
*/
|
||||
void _addSettingToContainer(SettingContainer* parent, rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings = true);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//SETTING_REGISTRY_H
|
||||
|
||||
+127
-53
@@ -7,21 +7,38 @@
|
||||
#include "settings.h"
|
||||
#include "settingRegistry.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
//c++11 no longer defines M_PI, so add our own constant.
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
SettingsBase::SettingsBase()
|
||||
SettingsBaseVirtual::SettingsBaseVirtual()
|
||||
: parent(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
SettingsBase::SettingsBase(SettingsBase* parent)
|
||||
SettingsBaseVirtual::SettingsBaseVirtual(SettingsBaseVirtual* parent)
|
||||
: parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
SettingsBase::SettingsBase()
|
||||
: SettingsBaseVirtual(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
SettingsBase::SettingsBase(SettingsBaseVirtual* parent)
|
||||
: SettingsBaseVirtual(parent)
|
||||
{
|
||||
}
|
||||
|
||||
SettingsMessenger::SettingsMessenger(SettingsBaseVirtual* parent)
|
||||
: SettingsBaseVirtual(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void SettingsBase::setSetting(std::string key, std::string value)
|
||||
{
|
||||
if (SettingRegistry::getInstance()->settingExists(key))
|
||||
@@ -49,7 +66,6 @@ std::string SettingsBase::getSettingString(std::string key)
|
||||
if (SettingRegistry::getInstance()->settingExists(key))
|
||||
{
|
||||
setting_values[key] = SettingRegistry::getInstance()->getSettingConfig(key)->getDefaultValue();
|
||||
cura::logError("Using default for: %s = %s\n", key.c_str(), setting_values[key].c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -59,45 +75,69 @@ std::string SettingsBase::getSettingString(std::string key)
|
||||
return setting_values[key];
|
||||
}
|
||||
|
||||
bool SettingsBase::hasSetting(std::string key)
|
||||
void SettingsMessenger::setSetting(std::string key, std::string value)
|
||||
{
|
||||
if (setting_values.find(key) != setting_values.end())
|
||||
parent->setSetting(key, value);
|
||||
}
|
||||
|
||||
std::string SettingsMessenger::getSettingString(std::string key)
|
||||
{
|
||||
return parent->getSettingString(key);
|
||||
}
|
||||
|
||||
|
||||
void SettingsBase::setExtruderTrainDefaults(unsigned int extruder_nr)
|
||||
{
|
||||
const SettingContainer* machine_extruder_trains = SettingRegistry::getInstance()->getCategory(std::string("machine_extruder_trains"));
|
||||
|
||||
if (!machine_extruder_trains)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (parent)
|
||||
{
|
||||
return parent->hasSetting(key);
|
||||
logWarning("Error: no machine_extruder_trains category found in JSON!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
const SettingConfig* train = machine_extruder_trains->getChild(extruder_nr);
|
||||
|
||||
if (!train)
|
||||
{
|
||||
logError("Not enough extruder trains specified in JSON: %i\n", extruder_nr);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const SettingConfig& setting : train->getChildren())
|
||||
{
|
||||
if (setting_values.find(setting.getKey()) == setting_values.end())
|
||||
{
|
||||
setSetting(setting.getKey(), setting.getDefaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int SettingsBase::getSettingAsIndex(std::string key)
|
||||
int SettingsBaseVirtual::getSettingAsIndex(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return atoi(value.c_str());
|
||||
}
|
||||
|
||||
int SettingsBase::getSettingAsCount(std::string key)
|
||||
int SettingsBaseVirtual::getSettingAsCount(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return atoi(value.c_str());
|
||||
}
|
||||
|
||||
int SettingsBase::getSettingInMicrons(std::string key)
|
||||
int SettingsBaseVirtual::getSettingInMicrons(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return atof(value.c_str()) * 1000.0;
|
||||
}
|
||||
|
||||
double SettingsBase::getSettingInAngleRadians(std::string key)
|
||||
double SettingsBaseVirtual::getSettingInAngleRadians(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return atof(value.c_str()) / 180.0 * M_PI;
|
||||
}
|
||||
|
||||
bool SettingsBase::getSettingBoolean(std::string key)
|
||||
bool SettingsBaseVirtual::getSettingBoolean(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "on")
|
||||
@@ -106,83 +146,117 @@ bool SettingsBase::getSettingBoolean(std::string key)
|
||||
return true;
|
||||
if (value == "true" or value == "True") //Python uses "True"
|
||||
return true;
|
||||
return atoi(value.c_str()) != 0;
|
||||
int num = atoi(value.c_str());
|
||||
return num != 0;
|
||||
}
|
||||
|
||||
double SettingsBase::getSettingInDegreeCelsius(std::string key)
|
||||
double SettingsBaseVirtual::getSettingInDegreeCelsius(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return atof(value.c_str());
|
||||
}
|
||||
|
||||
double SettingsBase::getSettingInMillimetersPerSecond(std::string key)
|
||||
double SettingsBaseVirtual::getSettingInMillimetersPerSecond(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return std::max(1.0, atof(value.c_str()));
|
||||
}
|
||||
|
||||
double SettingsBase::getSettingInPercentage(std::string key)
|
||||
double SettingsBaseVirtual::getSettingInCubicMillimeters(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return std::max(0.0, atof(value.c_str()));
|
||||
}
|
||||
|
||||
double SettingsBase::getSettingInSeconds(std::string key)
|
||||
double SettingsBaseVirtual::getSettingInPercentage(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return std::max(0.0, atof(value.c_str()));
|
||||
}
|
||||
|
||||
EGCodeFlavor SettingsBase::getSettingAsGCodeFlavor(std::string key)
|
||||
double SettingsBaseVirtual::getSettingInSeconds(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return std::max(0.0, atof(value.c_str()));
|
||||
}
|
||||
|
||||
EGCodeFlavor SettingsBaseVirtual::getSettingAsGCodeFlavor(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "RepRap")
|
||||
return GCODE_FLAVOR_REPRAP;
|
||||
return EGCodeFlavor::REPRAP;
|
||||
else if (value == "UltiGCode")
|
||||
return GCODE_FLAVOR_ULTIGCODE;
|
||||
return EGCodeFlavor::ULTIGCODE;
|
||||
else if (value == "Makerbot")
|
||||
return GCODE_FLAVOR_MAKERBOT;
|
||||
return EGCodeFlavor::MAKERBOT;
|
||||
else if (value == "BFB")
|
||||
return GCODE_FLAVOR_BFB;
|
||||
return EGCodeFlavor::BFB;
|
||||
else if (value == "MACH3")
|
||||
return GCODE_FLAVOR_MACH3;
|
||||
return EGCodeFlavor::MACH3;
|
||||
else if (value == "RepRap (Volumatric)")
|
||||
return GCODE_FLAVOR_REPRAP_VOLUMATRIC;
|
||||
return GCODE_FLAVOR_REPRAP;
|
||||
return EGCodeFlavor::REPRAP_VOLUMATRIC;
|
||||
return EGCodeFlavor::REPRAP;
|
||||
}
|
||||
|
||||
EFillMethod SettingsBase::getSettingAsFillMethod(std::string key)
|
||||
EFillMethod SettingsBaseVirtual::getSettingAsFillMethod(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "Lines")
|
||||
return Fill_Lines;
|
||||
if (value == "Grid")
|
||||
return Fill_Grid;
|
||||
if (value == "Triangles")
|
||||
return Fill_Triangles;
|
||||
if (value == "Concentric")
|
||||
return Fill_Concentric;
|
||||
if (value == "ZigZag")
|
||||
return Fill_ZigZag;
|
||||
return Fill_None;
|
||||
if (value == "lines")
|
||||
return EFillMethod::LINES;
|
||||
if (value == "grid")
|
||||
return EFillMethod::GRID;
|
||||
if (value == "triangles")
|
||||
return EFillMethod::TRIANGLES;
|
||||
if (value == "concentric")
|
||||
return EFillMethod::CONCENTRIC;
|
||||
if (value == "zigzag")
|
||||
return EFillMethod::ZIG_ZAG;
|
||||
return EFillMethod::NONE;
|
||||
}
|
||||
|
||||
EPlatformAdhesion SettingsBase::getSettingAsPlatformAdhesion(std::string key)
|
||||
EPlatformAdhesion SettingsBaseVirtual::getSettingAsPlatformAdhesion(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "Brim")
|
||||
return Adhesion_Brim;
|
||||
if (value == "Raft")
|
||||
return Adhesion_Raft;
|
||||
return Adhesion_None;
|
||||
if (value == "brim")
|
||||
return EPlatformAdhesion::BRIM;
|
||||
if (value == "raft")
|
||||
return EPlatformAdhesion::RAFT;
|
||||
return EPlatformAdhesion::SKIRT;
|
||||
}
|
||||
|
||||
ESupportType SettingsBase::getSettingAsSupportType(std::string key)
|
||||
ESupportType SettingsBaseVirtual::getSettingAsSupportType(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "Everywhere")
|
||||
return Support_Everywhere;
|
||||
if (value == "Touching Buildplate")
|
||||
return Support_PlatformOnly;
|
||||
return Support_None;
|
||||
if (value == "everywhere")
|
||||
return ESupportType::EVERYWHERE;
|
||||
if (value == "touching_buildplate")
|
||||
return ESupportType::PLATFORM_ONLY;
|
||||
return ESupportType::NONE;
|
||||
}
|
||||
|
||||
EZSeamType SettingsBaseVirtual::getSettingAsZSeamType(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "random")
|
||||
return EZSeamType::RANDOM;
|
||||
if (value == "shortest")
|
||||
return EZSeamType::SHORTEST;
|
||||
if (value == "back")
|
||||
return EZSeamType::BACK;
|
||||
return EZSeamType::SHORTEST;
|
||||
}
|
||||
|
||||
ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "normal")
|
||||
return ESurfaceMode::NORMAL;
|
||||
if (value == "surface")
|
||||
return ESurfaceMode::SURFACE;
|
||||
if (value == "both")
|
||||
return ESurfaceMode::BOTH;
|
||||
return ESurfaceMode::NORMAL;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+128
-40
@@ -3,9 +3,14 @@
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
|
||||
#include "utils/floatpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
#ifndef VERSION
|
||||
#define VERSION "DEV"
|
||||
#endif
|
||||
@@ -14,7 +19,7 @@
|
||||
* Different flavors of GCode. Some machines require different types of GCode.
|
||||
* The GCode flavor definition handles this as a big setting to make major or minor modifications to the GCode.
|
||||
*/
|
||||
enum EGCodeFlavor
|
||||
enum class EGCodeFlavor
|
||||
{
|
||||
/**
|
||||
* RepRap flavored GCode is Marlin/Sprinter/Repetier based GCode.
|
||||
@@ -24,7 +29,7 @@ enum EGCodeFlavor
|
||||
* Retraction is done on E values with G1. Start/end code is added.
|
||||
* M106 Sxxx and M107 are used to turn the fan on/off.
|
||||
**/
|
||||
GCODE_FLAVOR_REPRAP = 0,
|
||||
REPRAP = 0,
|
||||
/**
|
||||
* UltiGCode flavored is Marlin based GCode.
|
||||
* UltiGCode uses less settings on the slicer and puts more settings in the firmware. This makes for more hardware/material independed GCode.
|
||||
@@ -34,7 +39,7 @@ enum EGCodeFlavor
|
||||
* Start/end code is not added.
|
||||
* M106 Sxxx and M107 are used to turn the fan on/off.
|
||||
**/
|
||||
GCODE_FLAVOR_ULTIGCODE = 1,
|
||||
ULTIGCODE = 1,
|
||||
/**
|
||||
* Makerbot flavored GCode.
|
||||
* Looks a lot like RepRap GCode with a few changes. Requires MakerWare to convert to X3G files.
|
||||
@@ -44,7 +49,7 @@ enum EGCodeFlavor
|
||||
* Fan OFF is M127 T0
|
||||
* Homing is done with G162 X Y F2000
|
||||
**/
|
||||
GCODE_FLAVOR_MAKERBOT = 2,
|
||||
MAKERBOT = 2,
|
||||
|
||||
/**
|
||||
* Bits From Bytes GCode.
|
||||
@@ -52,13 +57,13 @@ enum EGCodeFlavor
|
||||
* Need X,Y,Z,F on every line.
|
||||
* Needs extruder ON/OFF (M101, M103), has auto-retrection (M227 S[2560*mm] P[2560*mm])
|
||||
**/
|
||||
GCODE_FLAVOR_BFB = 3,
|
||||
BFB = 3,
|
||||
|
||||
/**
|
||||
* MACH3 GCode
|
||||
* MACH3 is CNC control software, which expects A/B/C/D for extruders, instead of E.
|
||||
**/
|
||||
GCODE_FLAVOR_MACH3 = 4,
|
||||
MACH3 = 4,
|
||||
/**
|
||||
* RepRap volumatric flavored GCode is Marlin based GCode.
|
||||
* Volumatric uses less settings on the slicer and puts more settings in the firmware. This makes for more hardware/material independed GCode.
|
||||
@@ -67,7 +72,7 @@ enum EGCodeFlavor
|
||||
* Retraction is done with G10 and G11. Retraction settings are ignored. G10 S1 is used for multi-extruder switch retraction.
|
||||
* M106 Sxxx and M107 are used to turn the fan on/off.
|
||||
**/
|
||||
GCODE_FLAVOR_REPRAP_VOLUMATRIC = 5,
|
||||
REPRAP_VOLUMATRIC = 5,
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -75,62 +80,79 @@ enum EGCodeFlavor
|
||||
* This enum defines which fill patterns are available to get a uniform naming troughout the engine.
|
||||
* The different methods are used for top/bottom, support and sparse infill.
|
||||
*/
|
||||
enum EFillMethod
|
||||
enum class EFillMethod
|
||||
{
|
||||
Fill_Lines,
|
||||
Fill_Grid,
|
||||
Fill_Triangles,
|
||||
Fill_Concentric,
|
||||
Fill_ZigZag,
|
||||
Fill_None
|
||||
LINES,
|
||||
GRID,
|
||||
TRIANGLES,
|
||||
CONCENTRIC,
|
||||
ZIG_ZAG,
|
||||
NONE
|
||||
};
|
||||
|
||||
/*!
|
||||
* Type of platform adheasion
|
||||
*/
|
||||
enum EPlatformAdhesion
|
||||
enum class EPlatformAdhesion
|
||||
{
|
||||
Adhesion_None,
|
||||
Adhesion_Brim,
|
||||
Adhesion_Raft
|
||||
SKIRT,
|
||||
BRIM,
|
||||
RAFT
|
||||
};
|
||||
|
||||
/*!
|
||||
* Type of support material to generate
|
||||
*/
|
||||
enum ESupportType
|
||||
enum class ESupportType
|
||||
{
|
||||
Support_None,
|
||||
Support_PlatformOnly,
|
||||
Support_Everywhere
|
||||
NONE,
|
||||
PLATFORM_ONLY,
|
||||
EVERYWHERE
|
||||
};
|
||||
|
||||
enum class EZSeamType
|
||||
{
|
||||
RANDOM,
|
||||
SHORTEST,
|
||||
BACK
|
||||
};
|
||||
|
||||
enum class ESurfaceMode
|
||||
{
|
||||
NORMAL,
|
||||
SURFACE,
|
||||
BOTH
|
||||
};
|
||||
|
||||
#define MAX_EXTRUDERS 16
|
||||
|
||||
//Maximum number of sparse layers that can be combined into a single sparse extrusion.
|
||||
#define MAX_SPARSE_COMBINE 8
|
||||
//Maximum number of infill layers that can be combined into a single infill extrusion area.
|
||||
#define MAX_INFILL_COMBINE 8
|
||||
|
||||
class SettingsBase;
|
||||
|
||||
/*!
|
||||
* Base class for every object that can hold settings.
|
||||
* The SettingBase object can hold multiple key-value pairs that define settings.
|
||||
* The settings that are set on a SettingBase are checked against the SettingRegistry to ensure keys are valid.
|
||||
* Different conversion functions are available for settings to increase code clarity and in the future make
|
||||
* unit conversions possible.
|
||||
* An abstract class for classes that can provide setting values.
|
||||
* These are: SettingsBase, which contains setting information
|
||||
* and SettingsMessenger, which can pass on setting information from a SettingsBase
|
||||
*/
|
||||
class SettingsBase
|
||||
class SettingsBaseVirtual
|
||||
{
|
||||
private:
|
||||
std::map<std::string, std::string> setting_values;
|
||||
SettingsBase* parent;
|
||||
protected:
|
||||
SettingsBaseVirtual* parent;
|
||||
public:
|
||||
SettingsBase();
|
||||
SettingsBase(SettingsBase* parent);
|
||||
|
||||
bool hasSetting(std::string key);
|
||||
|
||||
void setSetting(std::string key, std::string value);
|
||||
virtual std::string getSettingString(std::string key) = 0;
|
||||
|
||||
virtual void setSetting(std::string key, std::string value) = 0;
|
||||
|
||||
virtual ~SettingsBaseVirtual() {}
|
||||
|
||||
SettingsBaseVirtual(); //!< SettingsBaseVirtual without a parent settings object
|
||||
SettingsBaseVirtual(SettingsBaseVirtual* parent); //!< construct a SettingsBaseVirtual with a parent settings object
|
||||
|
||||
void setParent(SettingsBaseVirtual* parent) { this->parent = parent; }
|
||||
SettingsBaseVirtual* getParent() { return parent; }
|
||||
|
||||
std::string getSettingString(std::string key);
|
||||
int getSettingAsIndex(std::string key);
|
||||
int getSettingAsCount(std::string key);
|
||||
|
||||
@@ -139,6 +161,7 @@ public:
|
||||
bool getSettingBoolean(std::string key);
|
||||
double getSettingInDegreeCelsius(std::string key);
|
||||
double getSettingInMillimetersPerSecond(std::string key);
|
||||
double getSettingInCubicMillimeters(std::string key);
|
||||
double getSettingInPercentage(std::string key);
|
||||
double getSettingInSeconds(std::string key);
|
||||
|
||||
@@ -146,7 +169,72 @@ public:
|
||||
EFillMethod getSettingAsFillMethod(std::string key);
|
||||
EPlatformAdhesion getSettingAsPlatformAdhesion(std::string key);
|
||||
ESupportType getSettingAsSupportType(std::string key);
|
||||
EZSeamType getSettingAsZSeamType(std::string key);
|
||||
ESurfaceMode getSettingAsSurfaceMode(std::string key);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Base class for every object that can hold settings.
|
||||
* The SettingBase object can hold multiple key-value pairs that define settings.
|
||||
* The settings that are set on a SettingBase are checked against the SettingRegistry to ensure keys are valid.
|
||||
* Different conversion functions are available for settings to increase code clarity and in the future make
|
||||
* unit conversions possible.
|
||||
*/
|
||||
class SettingsBase : public SettingsBaseVirtual
|
||||
{
|
||||
private:
|
||||
std::unordered_map<std::string, std::string> setting_values;
|
||||
public:
|
||||
SettingsBase(); //!< SettingsBase without a parent settings object
|
||||
SettingsBase(SettingsBaseVirtual* parent); //!< construct a SettingsBase with a parent settings object
|
||||
|
||||
/*!
|
||||
* Retrieve the defaults for each extruder train from the machine_extruder_trains settings
|
||||
* and set the general settings to those defaults if they haven't been set yet.
|
||||
*
|
||||
* Only sets those settings which haven't already been set on that level - not looking at its parent (FffProcessor, meshgroup) or children (meshes).
|
||||
*
|
||||
* \param extruder_nr The index of which extruder train in machine_extruder_trains to get the settings from
|
||||
*/
|
||||
void setExtruderTrainDefaults(unsigned int extruder_nr);
|
||||
|
||||
void setSetting(std::string key, std::string value);
|
||||
std::string getSettingString(std::string key); //!< Get a setting from this SettingsBase (or any ancestral SettingsBase)
|
||||
|
||||
std::string getAllLocalSettingsString()
|
||||
{
|
||||
std::stringstream sstream;
|
||||
for (auto pair : setting_values)
|
||||
{
|
||||
if (!pair.second.empty())
|
||||
{
|
||||
sstream << " -s " << pair.first << "=\"" << pair.second << "\"";
|
||||
}
|
||||
}
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
void debugOutputAllLocalSettings()
|
||||
{
|
||||
for (auto pair : setting_values)
|
||||
std::cerr << pair.first << " : " << pair.second << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Base class for an object which passes on settings from another object.
|
||||
* An object which is a subclass of SettingsMessenger can be handled as a SettingsBase;
|
||||
* the difference is that such an object cannot hold any settings, but can only pass on the settings from its parent.
|
||||
*/
|
||||
class SettingsMessenger : public SettingsBaseVirtual
|
||||
{
|
||||
public:
|
||||
SettingsMessenger(SettingsBaseVirtual* parent); //!< construct a SettingsMessenger with a parent settings object
|
||||
|
||||
void setSetting(std::string key, std::string value); //!< Set a setting of the parent SettingsBase to a given value
|
||||
std::string getSettingString(std::string key); //!< Get a setting from the parent SettingsBase (or any further ancestral SettingsBase)
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
#endif//SETTINGS_H
|
||||
|
||||
+36
-35
@@ -1,12 +1,14 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "skin.h"
|
||||
#include "polygonOptimizer.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
#define MIN_AREA_SIZE (INT2MM(extrusionWidth) * INT2MM(extrusionWidth))
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int insetCount, bool avoidOverlappingPerimeters)
|
||||
|
||||
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
generateSkinAreas(layerNr, storage, extrusionWidth, downSkinCount, upSkinCount);
|
||||
|
||||
@@ -14,56 +16,55 @@ void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, i
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
{
|
||||
SliceLayerPart* part = &layer->parts[partNr];
|
||||
generateSkinInsets(part, extrusionWidth, insetCount, avoidOverlappingPerimeters);
|
||||
generateSkinInsets(part, extrusionWidth, insetCount, avoidOverlappingPerimeters_0, avoidOverlappingPerimeters);
|
||||
}
|
||||
}
|
||||
|
||||
void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount)
|
||||
{
|
||||
SliceLayer* layer = &storage.layers[layerNr];
|
||||
SliceLayer& layer = storage.layers[layerNr];
|
||||
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
for(unsigned int partNr = 0; partNr < layer.parts.size(); partNr++)
|
||||
{
|
||||
SliceLayerPart* part = &layer->parts[partNr];
|
||||
SliceLayerPart& part = layer.parts[partNr];
|
||||
|
||||
Polygons upskin = part->insets.back().offset(-extrusionWidth/2);
|
||||
Polygons upskin = part.insets.back().offset(-extrusionWidth/2);
|
||||
Polygons downskin = upskin;
|
||||
|
||||
|
||||
if (static_cast<int>(layerNr - downSkinCount) >= 0)
|
||||
{
|
||||
SliceLayer* layer2 = &storage.layers[layerNr - downSkinCount];
|
||||
for(SliceLayerPart& part2 : layer2->parts)
|
||||
SliceLayer& layer2 = storage.layers[layerNr - downSkinCount];
|
||||
for(SliceLayerPart& part2 : layer2.parts)
|
||||
{
|
||||
if (part->boundaryBox.hit(part2.boundaryBox))
|
||||
if (part.boundaryBox.hit(part2.boundaryBox))
|
||||
downskin = downskin.difference(part2.insets.back());
|
||||
}
|
||||
}
|
||||
if (static_cast<int>(layerNr + upSkinCount) < static_cast<int>(storage.layers.size()))
|
||||
{
|
||||
SliceLayer* layer2 = &storage.layers[layerNr + upSkinCount];
|
||||
for(SliceLayerPart& part2 : layer2->parts)
|
||||
SliceLayer& layer2 = storage.layers[layerNr + upSkinCount];
|
||||
for(SliceLayerPart& part2 : layer2.parts)
|
||||
{
|
||||
if (part->boundaryBox.hit(part2.boundaryBox))
|
||||
if (part.boundaryBox.hit(part2.boundaryBox))
|
||||
upskin = upskin.difference(part2.insets.back());
|
||||
}
|
||||
}
|
||||
|
||||
Polygons skin = upskin.unionPolygons(downskin);
|
||||
|
||||
double minAreaSize = (2 * M_PI * INT2MM(extrusionWidth) * INT2MM(extrusionWidth)) * 0.3; // TODO: hardcoded value!
|
||||
skin.removeSmallAreas(minAreaSize);
|
||||
skin.removeSmallAreas(MIN_AREA_SIZE);
|
||||
|
||||
for (Polygons& skin_area_part : skin.splitIntoParts())
|
||||
for (PolygonsPart& skin_area_part : skin.splitIntoParts())
|
||||
{
|
||||
part->skin_parts.emplace_back();
|
||||
part->skin_parts.back().outline = skin_area_part;
|
||||
part.skin_parts.emplace_back();
|
||||
part.skin_parts.back().outline = skin_area_part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters)
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
if (insetCount == 0)
|
||||
{
|
||||
@@ -77,15 +78,16 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
skin_part.insets.push_back(Polygons());
|
||||
if (i == 0)
|
||||
{
|
||||
offsetSafe(skin_part.outline, - extrusionWidth/2, extrusionWidth, skin_part.insets[0], avoidOverlappingPerimeters);
|
||||
PolygonUtils::offsetSafe(skin_part.outline, - extrusionWidth/2, extrusionWidth, skin_part.insets[0], avoidOverlappingPerimeters_0);
|
||||
Polygons in_between = skin_part.outline.difference(skin_part.insets[0].offset(extrusionWidth/2));
|
||||
skin_part.perimeterGaps.add(in_between);
|
||||
} else
|
||||
{
|
||||
offsetExtrusionWidth(skin_part.insets[i-1], true, extrusionWidth, skin_part.insets[i], &skin_part.perimeterGaps, avoidOverlappingPerimeters);
|
||||
PolygonUtils::offsetExtrusionWidth(skin_part.insets[i-1], true, extrusionWidth, skin_part.insets[i], &skin_part.perimeterGaps, avoidOverlappingPerimeters);
|
||||
}
|
||||
|
||||
optimizePolygons(skin_part.insets[i]);
|
||||
|
||||
// optimize polygons: remove unnnecesary verts
|
||||
skin_part.insets[i].simplify();
|
||||
if (skin_part.insets[i].size() < 1)
|
||||
{
|
||||
skin_part.insets.pop_back();
|
||||
@@ -95,13 +97,13 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
}
|
||||
}
|
||||
|
||||
void generateSparse(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int infill_skin_overlap)
|
||||
void generateInfill(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int infill_skin_overlap)
|
||||
{
|
||||
SliceLayer& layer = storage.layers[layerNr];
|
||||
|
||||
for(SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
Polygons sparse = part.insets.back().offset(-extrusionWidth / 2 - infill_skin_overlap);
|
||||
Polygons infill = part.insets.back().offset(-extrusionWidth / 2 - infill_skin_overlap);
|
||||
|
||||
for(SliceLayerPart& part2 : layer.parts)
|
||||
{
|
||||
@@ -109,17 +111,17 @@ void generateSparse(int layerNr, SliceMeshStorage& storage, int extrusionWidth,
|
||||
{
|
||||
for(SkinPart& skin_part : part2.skin_parts)
|
||||
{
|
||||
sparse = sparse.difference(skin_part.outline);
|
||||
infill = infill.difference(skin_part.outline);
|
||||
}
|
||||
}
|
||||
}
|
||||
sparse.removeSmallAreas(3.0);//(2 * M_PI * INT2MM(config.extrusionWidth) * INT2MM(config.extrusionWidth)) * 3;
|
||||
infill.removeSmallAreas(MIN_AREA_SIZE);
|
||||
|
||||
part.sparse_outline.push_back(sparse.offset(infill_skin_overlap));
|
||||
part.infill_area.push_back(infill.offset(infill_skin_overlap));
|
||||
}
|
||||
}
|
||||
|
||||
void combineSparseLayers(int layerNr, SliceMeshStorage& storage, int amount)
|
||||
void combineInfillLayers(int layerNr, SliceMeshStorage& storage, int amount)
|
||||
{
|
||||
SliceLayer* layer = &storage.layers[layerNr];
|
||||
|
||||
@@ -136,14 +138,14 @@ void combineSparseLayers(int layerNr, SliceMeshStorage& storage, int amount)
|
||||
{
|
||||
if (part.boundaryBox.hit(part2.boundaryBox))
|
||||
{
|
||||
Polygons intersection = part.sparse_outline[n - 1].intersection(part2.sparse_outline[0]).offset(-200).offset(200);
|
||||
Polygons intersection = part.infill_area[n - 1].intersection(part2.infill_area[0]).offset(-200).offset(200);
|
||||
result.add(intersection);
|
||||
part.sparse_outline[n - 1] = part.sparse_outline[n - 1].difference(intersection);
|
||||
part2.sparse_outline[0] = part2.sparse_outline[0].difference(intersection);
|
||||
part.infill_area[n - 1] = part.infill_area[n - 1].difference(intersection);
|
||||
part2.infill_area[0] = part2.infill_area[0].difference(intersection);
|
||||
}
|
||||
}
|
||||
|
||||
part.sparse_outline.push_back(result);
|
||||
part.infill_area.push_back(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,8 +178,7 @@ void generatePerimeterGaps(int layer_nr, SliceMeshStorage& storage, int extrusio
|
||||
}
|
||||
part.perimeterGaps = part.perimeterGaps.intersection(outlines_above.xorPolygons(outlines_below));
|
||||
}
|
||||
double minAreaSize = (2 * M_PI * INT2MM(extrusionWidth) * INT2MM(extrusionWidth)) * 0.3; // TODO: hardcoded value!
|
||||
part.perimeterGaps.removeSmallAreas(minAreaSize);
|
||||
part.perimeterGaps.removeSmallAreas(MIN_AREA_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -27,9 +27,10 @@ void generatePerimeterGaps(int layerNr, SliceMeshStorage& storage, int extrusion
|
||||
* \param downSkinCount The number of layers of bottom skin
|
||||
* \param upSkinCount The number of layers of top skin
|
||||
* \param insetCount The number of perimeters to surround the skin
|
||||
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p storage)
|
||||
*/
|
||||
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int insetCount, bool avoidOverlappingPerimeters);
|
||||
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
|
||||
/*!
|
||||
* Generate the skin areas (outlines)
|
||||
@@ -49,9 +50,10 @@ void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidt
|
||||
* \param part The part where the skin outline information (input) is stored and where the skin insets (output) are stored.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param insetCount The number of perimeters to surround the skin
|
||||
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p storage)
|
||||
*/
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters);
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
|
||||
/*!
|
||||
* Generate Infill
|
||||
@@ -60,8 +62,8 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
* \param extrusionWidth width of the wall lines
|
||||
* \param infill_skin_overlap overlap distance between infill and skin
|
||||
*/
|
||||
void generateSparse(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int infill_skin_overlap);
|
||||
void combineSparseLayers(int layerNr, SliceMeshStorage& storage, int amount);
|
||||
void generateInfill(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int infill_skin_overlap);
|
||||
void combineInfillLayers(int layerNr, SliceMeshStorage& storage, int amount);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
+58
-65
@@ -2,94 +2,87 @@
|
||||
#include "skirt.h"
|
||||
#include "support.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void generateSkirt(SliceDataStorage& storage, int distance, int extrusionWidth, int count, int minLength)
|
||||
void generateSkirt(SliceDataStorage& storage, int distance, int count, int minLength)
|
||||
{
|
||||
if (count == 0) return;
|
||||
|
||||
bool externalOnly = (distance > 0);
|
||||
bool externalOnly = (distance > 0); // whether to include holes or not
|
||||
|
||||
Polygons support;
|
||||
if (storage.support.generated)
|
||||
support = storage.support.supportAreasPerLayer[0];
|
||||
{ // get support polygons
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (mesh.layers.size() < 1) continue;
|
||||
SliceLayer* layer = &mesh.layers[0];
|
||||
for(unsigned int i=0; i<layer->parts.size(); i++)
|
||||
support = support.difference(layer->parts[i].outline);
|
||||
}
|
||||
|
||||
// expand and contract to smooth the final polygon
|
||||
if (count == 1 && distance > 0)
|
||||
{
|
||||
int dist = extrusionWidth * 5;
|
||||
support = support.offset(dist).offset(-dist);
|
||||
}
|
||||
}
|
||||
int primary_extruder = 0; // TODO allow for other extruder to be primary
|
||||
int primary_extrusion_width = storage.meshgroup->getExtruderTrain(primary_extruder)->getSettingInMicrons("skirt_line_width");
|
||||
|
||||
int overshoot = 0; // distance by which to expand and contract the skirt to approximate the convex hull of the first layer
|
||||
if (count == 1 && distance > 0)
|
||||
{
|
||||
overshoot = 100000; // 10 cm
|
||||
}
|
||||
Polygons& skirt_primary_extruder = storage.skirt[primary_extruder];
|
||||
|
||||
bool get_convex_hull = count == 1 && distance > 0;
|
||||
|
||||
Polygons first_layer_outline = storage.getLayerOutlines(0, true, externalOnly);
|
||||
|
||||
std::vector<Polygons> skirts;
|
||||
for(int skirtNr=0; skirtNr<count;skirtNr++)
|
||||
{
|
||||
int offsetDistance = distance + extrusionWidth * skirtNr + extrusionWidth / 2 + overshoot;
|
||||
int offsetDistance = distance + primary_extrusion_width * skirtNr + primary_extrusion_width / 2;
|
||||
|
||||
Polygons skirtPolygons(storage.wipeTower.offset(offsetDistance));
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (mesh.layers.size() < 1) continue;
|
||||
SliceLayer* layer = &mesh.layers[0];
|
||||
for(unsigned int i=0; i<layer->parts.size(); i++)
|
||||
{
|
||||
if (externalOnly)
|
||||
{
|
||||
Polygons p;
|
||||
p.add(layer->parts[i].outline[0]);
|
||||
skirtPolygons = skirtPolygons.unionPolygons(p.offset(offsetDistance, ClipperLib::jtRound));
|
||||
}
|
||||
else
|
||||
{
|
||||
skirtPolygons = skirtPolygons.unionPolygons(layer->parts[i].outline.offset(offsetDistance, ClipperLib::jtRound));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skirtPolygons = skirtPolygons.unionPolygons(support.offset(offsetDistance, ClipperLib::jtRound));
|
||||
skirts.emplace_back(first_layer_outline.offset(offsetDistance, ClipperLib::jtRound));
|
||||
Polygons& skirt_polygons = skirts.back();
|
||||
|
||||
//Remove small inner skirt holes. Holes have a negative area, remove anything smaller then 100x extrusion "area"
|
||||
for(unsigned int n=0; n<skirtPolygons.size(); n++)
|
||||
for(unsigned int n=0; n<skirt_polygons.size(); n++)
|
||||
{
|
||||
double area = skirtPolygons[n].area();
|
||||
if (area < 0 && area > -extrusionWidth * extrusionWidth * 100)
|
||||
skirtPolygons.remove(n--);
|
||||
double area = skirt_polygons[n].area();
|
||||
if (area < 0 && area > -primary_extrusion_width * primary_extrusion_width * 100)
|
||||
skirt_polygons.remove(n--);
|
||||
}
|
||||
|
||||
if (get_convex_hull)
|
||||
{
|
||||
skirt_polygons = skirt_polygons.convexHull();
|
||||
}
|
||||
|
||||
storage.skirt.add(skirtPolygons);
|
||||
skirt_primary_extruder.add(skirt_polygons);
|
||||
|
||||
int lenght = storage.skirt.polygonLength();
|
||||
if (skirtNr + 1 >= count && lenght > 0 && lenght < minLength) // make brim have more lines when total length is too small
|
||||
int length = skirt_primary_extruder.polygonLength();
|
||||
if (skirtNr + 1 >= count && length > 0 && length < minLength) // make brim have more lines when total length is too small
|
||||
count++;
|
||||
}
|
||||
|
||||
|
||||
//Add a skirt under the wipetower to make it stick better.
|
||||
Polygons wipe_tower = storage.wipeTower.offset(-extrusionWidth / 2);
|
||||
while(wipe_tower.size() > 0)
|
||||
{
|
||||
storage.skirt.add(wipe_tower);
|
||||
wipe_tower = wipe_tower.offset(-extrusionWidth);
|
||||
if (false) // the code below is for the old prime tower
|
||||
{ //Add a skirt UNDER the prime tower to make it stick better.
|
||||
Polygons prime_tower = storage.primeTower.ground_poly.offset(-primary_extrusion_width / 2);
|
||||
std::queue<Polygons> prime_tower_insets;
|
||||
while(prime_tower.size() > 0)
|
||||
{
|
||||
prime_tower_insets.emplace(prime_tower);
|
||||
prime_tower = prime_tower.offset(-primary_extrusion_width);
|
||||
}
|
||||
while (!prime_tower_insets.empty())
|
||||
{
|
||||
Polygons& inset = prime_tower_insets.back();
|
||||
skirt_primary_extruder.add(inset);
|
||||
prime_tower_insets.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if (overshoot > 0)
|
||||
{
|
||||
storage.skirt = storage.skirt.offset(-overshoot, ClipperLib::jtRound);
|
||||
|
||||
{ // process other extruders' brim/skirt (as one brim line around the old brim)
|
||||
int offset_distance = 0;
|
||||
int last_width = primary_extrusion_width;
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
if (extruder == primary_extruder) { continue; }
|
||||
int width = storage.meshgroup->getExtruderTrain(extruder)->getSettingInMicrons("skirt_line_width");
|
||||
offset_distance += last_width / 2 + width/2;
|
||||
last_width = width;
|
||||
while (storage.skirt[extruder].polygonLength() < minLength)
|
||||
{
|
||||
storage.skirt[extruder].add(skirts.back().offset(offset_distance, ClipperLib::jtRound));
|
||||
offset_distance += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -12,11 +12,10 @@ namespace cura
|
||||
*
|
||||
* \param storage Storage containing the parts at the first layer
|
||||
* \param distance The distance of the first outset from the parts at the first layer
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param count Number of outsets / brim lines
|
||||
* \param minLength The minimum length the skirt should have (enforced by taking more outsets)
|
||||
*/
|
||||
void generateSkirt(SliceDataStorage& storage, int distance, int extrusionWidth, int count, int minLength);
|
||||
void generateSkirt(SliceDataStorage& storage, int distance, int count, int minLength);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
Polygons SliceLayer::getOutlines(bool external_polys_only)
|
||||
{
|
||||
Polygons ret;
|
||||
getOutlines(ret, external_polys_only);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SliceLayer::getOutlines(Polygons& result, bool external_polys_only)
|
||||
{
|
||||
for (SliceLayerPart& part : parts)
|
||||
{
|
||||
if (external_polys_only)
|
||||
{
|
||||
result.add(part.outline.outerPolygon());
|
||||
}
|
||||
else
|
||||
{
|
||||
result.add(part.outline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Polygons SliceDataStorage::getLayerOutlines(unsigned int layer_nr, bool include_helper_parts, bool external_polys_only)
|
||||
{
|
||||
Polygons total;
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
layer.getOutlines(total, external_polys_only);
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
{
|
||||
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
|
||||
}
|
||||
}
|
||||
if (include_helper_parts)
|
||||
{
|
||||
if (support.generated)
|
||||
{
|
||||
total.add(support.supportLayers[layer_nr].supportAreas);
|
||||
total.add(support.supportLayers[layer_nr].roofs);
|
||||
}
|
||||
total.add(primeTower.ground_poly);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace cura
|
||||
+91
-28
@@ -6,35 +6,36 @@
|
||||
#include "utils/polygon.h"
|
||||
#include "mesh.h"
|
||||
#include "gcodePlanner.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "PrimeTower.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
/*!
|
||||
* A SkinPart is a connected area designated as top and/or bottom skin.
|
||||
* Surrounding each non-bridged skin area with an outline may result in better top skins.
|
||||
* It's filled during fffProcessor.processSliceData(.) and used in fffProcessor.writeGCode(.) to generate the final gcode.
|
||||
* It's filled during FffProcessor.processSliceData(.) and used in FffProcessor.writeGCode(.) to generate the final gcode.
|
||||
*/
|
||||
class SkinPart
|
||||
{
|
||||
public:
|
||||
Polygons outline; //!< The skinOutline is the area which needs to be 100% filled to generate a proper top&bottom filling. It's filled by the "skin" module.
|
||||
PolygonsPart outline; //!< The skinOutline is the area which needs to be 100% filled to generate a proper top&bottom filling. It's filled by the "skin" module.
|
||||
std::vector<Polygons> insets; //!< The skin can have perimeters so that the skin lines always start at a perimeter instead of in the middle of an infill cell.
|
||||
Polygons perimeterGaps; //!< The gaps introduced by avoidOverlappingPerimeters which would otherwise be overlapping perimeters.
|
||||
};
|
||||
/*!
|
||||
The SliceLayerPart is a single enclosed printable area for a single layer. (Also known as islands)
|
||||
It's filled during the fffProcessor.processSliceData(.), where each step uses data from the previous steps.
|
||||
Finally it's used in the fffProcessor.writeGCode(.) to generate the final gcode.
|
||||
It's filled during the FffProcessor.processSliceData(.), where each step uses data from the previous steps.
|
||||
Finally it's used in the FffProcessor.writeGCode(.) to generate the final gcode.
|
||||
*/
|
||||
class SliceLayerPart
|
||||
{
|
||||
public:
|
||||
AABB boundaryBox; //!< The boundaryBox is an axis-aligned bounardy box which is used to quickly check for possible collision between different parts on different layers. It's an optimalization used during skin calculations.
|
||||
Polygons outline; //!< The outline is the first member that is filled, and it's filled with polygons that match a cross section of the 3D model. The first polygon is the outer boundary polygon and the rest are holes.
|
||||
Polygons combBoundery; //!< The combBoundery is generated from the online. It's the area in which the nozzle tries to stay during traveling.
|
||||
PolygonsPart outline; //!< The outline is the first member that is filled, and it's filled with polygons that match a cross section of the 3D model. The first polygon is the outer boundary polygon and the rest are holes.
|
||||
std::vector<Polygons> insets; //!< The insets are generated with: an offset of (index * line_width + line_width/2) compared to the outline. The insets are also known as perimeters, and printed inside out.
|
||||
std::vector<SkinPart> skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets.
|
||||
std::vector<Polygons> sparse_outline; //!< The sparse_outline are the areas which need to be filled with sparse (0-99%) infill. The sparse_outline is an array to support thicker layers of sparse infill. sparse_outline[n] is sparse outline of (n+1) layers thick.
|
||||
std::vector<Polygons> infill_area; //!< The infill_area are the areas which need to be filled with sparse (0-99%) infill. The infill_area is an array to support thicker layers of sparse infill. infill_area[n] is infill_area of (n+1) layers thick.
|
||||
Polygons perimeterGaps; //!< The gaps introduced by avoidOverlappingPerimeters which would otherwise be overlapping perimeters.
|
||||
};
|
||||
|
||||
@@ -48,63 +49,125 @@ public:
|
||||
// TODO: remove this /\ unused member!
|
||||
int printZ; //!< The height at which this layer needs to be printed. Can differ from sliceZ due to the raft.
|
||||
std::vector<SliceLayerPart> parts; //!< An array of LayerParts which contain the actual data. The parts are printed one at a time to minimize travel outside of the 3D model.
|
||||
Polygons openLines; //!< A list of lines which were never hooked up into a 2D polygon. (Currently unused in normal operation)
|
||||
Polygons openPolyLines; //!< A list of lines which were never hooked up into a 2D polygon. (Currently unused in normal operation)
|
||||
|
||||
Polygons getOutlines(bool external_polys_only = false);
|
||||
void getOutlines(Polygons& result, bool external_polys_only = false);
|
||||
};
|
||||
|
||||
/******************/
|
||||
class SupportLayer
|
||||
{
|
||||
public:
|
||||
Polygons supportAreas; //!< normal support areas
|
||||
Polygons roofs; //!< the support areas which are to be printed as denser roofs. Note that the roof areas and support areas are mutually exclusive.
|
||||
};
|
||||
|
||||
class SupportStorage
|
||||
{
|
||||
public:
|
||||
bool generated; //!< whether generateSupportGrid(.) has completed (successfully)
|
||||
|
||||
std::vector<Polygons> supportAreasPerLayer;
|
||||
int layer_nr_max_filled_layer; //!< the layer number of the uppermost layer with content
|
||||
|
||||
std::vector<SupportLayer> supportLayers;
|
||||
|
||||
SupportStorage(){}
|
||||
~SupportStorage(){supportAreasPerLayer.clear(); }
|
||||
SupportStorage() : generated(false), layer_nr_max_filled_layer(-1) { }
|
||||
~SupportStorage(){ supportLayers.clear(); }
|
||||
};
|
||||
/******************/
|
||||
|
||||
class SliceMeshStorage
|
||||
class SliceMeshStorage : public SettingsMessenger // passes on settings from a Mesh object
|
||||
{
|
||||
public:
|
||||
SettingsBase* settings;
|
||||
std::vector<SliceLayer> layers;
|
||||
|
||||
int layer_nr_max_filled_layer; //!< the layer number of the uppermost layer with content
|
||||
|
||||
RetractionConfig retraction_config;
|
||||
GCodePathConfig inset0_config;
|
||||
GCodePathConfig insetX_config;
|
||||
GCodePathConfig skin_config;
|
||||
GCodePathConfig infill_config[MAX_SPARSE_COMBINE];
|
||||
std::vector<GCodePathConfig> infill_config;
|
||||
|
||||
SliceMeshStorage(SettingsBase* settings)
|
||||
: settings(settings), inset0_config(&retraction_config, "WALL-OUTER"), insetX_config(&retraction_config, "WALL-INNER"), skin_config(&retraction_config, "SKIN")
|
||||
SliceMeshStorage(SettingsBaseVirtual* settings)
|
||||
: SettingsMessenger(settings), layer_nr_max_filled_layer(0), inset0_config(&retraction_config, "WALL-OUTER"), insetX_config(&retraction_config, "WALL-INNER"), skin_config(&retraction_config, "SKIN")
|
||||
{
|
||||
for(int n=0; n<MAX_SPARSE_COMBINE; n++)
|
||||
infill_config[n] = GCodePathConfig(&retraction_config, "FILL");
|
||||
infill_config.reserve(MAX_INFILL_COMBINE);
|
||||
for(int n=0; n<MAX_INFILL_COMBINE; n++)
|
||||
infill_config.emplace_back(&retraction_config, "FILL");
|
||||
}
|
||||
};
|
||||
|
||||
class SliceDataStorage
|
||||
class SliceDataStorage : public SettingsMessenger
|
||||
{
|
||||
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.)
|
||||
|
||||
Point3 model_size, model_min, model_max;
|
||||
Polygons skirt;
|
||||
Polygons raftOutline; //Storage for the outline of the raft. Will be filled with lines when the GCode is generated.
|
||||
std::vector<Polygons> oozeShield; //oozeShield per layer
|
||||
std::vector<SliceMeshStorage> meshes;
|
||||
|
||||
RetractionConfig retraction_config;
|
||||
GCodePathConfig skirt_config;
|
||||
|
||||
std::vector<RetractionConfig> retraction_config_per_extruder; //!< used for support, skirt, etc.
|
||||
RetractionConfig retraction_config; //!< The retraction config used as fallback when getting the per_extruder_config or the mesh config was impossible (for travelConfig)
|
||||
|
||||
std::vector<GCodePathConfig> skirt_config; //!< config for skirt per extruder
|
||||
std::vector<CoastingConfig> coasting_config; //!< coasting config per extruder
|
||||
|
||||
GCodePathConfig support_config;
|
||||
GCodePathConfig support_roof_config;
|
||||
|
||||
SupportStorage support;
|
||||
Polygons wipeTower;
|
||||
|
||||
Polygons skirt[MAX_EXTRUDERS]; //!< Skirt polygons per extruder, ordered from inner to outer polygons
|
||||
Polygons raftOutline; //Storage for the outline of the raft. Will be filled with lines when the GCode is generated.
|
||||
|
||||
int max_object_height_second_to_last_extruder; //!< Used in multi-extrusion: the layer number beyond which all models are printed with the same extruder
|
||||
PrimeTower primeTower;
|
||||
|
||||
std::vector<Polygons> oozeShield; //oozeShield per layer
|
||||
Polygons draft_protection_shield; //!< The polygons for a heightened skirt which protects from warping by gusts of wind and acts as a heated chamber.
|
||||
Point wipePoint;
|
||||
|
||||
SliceDataStorage()
|
||||
: skirt_config(&retraction_config, "SKIRT"), support_config(&retraction_config, "SUPPORT")
|
||||
std::vector<RetractionConfig> initializeRetractionConfigs()
|
||||
{
|
||||
std::vector<RetractionConfig> ret;
|
||||
ret.resize(meshgroup->getExtruderCount()); // initializes with constructor RetractionConfig()
|
||||
return ret;
|
||||
}
|
||||
std::vector<GCodePathConfig> initializeSkirtConfigs()
|
||||
{
|
||||
std::vector<GCodePathConfig> ret;
|
||||
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
RetractionConfig* extruder_retraction_config = &retraction_config_per_extruder[extruder];
|
||||
skirt_config.emplace_back(extruder_retraction_config, "SKIRT");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
SliceDataStorage(MeshGroup* meshgroup)
|
||||
: SettingsMessenger(meshgroup)
|
||||
, meshgroup(meshgroup)
|
||||
, retraction_config_per_extruder(initializeRetractionConfigs())
|
||||
, skirt_config(initializeSkirtConfigs())
|
||||
, support_config(&retraction_config_per_extruder[meshgroup->getSettingAsIndex("support_extruder_nr")], "SUPPORT")
|
||||
, support_roof_config(&retraction_config_per_extruder[meshgroup->getSettingAsIndex("support_roof_extruder_nr")], "SKIN")
|
||||
, max_object_height_second_to_last_extruder(-1)
|
||||
// , primeTower()
|
||||
{
|
||||
}
|
||||
|
||||
~SliceDataStorage()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get all outlines within a given layer.
|
||||
*
|
||||
* \param layer_nr the index of the layer for which to get the outlines
|
||||
* \param include_helper_parts whether to include support and prime tower
|
||||
* \param external_polys_only whether to disregard all hole polygons
|
||||
*/
|
||||
Polygons getLayerOutlines(unsigned int layer_nr, bool include_helper_parts, bool external_polys_only = false);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+29
-22
@@ -5,7 +5,7 @@
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
#include "slicer.h"
|
||||
#include "polygonOptimizer.h"
|
||||
#include "debug.h" // TODO remove
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -13,6 +13,7 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
{
|
||||
Polygons openPolygonList;
|
||||
|
||||
// connect line segments
|
||||
for(unsigned int startSegment=0; startSegment < segmentList.size(); startSegment++)
|
||||
{
|
||||
if (segmentList[startSegment].addedToPolygon)
|
||||
@@ -30,20 +31,22 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
Point p0 = segmentList[segmentIndex].end;
|
||||
poly.add(p0);
|
||||
int nextIndex = -1;
|
||||
MeshFace* face = &mesh->faces[segmentList[segmentIndex].faceIndex];
|
||||
const MeshFace& face = mesh->faces[segmentList[segmentIndex].faceIndex];
|
||||
for(unsigned int i=0;i<3;i++)
|
||||
{
|
||||
if (face->connected_face_index[i] > -1 && face_idx_to_segment_index.find(face->connected_face_index[i]) != face_idx_to_segment_index.end())
|
||||
decltype(face_idx_to_segment_index.begin()) it;
|
||||
if (face.connected_face_index[i] > -1 && (it = face_idx_to_segment_index.find(face.connected_face_index[i])) != face_idx_to_segment_index.end())
|
||||
{
|
||||
Point p1 = segmentList[face_idx_to_segment_index[face->connected_face_index[i]]].start;
|
||||
int index = (*it).second;
|
||||
Point p1 = segmentList[index].start;
|
||||
Point diff = p0 - p1;
|
||||
if (shorterThen(diff, MM2INT(0.01)))
|
||||
{
|
||||
if (face_idx_to_segment_index[face->connected_face_index[i]] == static_cast<int>(startSegment))
|
||||
if (index == static_cast<int>(startSegment))
|
||||
canClose = true;
|
||||
if (segmentList[face_idx_to_segment_index[face->connected_face_index[i]]].addedToPolygon)
|
||||
if (segmentList[index].addedToPolygon)
|
||||
continue;
|
||||
nextIndex = face_idx_to_segment_index[face->connected_face_index[i]];
|
||||
nextIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +61,8 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
}
|
||||
//Clear the segmentList to save memory, it is no longer needed after this point.
|
||||
segmentList.clear();
|
||||
|
||||
// TODO: (?) for mesh surface mode: connect open polygons. Maybe the above algorithm can create two open polygons which are actually connected when the starting segment is in the middle between the two open polygons.
|
||||
|
||||
//Connecting polygons that are not closed yet, as models are not always perfect manifold we need to join some stuff up to get proper polygons
|
||||
//First link up polygon ends that are within 2 microns.
|
||||
@@ -88,9 +93,11 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
}
|
||||
}
|
||||
|
||||
//Next link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time.
|
||||
while(1)
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::NORMAL)
|
||||
{
|
||||
//Next link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time.
|
||||
while(1)
|
||||
{
|
||||
int64_t bestScore = MM2INT(10.0) * MM2INT(10.0);
|
||||
unsigned int bestA = -1;
|
||||
unsigned int bestB = -1;
|
||||
@@ -154,7 +161,7 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (extensive_stitching)
|
||||
{
|
||||
//For extensive stitching find 2 open polygons that are touching 2 closed polygons.
|
||||
@@ -166,7 +173,7 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
{
|
||||
unsigned int bestA = -1;
|
||||
unsigned int bestB = -1;
|
||||
gapCloserResult bestResult;
|
||||
GapCloserResult bestResult;
|
||||
bestResult.len = POINT_MAX;
|
||||
bestResult.polygonIdx = -1;
|
||||
bestResult.pointIdxA = -1;
|
||||
@@ -177,7 +184,7 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
if (openPolygonList[i].size() < 1) continue;
|
||||
|
||||
{
|
||||
gapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[i][openPolygonList[i].size()-1]);
|
||||
GapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[i][openPolygonList[i].size()-1]);
|
||||
if (res.len > 0 && res.len < bestResult.len)
|
||||
{
|
||||
bestA = i;
|
||||
@@ -190,7 +197,7 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
{
|
||||
if (openPolygonList[j].size() < 1 || i == j) continue;
|
||||
|
||||
gapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[j][openPolygonList[j].size()-1]);
|
||||
GapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[j][openPolygonList[j].size()-1]);
|
||||
if (res.len > 0 && res.len < bestResult.len)
|
||||
{
|
||||
bestA = i;
|
||||
@@ -275,7 +282,7 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
for(unsigned int i=0;i<openPolygonList.size();i++)
|
||||
{
|
||||
if (openPolygonList[i].size() > 0)
|
||||
openPolygons.newPoly() = openPolygonList[i];
|
||||
openPolylines.add(openPolygonList[i]);
|
||||
}
|
||||
|
||||
//Remove all the tiny polygons, or polygons that are not closed. As they do not contribute to the actual print.
|
||||
@@ -298,9 +305,9 @@ void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive
|
||||
}
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
optimizePolygons(polygonList);
|
||||
polygonList.simplify();
|
||||
|
||||
polygonList = polygonList.removeDegenerateVerts(); // remove verts connected to overlapping line segments
|
||||
polygonList.removeDegenerateVerts(); // remove verts connected to overlapping line segments
|
||||
|
||||
int xy_offset = mesh->getSettingInMicrons("xy_offset");
|
||||
if (xy_offset != 0)
|
||||
@@ -321,9 +328,9 @@ Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool kee
|
||||
layers[layer_nr].z = initial + thickness * layer_nr;
|
||||
}
|
||||
|
||||
for(unsigned int i=0; i<mesh->faces.size(); i++)
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < mesh->faces.size(); mesh_idx++)
|
||||
{
|
||||
MeshFace& face = mesh->faces[i];
|
||||
MeshFace& face = mesh->faces[mesh_idx];
|
||||
Point3 p0 = mesh->vertices[face.vertex_index[0]].p;
|
||||
Point3 p1 = mesh->vertices[face.vertex_index[1]].p;
|
||||
Point3 p2 = mesh->vertices[face.vertex_index[2]].p;
|
||||
@@ -333,8 +340,8 @@ Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool kee
|
||||
if (p2.z < minZ) minZ = p2.z;
|
||||
if (p1.z > maxZ) maxZ = p1.z;
|
||||
if (p2.z > maxZ) maxZ = p2.z;
|
||||
|
||||
for(int32_t layer_nr = (minZ - initial) / thickness; layer_nr <= (maxZ - initial) / thickness; layer_nr++)
|
||||
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;
|
||||
@@ -361,8 +368,8 @@ Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool kee
|
||||
// on the slice would create two segments
|
||||
continue;
|
||||
}
|
||||
layers[layer_nr].face_idx_to_segment_index[i] = layers[layer_nr].segmentList.size();
|
||||
s.faceIndex = i;
|
||||
layers[layer_nr].face_idx_to_segment_index.insert(std::make_pair(mesh_idx, layers[layer_nr].segmentList.size()));
|
||||
s.faceIndex = mesh_idx;
|
||||
s.addedToPolygon = false;
|
||||
layers[layer_nr].segmentList.push_back(s);
|
||||
}
|
||||
|
||||
+10
-10
@@ -18,7 +18,7 @@ public:
|
||||
bool addedToPolygon;
|
||||
};
|
||||
|
||||
class closePolygonResult
|
||||
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:
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
int polygonIdx;
|
||||
unsigned int pointIdx;
|
||||
};
|
||||
class gapCloserResult
|
||||
class GapCloserResult
|
||||
{
|
||||
public:
|
||||
int64_t len;
|
||||
@@ -40,20 +40,20 @@ class SlicerLayer
|
||||
{
|
||||
public:
|
||||
std::vector<SlicerSegment> segmentList;
|
||||
std::map<int, int> face_idx_to_segment_index;
|
||||
std::unordered_map<int, int> face_idx_to_segment_index; // topology
|
||||
|
||||
int z;
|
||||
Polygons polygonList;
|
||||
Polygons openPolygons;
|
||||
Polygons openPolylines;
|
||||
|
||||
void makePolygons(Mesh* mesh, bool keepNoneClosed, bool extensiveStitching);
|
||||
|
||||
private:
|
||||
gapCloserResult findPolygonGapCloser(Point ip0, Point ip1)
|
||||
GapCloserResult findPolygonGapCloser(Point ip0, Point ip1)
|
||||
{
|
||||
gapCloserResult ret;
|
||||
closePolygonResult c1 = findPolygonPointClosestTo(ip0);
|
||||
closePolygonResult c2 = findPolygonPointClosestTo(ip1);
|
||||
GapCloserResult ret;
|
||||
ClosePolygonResult c1 = findPolygonPointClosestTo(ip0);
|
||||
ClosePolygonResult c2 = findPolygonPointClosestTo(ip1);
|
||||
if (c1.polygonIdx < 0 || c1.polygonIdx != c2.polygonIdx)
|
||||
{
|
||||
ret.len = -1;
|
||||
@@ -102,9 +102,9 @@ private:
|
||||
return ret;
|
||||
}
|
||||
|
||||
closePolygonResult findPolygonPointClosestTo(Point input)
|
||||
ClosePolygonResult findPolygonPointClosestTo(Point input)
|
||||
{
|
||||
closePolygonResult ret;
|
||||
ClosePolygonResult ret;
|
||||
for(unsigned int n=0; n<polygonList.size(); n++)
|
||||
{
|
||||
Point p0 = polygonList[n][polygonList[n].size()-1];
|
||||
|
||||
+197
-105
@@ -3,9 +3,70 @@
|
||||
|
||||
#include <cmath> // sqrt
|
||||
#include <utility> // pair
|
||||
#include "Progress.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
Polygons AreaSupport::join(Polygons& supportLayer_up, Polygons& supportLayer_this, int64_t supportJoinDistance, int64_t smoothing_distance, int min_smoothing_area, bool conical_support, int64_t conical_support_offset, int64_t conical_smallest_breadth)
|
||||
{
|
||||
Polygons joined;
|
||||
if (conical_support)
|
||||
{
|
||||
Polygons insetted = supportLayer_up.offset(-conical_smallest_breadth/2);
|
||||
Polygons small_parts = supportLayer_up.difference(insetted.offset(conical_smallest_breadth/2+20));
|
||||
joined = supportLayer_this.unionPolygons(supportLayer_up.offset(conical_support_offset))
|
||||
.unionPolygons(small_parts);
|
||||
}
|
||||
else
|
||||
{
|
||||
joined = supportLayer_this.unionPolygons(supportLayer_up);
|
||||
}
|
||||
// join different parts
|
||||
if (supportJoinDistance > 0)
|
||||
{
|
||||
joined = joined.offset(supportJoinDistance)
|
||||
.offset(-supportJoinDistance);
|
||||
}
|
||||
if (smoothing_distance > 0)
|
||||
joined = joined.smooth(smoothing_distance, min_smoothing_area);
|
||||
|
||||
return joined;
|
||||
}
|
||||
|
||||
void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count, CommandSocket* commandSocket)
|
||||
{
|
||||
// initialization of supportAreasPerLayer
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count ; layer_idx++)
|
||||
storage.support.supportLayers.emplace_back();
|
||||
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
std::vector<Polygons> supportAreas;
|
||||
supportAreas.resize(layer_count, Polygons());
|
||||
generateSupportAreas(storage, &mesh, layer_count, supportAreas, commandSocket);
|
||||
|
||||
if (mesh.getSettingBoolean("support_roof_enable"))
|
||||
{
|
||||
generateSupportRoofs(storage, supportAreas, layer_count, mesh.getSettingInMicrons("layer_height"), mesh.getSettingInMicrons("support_roof_height"), commandSocket);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count ; layer_idx++)
|
||||
{
|
||||
storage.support.supportLayers[layer_idx].supportAreas.add(supportAreas[layer_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count ; layer_idx++)
|
||||
{
|
||||
storage.support.supportLayers[layer_idx].supportAreas = storage.support.supportLayers[layer_idx].supportAreas.unionPolygons();
|
||||
}
|
||||
|
||||
storage.support.generated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Algorithm:
|
||||
@@ -18,40 +79,43 @@ namespace cura
|
||||
*
|
||||
* for support buildplate only: purge all support not connected to buildplate
|
||||
*/
|
||||
void generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, int layer_count)
|
||||
void AreaSupport::generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, unsigned int layer_count, std::vector<Polygons>& supportAreas, CommandSocket* commandSocket)
|
||||
{
|
||||
// given settings
|
||||
ESupportType support_type = object->settings->getSettingAsSupportType("support_type");
|
||||
ESupportType support_type = object->getSettingAsSupportType("support_type");
|
||||
|
||||
storage.support.generated = false;
|
||||
if (!object->settings->getSettingBoolean("support_enable"))
|
||||
if (!object->getSettingBoolean("support_enable"))
|
||||
return;
|
||||
if (support_type == Support_None)
|
||||
if (support_type == ESupportType::NONE)
|
||||
return;
|
||||
|
||||
double supportAngle = object->settings->getSettingInAngleRadians("support_angle");
|
||||
bool supportOnBuildplateOnly = support_type == Support_PlatformOnly;
|
||||
int supportZDistance = object->settings->getSettingInMicrons("support_z_distance");
|
||||
int supportZDistanceBottom = object->settings->getSettingInMicrons("support_bottom_distance");
|
||||
int supportZDistanceTop = object->settings->getSettingInMicrons("support_top_distance");
|
||||
int supportJoinDistance = object->settings->getSettingInMicrons("support_join_distance");
|
||||
int support_bottom_stair_step_height = object->settings->getSettingInMicrons("support_bottom_stair_step_height");
|
||||
int smoothing_distance = object->settings->getSettingInMicrons("support_area_smoothing");
|
||||
|
||||
int supportTowerDiameter = object->settings->getSettingInMicrons("support_tower_diameter");
|
||||
int supportMinAreaSqrt = object->settings->getSettingInMicrons("support_minimal_diameter");
|
||||
double supportTowerRoofAngle = object->settings->getSettingInAngleRadians("support_tower_roof_angle");
|
||||
double supportAngle = object->getSettingInAngleRadians("support_angle");
|
||||
bool supportOnBuildplateOnly = support_type == ESupportType::PLATFORM_ONLY;
|
||||
int supportZDistance = object->getSettingInMicrons("support_z_distance");
|
||||
int supportZDistanceBottom = object->getSettingInMicrons("support_bottom_distance");
|
||||
int supportZDistanceTop = object->getSettingInMicrons("support_top_distance");
|
||||
int join_distance = object->getSettingInMicrons("support_join_distance");
|
||||
int support_bottom_stair_step_height = object->getSettingInMicrons("support_bottom_stair_step_height");
|
||||
int smoothing_distance = object->getSettingInMicrons("support_area_smoothing");
|
||||
|
||||
int extension_offset = object->getSettingInMicrons("support_offset");
|
||||
|
||||
int supportTowerDiameter = object->getSettingInMicrons("support_tower_diameter");
|
||||
int supportMinAreaSqrt = object->getSettingInMicrons("support_minimal_diameter");
|
||||
double supportTowerRoofAngle = object->getSettingInAngleRadians("support_tower_roof_angle");
|
||||
|
||||
//std::cerr <<" towerDiameter=" << towerDiameter <<", supportMinAreaSqrt=" << supportMinAreaSqrt << std::endl;
|
||||
|
||||
int min_smoothing_area = 100*100; // minimal area for which to perform smoothing
|
||||
int z_layer_distance_tower = 1; // start tower directly below overhang point
|
||||
|
||||
int layerThickness = object->settings->getSettingInMicrons("layer_height");
|
||||
int extrusionWidth = object->settings->getSettingInMicrons("wall_line_width_x"); // TODO check for layer0extrusionWidth!
|
||||
int supportXYDistance = object->settings->getSettingInMicrons("support_xy_distance") + extrusionWidth / 2;
|
||||
int layerThickness = object->getSettingInMicrons("layer_height");
|
||||
int extrusionWidth = object->getSettingInMicrons("support_line_width");
|
||||
int supportXYDistance = object->getSettingInMicrons("support_xy_distance") + extrusionWidth / 2;
|
||||
|
||||
|
||||
bool conical_support = object->getSettingBoolean("support_conical_enabled");
|
||||
double conical_support_angle = object->getSettingInAngleRadians("support_conical_angle");
|
||||
int64_t conical_smallest_breadth = object->getSettingInMicrons("support_conical_min_width");
|
||||
|
||||
// derived settings:
|
||||
|
||||
@@ -62,12 +126,22 @@ void generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, i
|
||||
int supportLayerThickness = layerThickness;
|
||||
|
||||
int layerZdistanceTop = supportZDistanceTop / supportLayerThickness + 1; // support must always be 1 layer below overhang
|
||||
int layerZdistanceBottom = supportZDistanceBottom / supportLayerThickness;
|
||||
unsigned int layerZdistanceBottom = std::max(0, supportZDistanceBottom / supportLayerThickness);
|
||||
|
||||
double tanAngle = tan(supportAngle) - 0.01; // the XY-component of the supportAngle
|
||||
int maxDistFromLowerLayer = tanAngle * supportLayerThickness; // max dist which can be bridged
|
||||
|
||||
int support_layer_count = layer_count;
|
||||
int64_t conical_support_offset;
|
||||
if (conical_support_angle > 0)
|
||||
{
|
||||
conical_support_offset = (tan(conical_support_angle) - 0.01) * supportLayerThickness;
|
||||
}
|
||||
else
|
||||
{
|
||||
conical_support_offset = -(tan(-conical_support_angle) - 0.01) * supportLayerThickness;
|
||||
}
|
||||
|
||||
unsigned int support_layer_count = layer_count;
|
||||
|
||||
double tanTowerRoofAngle = tan(supportTowerRoofAngle);
|
||||
int towerRoofExpansionDistance = layerThickness / tanTowerRoofAngle;
|
||||
@@ -85,47 +159,57 @@ void generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, i
|
||||
// computation
|
||||
|
||||
|
||||
std::vector<Polygons> joinedLayers; // join model layers of all meshes into polygons and store small areas which need tower support
|
||||
std::vector<std::pair<int, std::vector<Polygons>>> overhang_points; // stores overhang_points along with the layer index at which the overhang point occurs
|
||||
AreaSupport::joinMeshesAndDetectOverhangPoints(storage, joinedLayers, overhang_points, layer_count, supportMinAreaSqrt, extrusionWidth);
|
||||
AreaSupport::detectOverhangPoints(storage, *object, overhang_points, layer_count, supportMinAreaSqrt, extrusionWidth);
|
||||
|
||||
|
||||
// initialization of supportAreasPerLayer
|
||||
for (int layer_idx = 0; layer_idx < layer_count ; layer_idx++)
|
||||
storage.support.supportAreasPerLayer.emplace_back();
|
||||
|
||||
|
||||
bool still_in_upper_empty_layers = true;
|
||||
int overhang_points_pos = overhang_points.size() - 1;
|
||||
Polygons supportLayer_last;
|
||||
std::vector<Polygons> towerRoofs;
|
||||
for (int layer_idx = support_layer_count - 1 - layerZdistanceTop; layer_idx >= 0 ; layer_idx--)
|
||||
for (unsigned int layer_idx = support_layer_count - 1 - layerZdistanceTop; layer_idx != (unsigned int) -1 ; layer_idx--)
|
||||
{
|
||||
|
||||
Polygons overhang;
|
||||
{
|
||||
// compute basic overhang and put in right layer ([layerZdistanceTOp] layers below)
|
||||
Polygons supportLayer_supportee = object->layers[layer_idx+layerZdistanceTop].getOutlines();
|
||||
Polygons supportLayer_supporter = storage.getLayerOutlines(layer_idx-1+layerZdistanceTop, false);
|
||||
|
||||
Polygons supportLayer_supported = supportLayer_supporter.offset(maxDistFromLowerLayer);
|
||||
Polygons basic_overhang = supportLayer_supportee.difference(supportLayer_supported);
|
||||
|
||||
// compute basic overhang and put in right layer ([layerZdistanceTOp] layers below)
|
||||
Polygons supportLayer_supportee = joinedLayers[layer_idx+layerZdistanceTop];
|
||||
Polygons supportLayer_supported = joinedLayers[layer_idx-1+layerZdistanceTop].offset(maxDistFromLowerLayer);
|
||||
Polygons basic_overhang = supportLayer_supportee.difference(supportLayer_supported);
|
||||
|
||||
Polygons support_extension = basic_overhang.offset(maxDistFromLowerLayer);
|
||||
support_extension = support_extension.intersection(supportLayer_supported);
|
||||
support_extension = support_extension.intersection(supportLayer_supportee);
|
||||
|
||||
Polygons overhang = basic_overhang.unionPolygons(support_extension);
|
||||
|
||||
/* supported
|
||||
* .................
|
||||
* ______________|
|
||||
* _______| ^^^^^ basic overhang
|
||||
*
|
||||
* ^^^^^^^^^ overhang extensions
|
||||
* ^^^^^^^^^^^^^^ overhang
|
||||
*/
|
||||
// Polygons support_extension = basic_overhang.offset(maxDistFromLowerLayer);
|
||||
// support_extension = support_extension.intersection(supportLayer_supported);
|
||||
// support_extension = support_extension.intersection(supportLayer_supportee);
|
||||
//
|
||||
// Polygons overhang = basic_overhang.unionPolygons(support_extension);
|
||||
// presumably the computation above is slower than the one below
|
||||
|
||||
Polygons overhang_extented = basic_overhang.offset(maxDistFromLowerLayer + 100); // +100 for easier joining with support from layer above
|
||||
overhang = overhang_extented.intersection(supportLayer_supported.unionPolygons(supportLayer_supportee));
|
||||
|
||||
/* layer 2
|
||||
* layer 1 ______________|
|
||||
* _______| ^^^^^ basic overhang
|
||||
*
|
||||
* ^^^^^^^ supporter
|
||||
* ^^^^^^^^^^^^^^^^^ supported
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^ supportee
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^ overhang extended
|
||||
* ^^^^^^^^^ overhang extensions
|
||||
* ^^^^^^^^^^^^^^ overhang
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
Polygons& supportLayer_this = overhang;
|
||||
|
||||
supportLayer_this = supportLayer_this.simplify(50); // TODO: hardcoded value!
|
||||
if (extension_offset)
|
||||
{
|
||||
supportLayer_this = supportLayer_this.offset(extension_offset);
|
||||
}
|
||||
|
||||
supportLayer_this.simplify(50); // TODO: hardcoded value!
|
||||
|
||||
if (supportMinAreaSqrt > 0)
|
||||
{
|
||||
@@ -134,68 +218,58 @@ void generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, i
|
||||
// handle towers
|
||||
AreaSupport::handleTowers(supportLayer_this, towerRoofs, overhang_points, overhang_points_pos, layer_idx, towerRoofExpansionDistance, supportTowerDiameter, supportMinAreaSqrt, layer_count, z_layer_distance_tower);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (layer_idx+1 < support_layer_count)
|
||||
{ // join with support from layer up
|
||||
Polygons& supportLayer_up = supportLayer_last;
|
||||
|
||||
Polygons joined = supportLayer_this.unionPolygons(supportLayer_up);
|
||||
// join different parts
|
||||
if (supportJoinDistance > 0)
|
||||
{
|
||||
joined = joined.offset(supportJoinDistance);
|
||||
joined = joined.offset(-supportJoinDistance);
|
||||
}
|
||||
if (smoothing_distance > 0)
|
||||
joined = joined.smooth(smoothing_distance, min_smoothing_area);
|
||||
|
||||
// remove layer
|
||||
Polygons insetted = joined.difference(joinedLayers[layer_idx]);
|
||||
supportLayer_this = insetted;
|
||||
|
||||
{ // join with support from layer up
|
||||
supportLayer_this = AreaSupport::join(supportLayer_last, supportLayer_this, join_distance, smoothing_distance, min_smoothing_area, conical_support, conical_support_offset, conical_smallest_breadth);
|
||||
}
|
||||
|
||||
|
||||
supportLayer_last = supportLayer_this;
|
||||
|
||||
// inset using X/Y distance
|
||||
if (supportLayer_this.size() > 0)
|
||||
supportLayer_this = supportLayer_this.difference(joinedLayers[layer_idx].offset(supportXYDistance));
|
||||
|
||||
// move up from model
|
||||
if (layerZdistanceBottom > 0 && layer_idx >= layerZdistanceBottom)
|
||||
{
|
||||
int stepHeight = support_bottom_stair_step_height / supportLayerThickness + 1;
|
||||
int bottomLayer = ((layer_idx - layerZdistanceBottom) / stepHeight) * stepHeight;
|
||||
supportLayer_this = supportLayer_this.difference(joinedLayers[bottomLayer]);
|
||||
supportLayer_this = supportLayer_this.difference(storage.getLayerOutlines(bottomLayer, false));
|
||||
}
|
||||
|
||||
storage.support.supportAreasPerLayer[layer_idx] = supportLayer_this;
|
||||
|
||||
logProgress("support", support_layer_count - layer_idx, support_layer_count);
|
||||
supportLayer_last = supportLayer_this;
|
||||
|
||||
|
||||
// inset using X/Y distance
|
||||
if (supportLayer_this.size() > 0)
|
||||
supportLayer_this = supportLayer_this.difference(storage.getLayerOutlines(layer_idx, false).offset(supportXYDistance));
|
||||
|
||||
supportAreas[layer_idx] = supportLayer_this;
|
||||
|
||||
if (still_in_upper_empty_layers && supportLayer_this.size() > 0)
|
||||
{
|
||||
storage.support.layer_nr_max_filled_layer = layer_idx;
|
||||
still_in_upper_empty_layers = false;
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::SUPPORT, support_layer_count - layer_idx, support_layer_count, commandSocket);
|
||||
}
|
||||
|
||||
// do stuff for when support on buildplate only
|
||||
if (supportOnBuildplateOnly)
|
||||
{
|
||||
Polygons touching_buildplate = storage.support.supportAreasPerLayer[0];
|
||||
for (unsigned int layer_idx = 1 ; layer_idx < storage.support.supportAreasPerLayer.size() ; layer_idx++)
|
||||
Polygons touching_buildplate = supportAreas[0]; // TODO: not working for conical support!
|
||||
for (unsigned int layer_idx = 1 ; layer_idx < storage.support.supportLayers.size() ; layer_idx++)
|
||||
{
|
||||
Polygons& supportLayer = storage.support.supportAreasPerLayer[layer_idx];
|
||||
Polygons& supportLayer = supportAreas[layer_idx];
|
||||
|
||||
touching_buildplate = supportLayer.intersection(touching_buildplate); // from bottom to top, support areas can only decrease!
|
||||
|
||||
storage.support.supportAreasPerLayer[layer_idx] = touching_buildplate;
|
||||
supportAreas[layer_idx] = touching_buildplate;
|
||||
}
|
||||
}
|
||||
|
||||
storage.support.generated = true;
|
||||
}
|
||||
|
||||
void AreaSupport::joinMeshesAndDetectOverhangPoints(
|
||||
void AreaSupport::detectOverhangPoints(
|
||||
SliceDataStorage& storage,
|
||||
std::vector<Polygons>& joinedLayers,
|
||||
SliceMeshStorage& mesh,
|
||||
std::vector<std::pair<int, std::vector<Polygons>>>& overhang_points, // stores overhang_points along with the layer index at which the overhang point occurs)
|
||||
int layer_count,
|
||||
int supportMinAreaSqrt,
|
||||
@@ -204,30 +278,26 @@ void AreaSupport::joinMeshesAndDetectOverhangPoints(
|
||||
{
|
||||
for (int layer_idx = 0 ; layer_idx < layer_count ; layer_idx++)
|
||||
{
|
||||
joinedLayers.emplace_back();
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
if (part.outline.outerPolygon().area() < supportMinAreaSqrt * supportMinAreaSqrt)
|
||||
{
|
||||
Polygons part_poly_computed;
|
||||
Polygons& part_poly = (part.insets.size() > 0)? part.insets[0] : part_poly_computed; // don't copy inset if its already computed
|
||||
if (part.insets.size() == 0) { part_poly_computed = part.outline.offset(-extrusionWidth/2); }
|
||||
|
||||
if (part.outline[0].area() < supportMinAreaSqrt * supportMinAreaSqrt)
|
||||
if (part_poly.size() > 0)
|
||||
{
|
||||
Polygons part_poly = part.outline.offset(-extrusionWidth/2);
|
||||
if (part_poly.size() > 0)
|
||||
if (overhang_points.size() > 0 && overhang_points.back().first == layer_idx)
|
||||
overhang_points.back().second.push_back(part_poly);
|
||||
else
|
||||
{
|
||||
if (overhang_points.size() > 0 && overhang_points.back().first == layer_idx)
|
||||
overhang_points.back().second.push_back(part_poly);
|
||||
else
|
||||
{
|
||||
std::vector<Polygons> small_part_polys;
|
||||
small_part_polys.push_back(part_poly);
|
||||
overhang_points.emplace_back<std::pair<int, std::vector<Polygons>>>(std::make_pair(layer_idx, small_part_polys));
|
||||
}
|
||||
std::vector<Polygons> small_part_polys;
|
||||
small_part_polys.push_back(part_poly);
|
||||
overhang_points.emplace_back<std::pair<int, std::vector<Polygons>>>(std::make_pair(layer_idx, small_part_polys));
|
||||
}
|
||||
|
||||
}
|
||||
joinedLayers.back() = joinedLayers.back().unionPolygons(part.outline);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -336,6 +406,28 @@ void AreaSupport::handleWallStruts(
|
||||
}
|
||||
|
||||
|
||||
void AreaSupport::generateSupportRoofs(SliceDataStorage& storage, std::vector<Polygons>& supportAreas, unsigned int layer_count, int layerThickness, int support_roof_height, CommandSocket* commandSocket)
|
||||
{
|
||||
int roof_layer_count = support_roof_height / layerThickness;
|
||||
|
||||
std::vector<SupportLayer>& supportLayers = storage.support.supportLayers;
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count; layer_idx++)
|
||||
{
|
||||
SupportLayer& layer = supportLayers[layer_idx];
|
||||
|
||||
if (layer_idx + roof_layer_count < supportLayers.size())
|
||||
{
|
||||
Polygons roofs = supportAreas[layer_idx].difference(supportAreas[layer_idx + roof_layer_count]);
|
||||
roofs.removeSmallAreas(1.0);
|
||||
layer.roofs.add(roofs);
|
||||
layer.supportAreas.add(supportAreas[layer_idx].difference(layer.roofs));
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.roofs.add(layer.supportAreas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+54
-15
@@ -3,13 +3,64 @@
|
||||
#define SUPPORT_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
#include "modelFile/modelFile.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "commandSocket.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
class AreaSupport {
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Generate the support areas and support roof areas for all models.
|
||||
* \param storage data storage containing the input layer outline data and containing the output support storage per layer
|
||||
* \param layer_count total number of layers
|
||||
* \param commandSocket Socket over which to report the progress
|
||||
*/
|
||||
static void generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count, CommandSocket* commandSocket);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Generate support polygons over all layers for one object.
|
||||
*
|
||||
* This function also handles small overhang areas (creates towers with larger diameter than just the overhang area) and single walls which could otherwise fall over.
|
||||
*
|
||||
* \param storage data storage containing the input layer outline data
|
||||
* \param object The object for which to generate support areas
|
||||
* \param layer_count total number of layers
|
||||
* \param commandSocket Socket over which to report the progress
|
||||
*/
|
||||
static void generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, unsigned int layer_count, std::vector<Polygons>& supportAreas, CommandSocket* commandSocket);
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* Generate support roof areas and non-roof areas for a given mesh.
|
||||
*
|
||||
* \param storage Output storage: support area + support roof area output
|
||||
* \param supportAreas The basic support areas for the current mesh
|
||||
* \param commandSocket Socket over which to report the progress
|
||||
* \param layerThickness The layer height
|
||||
* \param support_roof_height The thickness of the hammock in z directiontt
|
||||
*/
|
||||
static void generateSupportRoofs(SliceDataStorage& storage, std::vector<Polygons>& supportAreas, unsigned int layer_count, int layerThickness, int support_roof_height, CommandSocket* commandSocket);
|
||||
|
||||
/*!
|
||||
* Join current support layer with the support of the layer above, (make support conical) and perform smoothing etc operations.
|
||||
*
|
||||
* \param supportLayer_up The support areas the layer above
|
||||
* \param supportLayer_this The overhang areas of the current layer at hand
|
||||
* \param supportJoinDistance The distance to be filled between two support areas
|
||||
* \param smoothing_distance Maximal distance in the X/Y directions of a line segment which is to be smoothed out.
|
||||
* \param min_smoothing_area minimal area for which to perform smoothing
|
||||
* \param conical_support Whether the support should be conical instead of cylindrical
|
||||
* \param conical_support_offset The offset determining the angle of the conical support
|
||||
* \param conical_smallest_breadth The breadth of the smallest support area which is not to be redoces to a smaller size due to conical support.
|
||||
*
|
||||
* \return The joined support areas for this layer.
|
||||
*/
|
||||
static Polygons join(Polygons& supportLayer_up, Polygons& supportLayer_this, int64_t supportJoinDistance, int64_t smoothing_distance, int min_smoothing_area, bool conical_support, int64_t conical_support_offset, int64_t conical_smallest_breadth);
|
||||
|
||||
/*!
|
||||
* Joins the layerpart outlines of all meshes and collects the overhang points (small areas).
|
||||
* \param storage input layer outline information
|
||||
@@ -18,9 +69,9 @@ public:
|
||||
* \param supportMinAreaSqrt diameter of the minimal area which can be supported without a specialized strut
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
static void joinMeshesAndDetectOverhangPoints(
|
||||
static void detectOverhangPoints(
|
||||
SliceDataStorage& storage,
|
||||
std::vector<Polygons>& joinedLayers,
|
||||
SliceMeshStorage& mesh,
|
||||
std::vector<std::pair<int, std::vector<Polygons>>>& overhang_points,
|
||||
int layer_count,
|
||||
int supportMinAreaSqrt,
|
||||
@@ -67,18 +118,6 @@ public:
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* Generate support polygons over all layers.
|
||||
*
|
||||
* This function also handles small overhang areas (creates towers with larger diameter than just the overhang area) and single walls which could otherwise fall over.
|
||||
*
|
||||
* \param storage data storage containing the input layer outline data and containing the output support storage per layer
|
||||
* \param object The object for which to generate support areas
|
||||
* \param layer_count total number of layers
|
||||
*/
|
||||
void generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, int layer_count);
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
+14
-9
@@ -7,10 +7,6 @@
|
||||
|
||||
|
||||
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
using namespace cura;
|
||||
|
||||
/*
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/polygon.h"
|
||||
@@ -166,6 +162,8 @@ void test_BucketGrid2D()
|
||||
/*
|
||||
#include <math.h>
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
void test_findClosestConnection()
|
||||
{
|
||||
srand(1234);
|
||||
@@ -267,6 +265,11 @@ void test_findClosestConnection()
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#include "utils/polygon.h"
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void test_clipper()
|
||||
{
|
||||
Polygon p;
|
||||
@@ -300,13 +303,15 @@ void test_clipper()
|
||||
Polygons polys;
|
||||
polys.add(p);
|
||||
|
||||
polys.debugOutputHTML("output/problem_test.html", true);
|
||||
polys.offset(-400).debugOutputHTML("output/problem_test_offset.html", true);
|
||||
polys = polys.removeDegenerateVerts();
|
||||
polys.offset(-400).debugOutputHTML("output/problem_test_offset_solved.html", true);
|
||||
// polys.debugOutputHTML("output/problem_test.html", true);
|
||||
// polys.offset(-400).debugOutputHTML("output/problem_test_offset.html", true);
|
||||
polys.removeDegenerateVerts();
|
||||
// polys.offset(-400).debugOutputHTML("output/problem_test_offset_solved.html", true);
|
||||
}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// test_findClosestConnection();
|
||||
test_clipper();
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
+10
-4
@@ -4,6 +4,9 @@
|
||||
#include <algorithm>
|
||||
#include "timeEstimate.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
#define MINIMUM_PLANNER_SPEED 0.05// (mm/sec)
|
||||
|
||||
const double max_feedrate[TimeEstimateCalculator::NUM_AXIS] = {600, 600, 40, 25};
|
||||
@@ -190,11 +193,12 @@ double TimeEstimateCalculator::calculate()
|
||||
double totalTime = 0;
|
||||
for(unsigned int n=0; n<blocks.size(); n++)
|
||||
{
|
||||
double plateau_distance = blocks[n].decelerate_after - blocks[n].accelerate_until;
|
||||
Block& block = blocks[n];
|
||||
double plateau_distance = block.decelerate_after - block.accelerate_until;
|
||||
|
||||
totalTime += acceleration_time_from_distance(blocks[n].initial_feedrate, blocks[n].accelerate_until, blocks[n].acceleration);
|
||||
totalTime += plateau_distance / blocks[n].nominal_feedrate;
|
||||
totalTime += acceleration_time_from_distance(blocks[n].final_feedrate, (blocks[n].distance - blocks[n].decelerate_after), blocks[n].acceleration);
|
||||
totalTime += acceleration_time_from_distance(block.initial_feedrate, block.accelerate_until, block.acceleration);
|
||||
totalTime += plateau_distance / block.nominal_feedrate;
|
||||
totalTime += acceleration_time_from_distance(block.final_feedrate, (block.distance - block.decelerate_after), block.acceleration);
|
||||
}
|
||||
return totalTime;
|
||||
}
|
||||
@@ -305,3 +309,5 @@ void TimeEstimateCalculator::recalculate_trapezoids()
|
||||
next->recalculate_flag = false;
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -4,6 +4,9 @@
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/**
|
||||
The TimeEstimateCalculator class generates a estimate of printing time calculated with acceleration in mind.
|
||||
Some of this code has been addapted from the Marlin sources.
|
||||
@@ -73,4 +76,5 @@ private:
|
||||
void planner_forward_pass_kernel(Block *previous, Block *current, Block *next);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//TIME_ESTIMATE_H
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#ifndef AABB_H
|
||||
#define AABB_H
|
||||
|
||||
|
||||
#include <limits>
|
||||
#include "intpoint.h"
|
||||
#include "polygon.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
/* Axis aligned boundary box */
|
||||
class AABB
|
||||
{
|
||||
public:
|
||||
Point min, max;
|
||||
|
||||
AABB()
|
||||
: min(POINT_MIN, POINT_MIN), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
}
|
||||
AABB(Point&min, Point& max)
|
||||
: min(min), max(max)
|
||||
{
|
||||
}
|
||||
AABB(Polygons& polys)
|
||||
: min(POINT_MIN, POINT_MIN), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
calculate(polys);
|
||||
}
|
||||
|
||||
void calculate(Polygons& polys)
|
||||
{
|
||||
min = Point(POINT_MAX, POINT_MAX);
|
||||
max = Point(POINT_MIN, POINT_MIN);
|
||||
for(unsigned int i=0; i<polys.size(); i++)
|
||||
{
|
||||
for(unsigned int j=0; j<polys[i].size(); j++)
|
||||
{
|
||||
if (min.X > polys[i][j].X) min.X = polys[i][j].X;
|
||||
if (min.Y > polys[i][j].Y) min.Y = polys[i][j].Y;
|
||||
if (max.X < polys[i][j].X) max.X = polys[i][j].X;
|
||||
if (max.Y < polys[i][j].Y) max.Y = polys[i][j].Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hit(const AABB& other) const
|
||||
{
|
||||
if (max.X < other.min.X) return false;
|
||||
if (min.X > other.max.X) return false;
|
||||
if (max.Y < other.min.Y) return false;
|
||||
if (min.Y > other.max.Y) return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
An Axis Aligned Bounding Box. Has a min and max vector, representing minimal and maximal coordinates in the three axes.
|
||||
*/
|
||||
struct AABB3D
|
||||
{
|
||||
Point3 min; //!< The minimal coordinates in x, y and z direction
|
||||
Point3 max; //!< The maximal coordinates in x, y and z direction
|
||||
|
||||
/*!
|
||||
* Create an AABB3D with coordinates at the numeric limits.
|
||||
*/
|
||||
AABB3D()
|
||||
: min(std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max())
|
||||
, max(std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min())
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Expand the AABB3D to include the point \p p.
|
||||
* \param p The point to include with the bounding box.
|
||||
*/
|
||||
void include(Point3 p)
|
||||
{
|
||||
min.x = std::min(min.x, p.x);
|
||||
min.y = std::min(min.y, p.y);
|
||||
min.z = std::min(min.z, p.z);
|
||||
max.x = std::max(max.x, p.x);
|
||||
max.y = std::max(max.y, p.y);
|
||||
max.z = std::max(max.z, p.z);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Offset the coordinates of the bounding box.
|
||||
* \param offset The offset with which to offset the AABB3D.
|
||||
*/
|
||||
void offset(Point3 offset)
|
||||
{
|
||||
min += offset;
|
||||
max += offset;
|
||||
}
|
||||
/*!
|
||||
* Offset the coordinates of the bounding box.
|
||||
* \param offset The offset with which to offset the AABB3D.
|
||||
*/
|
||||
void offset(Point offset)
|
||||
{
|
||||
min += offset;
|
||||
max += offset;
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//AABB_H
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Copyright (C) 2015 Tim Kuipers- Released under terms of the AGPLv3 License */
|
||||
#ifndef BUCKET_GRID_2D_H
|
||||
#define BUCKET_GRID_2D_H
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_BUCKET_GRID_2D_H
|
||||
#define UTILS_BUCKET_GRID_2D_H
|
||||
|
||||
#include "logoutput.h"
|
||||
#include "intpoint.h"
|
||||
@@ -46,7 +46,6 @@ private:
|
||||
inline uint32_t pointHash_simple(const Point& p) const
|
||||
{
|
||||
return p.X ^ (p.Y << 8);
|
||||
//return (p.X / 20000) ^ (p.Y / 20000) << 8;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -200,8 +199,9 @@ public:
|
||||
*/
|
||||
void insert(Point& p, T& t)
|
||||
{
|
||||
typedef typename Map::iterator iter;
|
||||
std::pair<iter, bool> emplaced = point2object.emplace(p, t);
|
||||
// typedef typename Map::iterator iter;
|
||||
// std::pair<iter, bool> emplaced =
|
||||
point2object.emplace(p, t);
|
||||
// if (! emplaced.second)
|
||||
// logError("Error! BucketGrid2D couldn't insert object!");
|
||||
};
|
||||
@@ -212,5 +212,5 @@ public:
|
||||
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
}//namespace cura
|
||||
#endif//BUCKET_GRID_2D_H
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef UTILS_NO_COPY_H
|
||||
#define UTILS_NO_COPY_H
|
||||
|
||||
/**
|
||||
Util class to base other objects off which should never be copied.
|
||||
Is needed because C++ has an implicit copy constructor and assign operator when none are defined.
|
||||
*/
|
||||
class NoCopy
|
||||
{
|
||||
protected:
|
||||
NoCopy() {}
|
||||
|
||||
private:
|
||||
/// Private copy constructor.
|
||||
/// Cannot be called because it is private.
|
||||
NoCopy(const NoCopy&);
|
||||
|
||||
/// Private assign operator.
|
||||
/// Cannot be called because it is private.
|
||||
NoCopy& operator =(const NoCopy&);
|
||||
};
|
||||
|
||||
#endif // UTILS_NO_COPY_H
|
||||
@@ -0,0 +1,216 @@
|
||||
#ifndef SVG_H
|
||||
#define SVG_H
|
||||
|
||||
#include <stdio.h> // for file output
|
||||
|
||||
#include "polygon.h"
|
||||
#include "intpoint.h"
|
||||
#include "AABB.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
class SVG
|
||||
{
|
||||
public:
|
||||
enum class Color {
|
||||
BLACK,
|
||||
WHITE,
|
||||
GRAY,
|
||||
RED,
|
||||
BLUE,
|
||||
GREEN
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
std::string toString(Color color)
|
||||
{
|
||||
switch (color)
|
||||
{
|
||||
case SVG::Color::BLACK: return "black";
|
||||
case SVG::Color::WHITE: return "white";
|
||||
case SVG::Color::GRAY: return "gray";
|
||||
case SVG::Color::RED: return "red";
|
||||
case SVG::Color::BLUE: return "blue";
|
||||
case SVG::Color::GREEN: return "green";
|
||||
default: return "black";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
FILE* out; // the output file
|
||||
const AABB aabb; // the boundary box to display
|
||||
const Point aabb_size;
|
||||
const double scale;
|
||||
|
||||
public:
|
||||
SVG(const char* filename, AABB aabb, Point canvas_size = Point(1024 * 4, 1024 * 4))
|
||||
: aabb(aabb)
|
||||
, aabb_size(aabb.max - aabb.min)
|
||||
, scale(std::min(double(canvas_size.X - 20) / aabb_size.X, double(canvas_size.Y - 20) / aabb_size.Y))
|
||||
{
|
||||
out = fopen(filename, "w");
|
||||
fprintf(out, "<!DOCTYPE html><html><body>\n");
|
||||
fprintf(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width:%llipx;height:%llipx\">\n", canvas_size.Y, canvas_size.X);
|
||||
|
||||
// fprintf(out, "<marker id='MidMarker' viewBox='0 0 10 10' refX='5' refY='5' markerUnits='strokeWidth' markerWidth='10' markerHeight='10' stroke='lightblue' stroke-width='2' fill='none' orient='auto'>");
|
||||
// fprintf(out, "<path d='M 0 0 L 10 5 M 0 10 L 10 5'/>");
|
||||
// fprintf(out, "</marker>");
|
||||
}
|
||||
|
||||
~SVG()
|
||||
{
|
||||
fprintf(out, "</svg>\n");
|
||||
fprintf(out, "</body></html>");
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
/*!
|
||||
* transform a point in real space to canvas space
|
||||
*/
|
||||
Point transform(Point& p)
|
||||
{
|
||||
return Point((p.X-aabb.min.X)*scale, (p.Y-aabb.min.Y)*scale) + Point(10,10);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// void _writeLines(PolygonRef polygon, Color color = Color::GRAY)
|
||||
// {
|
||||
// for(unsigned int n=0; n<polygon.size(); n++)
|
||||
// {
|
||||
// if (n == 0)
|
||||
// fprintf(out, "M");
|
||||
// else
|
||||
// fprintf(out, "L");
|
||||
// Point pf = transform(polygon[n]);
|
||||
// fprintf(out, "%lli,%lli ", pf.X, pf.Y);
|
||||
// }
|
||||
// fprintf(out, "Z\n");
|
||||
// }
|
||||
|
||||
public:
|
||||
// void writeLines(Polygons& polygons, Color color = Color::GRAY, Color outline_color = Color::BLACK)
|
||||
// {
|
||||
// fprintf(out, "<g fill-rule='evenodd' style=\"fill: %s; stroke:%s;stroke-width:1\">\n", toString(color).c_str(), toString(outline_color).c_str());
|
||||
// fprintf(out, "<path marker-mid='url(#MidMarker)' d=\"");
|
||||
// for(PolygonRef poly : polygons)
|
||||
// {
|
||||
// _writeLines(poly, outline_color);
|
||||
// }
|
||||
// fprintf(out, "\"/>");
|
||||
// fprintf(out, "</g>\n");
|
||||
// }
|
||||
// void writeLines(PolygonRef poly, Color color = Color::GRAY, Color outline_color = Color::BLACK)
|
||||
// {
|
||||
// fprintf(out, "<g fill-rule='evenodd' style=\"fill: %s; stroke:%s;stroke-width:1\">\n", toString(color).c_str(), toString(outline_color).c_str());
|
||||
// fprintf(out, "<path marker-mid='url(#MidMarker)' d=\"");
|
||||
// writeLines(poly, outline_color);
|
||||
// fprintf(out, "\"/>");
|
||||
// fprintf(out, "</g>\n");
|
||||
// }
|
||||
|
||||
void writeAreas(Polygons& polygons, Color color = Color::GRAY, Color outline_color = Color::BLACK)
|
||||
{
|
||||
|
||||
for(PolygonsPart& parts : polygons.splitIntoParts())
|
||||
{
|
||||
for(unsigned int j=0;j<parts.size();j++)
|
||||
{
|
||||
Polygon poly = parts[j];
|
||||
fprintf(out, "<polygon points=\"");
|
||||
for(Point& p : poly)
|
||||
{
|
||||
Point fp = transform(p);
|
||||
fprintf(out, "%lli,%lli ", fp.Y, fp.X);
|
||||
}
|
||||
if (j == 0)
|
||||
fprintf(out, "\" style=\"fill:%s;stroke:%s;stroke-width:1\" />\n", toString(color).c_str(), toString(outline_color).c_str());
|
||||
else
|
||||
fprintf(out, "\" style=\"fill:white;stroke:%s;stroke-width:1\" />\n", toString(outline_color).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writePoint(Point& p, bool write_coords=false, int size = 5, Color color = Color::BLACK)
|
||||
{
|
||||
Point pf = transform(p);
|
||||
fprintf(out, "<circle cx=\"%lli\" cy=\"%lli\" r=\"%d\" stroke=\"%s\" stroke-width=\"1\" fill=\"%s\" />\n",pf.Y, pf.X, size, toString(color).c_str(), toString(color).c_str());
|
||||
|
||||
if (write_coords)
|
||||
{
|
||||
fprintf(out, "<text x=\"%lli\" y=\"%lli\" style=\"font-size: 10;\" fill=\"black\">%lli,%lli</text>\n",pf.Y, pf.X, p.X, p.Y);
|
||||
}
|
||||
}
|
||||
void writePoints(PolygonRef poly, bool write_coords=false, int size = 5, Color color = Color::BLACK)
|
||||
{
|
||||
for (Point& p : poly)
|
||||
{
|
||||
writePoint(p, write_coords, size, color);
|
||||
}
|
||||
}
|
||||
|
||||
void writePoints(Polygons& polygons, bool write_coords=false, int size = 5, Color color = Color::BLACK)
|
||||
{
|
||||
for (PolygonRef poly : polygons)
|
||||
{
|
||||
writePoints(poly, write_coords, size, color);
|
||||
}
|
||||
}
|
||||
|
||||
void writeLine(Point& a, Point& b, Color color = Color::BLACK)
|
||||
{
|
||||
|
||||
fprintf(out, "<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:%s;stroke-width:1\" />", a.Y, a.X, b.Y, b.X, toString(color).c_str());
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void printf(const char* txt, Args... args)
|
||||
{
|
||||
fprintf(out, txt, args...);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
void Polygons::debugOutputHTML(const char* filename, bool dotTheVertices)
|
||||
{
|
||||
FILE* out = fopen(filename, "w");
|
||||
fprintf(out, "<!DOCTYPE html><html><body>");
|
||||
Point modelSize = max() - min();
|
||||
modelSize.X = std::max(modelSize.X, modelSize.Y);
|
||||
modelSize.Y = std::max(modelSize.X, modelSize.Y);
|
||||
Point modelMin = min();
|
||||
|
||||
fprintf(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width: 500px; height:500px\">\n");
|
||||
for(PolygonsPart& parts : splitIntoParts())
|
||||
{
|
||||
for(unsigned int j=0;j<parts.size();j++)
|
||||
{
|
||||
Polygon poly = parts[j];
|
||||
fprintf(out, "<polygon points=\"");
|
||||
for(Point& p : poly)
|
||||
{
|
||||
fprintf(out, "%f,%f ", float(p.X - modelMin.X)/modelSize.X*500, float(p.Y - modelMin.Y)/modelSize.Y*500);
|
||||
}
|
||||
if (j == 0)
|
||||
fprintf(out, "\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n");
|
||||
else
|
||||
fprintf(out, "\" style=\"fill:red; stroke:black;stroke-width:1\" />\n");
|
||||
|
||||
if (dotTheVertices)
|
||||
for(Point& p : poly)
|
||||
fprintf(out, "<circle cx=\"%f\" cy=\"%f\" r=\"2\" stroke=\"black\" stroke-width=\"3\" fill=\"black\" />", float(p.X - modelMin.X)/modelSize.X*500, float(p.Y - modelMin.Y)/modelSize.Y*500);
|
||||
}
|
||||
}
|
||||
fprintf(out, "</svg>\n");
|
||||
fprintf(out, "</body></html>");
|
||||
fclose(out);
|
||||
} */
|
||||
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
#endif // SVG_H
|
||||
+52
-48
@@ -2,22 +2,25 @@
|
||||
#ifndef FLOAT_POINT_H
|
||||
#define FLOAT_POINT_H
|
||||
|
||||
/*
|
||||
Floating point 3D points are used during model loading as 3D vectors.
|
||||
They represent millimeters in 3D space.
|
||||
*/
|
||||
|
||||
#include "intpoint.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*
|
||||
Floating point 3D points are used during model loading as 3D vectors.
|
||||
They represent millimeters in 3D space.
|
||||
*/
|
||||
class FPoint3
|
||||
{
|
||||
public:
|
||||
float x,y,z;
|
||||
FPoint3() {}
|
||||
FPoint3(float _x, float _y, float _z): x(_x), y(_y), z(_z) {}
|
||||
FPoint3(float _x, float _y, float _z): x(_x), y(_y), z(_z) {}
|
||||
FPoint3(const Point3& p): x(p.x*.001), y(p.y*.001), z(p.z*.001) {}
|
||||
|
||||
FPoint3 operator+(const FPoint3& p) const { return FPoint3(x+p.x, y+p.y, z+p.z); }
|
||||
@@ -25,9 +28,9 @@ public:
|
||||
FPoint3 operator*(const float f) const { return FPoint3(x*f, y*f, z*f); }
|
||||
FPoint3 operator/(const float f) const { return FPoint3(x/f, y/f, z/f); }
|
||||
|
||||
FPoint3& operator += (const FPoint3& p) { x += p.x; y += p.y; z += p.z; return *this; }
|
||||
FPoint3& operator += (const FPoint3& p) { x += p.x; y += p.y; z += p.z; return *this; }
|
||||
FPoint3& operator -= (const FPoint3& p) { x -= p.x; y -= p.y; z -= p.z; return *this; }
|
||||
FPoint3& operator *= (const float f) { x *= f; y *= f; z *= f; return *this; }
|
||||
FPoint3& operator *= (const float f) { x *= f; y *= f; z *= f; return *this; }
|
||||
|
||||
bool operator==(FPoint3& p) const { return x==p.x&&y==p.y&&z==p.z; }
|
||||
bool operator!=(FPoint3& p) const { return x!=p.x||y!=p.y||z!=p.z; }
|
||||
@@ -52,48 +55,48 @@ public:
|
||||
float vSize()
|
||||
{
|
||||
return sqrt(vSize2());
|
||||
}
|
||||
|
||||
inline FPoint3 normalized()
|
||||
{
|
||||
return (*this)/vSize();
|
||||
}
|
||||
|
||||
FPoint3 cross(const FPoint3& p)
|
||||
{
|
||||
return FPoint3(
|
||||
y*p.z-z*p.y,
|
||||
z*p.x-x*p.z,
|
||||
x*p.y-y*p.x);
|
||||
}
|
||||
|
||||
static FPoint3 cross(const Point3& a, const Point3& b)
|
||||
{
|
||||
return FPoint3(a).cross(FPoint3(b));
|
||||
// FPoint3(
|
||||
// a.y*b.z-a.z*b.y,
|
||||
// a.z*b.x-a.x*b.z,
|
||||
// a.x*b.y-a.y*b.x);
|
||||
}
|
||||
|
||||
Point3 toPoint3()
|
||||
{
|
||||
return Point3(x*1000, y*1000, z*1000);
|
||||
}
|
||||
|
||||
inline FPoint3 normalized()
|
||||
{
|
||||
return (*this)/vSize();
|
||||
}
|
||||
|
||||
FPoint3 cross(const FPoint3& p)
|
||||
{
|
||||
return FPoint3(
|
||||
y*p.z-z*p.y,
|
||||
z*p.x-x*p.z,
|
||||
x*p.y-y*p.x);
|
||||
}
|
||||
|
||||
static FPoint3 cross(const Point3& a, const Point3& b)
|
||||
{
|
||||
return FPoint3(a).cross(FPoint3(b));
|
||||
// FPoint3(
|
||||
// a.y*b.z-a.z*b.y,
|
||||
// a.z*b.x-a.x*b.z,
|
||||
// a.x*b.y-a.y*b.x);
|
||||
}
|
||||
|
||||
Point3 toPoint3()
|
||||
{
|
||||
return Point3(x*1000, y*1000, z*1000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//inline FPoint3 operator+(FPoint3 lhs, const FPoint3& rhs) {
|
||||
// lhs += rhs;
|
||||
// return lhs;
|
||||
//}
|
||||
inline float operator*(FPoint3 lhs, const FPoint3& rhs) {
|
||||
return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z;
|
||||
}
|
||||
//inline FPoint3 operator*(FPoint3 lhs, const float f) {
|
||||
// lhs *= f;
|
||||
// return lhs;
|
||||
//}
|
||||
|
||||
|
||||
//inline FPoint3 operator+(FPoint3 lhs, const FPoint3& rhs) {
|
||||
// lhs += rhs;
|
||||
// return lhs;
|
||||
//}
|
||||
inline float operator*(FPoint3 lhs, const FPoint3& rhs) {
|
||||
return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z;
|
||||
}
|
||||
//inline FPoint3 operator*(FPoint3 lhs, const float f) {
|
||||
// lhs *= f;
|
||||
// return lhs;
|
||||
//}
|
||||
|
||||
class FMatrix3x3
|
||||
{
|
||||
@@ -122,4 +125,5 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//INT_POINT_H
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "gettime.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
TimeKeeper::TimeKeeper()
|
||||
{
|
||||
restart();
|
||||
@@ -12,3 +15,5 @@ double TimeKeeper::restart()
|
||||
startTime = getTime();
|
||||
return ret;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
|
||||
namespace cura
|
||||
{
|
||||
static inline double getTime()
|
||||
{
|
||||
#ifdef __WIN32
|
||||
@@ -30,4 +32,5 @@ public:
|
||||
double restart();
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//GETTIME_H
|
||||
|
||||
@@ -15,10 +15,15 @@ Integer points are used to avoid floating point rounding errors, and because Cli
|
||||
#include <stdint.h>
|
||||
#include <cmath>
|
||||
|
||||
#include <functional> // for hash function obkject
|
||||
|
||||
#include <iostream> // auto-serialization / auto-toString()
|
||||
|
||||
#define INT2MM(n) (double(n) / 1000.0)
|
||||
#define INT2MM2(n) (double(n) / 1000000.0)
|
||||
#define MM2INT(n) (int64_t((n) * 1000))
|
||||
#define MM2_2INT(n) (int64_t((n) * 1000000))
|
||||
#define MM3_2INT(n) (int64_t((n) * 1000000000))
|
||||
|
||||
#define INT2MICRON(n) ((n) / 1)
|
||||
#define MICRON2INT(n) ((n) * 1)
|
||||
@@ -37,6 +42,10 @@ Integer points are used to avoid floating point rounding errors, and because Cli
|
||||
#define DEPRECATED(func) func
|
||||
#endif
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class Point3
|
||||
{
|
||||
public:
|
||||
@@ -143,6 +152,7 @@ INLINE Point operator-(const Point& p0, const Point& p1) { return Point(p0.X-p1.
|
||||
INLINE Point operator*(const Point& p0, const int32_t i) { return Point(p0.X*i, p0.Y*i); }
|
||||
INLINE Point operator*(const int32_t i, const Point& p0) { return p0 * i; }
|
||||
INLINE Point operator/(const Point& p0, const int32_t i) { return Point(p0.X/i, p0.Y/i); }
|
||||
INLINE Point operator/(const Point& p0, const Point& p1) { return Point(p0.X/p1.X, p0.Y/p1.Y); }
|
||||
|
||||
INLINE Point& operator += (Point& p0, const Point& p1) { p0.X += p1.X; p0.Y += p1.Y; return p0; }
|
||||
INLINE Point& operator -= (Point& p0, const Point& p1) { p0.X -= p1.X; p0.Y -= p1.Y; return p0; }
|
||||
@@ -204,6 +214,25 @@ INLINE int angle(const Point& p)
|
||||
return angle;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<cura::Point> {
|
||||
size_t operator()(const cura::Point & pp) const
|
||||
{
|
||||
static int prime = 31;
|
||||
int result = 89;
|
||||
result = result * prime + pp.X;
|
||||
result = result * prime + pp.Y;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class PointMatrix
|
||||
{
|
||||
public:
|
||||
@@ -252,6 +281,11 @@ public:
|
||||
inline Point3 operator+(const Point3& p3, const Point& p2) {
|
||||
return Point3(p3.x + p2.X, p3.y + p2.Y, p3.z);
|
||||
}
|
||||
inline Point3& operator+=(Point3& p3, const Point& p2) {
|
||||
p3.x += p2.X;
|
||||
p3.y += p2.Y;
|
||||
return p3;
|
||||
}
|
||||
|
||||
inline Point operator+(const Point& p2, const Point3& p3) {
|
||||
return Point(p3.x + p2.X, p3.y + p2.Y);
|
||||
@@ -261,9 +295,15 @@ inline Point operator+(const Point& p2, const Point3& p3) {
|
||||
inline Point3 operator-(const Point3& p3, const Point& p2) {
|
||||
return Point3(p3.x - p2.X, p3.y - p2.Y, p3.z);
|
||||
}
|
||||
inline Point3& operator-=(Point3& p3, const Point& p2) {
|
||||
p3.x -= p2.X;
|
||||
p3.y -= p2.Y;
|
||||
return p3;
|
||||
}
|
||||
|
||||
inline Point operator-(const Point& p2, const Point3& p3) {
|
||||
return Point(p2.X - p3.x, p2.Y - p3.y);
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
#endif//INT_POINT_H
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_LINEAR_ALG_2D_H
|
||||
#define UTILS_LINEAR_ALG_2D_H
|
||||
|
||||
#include "intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
class LinearAlg2D
|
||||
{
|
||||
public:
|
||||
static short pointLiesOnTheRightOfLine(Point p, Point p0, Point p1)
|
||||
{
|
||||
// no tests unless the segment p0-p1 is at least partly at, or to right of, p.X
|
||||
if ( std::max(p0.X, p1.X) >= p.X )
|
||||
{
|
||||
int64_t pdY = p1.Y-p0.Y;
|
||||
if (pdY < 0) // p0->p1 is 'falling'
|
||||
{
|
||||
if ( p1.Y <= p.Y && p0.Y > p.Y ) // candidate
|
||||
{
|
||||
// dx > 0 if intersection is to right of p.X
|
||||
int64_t dx = (p1.X - p0.X) * (p1.Y - p.Y) - (p1.X-p.X)*pdY;
|
||||
if (dx == 0) // includes p == p1
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (dx > 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (p.Y >= p0.Y)
|
||||
{
|
||||
if (p.Y < p1.Y) // candidate for p0->p1 'rising' and includes p.Y
|
||||
{
|
||||
// dx > 0 if intersection is to right of p.X
|
||||
int64_t dx = (p1.X - p0.X) * (p.Y - p0.Y) - (p.X-p0.X)*pdY;
|
||||
if (dx == 0) // includes p == p0
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (dx > 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (p.Y == p1.Y)
|
||||
{
|
||||
// some special cases here, points on border:
|
||||
// - p1 exactly matches p (might otherwise be missed)
|
||||
// - p0->p1 exactly horizontal, and includes p.
|
||||
// (we already tested std::max(p0.X,p1.X) >= p.X )
|
||||
if (p.X == p1.X ||
|
||||
(pdY==0 && std::min(p0.X,p1.X) <= p.X) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Find the point closest to \p from on the line from \p p0 to \p p1
|
||||
*/
|
||||
static Point getClosestOnLineSegment(Point from, Point p0, Point p1)
|
||||
{
|
||||
Point direction = p1 - p0;
|
||||
Point toFrom = from-p0;
|
||||
int64_t projected_x = dot(toFrom, direction) ;
|
||||
|
||||
int64_t x_p0 = 0;
|
||||
int64_t x_p1 = vSize2(direction);
|
||||
|
||||
if (x_p1 == 0)
|
||||
{
|
||||
// std::cout << "warning! too small segment" << std::endl;
|
||||
return p0;
|
||||
}
|
||||
if (projected_x <= x_p0)
|
||||
{
|
||||
return p0;
|
||||
}
|
||||
if (projected_x >= x_p1)
|
||||
{
|
||||
return p1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Point ret = p0 + projected_x / vSize(direction) * direction / vSize(direction);
|
||||
return ret ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* Get the squared distance from point \p b to a line *segment* from \p a to \p c.
|
||||
*
|
||||
* \param a the first point of the line segment
|
||||
* \param b the point to measure the distance from
|
||||
* \param c the second point on the line segment
|
||||
* \param b_is_beyond_ac optional output parameter: whether \p b is closest to the line segment (0), to \p a (-1) or \p b (1)
|
||||
*/
|
||||
static int64_t getDist2FromLineSegment(Point& a, Point& b, Point& c, char* b_is_beyond_ac = nullptr)
|
||||
{
|
||||
/*
|
||||
* a,
|
||||
* /|
|
||||
* / |
|
||||
* b,/__|, x
|
||||
* \ |
|
||||
* \ |
|
||||
* \|
|
||||
* 'c
|
||||
*
|
||||
* x = b projected on ac
|
||||
* ax = ab dot ac / vSize(ac)
|
||||
* xb = ab - ax
|
||||
* error = vSize(xb)
|
||||
*/
|
||||
Point ac = c - a;
|
||||
int64_t ac_size = vSize(ac);
|
||||
if (ac_size == 0) { return 0; }
|
||||
|
||||
Point ab = b - a;
|
||||
int64_t projected_x = dot(ab, ac);
|
||||
int64_t ax_size = projected_x / ac_size;
|
||||
|
||||
if (ax_size < 0)
|
||||
{// b is 'before' segment ac
|
||||
if (b_is_beyond_ac) { *b_is_beyond_ac = -1; }
|
||||
return vSize2(ab);
|
||||
}
|
||||
if (ax_size > ac_size)
|
||||
{// b is 'after' segment ac
|
||||
if (b_is_beyond_ac) { *b_is_beyond_ac = 1; }
|
||||
return vSize2(b - c);
|
||||
}
|
||||
|
||||
Point ax = ac * ax_size / ac_size;
|
||||
Point bx = ab - ax;
|
||||
if (b_is_beyond_ac) { *b_is_beyond_ac = 0; }
|
||||
return vSize2(bx);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
#endif//UTILS_LINEAR_ALG_2D_H
|
||||
@@ -26,8 +26,17 @@ void logError(const char* fmt, ...)
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void logWarning(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
void logCopyright(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
@@ -48,12 +57,12 @@ void log(const char* fmt, ...)
|
||||
va_end(args);
|
||||
fflush(stderr);
|
||||
}
|
||||
void logProgress(const char* type, int value, int maxValue)
|
||||
void logProgress(const char* type, int value, int maxValue, float percent)
|
||||
{
|
||||
if (!progressLogging)
|
||||
return;
|
||||
|
||||
fprintf(stderr, "Progress:%s:%i:%i\n", type, value, maxValue);
|
||||
fprintf(stderr, "Progress:%s:%i:%i \t%f%%\n", type, value, maxValue, percent);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ void enableProgressLogging();
|
||||
|
||||
//Report an error message (always reported, independed of verbose level)
|
||||
void logError(const char* fmt, ...);
|
||||
//Report a warning message (always reported, independed of verbose level)
|
||||
void logWarning(const char* fmt, ...);
|
||||
//Report a message if the verbose level is 1 or higher. (defined as _log to prevent clash with log() function from <math.h>)
|
||||
void log(const char* fmt, ...);
|
||||
//Report an copyright message (always reported, independed of verbose level)
|
||||
void logCopyright(const char* fmt, ...);
|
||||
void logCopyright(const char* fmt, ...);
|
||||
|
||||
//Report engine progress to interface if any. Only if "enableProgressLogging()" has been called.
|
||||
void logProgress(const char* type, int value, int maxValue);
|
||||
void logProgress(const char* type, int value, int maxValue, float percent);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "polygon.h"
|
||||
|
||||
#include "linearAlg2D.h" // pointLiesOnTheRightOfLine
|
||||
|
||||
#include "../debug.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
bool PolygonRef::inside(Point p, bool border_result)
|
||||
{
|
||||
PolygonRef thiss = *this;
|
||||
if (size() < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int crossings = 0;
|
||||
Point p0 = back();
|
||||
for(unsigned int n=0; n<size(); n++)
|
||||
{
|
||||
Point p1 = thiss[n];
|
||||
// no tests unless the segment p0-p1 is at least partly at, or to right of, p.X
|
||||
short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1);
|
||||
if (comp == 1)
|
||||
{
|
||||
crossings++;
|
||||
}
|
||||
else if (comp == 0)
|
||||
{
|
||||
return border_result;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
return (crossings % 2) == 1;
|
||||
}
|
||||
|
||||
bool Polygons::inside(Point p, bool border_result)
|
||||
{
|
||||
Polygons& thiss = *this;
|
||||
if (size() < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int crossings = 0;
|
||||
for (PolygonRef poly : thiss)
|
||||
{
|
||||
Point p0 = poly.back();
|
||||
for(Point& p1 : poly)
|
||||
{
|
||||
short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1);
|
||||
if (comp == 1)
|
||||
{
|
||||
crossings++;
|
||||
}
|
||||
else if (comp == 0)
|
||||
{
|
||||
return border_result;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
return (crossings % 2) == 1;
|
||||
}
|
||||
|
||||
unsigned int Polygons::findInside(Point p, bool border_result)
|
||||
{
|
||||
Polygons& thiss = *this;
|
||||
if (size() < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t min_x[size()];
|
||||
std::fill_n(min_x, size(), std::numeric_limits<int64_t>::max()); // initialize with int.max
|
||||
int crossings[size()];
|
||||
std::fill_n(crossings, size(), 0); // initialize with zeros
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++)
|
||||
{
|
||||
PolygonRef poly = thiss[poly_idx];
|
||||
Point p0 = poly.back();
|
||||
for(Point& p1 : poly)
|
||||
{
|
||||
short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1);
|
||||
if (comp == 1)
|
||||
{
|
||||
crossings[poly_idx]++;
|
||||
int64_t x;
|
||||
if (p1.Y == p0.Y)
|
||||
{
|
||||
x = p0.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = p0.X + (p1.X-p0.X) * (p.Y-p0.Y) / (p1.Y-p0.Y);
|
||||
}
|
||||
if (x < min_x[poly_idx])
|
||||
{
|
||||
min_x[poly_idx] = x;
|
||||
}
|
||||
}
|
||||
else if (border_result && comp == 0)
|
||||
{
|
||||
return poly_idx;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t min_x_uneven = std::numeric_limits<int64_t>::max();
|
||||
unsigned int ret = NO_INDEX;
|
||||
unsigned int n_unevens = 0;
|
||||
for (unsigned int array_idx = 0; array_idx < size(); array_idx++)
|
||||
{
|
||||
if (crossings[array_idx] % 2 == 1)
|
||||
{
|
||||
n_unevens++;
|
||||
if (min_x[array_idx] < min_x_uneven)
|
||||
{
|
||||
min_x_uneven = min_x[array_idx];
|
||||
ret = array_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (n_unevens % 2 == 0) { ret = NO_INDEX; }
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PolygonRef::simplify(int smallest_line_segment_squared, int allowed_error_distance_squared){
|
||||
PolygonRef& thiss = *this;
|
||||
|
||||
if (size() <= 2)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
{ // remove segments smaller than allowed_error_distance
|
||||
// this is neccesary in order to avoid the case where a long segment is followed by a lot of small segments would get simplified to a long segment going to the wrong end point
|
||||
// ....... _ _______
|
||||
// | / |
|
||||
// | would become / instead of |
|
||||
// | / |
|
||||
Point* last = &thiss.back();
|
||||
unsigned int writing_idx = 0;
|
||||
for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++)
|
||||
{
|
||||
Point& here = thiss[poly_idx];
|
||||
if (vSize2(*last - here) < smallest_line_segment_squared)
|
||||
{
|
||||
// don't add the point
|
||||
}
|
||||
else
|
||||
{
|
||||
thiss[writing_idx] = here;
|
||||
writing_idx++;
|
||||
last = &here;
|
||||
}
|
||||
}
|
||||
polygon->erase(polygon->begin() + writing_idx , polygon->end());
|
||||
}
|
||||
|
||||
Point* last = &thiss[0];
|
||||
unsigned int writing_idx = 1;
|
||||
for (unsigned int poly_idx = 1; poly_idx < size(); poly_idx++)
|
||||
{
|
||||
Point& here = thiss[poly_idx];
|
||||
if ( vSize2(here-*last) < allowed_error_distance_squared )
|
||||
{
|
||||
// don't add the point to the result
|
||||
continue;
|
||||
}
|
||||
Point& next = thiss[(poly_idx+1) % size()];
|
||||
char here_is_beyond_line;
|
||||
int64_t error2 = LinearAlg2D::getDist2FromLineSegment(*last, here, next, &here_is_beyond_line);
|
||||
if (here_is_beyond_line == 0 && error2 < allowed_error_distance_squared)
|
||||
{// don't add the point to the result
|
||||
} else
|
||||
{
|
||||
thiss[writing_idx] = here;
|
||||
writing_idx++;
|
||||
last = &here;
|
||||
}
|
||||
}
|
||||
polygon->erase(polygon->begin() + writing_idx , polygon->end());
|
||||
|
||||
|
||||
if (size() < 3)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
{ // handle the line segments spanning the vector end and begin
|
||||
Point* last = &thiss.back();
|
||||
Point& here = thiss[0];
|
||||
if ( vSize2(here-*last) < allowed_error_distance_squared )
|
||||
{
|
||||
remove(0);
|
||||
}
|
||||
Point& next = thiss[1];
|
||||
int64_t error2 = LinearAlg2D::getDist2FromLineSegment(*last, here, next);
|
||||
if (error2 < allowed_error_distance_squared)
|
||||
{
|
||||
remove(0);
|
||||
} else
|
||||
{
|
||||
// leave it in
|
||||
}
|
||||
}
|
||||
|
||||
if (size() < 3)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PolygonsPart> Polygons::splitIntoParts(bool unionAll) const
|
||||
{
|
||||
std::vector<PolygonsPart> ret;
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
ClipperLib::PolyTree resultPolyTree;
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
if (unionAll)
|
||||
clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
else
|
||||
clipper.Execute(ClipperLib::ctUnion, resultPolyTree);
|
||||
|
||||
splitIntoParts_processPolyTreeNode(&resultPolyTree, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Polygons::splitIntoParts_processPolyTreeNode(ClipperLib::PolyNode* node, std::vector<PolygonsPart>& ret) const
|
||||
{
|
||||
for(int n=0; n<node->ChildCount(); n++)
|
||||
{
|
||||
ClipperLib::PolyNode* child = node->Childs[n];
|
||||
PolygonsPart part;
|
||||
part.add(child->Contour);
|
||||
for(int i=0; i<child->ChildCount(); i++)
|
||||
{
|
||||
part.add(child->Childs[i]->Contour);
|
||||
splitIntoParts_processPolyTreeNode(child->Childs[i], ret);
|
||||
}
|
||||
ret.push_back(part);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int PartsView::getPartContaining(unsigned int poly_idx, unsigned int* boundary_poly_idx)
|
||||
{
|
||||
PartsView& partsView = *this;
|
||||
for (unsigned int part_idx_now = 0; part_idx_now < partsView.size(); part_idx_now++)
|
||||
{
|
||||
std::vector<unsigned int>& partView = partsView[part_idx_now];
|
||||
if (partView.size() == 0) { continue; }
|
||||
std::vector<unsigned int>::iterator result = std::find(partView.begin(), partView.end(), poly_idx);
|
||||
if (result != partView.end())
|
||||
{
|
||||
if (boundary_poly_idx) { *boundary_poly_idx = partView[0]; }
|
||||
return part_idx_now;
|
||||
}
|
||||
}
|
||||
return NO_INDEX;
|
||||
}
|
||||
|
||||
PolygonsPart PartsView::assemblePart(unsigned int part_idx)
|
||||
{
|
||||
PartsView& partsView = *this;
|
||||
PolygonsPart ret;
|
||||
if (part_idx != NO_INDEX)
|
||||
{
|
||||
for (unsigned int poly_idx_ff : partsView[part_idx])
|
||||
{
|
||||
ret.add(polygons[poly_idx_ff]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PolygonsPart PartsView::assemblePartContaining(unsigned int poly_idx, unsigned int* boundary_poly_idx)
|
||||
{
|
||||
PolygonsPart ret;
|
||||
unsigned int part_idx = getPartContaining(poly_idx, boundary_poly_idx);
|
||||
if (part_idx != NO_INDEX)
|
||||
{
|
||||
return assemblePart(part_idx);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PartsView Polygons::splitIntoPartsView(bool unionAll)
|
||||
{
|
||||
Polygons reordered;
|
||||
PartsView partsView(*this);
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
ClipperLib::PolyTree resultPolyTree;
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
if (unionAll)
|
||||
clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
else
|
||||
clipper.Execute(ClipperLib::ctUnion, resultPolyTree);
|
||||
|
||||
splitIntoPartsView_processPolyTreeNode(partsView, reordered, &resultPolyTree);
|
||||
|
||||
(*this) = reordered;
|
||||
return partsView;
|
||||
}
|
||||
|
||||
void Polygons::splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node)
|
||||
{
|
||||
for(int n=0; n<node->ChildCount(); n++)
|
||||
{
|
||||
ClipperLib::PolyNode* child = node->Childs[n];
|
||||
partsView.emplace_back();
|
||||
unsigned int pos = partsView.size() - 1;
|
||||
partsView[pos].push_back(reordered.size());
|
||||
reordered.add(child->Contour);
|
||||
for(int i = 0; i < child->ChildCount(); i++)
|
||||
{
|
||||
partsView[pos].push_back(reordered.size());
|
||||
reordered.add(child->Childs[i]->Contour);
|
||||
splitIntoPartsView_processPolyTreeNode(partsView, reordered, child->Childs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//namespace cura
|
||||
+205
-260
@@ -6,8 +6,9 @@
|
||||
#include <float.h>
|
||||
#include <clipper/clipper.hpp>
|
||||
|
||||
#include <algorithm> // std::reverse
|
||||
#include <algorithm> // std::reverse, fill_n array
|
||||
#include <cmath> // fabs
|
||||
#include <limits> // int64_t.min
|
||||
|
||||
#include "intpoint.h"
|
||||
|
||||
@@ -32,6 +33,9 @@ enum PolygonType
|
||||
SupportInfillType
|
||||
};
|
||||
|
||||
|
||||
class PartsView;
|
||||
|
||||
const static int clipper_init = (0);
|
||||
#define NO_INDEX (std::numeric_limits<unsigned int>::max())
|
||||
|
||||
@@ -45,6 +49,11 @@ public:
|
||||
PolygonRef(ClipperLib::Path& polygon)
|
||||
: polygon(&polygon)
|
||||
{}
|
||||
|
||||
PolygonRef(const PolygonRef& other)
|
||||
{
|
||||
polygon = other.polygon;
|
||||
}
|
||||
|
||||
unsigned int size() const
|
||||
{
|
||||
@@ -67,6 +76,10 @@ public:
|
||||
polygon->push_back(p);
|
||||
}
|
||||
|
||||
PolygonRef& operator=(const PolygonRef& other) { polygon = other.polygon; return *this; }
|
||||
|
||||
ClipperLib::Path& operator*() { return *polygon; }
|
||||
|
||||
template <typename... Args>
|
||||
void emplace_back(Args... args)
|
||||
{
|
||||
@@ -84,6 +97,11 @@ public:
|
||||
polygon->clear();
|
||||
}
|
||||
|
||||
/*!
|
||||
* On Y-axis positive upward displays, Orientation will return true if the polygon's orientation is counter-clockwise.
|
||||
*
|
||||
* from http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/Orientation.htm
|
||||
*/
|
||||
bool orientation() const
|
||||
{
|
||||
return ClipperLib::Orientation(*polygon);
|
||||
@@ -129,7 +147,6 @@ public:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
double area() const
|
||||
{
|
||||
return ClipperLib::Area(*polygon);
|
||||
@@ -202,74 +219,15 @@ public:
|
||||
* \param border_result What to return when the point is exactly on the border
|
||||
* \return Whether the point \p p is inside this polygon (or \p border_result when it is on the border)
|
||||
*/
|
||||
bool inside(Point p, bool border_result=false)
|
||||
{
|
||||
PolygonRef thiss = *this;
|
||||
if (size() < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int crossings = 0;
|
||||
Point p0 = back();
|
||||
for(unsigned int n=0; n<size(); n++)
|
||||
{
|
||||
Point p1 = thiss[n];
|
||||
// no tests unless the segment p0-p1 is at least partly at, or to right of, p.X
|
||||
if ( std::max(p0.X, p1.X) >= p.X )
|
||||
{
|
||||
int64_t pdY = p1.Y-p0.Y;
|
||||
if (pdY < 0) // p0->p1 is 'falling'
|
||||
{
|
||||
if ( p1.Y <= p.Y && p0.Y > p.Y ) // candidate
|
||||
{
|
||||
// dx > 0 if intersection is to right of p.X
|
||||
int64_t dx = (p1.X - p0.X) * (p1.Y - p.Y) - (p1.X-p.X)*pdY;
|
||||
if (dx == 0) // includes p == p1
|
||||
{
|
||||
return border_result;
|
||||
}
|
||||
if (dx > 0)
|
||||
{
|
||||
crossings ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (p.Y >= p0.Y)
|
||||
{
|
||||
if (p.Y < p1.Y) // candidate for p0->p1 'rising' and includes p.Y
|
||||
{
|
||||
// dx > 0 if intersection is to right of p.X
|
||||
int64_t dx = (p1.X - p0.X) * (p.Y - p0.Y) - (p.X-p0.X)*pdY;
|
||||
if (dx == 0) // includes p == p0
|
||||
{
|
||||
return border_result;
|
||||
}
|
||||
if (dx > 0)
|
||||
{
|
||||
crossings ++;
|
||||
}
|
||||
}
|
||||
else if (p.Y == p1.Y)
|
||||
{
|
||||
// some special cases here, points on border:
|
||||
// - p1 exactly matches p (might otherwise be missed)
|
||||
// - p0->p1 exactly horizontal, and includes p.
|
||||
// (we already tested std::max(p0.X,p1.X) >= p.X )
|
||||
if (p.X == p1.X ||
|
||||
(pdY==0 && std::min(p0.X,p1.X) <= p.X) )
|
||||
{
|
||||
return border_result;
|
||||
// otherwise, count no crossings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
return (crossings % 2) == 1;
|
||||
}
|
||||
bool inside(Point p, bool border_result = false);
|
||||
|
||||
/*!
|
||||
* Smooth out the polygon and store the result in \p result.
|
||||
* Smoothing is performed by removing line segments smaller than \p remove_length
|
||||
*
|
||||
* \param remove_length The length of the largest segment removed
|
||||
* \param result (output) The result polygon, assumed to be empty
|
||||
*/
|
||||
void smooth(int remove_length, PolygonRef result)
|
||||
{
|
||||
PolygonRef& thiss = *this;
|
||||
@@ -287,68 +245,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void simplify(int allowed_error_distance_squared, PolygonRef result) //!< removes consecutive line segments with same orientation
|
||||
{
|
||||
PolygonRef& thiss = *this;
|
||||
ClipperLib::Path* poly = result.polygon;
|
||||
|
||||
if (size() < 4)
|
||||
{
|
||||
for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++)
|
||||
poly->push_back(thiss[poly_idx]);
|
||||
return;
|
||||
}
|
||||
|
||||
Point& last = thiss[0];
|
||||
result.add(last);
|
||||
for (unsigned int poly_idx = 1; poly_idx < size(); poly_idx++)
|
||||
{
|
||||
/*
|
||||
* /|
|
||||
* c / | a
|
||||
* /__|
|
||||
* \ b|
|
||||
* e \ | d
|
||||
* \|
|
||||
*
|
||||
* b^2 = c^2 - a^2
|
||||
* b^2 = e^2 - d^2
|
||||
*
|
||||
* approximately: (this is asymptotically true for d -> 0)
|
||||
* a/d = c/e
|
||||
* a/(a+d) = c/(c+e)
|
||||
* a^2 / (a+d)^2 = c^2 / (c+e)^2
|
||||
* a^2 = c^2 * (a+d)^2/ (c+e)^2
|
||||
*
|
||||
*/
|
||||
if ( vSize2(thiss[poly_idx]-last) < allowed_error_distance_squared )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Point& next = thiss[(poly_idx+1) % size()];
|
||||
auto square = [](double in) { return in*in; };
|
||||
int64_t a2 = vSize2(next-thiss[poly_idx]) * vSize2(next-last) / static_cast<int64_t>(square(vSizeMM(next-last) + vSizeMM(thiss[poly_idx]-last))*1000*1000);
|
||||
|
||||
int64_t error2 = vSize2(next-thiss[poly_idx]) - a2;
|
||||
if (error2 < allowed_error_distance_squared)
|
||||
{
|
||||
// don't add the point to the result
|
||||
} else
|
||||
{
|
||||
poly->push_back(thiss[poly_idx]);
|
||||
last = thiss[poly_idx];
|
||||
}
|
||||
}
|
||||
|
||||
if (result.size() < 3)
|
||||
{
|
||||
poly->clear();
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++)
|
||||
poly->push_back(thiss[poly_idx]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/*!
|
||||
* removes consecutive line segments with same orientation and changes this polygon
|
||||
*
|
||||
* \param smallest_line_segment_squared maximal squared length of removed line segments
|
||||
* \param allowed_error_distance_squared The square of the distance of the middle point to the line segment of the consecutive and previous point for which the middle point is removed
|
||||
*/
|
||||
void simplify(int smallest_line_segment_squared = 100, int allowed_error_distance_squared = 25);
|
||||
|
||||
void pop_back()
|
||||
{
|
||||
@@ -400,9 +303,11 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class PolygonsPart;
|
||||
|
||||
class Polygons
|
||||
{
|
||||
private:
|
||||
protected:
|
||||
ClipperLib::Paths polygons;
|
||||
public:
|
||||
unsigned int size() const
|
||||
@@ -472,6 +377,13 @@ public:
|
||||
clipper.Execute(ClipperLib::ctUnion, ret.polygons, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
return ret;
|
||||
}
|
||||
/*!
|
||||
* Union all polygons with each other (When polygons.add(polygon) has been called for overlapping polygons)
|
||||
*/
|
||||
Polygons unionPolygons() const
|
||||
{
|
||||
return unionPolygons(Polygons());
|
||||
}
|
||||
Polygons intersection(const Polygons& other) const
|
||||
{
|
||||
Polygons ret;
|
||||
@@ -501,6 +413,63 @@ public:
|
||||
return ret;
|
||||
}
|
||||
|
||||
Polygons offsetPolyLine(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter) const
|
||||
{
|
||||
Polygons ret;
|
||||
double miterLimit = 1.2;
|
||||
ClipperLib::ClipperOffset clipper(miterLimit, 10.0);
|
||||
clipper.AddPaths(polygons, joinType, ClipperLib::etOpenSquare);
|
||||
clipper.MiterLimit = miterLimit;
|
||||
clipper.Execute(ret.polygons, distance);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Check if we are inside the polygon. We do this by tracing from the point towards the positive X direction,
|
||||
* every line we cross increments the crossings counter. If we have an even number of crossings then we are not inside the polygon.
|
||||
* Care needs to be taken, if p.Y exactly matches a vertex to the right of p, then we need to count 1 intersect if the
|
||||
* outline passes vertically past; and 0 (or 2) intersections if that point on the outline is a 'top' or 'bottom' vertex.
|
||||
* The easiest way to do this is to break out two cases for increasing and decreasing Y ( from p0 to p1 ).
|
||||
* A segment is tested if pa.Y <= p.Y < pb.Y, where pa and pb are the points (from p0,p1) with smallest & largest Y.
|
||||
* When both have the same Y, no intersections are counted but there is a special test to see if the point falls
|
||||
* exactly on the line.
|
||||
*
|
||||
* Returns false if outside, true if inside; if the point lies exactly on the border, will return \p border_result.
|
||||
*
|
||||
* \param p The point for which to check if it is inside this polygon
|
||||
* \param border_result What to return when the point is exactly on the border
|
||||
* \return Whether the point \p p is inside this polygon (or \p border_result when it is on the border)
|
||||
*/
|
||||
bool inside(Point p, bool border_result = false);
|
||||
|
||||
/*!
|
||||
* Find the polygon inside which point \p p resides.
|
||||
*
|
||||
* We do this by tracing from the point towards the positive X direction,
|
||||
* every line we cross increments the crossings counter. If we have an even number of crossings then we are not inside the polygon.
|
||||
* We then find the polygon with an uneven number of crossings which is closest to \p p.
|
||||
*
|
||||
* If \p border_result, we return the first polygon which is exactly on \p p.
|
||||
*
|
||||
* \param p The point for which to check in which polygon it is.
|
||||
* \param border_result Whether a point exactly on a polygon counts as inside
|
||||
* \return The index of the polygon inside which the point \p p resides
|
||||
*/
|
||||
unsigned int findInside(Point p, bool border_result = false);
|
||||
|
||||
/*!
|
||||
* Approximates the convex hull of the polygons.
|
||||
* \p extra_outset Extra offset outward
|
||||
* \return the convex hull (approximately)
|
||||
*
|
||||
*/
|
||||
Polygons convexHull(int extra_outset = 0)
|
||||
{
|
||||
int overshoot = 100000; // 10 cm (hardcoded value)
|
||||
|
||||
return offset(overshoot, ClipperLib::jtRound).offset(-overshoot+extra_outset, ClipperLib::jtRound);
|
||||
}
|
||||
|
||||
Polygons smooth(int remove_length, int min_area) //!< removes points connected to small lines
|
||||
{
|
||||
Polygons ret;
|
||||
@@ -525,35 +494,46 @@ public:
|
||||
return ret;
|
||||
}
|
||||
|
||||
Polygons simplify(int allowed_error_distance) //!< removes points connected to similarly oriented lines
|
||||
/*!
|
||||
* removes points connected to similarly oriented lines
|
||||
*
|
||||
* \param smallest_line_segment_squared maximal squared length of removed line segments
|
||||
* \param allowed_error_distance_squared The square of the distance of the middle point to the line segment of the consecutive and previous point for which the middle point is removed
|
||||
*/
|
||||
void simplify(int smallest_line_segment = 10, int allowed_error_distance = 5)
|
||||
{
|
||||
int allowed_error_distance_squared = allowed_error_distance * allowed_error_distance;
|
||||
Polygons ret;
|
||||
int smallest_line_segment_squared = smallest_line_segment * smallest_line_segment;
|
||||
Polygons& thiss = *this;
|
||||
for (unsigned int p = 0; p < size(); p++)
|
||||
{
|
||||
thiss[p].simplify(allowed_error_distance_squared, ret.newPoly());
|
||||
thiss[p].simplify(smallest_line_segment_squared, allowed_error_distance_squared);
|
||||
if (thiss[p].size() < 3)
|
||||
{
|
||||
remove(p);
|
||||
p--;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Split up the polygons into groups according to the even-odd rule.
|
||||
* Each polygons in the result has an outline as first polygon, whereas the rest are holes.
|
||||
* Each PolygonsPart in the result has an outline as first polygon, whereas the rest are holes.
|
||||
*/
|
||||
std::vector<Polygons> splitIntoParts(bool unionAll = false) const
|
||||
{
|
||||
std::vector<Polygons> ret;
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
ClipperLib::PolyTree resultPolyTree;
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
if (unionAll)
|
||||
clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
else
|
||||
clipper.Execute(ClipperLib::ctUnion, resultPolyTree);
|
||||
|
||||
_processPolyTreeNode(&resultPolyTree, ret);
|
||||
return ret;
|
||||
}
|
||||
std::vector<PolygonsPart> splitIntoParts(bool unionAll = false) const;
|
||||
private:
|
||||
void splitIntoParts_processPolyTreeNode(ClipperLib::PolyNode* node, std::vector<PolygonsPart>& ret) const;
|
||||
public:
|
||||
/*!
|
||||
* Split up the polygons into groups according to the even-odd rule.
|
||||
* Each vector in the result has the index to an outline as first index, whereas the rest are indices to holes.
|
||||
*
|
||||
* \warning Note that this function reorders the polygons!
|
||||
*/
|
||||
PartsView splitIntoPartsView(bool unionAll = false);
|
||||
private:
|
||||
void splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node);
|
||||
public:
|
||||
/*!
|
||||
* Removes polygons with area smaller than \p minAreaSize (note that minAreaSize is in mm^2, not in micron^2).
|
||||
*/
|
||||
@@ -573,11 +553,12 @@ public:
|
||||
/*!
|
||||
* Removes overlapping consecutive line segments which don't delimit a positive area.
|
||||
*/
|
||||
Polygons removeDegenerateVerts()
|
||||
void removeDegenerateVerts()
|
||||
{
|
||||
Polygons ret;
|
||||
for (PolygonRef poly : *this)
|
||||
Polygons& thiss = *this;
|
||||
for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++)
|
||||
{
|
||||
PolygonRef poly = thiss[poly_idx];
|
||||
Polygon result;
|
||||
|
||||
auto isDegenerate = [](Point& last, Point& now, Point& next)
|
||||
@@ -586,7 +567,7 @@ public:
|
||||
Point next_line = next - now;
|
||||
return dot(last_line, next_line) == -1 * vSize(last_line) * vSize(next_line);
|
||||
};
|
||||
|
||||
bool isChanged = false;
|
||||
for (unsigned int idx = 0; idx < poly.size(); idx++)
|
||||
{
|
||||
Point& last = (result.size() == 0) ? poly.back() : result.back();
|
||||
@@ -595,6 +576,7 @@ public:
|
||||
if ( isDegenerate(last, poly[idx], next) )
|
||||
{ // lines are in the opposite direction
|
||||
// don't add vert to the result
|
||||
isChanged = true;
|
||||
while (result.size() > 1 && isDegenerate(result[result.size()-2], result.back(), next) )
|
||||
{
|
||||
result.pop_back();
|
||||
@@ -606,10 +588,19 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (result.size() > 2) { ret.add(result); }
|
||||
if (isChanged)
|
||||
{
|
||||
if (result.size() > 2)
|
||||
{
|
||||
*poly = *result;
|
||||
}
|
||||
else
|
||||
{
|
||||
thiss.remove(poly_idx);
|
||||
poly_idx--; // effectively the next iteration has the same poly_idx (referring to a new poly which is not yet processed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
/*!
|
||||
* Removes the same polygons from this set (and also empty polygons).
|
||||
@@ -666,24 +657,7 @@ public:
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
void _processPolyTreeNode(ClipperLib::PolyNode* node, std::vector<Polygons>& ret) const
|
||||
{
|
||||
for(int n=0; n<node->ChildCount(); n++)
|
||||
{
|
||||
ClipperLib::PolyNode* child = node->Childs[n];
|
||||
Polygons polygons;
|
||||
polygons.add(child->Contour);
|
||||
for(int i=0; i<child->ChildCount(); i++)
|
||||
{
|
||||
polygons.add(child->Childs[i]->Contour);
|
||||
_processPolyTreeNode(child->Childs[i], ret);
|
||||
}
|
||||
ret.push_back(polygons);
|
||||
}
|
||||
}
|
||||
public:
|
||||
|
||||
Polygons processEvenOdd() const
|
||||
{
|
||||
Polygons ret;
|
||||
@@ -737,6 +711,32 @@ public:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void applyMatrix(const PointMatrix& matrix)
|
||||
{
|
||||
for(unsigned int i=0; i<polygons.size(); i++)
|
||||
{
|
||||
for(unsigned int j=0; j<polygons[i].size(); j++)
|
||||
{
|
||||
polygons[i][j] = matrix.apply(polygons[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* A single area with holes. The first polygon is the outline, while the rest are holes within this outline.
|
||||
*
|
||||
* This class has little more functionality than Polygons, but serves to show that a specific instance is ordered such that the first Polygon is the outline and the rest are holes.
|
||||
*/
|
||||
class PolygonsPart : public Polygons
|
||||
{
|
||||
public:
|
||||
PolygonRef outerPolygon()
|
||||
{
|
||||
Polygons& thiss = *this;
|
||||
return thiss[0];
|
||||
}
|
||||
|
||||
bool inside(Point p)
|
||||
{
|
||||
if (size() < 1)
|
||||
@@ -750,94 +750,39 @@ public:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void applyMatrix(const PointMatrix& matrix)
|
||||
{
|
||||
for(unsigned int i=0; i<polygons.size(); i++)
|
||||
{
|
||||
for(unsigned int j=0; j<polygons[i].size(); j++)
|
||||
{
|
||||
polygons[i][j] = matrix.apply(polygons[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void debugOutputHTML(const char* filename, bool dotTheVertices = false)
|
||||
{
|
||||
FILE* out = fopen(filename, "w");
|
||||
fprintf(out, "<!DOCTYPE html><html><body>");
|
||||
Point modelSize = max() - min();
|
||||
modelSize.X = std::max(modelSize.X, modelSize.Y);
|
||||
modelSize.Y = std::max(modelSize.X, modelSize.Y);
|
||||
Point modelMin = min();
|
||||
|
||||
fprintf(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style=\"width: 500px; height:500px\">\n");
|
||||
for(Polygons& parts : splitIntoParts())
|
||||
{
|
||||
for(unsigned int j=0;j<parts.size();j++)
|
||||
{
|
||||
Polygon poly = parts[j];
|
||||
fprintf(out, "<polygon points=\"");
|
||||
for(Point& p : poly)
|
||||
{
|
||||
fprintf(out, "%f,%f ", float(p.X - modelMin.X)/modelSize.X*500, float(p.Y - modelMin.Y)/modelSize.Y*500);
|
||||
}
|
||||
if (j == 0)
|
||||
fprintf(out, "\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n");
|
||||
else
|
||||
fprintf(out, "\" style=\"fill:red; stroke:black;stroke-width:1\" />\n");
|
||||
|
||||
if (dotTheVertices)
|
||||
for(Point& p : poly)
|
||||
fprintf(out, "<circle cx=\"%f\" cy=\"%f\" r=\"2\" stroke=\"black\" stroke-width=\"3\" fill=\"black\" />", float(p.X - modelMin.X)/modelSize.X*500, float(p.Y - modelMin.Y)/modelSize.Y*500);
|
||||
}
|
||||
}
|
||||
fprintf(out, "</svg>\n");
|
||||
fprintf(out, "</body></html>");
|
||||
fclose(out);
|
||||
}
|
||||
};
|
||||
|
||||
/* Axis aligned boundary box */
|
||||
class AABB
|
||||
/*!
|
||||
* Extension of vector<vector<unsigned int>> which is similar to a vector of PolygonParts, except the base of the container is indices to polygons into the original Polygons, instead of the polygons themselves
|
||||
*/
|
||||
class PartsView : public std::vector<std::vector<unsigned int>>
|
||||
{
|
||||
public:
|
||||
Point min, max;
|
||||
|
||||
AABB()
|
||||
: min(POINT_MIN, POINT_MIN), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
}
|
||||
AABB(Polygons polys)
|
||||
: min(POINT_MIN, POINT_MIN), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
calculate(polys);
|
||||
}
|
||||
|
||||
void calculate(Polygons polys)
|
||||
{
|
||||
min = Point(POINT_MAX, POINT_MAX);
|
||||
max = Point(POINT_MIN, POINT_MIN);
|
||||
for(unsigned int i=0; i<polys.size(); i++)
|
||||
{
|
||||
for(unsigned int j=0; j<polys[i].size(); j++)
|
||||
{
|
||||
if (min.X > polys[i][j].X) min.X = polys[i][j].X;
|
||||
if (min.Y > polys[i][j].Y) min.Y = polys[i][j].Y;
|
||||
if (max.X < polys[i][j].X) max.X = polys[i][j].X;
|
||||
if (max.Y < polys[i][j].Y) max.Y = polys[i][j].Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hit(const AABB& other) const
|
||||
{
|
||||
if (max.X < other.min.X) return false;
|
||||
if (min.X > other.max.X) return false;
|
||||
if (max.Y < other.min.Y) return false;
|
||||
if (min.Y > other.max.Y) return false;
|
||||
return true;
|
||||
}
|
||||
Polygons& polygons;
|
||||
PartsView(Polygons& polygons) : polygons(polygons) { }
|
||||
/*!
|
||||
* Get the index of the PolygonsPart of which the polygon with index \p poly_idx is part.
|
||||
*
|
||||
* \param poly_idx The index of the polygon in \p polygons
|
||||
* \param boundary_poly_idx Optional output parameter: The index of the boundary polygon of the part in \p polygons
|
||||
* \return The PolygonsPart containing the polygon with index \p poly_idx
|
||||
*/
|
||||
unsigned int getPartContaining(unsigned int poly_idx, unsigned int* boundary_poly_idx = nullptr);
|
||||
/*!
|
||||
* Assemble the PolygonsPart of which the polygon with index \p poly_idx is part.
|
||||
*
|
||||
* \param poly_idx The index of the polygon in \p polygons
|
||||
* \param boundary_poly_idx Optional output parameter: The index of the boundary polygon of the part in \p polygons
|
||||
* \return The PolygonsPart containing the polygon with index \p poly_idx
|
||||
*/
|
||||
PolygonsPart assemblePartContaining(unsigned int poly_idx, unsigned int* boundary_poly_idx = nullptr);
|
||||
/*!
|
||||
* Assemble the PolygonsPart of which the polygon with index \p poly_idx is part.
|
||||
*
|
||||
* \param part_idx The index of the part
|
||||
* \return The PolygonsPart with index \p poly_idx
|
||||
*/
|
||||
PolygonsPart assemblePart(unsigned int part_idx);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+218
-87
@@ -1,52 +1,193 @@
|
||||
/** Copyright (C) 2015 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "polygonUtils.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "linearAlg2D.h"
|
||||
#include "../debug.h"
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void offsetExtrusionWidth(Polygons& poly, bool inward, int extrusionWidth, Polygons& result, Polygons* in_between, bool avoidOverlappingPerimeters)
|
||||
int64_t offset_safe_allowance = 20; // make all offset safe operations a bit less safe to allow for small variations in walls which are supposed to be exactly x perimeters thick
|
||||
int64_t in_between_min_dist_half = 10;
|
||||
|
||||
void PolygonUtils::offsetExtrusionWidth(const Polygons& poly, bool inward, int extrusionWidth, Polygons& result, Polygons* in_between, bool removeOverlappingPerimeters)
|
||||
{
|
||||
int direction = (inward)? -1 : 1;
|
||||
int distance = (inward)? -extrusionWidth : extrusionWidth;
|
||||
if (!avoidOverlappingPerimeters)
|
||||
if (!removeOverlappingPerimeters)
|
||||
{
|
||||
result = poly.offset(distance);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = poly.offset(distance*3/2).offset(-distance/2); // overshoot by half the extrusionWidth
|
||||
result = poly.offset(distance*3/2 - direction*offset_safe_allowance).offset(-distance/2 + direction*offset_safe_allowance); // overshoot by half the extrusionWidth
|
||||
if (in_between) // if a pointer for in_between is given
|
||||
in_between->add(poly.offset(distance/2).difference(result.offset(-distance/2)));
|
||||
in_between->add(poly.offset(distance/2 + direction*in_between_min_dist_half).difference(result.offset(-distance/2 - direction*in_between_min_dist_half)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void offsetSafe(Polygons& poly, int distance, int extrusionWidth, Polygons& result, bool avoidOverlappingPerimeters)
|
||||
void PolygonUtils::offsetSafe(const Polygons& poly, int distance, int offset_first_boundary, int extrusion_width, Polygons& result, Polygons* in_between, bool removeOverlappingPerimeters)
|
||||
{
|
||||
int direction = (distance > 0)? 1 : -1;
|
||||
if (!avoidOverlappingPerimeters)
|
||||
if (!removeOverlappingPerimeters)
|
||||
{
|
||||
result = poly.offset(distance);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = poly.offset(distance + direction*extrusionWidth/2).offset(-direction * extrusionWidth/2);
|
||||
result = poly.offset(distance + direction*extrusion_width / 2 - direction*offset_safe_allowance).offset(-direction*extrusion_width/2 + direction*offset_safe_allowance); // overshoot by half the extrusionWidth
|
||||
if (in_between) // if a pointer for in_between is given
|
||||
in_between->add(poly.offset(offset_first_boundary + direction*in_between_min_dist_half).difference(result.offset(-direction * extrusion_width/2 - direction*in_between_min_dist_half)));
|
||||
}
|
||||
}
|
||||
|
||||
void removeOverlapping(Polygons& poly, int extrusionWidth, Polygons& result)
|
||||
|
||||
void PolygonUtils::offsetSafe(const Polygons& poly, int distance, int extrusionWidth, Polygons& result, bool removeOverlappingPerimeters)
|
||||
{
|
||||
int direction = (distance > 0)? 1 : -1;
|
||||
if (!removeOverlappingPerimeters)
|
||||
{
|
||||
result = poly.offset(distance);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = poly.offset(distance + direction*extrusionWidth/2 - direction*offset_safe_allowance).offset(-direction * extrusionWidth/2 + direction*offset_safe_allowance);
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonUtils::removeOverlapping(const Polygons& poly, int extrusionWidth, Polygons& result)
|
||||
{
|
||||
result = poly.offset(extrusionWidth/2).offset(-extrusionWidth).offset(extrusionWidth/2);
|
||||
}
|
||||
|
||||
Point PolygonUtils::getBoundaryPointWithOffset(PolygonRef poly, unsigned int point_idx, int64_t offset)
|
||||
{
|
||||
Point p0 = poly[(point_idx > 0) ? (point_idx - 1) : (poly.size() - 1)];
|
||||
Point p1 = poly[point_idx];
|
||||
Point p2 = poly[(point_idx < (poly.size() - 1)) ? (point_idx + 1) : 0];
|
||||
|
||||
Point off0 = crossZ(normal(p1 - p0, MM2INT(1.0))); // 1.0 for some precision
|
||||
Point off1 = crossZ(normal(p2 - p1, MM2INT(1.0))); // 1.0 for some precision
|
||||
Point n = normal(off0 + off1, -offset);
|
||||
|
||||
return p1 + n;
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation assumes moving inside, but moving outside should just as well be possible.
|
||||
*/
|
||||
unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||
{
|
||||
Point ret = from;
|
||||
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
|
||||
unsigned int bestPoly = NO_INDEX;
|
||||
bool is_inside = false;
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
PolygonRef poly = polygons[poly_idx];
|
||||
if (poly.size() < 2)
|
||||
continue;
|
||||
Point p0 = poly[poly.size()-2];
|
||||
Point p1 = poly.back();
|
||||
bool projected_p_beyond_prev_segment = dot(p1 - p0, from - p0) > vSize2(p1 - p0);
|
||||
for(Point& p2 : poly)
|
||||
{
|
||||
// X = A + Normal( B - A ) * ((( B - A ) dot ( P - A )) / VSize( A - B ));
|
||||
// X = P projected on AB
|
||||
Point& a = p1;
|
||||
Point& b = p2;
|
||||
Point& p = from;
|
||||
Point ab = b - a;
|
||||
Point ap = p - a;
|
||||
int64_t ab_length = vSize(ab);
|
||||
int64_t ax_length = dot(ab, ap) / ab_length;
|
||||
if (ax_length < 0) // x is projected to before ab
|
||||
{
|
||||
if (projected_p_beyond_prev_segment)
|
||||
{ // case which looks like: > .
|
||||
projected_p_beyond_prev_segment = false;
|
||||
Point& x = p1;
|
||||
|
||||
int64_t dist2 = vSize2(x - p);
|
||||
if (dist2 < bestDist2)
|
||||
{
|
||||
bestDist2 = dist2;
|
||||
bestPoly = poly_idx;
|
||||
if (distance == 0) { ret = x; }
|
||||
else
|
||||
{
|
||||
Point inward_dir = crossZ(normal(a, distance*4) + normal(p1 - p0, distance*4));
|
||||
ret = x + normal(inward_dir, distance); // *4 to retain more precision for the eventual normalization
|
||||
is_inside = dot(inward_dir, p - x) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
projected_p_beyond_prev_segment = false;
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (ax_length > ab_length) // x is projected to beyond ab
|
||||
{
|
||||
projected_p_beyond_prev_segment = true;
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
projected_p_beyond_prev_segment = false;
|
||||
Point x = a + ab * ax_length / ab_length;
|
||||
|
||||
int64_t dist2 = vSize2(p - x);
|
||||
if (dist2 < bestDist2)
|
||||
{
|
||||
bestDist2 = dist2;
|
||||
bestPoly = poly_idx;
|
||||
if (distance == 0) { ret = x; }
|
||||
else
|
||||
{
|
||||
Point inward_dir = crossZ(normal(ab, distance));
|
||||
ret = x + inward_dir;
|
||||
is_inside = dot(inward_dir, p - x) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
if (is_inside)
|
||||
{
|
||||
if (bestDist2 < distance * distance)
|
||||
{
|
||||
from = ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
// from = from; // original point stays unaltered. It is already inside by enough distance
|
||||
}
|
||||
return bestPoly;
|
||||
}
|
||||
else if (bestDist2 < maxDist2)
|
||||
{
|
||||
from = ret;
|
||||
return bestPoly;
|
||||
}
|
||||
return NO_INDEX;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result, int sample_size)
|
||||
void PolygonUtils::findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result, int sample_size)
|
||||
{
|
||||
PolygonRef poly1 = poly1_result.poly;
|
||||
PolygonRef poly2 = poly2_result.poly;
|
||||
@@ -76,40 +217,7 @@ void findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoi
|
||||
walkToNearestSmallestConnection(poly1_result, poly2_result);
|
||||
}
|
||||
|
||||
void findSmallestConnection_OLD(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result, int sample_size)
|
||||
{
|
||||
PolygonRef poly1 = poly1_result.poly;
|
||||
PolygonRef poly2 = poly2_result.poly;
|
||||
if (poly1.size() == 0 || poly2.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bestDist2 = -1;
|
||||
|
||||
int step1 = std::max<unsigned int>(2, poly1.size() / sample_size);
|
||||
int step2 = std::max<unsigned int>(2, poly2.size() / sample_size);
|
||||
for (unsigned int i = 0; i < poly1.size(); i += step1)
|
||||
{
|
||||
for (unsigned int j = 0; j < poly2.size(); j += step2)
|
||||
{
|
||||
ClosestPolygonPoint here1(i, poly1);
|
||||
ClosestPolygonPoint here2(j, poly2);
|
||||
walkToNearestSmallestConnection(here1, here2);
|
||||
|
||||
int dist2 = vSize2(here1.location - here2.location);
|
||||
if (bestDist2 == -1 || dist2 < bestDist2)
|
||||
{
|
||||
bestDist2 = dist2;
|
||||
poly1_result = here1;
|
||||
poly2_result = here2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result)
|
||||
void PolygonUtils::walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result)
|
||||
{
|
||||
PolygonRef poly1 = poly1_result.poly;
|
||||
PolygonRef poly2 = poly2_result.poly;
|
||||
@@ -133,7 +241,7 @@ void walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestP
|
||||
}
|
||||
}
|
||||
|
||||
ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start_idx)
|
||||
ClosestPolygonPoint PolygonUtils::findNearestClosest(Point from, PolygonRef polygon, int start_idx)
|
||||
{
|
||||
ClosestPolygonPoint forth = findNearestClosest(from, polygon, start_idx, 1);
|
||||
ClosestPolygonPoint back = findNearestClosest(from, polygon, start_idx, -1);
|
||||
@@ -147,7 +255,7 @@ ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start
|
||||
}
|
||||
}
|
||||
|
||||
ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start_idx, int direction)
|
||||
ClosestPolygonPoint PolygonUtils::findNearestClosest(Point from, PolygonRef polygon, int start_idx, int direction)
|
||||
{
|
||||
if (polygon.size() == 0)
|
||||
{
|
||||
@@ -166,7 +274,7 @@ ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start
|
||||
Point& p1 = polygon[p1_idx];
|
||||
Point& p2 = polygon[p2_idx];
|
||||
|
||||
Point closestHere = getClosestOnLine(from, p1 ,p2);
|
||||
Point closestHere = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
|
||||
int64_t dist = vSize2(from - closestHere);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
@@ -183,7 +291,7 @@ ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start
|
||||
return ClosestPolygonPoint(best, bestPos, polygon);
|
||||
}
|
||||
|
||||
ClosestPolygonPoint findClosest(Point from, Polygons& polygons)
|
||||
ClosestPolygonPoint PolygonUtils::findClosest(Point from, Polygons& polygons)
|
||||
{
|
||||
|
||||
Polygon emptyPoly;
|
||||
@@ -215,7 +323,7 @@ ClosestPolygonPoint findClosest(Point from, Polygons& polygons)
|
||||
return best;
|
||||
}
|
||||
|
||||
ClosestPolygonPoint findClosest(Point from, PolygonRef polygon)
|
||||
ClosestPolygonPoint PolygonUtils::findClosest(Point from, PolygonRef polygon)
|
||||
{
|
||||
if (polygon.size() == 0)
|
||||
{
|
||||
@@ -235,7 +343,7 @@ ClosestPolygonPoint findClosest(Point from, PolygonRef polygon)
|
||||
if (p2_idx >= polygon.size()) p2_idx = 0;
|
||||
Point& p2 = polygon[p2_idx];
|
||||
|
||||
Point closestHere = getClosestOnLine(from, p1 ,p2);
|
||||
Point closestHere = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
|
||||
int64_t dist = vSize2(from - closestHere);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
@@ -249,35 +357,6 @@ ClosestPolygonPoint findClosest(Point from, PolygonRef polygon)
|
||||
}
|
||||
|
||||
|
||||
Point getClosestOnLine(Point from, Point p0, Point p1)
|
||||
{
|
||||
Point direction = p1 - p0;
|
||||
Point toFrom = from-p0;
|
||||
int64_t projected_x = dot(toFrom, direction) ;
|
||||
|
||||
int64_t x_p0 = 0;
|
||||
int64_t x_p1 = vSize2(direction);
|
||||
|
||||
if (projected_x <= x_p0)
|
||||
{
|
||||
return p0;
|
||||
}
|
||||
if (projected_x >= x_p1)
|
||||
{
|
||||
return p1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vSize2(direction) == 0)
|
||||
{
|
||||
std::cout << "warning! too small segment" << std::endl;
|
||||
return p0;
|
||||
}
|
||||
Point ret = p0 + projected_x / vSize(direction) * direction / vSize(direction);
|
||||
return ret ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -287,12 +366,7 @@ Point getClosestOnLine(Point from, Point p0, Point p1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool getNextPointWithDistance(Point from, int64_t dist, const PolygonRef poly, int start_idx, int poly_start_idx, GivenDistPoint& result)
|
||||
bool PolygonUtils::getNextPointWithDistance(Point from, int64_t dist, const PolygonRef poly, int start_idx, int poly_start_idx, GivenDistPoint& result)
|
||||
{
|
||||
|
||||
Point prev_poly_point = poly[(start_idx + poly_start_idx) % poly.size()];
|
||||
@@ -369,4 +443,61 @@ bool getNextPointWithDistance(Point from, int64_t dist, const PolygonRef poly, i
|
||||
|
||||
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithlineSegment(PolygonRef poly, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix)
|
||||
{
|
||||
Point p0 = transformation_matrix.apply(poly.back());
|
||||
for(Point p1_ : poly)
|
||||
{
|
||||
Point p1 = transformation_matrix.apply(p1_);
|
||||
if ((p0.Y > transformed_startPoint.Y && p1.Y < transformed_startPoint.Y) || (p1.Y > transformed_startPoint.Y && p0.Y < transformed_startPoint.Y))
|
||||
{
|
||||
int64_t x = p0.X + (p1.X - p0.X) * (transformed_startPoint.Y - p0.Y) / (p1.Y - p0.Y);
|
||||
|
||||
if (x > transformed_startPoint.X && x < transformed_endPoint.X)
|
||||
return true;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithlineSegment(PolygonRef poly, Point& startPoint, Point& endPoint)
|
||||
{
|
||||
Point diff = endPoint - startPoint;
|
||||
|
||||
PointMatrix transformation_matrix = PointMatrix(diff);
|
||||
Point transformed_startPoint = transformation_matrix.apply(startPoint);
|
||||
Point transformed_endPoint = transformation_matrix.apply(endPoint);
|
||||
|
||||
return PolygonUtils::polygonCollidesWithlineSegment(poly, transformed_startPoint, transformed_endPoint, transformation_matrix);
|
||||
}
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithlineSegment(Polygons& polys, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix)
|
||||
{
|
||||
for(PolygonRef poly : polys)
|
||||
{
|
||||
if (poly.size() == 0) { continue; }
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(poly, transformed_startPoint, transformed_endPoint, transformation_matrix))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithlineSegment(Polygons& polys, Point& startPoint, Point& endPoint)
|
||||
{
|
||||
Point diff = endPoint - startPoint;
|
||||
|
||||
PointMatrix transformation_matrix = PointMatrix(diff);
|
||||
Point transformed_startPoint = transformation_matrix.apply(startPoint);
|
||||
Point transformed_endPoint = transformation_matrix.apply(endPoint);
|
||||
|
||||
return polygonCollidesWithlineSegment(polys, transformed_startPoint, transformed_endPoint, transformation_matrix);
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+152
-73
@@ -1,20 +1,11 @@
|
||||
/** Copyright (C) 2015 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef POLYGON_UTILS_H
|
||||
#define POLYGON_UTILS_H
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_POLYGON_UTILS_H
|
||||
#define UTILS_POLYGON_UTILS_H
|
||||
|
||||
#include "polygon.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
//! performs an offset compared to an adjacent inset/outset and also computes the area created by gaps between the two consecutive insets/outsets
|
||||
void offsetExtrusionWidth(Polygons& poly, bool inward, int extrusionWidth, Polygons& result, Polygons* in_between, bool avoidOverlappingPerimeters);
|
||||
|
||||
//! performs an offset and makes sure the lines don't overlap (ignores any area between the original poly and the resulting poly)
|
||||
void offsetSafe(Polygons& poly, int distance, int extrusionWidth, Polygons& result, bool avoidOverlappingPerimeters);
|
||||
|
||||
//! performs offsets to make sure the lines don't overlap (ignores any area between the original poly and the resulting poly)
|
||||
void removeOverlapping(Polygons& poly, int extrusionWidth, Polygons& result);
|
||||
|
||||
/*!
|
||||
* Result of finding the closest point to a given within a set of polygons, with extra information on where the point is.
|
||||
@@ -37,69 +28,157 @@ struct GivenDistPoint
|
||||
Point location; //!< Result location
|
||||
int pos; //!< Index to the first point in the polygon of the line segment on which the result was found
|
||||
};
|
||||
|
||||
/*!
|
||||
* Find the two points in two polygons with the smallest distance.
|
||||
*
|
||||
* \warning The ClosestPolygonPoint::poly fields output parameters should be initialized with the polygons for which to find the smallest connection.
|
||||
*
|
||||
* \param poly1_result Output parameter: the point at the one end of the smallest connection between its poly and \p poly2_result.poly.
|
||||
* \param poly2_result Output parameter: the point at the other end of the smallest connection between its poly and \p poly1_result.poly.
|
||||
* \param sample_size The number of points on each polygon to start the hill climbing search from.
|
||||
*/
|
||||
void findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result, int sample_size);
|
||||
|
||||
/*!
|
||||
*
|
||||
* \warning Assumes \p poly1_result and \p poly2_result have their pos and poly fields initialized!
|
||||
*/
|
||||
void walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result);
|
||||
|
||||
/*!
|
||||
* Find the nearest closest point on a polygon from a given index.
|
||||
*
|
||||
* \param from The point from which to get the smallest distance.
|
||||
* \param polygon The polygon on which to find the point with the smallest distance.
|
||||
* \param start_idx The index of the point in the polygon from which to start looking.
|
||||
* \return The nearest point from \p start_idx going along the \p polygon (in both directions) with a locally minimal distance to \p from.
|
||||
*/
|
||||
ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start_idx);
|
||||
|
||||
/*!
|
||||
* Find the nearest closest point on a polygon from a given index walking in one direction along the polygon.
|
||||
*
|
||||
* \param from The point from which to get the smallest distance.
|
||||
* \param polygon The polygon on which to find the point with the smallest distance.
|
||||
* \param start_idx The index of the point in the polygon from which to start looking.
|
||||
* \param direction The direction to walk: 1 for walking along the \p polygon, -1 for walking in opposite direction
|
||||
* \return The nearest point from \p start_idx going along the \p polygon with a locally minimal distance to \p from.
|
||||
*/
|
||||
ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start_idx, int direction);
|
||||
|
||||
/*!
|
||||
* Find the point closest to \p from in all polygons in \p polygons.
|
||||
*/
|
||||
ClosestPolygonPoint findClosest(Point from, Polygons& polygons);
|
||||
|
||||
/*!
|
||||
* Find the point closest to \p from in the polygon \p polygon.
|
||||
*/
|
||||
ClosestPolygonPoint findClosest(Point from, PolygonRef polygon);
|
||||
|
||||
/*!
|
||||
* Find the point closest to \p from on the line from \p p0 to \p p1
|
||||
*/
|
||||
Point getClosestOnLine(Point from, Point p0, Point p1);
|
||||
class PolygonUtils
|
||||
{
|
||||
public:
|
||||
//! performs an offset compared to an adjacent inset/outset and also computes the area created by gaps between the two consecutive insets/outsets
|
||||
static void offsetExtrusionWidth(const Polygons& poly, bool inward, int extrusionWidth, Polygons& result, Polygons* in_between, bool removeOverlappingPerimeters);
|
||||
|
||||
/*!
|
||||
* Find the next point (going along the direction of the polygon) with a distance \p dist from the point \p from within the \p poly.
|
||||
* Returns whether another point could be found within the \p poly which can be found before encountering the point at index \p start_idx.
|
||||
* The point \p from and the polygon \p poly are assumed to lie on the same plane.
|
||||
*
|
||||
* \param start_idx the index of the prev poly point on the poly.
|
||||
* \param poly_start_idx The index of the point in the polygon which is to be handled as the start of the polygon. No point further than this point will be the result.
|
||||
*/
|
||||
bool getNextPointWithDistance(Point from, int64_t dist, const PolygonRef poly, int start_idx, int poly_start_idx, GivenDistPoint& result);
|
||||
/*!
|
||||
* performs an offset compared to an adjacent inset/outset and also computes the area created by gaps between the two consecutive insets/outsets.
|
||||
* This function allows for different extrusion widths between the two insets.
|
||||
*/
|
||||
static void offsetSafe(const Polygons& poly, int distance, int offset_first_boundary, int extrusion_width, Polygons& result, Polygons* in_between, bool removeOverlappingPerimeters);
|
||||
|
||||
//! performs an offset and makes sure the lines don't overlap (ignores any area between the original poly and the resulting poly)
|
||||
static void offsetSafe(const Polygons& poly, int distance, int extrusionWidth, Polygons& result, bool removeOverlappingPerimeters);
|
||||
|
||||
//! performs offsets to make sure the lines don't overlap (ignores any area between the original poly and the resulting poly)
|
||||
static void removeOverlapping(const Polygons& poly, int extrusionWidth, Polygons& result);
|
||||
|
||||
/*!
|
||||
* Get a point from the \p poly with a given \p offset.
|
||||
*
|
||||
* \param poly The polygon.
|
||||
* \param point_idx The index of the point in the polygon.
|
||||
* \param offset The distance the point has to be moved outward from the polygon.
|
||||
* \return A point at the given distance inward from the point on the boundary polygon.
|
||||
*/
|
||||
static Point getBoundaryPointWithOffset(PolygonRef poly, unsigned int point_idx, int64_t offset);
|
||||
|
||||
/*!
|
||||
* Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within \p distance.
|
||||
* Given a \p distance more than zero, the point will end up inside, and conversely outside.
|
||||
* When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned.
|
||||
* When the point is in/outside by less than \p distance, \p from is moved to the correct place.
|
||||
*
|
||||
* \param polygons The polygons onto which to move the point
|
||||
* \param from The point to move.
|
||||
* \param distance The distance by which to move the point.
|
||||
* \param maxDist2 The squared maximal allowed distance from the point to the nearest polygon.
|
||||
* \return The index to the polygon onto which we have moved the point.
|
||||
*/
|
||||
static unsigned int moveInside(Polygons& polygons, Point& from, int distance = 0, int64_t maxDist2 = std::numeric_limits<int64_t>::max());
|
||||
|
||||
/*!
|
||||
* Find the two points in two polygons with the smallest distance.
|
||||
*
|
||||
* Note: The amount of preliminary distance checks is quadratic in \p sample_size : `O(sample_size ^2)`.
|
||||
* Further convergence time depends on polygon size and shape.
|
||||
*
|
||||
* \warning The ClosestPolygonPoint::poly fields output parameters should be initialized with the polygons for which to find the smallest connection.
|
||||
*
|
||||
* \param poly1_result Output parameter: the point at the one end of the smallest connection between its poly and \p poly2_result.poly.
|
||||
* \param poly2_result Output parameter: the point at the other end of the smallest connection between its poly and \p poly1_result.poly.
|
||||
* \param sample_size The number of points on each polygon to start the hill climbing search from.
|
||||
*/
|
||||
static void findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result, int sample_size);
|
||||
|
||||
/*!
|
||||
*
|
||||
* \warning Assumes \p poly1_result and \p poly2_result have their pos and poly fields initialized!
|
||||
*/
|
||||
static void walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result);
|
||||
|
||||
/*!
|
||||
* Find the nearest closest point on a polygon from a given index.
|
||||
*
|
||||
* \param from The point from which to get the smallest distance.
|
||||
* \param polygon The polygon on which to find the point with the smallest distance.
|
||||
* \param start_idx The index of the point in the polygon from which to start looking.
|
||||
* \return The nearest point from \p start_idx going along the \p polygon (in both directions) with a locally minimal distance to \p from.
|
||||
*/
|
||||
static ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start_idx);
|
||||
|
||||
/*!
|
||||
* Find the nearest closest point on a polygon from a given index walking in one direction along the polygon.
|
||||
*
|
||||
* \param from The point from which to get the smallest distance.
|
||||
* \param polygon The polygon on which to find the point with the smallest distance.
|
||||
* \param start_idx The index of the point in the polygon from which to start looking.
|
||||
* \param direction The direction to walk: 1 for walking along the \p polygon, -1 for walking in opposite direction
|
||||
* \return The nearest point from \p start_idx going along the \p polygon with a locally minimal distance to \p from.
|
||||
*/
|
||||
static ClosestPolygonPoint findNearestClosest(Point from, PolygonRef polygon, int start_idx, int direction);
|
||||
|
||||
/*!
|
||||
* Find the point closest to \p from in all polygons in \p polygons.
|
||||
*/
|
||||
static ClosestPolygonPoint findClosest(Point from, Polygons& polygons);
|
||||
|
||||
/*!
|
||||
* Find the point closest to \p from in the polygon \p polygon.
|
||||
*/
|
||||
static ClosestPolygonPoint findClosest(Point from, PolygonRef polygon);
|
||||
|
||||
/*!
|
||||
* Find the next point (going along the direction of the polygon) with a distance \p dist from the point \p from within the \p poly.
|
||||
* Returns whether another point could be found within the \p poly which can be found before encountering the point at index \p start_idx.
|
||||
* The point \p from and the polygon \p poly are assumed to lie on the same plane.
|
||||
*
|
||||
* \param from The point from whitch to find a point on the polygon satisfying the conditions
|
||||
* \param start_idx the index of the prev poly point on the poly.
|
||||
* \param poly_start_idx The index of the point in the polygon which is to be handled as the start of the polygon. No point further than this point will be the result.
|
||||
*/
|
||||
static bool getNextPointWithDistance(Point from, int64_t dist, const PolygonRef poly, int start_idx, int poly_start_idx, GivenDistPoint& result);
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* Checks whether a given line segment collides with a given polygon(s).
|
||||
* The transformed_startPoint and transformed_endPoint should have the same Y coordinate.
|
||||
*
|
||||
* \param poly The polygon
|
||||
* \param transformed_startPoint The start point transformed such that it is on the same horizontal line as the end point
|
||||
* \param transformed_endPoint The end point transformed such that it is on the same horizontal line as the start point
|
||||
* \param transformation_matrix The transformation applied to the start and end point to be applied to the polygon(s)
|
||||
* \return whether the line segment collides with the boundary of the polygon(s)
|
||||
*/
|
||||
static bool polygonCollidesWithlineSegment(PolygonRef poly, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix);
|
||||
|
||||
/*!
|
||||
* Checks whether a given line segment collides with a given polygon(s).
|
||||
*
|
||||
* \param poly The polygon
|
||||
* \param startPoint The start point
|
||||
* \param endPoint The end point
|
||||
* \return whether the line segment collides with the boundary of the polygon(s)
|
||||
*/
|
||||
static bool polygonCollidesWithlineSegment(PolygonRef poly, Point& startPoint, Point& endPoint);
|
||||
|
||||
/*!
|
||||
* Checks whether a given line segment collides with a given polygon(s).
|
||||
* The transformed_startPoint and transformed_endPoint should have the same Y coordinate.
|
||||
*
|
||||
* \param poly The polygon
|
||||
* \param transformed_startPoint The start point transformed such that it is on the same horizontal line as the end point
|
||||
* \param transformed_endPoint The end point transformed such that it is on the same horizontal line as the start point
|
||||
* \param transformation_matrix The transformation applied to the start and end point to be applied to the polygon(s)
|
||||
* \return whether the line segment collides with the boundary of the polygon(s)
|
||||
*/
|
||||
static bool polygonCollidesWithlineSegment(Polygons& polys, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix);
|
||||
|
||||
/*!
|
||||
* Checks whether a given line segment collides with a given polygon(s).
|
||||
*
|
||||
* \param poly The polygon
|
||||
* \param startPoint The start point
|
||||
* \param endPoint The end point
|
||||
* \return whether the line segment collides with the boundary of the polygon(s)
|
||||
*/
|
||||
static bool polygonCollidesWithlineSegment(Polygons& polys, Point& startPoint, Point& endPoint);
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#ifndef POLYGON_DEBUG_H
|
||||
#define POLYGON_DEBUG_H
|
||||
|
||||
#define DEBUG_SCALE 50.0
|
||||
|
||||
#include "polygon.h"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace cura {
|
||||
|
||||
class PolygonDebug
|
||||
{
|
||||
private:
|
||||
FILE* f;
|
||||
public:
|
||||
|
||||
PolygonDebug(const char* filename)
|
||||
{
|
||||
f = fopen(filename, "w");
|
||||
fprintf(f, "<!DOCTYPE html><html><body>\n");
|
||||
|
||||
fprintf(f, "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style='width:%ipx;height:%ipx'>\n", 1024 * 16, 1024 * 16);
|
||||
fprintf(f, "<marker id='MidMarker' viewBox='0 0 10 10' refX='5' refY='5' markerUnits='strokeWidth' markerWidth='10' markerHeight='10' stroke='lightblue' stroke-width='2' fill='none' orient='auto'>");
|
||||
fprintf(f, "<path d='M 0 0 L 10 5 M 0 10 L 10 5'/>");
|
||||
fprintf(f, "</marker>");
|
||||
}
|
||||
|
||||
~PolygonDebug()
|
||||
{
|
||||
fprintf(f, "</svg>\n");
|
||||
fprintf(f, "</body></html>");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void write(Polygons& polygons)
|
||||
{
|
||||
fprintf(f, "<g fill-rule='evenodd' style=\"fill: gray; stroke:black;stroke-width:1\">\n");
|
||||
fprintf(f, "<path marker-mid='url(#MidMarker)' d=\"");
|
||||
for(unsigned int j=0; j<polygons.size(); j++)
|
||||
{
|
||||
PolygonRef p = polygons[j];
|
||||
for(unsigned int n=0; n<p.size(); n++)
|
||||
{
|
||||
if (n == 0)
|
||||
fprintf(f, "M");
|
||||
else
|
||||
fprintf(f, "L");
|
||||
fprintf(f, "%f,%f ", float(p[n].X)/DEBUG_SCALE, float(p[n].Y)/DEBUG_SCALE);
|
||||
}
|
||||
fprintf(f, "Z\n");
|
||||
}
|
||||
fprintf(f, "\"/>");
|
||||
fprintf(f, "</g>\n");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif//POLYGON_DEBUG_H
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "socket.h"
|
||||
#include "logoutput.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
#ifdef __WIN32
|
||||
bool wsaStartupDone = false;
|
||||
#endif
|
||||
@@ -135,3 +138,5 @@ void ClientSocket::close()
|
||||
#endif
|
||||
sockfd = -1;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -2,7 +2,9 @@
|
||||
#define SOCKET_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class ClientSocket
|
||||
{
|
||||
int sockfd;
|
||||
@@ -12,15 +14,16 @@ public:
|
||||
|
||||
void connectTo(std::string host, int port);
|
||||
|
||||
void sendInt32(int32_t nr);
|
||||
void sendInt32(int32_t nr);
|
||||
void sendFloat32(float f);
|
||||
void sendAll(const void* data, int length);
|
||||
void sendAll(const void* data, int length);
|
||||
|
||||
int32_t recvInt32();
|
||||
int32_t recvInt32();
|
||||
float recvFloat32();
|
||||
void recvAll(void* data, int length);
|
||||
|
||||
void close();
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//SOCKET_H
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#ifndef STRING_H
|
||||
#define STRING_H
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
//c++11 no longer supplies a strcasecmp, so define our own version.
|
||||
static inline int stringcasecompare(const char* a, const char* b)
|
||||
{
|
||||
@@ -16,4 +19,5 @@ static inline int stringcasecompare(const char* a, const char* b)
|
||||
return *a - *b;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
#endif//STRING_H
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
#include "wallOverlap.h"
|
||||
|
||||
#include <cmath> // isfinite
|
||||
|
||||
#include "debug.h"
|
||||
#include "utils/AABB.h" // for debug output svg html
|
||||
#include "utils/SVG.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void WallOverlapComputation::findOverlapPoints()
|
||||
{
|
||||
for (unsigned int poly_idx = 0; poly_idx < list_polygons.size(); poly_idx++)
|
||||
{
|
||||
ListPolygon& poly = list_polygons[poly_idx];
|
||||
for (unsigned int poly2_idx = 0; poly2_idx <= poly_idx; poly2_idx++)
|
||||
{
|
||||
for (ListPolygon::iterator it = poly.begin(); it != poly.end(); ++it)
|
||||
{
|
||||
ListPolyIt lpi(poly, it);
|
||||
if (poly_idx == poly2_idx)
|
||||
{
|
||||
// ListPolygon::iterator it2(it);
|
||||
// ++it2;
|
||||
// if (it2 != poly.end())
|
||||
{
|
||||
findOverlapPoints(lpi, poly2_idx, it);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
findOverlapPoints(lpi, poly2_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void WallOverlapComputation::convertPolygonsToLists(Polygons& polys, ListPolygons& result)
|
||||
{
|
||||
for (PolygonRef poly : polys)
|
||||
{
|
||||
result.emplace_back();
|
||||
for (Point& p : poly)
|
||||
{
|
||||
result.back().push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallOverlapComputation::convertListPolygonsToPolygons(ListPolygons& list_polygons, Polygons& polygons)
|
||||
{
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
polygons[poly_idx].clear();
|
||||
for (Point& p : list_polygons[poly_idx])
|
||||
{
|
||||
polygons[poly_idx].add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallOverlapComputation::findOverlapPoints(ListPolyIt from, unsigned int to_list_poly_idx)
|
||||
{
|
||||
findOverlapPoints(from, to_list_poly_idx, list_polygons[to_list_poly_idx].begin());
|
||||
}
|
||||
|
||||
void WallOverlapComputation::findOverlapPoints(ListPolyIt from_it, unsigned int to_list_poly_idx, const ListPolygon::iterator start)
|
||||
{
|
||||
ListPolygon& to_list_poly = list_polygons[to_list_poly_idx];
|
||||
Point& from = from_it.p();
|
||||
ListPolygon::iterator last_it = to_list_poly.end();
|
||||
last_it--;
|
||||
for (ListPolygon::iterator it = start; it != to_list_poly.end(); ++it)
|
||||
{
|
||||
Point& last_point = *last_it;
|
||||
Point& point = *it;
|
||||
|
||||
if ( from_it.poly == to_list_poly
|
||||
&& (
|
||||
(from_it.it == last_it || from_it.it == it) // we currently consider a linesegment directly connected to [from]
|
||||
|| (from_it.prev().it == it || from_it.next().it == last_it) // line segment from [last_point] to [point] is connected to line segment of which [from] is the other end
|
||||
)
|
||||
)
|
||||
{
|
||||
last_it = it;
|
||||
continue;
|
||||
}
|
||||
Point closest = LinearAlg2D::getClosestOnLineSegment(from, last_point, point);
|
||||
|
||||
int64_t dist2 = vSize2(closest - from);
|
||||
|
||||
if (dist2 > line_width * line_width
|
||||
|| ( from_it.poly == to_list_poly
|
||||
&& dot(from_it.next().p() - from, point - last_point) > 0
|
||||
&& dot(from - from_it.prev().p(), point - last_point) > 0 ) // line segments are likely connected, because the winding order is in the same general direction
|
||||
)
|
||||
{ // line segment too far away to have overlap
|
||||
last_it = it;
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t dist = sqrt(dist2);
|
||||
|
||||
if (closest == last_point)
|
||||
{
|
||||
addOverlapPoint(from_it, ListPolyIt(to_list_poly, last_it), dist);
|
||||
}
|
||||
else if (closest == point)
|
||||
{
|
||||
addOverlapPoint(from_it, ListPolyIt(to_list_poly, it), dist);
|
||||
}
|
||||
else
|
||||
{
|
||||
ListPolygon::iterator new_it = to_list_poly.insert(it, closest);
|
||||
addOverlapPoint(from_it, ListPolyIt(to_list_poly, new_it), dist);
|
||||
}
|
||||
|
||||
last_it = it;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool WallOverlapComputation::addOverlapPoint(ListPolyIt from, ListPolyIt to, int64_t dist)
|
||||
{
|
||||
WallOverlapPointLink link(from, to);
|
||||
WallOverlapPointLinkAttributes attr(dist, false);
|
||||
std::pair<WallOverlapPointLinks::iterator, bool> result =
|
||||
overlap_point_links.emplace(link, attr);
|
||||
|
||||
if (! result.second)
|
||||
{
|
||||
// DEBUG_PRINTLN("couldn't emplace in overlap_point_links! : ");
|
||||
result.first->second = attr;
|
||||
}
|
||||
|
||||
WallOverlapPointLinks::iterator it = result.first;
|
||||
addToPoint2LinkMap(*it->first.a.it, it);
|
||||
addToPoint2LinkMap(*it->first.b.it, it);
|
||||
|
||||
|
||||
return result.second;
|
||||
}
|
||||
|
||||
bool WallOverlapComputation::addOverlapPoint_endings(ListPolyIt from, ListPolyIt to, int64_t dist)
|
||||
{
|
||||
WallOverlapPointLink link(from, to);
|
||||
WallOverlapPointLinkAttributes attr(dist, false);
|
||||
std::pair<WallOverlapPointLinks::iterator, bool> result =
|
||||
overlap_point_links_endings.emplace(link, attr);
|
||||
|
||||
if (! result.second)
|
||||
{
|
||||
// DEBUG_PRINTLN("couldn't emplace in overlap_point_links! : ");
|
||||
result.first->second = attr;
|
||||
}
|
||||
|
||||
WallOverlapPointLinks::iterator it = result.first;
|
||||
addToPoint2LinkMap(*it->first.a.it, it);
|
||||
addToPoint2LinkMap(*it->first.b.it, it);
|
||||
|
||||
|
||||
return result.second;
|
||||
}
|
||||
|
||||
void WallOverlapComputation::addOverlapEndings()
|
||||
{
|
||||
for (std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> link_pair : overlap_point_links)
|
||||
{
|
||||
|
||||
if (link_pair.second.dist == line_width)
|
||||
{ // its ending itself
|
||||
continue;
|
||||
}
|
||||
WallOverlapPointLink& link = link_pair.first;
|
||||
const ListPolyIt& a_1 = link.a;
|
||||
const ListPolyIt& b_1 = link.b;
|
||||
// an overlap segment can be an ending in two directions
|
||||
{
|
||||
ListPolyIt a_2 = a_1.next();
|
||||
ListPolyIt b_2 = b_1.prev();
|
||||
addOverlapEnding(link_pair, a_2, b_2, a_2, b_1);
|
||||
}
|
||||
{
|
||||
ListPolyIt a_2 = a_1.prev();
|
||||
ListPolyIt b_2 = b_1.next();
|
||||
addOverlapEnding(link_pair, a_2, b_2, a_1, b_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallOverlapComputation::addOverlapEnding(std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> link_pair, const ListPolyIt& a2_it, const ListPolyIt& b2_it, const ListPolyIt& a_after_middle, const ListPolyIt& b_after_middle)
|
||||
{
|
||||
WallOverlapPointLink& link = link_pair.first;
|
||||
Point& a1 = link.a.p();
|
||||
Point& a2 = a2_it.p();
|
||||
Point& b1 = link.b.p();
|
||||
Point& b2 = b2_it.p();
|
||||
Point a = a2-a1;
|
||||
Point b = b2-b1;
|
||||
|
||||
if (point_to_link.find(a2_it.p()) == point_to_link.end()
|
||||
|| point_to_link.find(b2_it.p()) == point_to_link.end())
|
||||
{
|
||||
int64_t dist = overlapEndingDistance(a1, a2, b1, b2, link_pair.second.dist);
|
||||
if (dist < 0) { return; }
|
||||
int64_t a_length2 = vSize2(a);
|
||||
int64_t b_length2 = vSize2(b);
|
||||
if (dist*dist > std::min(a_length2, b_length2) )
|
||||
{ // TODO remove this /\ case if error below is never shown
|
||||
// DEBUG_PRINTLN("Next point should have been linked already!!");
|
||||
dist = std::sqrt(std::min(a_length2, b_length2));
|
||||
if (a_length2 < b_length2)
|
||||
{
|
||||
Point b_p = b1 + normal(b, dist);
|
||||
ListPolygon::iterator new_b = link.b.poly.insert(b_after_middle.it, b_p);
|
||||
addOverlapPoint_endings(a2_it, ListPolyIt(link.b.poly, new_b), line_width);
|
||||
}
|
||||
else if (b_length2 < a_length2)
|
||||
{
|
||||
Point a_p = a1 + normal(a, dist);
|
||||
ListPolygon::iterator new_a = link.a.poly.insert(a_after_middle.it, a_p);
|
||||
addOverlapPoint_endings(ListPolyIt(link.a.poly, new_a), b2_it, line_width);
|
||||
}
|
||||
else // equal
|
||||
{
|
||||
addOverlapPoint_endings(a2_it, b2_it, line_width);
|
||||
}
|
||||
}
|
||||
if (dist > 0)
|
||||
{
|
||||
Point a_p = a1 + normal(a, dist);
|
||||
ListPolygon::iterator new_a = link.a.poly.insert(a_after_middle.it, a_p);
|
||||
Point b_p = b1 + normal(b, dist);
|
||||
ListPolygon::iterator new_b = link.b.poly.insert(b_after_middle.it, b_p);
|
||||
addOverlapPoint_endings(ListPolyIt(link.a.poly, new_a), ListPolyIt(link.b.poly, new_b), line_width);
|
||||
}
|
||||
else if (dist == 0)
|
||||
{
|
||||
addOverlapPoint_endings(link.a, link.b, line_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t WallOverlapComputation::overlapEndingDistance(Point& a1, Point& a2, Point& b1, Point& b2, int a1b1_dist)
|
||||
{
|
||||
int overlap = line_width - a1b1_dist;
|
||||
Point a = a2-a1;
|
||||
Point b = b2-b1;
|
||||
double cos_angle = INT2MM2(dot(a, b)) / vSizeMM(a) / vSizeMM(b);
|
||||
// result == .5*overlap / tan(.5*angle) == .5*overlap / tan(.5*acos(cos_angle))
|
||||
// [wolfram alpha] == 0.5*overlap * sqrt(cos_angle+1)/sqrt(1-cos_angle)
|
||||
// [assuming positive x] == 0.5*overlap / sqrt( 2 / (cos_angle + 1) - 1 )
|
||||
if (cos_angle <= 0
|
||||
|| ! std::isfinite(cos_angle) )
|
||||
{
|
||||
return -1; // line_width / 2;
|
||||
}
|
||||
else if (cos_angle > .9999) // values near 1 can lead too large numbers for 1/x
|
||||
{
|
||||
return std::min(vSize(b), vSize(a));
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t dist = overlap * double ( 1.0 / (2.0 * sqrt(2.0 / (cos_angle+1.0) - 1.0)) );
|
||||
return dist;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void WallOverlapComputation::addSharpCorners()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void WallOverlapComputation::addToPoint2LinkMap(Point p, WallOverlapPointLinks::iterator it)
|
||||
{
|
||||
point_to_link.emplace(p, it);
|
||||
// TODO: what to do if the map already contained a link? > three-way overlap
|
||||
}
|
||||
|
||||
float WallOverlapComputation::getFlow(Point& from, Point& to)
|
||||
{
|
||||
Point2Link::iterator from_link_pair = point_to_link.find(from);
|
||||
if (from_link_pair == point_to_link.end()) { return 1; }
|
||||
WallOverlapPointLinks::iterator from_link = from_link_pair->second;
|
||||
if (!from_link->second.passed)// || !to_link->second.passed)
|
||||
{
|
||||
from_link->second.passed = true;
|
||||
// to_link->second.passed = true;
|
||||
return 1;
|
||||
}
|
||||
from_link->second.passed = true;
|
||||
|
||||
Point2Link::iterator to_link_pair = point_to_link.find(to);
|
||||
if (to_link_pair == point_to_link.end()) { return 1; }
|
||||
WallOverlapPointLinks::iterator to_link = to_link_pair->second;
|
||||
// to_link->second = true;
|
||||
|
||||
|
||||
// both points have already been passed
|
||||
|
||||
float avg_link_dist = 0.5 * ( INT2MM(from_link->second.dist) + INT2MM(to_link->second.dist) );
|
||||
|
||||
float ratio = avg_link_dist / INT2MM(line_width);
|
||||
|
||||
if (ratio > 1.0) { return 1.0; }
|
||||
|
||||
return ratio;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void WallOverlapComputation::debugCheck()
|
||||
{
|
||||
for (std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> pair : overlap_point_links)
|
||||
{
|
||||
if (std::abs(vSize( pair.first.a.p() - pair.first.b.p()) - pair.second.dist) > 10)
|
||||
DEBUG_PRINTLN(vSize( pair.first.a.p() - pair.first.b.p())<<" != " << pair.second.dist);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WallOverlapComputation::wallOverlaps2HTML(const char* filename)
|
||||
{
|
||||
AABB aabb(polygons);
|
||||
|
||||
SVG svg(filename, aabb);
|
||||
|
||||
|
||||
svg.writeAreas(polygons);
|
||||
/*
|
||||
for(PolygonsPart part : polygons.splitIntoParts())
|
||||
{
|
||||
for (unsigned int j = 0; j < part.size(); j++)
|
||||
{
|
||||
fprintf(out, "<polygon points=\"");
|
||||
for(Point& p : part[j])
|
||||
{
|
||||
Point pf = transform(p);
|
||||
fprintf(out, "%lli,%lli ", pf.Y, pf.X);
|
||||
}
|
||||
if (j == 0)
|
||||
fprintf(out, "\" style=\"fill:gray; stroke:black;stroke-width:1\" />\n");
|
||||
else
|
||||
fprintf(out, "\" style=\"fill:white; stroke:black;stroke-width:1\" />\n");
|
||||
}
|
||||
}*/
|
||||
|
||||
for (ListPolygon poly : list_polygons)
|
||||
{
|
||||
for (Point& p : poly)
|
||||
{
|
||||
svg.writePoint(p, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (std::pair<WallOverlapPointLink , WallOverlapPointLinkAttributes> link_pair : overlap_point_links)
|
||||
{
|
||||
WallOverlapPointLink& link = link_pair.first;
|
||||
Point a = svg.transform(link.a.p());
|
||||
Point b = svg.transform(link.b.p());
|
||||
svg.printf("<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:rgb(%d,%d,0);stroke-width:1\" />", a.Y, a.X, b.Y, b.X, link_pair.second.dist == line_width? 0 : 255, link_pair.second.dist==line_width? 255 : 0);
|
||||
}
|
||||
|
||||
for (std::pair<WallOverlapPointLink , WallOverlapPointLinkAttributes> link_pair : overlap_point_links_endings)
|
||||
{
|
||||
WallOverlapPointLink& link = link_pair.first;
|
||||
Point a = svg.transform(link.a.p());
|
||||
Point b = svg.transform(link.b.p());
|
||||
svg.printf("<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:rgb(%d,%d,0);stroke-width:1\" />", a.Y, a.X, b.Y, b.X, link_pair.second.dist == line_width? 0 : 255, link_pair.second.dist==line_width? 255 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,268 @@
|
||||
#ifndef WALL_OVERLAP_H
|
||||
#define WALL_OVERLAP_H
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
|
||||
#include <functional> // hash function object
|
||||
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/linearAlg2D.h"
|
||||
|
||||
#include "debug.h" // TODO remove
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for computing and compensating for overlapping (outer) wall lines.
|
||||
* The overlapping area is approximated with connected trapzoids.
|
||||
* All places where the wall is closer than the nozzle width to another piece of wall are recorded.
|
||||
* The area of a trapezoid is then the length between two such locations multiplied by the average overlap at the two locations.
|
||||
*
|
||||
* The amount of overlap between two locations is recorded in a link, so that we can look up the overlap at a given point in the polygon.
|
||||
* A link always occurs between a point already on a polygon and either another point of a polygon or a point on a line segment of a polygon.
|
||||
* In the latter case we insert the point into the polygon so that we can later look up by how much to reduce the extrusion at the corresponding line segment.
|
||||
* This is the reason that the polygons are converted to linked lists before the wall overlap compensation computation takes place, after which they are converted back.
|
||||
*
|
||||
* At the end of a sequence of trapezoids the overlap area generally ends with a residual triangle.
|
||||
* Therefore points are introduced on the line segments involved and a link is created with overlap zero.
|
||||
*
|
||||
* We end up with a mapping from each link to a boolean value representing whether the trapezoid is already compensated for.
|
||||
* Each point on the polygons then maps to a link (and its corresponding boolean), so that we can easily look up which links corresponds
|
||||
* to the current line segment being produced when producing gcode.
|
||||
*
|
||||
* When producing gcode, the first line crossing the overlap area is laid down normally and the second line is reduced by the overlap amount.
|
||||
*
|
||||
* The main functionality of this class is performed by the constructor.
|
||||
* The adjustment during gcode generation is made with the help of WallOverlapComputation::getFlow
|
||||
*/
|
||||
class WallOverlapComputation
|
||||
{
|
||||
|
||||
typedef std::list<Point> ListPolygon; //!< A polygon represented by a linked list instead of a vector
|
||||
typedef std::vector<ListPolygon> ListPolygons; //!< Polygons represented by a vector of linked lists instead of a vector of vectors
|
||||
|
||||
/*!
|
||||
* Convert Polygons to ListPolygons
|
||||
*
|
||||
* \param polys The polygons to convert
|
||||
* \param result The converted polygons
|
||||
*/
|
||||
static void convertPolygonsToLists(Polygons& polys, ListPolygons& result);
|
||||
/*!
|
||||
* Convert ListPolygons to Polygons
|
||||
*
|
||||
* \param list_polygons The polygons to convert
|
||||
* \param polygons The converted polygons
|
||||
*/
|
||||
static void convertListPolygonsToPolygons(ListPolygons& list_polygons, Polygons& polygons);
|
||||
|
||||
|
||||
/*!
|
||||
* A wrapper class for a ListPolygon::iterator and a reference to the containing ListPolygon
|
||||
*/
|
||||
struct ListPolyIt
|
||||
{
|
||||
ListPolygon& poly; //!< The polygon
|
||||
ListPolygon::iterator it; //!< The iterator into ListPolyIt::poly
|
||||
ListPolyIt(const ListPolyIt& other)
|
||||
: poly(other.poly), it(other.it) { }
|
||||
ListPolyIt(ListPolygon& poly, ListPolygon::iterator it)
|
||||
: poly(poly), it(it) { }
|
||||
Point& p() const { return *it; }
|
||||
bool operator==(const ListPolyIt& other) const { return poly == other.poly && it == other.it; }
|
||||
void operator=(const ListPolyIt& other) { poly = other.poly; it = other.it; }
|
||||
//!< move the iterator forward (and wrap around at the end)
|
||||
ListPolyIt& operator++()
|
||||
{
|
||||
++it;
|
||||
if (it == poly.end()) { it = poly.begin(); }
|
||||
return *this;
|
||||
}
|
||||
//!< move the iterator backward (and wrap around at the beginning)
|
||||
ListPolyIt& operator--()
|
||||
{
|
||||
if (it == poly.begin()) { it = poly.end(); }
|
||||
--it;
|
||||
return *this;
|
||||
}
|
||||
ListPolyIt next() const
|
||||
{
|
||||
ListPolyIt ret(*this);
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
ListPolyIt prev() const
|
||||
{
|
||||
ListPolyIt ret(*this);
|
||||
--ret;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
/*!
|
||||
* A class recording the amount of overlap implicitly by recording the distance between two points on two different polygons or one and the same polygon.
|
||||
* The order of the two points doesn't matter.
|
||||
*/
|
||||
struct WallOverlapPointLink
|
||||
{
|
||||
const ListPolyIt a; //!< the one point (invalidated after list_polygons have been cleared!)
|
||||
const ListPolyIt b; //!< the other point (invalidated after list_polygons have been cleared!)
|
||||
WallOverlapPointLink(const ListPolyIt a, const ListPolyIt b) : a(a), b(b) { }
|
||||
bool operator==(const WallOverlapPointLink& other) const { return (a == other.a && b == other.b) || (a == other.b && b == other.a); }
|
||||
};
|
||||
|
||||
/*!
|
||||
* The hash function object for WallOverlapPointLink
|
||||
*/
|
||||
struct WallOverlapPointLink_Hasher
|
||||
{
|
||||
std::size_t operator()(const WallOverlapPointLink& pp) const
|
||||
{
|
||||
return std::hash<Point>()(*pp.a.it) + std::hash<Point>()(*pp.b.it);
|
||||
}
|
||||
};
|
||||
|
||||
struct WallOverlapPointLinkAttributes
|
||||
{
|
||||
int dist; //!< The distance between the two points
|
||||
bool passed; //!< Whether this point has been passed while writing gcode
|
||||
WallOverlapPointLinkAttributes(int dist, bool passed) : dist(dist), passed(passed) { }
|
||||
};
|
||||
|
||||
typedef std::unordered_map<WallOverlapPointLink, WallOverlapPointLinkAttributes, WallOverlapPointLink_Hasher> WallOverlapPointLinks; //!< The type of WallOverlapComputation::overlap_point_links
|
||||
typedef std::unordered_map<Point, WallOverlapPointLinks::iterator> Point2Link; //!< The type of WallOverlapComputation::point_to_link
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
Polygons& polygons; //!< The polygons for which to compensate overlapping walls for
|
||||
ListPolygons list_polygons; //!< The WallOverlapComputation::polygons converted
|
||||
|
||||
int line_width; //!< The line width of the walls
|
||||
|
||||
WallOverlapPointLinks overlap_point_links; //!< mapping from each link to its attributes
|
||||
WallOverlapPointLinks overlap_point_links_endings; //!< mapping from each ending link to its attributes (which has a distance field equal to WallOverlapComputation::line_width). Note that this is a separate map from WallOverlapComputation::overlap_point_links, because that magically solved a bug .
|
||||
|
||||
Point2Link point_to_link; //!< mapping from each point to the/a corresponding link (collisions are ignored as of yet)
|
||||
|
||||
void findOverlapPoints(); //!< find the basic overlap links (for trapezoids) and record them into WallOverlapComputation::overlap_point_links
|
||||
/*!
|
||||
* find the basic overlap links (for trapezoids) between a given point and a polygon and record them into WallOverlapComputation::overlap_point_links
|
||||
*
|
||||
* \param from The point from which to check for overlap
|
||||
* \param to_list_poly_idx The index into WallOverlapComputation::list_polygons for the polygon to check
|
||||
*/
|
||||
void findOverlapPoints(ListPolyIt from, unsigned int to_list_poly_idx);
|
||||
/*!
|
||||
* Find the basic overlap links (for trapezoids) between a given point and a polygon up from a particular index and record them into WallOverlapComputation::overlap_point_links
|
||||
*
|
||||
* This function is used for finding overlaps within a single polygon. It then uses a \p start different from the first point in the polygon.
|
||||
*
|
||||
* \param from The point from which to check for overlap
|
||||
* \param to_list_poly_idx The index into WallOverlapComputation::list_polygons for the polygon to check
|
||||
* \param start Where to start looking into the polygon with index \p to_list_poly_idx
|
||||
*/
|
||||
void findOverlapPoints(ListPolyIt from, unsigned int to_list_poly_idx, const ListPolygon::iterator start);
|
||||
|
||||
/*!
|
||||
* Add a link between \p from and \p to to WallOverlapComputation::overlap_point_links and add the appropriate mappings to WallOverlapComputation::point_to_link
|
||||
*
|
||||
* \param from The one point of the link
|
||||
* \param to The other point of the link
|
||||
* \param dist The distance between the two points
|
||||
* \return Whether the point has been added
|
||||
*/
|
||||
bool addOverlapPoint(ListPolyIt from, ListPolyIt to, int64_t dist);
|
||||
/*!
|
||||
* Add a link between \p from and \p to to WallOverlapComputation::overlap_point_links_endings and add the appropriate mappings to WallOverlapComputation::point_to_link
|
||||
*
|
||||
* \param from The one point of the link
|
||||
* \param to The other point of the link
|
||||
* \param dist The distance between the two points
|
||||
* \return Whether the point has been added
|
||||
*/
|
||||
bool addOverlapPoint_endings(ListPolyIt from, ListPolyIt to, int64_t dist);
|
||||
|
||||
/*!
|
||||
* Add links for the ending points of overlap regions, supporting the residual triangles.
|
||||
*/
|
||||
void addOverlapEndings();
|
||||
|
||||
/*!
|
||||
* Add a link for the ending point of a given overlap region, if it is an ending.
|
||||
*
|
||||
* \param link_pair The link which might be an ending
|
||||
* \param a_next The next point from ListPolyIt::a of \p link
|
||||
* \param b_next The next point from ListPolyIt::b of \p link (in the opposite direction of \p a_next)
|
||||
* \param a_before_middle Where to insert a new point for a if this is indeed en ending
|
||||
* \param b_before_middle Where to insert a new point for b if this is indeed en ending
|
||||
*/
|
||||
void addOverlapEnding(std::pair<WallOverlapPointLink, WallOverlapPointLinkAttributes> link_pair, const ListPolyIt& a_next, const ListPolyIt& b_next, const ListPolyIt& a_before_middle, const ListPolyIt& b_before_middle);
|
||||
|
||||
/*!
|
||||
* Compute the distance between the points of the last link and the points introduced to account for the overlap endings.
|
||||
*/
|
||||
int64_t overlapEndingDistance(Point& a1, Point& a2, Point& b1, Point& b2, int a1b1_dist);
|
||||
|
||||
|
||||
/*!
|
||||
* Add overlap links for sharp corners, so that the overlap of two consecutive line segments is compensated for.
|
||||
*
|
||||
* Currently UNIMPLEMENTED.
|
||||
*/
|
||||
void addSharpCorners();
|
||||
|
||||
/*!
|
||||
* Map a point to a link in WallOverlapComputation::point_to_link
|
||||
*
|
||||
* \param p The key
|
||||
* \param it The value
|
||||
*/
|
||||
void addToPoint2LinkMap(Point p, WallOverlapPointLinks::iterator it);
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Compute the flow for a given line segment in the wall.
|
||||
*
|
||||
* \param from The beginning of the line segment
|
||||
* \param to The ending of the line segment
|
||||
* \return a value between zero and one representing the reduced flow of the line segment
|
||||
*/
|
||||
float getFlow(Point& from, Point& to);
|
||||
|
||||
void debugCheck(); //!< debug
|
||||
|
||||
void wallOverlaps2HTML(const char* filename); //!< debug
|
||||
|
||||
/*!
|
||||
* Computes the neccesary priliminaries in order to efficiently compute the flow when generatign gcode paths.
|
||||
* \param polygons The wall polygons for which to compute the overlaps
|
||||
*/
|
||||
WallOverlapComputation(Polygons& polygons, int lineWidth) : polygons(polygons), line_width(lineWidth)
|
||||
{
|
||||
// convert to list polygons for insertion of points
|
||||
convertPolygonsToLists(polygons, list_polygons);
|
||||
|
||||
findOverlapPoints();
|
||||
addOverlapEndings();
|
||||
// TODO: add sharp corners
|
||||
|
||||
// convert list polygons back
|
||||
convertListPolygonsToPolygons(list_polygons, polygons);
|
||||
// wallOverlaps2HTML("output/output.html");
|
||||
// list_polygons.clear(); // clear up some space! (unneccesary? it's just for the time the gcode is being generated...)
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
|
||||
#endif//WALL_OVERLAP_H
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "utils/polygon.h"
|
||||
#include "mesh.h"
|
||||
#include "gcodePlanner.h"
|
||||
#include "MeshGroup.h"
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
@@ -70,6 +71,7 @@ struct WeaveLayer : WeaveConnection
|
||||
};
|
||||
struct WireFrame
|
||||
{
|
||||
MeshGroup* meshgroup;
|
||||
WeaveRoof bottom_infill;
|
||||
Polygons bottom_outline;
|
||||
int z_bottom;
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário