Comparar commits
246 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 1730923083 | |||
| 8e5a936522 | |||
| d29da8f7f8 | |||
| ad56ce8866 | |||
| 565b09e37d | |||
| edc9767912 | |||
| ce6f58f136 | |||
| 5b941d07ff | |||
| 350b42052a | |||
| 0ba152d081 | |||
| 2c70c2ecb7 | |||
| 4da8c6d8e7 | |||
| c5ce425924 | |||
| c74d4f7550 | |||
| 19f093e0cc | |||
| 2a4cca0402 | |||
| 32804c102c | |||
| 776f56fc37 | |||
| a92cd23e62 | |||
| 4725001564 | |||
| a18595877f | |||
| 40e7c450d5 | |||
| 13a0b11d68 | |||
| 4fc69f608a | |||
| deb577d559 | |||
| c8161da3eb | |||
| 87733834d1 | |||
| b9aeea425f | |||
| 44b3039db6 | |||
| 6476270cf6 | |||
| e532f3ddda | |||
| 7aaab151e7 | |||
| 0353433919 | |||
| 40a6c495c7 | |||
| cfa6758911 | |||
| f6b29b1d8a | |||
| bd79a8468e | |||
| 35dcc6906f | |||
| db7bc279ee | |||
| a7ea623266 | |||
| 724589c13a | |||
| db1fa098ad | |||
| be99db30c0 | |||
| 9456592dd7 | |||
| 20cd4275fc | |||
| 67697d5258 | |||
| 7d8e4de7ba | |||
| 2486120a38 | |||
| 43c8f2a913 | |||
| dd8b57b666 | |||
| 72ed492a61 | |||
| c02482590f | |||
| f32e2d9554 | |||
| bf61814849 | |||
| 5741e79ade | |||
| 09419fd6be | |||
| a002f4b3b2 | |||
| 25c7ccb0d3 | |||
| 58a99a403b | |||
| dd8594b200 | |||
| 6df5368cb9 | |||
| 3a038a2cd2 | |||
| 9613e186a3 | |||
| 82b2362b2d | |||
| a6ee34602c | |||
| 05d29eabcd | |||
| f7e3534a79 | |||
| bd4972e466 | |||
| 0acf3beec2 | |||
| 081055be46 | |||
| 560d23d3f2 | |||
| 44e7b0e0be | |||
| 8c48d1ce82 | |||
| 8432e9ed9f | |||
| 1b014199c9 | |||
| 7e0c5c6323 | |||
| 9ada0901a6 | |||
| d60d32a5d8 | |||
| 667c00aa5a | |||
| cf0ca05843 | |||
| 3bcabacef4 | |||
| b5801ea847 | |||
| 1bb90a2f03 | |||
| cf55aef52b | |||
| c0f7538fdb | |||
| 395ab9b7cd | |||
| 80ecabb618 | |||
| ff291cc4d1 | |||
| 4183835d2b | |||
| 0e24d8db47 | |||
| f7bde54869 | |||
| bf8f027a97 | |||
| 60625ea4bf | |||
| a9f6ae1943 | |||
| cd01d7051b | |||
| bd126bb841 | |||
| f1b3fb3cd6 | |||
| d48f06db0e | |||
| 5567b06ed4 | |||
| e87b11179f | |||
| a17fef9d4b | |||
| 4c49bf7894 | |||
| bcb0ded784 | |||
| 54ba25e7f5 | |||
| 84a7f401a2 | |||
| 6afdf19ce4 | |||
| 4aa1cc47f4 | |||
| 166473596a | |||
| feb21b67d1 | |||
| dae7ec184f | |||
| cd170cae99 | |||
| d47d0a2e46 | |||
| f0d59db203 | |||
| f34a4e566b | |||
| 9c1ef177d1 | |||
| e1c7e86b66 | |||
| 79d1074d47 | |||
| 348ca93dcb | |||
| e70b3c099f | |||
| 8c9858a14c | |||
| 6e56cb3416 | |||
| 8761ae1e53 | |||
| d32be27b50 | |||
| 11b33215a2 | |||
| e8a31f1380 | |||
| 7cbfb22f1f | |||
| 40e6aac22b | |||
| 33cc601a1b | |||
| d50b0c9c2f | |||
| 77f415be61 | |||
| 1aa784e521 | |||
| ef8258da2f | |||
| 79692200b0 | |||
| 966912ccc5 | |||
| 1513dcad5c | |||
| 41050ac835 | |||
| fb8625756f | |||
| 886bab29f7 | |||
| 5bdf538d35 | |||
| 7d5040e283 | |||
| a49ed9a90e | |||
| 7a4e732f3b | |||
| 653ce82255 | |||
| 0b1df81945 | |||
| f65993c5b6 | |||
| df91b3d8aa | |||
| 4b58ab0ad9 | |||
| 9cbdfd2152 | |||
| 4a0a0088fe | |||
| dc37384ffd | |||
| 2a21e6c348 | |||
| 30011a5285 | |||
| 965b28e009 | |||
| 1b37007003 | |||
| 5cf0bcd399 | |||
| 0930d61dad | |||
| a181977ce9 | |||
| 3ab19f2f29 | |||
| c4a3830838 | |||
| d546c27462 | |||
| 7664e81aa2 | |||
| aa2ce7c770 | |||
| f63fc1ed74 | |||
| ac183f4e4b | |||
| 4f3c337cc7 | |||
| 1e24769061 | |||
| 02134eb822 | |||
| b436da841a | |||
| fa95e56b76 | |||
| 6bed19c295 | |||
| 2a20853b92 | |||
| 09ad6301e7 | |||
| d8fa29e979 | |||
| ab91cc2def | |||
| cd4e2e29a3 | |||
| 0f454e897b | |||
| c1d53811f3 | |||
| 0c395c5bfa | |||
| f312f15813 | |||
| 406ea7155c | |||
| f0f23ed732 | |||
| 0a59a059f4 | |||
| e902fb3fc9 | |||
| c7be93e850 | |||
| fe8207fb1b | |||
| d1fbe96d6f | |||
| ccc89e6263 | |||
| d235de6ca2 | |||
| 4d98e07eb4 | |||
| 7c3f69a5bf | |||
| 846bb1109a | |||
| 8c5ba49068 | |||
| 8e18139ae9 | |||
| 410d42ccb3 | |||
| 2c35ba595c | |||
| 8f04afbff9 | |||
| 1fd5355290 | |||
| 65b6c48391 | |||
| 34c0960cc2 | |||
| d6175e8269 | |||
| 47984afb5f | |||
| c86823033d | |||
| 6c157b4e1f | |||
| 97b8d63547 | |||
| f0536be401 | |||
| 2c983ce39c | |||
| a3eb8ebb2d | |||
| bb98cb983e | |||
| a67f7465c1 | |||
| 9b8ef3981a | |||
| 24a22eec82 | |||
| 4d34cbc66b | |||
| 405a2b2a5f | |||
| 17260b0272 | |||
| 2785baf7be | |||
| c4bc6b8d19 | |||
| a29af7f791 | |||
| f304c09db4 | |||
| d50f67e583 | |||
| b2d837efde | |||
| 5c680b312b | |||
| 0a683ff05e | |||
| 0dc6dcbb34 | |||
| dbb48c82bf | |||
| 2a1e4da930 | |||
| dd481bcc2e | |||
| f68d1a4e2d | |||
| 4e96d9cbe6 | |||
| 478bd31d02 | |||
| faab907bab | |||
| dff554863c | |||
| f2222f97fd | |||
| aa18e7bd08 | |||
| 07dc53765a | |||
| 559deb8914 | |||
| 421ff6d818 | |||
| e85a1004cd | |||
| 627848bc41 | |||
| eccc62cf1d | |||
| 1d251fef70 | |||
| 16a0cf0fd5 | |||
| 58e2e1a4e1 | |||
| 4ebbceb3e3 | |||
| ace0045109 | |||
| 7a4a7fe46a | |||
| 977d02a9a2 |
@@ -66,6 +66,7 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/MeshGroup.cpp
|
||||
src/multiVolumes.cpp
|
||||
src/pathOrderOptimizer.cpp
|
||||
src/Preheat.cpp
|
||||
src/PrimeTower.cpp
|
||||
src/raft.cpp
|
||||
src/skin.cpp
|
||||
|
||||
+1
-1
@@ -832,7 +832,7 @@ EXAMPLE_RECURSIVE = NO
|
||||
# that contain images that are to be included in the documentation (see the
|
||||
# \image command).
|
||||
|
||||
IMAGE_PATH = documentation/assets
|
||||
IMAGE_PATH = docs/assets
|
||||
|
||||
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
||||
# invoke to filter for each input file. Doxygen will invoke the filter program
|
||||
|
||||
@@ -31,6 +31,7 @@ CMake compilation:
|
||||
4. ```$ make```
|
||||
|
||||
Project files generation:
|
||||
|
||||
1. Navigate to the CuraEngine directory and execute the following commands
|
||||
2. ```cmake . -G "CodeBlocks - Unix Makefiles"```
|
||||
3. (for a list of supported IDE's see http://www.cmake.org/Wiki/CMake_Generator_Specific_Information#Code::Blocks_Generator)
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 18 KiB Depois Largura: | Altura: | Tamanho: 18 KiB |
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 70 KiB |
|
Antes Largura: | Altura: | Tamanho: 20 KiB Depois Largura: | Altura: | Tamanho: 20 KiB |
@@ -7,4 +7,4 @@ This is the documentation for CuraEngine, the back-end slicer of Cura.
|
||||
|
||||
[Glossary](documentation/glossary.md)
|
||||
|
||||
[Code Conventions](documentation/code_conventions.md)
|
||||
[Code Conventions](https://github.com/Ultimaker/Meta/blob/master/code_conventions.md)
|
||||
@@ -1 +0,0 @@
|
||||
html/index.html
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Command line setting defaults CuraEngine",
|
||||
"metadata":
|
||||
{
|
||||
"author": "Ultimaker B.V."
|
||||
},
|
||||
"settings": {
|
||||
"command_line_settings": {
|
||||
"label": "Command Line Settings",
|
||||
"type": "category",
|
||||
"children": {
|
||||
"center_object": {
|
||||
"description": "Whether to center the object on the middle of the build platform (0,0), instead of using the coordinate system in which the object was saved.",
|
||||
"type": "bool",
|
||||
"label": "Center object",
|
||||
"default_value": true
|
||||
},
|
||||
"machine_print_temp_wait": {
|
||||
"description": "Whether to wait for the nozzle temperature to be reached when preheating the nozzles at the start of the gcode.",
|
||||
"type": "bool",
|
||||
"label": "Machine print temp wait",
|
||||
"default_value": true
|
||||
},
|
||||
"mesh_position_x": {
|
||||
"description": "Offset applied to the object in the x direction.",
|
||||
"type": "float",
|
||||
"label": "Mesh position x",
|
||||
"default_value": 0
|
||||
},
|
||||
"mesh_position_y": {
|
||||
"description": "Offset applied to the object in the y direction.",
|
||||
"type": "float",
|
||||
"label": "Mesh position y",
|
||||
"default_value": 0
|
||||
},
|
||||
"mesh_position_z": {
|
||||
"description": "Offset applied to the object in the z direction. With this you can perform what was used to call 'Object Sink'.",
|
||||
"type": "float",
|
||||
"label": "Mesh position z",
|
||||
"default_value": 0
|
||||
},
|
||||
"mesh_rotation_matrix": {
|
||||
"label": "Mesh Rotation Matrix",
|
||||
"description": "Transformation matrix to be applied to the model when loading it from file.",
|
||||
"type": "string",
|
||||
"default_value": "[[1,0,0], [0,1,0], [0,0,1]]"
|
||||
},
|
||||
"prime_tower_dir_outward": {
|
||||
"description": "Whether to start printing in the middle of the prime tower and end up at the perimeter, or the other way around. This is only used for certain types of prime tower.",
|
||||
"type": "bool",
|
||||
"label": "Prime tower direction outward",
|
||||
"default_value": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ struct FanSpeedLayerTimeSettings
|
||||
public:
|
||||
double cool_min_layer_time;
|
||||
double cool_min_layer_time_fan_speed_max;
|
||||
double cool_fan_speed_0;
|
||||
double cool_fan_speed_min;
|
||||
double cool_fan_speed_max;
|
||||
double cool_min_speed;
|
||||
|
||||
+231
-145
@@ -105,12 +105,14 @@ void FffGcodeWriter::setConfigFanSpeedLayerTime(SliceDataStorage& storage)
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extr);
|
||||
fan_speed_layer_time_settings.cool_min_layer_time = train->getSettingInSeconds("cool_min_layer_time");
|
||||
fan_speed_layer_time_settings.cool_min_layer_time_fan_speed_max = train->getSettingInSeconds("cool_min_layer_time_fan_speed_max");
|
||||
fan_speed_layer_time_settings.cool_fan_speed_0 = train->getSettingInPercentage("cool_fan_speed_0");
|
||||
fan_speed_layer_time_settings.cool_fan_speed_min = train->getSettingInPercentage("cool_fan_speed_min");
|
||||
fan_speed_layer_time_settings.cool_fan_speed_max = train->getSettingInPercentage("cool_fan_speed_max");
|
||||
fan_speed_layer_time_settings.cool_min_speed = train->getSettingInMillimetersPerSecond("cool_min_speed");
|
||||
fan_speed_layer_time_settings.cool_fan_full_layer = train->getSettingAsLayerNumber("cool_fan_full_layer");
|
||||
if (!train->getSettingBoolean("cool_fan_enabled"))
|
||||
{
|
||||
fan_speed_layer_time_settings.cool_fan_speed_0 = 0;
|
||||
fan_speed_layer_time_settings.cool_fan_speed_min = 0;
|
||||
fan_speed_layer_time_settings.cool_fan_speed_max = 0;
|
||||
}
|
||||
@@ -207,9 +209,9 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
{
|
||||
if (getSettingBoolean("material_bed_temp_prepend"))
|
||||
{
|
||||
if (getSettingBoolean("machine_heated_bed") && getSettingInDegreeCelsius("material_bed_temperature") > 0)
|
||||
if (getSettingBoolean("machine_heated_bed") && getSettingInDegreeCelsius("material_bed_temperature_layer_0") > 0)
|
||||
{
|
||||
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature"), getSettingBoolean("material_bed_temp_wait"));
|
||||
gcode.writeBedTemperatureCommand(getSettingInDegreeCelsius("material_bed_temperature_layer_0"), getSettingBoolean("material_bed_temp_wait"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,15 +219,19 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < storage.getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{
|
||||
double print_temp = storage.meshgroup->getExtruderTrain(extruder_nr)->getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(extruder_nr, print_temp);
|
||||
ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
double print_temp_0 = train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
double print_temp_here = (print_temp_0 != 0)? print_temp_0 : train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(extruder_nr, print_temp_here);
|
||||
}
|
||||
if (getSettingBoolean("material_print_temp_wait"))
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < storage.getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{
|
||||
double print_temp = storage.meshgroup->getExtruderTrain(extruder_nr)->getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(extruder_nr, print_temp, true);
|
||||
ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
double print_temp_0 = train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
double print_temp_here = (print_temp_0 != 0)? print_temp_0 : train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(extruder_nr, print_temp_here, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,7 +253,9 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
gcode.startExtruder(start_extruder_nr);
|
||||
ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(start_extruder_nr);
|
||||
constexpr bool wait = true;
|
||||
gcode.writeTemperatureCommand(start_extruder_nr, train.getSettingInDegreeCelsius("material_print_temperature"), wait);
|
||||
double print_temp_0 = train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
double print_temp_here = (print_temp_0 != 0)? print_temp_0 : train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(start_extruder_nr, print_temp_here, wait);
|
||||
gcode.writePrimeTrain(train.getSettingInMillimetersPerSecond("speed_travel"));
|
||||
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[start_extruder_nr];
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
@@ -257,8 +265,13 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
void FffGcodeWriter::processNextMeshGroupCode(SliceDataStorage& storage)
|
||||
{
|
||||
gcode.writeFanCommand(0);
|
||||
|
||||
bool wait = true;
|
||||
gcode.writeBedTemperatureCommand(storage.getSettingInDegreeCelsius("material_bed_temperature_layer_0"), wait);
|
||||
|
||||
gcode.resetExtrusionValue();
|
||||
CommandSocket::setSendCurrentPosition(gcode.getPositionXY());
|
||||
|
||||
gcode.setZ(max_object_height + 5000);
|
||||
gcode.writeMove(gcode.getPositionXY(), storage.meshgroup->getExtruderTrain(gcode.getExtruderNr())->getSettingInMillimetersPerSecond("speed_travel"), 0);
|
||||
last_position_planned = Point(storage.model_min.x, storage.model_min.y);
|
||||
@@ -299,8 +312,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
int layer_height = train->getSettingInMicrons("raft_base_thickness");
|
||||
z += layer_height;
|
||||
int64_t comb_offset = train->getSettingInMicrons("raft_base_line_spacing");
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings_per_extruder, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
gcode_layer.setIsInside(true);
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings_per_extruder, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") > 0)
|
||||
{
|
||||
@@ -322,7 +334,8 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
|
||||
|
||||
ensureAllExtrudersArePrimed(storage, gcode_layer, layer_nr);
|
||||
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
gcode_layer.overrideFanSpeeds(train->getSettingInPercentage("raft_base_fan_speed"));
|
||||
@@ -333,8 +346,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
int layer_height = train->getSettingInMicrons("raft_interface_thickness");
|
||||
z += layer_height;
|
||||
int64_t comb_offset = train->getSettingInMicrons("raft_interface_line_spacing");
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings_per_extruder, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
gcode_layer.setIsInside(true);
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings_per_extruder, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
@@ -350,7 +362,6 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
|
||||
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
gcode_layer.overrideFanSpeeds(train->getSettingInPercentage("raft_interface_fan_speed"));
|
||||
@@ -363,8 +374,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
const int layer_nr = initial_raft_layer_nr + 2 + raftSurfaceLayer - 1; // 2: 1 base layer, 1 interface layer
|
||||
z += layer_height;
|
||||
const int64_t comb_offset = train->getSettingInMicrons("raft_surface_line_spacing");
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings_per_extruder, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
gcode_layer.setIsInside(true);
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings_per_extruder, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
@@ -380,8 +390,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
|
||||
|
||||
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
gcode_layer.overrideFanSpeeds(train->getSettingInPercentage("raft_surface_fan_speed"));
|
||||
}
|
||||
@@ -447,7 +456,7 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, int layer_nr, unsig
|
||||
|
||||
|
||||
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_thickness, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings_per_extruder, getSettingAsCombingMode("retraction_combing"), comb_offset_from_outlines, avoid_other_parts, avoid_distance);
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_thickness, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings_per_extruder, getSettingAsCombingMode("retraction_combing"), comb_offset_from_outlines, avoid_other_parts, avoid_distance);
|
||||
|
||||
if (include_helper_parts && layer_nr == 0)
|
||||
{ // process the skirt or the brim of the starting extruder.
|
||||
@@ -459,64 +468,67 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, int layer_nr, unsig
|
||||
}
|
||||
}
|
||||
|
||||
int extruder_nr_before = gcode_layer.getExtruder();
|
||||
if (include_helper_parts)
|
||||
int support_skin_extruder_nr = getSettingAsIndex("support_interface_extruder_nr");
|
||||
int support_infill_extruder_nr = (layer_nr <= 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
|
||||
//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<int> extruder_order = calculateExtruderOrder(storage, gcode_layer.getExtruder());
|
||||
for (int extruder_nr : extruder_order)
|
||||
{
|
||||
addSupportToGCode(storage, gcode_layer, std::max(0, layer_nr), extruder_nr_before, true);
|
||||
|
||||
processOozeShield(storage, gcode_layer, std::max(0, layer_nr));
|
||||
|
||||
processDraftShield(storage, gcode_layer, std::max(0, layer_nr));
|
||||
}
|
||||
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
//Figure out in which order to print the meshes, do this by looking at the current extruder and preferer the meshes that use that extruder.
|
||||
std::vector<unsigned int> mesh_order = calculateMeshOrder(storage, gcode_layer.getExtruder());
|
||||
for(unsigned int mesh_idx : mesh_order)
|
||||
if (include_helper_parts
|
||||
&& (extruder_nr == support_infill_extruder_nr || extruder_nr == support_skin_extruder_nr))
|
||||
{
|
||||
SliceMeshStorage* mesh = &storage.meshes[mesh_idx];
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
addSupportToGCode(storage, gcode_layer, layer_nr, extruder_nr);
|
||||
}
|
||||
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
std::vector<unsigned int> mesh_order = calculateMeshOrder(storage, extruder_nr);
|
||||
for (unsigned int mesh_idx : mesh_order)
|
||||
{
|
||||
addMeshLayerToGCode_meshSurfaceMode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addMeshLayerToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
SliceMeshStorage* mesh = &storage.meshes[mesh_idx];
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
{
|
||||
addMeshLayerToGCode_meshSurfaceMode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addMeshLayerToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (include_helper_parts)
|
||||
{
|
||||
addSupportToGCode(storage, gcode_layer, std::max(0, layer_nr), extruder_nr_before, false);
|
||||
}
|
||||
|
||||
if (include_helper_parts && layer_nr == 0)
|
||||
{ //Add skirt for all extruders which haven't primed the skirt or brim yet.
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
if (gcode.getExtruderIsUsed(extruder_nr) && !skirt_brim_is_processed[extruder_nr] && storage.skirt_brim[extruder_nr].size() > 0)
|
||||
{
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, extruder_nr);
|
||||
}
|
||||
}
|
||||
{
|
||||
ensureAllExtrudersArePrimed(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
|
||||
if (include_helper_parts)
|
||||
{ // add prime tower if it hasn't already been added
|
||||
// print the prime tower if it hasn't been printed yet
|
||||
int prev_extruder = gcode_layer.getExtruder(); // most likely the same extruder as we are extruding with now
|
||||
addPrimeTower(storage, gcode_layer, std::max(0, layer_nr), prev_extruder);
|
||||
addPrimeTower(storage, gcode_layer, layer_nr, prev_extruder);
|
||||
}
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
|
||||
|
||||
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
}
|
||||
|
||||
void FffGcodeWriter::ensureAllExtrudersArePrimed(SliceDataStorage& storage, GCodePlanner& gcode_layer, const int layer_nr)
|
||||
{
|
||||
//Add skirt for all extruders which haven't primed the skirt or brim yet.
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
if (gcode.getExtruderIsUsed(extruder_nr) && !skirt_brim_is_processed[extruder_nr])
|
||||
{
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, extruder_nr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processSkirtBrim(SliceDataStorage& storage, GCodePlanner& gcode_layer, unsigned int extruder_nr)
|
||||
{
|
||||
if (skirt_brim_is_processed[extruder_nr])
|
||||
@@ -575,30 +587,33 @@ void FffGcodeWriter::processDraftShield(SliceDataStorage& storage, GCodePlanner&
|
||||
gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, &storage.skirt_brim_config[0]); //TODO: Skirt and brim configuration index should correspond to draft shield extruder number.
|
||||
}
|
||||
|
||||
std::vector<unsigned int> FffGcodeWriter::calculateMeshOrder(SliceDataStorage& storage, int current_extruder)
|
||||
std::vector<int> FffGcodeWriter::calculateExtruderOrder(SliceDataStorage& storage, int current_extruder)
|
||||
{
|
||||
int extruder_count = storage.getSettingAsCount("machine_extruder_count");
|
||||
std::vector<int> ret;
|
||||
ret.push_back(current_extruder);
|
||||
for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
if (extruder_nr == current_extruder)
|
||||
{ // skip the current extruder, it's the one we started out planning
|
||||
continue;
|
||||
}
|
||||
ret.push_back(extruder_nr);
|
||||
}
|
||||
assert(ret.size() == (size_t)extruder_count && "All extruders must be planned, even if later it appears one wasn't used.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> FffGcodeWriter::calculateMeshOrder(SliceDataStorage& storage, int extruder_nr)
|
||||
{
|
||||
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 (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
{
|
||||
for(auto add_it = add_list.begin(); add_it != add_list.end(); )
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
if (mesh.getSettingAsIndex("extruder_nr") == extruder_nr)
|
||||
{
|
||||
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;
|
||||
}
|
||||
ret.push_back(mesh_idx);
|
||||
}
|
||||
if (add_list.size() > 0)
|
||||
add_extruder_nr = storage.meshes[*add_list.begin()].getSettingAsIndex("extruder_nr");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -609,7 +624,14 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(SliceDataStorage& stora
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| mesh->getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, mesh->getSettingAsIndex("extruder_nr"));
|
||||
|
||||
SliceLayer* layer = &mesh->layers[layer_nr];
|
||||
@@ -622,7 +644,7 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(SliceDataStorage& stora
|
||||
}
|
||||
|
||||
EZSeamType z_seam_type = mesh->getSettingAsZSeamType("z_seam_type");
|
||||
gcode_layer.addPolygonsByOptimizer(polygons, &mesh->inset0_config, nullptr, z_seam_type, mesh->getSettingBoolean("magic_spiralize"));
|
||||
gcode_layer.addPolygonsByOptimizer(polygons, &mesh->inset0_config, nullptr, z_seam_type, mesh->getSettingInMicrons("wall_0_wipe_dist"), mesh->getSettingBoolean("magic_spiralize"));
|
||||
|
||||
addMeshOpenPolyLinesToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
@@ -652,7 +674,14 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (mesh->getSettingBoolean("anti_overhang_mesh")
|
||||
|| mesh->getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SliceLayer* layer = &mesh->layers[layer_nr];
|
||||
|
||||
if (layer->parts.size() == 0)
|
||||
@@ -680,7 +709,12 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, mesh->getSettingAsIndex("extruder_nr"));
|
||||
|
||||
EZSeamType z_seam_type = mesh->getSettingAsZSeamType("z_seam_type");
|
||||
PathOrderOptimizer part_order_optimizer(last_position_planned, z_seam_type);
|
||||
Point layer_start_position = last_position_planned;
|
||||
if (storage.getSettingBoolean("start_layers_at_same_position"))
|
||||
{
|
||||
layer_start_position = Point(storage.getSettingInMicrons("layer_start_x"), storage.getSettingInMicrons("layer_start_y"));
|
||||
}
|
||||
PathOrderOptimizer part_order_optimizer(layer_start_position, z_seam_type);
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
{
|
||||
part_order_optimizer.addPolygon(layer->parts[partNr].insets[0][0]);
|
||||
@@ -707,7 +741,7 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
int infill_line_distance = mesh->getSettingInMicrons("infill_line_distance");
|
||||
int infill_overlap = mesh->getSettingInMicrons("infill_overlap_mm");
|
||||
|
||||
gcode_layer.setIsInside(true); // going to print inside stuff below
|
||||
gcode_layer.setIsInside(&part); // going to print inside stuff below
|
||||
|
||||
if (mesh->getSettingBoolean("infill_before_walls"))
|
||||
{
|
||||
@@ -738,10 +772,10 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
//After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter.
|
||||
if (!mesh->getSettingBoolean("magic_spiralize") || static_cast<int>(layer_nr) < mesh->getSettingAsCount("bottom_layers"))
|
||||
{
|
||||
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons((mesh->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
|
||||
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons((mesh->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1, part);
|
||||
}
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
gcode_layer.setIsInside(nullptr);
|
||||
}
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
{
|
||||
@@ -775,7 +809,7 @@ void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMes
|
||||
infill_line_distance_here /= 2;
|
||||
}
|
||||
|
||||
Infill infill_comp(infill_pattern, part.infill_area_per_combine_per_density[density_idx][combine_idx], 0, infill_line_width, infill_line_distance_here, infill_overlap, infill_angle, z, infill_shift, false, false);
|
||||
Infill infill_comp(infill_pattern, part.infill_area_per_combine_per_density[density_idx][combine_idx], 0, infill_line_width, infill_line_distance_here, infill_overlap, infill_angle, z, infill_shift);
|
||||
infill_comp.generate(infill_polygons, infill_lines);
|
||||
}
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[combine_idx]);
|
||||
@@ -831,7 +865,7 @@ void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMe
|
||||
// ^ highest density line dist
|
||||
infill_line_distance_here /= 2;
|
||||
}
|
||||
Infill infill_comp(pattern, part.infill_area_per_combine_per_density[density_idx][0], 0, infill_line_width, infill_line_distance_here, infill_overlap, infill_angle, z, infill_shift, false, false);
|
||||
Infill infill_comp(pattern, part.infill_area_per_combine_per_density[density_idx][0], 0, infill_line_width, infill_line_distance_here, infill_overlap, infill_angle, z, infill_shift);
|
||||
infill_comp.generate(infill_polygons, infill_lines);
|
||||
}
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[0]);
|
||||
@@ -860,7 +894,7 @@ void FffGcodeWriter::processInsets(GCodePlanner& gcode_layer, SliceMeshStorage*
|
||||
}
|
||||
if (static_cast<int>(layer_nr) == mesh->getSettingAsCount("bottom_layers") && part.insets.size() > 0)
|
||||
{ // on the last normal layer first make the outer wall normally and then start a second outer wall from the same hight, but gradually moving upward
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->insetX_config, nullptr, EZSeamType::SHORTEST, false);
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->insetX_config, nullptr, EZSeamType::SHORTEST, mesh->getSettingInMicrons("wall_0_wipe_dist"), false);
|
||||
}
|
||||
}
|
||||
int processed_inset_number = -1;
|
||||
@@ -875,13 +909,13 @@ void FffGcodeWriter::processInsets(GCodePlanner& gcode_layer, SliceMeshStorage*
|
||||
{
|
||||
if (!compensate_overlap_0)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->inset0_config, nullptr, z_seam_type, spiralize);
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->inset0_config, nullptr, z_seam_type, mesh->getSettingInMicrons("wall_0_wipe_dist"), spiralize);
|
||||
}
|
||||
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, spiralize);
|
||||
gcode_layer.addPolygonsByOptimizer(outer_wall, &mesh->inset0_config, &wall_overlap_computation, z_seam_type, mesh->getSettingInMicrons("wall_0_wipe_dist"), spiralize);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -907,8 +941,21 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
int64_t z = layer_nr * getSettingInMicrons("layer_height");
|
||||
const unsigned int skin_line_width = mesh->skin_config.getLineWidth();
|
||||
|
||||
for(SkinPart& skin_part : part.skin_parts) // TODO: optimize parts order
|
||||
constexpr int perimeter_gaps_extra_offset = 15; // extra offset so that the perimeter gaps aren't created everywhere due to rounding errors
|
||||
bool fill_perimeter_gaps = mesh->getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") != FillPerimeterGapMode::NOWHERE;
|
||||
|
||||
PathOrderOptimizer part_order_optimizer(gcode_layer.getLastPosition(), EZSeamType::SHORTEST);
|
||||
for (unsigned int skin_part_idx = 0; skin_part_idx < part.skin_parts.size(); skin_part_idx++)
|
||||
{
|
||||
PolygonsPart& outline = part.skin_parts[skin_part_idx].outline;
|
||||
part_order_optimizer.addPolygon(outline.outerPolygon());
|
||||
}
|
||||
part_order_optimizer.optimize();
|
||||
|
||||
for (int ordered_skin_part_idx : part_order_optimizer.polyOrder)
|
||||
{
|
||||
SkinPart& skin_part = part.skin_parts[ordered_skin_part_idx];
|
||||
|
||||
Polygons skin_polygons;
|
||||
Polygons skin_lines;
|
||||
|
||||
@@ -921,6 +968,9 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
pattern = EFillMethod::LINES;
|
||||
skin_angle = bridge;
|
||||
}
|
||||
|
||||
Polygons perimeter_gaps; // the perimeter gaps of the insets of this skin part
|
||||
|
||||
Polygons* inner_skin_outline = nullptr;
|
||||
int offset_from_inner_skin_outline = 0;
|
||||
if (pattern != EFillMethod::CONCENTRIC)
|
||||
@@ -933,6 +983,21 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
{
|
||||
inner_skin_outline = &skin_part.insets.back();
|
||||
offset_from_inner_skin_outline = -mesh->insetX_config.getLineWidth() / 2;
|
||||
|
||||
if (fill_perimeter_gaps)
|
||||
{
|
||||
// add perimeter gaps between the outer skin inset and the innermost wall
|
||||
const Polygons outer = skin_part.outline;
|
||||
const Polygons inner = skin_part.insets[0].offset(mesh->insetX_config.getLineWidth() / 2 + perimeter_gaps_extra_offset * 2);
|
||||
perimeter_gaps.add(outer.difference(inner));
|
||||
|
||||
for (unsigned int inset_idx = 1; inset_idx < skin_part.insets.size(); inset_idx++)
|
||||
{ // add perimeter gaps between consecutive skin walls
|
||||
const Polygons outer = skin_part.insets[inset_idx - 1].offset(-1 * mesh->insetX_config.getLineWidth() / 2 - perimeter_gaps_extra_offset);
|
||||
const Polygons inner = skin_part.insets[inset_idx].offset(mesh->insetX_config.getLineWidth() / 2 + perimeter_gaps_extra_offset);
|
||||
perimeter_gaps.add(outer.difference(inner));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,9 +1007,17 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
}
|
||||
|
||||
int extra_infill_shift = 0;
|
||||
Infill infill_comp(pattern, *inner_skin_outline, offset_from_inner_skin_outline, skin_line_width, skin_line_width, skin_overlap, skin_angle, z, extra_infill_shift, false, false);
|
||||
Polygons* perimeter_gaps_output = (fill_perimeter_gaps)? &perimeter_gaps : nullptr;
|
||||
Infill infill_comp(pattern, *inner_skin_outline, offset_from_inner_skin_outline, skin_line_width, skin_line_width, skin_overlap, skin_angle, z, extra_infill_shift, perimeter_gaps_output);
|
||||
infill_comp.generate(skin_polygons, skin_lines);
|
||||
|
||||
if (fill_perimeter_gaps)
|
||||
{ // handle perimeter_gaps of skin insets
|
||||
int offset = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, perimeter_gaps, offset, skin_line_width, skin_line_width, skin_overlap, skin_angle, z, extra_infill_shift);
|
||||
infill_comp.generate(skin_polygons, skin_lines);
|
||||
}
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(skin_polygons, &mesh->skin_config);
|
||||
|
||||
if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES)
|
||||
@@ -956,57 +1029,67 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
}
|
||||
|
||||
if (fill_perimeter_gaps)
|
||||
{ // handle perimeter gaps of normal insets
|
||||
Polygons perimeter_gaps;
|
||||
int line_width = mesh->inset0_config.getLineWidth();
|
||||
for (unsigned int inset_idx = 1; inset_idx < part.insets.size(); inset_idx++)
|
||||
{
|
||||
const Polygons outer = part.insets[inset_idx - 1].offset(-1 * line_width / 2 - perimeter_gaps_extra_offset);
|
||||
line_width = mesh->insetX_config.getLineWidth();
|
||||
const Polygons inner = part.insets[inset_idx].offset(line_width / 2 + perimeter_gaps_extra_offset);
|
||||
perimeter_gaps.add(outer.difference(inner));
|
||||
}
|
||||
|
||||
Polygons skin_polygons; // unused
|
||||
Polygons skin_lines; // soon to be generated gap filler lines
|
||||
int offset = 0;
|
||||
int extra_infill_shift = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, perimeter_gaps, offset, skin_line_width, skin_line_width, skin_overlap, skin_angle, z, extra_infill_shift);
|
||||
infill_comp.generate(skin_polygons, skin_lines);
|
||||
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, SpaceFillType::Lines);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr_before, bool before_rest)
|
||||
bool FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr)
|
||||
{
|
||||
bool support_added = false;
|
||||
if (!storage.support.generated || layer_nr > storage.support.layer_nr_max_filled_layer)
|
||||
return;
|
||||
|
||||
int support_skin_extruder_nr = getSettingAsIndex("support_interface_extruder_nr");
|
||||
int support_infill_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
|
||||
bool print_support_before_rest = support_infill_extruder_nr == extruder_nr_before
|
||||
|| support_skin_extruder_nr == extruder_nr_before;
|
||||
// TODO: always print support after rest when only one nozzle is used for the whole meshgroup
|
||||
|
||||
if (print_support_before_rest != before_rest)
|
||||
return;
|
||||
|
||||
{
|
||||
return support_added;
|
||||
}
|
||||
|
||||
int support_interface_extruder_nr = getSettingAsIndex("support_interface_extruder_nr");
|
||||
int support_infill_extruder_nr = (layer_nr <= 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
|
||||
if (support_layer.skin.size() == 0 && support_layer.supportAreas.size() == 0)
|
||||
{
|
||||
return;
|
||||
return support_added;
|
||||
}
|
||||
|
||||
int current_extruder_nr = gcode_layer.getExtruder();
|
||||
|
||||
if (support_layer.skin.size() > 0)
|
||||
|
||||
|
||||
if (extruder_nr == support_infill_extruder_nr)
|
||||
{
|
||||
if (support_skin_extruder_nr != support_infill_extruder_nr && support_skin_extruder_nr == current_extruder_nr)
|
||||
{
|
||||
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
support_added |= addSupportInfillToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
if (extruder_nr == support_interface_extruder_nr)
|
||||
{
|
||||
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
|
||||
support_added |= addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
return support_added;
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
|
||||
bool FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
|
||||
{
|
||||
bool added = false;
|
||||
if (!storage.support.generated
|
||||
|| layer_nr > storage.support.layer_nr_max_filled_layer
|
||||
|| storage.support.supportLayers[layer_nr].supportAreas.size() == 0)
|
||||
{
|
||||
return;
|
||||
return added;
|
||||
}
|
||||
|
||||
int64_t z = layer_nr * getSettingInMicrons("layer_height");
|
||||
@@ -1015,12 +1098,12 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
int support_line_distance = infill_extr.getSettingInMicrons("support_line_distance"); // first layer line distance must be the same as the second layer line distance
|
||||
const int support_line_width = storage.support_config.getLineWidth();
|
||||
EFillMethod support_pattern = infill_extr.getSettingAsFillMethod("support_pattern"); // first layer pattern must be same as other layers
|
||||
if (layer_nr == 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG)) { support_pattern = EFillMethod::GRID; }
|
||||
if (layer_nr <= 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG)) { support_pattern = EFillMethod::GRID; }
|
||||
|
||||
int infill_extruder_nr_here = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
int infill_extruder_nr_here = (layer_nr <= 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
const ExtruderTrain& infill_extr_here = *storage.meshgroup->getExtruderTrain(infill_extruder_nr_here);
|
||||
|
||||
Polygons& support = storage.support.supportLayers[layer_nr].supportAreas;
|
||||
Polygons& support = storage.support.supportLayers[std::max(0, layer_nr)].supportAreas;
|
||||
|
||||
std::vector<PolygonsPart> support_islands = support.splitIntoParts();
|
||||
|
||||
@@ -1051,7 +1134,10 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
}
|
||||
|
||||
int extra_infill_shift = 0;
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, support_line_width, support_line_distance, support_infill_overlap, 0, z, extra_infill_shift, infill_extr.getSettingBoolean("support_connect_zigzags"), true);
|
||||
bool use_endpieces = true;
|
||||
Polygons* perimeter_gaps = nullptr;
|
||||
double fill_angle = 0;
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, support_line_width, support_line_distance, support_infill_overlap, fill_angle, z, extra_infill_shift, perimeter_gaps, infill_extr.getSettingBoolean("support_connect_zigzags"), use_endpieces);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines);
|
||||
@@ -1060,17 +1146,20 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, infill_extruder_nr_here); // only switch extruder if we're sure we're going to switch
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_config, (support_pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
|
||||
bool FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
|
||||
{
|
||||
bool added = false;
|
||||
if (!storage.support.generated
|
||||
|| layer_nr > storage.support.layer_nr_max_filled_layer
|
||||
|| storage.support.supportLayers[layer_nr].skin.size() == 0)
|
||||
|| storage.support.supportLayers[std::max(0, layer_nr)].skin.size() == 0)
|
||||
{
|
||||
return;
|
||||
return added;
|
||||
}
|
||||
|
||||
int64_t z = layer_nr * getSettingInMicrons("layer_height");
|
||||
@@ -1103,24 +1192,28 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
|
||||
}
|
||||
else
|
||||
{
|
||||
fillAngle = 45 + (layer_nr % 2) * 90; // alternate between the two kinds of diagonal: / and \ .
|
||||
fillAngle = 45 + (((layer_nr % 2) + 2) % 2) * 90; // alternate between the two kinds of diagonal: / and \ .
|
||||
// +2) %2 to handle negative layer numbers
|
||||
}
|
||||
int support_skin_overlap = 0; // the skin (roofs/bottoms) should never be expanded outwards
|
||||
int outline_offset = 0;
|
||||
int extra_infill_shift = 0;
|
||||
|
||||
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].skin, outline_offset, storage.support_skin_config.getLineWidth(), support_line_distance, support_skin_overlap, fillAngle, z, extra_infill_shift, false, true);
|
||||
Polygons* perimeter_gaps = nullptr;
|
||||
bool use_endpieces = true;
|
||||
bool connected_zigzags = false;
|
||||
Infill infill_comp(pattern, storage.support.supportLayers[std::max(0, layer_nr)].skin, outline_offset, storage.support_skin_config.getLineWidth(), support_line_distance, support_skin_overlap, fillAngle, z, extra_infill_shift, perimeter_gaps, connected_zigzags, use_endpieces);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines);
|
||||
|
||||
if (support_polygons.size() == 0 && support_lines.size() == 0)
|
||||
if (support_lines.size() > 0 || support_polygons.size() > 0)
|
||||
{
|
||||
return;
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, skin_extruder_nr);
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_skin_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
added = true;
|
||||
}
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, skin_extruder_nr);
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_skin_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
return added;
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr)
|
||||
@@ -1138,25 +1231,18 @@ void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanne
|
||||
{
|
||||
processSkirtBrim(storage, gcode_layer, extruder_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addPrimeTower(storage, gcode_layer, layer_nr, previous_extruder);
|
||||
|
||||
}
|
||||
addPrimeTower(storage, gcode_layer, layer_nr, previous_extruder);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int prev_extruder)
|
||||
void FffGcodeWriter::addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int prev_extruder)
|
||||
{
|
||||
|
||||
if (!getSettingBoolean("prime_tower_enable"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool wipe = getSettingBoolean("prime_tower_wipe_enabled");
|
||||
|
||||
storage.primeTower.addToGcode(storage, gcodeLayer, gcode, layer_nr, prev_extruder, wipe);
|
||||
storage.primeTower.addToGcode(storage, gcode_layer, gcode, layer_nr, prev_extruder, gcode_layer.getExtruder());
|
||||
}
|
||||
|
||||
void FffGcodeWriter::finalize()
|
||||
|
||||
+31
-15
@@ -69,14 +69,12 @@ private:
|
||||
|
||||
Point last_position_planned; //!< The position of the head before planning the next layer
|
||||
int current_extruder_planned; //!< The extruder train in use before planning the next layer
|
||||
bool is_inside_mesh_layer_part; //!< Whether the last position was inside a layer part (used in combing)
|
||||
public:
|
||||
FffGcodeWriter(SettingsBase* settings_)
|
||||
: SettingsMessenger(settings_)
|
||||
, layer_plan_buffer(this, gcode)
|
||||
, last_position_planned(no_point)
|
||||
, current_extruder_planned(0) // changed somewhere early in FffGcodeWriter::writeGCode
|
||||
, is_inside_mesh_layer_part(false)
|
||||
{
|
||||
max_object_height = 0;
|
||||
}
|
||||
@@ -211,7 +209,15 @@ private:
|
||||
* \param total_layers The total number of layers.
|
||||
*/
|
||||
void processLayer(SliceDataStorage& storage, int layer_nr, unsigned int total_layers);
|
||||
|
||||
|
||||
/*!
|
||||
* Plan priming of all used extruders which haven't been primed yet
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param layer_plan The initial planning of the g-code of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*/
|
||||
void ensureAllExtrudersArePrimed(SliceDataStorage& storage, GCodePlanner& layer_plan, const int layer_nr);
|
||||
|
||||
/*!
|
||||
* Add the skirt or the brim to the layer plan \p gcodeLayer.
|
||||
*
|
||||
@@ -239,16 +245,25 @@ private:
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, GCodePlanner& gcodeLayer, unsigned int layer_nr);
|
||||
|
||||
|
||||
/*!
|
||||
* Calculate in which order to print the meshes.
|
||||
* Calculate in which order to plan the extruders
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param current_extruder The current extruder with which we last printed
|
||||
* \return A vector of mesh indices ordered on print order.
|
||||
* \return A vector of pairs of extruder numbers coupled with the mesh indices ordered on print order for that extruder.
|
||||
*/
|
||||
std::vector<unsigned int> calculateMeshOrder(SliceDataStorage& storage, int current_extruder);
|
||||
|
||||
std::vector<int> calculateExtruderOrder(SliceDataStorage& storage, int current_extruder);
|
||||
|
||||
/*!
|
||||
* Calculate in which order to plan the meshes of a specific extruder
|
||||
*
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param extruder_nr The extruder for which to determine the order
|
||||
* \return A vector of pairs of extruder numbers coupled with the mesh indices ordered on print order for that extruder.
|
||||
*/
|
||||
std::vector<unsigned int> calculateMeshOrder(SliceDataStorage& storage, int extruder_nr);
|
||||
|
||||
/*!
|
||||
* Add a single layer from a single mesh-volume to the layer plan \p gcodeLayer in mesh surface mode.
|
||||
*
|
||||
@@ -328,30 +343,31 @@ private:
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
*/
|
||||
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int infill_angle);
|
||||
|
||||
|
||||
/*!
|
||||
* Add the support to the layer plan \p gcodeLayer of the current layer.
|
||||
* Add the support to the layer plan \p gcodeLayer of the current layer for all support parts with the given \p extruder_nr.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \param extruder_nr_before The extruder number at the start of the layer (before other print parts aka the rest)
|
||||
* \param before_rest Whether the function has been called before adding the rest to the layer plan \p gcodeLayer, or after.
|
||||
* \return whether any support was added to the layer plan
|
||||
*/
|
||||
void addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int extruder_nr_before, bool before_rest);
|
||||
bool addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int extruder_nr);
|
||||
/*!
|
||||
* Add the support lines/walls to the layer plan \p gcodeLayer of the current layer.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \return whether any support infill was added to the layer plan
|
||||
*/
|
||||
void addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
bool addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
/*!
|
||||
* Add the support skins to the layer plan \p gcodeLayer of the current layer.
|
||||
* \param[in] storage where the slice data is stored.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
* \return whether any support skin was added to the layer plan
|
||||
*/
|
||||
void addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
bool addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
|
||||
/*!
|
||||
* Change to a new extruder, and add the prime tower instructions if the new extruder is different from the last.
|
||||
|
||||
+107
-41
@@ -4,6 +4,7 @@
|
||||
#include <map> // multimap (ordered map allowing duplicate keys)
|
||||
|
||||
#include "utils/math.h"
|
||||
#include "utils/algorithm.h"
|
||||
#include "slicer.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
@@ -115,20 +116,23 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
|
||||
|
||||
carveMultipleVolumes(slicerList);
|
||||
if (storage.getSettingBoolean("carve_multiple_volumes"))
|
||||
{
|
||||
carveMultipleVolumes(slicerList, storage.getSettingBoolean("alternate_carve_order"));
|
||||
}
|
||||
generateMultipleVolumesOverlap(slicerList);
|
||||
|
||||
size_t max_layer_count = 0;
|
||||
storage.print_layer_count = 0;
|
||||
for (unsigned int meshIdx = 0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
|
||||
Slicer* slicer = slicerList[meshIdx];
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh") && !mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
max_layer_count = std::max(max_layer_count, slicer->layers.size());
|
||||
storage.print_layer_count = std::max(storage.print_layer_count, (unsigned int)slicer->layers.size());
|
||||
}
|
||||
}
|
||||
storage.support.supportLayers.resize(max_layer_count);
|
||||
storage.support.supportLayers.resize(storage.print_layer_count);
|
||||
|
||||
storage.meshes.reserve(slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated.
|
||||
for (unsigned int meshIdx = 0; meshIdx < slicerList.size(); meshIdx++)
|
||||
@@ -232,11 +236,10 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
}
|
||||
for (unsigned int mesh_order_idx(0); mesh_order_idx < mesh_order.size(); ++mesh_order_idx)
|
||||
{
|
||||
processBasicWallsSkinInfill(storage, mesh_order_idx, mesh_order, slice_layer_count, inset_skin_progress_estimate);
|
||||
processBasicWallsSkinInfill(storage, mesh_order_idx, mesh_order, inset_skin_progress_estimate);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, mesh_order_idx + 1, storage.meshes.size());
|
||||
}
|
||||
|
||||
unsigned int print_layer_count = 0;
|
||||
for (unsigned int layer_nr = 0; layer_nr < slice_layer_count; layer_nr++)
|
||||
{
|
||||
SliceLayer* layer = nullptr;
|
||||
@@ -246,7 +249,6 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
if (int(layer_nr) <= mesh.layer_nr_max_filled_layer)
|
||||
{
|
||||
layer = &mesh.layers[layer_nr];
|
||||
print_layer_count = layer_nr + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -259,21 +261,21 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
}
|
||||
}
|
||||
|
||||
log("Layer count: %i\n", print_layer_count);
|
||||
log("Layer count: %i\n", storage.print_layer_count);
|
||||
|
||||
//layerparts2HTML(storage, "output/output.html");
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
|
||||
|
||||
AreaSupport::generateSupportAreas(storage, print_layer_count);
|
||||
AreaSupport::generateSupportAreas(storage, storage.print_layer_count);
|
||||
|
||||
// we need to remove empty layers after we have procesed the insets
|
||||
// processInsets might throw away parts if they have no wall at all (cause it doesn't fit)
|
||||
// brim depends on the first layer not being empty
|
||||
// only remove empty layers if we haven't generate support, because then support was added underneath the model.
|
||||
// for some materials it's better to print on support than on the buildplate.
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), print_layer_count); // changes total_layers!
|
||||
if (print_layer_count == 0)
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), storage.print_layer_count); // changes storage.print_layer_count!
|
||||
if (storage.print_layer_count == 0)
|
||||
{
|
||||
log("Stopping process because there are no non-empty layers.\n");
|
||||
return;
|
||||
@@ -282,7 +284,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
/*
|
||||
if (storage.support.generated)
|
||||
{
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
for (unsigned int layer_idx = 0; layer_idx < storage.print_layer_count; layer_idx++)
|
||||
{
|
||||
Polygons& support = storage.support.supportLayers[layer_idx].supportAreas;
|
||||
ExtruderTrain* infill_extr = storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("support_infill_extruder_nr"));
|
||||
@@ -291,15 +293,16 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
}
|
||||
*/
|
||||
|
||||
computePrintHeightStatistics(storage);
|
||||
|
||||
// handle helpers
|
||||
storage.primeTower.computePrimeTowerMax(storage);
|
||||
storage.primeTower.generatePaths(storage, print_layer_count);
|
||||
storage.primeTower.generatePaths(storage);
|
||||
|
||||
logDebug("Processing ooze shield\n");
|
||||
processOozeShield(storage);
|
||||
|
||||
logDebug("Processing draft shield\n");
|
||||
processDraftShield(storage, print_layer_count);
|
||||
processDraftShield(storage);
|
||||
|
||||
logDebug("Processing platform adhesion\n");
|
||||
processPlatformAdhesion(storage);
|
||||
@@ -307,17 +310,18 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
// meshes post processing
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
processDerivedWallsSkinInfill(mesh, print_layer_count);
|
||||
processDerivedWallsSkinInfill(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate)
|
||||
void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, ProgressStageEstimator& inset_skin_progress_estimate)
|
||||
{
|
||||
unsigned int mesh_idx = mesh_order[mesh_order_idx];
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
size_t mesh_layer_count = mesh.layers.size();
|
||||
if (mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
processInfillMesh(storage, mesh_order_idx, mesh_order, total_layers);
|
||||
processInfillMesh(storage, mesh_order_idx, mesh_order);
|
||||
}
|
||||
|
||||
// TODO: make progress more accurate!!
|
||||
@@ -327,20 +331,20 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
|
||||
inset_skin_progress_estimate.nextStage(mesh_inset_skin_progress_estimator); // the stage of this function call
|
||||
|
||||
ProgressEstimatorLinear* inset_estimator = new ProgressEstimatorLinear(total_layers);
|
||||
ProgressEstimatorLinear* inset_estimator = new ProgressEstimatorLinear(mesh_layer_count);
|
||||
mesh_inset_skin_progress_estimator->nextStage(inset_estimator);
|
||||
|
||||
|
||||
// walls
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
for (unsigned int layer_number = 0; layer_number < mesh.layers.size(); layer_number++)
|
||||
{
|
||||
logDebug("Processing insets for layer %i of %i\n", layer_number, total_layers);
|
||||
logDebug("Processing insets for layer %i of %i\n", layer_number, mesh_layer_count);
|
||||
processInsets(mesh, layer_number);
|
||||
double progress = inset_skin_progress_estimate.progress(layer_number);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100);
|
||||
}
|
||||
|
||||
ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(total_layers);
|
||||
ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(mesh_layer_count);
|
||||
mesh_inset_skin_progress_estimator->nextStage(skin_estimator);
|
||||
|
||||
bool process_infill = mesh.getSettingInMicrons("infill_line_distance") > 0;
|
||||
@@ -368,9 +372,9 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
{
|
||||
mesh_max_bottom_layer_count = std::max(mesh_max_bottom_layer_count, mesh.getSettingAsCount("bottom_layers"));
|
||||
}
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
for (unsigned int layer_number = 0; layer_number < mesh.layers.size(); layer_number++)
|
||||
{
|
||||
logDebug("Processing skins and infill layer %i of %i\n", layer_number, total_layers);
|
||||
logDebug("Processing skins and infill layer %i of %i\n", layer_number, mesh_layer_count);
|
||||
if (!mesh.getSettingBoolean("magic_spiralize") || static_cast<int>(layer_number) < mesh_max_bottom_layer_count) //Only generate up/downskin and infill for the first X layers when spiralize is choosen.
|
||||
{
|
||||
processSkinsAndInfill(mesh, layer_number, process_infill);
|
||||
@@ -380,7 +384,7 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage,
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, size_t total_layers)
|
||||
void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order)
|
||||
{
|
||||
unsigned int mesh_idx = mesh_order[mesh_order_idx];
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
@@ -451,7 +455,7 @@ void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned
|
||||
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers)
|
||||
void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh)
|
||||
{
|
||||
// create gradual infill areas
|
||||
SkinInfillAreaComputation::generateGradualInfill(mesh, mesh.getSettingInMicrons("gradual_infill_step_height"), mesh.getSettingAsCount("gradual_infill_steps"));
|
||||
@@ -473,12 +477,14 @@ void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int lay
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::SURFACE)
|
||||
{
|
||||
int inset_count = mesh.getSettingAsCount("wall_line_count");
|
||||
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
|
||||
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && ((layer_nr % 2) + 2) % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
|
||||
inset_count += 5;
|
||||
int line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
|
||||
int line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
|
||||
if (mesh.getSettingBoolean("alternate_extra_perimeter"))
|
||||
inset_count += layer_nr % 2;
|
||||
{
|
||||
inset_count += ((layer_nr % 2) + 2) % 2;
|
||||
}
|
||||
bool recompute_outline_based_on_outer_wall = mesh.getSettingBoolean("support_enable");
|
||||
WallsComputation walls_computation(mesh.getSettingInMicrons("wall_0_inset"), line_width_0, line_width_x, inset_count, recompute_outline_based_on_outer_wall);
|
||||
walls_computation.generateInsets(layer);
|
||||
@@ -487,16 +493,19 @@ void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int lay
|
||||
|
||||
void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, const int layer_height, unsigned int& total_layers)
|
||||
{
|
||||
// only remove empty layers if we haven't generate support, because then support was added underneath the model.
|
||||
// for some materials it's better to print on support than on the buildplate.
|
||||
if (storage.support.generated)
|
||||
{
|
||||
return; // the first layer will have support and therefore not be empty
|
||||
}
|
||||
int n_empty_first_layers = 0;
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
{
|
||||
bool layer_is_empty = true;
|
||||
if (storage.support.generated && layer_idx < storage.support.supportLayers.size())
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_idx];
|
||||
if (support_layer.supportAreas.size() > 0 || support_layer.skin.size() > 0)
|
||||
{
|
||||
layer_is_empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
@@ -527,8 +536,12 @@ void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, cons
|
||||
{
|
||||
layer.printZ -= n_empty_first_layers * layer_height;
|
||||
}
|
||||
mesh.layer_nr_max_filled_layer -= n_empty_first_layers;
|
||||
}
|
||||
total_layers -= n_empty_first_layers;
|
||||
storage.support.layer_nr_max_filled_layer -= n_empty_first_layers;
|
||||
std::vector<SupportLayer>& support_layers = storage.support.supportLayers;
|
||||
support_layers.erase(support_layers.begin(), support_layers.begin() + n_empty_first_layers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,6 +569,57 @@ void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, unsigned
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage)
|
||||
{
|
||||
int extruder_count = storage.meshgroup->getExtruderCount();
|
||||
|
||||
std::vector<int>& max_print_height_per_extruder = storage.max_print_height_per_extruder;
|
||||
assert(max_print_height_per_extruder.size() == 0 && "storage.max_print_height_per_extruder shouldn't have been initialized yet!");
|
||||
max_print_height_per_extruder.resize(extruder_count, -1); //Initialize all as -1.
|
||||
{ // compute max_object_height_per_extruder
|
||||
//Height of the meshes themselves.
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (mesh.getSettingBoolean("anti_overhang_mesh") || mesh.getSettingBoolean("support_mesh"))
|
||||
{
|
||||
continue; //Special type of mesh that doesn't get printed.
|
||||
}
|
||||
const unsigned int extr_nr = mesh.getSettingAsIndex("extruder_nr");
|
||||
max_print_height_per_extruder[extr_nr] = std::max(max_print_height_per_extruder[extr_nr], mesh.layer_nr_max_filled_layer);
|
||||
}
|
||||
|
||||
//Height of where the support reaches.
|
||||
const unsigned int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); // TODO: support extruder should be configurable per object
|
||||
max_print_height_per_extruder[support_infill_extruder_nr] =
|
||||
std::max(max_print_height_per_extruder[support_infill_extruder_nr],
|
||||
storage.support.layer_nr_max_filled_layer);
|
||||
const unsigned int support_skin_extruder_nr = storage.getSettingAsIndex("support_interface_extruder_nr"); // TODO: support skin extruder should be configurable per object
|
||||
max_print_height_per_extruder[support_skin_extruder_nr] =
|
||||
std::max(max_print_height_per_extruder[support_skin_extruder_nr],
|
||||
storage.support.layer_nr_max_filled_layer);
|
||||
|
||||
//Height of where the platform adhesion reaches.
|
||||
if (storage.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
const unsigned int adhesion_extruder_nr = storage.getSettingAsIndex("adhesion_extruder_nr");
|
||||
max_print_height_per_extruder[adhesion_extruder_nr] =
|
||||
std::max(0, max_print_height_per_extruder[adhesion_extruder_nr]);
|
||||
}
|
||||
}
|
||||
|
||||
storage.max_print_height_order = order(max_print_height_per_extruder);
|
||||
if (extruder_count >= 2)
|
||||
{
|
||||
int second_highest_extruder = storage.max_print_height_order[extruder_count - 2];
|
||||
storage.max_print_height_second_to_last_extruder = max_print_height_per_extruder[second_highest_extruder];
|
||||
}
|
||||
else
|
||||
{
|
||||
storage.max_print_height_second_to_last_extruder = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage)
|
||||
{
|
||||
if (!getSettingBoolean("ooze_shield_enabled"))
|
||||
@@ -565,7 +629,7 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage)
|
||||
|
||||
const int ooze_shield_dist = getSettingInMicrons("ooze_shield_dist");
|
||||
|
||||
for (int layer_nr = 0; layer_nr <= storage.max_object_height_second_to_last_extruder; layer_nr++)
|
||||
for (int layer_nr = 0; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++)
|
||||
{
|
||||
storage.oozeShield.push_back(storage.getLayerOutlines(layer_nr, true).offset(ooze_shield_dist, ClipperLib::jtRound));
|
||||
}
|
||||
@@ -574,26 +638,26 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage)
|
||||
if (angle <= 89)
|
||||
{
|
||||
int allowed_angle_offset = tan(getSettingInAngleRadians("ooze_shield_angle")) * getSettingInMicrons("layer_height"); // Allow for a 60deg angle in the oozeShield.
|
||||
for (int layer_nr = 1; layer_nr <= storage.max_object_height_second_to_last_extruder; layer_nr++)
|
||||
for (int layer_nr = 1; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++)
|
||||
{
|
||||
storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].unionPolygons(storage.oozeShield[layer_nr - 1].offset(-allowed_angle_offset));
|
||||
}
|
||||
for (int layer_nr = storage.max_object_height_second_to_last_extruder; layer_nr > 0; layer_nr--)
|
||||
for (int layer_nr = storage.max_print_height_second_to_last_extruder; layer_nr > 0; layer_nr--)
|
||||
{
|
||||
storage.oozeShield[layer_nr - 1] = storage.oozeShield[layer_nr - 1].unionPolygons(storage.oozeShield[layer_nr].offset(-allowed_angle_offset));
|
||||
}
|
||||
}
|
||||
|
||||
const float largest_printed_area = 1.0; // TODO: make var a parameter, and perhaps even a setting?
|
||||
for (int layer_nr = 0; layer_nr <= storage.max_object_height_second_to_last_extruder; layer_nr++)
|
||||
for (int layer_nr = 0; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++)
|
||||
{
|
||||
storage.oozeShield[layer_nr].removeSmallAreas(largest_printed_area);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned int total_layers)
|
||||
void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage)
|
||||
{
|
||||
const unsigned int draft_shield_layers = getDraftShieldLayerCount(total_layers);
|
||||
const unsigned int draft_shield_layers = getDraftShieldLayerCount(storage.print_layer_count);
|
||||
if (draft_shield_layers <= 0)
|
||||
{
|
||||
return;
|
||||
@@ -603,7 +667,7 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned
|
||||
const unsigned int layer_skip = 500 / layer_height + 1;
|
||||
|
||||
Polygons& draft_shield = storage.draft_protection_shield;
|
||||
for (unsigned int layer_nr = 0; layer_nr < total_layers && layer_nr < draft_shield_layers; layer_nr += layer_skip)
|
||||
for (unsigned int layer_nr = 0; layer_nr < storage.print_layer_count && layer_nr < draft_shield_layers; layer_nr += layer_skip)
|
||||
{
|
||||
draft_shield = draft_shield.unionPolygons(storage.getLayerOutlines(layer_nr, true));
|
||||
}
|
||||
@@ -629,6 +693,8 @@ void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
|
||||
case EPlatformAdhesion::RAFT:
|
||||
Raft::generate(storage, train->getSettingInMicrons("raft_margin"));
|
||||
break;
|
||||
case EPlatformAdhesion::NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,10 +84,9 @@ private:
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh_order_idx The index of the mesh_idx in \p mesh_order to process in the vector of meshes in \p storage
|
||||
* \param mesh_order The order in which the meshes are processed (used for infill meshes)
|
||||
* \param total_layers The total number of layers over all objects
|
||||
* \param inset_skin_progress_estimate The progress stage estimate calculator
|
||||
*/
|
||||
void processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate);
|
||||
void processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, ProgressStageEstimator& inset_skin_progress_estimate);
|
||||
|
||||
/*!
|
||||
* Process the mesh to be an infill mesh: limit all outlines to within the infill of normal meshes and subtract their volume from the infill of those meshes
|
||||
@@ -95,18 +94,16 @@ private:
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh_order_idx The index of the mesh_idx in \p mesh_order to process in the vector of meshes in \p storage
|
||||
* \param mesh_order The order in which the meshes are processed
|
||||
* \param total_layers The total number of layers over all objects
|
||||
*/
|
||||
void processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order, size_t total_layers);
|
||||
void processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector<unsigned int>& mesh_order);
|
||||
|
||||
/*!
|
||||
* Process features which are derived from the basic walls, skin, and infill:
|
||||
* fuzzy skin, infill combine
|
||||
*
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param total_layers The total number of layers over all objects
|
||||
*/
|
||||
void processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers);
|
||||
void processDerivedWallsSkinInfill(SliceMeshStorage& mesh);
|
||||
|
||||
/*!
|
||||
* Remove all bottom layers which are empty.
|
||||
@@ -118,7 +115,14 @@ private:
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void removeEmptyFirstLayers(SliceDataStorage& storage, const int layer_height, unsigned int& total_layers);
|
||||
|
||||
|
||||
/*!
|
||||
* Set \ref SliceDataStorage::max_print_height_per_extruder and \ref SliceDataStorage::max_print_height_order and \ref SliceDataStorage::max_print_height_second_to_last_extruder
|
||||
*
|
||||
* \param[in,out] storage Where to retrieve mesh and support etc settings from and where the print height statistics are saved.
|
||||
*/
|
||||
void computePrintHeightStatistics(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Generate the inset polygons which form the walls.
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
@@ -144,9 +148,8 @@ private:
|
||||
* Generate the polygons where the draft screen should be.
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void processDraftShield(SliceDataStorage& storage, unsigned int total_layers);
|
||||
void processDraftShield(SliceDataStorage& storage);
|
||||
|
||||
/*!
|
||||
* Generate the skirt/brim/raft areas/insets.
|
||||
|
||||
+190
-31
@@ -13,7 +13,7 @@ void LayerPlanBuffer::flush()
|
||||
{
|
||||
if (buffer.size() > 0)
|
||||
{
|
||||
insertPreheatCommands(); // insert preheat commands of the very last layer
|
||||
insertTempCommands(); // insert preheat commands of the very last layer
|
||||
}
|
||||
while (!buffer.empty())
|
||||
{
|
||||
@@ -38,41 +38,51 @@ void LayerPlanBuffer::insertPreheatCommand(ExtruderPlan& extruder_plan_before, d
|
||||
if (acc_time > time_after_extruder_plan_start)
|
||||
{
|
||||
const double time_before_path_end = acc_time - time_after_extruder_plan_start;
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, false, time_this_path - time_before_path_end);
|
||||
bool wait = false;
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, wait, time_this_path - time_before_path_end);
|
||||
return;
|
||||
}
|
||||
}
|
||||
extruder_plan_before.insertCommand(0, extruder, temp, false); // insert at start of extruder plan if time_after_extruder_plan_start > extruder_plan.time
|
||||
bool wait = false;
|
||||
unsigned int path_idx = 0;
|
||||
extruder_plan_before.insertCommand(path_idx, extruder, temp, wait); // insert at start of extruder plan if time_after_extruder_plan_start > extruder_plan.time
|
||||
}
|
||||
|
||||
Preheat::WarmUpResult LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
|
||||
double initial_print_temp = extruder_plan.initial_printing_temperature;
|
||||
|
||||
double in_between_time = 0.0;
|
||||
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{ // find a previous extruder plan where the same extruder is used to see what time this extruder wasn't used
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_before_idx];
|
||||
if (extruder_plan.extruder == extruder)
|
||||
ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
|
||||
if (extruder_plan_before.extruder == extruder)
|
||||
{
|
||||
Preheat::WarmUpResult warm_up = preheat_config.timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(in_between_time, extruder, required_temp);
|
||||
double temp_before = preheat_config.getFinalPrintTemp(extruder);
|
||||
if (temp_before == 0)
|
||||
{
|
||||
temp_before = extruder_plan_before.printing_temperature;
|
||||
}
|
||||
constexpr bool during_printing = false;
|
||||
Preheat::WarmUpResult warm_up = preheat_config.getWarmUpPointAfterCoolDown(in_between_time, extruder, temp_before, preheat_config.getStandbyTemp(extruder), initial_print_temp, during_printing);
|
||||
warm_up.heating_time = std::min(in_between_time, warm_up.heating_time + extra_preheat_time);
|
||||
return warm_up;
|
||||
}
|
||||
in_between_time += extruder_plan.estimates.getTotalTime();
|
||||
in_between_time += extruder_plan_before.estimates.getTotalTime();
|
||||
}
|
||||
// The last extruder plan with the same extruder falls outside of the buffer
|
||||
// assume the nozzle has cooled down to strandby temperature already.
|
||||
Preheat::WarmUpResult warm_up;
|
||||
warm_up.total_time_window = in_between_time;
|
||||
warm_up.lowest_temperature = preheat_config.getStandbyTemp(extruder);
|
||||
warm_up.heating_time = preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(warm_up.lowest_temperature, extruder, required_temp, false);
|
||||
constexpr bool during_printing = false;
|
||||
warm_up.heating_time = preheat_config.getTimeToGoFromTempToTemp(extruder, warm_up.lowest_temperature, initial_print_temp, during_printing);
|
||||
if (warm_up.heating_time > in_between_time)
|
||||
{
|
||||
warm_up.heating_time = in_between_time;
|
||||
warm_up.lowest_temperature = in_between_time / preheat_config.getTimeToHeatup1Degree(extruder);
|
||||
warm_up.lowest_temperature = in_between_time / preheat_config.getTimeToHeatup1Degree(extruder, during_printing);
|
||||
}
|
||||
warm_up.heating_time = warm_up.heating_time + extra_preheat_time;
|
||||
return warm_up;
|
||||
@@ -82,7 +92,8 @@ Preheat::WarmUpResult LayerPlanBuffer::timeBeforeExtruderPlanToInsert(std::vecto
|
||||
void LayerPlanBuffer::insertPreheatCommand_singleExtrusion(ExtruderPlan& prev_extruder_plan, int extruder, double required_temp)
|
||||
{
|
||||
// time_before_extruder_plan_end is halved, so that at the layer change the temperature will be half way betewen the two requested temperatures
|
||||
double time_before_extruder_plan_end = 0.5 * preheat_config.timeBeforeEndToInsertPreheatCommand_warmUp(prev_extruder_plan.required_temp, extruder, required_temp, true);
|
||||
constexpr bool during_printing = true;
|
||||
double time_before_extruder_plan_end = 0.5 * preheat_config.getTimeToGoFromTempToTemp(extruder, prev_extruder_plan.printing_temperature, required_temp, during_printing);
|
||||
time_before_extruder_plan_end = std::min(prev_extruder_plan.estimates.getTotalTime(), time_before_extruder_plan_end);
|
||||
|
||||
insertPreheatCommand(prev_extruder_plan, time_before_extruder_plan_end, extruder, required_temp);
|
||||
@@ -111,13 +122,13 @@ void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<ExtruderPl
|
||||
{
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
double initial_print_temp = extruder_plan.initial_printing_temperature;
|
||||
|
||||
Preheat::WarmUpResult heating_time_and_from_temp = timeBeforeExtruderPlanToInsert(extruder_plans, extruder_plan_idx);
|
||||
|
||||
if (heating_time_and_from_temp.total_time_window < preheat_config.getMinimalTimeWindow(extruder))
|
||||
{
|
||||
handleStandbyTemp(extruder_plans, extruder_plan_idx, required_temp);
|
||||
handleStandbyTemp(extruder_plans, extruder_plan_idx, initial_print_temp);
|
||||
return; // don't insert preheat command and just stay on printing temperature
|
||||
}
|
||||
else
|
||||
@@ -125,30 +136,32 @@ void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<ExtruderPl
|
||||
handleStandbyTemp(extruder_plans, extruder_plan_idx, heating_time_and_from_temp.lowest_temperature);
|
||||
}
|
||||
|
||||
// handle preheat command
|
||||
double time_before_extruder_plan_to_insert = heating_time_and_from_temp.heating_time;
|
||||
for (unsigned int extruder_plan_before_idx = extruder_plan_idx - 1; int(extruder_plan_before_idx) >= 0; extruder_plan_before_idx--)
|
||||
{
|
||||
ExtruderPlan& extruder_plan_before = *extruder_plans[extruder_plan_before_idx];
|
||||
assert (extruder_plan_before.extruder != extruder);
|
||||
|
||||
|
||||
double time_here = extruder_plan_before.estimates.getTotalTime();
|
||||
if (time_here >= time_before_extruder_plan_to_insert)
|
||||
{
|
||||
insertPreheatCommand(extruder_plan_before, time_before_extruder_plan_to_insert, extruder, required_temp);
|
||||
insertPreheatCommand(extruder_plan_before, time_before_extruder_plan_to_insert, extruder, initial_print_temp);
|
||||
return;
|
||||
}
|
||||
time_before_extruder_plan_to_insert -= time_here;
|
||||
}
|
||||
|
||||
|
||||
// time_before_extruder_plan_to_insert falls before all plans in the buffer
|
||||
extruder_plans[0]->insertCommand(0, extruder, required_temp, false); // insert preheat command at verfy beginning of buffer
|
||||
bool wait = false;
|
||||
unsigned int path_idx = 0;
|
||||
extruder_plans[0]->insertCommand(path_idx, extruder, initial_print_temp, wait); // insert preheat command at verfy beginning of buffer
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
void LayerPlanBuffer::insertTempCommands(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& extruder_plan = *extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double required_temp = extruder_plan.required_temp;
|
||||
|
||||
|
||||
ExtruderPlan* prev_extruder_plan = extruder_plans[extruder_plan_idx - 1];
|
||||
@@ -162,19 +175,154 @@ void LayerPlanBuffer::insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_
|
||||
|
||||
if (prev_extruder == extruder)
|
||||
{
|
||||
if (preheat_config.usesFlowDependentTemp(extruder))
|
||||
{
|
||||
insertPreheatCommand_singleExtrusion(*prev_extruder_plan, extruder, required_temp);
|
||||
}
|
||||
insertPreheatCommand_singleExtrusion(*prev_extruder_plan, extruder, extruder_plan.printing_temperature);
|
||||
prev_extruder_plan->printing_temperature_command = --prev_extruder_plan->inserts.end();
|
||||
}
|
||||
else
|
||||
{
|
||||
insertPreheatCommand_multiExtrusion(extruder_plans, extruder_plan_idx);
|
||||
insertFinalPrintTempCommand(extruder_plans, extruder_plan_idx - 1);
|
||||
insertPrintTempCommand(extruder_plan);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommands()
|
||||
void LayerPlanBuffer::insertPrintTempCommand(ExtruderPlan& extruder_plan)
|
||||
{
|
||||
unsigned int extruder = extruder_plan.extruder;
|
||||
double print_temp = extruder_plan.printing_temperature;
|
||||
|
||||
double heated_pre_travel_time = 0;
|
||||
if (preheat_config.getInitialPrintTemp(extruder) != 0)
|
||||
{ // handle heating from initial_print_temperature to printing_tempreature
|
||||
unsigned int path_idx;
|
||||
for (path_idx = 0; path_idx < extruder_plan.paths.size(); path_idx++)
|
||||
{
|
||||
GCodePath& path = extruder_plan.paths[path_idx];
|
||||
heated_pre_travel_time += path.estimates.getTotalTime();
|
||||
if (!path.isTravelPath())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool wait = false;
|
||||
extruder_plan.insertCommand(path_idx, extruder, print_temp, wait);
|
||||
}
|
||||
extruder_plan.heated_pre_travel_time = heated_pre_travel_time;
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int last_extruder_plan_idx)
|
||||
{
|
||||
ExtruderPlan& last_extruder_plan = *extruder_plans[last_extruder_plan_idx];
|
||||
int extruder = last_extruder_plan.extruder;
|
||||
|
||||
double final_print_temp = preheat_config.getFinalPrintTemp(extruder);
|
||||
if (final_print_temp == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double heated_post_travel_time = 0; // The time after the last extrude move toward the end of the extruder plan during which the nozzle is stable at the final print temperature
|
||||
{ // compute heated_post_travel_time
|
||||
unsigned int path_idx;
|
||||
for (path_idx = last_extruder_plan.paths.size() - 1; int(path_idx) >= 0; path_idx--)
|
||||
{
|
||||
GCodePath& path = last_extruder_plan.paths[path_idx];
|
||||
if (!path.isTravelPath())
|
||||
{
|
||||
break;
|
||||
}
|
||||
heated_post_travel_time += path.estimates.getTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
double time_window = 0; // The time window within which the nozzle needs to heat from the initial print temp to the printing temperature and then back to the final print temp; i.e. from the first to the last extrusion move with this extruder
|
||||
double weighted_average_print_temp = 0; // The average of the normal printing temperatures of the extruder plans (which might be different due to flow dependent temp or due to initial layer temp) Weighted by time
|
||||
double initial_print_temp = -1; // The initial print temp of the first extruder plan with this extruder
|
||||
{ // compute time window and print temp statistics
|
||||
double heated_pre_travel_time = -1; // The time before the first extrude move from the start of the extruder plan during which the nozzle is stable at the initial print temperature
|
||||
for (unsigned int prev_extruder_plan_idx = last_extruder_plan_idx; (int)prev_extruder_plan_idx >= 0; prev_extruder_plan_idx--)
|
||||
{
|
||||
ExtruderPlan& prev_extruder_plan = *extruder_plans[prev_extruder_plan_idx];
|
||||
if (prev_extruder_plan.extruder != extruder)
|
||||
{
|
||||
break;
|
||||
}
|
||||
double prev_extruder_plan_time = prev_extruder_plan.estimates.getTotalTime();
|
||||
time_window += prev_extruder_plan_time;
|
||||
heated_pre_travel_time = prev_extruder_plan.heated_pre_travel_time;
|
||||
|
||||
if (prev_extruder_plan.estimates.getTotalUnretractedTime() > 0 && prev_extruder_plan.estimates.getMaterial() > 0)
|
||||
{ // handle temp statistics
|
||||
assert(prev_extruder_plan.printing_temperature != -1 && "Previous extruder plan should already have a temperature planned");
|
||||
weighted_average_print_temp += prev_extruder_plan.printing_temperature * prev_extruder_plan_time;
|
||||
initial_print_temp = prev_extruder_plan.initial_printing_temperature;
|
||||
}
|
||||
}
|
||||
weighted_average_print_temp /= time_window;
|
||||
time_window -= heated_pre_travel_time + heated_post_travel_time;
|
||||
assert(heated_pre_travel_time != -1 && "heated_pre_travel_time must have been computed; there must have been an extruder plan!");
|
||||
}
|
||||
|
||||
assert((time_window >= 0 || last_extruder_plan.estimates.getMaterial() == 0) && "Time window should always be positive if we actually extrude");
|
||||
|
||||
// ,layer change .
|
||||
// : ,precool command ,layer change .
|
||||
// : ____: : ,precool command .
|
||||
// :/ \ _____:_____: .
|
||||
// _____/ \ / \ .
|
||||
// / \ / \ .
|
||||
// / / .
|
||||
// / / .
|
||||
// .
|
||||
// approximate ^ by ^ .
|
||||
// This approximation is quite ok since it only determines where to insert the precool temp command,
|
||||
// which means the stable temperature of the previous extruder plan and the stable temperature of the next extruder plan couldn't be reached
|
||||
constexpr bool during_printing = true;
|
||||
Preheat::CoolDownResult warm_cool_result = preheat_config.getCoolDownPointAfterWarmUp(time_window, extruder, initial_print_temp, weighted_average_print_temp, final_print_temp, during_printing);
|
||||
double cool_down_time = warm_cool_result.cooling_time;
|
||||
assert(cool_down_time >= 0);
|
||||
|
||||
// find extruder plan in which to insert cooling command
|
||||
ExtruderPlan* precool_extruder_plan = &last_extruder_plan;
|
||||
{
|
||||
for (unsigned int precool_extruder_plan_idx = last_extruder_plan_idx; (int)precool_extruder_plan_idx >= 0; precool_extruder_plan_idx--)
|
||||
{
|
||||
precool_extruder_plan = extruder_plans[precool_extruder_plan_idx];
|
||||
if (precool_extruder_plan->printing_temperature_command)
|
||||
{ // the precool command ends up before the command to go to the print temperature of the next extruder plan, so remove that print temp command
|
||||
precool_extruder_plan->inserts.erase(*precool_extruder_plan->printing_temperature_command);
|
||||
}
|
||||
double time_here = precool_extruder_plan->estimates.getTotalTime();
|
||||
if (cool_down_time < time_here)
|
||||
{
|
||||
break;
|
||||
}
|
||||
cool_down_time -= time_here;
|
||||
}
|
||||
}
|
||||
|
||||
// at this point cool_down_time is what time is left if cool down time of extruder plans after precool_extruder_plan (up until last_extruder_plan) are already taken into account
|
||||
|
||||
{ // insert temp command in precool_extruder_plan
|
||||
double extrusion_time_seen = 0;
|
||||
unsigned int path_idx;
|
||||
for (path_idx = precool_extruder_plan->paths.size() - 1; int(path_idx) >= 0; path_idx--)
|
||||
{
|
||||
GCodePath& path = precool_extruder_plan->paths[path_idx];
|
||||
extrusion_time_seen += path.estimates.getTotalTime();
|
||||
if (extrusion_time_seen >= cool_down_time)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool wait = false;
|
||||
double time_after_path_start = extrusion_time_seen - cool_down_time;
|
||||
precool_extruder_plan->insertCommand(path_idx, extruder, final_print_temp, wait, time_after_path_start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LayerPlanBuffer::insertTempCommands()
|
||||
{
|
||||
if (buffer.back().extruder_plans.size() == 0 || (buffer.back().extruder_plans.size() == 1 && buffer.back().extruder_plans[0].paths.size() == 0))
|
||||
{ // disregard empty layer
|
||||
@@ -197,7 +345,9 @@ void LayerPlanBuffer::insertPreheatCommands()
|
||||
GCodePlanner& layer_plan = buffer.back();
|
||||
for (unsigned int extruder_plan_idx = 0; extruder_plan_idx < layer_plan.extruder_plans.size(); extruder_plan_idx++)
|
||||
{
|
||||
unsigned int overall_extruder_plan_idx = extruder_plans.size() - layer_plan.extruder_plans.size() + extruder_plan_idx;
|
||||
ExtruderPlan& extruder_plan = layer_plan.extruder_plans[extruder_plan_idx];
|
||||
int extruder = extruder_plan.extruder;
|
||||
double time = extruder_plan.estimates.getTotalUnretractedTime();
|
||||
if (time <= 0.0
|
||||
|| extruder_plan.estimates.getMaterial() == 0.0 // extruder plan only consists of moves (when an extruder switch occurs at the beginning of a layer)
|
||||
@@ -206,8 +356,18 @@ void LayerPlanBuffer::insertPreheatCommands()
|
||||
continue;
|
||||
}
|
||||
|
||||
double avg_flow = extruder_plan.estimates.getMaterial() / time; // TODO: subtract retracted travel time
|
||||
extruder_plan.required_temp = preheat_config.getTemp(extruder_plan.extruder, avg_flow);
|
||||
double avg_flow = extruder_plan.estimates.getMaterial() / time;
|
||||
extruder_plan.printing_temperature = preheat_config.getTemp(extruder, avg_flow, extruder_plan.is_initial_layer);
|
||||
extruder_plan.initial_printing_temperature = preheat_config.getInitialPrintTemp(extruder);
|
||||
if (extruder_plan.initial_printing_temperature == 0
|
||||
|| !extruder_used_in_meshgroup[extruder]
|
||||
|| (overall_extruder_plan_idx > 0 && extruder_plans[overall_extruder_plan_idx - 1]->extruder == extruder)
|
||||
)
|
||||
{
|
||||
extruder_plan.initial_printing_temperature = extruder_plan.printing_temperature;
|
||||
extruder_used_in_meshgroup[extruder] = true;
|
||||
}
|
||||
assert(extruder_plan.printing_temperature != -1 && "extruder_plan.printing_temperature should now have been set");
|
||||
|
||||
if (buffer.size() == 1 && extruder_plan_idx == 0)
|
||||
{ // the very first extruder plan of the current meshgroup
|
||||
@@ -221,7 +381,7 @@ void LayerPlanBuffer::insertPreheatCommands()
|
||||
// see FffGcodeWriter::processStartingCode
|
||||
if (extruder_idx == extruder)
|
||||
{
|
||||
gcode.setInitialTemp(extruder_idx, extruder_plan.required_temp);
|
||||
gcode.setInitialTemp(extruder_idx, extruder_plan.printing_temperature);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -239,8 +399,7 @@ void LayerPlanBuffer::insertPreheatCommands()
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned int overall_extruder_plan_idx = extruder_plans.size() - layer_plan.extruder_plans.size() + extruder_plan_idx;
|
||||
insertPreheatCommand(extruder_plans, overall_extruder_plan_idx);
|
||||
insertTempCommands(extruder_plans, overall_extruder_plan_idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+42
-5
@@ -16,6 +16,19 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for buffering multiple layer plans (\ref GCodePlanner) / extruder plans within those layer plans, so that temperature commands can be inserted in earlier layer plans.
|
||||
*
|
||||
* This class handles where to insert temperature commands for:
|
||||
* - initial layer temperature
|
||||
* - flow dependent temperature
|
||||
* - starting to heat up from the standby temperature
|
||||
* - initial printing temperature | printing temperature | final printing temperature
|
||||
*
|
||||
* \image html assets/precool.png "Temperature Regulation" width=10cm
|
||||
* \image latex assets/precool.png "Temperature Regulation" width=10cm
|
||||
*
|
||||
*/
|
||||
class LayerPlanBuffer : SettingsMessenger
|
||||
{
|
||||
GCodeExport& gcode;
|
||||
@@ -27,12 +40,14 @@ class LayerPlanBuffer : SettingsMessenger
|
||||
|
||||
static constexpr const double extra_preheat_time = 1.0; //!< Time to start heating earlier than computed to avoid accummulative discrepancy between actual heating times and computed ones.
|
||||
|
||||
std::vector<bool> extruder_used_in_meshgroup; //!< For each extruder whether it has already been planned once in this meshgroup. This is used to see whether we should heat to the initial_print_temp or to the printing_temperature
|
||||
public:
|
||||
std::list<GCodePlanner> buffer; //!< The buffer containing several layer plans (GCodePlanner) before writing them to gcode.
|
||||
|
||||
LayerPlanBuffer(SettingsBaseVirtual* settings, GCodeExport& gcode)
|
||||
: SettingsMessenger(settings)
|
||||
, gcode(gcode)
|
||||
, extruder_used_in_meshgroup(MAX_EXTRUDERS, false)
|
||||
{ }
|
||||
|
||||
void setPreheatConfig(MeshGroup& settings)
|
||||
@@ -49,7 +64,7 @@ public:
|
||||
{
|
||||
if (buffer.size() > 0)
|
||||
{
|
||||
insertPreheatCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
|
||||
insertTempCommands(); // insert preheat commands of the just completed layer plan (not the newly emplaced one)
|
||||
}
|
||||
buffer.emplace_back(constructor_args...);
|
||||
if (buffer.size() > buffer_size)
|
||||
@@ -68,7 +83,8 @@ public:
|
||||
* Write all remaining layer plans (GCodePlanner) to gcode and empty the buffer.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Insert the preheat command for @p extruder into @p extruder_plan_before
|
||||
*
|
||||
@@ -118,13 +134,34 @@ public:
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to generate the preheat command
|
||||
*/
|
||||
void insertPreheatCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
|
||||
void insertTempCommands(std::vector<ExtruderPlan*>& extruder_plans, unsigned int extruder_plan_idx);
|
||||
|
||||
/*!
|
||||
* Insert the temperature command to heat from the initial print temperature to the printing temperature
|
||||
*
|
||||
* The temperature command is insert at the start of the very first extrusion move
|
||||
*
|
||||
* \param extruder_plan The extruder plan in which to insert the heat up command
|
||||
*/
|
||||
void insertPrintTempCommand(ExtruderPlan& extruder_plan);
|
||||
|
||||
/*!
|
||||
* Insert the temp command to start cooling from the printing temperature to the final print temp
|
||||
*
|
||||
* The print temp is inserted before the last extrusion move of the extruder plan corresponding to \p last_extruder_plan_idx
|
||||
*
|
||||
* The command is inserted at a timed offset before the end of the last extrusion move
|
||||
*
|
||||
* \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers)
|
||||
* \param last_extruder_plan_idx The index of the last extruder plan in \p extruder_plans with the same extruder as previous extruder plans
|
||||
*/
|
||||
void insertFinalPrintTempCommand(std::vector<ExtruderPlan*>& extruder_plans, unsigned int last_extruder_plan_idx);
|
||||
|
||||
/*!
|
||||
* Insert the preheat commands for the last added layer (unless that layer was empty)
|
||||
*/
|
||||
void insertPreheatCommands();
|
||||
private:
|
||||
void insertTempCommands();
|
||||
|
||||
/*!
|
||||
* Reconfigure the standby temperature during which we didn't print with this extruder.
|
||||
* Find the previous extruder plan with the same extruder as layers[layer_plan_idx].extruder_plans[extruder_plan_idx]
|
||||
|
||||
+7
-2
@@ -133,7 +133,7 @@ void MeshGroup::finalize()
|
||||
{
|
||||
createExtruderTrain(extruder_nr); // create it if it didn't exist yet
|
||||
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") == extruder_nr)
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") == extruder_nr && getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
getExtruderTrain(extruder_nr)->setIsUsed(true);
|
||||
continue;
|
||||
@@ -157,7 +157,12 @@ void MeshGroup::finalize()
|
||||
|
||||
for (const Mesh& mesh : meshes)
|
||||
{
|
||||
getExtruderTrain(mesh.getSettingAsIndex("extruder_nr"))->setIsUsed(true);
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh.getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
getExtruderTrain(mesh.getSettingAsIndex("extruder_nr"))->setIsUsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
//If the machine settings have been supplied, offset the given position vertices to the center of vertices (0,0,0) is at the bed center.
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
#include "Preheat.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void Preheat::setConfig(const MeshGroup& meshgroup)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < meshgroup.getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
assert(meshgroup.getExtruderTrain(extruder_nr) != nullptr);
|
||||
const ExtruderTrain& extruder_train = *meshgroup.getExtruderTrain(extruder_nr);
|
||||
config_per_extruder.emplace_back();
|
||||
Config& config = config_per_extruder.back();
|
||||
double machine_nozzle_cool_down_speed = extruder_train.getSettingInSeconds("machine_nozzle_cool_down_speed");
|
||||
double machine_nozzle_heat_up_speed = extruder_train.getSettingInSeconds("machine_nozzle_heat_up_speed");
|
||||
double material_extrusion_cool_down_speed = extruder_train.getSettingInSeconds("material_extrusion_cool_down_speed");
|
||||
assert(material_extrusion_cool_down_speed < machine_nozzle_heat_up_speed && "The extrusion cooldown speed must be smaller than the heat up speed; otherwise the printing temperature cannot be reached!");
|
||||
config.time_to_cooldown_1_degree[0] = 1.0 / machine_nozzle_cool_down_speed;
|
||||
config.time_to_heatup_1_degree[0] = 1.0 / machine_nozzle_heat_up_speed;
|
||||
config.time_to_cooldown_1_degree[1] = 1.0 / (machine_nozzle_cool_down_speed + material_extrusion_cool_down_speed);
|
||||
config.time_to_heatup_1_degree[1] = 1.0 / (machine_nozzle_heat_up_speed - material_extrusion_cool_down_speed);
|
||||
config.standby_temp = extruder_train.getSettingInSeconds("material_standby_temperature");
|
||||
|
||||
config.min_time_window = extruder_train.getSettingInSeconds("machine_min_cool_heat_time_window");
|
||||
|
||||
config.material_print_temperature = extruder_train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
config.material_print_temperature_layer_0 = extruder_train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
config.material_initial_print_temperature = extruder_train.getSettingInDegreeCelsius("material_initial_print_temperature");
|
||||
config.material_final_print_temperature = extruder_train.getSettingInDegreeCelsius("material_final_print_temperature");
|
||||
|
||||
config.flow_dependent_temperature = extruder_train.getSettingBoolean("material_flow_dependent_temperature");
|
||||
|
||||
config.flow_temp_graph = extruder_train.getSettingAsFlowTempGraph("material_flow_temp_graph"); // [[0.1,180],[20,230]]
|
||||
}
|
||||
}
|
||||
|
||||
double Preheat::getTimeToGoFromTempToTemp(int extruder, double temp_before, double temp_after, bool during_printing)
|
||||
{
|
||||
Config& config = config_per_extruder[extruder];
|
||||
double time;
|
||||
if (temp_after > temp_before)
|
||||
{
|
||||
time = (temp_after - temp_before) * config.time_to_heatup_1_degree[during_printing];
|
||||
}
|
||||
else
|
||||
{
|
||||
time = (temp_before - temp_after) * config.time_to_cooldown_1_degree[during_printing];
|
||||
}
|
||||
return std::max(0.0, time);
|
||||
}
|
||||
|
||||
double Preheat::getTemp(unsigned int extruder, double flow, bool is_initial_layer)
|
||||
{
|
||||
if (is_initial_layer && config_per_extruder[extruder].material_print_temperature_layer_0 != 0)
|
||||
{
|
||||
return config_per_extruder[extruder].material_print_temperature_layer_0;
|
||||
}
|
||||
return config_per_extruder[extruder].flow_temp_graph.getTemp(flow, config_per_extruder[extruder].material_print_temperature, config_per_extruder[extruder].flow_dependent_temperature);
|
||||
}
|
||||
|
||||
Preheat::WarmUpResult Preheat::getWarmUpPointAfterCoolDown(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing)
|
||||
{
|
||||
WarmUpResult result;
|
||||
const Config& config = config_per_extruder[extruder];
|
||||
double time_to_cooldown_1_degree = config.time_to_cooldown_1_degree[during_printing];
|
||||
double time_to_heatup_1_degree = config.time_to_heatup_1_degree[during_printing];
|
||||
result.total_time_window = time_window;
|
||||
|
||||
// ,temp_end
|
||||
// / .
|
||||
// ,temp_start / .
|
||||
// \ ' ' ' ' '/ ' ' '> outer_temp .
|
||||
// \________/ .
|
||||
// "-> temp_mid
|
||||
// ^^^^^^^^^^
|
||||
// limited_time_window
|
||||
double outer_temp;
|
||||
double limited_time_window;
|
||||
if (temp_start < temp_end)
|
||||
{ // extra time needed during heating
|
||||
double extra_heatup_time = (temp_end - temp_start) * time_to_heatup_1_degree;
|
||||
result.heating_time = extra_heatup_time;
|
||||
limited_time_window = time_window - extra_heatup_time;
|
||||
outer_temp = temp_start;
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.heating_time = 0.0;
|
||||
result.lowest_temperature = temp_start;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double extra_cooldown_time = (temp_start - temp_end) * time_to_cooldown_1_degree;
|
||||
result.heating_time = 0;
|
||||
limited_time_window = time_window - extra_cooldown_time;
|
||||
outer_temp = temp_end;
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.heating_time = 0.0;
|
||||
result.lowest_temperature = temp_end;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
double time_ratio_cooldown_heatup = time_to_cooldown_1_degree / time_to_heatup_1_degree;
|
||||
double time_to_heat_from_standby_to_print_temp = getTimeToGoFromTempToTemp(extruder, temp_mid, outer_temp, during_printing);
|
||||
double time_needed_to_reach_standby_temp = time_to_heat_from_standby_to_print_temp * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_standby_temp < limited_time_window)
|
||||
{
|
||||
result.heating_time += time_to_heat_from_standby_to_print_temp;
|
||||
result.lowest_temperature = temp_mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.heating_time += limited_time_window * time_to_heatup_1_degree / (time_to_cooldown_1_degree + time_to_heatup_1_degree);
|
||||
result.lowest_temperature = std::max(temp_mid, temp_end - result.heating_time / time_to_heatup_1_degree);
|
||||
}
|
||||
|
||||
if (result.heating_time > time_window || result.heating_time < 0.0)
|
||||
{
|
||||
logWarning("getWarmUpPointAfterCoolDown returns result outside of the time window!");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Preheat::CoolDownResult Preheat::getCoolDownPointAfterWarmUp(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing)
|
||||
{
|
||||
CoolDownResult result;
|
||||
const Config& config = config_per_extruder[extruder];
|
||||
double time_to_cooldown_1_degree = config.time_to_cooldown_1_degree[during_printing];
|
||||
double time_to_heatup_1_degree = config.time_to_heatup_1_degree[during_printing];
|
||||
|
||||
assert(temp_start != -1 && temp_mid != -1 && temp_end != -1 && "temperatures must be initialized!");
|
||||
|
||||
result.total_time_window = time_window;
|
||||
|
||||
// limited_time_window
|
||||
// :^^^^^^^^^^^^:
|
||||
// : ________. : . . .> temp_mid
|
||||
// : / \ : .
|
||||
// :/ . . . . .\:. . .> outer_temp .
|
||||
// ^temp_start \ .
|
||||
// \ .
|
||||
// ^temp_end
|
||||
double outer_temp;
|
||||
double limited_time_window;
|
||||
if (temp_start < temp_end)
|
||||
{ // extra time needed during heating
|
||||
double extra_heatup_time = (temp_end - temp_start) * time_to_heatup_1_degree;
|
||||
result.cooling_time = 0;
|
||||
limited_time_window = time_window - extra_heatup_time;
|
||||
outer_temp = temp_end;
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.cooling_time = 0.0;
|
||||
result.highest_temperature = temp_end;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double extra_cooldown_time = (temp_start - temp_end) * time_to_cooldown_1_degree;
|
||||
result.cooling_time = extra_cooldown_time;
|
||||
limited_time_window = time_window - extra_cooldown_time;
|
||||
outer_temp = temp_start;
|
||||
if (limited_time_window < 0.0)
|
||||
{
|
||||
result.cooling_time = 0.0;
|
||||
result.highest_temperature = temp_start;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
double time_ratio_cooldown_heatup = time_to_cooldown_1_degree / time_to_heatup_1_degree;
|
||||
double cool_down_time = getTimeToGoFromTempToTemp(extruder, temp_mid, outer_temp, during_printing);
|
||||
double time_needed_to_reach_temp1 = cool_down_time * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_temp1 < limited_time_window)
|
||||
{
|
||||
result.cooling_time += cool_down_time;
|
||||
result.highest_temperature = temp_mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.cooling_time += limited_time_window * time_to_heatup_1_degree / (time_to_cooldown_1_degree + time_to_heatup_1_degree);
|
||||
result.highest_temperature = std::min(temp_mid, temp_end + result.cooling_time / time_to_cooldown_1_degree);
|
||||
}
|
||||
|
||||
if (result.cooling_time > time_window || result.cooling_time < 0.0)
|
||||
{
|
||||
logWarning("getCoolDownPointAfterWarmUp returns result outside of the time window!");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
+88
-104
@@ -26,17 +26,21 @@ class Preheat
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
double time_to_heatup_1_degree; //!< average time it takes to heat up one degree (in the range of normal print temperatures and standby temperature)
|
||||
double time_to_cooldown_1_degree; //!< average time it takes to cool down one degree (in the range of normal print temperatures and standby temperature)
|
||||
|
||||
double heatup_cooldown_time_mod_while_printing; //!< The time to be added to Preheat::time_to_heatup_1_degree and subtracted from Preheat::time_to_cooldown_1_degree to get the timings while printing
|
||||
double time_to_heatup_1_degree[2]; //!< average time it takes to heat up one degree (in the range of normal print temperatures and standby temperature), while not-printing and while printing
|
||||
double time_to_cooldown_1_degree[2]; //!< average time it takes to cool down one degree (in the range of normal print temperatures and standby temperature), while not-printing and while printing
|
||||
|
||||
double standby_temp; //!< The temperature at which the nozzle rests when it is not printing.
|
||||
|
||||
double min_time_window; //!< Minimal time (in seconds) to allow an extruder to cool down and then warm up again.
|
||||
|
||||
double material_print_temperature; //!< default print temp (backward compatilibily)
|
||||
|
||||
|
||||
double material_print_temperature_layer_0; //!< initial layer print temp
|
||||
|
||||
double material_initial_print_temperature; //!< print temp when first starting to extrude after a layer switch
|
||||
|
||||
double material_final_print_temperature; //!< print temp at the end of all extrusion moves of an extruder to which it's cooled down just before - during the extrusion
|
||||
|
||||
bool flow_dependent_temperature; //!< Whether to make the temperature dependent on flow
|
||||
|
||||
FlowTempGraph flow_temp_graph; //!< The graph linking flows to corresponding temperatures
|
||||
@@ -54,6 +58,16 @@ public:
|
||||
double lowest_temperature; //!< The lower temperature from which heating starts.
|
||||
};
|
||||
|
||||
/*!
|
||||
* The type of result when computing when to start cooling down a nozzle before it's not going to be used again.
|
||||
*/
|
||||
struct CoolDownResult
|
||||
{
|
||||
double total_time_window; //!< The total time in which heating and cooling takes place.
|
||||
double cooling_time; //!< The total time needed to cool down to the required temperature.
|
||||
double highest_temperature; //!< The upper temperature from which cooling starts.
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the standby temperature of an extruder train
|
||||
* \param extruder the extruder train for which to get the standby tmep
|
||||
@@ -68,68 +82,52 @@ public:
|
||||
* Get the time it takes to heat up one degree celsius
|
||||
*
|
||||
* \param extruder the extruder train for which to get time it takes to heat up one degree celsius
|
||||
* \param during_printing whether the heating takes time during printing or when idle
|
||||
* \return the time it takes to heat up one degree celsius
|
||||
*/
|
||||
double getTimeToHeatup1Degree(int extruder)
|
||||
double getTimeToHeatup1Degree(int extruder, bool during_printing)
|
||||
{
|
||||
return config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
return config_per_extruder[extruder].time_to_heatup_1_degree[during_printing];
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the initial print temperature when starting to extrude.
|
||||
* \param during_printing whether the heating takes time during printing or when idle
|
||||
*/
|
||||
double getInitialPrintTemp(int extruder)
|
||||
{
|
||||
return config_per_extruder[extruder].material_initial_print_temperature;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the final print temperature at the end of all extrusion moves with the current extruder
|
||||
*/
|
||||
double getFinalPrintTemp(int extruder)
|
||||
{
|
||||
return config_per_extruder[extruder].material_final_print_temperature;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the nozzle and material temperature settings for each extruder train.
|
||||
* \param meshgroup Where to get settings from
|
||||
*/
|
||||
void setConfig(MeshGroup& settings)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < settings.getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
assert(settings.getExtruderTrain(extruder_nr) != nullptr);
|
||||
ExtruderTrain& extruder_train = *settings.getExtruderTrain(extruder_nr);
|
||||
config_per_extruder.emplace_back();
|
||||
Config& config = config_per_extruder.back();
|
||||
config.time_to_cooldown_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_cool_down_speed"); // 0.5
|
||||
config.time_to_heatup_1_degree = 1.0 / extruder_train.getSettingInSeconds("machine_nozzle_heat_up_speed"); // 0.5
|
||||
config.heatup_cooldown_time_mod_while_printing = 1.0 / extruder_train.getSettingInSeconds("material_extrusion_cool_down_speed"); // 0.1
|
||||
config.standby_temp = extruder_train.getSettingInSeconds("material_standby_temperature"); // 150
|
||||
void setConfig(const MeshGroup& meshgroup);
|
||||
|
||||
config.min_time_window = extruder_train.getSettingInSeconds("machine_min_cool_heat_time_window");
|
||||
|
||||
config.material_print_temperature = extruder_train.getSettingInDegreeCelsius("material_print_temperature"); // 220
|
||||
|
||||
config.flow_dependent_temperature = extruder_train.getSettingBoolean("material_flow_dependent_temperature");
|
||||
|
||||
config.flow_temp_graph = extruder_train.getSettingAsFlowTempGraph("material_flow_temp_graph"); // [[0.1,180],[20,230]]
|
||||
}
|
||||
}
|
||||
|
||||
bool usesFlowDependentTemp(int extruder_nr)
|
||||
{
|
||||
return config_per_extruder[extruder_nr].flow_dependent_temperature;
|
||||
}
|
||||
private:
|
||||
/*!
|
||||
* Calculate time to heat up from standby temperature to a given temperature.
|
||||
* Assumes @p temp is higher than the standby temperature.
|
||||
*
|
||||
* \param extruder The extruder for which to get the time
|
||||
* \param temp The temperature to be reached
|
||||
*/
|
||||
double timeToHeatFromStandbyToPrintTemp(unsigned int extruder, double temp)
|
||||
{
|
||||
return (temp - config_per_extruder[extruder].standby_temp) * config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Get the optimal temperature corresponding to a given average flow.
|
||||
* Get the optimal temperature corresponding to a given average flow,
|
||||
* or the initial layer temperature.
|
||||
*
|
||||
* \param extruder The extruder train
|
||||
* \param flow The flow for which to get the optimal temperature
|
||||
* \param is_initial_layer Whether the initial layer temperature should be returned instead of flow-based temperature
|
||||
* \return The corresponding optimal temperature
|
||||
*/
|
||||
double getTemp(unsigned int extruder, double flow)
|
||||
{
|
||||
return config_per_extruder[extruder].flow_temp_graph.getTemp(flow, config_per_extruder[extruder].material_print_temperature, config_per_extruder[extruder].flow_dependent_temperature);
|
||||
}
|
||||
double getTemp(unsigned int extruder, double flow, bool is_initial_layer);
|
||||
|
||||
/*!
|
||||
* Return the minimal time window of a specific extruder for letting an unused extruder cool down and warm up again
|
||||
@@ -142,76 +140,62 @@ public:
|
||||
}
|
||||
|
||||
/*!
|
||||
* Decide when to start warming up again after starting to cool down towards the standby temperature.
|
||||
* Decide when to start warming up again after starting to cool down towards \p temp_mid.
|
||||
* Two cases are considered:
|
||||
* the case where the standby temperature is reached \__/ .
|
||||
* and the case where it isn't \/ .
|
||||
*
|
||||
* IT is assumed that the printer is not printing during this cool down and warm up time.
|
||||
*
|
||||
* Assumes from_temp is approximately the same as @p temp
|
||||
* \warning it is assumed that \p temp_mid is lower than both \p temp_start and \p temp_end. If not somewhat weird results may follow.
|
||||
*
|
||||
// ,temp_end
|
||||
// / .
|
||||
// ,temp_start / .
|
||||
// \ / .
|
||||
// \________/ .
|
||||
// "-> temp_mid
|
||||
* \param window_time The time window within which the cooldown and heat up must take place.
|
||||
* \param extruder The extruder used
|
||||
* \param temp The temperature to which to heat
|
||||
* \param temp_start The temperature from which to start cooling down
|
||||
* \param temp_mid The temeprature to which we try to cool down
|
||||
* \param temp_end The temperature to which we need to have heated up at the end of the \p time_window
|
||||
* \param during_printing Whether the warming up and cooling down is performed during printing
|
||||
* \return The time before the end of the @p time_window to insert the preheat command and the temperature from which the heating starts
|
||||
*/
|
||||
WarmUpResult timeBeforeEndToInsertPreheatCommand_coolDownWarmUp(double time_window, unsigned int extruder, double temp)
|
||||
{
|
||||
WarmUpResult result;
|
||||
const Config& config = config_per_extruder[extruder];
|
||||
result.total_time_window = time_window;
|
||||
double time_ratio_cooldown_heatup = config.time_to_cooldown_1_degree / config.time_to_heatup_1_degree;
|
||||
double time_to_heat_from_standby_to_print_temp = timeToHeatFromStandbyToPrintTemp(extruder, temp);
|
||||
double time_needed_to_reach_standby_temp = time_to_heat_from_standby_to_print_temp * (1.0 + time_ratio_cooldown_heatup);
|
||||
if (time_needed_to_reach_standby_temp < time_window)
|
||||
{
|
||||
result.heating_time = time_to_heat_from_standby_to_print_temp;
|
||||
result.lowest_temperature = config.standby_temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.heating_time = time_window * config.time_to_heatup_1_degree / (config.time_to_cooldown_1_degree + config.time_to_heatup_1_degree);
|
||||
result.lowest_temperature = std::max(config.standby_temp, temp - result.heating_time / config.time_to_heatup_1_degree);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
WarmUpResult getWarmUpPointAfterCoolDown(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing);
|
||||
|
||||
/*!
|
||||
* Calculate time needed to warm up the nozzle from a given temp to a given temp.
|
||||
* If the printer is printing in the mean time the warming up will take longer.
|
||||
* Decide when to start cooling down again after starting to warm up towards the \p temp_mid
|
||||
* Two cases are considered:
|
||||
* the case where the temperature is reached /"""\ .
|
||||
* and the case where it isn't /\ .
|
||||
*
|
||||
* \warning it is assumed that \p temp_mid is higher than both \p temp_start and \p temp_end. If not somewhat weird results may follow.
|
||||
*
|
||||
* \param from_temp The temperature at which the nozzle was before
|
||||
// _> temp_mid
|
||||
// /""""""""\ .
|
||||
// / \ .
|
||||
// ^temp_start \ .
|
||||
// \ .
|
||||
// ^temp_end
|
||||
* \param window_time The time window within which the cooldown and heat up must take place.
|
||||
* \param extruder The extruder used
|
||||
* \param temp The temperature to which to heat
|
||||
* \param printing Whether the printer is printing in the time to heat up the nozzle
|
||||
* \return The time needed to reach the desired temperature (@p temp)
|
||||
* \param temp_start The temperature from which to start heating up
|
||||
* \param temp_mid The temeprature to which we try to heat up
|
||||
* \param temp_end The temperature to which we need to have cooled down after \p time_window time
|
||||
* \param during_printing Whether the warming up and cooling down is performed during printing
|
||||
* \return The time before the end of the \p time_window to insert the preheat command and the temperature from which the cooling starts
|
||||
*/
|
||||
double timeBeforeEndToInsertPreheatCommand_warmUp(double from_temp, unsigned int extruder, double temp, bool printing)
|
||||
{
|
||||
if (temp > from_temp)
|
||||
{
|
||||
if (printing)
|
||||
{
|
||||
return (temp - from_temp) * (config_per_extruder[extruder].time_to_heatup_1_degree + config_per_extruder[extruder].heatup_cooldown_time_mod_while_printing);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (temp - from_temp) * config_per_extruder[extruder].time_to_heatup_1_degree;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (printing)
|
||||
{
|
||||
return (from_temp - temp) * config_per_extruder[extruder].time_to_cooldown_1_degree;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (from_temp - temp) * std::max(0.0, config_per_extruder[extruder].time_to_cooldown_1_degree - config_per_extruder[extruder].heatup_cooldown_time_mod_while_printing);
|
||||
}
|
||||
}
|
||||
}
|
||||
CoolDownResult getCoolDownPointAfterWarmUp(double time_window, unsigned int extruder, double temp_start, double temp_mid, double temp_end, bool during_printing);
|
||||
|
||||
/*!
|
||||
* Get the time to go from one temperature to another temperature
|
||||
* \param extruder The extruder number for which to perform the heatup / cooldown
|
||||
* \param temp_before The before temperature
|
||||
* \param temp_after The after temperature
|
||||
* \param during_printing Whether the planned cooldown / warmup occurs during printing or while in standby mode
|
||||
* \return The time needed
|
||||
*/
|
||||
double getTimeToGoFromTempToTemp(int extruder, double temp_before, double temp_after, bool during_printing);
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
+102
-112
@@ -13,7 +13,9 @@ namespace cura
|
||||
{
|
||||
|
||||
PrimeTower::PrimeTower()
|
||||
: current_pre_wipe_location_idx(0)
|
||||
: is_hollow(false)
|
||||
, wipe_from_middle(false)
|
||||
, current_pre_wipe_location_idx(0)
|
||||
{
|
||||
for (int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++)
|
||||
{
|
||||
@@ -40,7 +42,6 @@ void PrimeTower::initConfigs(const MeshGroup* meshgroup)
|
||||
|
||||
void PrimeTower::setConfigs(const MeshGroup* meshgroup, const int layer_thickness)
|
||||
{
|
||||
|
||||
extruder_count = meshgroup->getExtruderCount();
|
||||
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
@@ -50,65 +51,19 @@ void PrimeTower::setConfigs(const MeshGroup* meshgroup, const int layer_thicknes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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.meshgroup->getExtruderCount();
|
||||
|
||||
int max_object_height_per_extruder[extruder_count];
|
||||
std::fill_n(max_object_height_per_extruder, extruder_count, -1); // unitialize all as -1
|
||||
{ // compute max_object_height_per_extruder
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
unsigned int extr_nr = mesh.getSettingAsIndex("extruder_nr");
|
||||
max_object_height_per_extruder[extr_nr] =
|
||||
std::max( max_object_height_per_extruder[extr_nr]
|
||||
, mesh.layer_nr_max_filled_layer );
|
||||
}
|
||||
int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); // TODO: support extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_infill_extruder_nr] =
|
||||
std::max( max_object_height_per_extruder[support_infill_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
int support_skin_extruder_nr = storage.getSettingAsIndex("support_interface_extruder_nr"); // TODO: support skin extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_skin_extruder_nr] =
|
||||
std::max( max_object_height_per_extruder[support_skin_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
}
|
||||
{ // // compute max_object_height_second_to_last_extruder
|
||||
int extruder_max_object_height = 0;
|
||||
for (int extruder_nr = 1; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
if (max_object_height_per_extruder[extruder_nr] > max_object_height_per_extruder[extruder_max_object_height])
|
||||
{
|
||||
extruder_max_object_height = extruder_nr;
|
||||
}
|
||||
}
|
||||
int extruder_second_max_object_height = -1;
|
||||
for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
if (extruder_nr == extruder_max_object_height) { continue; }
|
||||
if (extruder_second_max_object_height == -1 || max_object_height_per_extruder[extruder_nr] > max_object_height_per_extruder[extruder_second_max_object_height])
|
||||
{
|
||||
extruder_second_max_object_height = extruder_nr;
|
||||
}
|
||||
}
|
||||
if (extruder_second_max_object_height < 0)
|
||||
{
|
||||
storage.max_object_height_second_to_last_extruder = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
storage.max_object_height_second_to_last_extruder = max_object_height_per_extruder[extruder_second_max_object_height];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrimeTower::generateGroundpoly(const SliceDataStorage& storage)
|
||||
{
|
||||
extruder_count = storage.meshgroup->getExtruderCount();
|
||||
|
||||
int64_t prime_tower_wall_thickness = storage.getSettingInMicrons("prime_tower_wall_thickness");
|
||||
int64_t tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
|
||||
if (prime_tower_wall_thickness * 2 < tower_size)
|
||||
{
|
||||
is_hollow = true;
|
||||
}
|
||||
|
||||
PolygonRef p = ground_poly.newPoly();
|
||||
int tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
int tower_distance = 0;
|
||||
int x = storage.getSettingInMicrons("prime_tower_position_x"); // storage.model_max.x
|
||||
int y = storage.getSettingInMicrons("prime_tower_position_y"); // storage.model_max.y
|
||||
@@ -116,13 +71,23 @@ void PrimeTower::generateGroundpoly(const SliceDataStorage& storage)
|
||||
p.add(Point(x + tower_distance, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance + tower_size));
|
||||
p.add(Point(x + tower_distance - tower_size, y + tower_distance));
|
||||
middle = Point(x - tower_size / 2, y + tower_size / 2);
|
||||
|
||||
if (is_hollow)
|
||||
{
|
||||
ground_poly = ground_poly.difference(ground_poly.offset(-prime_tower_wall_thickness));
|
||||
}
|
||||
|
||||
post_wipe_point = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
|
||||
}
|
||||
|
||||
void PrimeTower::generatePaths(const SliceDataStorage& storage, unsigned int total_layers)
|
||||
void PrimeTower::generatePaths(const SliceDataStorage& storage)
|
||||
{
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingBoolean("prime_tower_enable"))
|
||||
enabled = storage.max_print_height_second_to_last_extruder >= 0
|
||||
&& storage.getSettingBoolean("prime_tower_enable")
|
||||
&& storage.getSettingInMicrons("prime_tower_wall_thickness") > 10
|
||||
&& storage.getSettingInMicrons("prime_tower_size") > 10;
|
||||
if (enabled)
|
||||
{
|
||||
generatePaths_denseInfill(storage);
|
||||
generateWipeLocations(storage);
|
||||
@@ -143,14 +108,16 @@ void PrimeTower::generatePaths_denseInfill(const SliceDataStorage& storage)
|
||||
{
|
||||
int line_width = storage.meshgroup->getExtruderTrain(extruder)->getSettingInMicrons("prime_tower_line_width");
|
||||
patterns_per_extruder.emplace_back(n_patterns);
|
||||
std::vector<Polygons>& patterns = patterns_per_extruder.back();
|
||||
std::vector<ExtrusionMoves>& patterns = patterns_per_extruder.back();
|
||||
patterns.resize(n_patterns);
|
||||
for (int pattern_idx = 0; pattern_idx < n_patterns; pattern_idx++)
|
||||
{
|
||||
Polygons result_polygons; // should remain empty, since we generate lines pattern!
|
||||
patterns[pattern_idx].polygons = ground_poly.offset(-line_width / 2);
|
||||
Polygons& result_lines = patterns[pattern_idx].lines;
|
||||
int outline_offset = -line_width;
|
||||
int line_distance = line_width;
|
||||
double fill_angle = 45 + pattern_idx * 90;
|
||||
Polygons& result_lines = patterns[pattern_idx];
|
||||
Polygons result_polygons; // should remain empty, since we generate lines pattern!
|
||||
Infill infill_comp(EFillMethod::LINES, ground_poly, outline_offset, line_width, line_distance, infill_overlap, fill_angle, z, extra_infill_shift);
|
||||
infill_comp.generate(result_polygons, result_lines);
|
||||
}
|
||||
@@ -158,9 +125,9 @@ void PrimeTower::generatePaths_denseInfill(const SliceDataStorage& storage)
|
||||
}
|
||||
|
||||
|
||||
void PrimeTower::addToGcode(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, bool wipe)
|
||||
void PrimeTower::addToGcode(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder)
|
||||
{
|
||||
if (!( storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0) )
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -174,48 +141,44 @@ void PrimeTower::addToGcode(const SliceDataStorage& storage, GCodePlanner& gcode
|
||||
return;
|
||||
}
|
||||
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
if (layer_nr > storage.max_print_height_second_to_last_extruder + 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
if (prev_extruder == gcodeLayer.getExtruder())
|
||||
bool pre_wipe = storage.meshgroup->getExtruderTrain(new_extruder)->getSettingBoolean("dual_pre_wipe");
|
||||
bool post_wipe = storage.meshgroup->getExtruderTrain(prev_extruder)->getSettingBoolean("prime_tower_wipe_enabled");
|
||||
|
||||
if (prev_extruder == new_extruder)
|
||||
{
|
||||
wipe = false;
|
||||
pre_wipe = false;
|
||||
post_wipe = false;
|
||||
}
|
||||
// pre-wipe:
|
||||
if (wipe)
|
||||
if (pre_wipe)
|
||||
{
|
||||
preWipe(storage, gcodeLayer, new_extruder);
|
||||
}
|
||||
|
||||
addToGcode_denseInfill(storage, gcodeLayer, gcode, layer_nr, prev_extruder);
|
||||
addToGcode_denseInfill(storage, gcodeLayer, gcode, layer_nr, prev_extruder, new_extruder);
|
||||
|
||||
// post-wipe:
|
||||
if (false && wipe) // TODO: make a separate setting for the post-wipe!
|
||||
if (post_wipe)
|
||||
{ //Make sure we wipe the old extruder on the prime tower.
|
||||
gcodeLayer.addTravel(post_wipe_point - gcode.getExtruderOffset(prev_extruder) + gcode.getExtruderOffset(new_extruder));
|
||||
}
|
||||
}
|
||||
|
||||
void PrimeTower::addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder)
|
||||
void PrimeTower::addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcodeLayer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder)
|
||||
{
|
||||
int new_extruder = gcodeLayer.getExtruder();
|
||||
|
||||
Polygons& pattern = patterns_per_extruder[new_extruder][layer_nr % 2];
|
||||
|
||||
ExtrusionMoves& pattern = patterns_per_extruder[new_extruder][((layer_nr % 2) + 2) % 2]; // +2) %2 to handle negative layer numbers
|
||||
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
int start_idx = 0; // TODO: figure out which idx is closest to the far right corner
|
||||
|
||||
Polygon outer_wall = ground_poly.offset(-config.getLineWidth() / 2).back();
|
||||
gcodeLayer.addPolygon(outer_wall, start_idx, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern, &config, SpaceFillType::Lines);
|
||||
gcodeLayer.addPolygonsByOptimizer(pattern.polygons, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern.lines, &config, SpaceFillType::Lines);
|
||||
|
||||
last_prime_tower_poly_printed[new_extruder] = layer_nr;
|
||||
|
||||
CommandSocket::sendPolygons(PrintFeatureType::Support, pattern, config.getLineWidth());
|
||||
}
|
||||
|
||||
Point PrimeTower::getLocationBeforePrimeTower(const SliceDataStorage& storage)
|
||||
@@ -248,37 +211,52 @@ Point PrimeTower::getLocationBeforePrimeTower(const SliceDataStorage& storage)
|
||||
|
||||
void PrimeTower::generateWipeLocations(const SliceDataStorage& storage)
|
||||
{
|
||||
Point from = getLocationBeforePrimeTower(storage);
|
||||
|
||||
// take the closer corner of the wipe tower and generate wipe locations on that side only:
|
||||
//
|
||||
// |
|
||||
// |
|
||||
// +-----
|
||||
// .
|
||||
// ^ nozzle switch location
|
||||
wipe_from_middle = is_hollow;
|
||||
// only wipe from the middle of the prime tower if we have a z hop already on the first move after the layer switch
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
const ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
wipe_from_middle &= train.getSettingBoolean("retraction_hop_enabled")
|
||||
&& (!train.getSettingBoolean("retraction_hop_only_when_collides") || train.getSettingBoolean("retraction_hop_after_extruder_switch"));
|
||||
}
|
||||
|
||||
PolygonsPointIndex segment_start; // from where to start the sequence of wipe points
|
||||
PolygonsPointIndex segment_end; // where to end the sequence of wipe points
|
||||
|
||||
// find the single line segment closest to [from] pointing most toward [from]
|
||||
PolygonsPointIndex closest_vert = PolygonUtils::findNearestVert(from, ground_poly);
|
||||
PolygonsPointIndex prev = closest_vert.prev();
|
||||
PolygonsPointIndex next = closest_vert.next();
|
||||
int64_t prev_dot_score = dot(from - closest_vert.p(), turn90CCW(prev.p() - closest_vert.p()));
|
||||
int64_t next_dot_score = dot(from - closest_vert.p(), turn90CCW(closest_vert.p() - next.p()));
|
||||
if (prev_dot_score > next_dot_score)
|
||||
if (wipe_from_middle)
|
||||
{
|
||||
segment_start = prev;
|
||||
segment_end = closest_vert;
|
||||
// take the same start as end point so that the whole poly os covered.
|
||||
// find the inner polygon.
|
||||
segment_start = segment_end = PolygonUtils::findNearestVert(middle, ground_poly);
|
||||
}
|
||||
else
|
||||
{
|
||||
segment_start = closest_vert;
|
||||
segment_end = next;
|
||||
}
|
||||
// take the closer corner of the wipe tower and generate wipe locations on that side only:
|
||||
//
|
||||
// |
|
||||
// |
|
||||
// +-----
|
||||
// .
|
||||
// ^ nozzle switch location
|
||||
Point from = getLocationBeforePrimeTower(storage);
|
||||
|
||||
// TODO: come up with alternatives for better segments once the prime tower can be different shapes
|
||||
// find the single line segment closest to [from] pointing most toward [from]
|
||||
PolygonsPointIndex closest_vert = PolygonUtils::findNearestVert(from, ground_poly);
|
||||
PolygonsPointIndex prev = closest_vert.prev();
|
||||
PolygonsPointIndex next = closest_vert.next();
|
||||
int64_t prev_dot_score = dot(from - closest_vert.p(), turn90CCW(prev.p() - closest_vert.p()));
|
||||
int64_t next_dot_score = dot(from - closest_vert.p(), turn90CCW(closest_vert.p() - next.p()));
|
||||
if (prev_dot_score > next_dot_score)
|
||||
{
|
||||
segment_start = prev;
|
||||
segment_end = closest_vert;
|
||||
}
|
||||
else
|
||||
{
|
||||
segment_start = closest_vert;
|
||||
segment_end = next;
|
||||
}
|
||||
}
|
||||
|
||||
PolygonUtils::spreadDots(segment_start, segment_end, number_of_pre_wipe_locations, pre_wipe_locations);
|
||||
}
|
||||
@@ -294,13 +272,25 @@ void PrimeTower::preWipe(const SliceDataStorage& storage, GCodePlanner& gcode_la
|
||||
const Point end = PolygonUtils::moveInsideDiagonally(wipe_location, inward_dist);
|
||||
const Point outward_dir = wipe_location.location - end;
|
||||
const Point start = wipe_location.location + normal(outward_dir, start_dist);
|
||||
// for hollow wipe tower:
|
||||
// start from above
|
||||
// go to the level of the previous layer
|
||||
// wipe
|
||||
// go to normal layer height (automatically on the next extrusion move...
|
||||
gcode_layer.addTravel(start); // TODO: verify that this move has a z hop ==> cylindric wipe tower
|
||||
// gcode_layer.makeLastPathZhopped which calls forceNewPathStart TODO ==> cylindric wipe tower
|
||||
if (wipe_from_middle)
|
||||
{
|
||||
// for hollow wipe tower:
|
||||
// start from above
|
||||
// go to wipe start
|
||||
// go to the Z height of the previous/current layer
|
||||
// wipe
|
||||
// go to normal layer height (automatically on the next extrusion move)...
|
||||
GCodePath& toward_middle = gcode_layer.addTravel(middle);
|
||||
toward_middle.perform_z_hop = true;
|
||||
gcode_layer.forceNewPathStart();
|
||||
GCodePath& toward_wipe_start = gcode_layer.addTravel_simple(start);
|
||||
toward_wipe_start.perform_z_hop = false;
|
||||
toward_wipe_start.retract = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.addTravel(start);
|
||||
}
|
||||
float flow = 0.0001; // force this path being interpreted as an extrusion path, so that no Z hop will occur (TODO: really separately handle travel and extrusion moves)
|
||||
gcode_layer.addExtrusionMove(end, &config_per_extruder[extruder_nr], SpaceFillType::None, flow);
|
||||
}
|
||||
|
||||
+26
-21
@@ -16,8 +16,6 @@ class SliceDataStorage;
|
||||
class GCodePlanner;
|
||||
class GCodeExport;
|
||||
|
||||
typedef std::vector<IntPoint> PolyLine;
|
||||
|
||||
/*!
|
||||
* Class for everything to do with the prime tower:
|
||||
* - generating the areas
|
||||
@@ -27,22 +25,34 @@ typedef std::vector<IntPoint> PolyLine;
|
||||
class PrimeTower
|
||||
{
|
||||
private:
|
||||
struct ExtrusionMoves
|
||||
{
|
||||
Polygons polygons;
|
||||
Polygons lines;
|
||||
};
|
||||
bool enabled; //!< Whether the prime tower is enabled
|
||||
|
||||
int extruder_count; //!< number of extruders
|
||||
std::vector<GCodePathConfig> config_per_extruder; //!< Path config for prime tower for each extruder
|
||||
|
||||
bool is_hollow; //!< Whether the prime tower is hollow
|
||||
|
||||
bool wipe_from_middle; //!< Whether to wipe on the inside of the hollow prime tower
|
||||
Point middle; //!< The middle of the prime tower
|
||||
|
||||
Point post_wipe_point; //!< location to post-wipe the unused nozzle off on
|
||||
|
||||
std::vector<PolyLine> extruder_paths; //!< Precomputed so that we don't need to generate the paths each layer over again
|
||||
|
||||
std::vector<ClosestPolygonPoint> pre_wipe_locations; //!< The differernt locations where to pre-wipe the active nozzle
|
||||
const unsigned int pre_wipe_location_skip = 8; //!< How big the steps are when stepping through \ref PrimeTower::wipe_locations
|
||||
const unsigned int number_of_pre_wipe_locations = 13; //!< The required size of \ref PrimeTower::wipe_locations
|
||||
// note that the above are two consecutive numbers in the fibonacci sequence
|
||||
const unsigned int pre_wipe_location_skip = 13; //!< How big the steps are when stepping through \ref PrimeTower::wipe_locations
|
||||
const unsigned int number_of_pre_wipe_locations = 21; //!< The required size of \ref PrimeTower::wipe_locations
|
||||
// note that the above are two consecutive numbers in the Fibonacci sequence
|
||||
int current_pre_wipe_location_idx; //!< Index into \ref PrimeTower::wipe_locations of where to pre-wipe the nozzle
|
||||
|
||||
public:
|
||||
Polygons ground_poly; //!< The outline of the prime tower to be used for each layer
|
||||
|
||||
std::vector<std::vector<ExtrusionMoves>> patterns_per_extruder; //!< for each extruder a vector of patterns to alternate between, over the layers
|
||||
|
||||
/*!
|
||||
* Initialize \ref PrimeTower::config_per_extruder with speed and line width settings.
|
||||
*
|
||||
@@ -61,26 +71,19 @@ public:
|
||||
/*!
|
||||
* Generate the prime tower area to be used on each layer
|
||||
*
|
||||
* Fills \ref PrimeTower::ground_poly and sets \ref PrimeTower::middle
|
||||
*
|
||||
* \param storage Where to retrieve prime tower settings from
|
||||
*/
|
||||
void generateGroundpoly(const SliceDataStorage& storage);
|
||||
|
||||
std::vector<std::vector<Polygons>> patterns_per_extruder; //!< for each extruder a vector of patterns to alternate between, over the layers
|
||||
|
||||
/*!
|
||||
* Generate the area where the prime tower should be.
|
||||
*
|
||||
* \param storage where to get settings from
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void generatePaths(const SliceDataStorage& storage, unsigned int total_layers);
|
||||
|
||||
/*!
|
||||
* Compute the maximum layer at which a layer switch will occur and store the result in \ref SliceDataStorage::max_object_height_second_to_last_extruder
|
||||
*
|
||||
* \param[in,out] storage Where to retrieve area data and extruder settings for those areas; where to store the max_object_height_second_to_last_extruder
|
||||
*/
|
||||
void computePrimeTowerMax(SliceDataStorage& storage);
|
||||
void generatePaths(const SliceDataStorage& storage);
|
||||
|
||||
PrimeTower(); //!< basic constructor
|
||||
|
||||
@@ -91,9 +94,9 @@ public:
|
||||
* \param[in,out] gcode_layer Where to get the current extruder from; where to store the generated layer paths
|
||||
* \param layer_nr The layer for which to generate the prime tower paths
|
||||
* \param prev_extruder The previous extruder with which paths were planned; from which extruder a switch was made
|
||||
* \param wipe Whether to wipe of the (not previous, but) current nozzle on the wipe tower (only occurs if previous extruder is different fromt he current one)
|
||||
* \param new_extruder The switched to extruder with which the prime tower paths should be generated.
|
||||
*/
|
||||
void addToGcode(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, bool wipe);
|
||||
void addToGcode(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder);
|
||||
private:
|
||||
/*!
|
||||
* Layer number of the last layer in which a prime tower has been printed per extruder train.
|
||||
@@ -121,7 +124,8 @@ private:
|
||||
/*!
|
||||
* \see WipeTower::generatePaths
|
||||
*
|
||||
* Generate the area where the prime tower should be.
|
||||
* Generate the extrude paths for each extruder on even and odd layers
|
||||
* Fill the ground poly with dense infill.
|
||||
*
|
||||
* \param storage where to get settings from
|
||||
* \param total_layers The total number of layers
|
||||
@@ -137,8 +141,9 @@ private:
|
||||
* \param[in,out] gcode_layer Where to get the current extruder from; where to store the generated layer paths
|
||||
* \param layer_nr The layer for which to generate the prime tower paths
|
||||
* \param prev_extruder The previous extruder with which paths were planned; from which extruder a switch was made
|
||||
* \param new_extruder The switched to extruder with which the prime tower paths should be generated.
|
||||
*/
|
||||
void addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder);
|
||||
void addToGcode_denseInfill(const SliceDataStorage& storage, GCodePlanner& gcode_layer, const GCodeExport& gcode, const int layer_nr, const int prev_extruder, const int new_extruder);
|
||||
|
||||
/*!
|
||||
* Plan the moves for wiping the current nozzles oozed material before starting to print the prime tower.
|
||||
|
||||
+2
-2
@@ -19,7 +19,6 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const unsigned i
|
||||
{ // add brim underneath support by removing support where there's brim around the model
|
||||
const bool include_helper_parts = false; // include manually below
|
||||
first_layer_outline = storage.getLayerOutlines(layer_nr, include_helper_parts, external_only);
|
||||
first_layer_outline.add(storage.primeTower.ground_poly); // don't remove parts of the prime tower, but make a brim for it
|
||||
Polygons first_layer_empty_holes;
|
||||
if (outside_only)
|
||||
{
|
||||
@@ -35,7 +34,7 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const unsigned i
|
||||
// || || ||[]|| > expand to fit an extra brim line
|
||||
// |+-+| |+--+|
|
||||
// +---+ +----+
|
||||
Polygons model_brim_covered_area = first_layer_outline.offset(primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2)); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides
|
||||
Polygons model_brim_covered_area = first_layer_outline.offset(primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2), ClipperLib::jtRound); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides
|
||||
if (outside_only)
|
||||
{ // don't remove support within empty holes where no brim is generated.
|
||||
model_brim_covered_area.add(first_layer_empty_holes);
|
||||
@@ -45,6 +44,7 @@ void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const unsigned i
|
||||
first_layer_outline.add(support_layer.supportAreas);
|
||||
first_layer_outline.add(support_layer.skin);
|
||||
}
|
||||
first_layer_outline.add(storage.primeTower.ground_poly); // don't remove parts of the prime tower, but make a brim for it
|
||||
}
|
||||
constexpr int join_distance = 20;
|
||||
first_layer_outline = first_layer_outline.offset(join_distance).offset(-join_distance); // merge adjacent models into single polygon
|
||||
|
||||
+49
-13
@@ -34,6 +34,8 @@ GCodeExport::GCodeExport()
|
||||
initial_bed_temp = 0;
|
||||
|
||||
extruder_count = 0;
|
||||
|
||||
total_bounding_box = AABB3D();
|
||||
}
|
||||
|
||||
GCodeExport::~GCodeExport()
|
||||
@@ -49,14 +51,19 @@ void GCodeExport::preSetup(const MeshGroup* meshgroup)
|
||||
|
||||
for (const Mesh& mesh : meshgroup->meshes)
|
||||
{
|
||||
extruder_attr[mesh.getSettingAsIndex("extruder_nr")].is_used = true;
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh.getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
extruder_attr[mesh.getSettingAsIndex("extruder_nr")].is_used = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
|
||||
{
|
||||
const ExtruderTrain* train = meshgroup->getExtruderTrain(extruder_nr);
|
||||
|
||||
if (meshgroup->getSettingAsIndex("adhesion_extruder_nr") == int(extruder_nr))
|
||||
if (meshgroup->getSettingAsIndex("adhesion_extruder_nr") == int(extruder_nr) && meshgroup->getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
extruder_attr[extruder_nr].is_used = true;
|
||||
}
|
||||
@@ -104,15 +111,22 @@ void GCodeExport::preSetup(const MeshGroup* meshgroup)
|
||||
|
||||
void GCodeExport::setInitialTemps(const MeshGroup& settings)
|
||||
{
|
||||
int start_extruder_nr = 0;
|
||||
if (settings.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
start_extruder_nr = settings.getSettingAsIndex("adhesion_extruder_nr");
|
||||
}
|
||||
for (unsigned int extr_nr = 0; extr_nr < extruder_count; extr_nr++)
|
||||
{
|
||||
const ExtruderTrain* extr_train = settings.getExtruderTrain(extr_nr);
|
||||
assert(extr_train);
|
||||
double temp = extr_train->getSettingInDegreeCelsius((extr_nr == 0)? "material_print_temperature" : "material_standby_temperature");
|
||||
const ExtruderTrain& train = *settings.getExtruderTrain(extr_nr);
|
||||
|
||||
double print_temp_0 = train.getSettingInDegreeCelsius("material_print_temperature_layer_0");
|
||||
double print_temp_here = (print_temp_0 != 0)? print_temp_0 : train.getSettingInDegreeCelsius("material_print_temperature");
|
||||
double temp = ((int)extr_nr == start_extruder_nr)? print_temp_here : train.getSettingInDegreeCelsius("material_standby_temperature");
|
||||
setInitialTemp(extr_nr, temp);
|
||||
}
|
||||
|
||||
initial_bed_temp = settings.getSettingInDegreeCelsius("material_bed_temperature");
|
||||
initial_bed_temp = settings.getSettingInDegreeCelsius("material_bed_temperature_layer_0");
|
||||
}
|
||||
|
||||
void GCodeExport::setInitialTemp(int extruder_nr, double temp)
|
||||
@@ -163,12 +177,12 @@ std::string GCodeExport::getFileHeader(const double* print_time, const std::vect
|
||||
prefix << ";PRINT.TIME:" << static_cast<int>(*print_time) << new_line;
|
||||
}
|
||||
|
||||
prefix << ";PRINT.SIZE.MIN.X:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Y:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Z:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.X:" << INT2MM(machine_dimensions.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Y:" << INT2MM(machine_dimensions.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Z:" << INT2MM(machine_dimensions.z) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.X:" << INT2MM(total_bounding_box.min.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Y:" << INT2MM(total_bounding_box.min.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Z:" << INT2MM(total_bounding_box.min.z) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.X:" << INT2MM(total_bounding_box.max.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Y:" << INT2MM(total_bounding_box.max.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Z:" << INT2MM(total_bounding_box.max.z) << new_line;
|
||||
prefix << ";END_OF_HEADER" << new_line;
|
||||
return prefix.str();
|
||||
default:
|
||||
@@ -538,6 +552,8 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(300)); // no crazy positions (this code should not be compiled for release)
|
||||
#endif //ASSERT_INSANE_OUTPUT
|
||||
|
||||
total_bounding_box.include(Point3(x, y, z));
|
||||
|
||||
if (extrusion_mm3_per_mm < 0)
|
||||
logWarning("Warning! Negative extrusion move!");
|
||||
|
||||
@@ -709,6 +725,16 @@ void GCodeExport::writeZhopStart(int hop_height)
|
||||
{
|
||||
isZHopped = hop_height;
|
||||
*output_stream << "G1 Z" << MMtoStream{currentPosition.z + isZHopped} << new_line;
|
||||
total_bounding_box.include(currentPosition + Point3(0, 0, isZHopped));
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeExport::writeZhopEnd()
|
||||
{
|
||||
if (isZHopped)
|
||||
{
|
||||
isZHopped = 0;
|
||||
*output_stream << "G1 Z" << MMtoStream{currentPosition.z} << new_line;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,7 +839,12 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
|
||||
{
|
||||
if (!wait && extruder_attr[extruder].currentTemperature == temperature)
|
||||
return;
|
||||
|
||||
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE)
|
||||
{ // The UM2 family doesn't support temperature commands (they are fixed in the firmware)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait)
|
||||
*output_stream << "M109";
|
||||
else
|
||||
@@ -829,6 +860,11 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
|
||||
|
||||
void GCodeExport::writeBedTemperatureCommand(double temperature, bool wait)
|
||||
{
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE)
|
||||
{ // The UM2 family doesn't support temperature commands (they are fixed in the firmware)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait)
|
||||
*output_stream << "M190 S";
|
||||
else
|
||||
|
||||
@@ -99,6 +99,8 @@ private:
|
||||
double current_jerk; //!< The current jerk in the XY direction (in mm/s^3)
|
||||
double current_max_z_feedrate; //!< The current max z speed
|
||||
|
||||
AABB3D total_bounding_box; //!< The bounding box of all g-code.
|
||||
|
||||
/*!
|
||||
* The z position to be used on the next xy move, if the head wasn't in the correct z position yet.
|
||||
*
|
||||
@@ -268,6 +270,12 @@ public:
|
||||
*/
|
||||
void writeZhopStart(int hop_height);
|
||||
|
||||
/*!
|
||||
* End a z hop: go back to the layer height
|
||||
*
|
||||
*/
|
||||
void writeZhopEnd();
|
||||
|
||||
/*!
|
||||
* Start the new_extruder:
|
||||
* - set new extruder
|
||||
|
||||
+108
-45
@@ -5,6 +5,7 @@
|
||||
#include "sliceDataStorage.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "MergeInfillLines.h"
|
||||
#include "raft.h" // getTotalExtraLayers
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -22,11 +23,14 @@ TimeMaterialEstimates& TimeMaterialEstimates::operator-=(const TimeMaterialEstim
|
||||
return *this;
|
||||
}
|
||||
|
||||
ExtruderPlan::ExtruderPlan(int extruder, Point start_position, int layer_nr, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config)
|
||||
ExtruderPlan::ExtruderPlan(int extruder, Point start_position, int layer_nr, bool is_initial_layer, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config)
|
||||
: extruder(extruder)
|
||||
, required_temp(-1)
|
||||
, heated_pre_travel_time(0)
|
||||
, initial_printing_temperature(-1)
|
||||
, printing_temperature(-1)
|
||||
, start_position(start_position)
|
||||
, layer_nr(layer_nr)
|
||||
, is_initial_layer(is_initial_layer)
|
||||
, layer_thickness(layer_thickness)
|
||||
, fan_speed_layer_time_settings(fan_speed_layer_time_settings)
|
||||
, retraction_config(retraction_config)
|
||||
@@ -89,9 +93,10 @@ void GCodePlanner::forceNewPathStart()
|
||||
paths[paths.size()-1].done = true;
|
||||
}
|
||||
|
||||
GCodePlanner::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, bool is_inside_mesh, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
GCodePlanner::GCodePlanner(SliceDataStorage& storage, int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
, layer_nr(layer_nr)
|
||||
, is_initial_layer(layer_nr == 0 - Raft::getTotalExtraLayers(storage))
|
||||
, z(z)
|
||||
, layer_thickness(layer_thickness)
|
||||
, start_position(last_position)
|
||||
@@ -102,10 +107,10 @@ GCodePlanner::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int
|
||||
, fan_speed_layer_time_settings_per_extruder(fan_speed_layer_time_settings_per_extruder)
|
||||
{
|
||||
extruder_plans.reserve(storage.meshgroup->getExtruderCount());
|
||||
extruder_plans.emplace_back(current_extruder, start_position, layer_nr, layer_thickness, fan_speed_layer_time_settings_per_extruder[current_extruder], storage.retraction_config_per_extruder[current_extruder]);
|
||||
extruder_plans.emplace_back(current_extruder, start_position, layer_nr, is_initial_layer, layer_thickness, fan_speed_layer_time_settings_per_extruder[current_extruder], storage.retraction_config_per_extruder[current_extruder]);
|
||||
comb = nullptr;
|
||||
was_inside = is_inside_mesh;
|
||||
is_inside = false; // assumes the next move will not be to inside a layer part (overwritten just before going into a layer part)
|
||||
was_inside = storage.getPartInside(layer_nr, start_position);
|
||||
is_inside = nullptr; // assumes the next move will not be to inside a layer part (overwritten just before going into a layer part)
|
||||
if (combing_mode != CombingMode::OFF)
|
||||
{
|
||||
comb = new Comb(storage, layer_nr, comb_boundary_inside, comb_boundary_offset, travel_avoid_other_parts, travel_avoid_distance);
|
||||
@@ -134,14 +139,7 @@ Polygons GCodePlanner::computeCombBoundaryInside(CombingMode combing_mode)
|
||||
}
|
||||
if (layer_nr < 0)
|
||||
{ // when a raft is present
|
||||
if (combing_mode == CombingMode::NO_SKIN)
|
||||
{
|
||||
return Polygons();
|
||||
}
|
||||
else
|
||||
{
|
||||
return storage.raftOutline.offset(MM2INT(0.1));
|
||||
}
|
||||
return Polygons();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -169,7 +167,7 @@ Polygons GCodePlanner::computeCombBoundaryInside(CombingMode combing_mode)
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::setIsInside(bool _is_inside)
|
||||
void GCodePlanner::setIsInside(SliceLayerPart* _is_inside)
|
||||
{
|
||||
is_inside = _is_inside;
|
||||
}
|
||||
@@ -180,7 +178,7 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
setIsInside(false);
|
||||
setIsInside(nullptr);
|
||||
{ // handle end position of the prev extruder
|
||||
SettingsBaseVirtual* train = getLastPlannedExtruderTrainSettings();
|
||||
bool end_pos_absolute = train->getSettingBoolean("machine_extruder_end_pos_abs");
|
||||
@@ -202,7 +200,8 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
}
|
||||
else
|
||||
{
|
||||
extruder_plans.emplace_back(extruder, lastPosition, layer_nr, layer_thickness, fan_speed_layer_time_settings_per_extruder[extruder], storage.retraction_config_per_extruder[extruder]);
|
||||
extruder_plans.emplace_back(extruder, lastPosition, layer_nr, is_initial_layer, layer_thickness, fan_speed_layer_time_settings_per_extruder[extruder], storage.retraction_config_per_extruder[extruder]);
|
||||
assert((int)extruder_plans.size() <= storage.meshgroup->getExtruderCount() && "Never use the same extruder twice on one layer!");
|
||||
}
|
||||
last_planned_extruder_setting_base = storage.meshgroup->getExtruderTrain(extruder);
|
||||
|
||||
@@ -226,11 +225,27 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
return true;
|
||||
}
|
||||
|
||||
void GCodePlanner::moveInsideCombBoundary(int distance)
|
||||
void GCodePlanner::moveInsideCombBoundary(int distance, const SliceLayerPart& part)
|
||||
{
|
||||
int max_dist2 = MM2INT(2.0) * MM2INT(2.0); // if we are further than this distance, we conclude we are not inside even though we thought we were.
|
||||
// this function is to be used to move from the boudary of a part to inside the part
|
||||
int max_dist2 = MM2INT(2.0) * MM2INT(2.0); // if we are further than this distance, we conclude we are not inside even though we thought we were.
|
||||
Point p = lastPosition; // copy, since we are going to move p
|
||||
{ // first move inside the last part, so that the chance is higher that we move inside the same part
|
||||
const Polygons* comb_boundary_here;
|
||||
if (part.insets.size() > 1)
|
||||
{
|
||||
comb_boundary_here = &part.insets[1];
|
||||
}
|
||||
else if (part.insets.size() == 1)
|
||||
{
|
||||
comb_boundary_here = &part.insets[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
comb_boundary_here = &part.print_outline;
|
||||
}
|
||||
PolygonUtils::moveInside(*comb_boundary_here, p, distance);
|
||||
}
|
||||
if (PolygonUtils::moveInside(comb_boundary_inside, p, distance, max_dist2) != NO_INDEX)
|
||||
{
|
||||
//Move inside again, so we move out of tight 90deg corners
|
||||
@@ -244,7 +259,7 @@ void GCodePlanner::moveInsideCombBoundary(int distance)
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::addTravel(Point p)
|
||||
GCodePath& GCodePlanner::addTravel(Point p)
|
||||
{
|
||||
GCodePath* path = nullptr;
|
||||
GCodePathConfig& travel_config = storage.travel_config_per_extruder[getExtruder()];
|
||||
@@ -322,7 +337,7 @@ void GCodePlanner::addTravel(Point p)
|
||||
if (was_inside) // when the previous location was from printing something which is considered inside (not support or prime tower etc)
|
||||
{ // then move inside the printed part, so that we don't ooze on the outer wall while retraction, but on the inside of the print.
|
||||
assert (extr != nullptr);
|
||||
moveInsideCombBoundary(extr->getSettingInMicrons((extr->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
|
||||
moveInsideCombBoundary(extr->getSettingInMicrons((extr->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1, *was_inside);
|
||||
}
|
||||
path = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
|
||||
path->retract = true;
|
||||
@@ -330,11 +345,12 @@ void GCodePlanner::addTravel(Point p)
|
||||
}
|
||||
}
|
||||
|
||||
addTravel_simple(p, path);
|
||||
GCodePath& ret = addTravel_simple(p, path);
|
||||
was_inside = is_inside;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
GCodePath& GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
{
|
||||
if (path == nullptr)
|
||||
{
|
||||
@@ -342,6 +358,7 @@ void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
}
|
||||
path->points.push_back(p);
|
||||
lastPosition = p;
|
||||
return *path;
|
||||
}
|
||||
|
||||
|
||||
@@ -351,22 +368,47 @@ void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillT
|
||||
lastPosition = p;
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, bool spiralize)
|
||||
void GCodePlanner::addPolygon(PolygonRef polygon, int start_idx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, coord_t wall_0_wipe_dist, bool spiralize)
|
||||
{
|
||||
Point p0 = polygon[startIdx];
|
||||
Point p0 = polygon[start_idx];
|
||||
addTravel(p0);
|
||||
for(unsigned int i=1; i<polygon.size(); i++)
|
||||
for (unsigned int point_idx = 1; point_idx < polygon.size(); point_idx++)
|
||||
{
|
||||
Point p1 = polygon[(startIdx + i) % polygon.size()];
|
||||
Point p1 = polygon[(start_idx + point_idx) % polygon.size()];
|
||||
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
|
||||
p0 = p1;
|
||||
}
|
||||
if (polygon.size() > 2)
|
||||
{
|
||||
Point& p1 = polygon[startIdx];
|
||||
Point& p1 = polygon[start_idx];
|
||||
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
|
||||
|
||||
if (wall_0_wipe_dist > 0)
|
||||
{ // apply outer wall wipe
|
||||
p0 = polygon[start_idx];
|
||||
int distance_traversed = 0;
|
||||
for (unsigned int point_idx = 1; ; point_idx++)
|
||||
{
|
||||
Point p1 = polygon[(start_idx + point_idx) % polygon.size()];
|
||||
int p0p1_dist = vSize(p1 - p0);
|
||||
if (distance_traversed + p0p1_dist >= wall_0_wipe_dist)
|
||||
{
|
||||
Point vector = p1 - p0;
|
||||
Point half_way = p0 + normal(vector, wall_0_wipe_dist - distance_traversed);
|
||||
addTravel_simple(half_way);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
addTravel_simple(p1);
|
||||
distance_traversed += p0p1_dist;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
forceNewPathStart();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -374,7 +416,7 @@ void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig*
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type, bool spiralize)
|
||||
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type, coord_t wall_0_wipe_dist, bool spiralize)
|
||||
{
|
||||
if (polygons.size() == 0)
|
||||
{
|
||||
@@ -388,7 +430,7 @@ void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* c
|
||||
orderOptimizer.optimize();
|
||||
for (unsigned int poly_idx : orderOptimizer.polyOrder)
|
||||
{
|
||||
addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config, wall_overlap_computation, spiralize);
|
||||
addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config, wall_overlap_computation, wall_0_wipe_dist, spiralize);
|
||||
}
|
||||
}
|
||||
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist)
|
||||
@@ -563,22 +605,23 @@ void ExtruderPlan::processFanSpeedAndMinimalLayerTime(bool force_minimal_layer_t
|
||||
}
|
||||
/*
|
||||
Supposing no influence of minimal layer time; i.e. layer time > min layer time fan speed min:
|
||||
|
||||
max.. fan 'full' on layer
|
||||
| :
|
||||
| :
|
||||
^ min..|..:________________
|
||||
fan | /
|
||||
speed | /
|
||||
zero..|/__________________
|
||||
layer nr >
|
||||
|
||||
|
||||
|
||||
max.. fan 'full' on layer
|
||||
| :
|
||||
| :
|
||||
^ min..|..:________________
|
||||
fan | /
|
||||
speed | /
|
||||
speed_0..|/
|
||||
|
|
||||
|__________________
|
||||
layer nr >
|
||||
|
||||
*/
|
||||
if (layer_nr < fsml.cool_fan_full_layer)
|
||||
{
|
||||
//Slow down the fan on the layers below the [cool_fan_full_layer], where layer 0 is speed 0.
|
||||
fan_speed = fan_speed * std::max(0, layer_nr) / fsml.cool_fan_full_layer;
|
||||
fan_speed = fsml.cool_fan_speed_0 + (fan_speed - fsml.cool_fan_speed_0) * std::max(0, layer_nr) / fsml.cool_fan_full_layer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,7 +656,13 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
gcode.setLayerNr(layer_nr);
|
||||
|
||||
gcode.writeLayerComment(layer_nr);
|
||||
|
||||
|
||||
if (layer_nr == 1 - Raft::getTotalExtraLayers(storage))
|
||||
{
|
||||
bool wait = false;
|
||||
gcode.writeBedTemperatureCommand(storage.getSettingInDegreeCelsius("material_bed_temperature"), wait);
|
||||
}
|
||||
|
||||
gcode.setZ(z);
|
||||
|
||||
|
||||
@@ -642,7 +691,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
|
||||
{ // require printing temperature to be met
|
||||
constexpr bool wait = true;
|
||||
gcode.writeTemperatureCommand(extruder, extruder_plan.required_temp, wait);
|
||||
gcode.writeTemperatureCommand(extruder, extruder_plan.initial_printing_temperature, wait);
|
||||
}
|
||||
|
||||
// prime extruder if it hadn't been used yet
|
||||
@@ -652,9 +701,19 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
if (extruder_plan.prev_extruder_standby_temp)
|
||||
{ // turn off previous extruder
|
||||
constexpr bool wait = false;
|
||||
gcode.writeTemperatureCommand(prev_extruder, *extruder_plan.prev_extruder_standby_temp, wait);
|
||||
double prev_extruder_temp = *extruder_plan.prev_extruder_standby_temp;
|
||||
int prev_layer_nr = (extruder_plan_idx == 0)? layer_nr - 1 : layer_nr;
|
||||
if (prev_layer_nr == storage.max_print_height_per_extruder[prev_extruder])
|
||||
{
|
||||
prev_extruder_temp = 0; // TODO ? should there be a setting for extruder_off_temperature ?
|
||||
}
|
||||
gcode.writeTemperatureCommand(prev_extruder, prev_extruder_temp, wait);
|
||||
}
|
||||
}
|
||||
else if (extruder_plan_idx == 0 && layer_nr != 0 && storage.meshgroup->getExtruderTrain(extruder)->getSettingBoolean("retract_at_layer_change"))
|
||||
{
|
||||
gcode.writeRetraction(&retraction_config);
|
||||
}
|
||||
gcode.writeFanCommand(extruder_plan.getFanSpeed());
|
||||
std::vector<GCodePath>& paths = extruder_plan.paths;
|
||||
|
||||
@@ -693,6 +752,10 @@ void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
{
|
||||
gcode.writeZhopStart(retraction_config.zHop);
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode.writeZhopEnd();
|
||||
}
|
||||
}
|
||||
if (!path.config->isTravelPath() && last_extrusion_config != path.config)
|
||||
{
|
||||
|
||||
+28
-14
@@ -20,6 +20,7 @@ namespace cura
|
||||
{
|
||||
|
||||
class SliceDataStorage;
|
||||
class SliceLayerPart;
|
||||
|
||||
/*!
|
||||
* A gcode command to insert before a specific path.
|
||||
@@ -39,7 +40,9 @@ struct NozzleTempInsert
|
||||
, extruder(extruder)
|
||||
, temperature(temperature)
|
||||
, wait(wait)
|
||||
{}
|
||||
{
|
||||
assert(temperature != 0 && temperature != -1 && "Temperature command must be set!");
|
||||
}
|
||||
|
||||
/*!
|
||||
* Write the temperature command at the current position in the gcode.
|
||||
@@ -281,7 +284,10 @@ protected:
|
||||
std::list<NozzleTempInsert> inserts; //!< The nozzle temperature command inserts, to be inserted in between paths
|
||||
|
||||
int extruder; //!< The extruder used for this paths in the current plan.
|
||||
double required_temp; //!< The required temperature at the start of this extruder plan.
|
||||
double heated_pre_travel_time; //!< The time at the start of this ExtruderPlan during which the head travels and has a temperature of initial_print_temperature
|
||||
double initial_printing_temperature; //!< The required temperature at the start of this extruder plan.
|
||||
double printing_temperature; //!< The normal temperature for printing this extruder plan. That start and end of this extruder plan may deviate because of the initial and final print temp
|
||||
std::optional<std::list<NozzleTempInsert>::iterator> printing_temperature_command; //!< The command to heat from the printing temperature of this extruder plan to the printing temperature of the next extruder plan (if it has the same extruder).
|
||||
std::optional<double> prev_extruder_standby_temp; //!< The temperature to which to set the previous extruder. Not used if the previous extruder plan was the same extruder.
|
||||
|
||||
TimeMaterialEstimates estimates; //!< Accumulated time and material estimates for all planned paths within this extruder plan.
|
||||
@@ -294,7 +300,7 @@ public:
|
||||
* \param extruder The extruder number for which this object is a plan.
|
||||
* \param start_position The position the head is when this extruder plan starts
|
||||
*/
|
||||
ExtruderPlan(int extruder, Point start_position, int layer_nr, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config);
|
||||
ExtruderPlan(int extruder, Point start_position, int layer_nr, bool is_initial_layer, int layer_thickness, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config);
|
||||
|
||||
/*!
|
||||
* Add a new Insert, constructed with the given arguments
|
||||
@@ -398,6 +404,8 @@ protected:
|
||||
Point start_position; //!< The position the print head was at at the start of this extruder plan
|
||||
|
||||
int layer_nr; //!< The layer number at which we are currently printing.
|
||||
bool is_initial_layer; //!< Whether this extruder plan is printed on the very first layer (which might be raft)
|
||||
|
||||
int layer_thickness; //!< The thickness of this layer in Z-direction
|
||||
|
||||
FanSpeedLayerTimeSettings& fan_speed_layer_time_settings; //!< The fan speed and layer time settings used to limit this extruder plan
|
||||
@@ -454,6 +462,7 @@ private:
|
||||
SliceDataStorage& storage; //!< The polygon data obtained from FffPolygonProcessor
|
||||
|
||||
int layer_nr; //!< The layer number of this layer plan
|
||||
int is_initial_layer; //!< Whether this is the first layer (which might be raft)
|
||||
|
||||
int z;
|
||||
|
||||
@@ -466,8 +475,8 @@ private:
|
||||
|
||||
int last_extruder_previous_layer; //!< The last id of the extruder with which was printed in the previous layer
|
||||
SettingsBaseVirtual* last_planned_extruder_setting_base; //!< The setting base of the last planned extruder.
|
||||
bool was_inside; //!< Whether the last planned (extrusion) move was inside a layer part
|
||||
bool is_inside; //!< Whether the destination of the next planned travel move is inside a layer part
|
||||
SliceLayerPart* was_inside; //!< The layer part the last planned (extrusion) move was inside (if any)
|
||||
SliceLayerPart* is_inside; //!< The layer part the destination of the next planned travel move is inside (if any)
|
||||
Polygons comb_boundary_inside; //!< The boundary within which to comb, or to move into when performing a retraction.
|
||||
Comb* comb;
|
||||
|
||||
@@ -486,7 +495,8 @@ private:
|
||||
* \return A path with the given config which is now the last path in GCodePlanner::paths
|
||||
*/
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0, bool spiralize = false);
|
||||
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Force GCodePlanner::getLatestPathWithConfig to return a new path.
|
||||
*
|
||||
@@ -498,7 +508,7 @@ private:
|
||||
* - when changing extruder, the same travel config is used, but its extruder field is changed.
|
||||
*/
|
||||
void forceNewPathStart();
|
||||
public:
|
||||
|
||||
/*!
|
||||
*
|
||||
* \param fan_speed_layer_time_settings_per_extruder The fan speed and layer time settings for each extruder.
|
||||
@@ -507,7 +517,7 @@ public:
|
||||
* \param last_position The position of the head at the start of this gcode layer
|
||||
* \param combing_mode Whether combing is enabled and full or within infill only.
|
||||
*/
|
||||
GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_height, Point last_position, int current_extruder, bool is_inside_mesh, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
GCodePlanner(SliceDataStorage& storage, int layer_nr, int z, int layer_height, Point last_position, int current_extruder, std::vector<FanSpeedLayerTimeSettings>& fan_speed_layer_time_settings_per_extruder, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
~GCodePlanner();
|
||||
|
||||
void overrideFanSpeeds(double speed);
|
||||
@@ -555,8 +565,9 @@ public:
|
||||
*
|
||||
* Features like infill, walls, skin etc. are considered inside.
|
||||
* Features like prime tower and support are considered outside.
|
||||
* \param inside_part The part in which the newly planned position is inside, or nullptr if not inside anything
|
||||
*/
|
||||
void setIsInside(bool going_to_comb);
|
||||
void setIsInside(SliceLayerPart* inside_part);
|
||||
|
||||
bool setExtruder(int extruder);
|
||||
|
||||
@@ -576,7 +587,7 @@ public:
|
||||
*
|
||||
* \param p The point to travel to
|
||||
*/
|
||||
void addTravel(Point p);
|
||||
GCodePath& addTravel(Point p);
|
||||
|
||||
/*!
|
||||
* Add a travel path to a certain point and retract if needed.
|
||||
@@ -586,7 +597,7 @@ public:
|
||||
* \param p The point to travel to
|
||||
* \param path (optional) The travel path to which to add the point \p p
|
||||
*/
|
||||
void addTravel_simple(Point p, GCodePath* path = nullptr);
|
||||
GCodePath& addTravel_simple(Point p, GCodePath* path = nullptr);
|
||||
|
||||
/*!
|
||||
* Add an extrusion move to a certain point, optionally with a different flow than the one in the \p config.
|
||||
@@ -605,9 +616,10 @@ public:
|
||||
* \param startIdx The index of the starting vertex of the \p polygon
|
||||
* \param config The config with which to print the polygon lines
|
||||
* \param wall_overlap_computation The wall overlap compensation calculator for each given segment (optionally nullptr)
|
||||
* \param wall_0_wipe_dist The distance to travel along the polygon after it has been laid down, in order to wipe the start and end of the wall together
|
||||
* \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over this polygon
|
||||
*/
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, bool spiralize = false);
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, coord_t wall_0_wipe_dist = 0, bool spiralize = false);
|
||||
|
||||
/*!
|
||||
* Add polygons to the gcode with optimized order.
|
||||
@@ -622,9 +634,10 @@ public:
|
||||
* \param config The config with which to print the polygon lines
|
||||
* \param wall_overlap_computation The wall overlap compensation calculator for each given segment (optionally nullptr)
|
||||
* \param z_seam_type The seam type / poly start optimizer
|
||||
* \param wall_0_wipe_dist The distance to travel along each polygon after it has been laid down, in order to wipe the start and end of the wall together
|
||||
* \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over each polygon printed
|
||||
*/
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST, bool spiralize = false);
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST, coord_t wall_0_wipe_dist = 0, bool spiralize = false);
|
||||
|
||||
/*!
|
||||
* Add lines to the gcode with optimized order.
|
||||
@@ -702,8 +715,9 @@ public:
|
||||
* This is supposed to be called when the nozzle is around the boundary of a layer part, not when the nozzle is in the middle of support, or in the middle of the air.
|
||||
*
|
||||
* \param distance The distance to the comb boundary after we moved inside it.
|
||||
* \param part_outline The part in which we last resided
|
||||
*/
|
||||
void moveInsideCombBoundary(int distance);
|
||||
void moveInsideCombBoundary(int distance, const SliceLayerPart& part);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+62
-19
@@ -21,7 +21,6 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
{
|
||||
if (in_outline.size() == 0) return;
|
||||
if (line_distance == 0) return;
|
||||
const Polygons* outline = &in_outline;
|
||||
Polygons outline_offsetted;
|
||||
switch(pattern)
|
||||
{
|
||||
@@ -41,9 +40,10 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
generateTriangleInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC:
|
||||
outline_offsetted = in_outline.offset(outline_offset - infill_line_width / 2); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
outline = &outline_offsetted;
|
||||
generateConcentricInfill(*outline, result_polygons, line_distance);
|
||||
generateConcentricInfill(result_polygons, line_distance);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC_3D:
|
||||
generateConcentric3DInfill(result_polygons);
|
||||
break;
|
||||
case EFillMethod::ZIG_ZAG:
|
||||
generateZigZagInfill(result_lines, line_distance, fill_angle, connected_zigzags, use_endpieces);
|
||||
@@ -54,15 +54,53 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
}
|
||||
}
|
||||
|
||||
void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int inset_value)
|
||||
void Infill::generateConcentricInfill(Polygons& result, int inset_value)
|
||||
{
|
||||
while(outline.size() > 0)
|
||||
Polygons first_concentric_wall = in_outline.offset(outline_offset - line_distance + infill_line_width / 2); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
|
||||
result.add(first_concentric_wall);
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
result.add(outline);
|
||||
outline = outline.offset(-inset_value);
|
||||
}
|
||||
const Polygons inner = first_concentric_wall.offset(infill_line_width / 2 + perimeter_gaps_extra_offset);
|
||||
const Polygons gaps_here = in_outline.difference(inner);
|
||||
perimeter_gaps->add(gaps_here);
|
||||
}
|
||||
generateConcentricInfill(first_concentric_wall, result, inset_value);
|
||||
}
|
||||
|
||||
void Infill::generateConcentricInfill(Polygons& first_concentric_wall, Polygons& result, int inset_value)
|
||||
{
|
||||
Polygons* prev_inset = &first_concentric_wall;
|
||||
Polygons next_inset;
|
||||
while (prev_inset->size() > 0)
|
||||
{
|
||||
next_inset = prev_inset->offset(-inset_value);
|
||||
result.add(next_inset);
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
const Polygons outer = prev_inset->offset(-infill_line_width / 2 - perimeter_gaps_extra_offset);
|
||||
const Polygons inner = next_inset.offset(infill_line_width / 2 + perimeter_gaps_extra_offset);
|
||||
const Polygons gaps_here = outer.difference(inner);
|
||||
perimeter_gaps->add(gaps_here);
|
||||
}
|
||||
prev_inset = &next_inset;
|
||||
}
|
||||
}
|
||||
|
||||
void Infill::generateConcentric3DInfill(Polygons& result)
|
||||
{
|
||||
int period = line_distance * 2;
|
||||
int shift = int64_t(one_over_sqrt_2 * z) % period;
|
||||
shift = std::min(shift, period - shift); // symmetry due to the fact that we are applying the shift in both directions
|
||||
shift = std::min(shift, period / 2 - infill_line_width / 2); // don't put lines too close to each other
|
||||
shift = std::max(shift, infill_line_width / 2); // don't put lines too close to each other
|
||||
Polygons first_wall;
|
||||
// in contrast to concentric infill we dont do "- infill_line_width / 2" cause this is already handled by the max two lines above
|
||||
first_wall = in_outline.offset(outline_offset - shift);
|
||||
generateConcentricInfill(first_wall, result, period);
|
||||
first_wall = in_outline.offset(outline_offset - period + shift);
|
||||
generateConcentricInfill(first_wall, result, period);
|
||||
}
|
||||
|
||||
void Infill::generateGridInfill(Polygons& result)
|
||||
{
|
||||
@@ -80,14 +118,15 @@ void Infill::generateCubicInfill(Polygons& result)
|
||||
|
||||
void Infill::generateTetrahedralInfill(Polygons& result)
|
||||
{
|
||||
int shift = int64_t(one_over_sqrt_2 * z) % line_distance;
|
||||
shift = std::min(shift, line_distance - shift); // symmetry due to the fact that we are applying the shift in both directions
|
||||
shift = std::min(shift, line_distance / 2 - infill_line_width / 2); // don't put lines too close to each other
|
||||
int period = line_distance * 2;
|
||||
int shift = int64_t(one_over_sqrt_2 * z) % period;
|
||||
shift = std::min(shift, period - shift); // symmetry due to the fact that we are applying the shift in both directions
|
||||
shift = std::min(shift, period / 2 - infill_line_width / 2); // don't put lines too close to each other
|
||||
shift = std::max(shift, infill_line_width / 2); // don't put lines too close to each other
|
||||
generateLineInfill(result, line_distance, fill_angle, shift);
|
||||
generateLineInfill(result, line_distance, fill_angle, -shift);
|
||||
generateLineInfill(result, line_distance, fill_angle + 90, shift);
|
||||
generateLineInfill(result, line_distance, fill_angle + 90, -shift);
|
||||
generateLineInfill(result, period, fill_angle, shift);
|
||||
generateLineInfill(result, period, fill_angle, -shift);
|
||||
generateLineInfill(result, period, fill_angle + 90, shift);
|
||||
generateLineInfill(result, period, fill_angle + 90, -shift);
|
||||
}
|
||||
|
||||
void Infill::generateTriangleInfill(Polygons& result)
|
||||
@@ -210,14 +249,18 @@ void Infill::generateLinearBasedInfill(const int outline_offset, Polygons& resul
|
||||
if (outline_offset != 0)
|
||||
{
|
||||
outline = in_outline.offset(outline_offset);
|
||||
if (perimeter_gaps)
|
||||
{
|
||||
perimeter_gaps->add(in_outline.difference(outline.offset(infill_line_width / 2 + perimeter_gaps_extra_offset)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outline = in_outline;
|
||||
}
|
||||
|
||||
outline = outline.offset(infill_overlap);
|
||||
|
||||
|
||||
outline = outline.offset(infill_overlap);
|
||||
|
||||
if (outline.size() == 0)
|
||||
{
|
||||
return;
|
||||
|
||||
+45
-4
@@ -20,6 +20,8 @@ namespace cura
|
||||
|
||||
class Infill
|
||||
{
|
||||
static constexpr int perimeter_gaps_extra_offset = 15; // extra offset so that the perimeter gaps aren't created everywhere due to rounding errors
|
||||
|
||||
EFillMethod pattern; //!< the space filling pattern of the infill to generate
|
||||
const Polygons& in_outline; //!< a reference polygon for getting the actual area within which to generate infill (see outline_offset)
|
||||
int outline_offset; //!< Offset from Infill::in_outline to get the actual area within which to generate infill
|
||||
@@ -29,12 +31,33 @@ class Infill
|
||||
double fill_angle; //!< for linear infill types: the angle of the infill lines (or the angle of the grid)
|
||||
int64_t z; //!< height of the layer for which we generate infill
|
||||
int64_t shift; //!< shift of the scanlines in the direction perpendicular to the fill_angle
|
||||
Polygons* perimeter_gaps; //!< (optional output) The areas in between consecutive insets when Concentric infill is used.
|
||||
bool connected_zigzags; //!< (ZigZag) Whether endpieces of zigzag infill should be connected to the nearest infill line on both sides of the zigzag connector
|
||||
bool use_endpieces; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself
|
||||
|
||||
static constexpr double one_over_sqrt_2 = 0.7071067811865475244008443621048490392848359376884740; //!< 1.0 / sqrt(2.0)
|
||||
public:
|
||||
Infill(EFillMethod pattern, const Polygons& in_outline, int outline_offset, int infill_line_width, int line_distance, int infill_overlap, double fill_angle, int64_t z, int64_t shift, bool connected_zigzags = false, bool use_endpieces = false)
|
||||
/*!
|
||||
* \warning If \p perimeter_gaps is given, then the difference between the \p in_outline
|
||||
* and the polygons which result from offsetting it by the \p outline_offset
|
||||
* and then expanding it again by half the \p infill_line_width
|
||||
* is added to the \p perimeter_gaps
|
||||
*
|
||||
* \param[out] perimeter_gaps (optional output) The areas in between consecutive insets when Concentric infill is used.
|
||||
*/
|
||||
Infill(EFillMethod pattern
|
||||
, const Polygons& in_outline
|
||||
, int outline_offset
|
||||
, int infill_line_width
|
||||
, int line_distance
|
||||
, int infill_overlap
|
||||
, double fill_angle
|
||||
, int64_t z
|
||||
, int64_t shift
|
||||
, Polygons* perimeter_gaps = nullptr
|
||||
, bool connected_zigzags = false
|
||||
, bool use_endpieces = false
|
||||
)
|
||||
: pattern(pattern)
|
||||
, in_outline(in_outline)
|
||||
, outline_offset(outline_offset)
|
||||
@@ -44,6 +67,7 @@ public:
|
||||
, fill_angle(fill_angle)
|
||||
, z(z)
|
||||
, shift(shift)
|
||||
, perimeter_gaps(perimeter_gaps)
|
||||
, connected_zigzags(connected_zigzags)
|
||||
, use_endpieces(use_endpieces)
|
||||
{
|
||||
@@ -68,13 +92,30 @@ private:
|
||||
* \param line_distance the width of the scan segments
|
||||
*/
|
||||
static inline int computeScanSegmentIdx(int x, int line_distance);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill
|
||||
* \param outline The actual outline of the area within which to generate infill
|
||||
* Generate sparse concentric infill
|
||||
*
|
||||
* Also adds \ref Inifll::perimeter_gaps between \ref Infill::in_outline and the first wall
|
||||
*
|
||||
* \param result (output) The resulting polygons
|
||||
* \param inset_value The offset between each consecutive two polygons
|
||||
*/
|
||||
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value);
|
||||
void generateConcentricInfill(Polygons& result, int inset_value);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill starting from a specific outer wall
|
||||
* \param first_wall The outer wall from which to start
|
||||
* \param result (output) The resulting polygons
|
||||
* \param inset_value The offset between each consecutive two polygons
|
||||
*/
|
||||
void generateConcentricInfill(Polygons& first_wall, Polygons& result, int inset_value);
|
||||
|
||||
/*!
|
||||
* Generate sparse concentric infill
|
||||
* \param result (output) The resulting polygons
|
||||
*/
|
||||
void generateConcentric3DInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Generate a rectangular grid of infill lines
|
||||
|
||||
@@ -46,7 +46,6 @@ void createLayerParts(SliceMeshStorage& mesh, Slicer* slicer, bool union_layers,
|
||||
{
|
||||
for(unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
mesh.layers.push_back(SliceLayer());
|
||||
mesh.layers[layer_nr].sliceZ = slicer->layers[layer_nr].z;
|
||||
mesh.layers[layer_nr].printZ = slicer->layers[layer_nr].z;
|
||||
createLayerWithParts(mesh.layers[layer_nr], &slicer->layers[layer_nr], union_layers, union_all_remove_holes);
|
||||
|
||||
+4
-1
@@ -74,7 +74,10 @@ AABB3D Mesh::getAABB() const
|
||||
}
|
||||
void Mesh::expandXY(int64_t offset)
|
||||
{
|
||||
aabb.expandXY(offset);
|
||||
if (offset)
|
||||
{
|
||||
aabb.expandXY(offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+10
-3
@@ -3,10 +3,10 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes, bool alternate_carve_order)
|
||||
{
|
||||
//Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas.
|
||||
for (unsigned int volume_1_idx = 0; volume_1_idx < volumes.size(); volume_1_idx++)
|
||||
for (unsigned int volume_1_idx = 1; volume_1_idx < volumes.size(); volume_1_idx++)
|
||||
{
|
||||
Slicer& volume_1 = *volumes[volume_1_idx];
|
||||
if (volume_1.mesh->getSettingBoolean("infill_mesh")
|
||||
@@ -34,7 +34,14 @@ void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
SlicerLayer& layer1 = volume_1.layers[layerNr];
|
||||
SlicerLayer& layer2 = volume_2.layers[layerNr];
|
||||
layer1.polygons = layer1.polygons.difference(layer2.polygons);
|
||||
if (alternate_carve_order && layerNr % 2 == 0)
|
||||
{
|
||||
layer2.polygons = layer2.polygons.difference(layer1.polygons);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer1.polygons = layer1.polygons.difference(layer2.polygons);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
/* This file contains code to help fixing up and changing layers that are build from multiple volumes. */
|
||||
namespace cura {
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &meshes);
|
||||
/*!
|
||||
*
|
||||
* \param alternate_carve_order Whether to switch which model carves out of which with every layer
|
||||
*/
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &meshes, bool alternate_carve_order);
|
||||
|
||||
/*!
|
||||
* Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
|
||||
@@ -18,7 +18,7 @@ class PathOrderOptimizer
|
||||
{
|
||||
public:
|
||||
EZSeamType type;
|
||||
Point startPoint; //!< The location of the nozzle before starting to print the current layer
|
||||
Point startPoint; //!< A location near the prefered start location
|
||||
std::vector<PolygonRef> polygons; //!< the parts of the layer (in arbitrary order)
|
||||
std::vector<int> polyStart; //!< polygons[i][polyStart[i]] = point of polygon i which is to be the starting point in printing the polygon
|
||||
std::vector<int> polyOrder; //!< the optimized order as indices in #polygons
|
||||
|
||||
+133
-50
@@ -2,62 +2,74 @@
|
||||
#include "Comb.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional> // function
|
||||
#include <unordered_set>
|
||||
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/PolygonsPointIndex.h"
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "../utils/SVG.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
// boundary_outside is only computed when it's needed!
|
||||
Polygons& Comb::getBoundaryOutside()
|
||||
Polygons Comb::getCombOutlines()
|
||||
{
|
||||
if (!boundary_outside)
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
boundary_outside = new Polygons();
|
||||
*boundary_outside = storage.getLayerOutlines(layer_nr, false).offset(offset_from_outlines_outside);
|
||||
bool include_helper_parts = false;
|
||||
return storage.getLayerOutlines(layer_nr, include_helper_parts);
|
||||
}
|
||||
else
|
||||
{
|
||||
return storage.raftOutline;
|
||||
}
|
||||
return *boundary_outside;
|
||||
}
|
||||
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& Comb::getOutsideLocToLine()
|
||||
LocToLineGrid& Comb::getOutsideLocToLine()
|
||||
{
|
||||
if (!outside_loc_to_line)
|
||||
{
|
||||
Polygons& outside = getBoundaryOutside();
|
||||
outside_loc_to_line = PolygonUtils::createLocToLineGrid(outside, offset_from_inside_to_outside * 3 / 2);
|
||||
}
|
||||
return *outside_loc_to_line;
|
||||
}
|
||||
|
||||
Polygons& Comb::getBoundaryOutside()
|
||||
{
|
||||
return *boundary_outside;
|
||||
}
|
||||
|
||||
Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
, 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)
|
||||
, max_move_inside_distance2(offset_from_outlines * 2 * offset_from_outlines * 2)
|
||||
, offset_from_outlines_outside(travel_avoid_distance)
|
||||
, offset_from_inside_to_outside(offset_from_outlines + offset_from_outlines_outside)
|
||||
, max_crossing_dist2(offset_from_inside_to_outside * offset_from_inside_to_outside * 2) // so max_crossing_dist = offset_from_inside_to_outside * sqrt(2) =approx 1.5 to allow for slightly diagonal crossings and slightly inaccurate crossing computation
|
||||
, avoid_other_parts(travel_avoid_other_parts)
|
||||
// , boundary_inside( boundary.offset(-offset_from_outlines) ) // TODO: make inside boundary configurable?
|
||||
, boundary_inside( comb_boundary_inside )
|
||||
, boundary_outside(nullptr)
|
||||
, outside_loc_to_line(nullptr)
|
||||
, partsView_inside( boundary_inside.splitIntoPartsView() ) // !! changes the order of boundary_inside !!
|
||||
, partsView_inside( boundary_inside.splitIntoPartsView() ) // WARNING !! changes the order of boundary_inside !!
|
||||
, outlines(getCombOutlines())
|
||||
, inside_loc_to_line(PolygonUtils::createLocToLineGrid(boundary_inside, comb_boundary_offset))
|
||||
, boundary_outside(
|
||||
[&storage, layer_nr, travel_avoid_distance]()
|
||||
{
|
||||
return storage.getLayerOutlines(layer_nr, false).offset(travel_avoid_distance);
|
||||
}
|
||||
)
|
||||
, outside_loc_to_line(
|
||||
[](Comb* comber, const int64_t offset_from_inside_to_outside)
|
||||
{
|
||||
return PolygonUtils::createLocToLineGrid(comber->getBoundaryOutside(), offset_from_inside_to_outside * 3 / 2);
|
||||
}
|
||||
, this
|
||||
, offset_from_inside_to_outside
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
Comb::~Comb()
|
||||
{
|
||||
if (boundary_outside)
|
||||
if (inside_loc_to_line)
|
||||
{
|
||||
delete boundary_outside;
|
||||
}
|
||||
if (outside_loc_to_line)
|
||||
{
|
||||
delete outside_loc_to_line;
|
||||
delete inside_loc_to_line;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +96,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
{ // normal combing within part
|
||||
PolygonsPart part = partsView_inside.assemblePart(start_part_idx);
|
||||
combPaths.emplace_back();
|
||||
return LinePolygonsCrossings::comb(part, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
return LinePolygonsCrossings::comb(part, *inside_loc_to_line, startPoint, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
}
|
||||
else
|
||||
{ // comb inside part to edge (if needed) >> move through air avoiding other parts >> comb inside end part upto the endpoint (if needed)
|
||||
@@ -99,31 +111,31 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
return false;
|
||||
}
|
||||
|
||||
Crossing start_crossing(startPoint, startInside, start_part_idx, start_part_boundary_poly_idx, boundary_inside);
|
||||
Crossing end_crossing(endPoint, endInside, end_part_idx, end_part_boundary_poly_idx, boundary_inside);
|
||||
Crossing start_crossing(startPoint, startInside, start_part_idx, start_part_boundary_poly_idx, boundary_inside, inside_loc_to_line);
|
||||
Crossing end_crossing(endPoint, endInside, end_part_idx, end_part_boundary_poly_idx, boundary_inside, inside_loc_to_line);
|
||||
|
||||
{ // find crossing over the in-between area between inside and outside
|
||||
start_crossing.findCrossingInOrMid(partsView_inside, endPoint);
|
||||
end_crossing.findCrossingInOrMid(partsView_inside, start_crossing.in_or_mid);
|
||||
}
|
||||
|
||||
bool avoid_other_parts_now = avoid_other_parts;
|
||||
if (avoid_other_parts_now && vSize2(start_crossing.in_or_mid - end_crossing.in_or_mid) < offset_from_inside_to_outside * offset_from_inside_to_outside * 4)
|
||||
bool skip_avoid_other_parts_path = false;
|
||||
if (skip_avoid_other_parts_path && vSize2(start_crossing.in_or_mid - end_crossing.in_or_mid) < offset_from_inside_to_outside * offset_from_inside_to_outside * 4)
|
||||
{ // parts are next to eachother, i.e. the direct crossing will always be smaller than two crossings via outside
|
||||
avoid_other_parts_now = false;
|
||||
skip_avoid_other_parts_path = true;
|
||||
}
|
||||
|
||||
if (avoid_other_parts_now)
|
||||
if (avoid_other_parts && !skip_avoid_other_parts_path)
|
||||
{ // compute the crossing points when moving through air
|
||||
Polygons& outside = getBoundaryOutside(); // comb through all air, since generally the outside consists of a single part
|
||||
// comb through all air, since generally the outside consists of a single part
|
||||
|
||||
bool success = start_crossing.findOutside(outside, end_crossing.in_or_mid, fail_on_unavoidable_obstacles, *this);
|
||||
bool success = start_crossing.findOutside(*boundary_outside, end_crossing.in_or_mid, fail_on_unavoidable_obstacles, *this);
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
success = end_crossing.findOutside(outside, start_crossing.out, fail_on_unavoidable_obstacles, *this);
|
||||
success = end_crossing.findOutside(*boundary_outside, start_crossing.out, fail_on_unavoidable_obstacles, *this);
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
@@ -136,7 +148,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
// start to boundary
|
||||
assert(start_crossing.dest_part.size() > 0 && "The part we start inside when combing should have been computed already!");
|
||||
combPaths.emplace_back();
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(start_crossing.dest_part, startPoint, start_crossing.in_or_mid, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(start_crossing.dest_part, *inside_loc_to_line, startPoint, start_crossing.in_or_mid, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{ // Couldn't comb between start point and computed crossing from the start part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon
|
||||
return false;
|
||||
@@ -144,7 +156,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
}
|
||||
|
||||
// throught air from boundary to boundary
|
||||
if (avoid_other_parts_now)
|
||||
if (avoid_other_parts && !skip_avoid_other_parts_path)
|
||||
{
|
||||
combPaths.emplace_back();
|
||||
combPaths.throughAir = true;
|
||||
@@ -155,7 +167,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
}
|
||||
else
|
||||
{
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(getBoundaryOutside(), start_crossing.out, end_crossing.out, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(*boundary_outside, *outside_loc_to_line, start_crossing.out, end_crossing.out, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{
|
||||
return false;
|
||||
@@ -166,10 +178,28 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
{ // directly through air (not avoiding other parts)
|
||||
combPaths.emplace_back();
|
||||
combPaths.throughAir = true;
|
||||
combPaths.back().cross_boundary = true; // TODO: calculate whether we cross a boundary!
|
||||
combPaths.back().cross_boundary = true; // note: we don't actually know whether this is cross boundary, but it might very well be
|
||||
combPaths.back().push_back(start_crossing.in_or_mid);
|
||||
combPaths.back().push_back(end_crossing.in_or_mid);
|
||||
}
|
||||
if (skip_avoid_other_parts_path)
|
||||
{
|
||||
if (startInside == endInside && start_part_idx == end_part_idx)
|
||||
{
|
||||
if (startInside)
|
||||
{ // both start and end are inside
|
||||
combPaths.back().cross_boundary = PolygonUtils::polygonCollidesWithLineSegment(startPoint, endPoint, *inside_loc_to_line);
|
||||
}
|
||||
else
|
||||
{ // both start and end are outside
|
||||
combPaths.back().cross_boundary = PolygonUtils::polygonCollidesWithLineSegment(startPoint, endPoint, *outside_loc_to_line);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
combPaths.back().cross_boundary = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (endInside)
|
||||
{
|
||||
@@ -177,7 +207,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
assert(end_crossing.dest_part.size() > 0 && "The part we end up inside when combing should have been computed already!");
|
||||
combPaths.emplace_back();
|
||||
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(end_crossing.dest_part, end_crossing.in_or_mid, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
bool combing_succeeded = LinePolygonsCrossings::comb(end_crossing.dest_part, *inside_loc_to_line, end_crossing.in_or_mid, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
if (!combing_succeeded)
|
||||
{ // Couldn't comb between end point and computed crossing to the end part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon
|
||||
return false;
|
||||
@@ -188,28 +218,47 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool _st
|
||||
}
|
||||
}
|
||||
|
||||
Comb::Crossing::Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside)
|
||||
Comb::Crossing::Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside, const LocToLineGrid* inside_loc_to_line)
|
||||
: dest_is_inside(dest_is_inside)
|
||||
, dest_crossing_poly(boundary_inside[dest_part_boundary_crossing_poly_idx]) // initialize with most obvious poly, cause mostly a combing move will move outside the part, rather than inside a hole in the part
|
||||
, boundary_inside(boundary_inside)
|
||||
, inside_loc_to_line(inside_loc_to_line)
|
||||
, dest_point(dest_point)
|
||||
, dest_part_idx(dest_part_idx)
|
||||
{
|
||||
|
||||
if (dest_is_inside)
|
||||
{
|
||||
dest_crossing_poly = boundary_inside[dest_part_boundary_crossing_poly_idx]; // initialize with most obvious poly, cause mostly a combing move will move outside the part, rather than inside a hole in the part
|
||||
}
|
||||
}
|
||||
|
||||
bool Comb::moveInside(bool is_inside, Point& dest_point, unsigned int& inside_poly)
|
||||
{
|
||||
if (is_inside)
|
||||
{
|
||||
ClosestPolygonPoint cpp = PolygonUtils::ensureInsideOrOutside(boundary_inside, dest_point, offset_extra_start_end, max_moveInside_distance2);
|
||||
if (cpp.point_idx == NO_INDEX)
|
||||
coord_t max_move_inside_distance2_here = std::numeric_limits<coord_t>::max(); // the distance which would make the moveInside fail
|
||||
if (storage.getSettingAsCombingMode("retraction_combing") == cura::CombingMode::NO_SKIN)
|
||||
{ // if we perform no_skin combing, then a far move inside is likely a consequence of there meing skin in between the destination point and the inside comb boundary
|
||||
// if we perform normal combing, then a far move inside is likely to be a consequence of sharp pointy segments in the layer part
|
||||
max_move_inside_distance2_here = max_move_inside_distance2;
|
||||
}
|
||||
Point original_dest_point = dest_point;
|
||||
ClosestPolygonPoint cpp = PolygonUtils::ensureInsideOrOutside(boundary_inside, dest_point, offset_extra_start_end, max_move_inside_distance2_here, &boundary_inside, inside_loc_to_line);
|
||||
if (!cpp.isValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
inside_poly = cpp.poly_idx;
|
||||
return true;
|
||||
if (vSize2(dest_point - original_dest_point) > max_move_inside_distance2 // only check for collision with outlines for long moves
|
||||
&& PolygonUtils::polygonCollidesWithLineSegment(outlines, dest_point, original_dest_point))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
inside_poly = cpp.poly_idx;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -223,10 +272,43 @@ void Comb::Crossing::findCrossingInOrMid(const PartsView& partsView_inside, cons
|
||||
Point _dest_point(dest_point); // copy to local variable for lambda capture
|
||||
std::function<int(Point)> close_towards_start_penalty_function([_dest_point](Point candidate){ return vSize2((candidate - _dest_point) / 10); });
|
||||
dest_part = partsView_inside.assemblePart(dest_part_idx);
|
||||
Point result(close_to);
|
||||
|
||||
ClosestPolygonPoint boundary_crossing_point;
|
||||
{ // set [result] to a point on the destination part closest to close_to (but also a bit close to fest_point)
|
||||
std::unordered_set<unsigned int> dest_part_poly_indices;
|
||||
for (unsigned int poly_idx : partsView_inside[dest_part_idx])
|
||||
{
|
||||
dest_part_poly_indices.emplace(poly_idx);
|
||||
}
|
||||
coord_t dist2_score = std::numeric_limits<coord_t>::max();
|
||||
std::function<bool (const PolygonsPointIndex&)> line_processor
|
||||
= [close_to, _dest_point, &boundary_crossing_point, &dist2_score, &dest_part_poly_indices](const PolygonsPointIndex& boundary_segment)
|
||||
{
|
||||
if (dest_part_poly_indices.find(boundary_segment.poly_idx) == dest_part_poly_indices.end())
|
||||
{ // we're not looking at a polygon from the dest_part
|
||||
return true; // a.k.a. continue;
|
||||
}
|
||||
Point closest_here = LinearAlg2D::getClosestOnLineSegment(close_to, boundary_segment.p(), boundary_segment.next().p());
|
||||
coord_t dist2_score_here = vSize2(close_to - closest_here) + vSize2(_dest_point - closest_here) / 10;
|
||||
if (dist2_score_here < dist2_score)
|
||||
{
|
||||
dist2_score = dist2_score_here;
|
||||
boundary_crossing_point = ClosestPolygonPoint(closest_here, boundary_segment.point_idx, boundary_segment.getPolygon(), boundary_segment.poly_idx);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
inside_loc_to_line->processLine(std::make_pair(dest_point, close_to), line_processor);
|
||||
}
|
||||
|
||||
Point result(boundary_crossing_point.p()); // the inside point of the crossing
|
||||
if (!boundary_crossing_point.isValid())
|
||||
{ // no point has been found in the sparse grid
|
||||
result = dest_point;
|
||||
}
|
||||
|
||||
int64_t max_dist2 = std::numeric_limits<int64_t>::max();
|
||||
ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::ensureInsideOrOutside(dest_part, result, offset_dist_to_get_from_on_the_polygon_to_outside, max_dist2, close_towards_start_penalty_function);
|
||||
if (crossing_1_in_cp.point_idx != NO_INDEX)
|
||||
ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::ensureInsideOrOutside(dest_part, result, boundary_crossing_point, offset_dist_to_get_from_on_the_polygon_to_outside, max_dist2, &boundary_inside, inside_loc_to_line, close_towards_start_penalty_function);
|
||||
if (crossing_1_in_cp.isValid())
|
||||
{
|
||||
dest_crossing_poly = crossing_1_in_cp.poly;
|
||||
in_or_mid = result;
|
||||
@@ -263,7 +345,8 @@ bool Comb::Crossing::findOutside(const Polygons& outside, const Point close_to,
|
||||
if (dest_is_inside && in_out_dist2_1 > comber.max_crossing_dist2) // moveInside moved too far
|
||||
{ // if move is too far over in_between
|
||||
// find crossing closer by
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(outside, dest_crossing_poly, dest_point, close_to, comber);
|
||||
assert(dest_crossing_poly && "destination crossing poly should have been instantiated!");
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(outside, *dest_crossing_poly, dest_point, close_to, comber);
|
||||
if (best)
|
||||
{
|
||||
in_or_mid = PolygonUtils::moveInside(best->first, comber.offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
|
||||
+29
-13
@@ -4,9 +4,11 @@
|
||||
|
||||
#include <memory> // shared_ptr
|
||||
|
||||
#include "../utils/optional.h"
|
||||
#include "../utils/polygon.h"
|
||||
#include "../utils/SparsePointGridInclusive.h"
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/LazyInitialization.h"
|
||||
|
||||
#include "LinePolygonsCrossings.h"
|
||||
#include "CombPath.h"
|
||||
@@ -30,7 +32,7 @@ class SliceDataStorage;
|
||||
* As an optimization, the combing paths inside are calculated on specifically those PolygonsParts within which to comb, while the coundary_outside isn't split into outside parts,
|
||||
* because generally there is only one outside part; encapsulated holes occur less often.
|
||||
*/
|
||||
class Comb
|
||||
class Comb
|
||||
{
|
||||
friend class LinePolygonsCrossings;
|
||||
private:
|
||||
@@ -46,7 +48,9 @@ private:
|
||||
Point in_or_mid; //!< The point on the inside boundary, or in between the inside and outside boundary if the start/end point isn't inside the inside boudary
|
||||
Point out; //!< The point on the outside boundary
|
||||
PolygonsPart dest_part; //!< The assembled inside-boundary PolygonsPart in which the dest_point lies. (will only be initialized when Crossing::dest_is_inside holds)
|
||||
PolygonRef dest_crossing_poly; //!< The polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon)
|
||||
std::optional<PolygonRef> dest_crossing_poly; //!< The polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon)
|
||||
const Polygons& boundary_inside; //!< The inside boundary as in \ref Comb::boundary_inside
|
||||
const LocToLineGrid* inside_loc_to_line; //!< The loc to line grid \ref Comb::inside_loc_to_line
|
||||
|
||||
/*!
|
||||
* Simple constructor
|
||||
@@ -57,7 +61,7 @@ private:
|
||||
* \param dest_part_boundary_crossing_poly_idx The index in \p boundary_inside of the polygon of the part in which dest_point lies, which will be crossed (often will be the outside polygon).
|
||||
* \param boundary_inside The boundary within which to comb.
|
||||
*/
|
||||
Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside);
|
||||
Crossing(const Point& dest_point, const bool dest_is_inside, const unsigned int dest_part_idx, const unsigned int dest_part_boundary_crossing_poly_idx, const Polygons& boundary_inside, const LocToLineGrid* inside_loc_to_line);
|
||||
|
||||
/*!
|
||||
* Find the not-outside location (Combing::in_or_mid) of the crossing between to the outside boundary
|
||||
@@ -101,7 +105,7 @@ private:
|
||||
const int layer_nr; //!< The layer number for the layer for which to compute the outside boundary, when needed.
|
||||
|
||||
const int64_t offset_from_outlines; //!< Offset from the boundary of a part to the comb path. (nozzle width / 2)
|
||||
const int64_t max_moveInside_distance2; //!< Maximal distance of a point to the Comb::boundary_inside which is still to be considered inside. (very sharp corners not allowed :S)
|
||||
const int64_t max_move_inside_distance2; //!< Maximal distance of a point to the Comb::boundary_inside which is still to be considered inside. (very sharp corners not allowed :S)
|
||||
const int64_t offset_from_outlines_outside; //!< Offset from the boundary of a part to a travel path which avoids it by this distance.
|
||||
const int64_t offset_from_inside_to_outside; //!< The sum of the offsets for the inside and outside boundary Comb::offset_from_outlines and Comb::offset_from_outlines_outside
|
||||
const int64_t max_crossing_dist2; //!< The maximal distance by which to cross the in_between area between inside and outside
|
||||
@@ -110,21 +114,28 @@ private:
|
||||
static const int64_t offset_extra_start_end = 100; //!< Distance to move start point and end point toward eachother to extra avoid collision with the boundaries.
|
||||
|
||||
const bool avoid_other_parts; //!< Whether to perform inverse combing a.k.a. avoid parts.
|
||||
|
||||
|
||||
Polygons& boundary_inside; //!< The boundary within which to comb.
|
||||
Polygons* boundary_outside; //!< The boundary outside of which to stay to avoid collision with other layer parts. This is a pointer cause we only compute it when we move outside the boundary (so not when there is only a single part in the layer)
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* outside_loc_to_line; //!< The SparsePointGridInclusive mapping locations to line segments of the outside boundary.
|
||||
PartsView partsView_inside; //!< Structured indices onto boundary_inside which shows which polygons belong to which part.
|
||||
Polygons outlines; //!< The actual boundary between the model and air
|
||||
LocToLineGrid* inside_loc_to_line; //!< The SparsePointGridInclusive mapping locations to line segments of the inner boundary.
|
||||
LazyInitialization<Polygons> boundary_outside; //!< The boundary outside of which to stay to avoid collision with other layer parts. This is a pointer cause we only compute it when we move outside the boundary (so not when there is only a single part in the layer)
|
||||
LazyInitialization<LocToLineGrid, Comb*, const int64_t> outside_loc_to_line; //!< The SparsePointGridInclusive mapping locations to line segments of the outside boundary.
|
||||
|
||||
/*!
|
||||
* Get the boundary_outside, which is an offset from the outlines of all meshes in the layer. Calculate it when it hasn't been calculated yet.
|
||||
* Get the outlines of the meshes or raft for this layer
|
||||
*/
|
||||
Polygons& getBoundaryOutside();
|
||||
|
||||
Polygons getCombOutlines();
|
||||
|
||||
/*!
|
||||
* Get the SparsePointGridInclusive mapping locations to line segments of the outside boundary. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& getOutsideLocToLine();
|
||||
LocToLineGrid& getOutsideLocToLine();
|
||||
|
||||
/*!
|
||||
* Get the boundary_outside, which is an offset from the outlines of all meshes in the layer. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
Polygons& getBoundaryOutside();
|
||||
|
||||
/*!
|
||||
* Move the startPoint or endPoint inside when it should be inside
|
||||
@@ -138,6 +149,9 @@ private:
|
||||
public:
|
||||
/*!
|
||||
* Initializes the combing areas for every mesh in the layer (not support)
|
||||
*
|
||||
* \warning \ref Comb::calc changes the order of polygons in \p Comb::comb_boundary_inside
|
||||
*
|
||||
* \param storage Where the layer polygon data is stored
|
||||
* \param layer_nr The number of the layer for which to generate the combing areas.
|
||||
* \param comb_boundary_inside The comb boundary within which to comb within layer parts.
|
||||
@@ -146,12 +160,14 @@ public:
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
*/
|
||||
Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t offset_from_outlines, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
|
||||
|
||||
~Comb();
|
||||
|
||||
/*!
|
||||
* Calculate the comb paths (if any) - one for each polygon combed alternated with travel paths
|
||||
*
|
||||
* \warning Changes the order of polygons in \ref Comb::comb_boundary_inside
|
||||
*
|
||||
* \param startPoint Where to start moving from
|
||||
* \param endPoint Where to move to
|
||||
* \param combPoints Output parameter: The points along the combing path, excluding the \p startPoint (?) and \p endPoint
|
||||
@@ -160,7 +176,7 @@ public:
|
||||
* \param via_outside_makes_combing_fail When going through air is inavoidable, stop calculation early and return false.
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \return Whether combing has succeeded; otherwise a retraction is needed.
|
||||
*/
|
||||
*/
|
||||
bool calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside, bool endInside, int64_t max_comb_distance_ignored, bool via_outside_makes_combing_fail, bool fail_on_unavoidable_obstacles);
|
||||
};
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ void LinePolygonsCrossings::getBasicCombingPath(CombPath& combPath)
|
||||
void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, CombPath& combPath)
|
||||
{
|
||||
PolygonRef poly = boundary[polyCrossings.poly_idx];
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x - dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x - std::abs(dist_to_move_boundary_point_outside), transformed_startPoint.Y)));
|
||||
if ( ( polyCrossings.max.point_idx - polyCrossings.min.point_idx + poly.size() ) % poly.size()
|
||||
< poly.size() / 2 )
|
||||
{ // follow the path in the same direction as the winding order of the boundary polygon
|
||||
@@ -166,7 +166,7 @@ void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, Co
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x + dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x + std::abs(dist_to_move_boundary_point_outside), transformed_startPoint.Y)));
|
||||
}
|
||||
|
||||
|
||||
@@ -194,9 +194,9 @@ bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimize
|
||||
continue;
|
||||
}
|
||||
Point& current_point = optimized_comb_path.back();
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx]))
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(current_point, comb_path[point_idx], loc_to_line_grid))
|
||||
{
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, current_point, comb_path[point_idx - 1]))
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(current_point, comb_path[point_idx - 1], loc_to_line_grid))
|
||||
{
|
||||
comb_path.cross_boundary = true;
|
||||
}
|
||||
@@ -209,7 +209,7 @@ bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimize
|
||||
// TODO: add the below extra optimization? (+/- 7% extra computation time, +/- 2% faster print for Dual_extrusion_support_generation.stl)
|
||||
while (optimized_comb_path.size() > 1)
|
||||
{
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(boundary, optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx]))
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(optimized_comb_path[optimized_comb_path.size() - 2], comb_path[point_idx], loc_to_line_grid))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#define PATH_PLANNING_LINE_POLYGONS_CROSSINGS_H
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "../utils/polygonUtils.h"
|
||||
#include "../utils/SparseLineGrid.h"
|
||||
|
||||
#include "CombPath.h"
|
||||
|
||||
@@ -80,6 +82,7 @@ private:
|
||||
unsigned int max_crossing_idx; //!< The index into LinePolygonsCrossings::crossings to the crossing with the maximal PolyCrossings::max crossing of all PolyCrossings's.
|
||||
|
||||
Polygons& boundary; //!< The boundary not to cross during combing.
|
||||
LocToLineGrid& loc_to_line_grid; //!< Mapping from locations to line segments of \ref LinePolygonsCrossings::boundary
|
||||
Point startPoint; //!< The start point of the scanline.
|
||||
Point endPoint; //!< The end point of the scanline.
|
||||
|
||||
@@ -163,8 +166,12 @@ private:
|
||||
* \param end the end point
|
||||
* \param dist_to_move_boundary_point_outside Distance used to move a point from a boundary so that it doesn't intersect with it anymore. (Precision issue)
|
||||
*/
|
||||
LinePolygonsCrossings(Polygons& boundary, Point& start, Point& end, int64_t dist_to_move_boundary_point_outside)
|
||||
: boundary(boundary), startPoint(start), endPoint(end), dist_to_move_boundary_point_outside(dist_to_move_boundary_point_outside)
|
||||
LinePolygonsCrossings(Polygons& boundary, LocToLineGrid& loc_to_line_grid, Point& start, Point& end, int64_t dist_to_move_boundary_point_outside)
|
||||
: boundary(boundary)
|
||||
, loc_to_line_grid(loc_to_line_grid)
|
||||
, startPoint(start)
|
||||
, endPoint(end)
|
||||
, dist_to_move_boundary_point_outside(dist_to_move_boundary_point_outside)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -173,15 +180,16 @@ public:
|
||||
/*!
|
||||
* The main function of this class: calculate one combing path within the boundary.
|
||||
* \param boundary The polygons to follow when calculating the basic combing path
|
||||
* \param loc_to_line_grid A sparse grid mapping cells to all line segments of (at least) \p boundary in those cells
|
||||
* \param startPoint From where to start the combing move.
|
||||
* \param endPoint Where to end the combing move.
|
||||
* \param combPath Output parameter: the combing path generated.
|
||||
* \param fail_on_unavoidable_obstacles When moving over other parts is inavoidable, stop calculation early and return false.
|
||||
* \return Whether combing succeeded, i.e. we didn't cross any gaps/other parts
|
||||
*/
|
||||
static bool comb(Polygons& boundary, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles)
|
||||
static bool comb(Polygons& boundary, LocToLineGrid& loc_to_line_grid, Point startPoint, Point endPoint, CombPath& combPath, int64_t dist_to_move_boundary_point_outside, int64_t max_comb_distance_ignored, bool fail_on_unavoidable_obstacles)
|
||||
{
|
||||
LinePolygonsCrossings linePolygonsCrossings(boundary, startPoint, endPoint, dist_to_move_boundary_point_outside);
|
||||
LinePolygonsCrossings linePolygonsCrossings(boundary, loc_to_line_grid, startPoint, endPoint, dist_to_move_boundary_point_outside);
|
||||
return linePolygonsCrossings.getCombingPath(combPath, max_comb_distance_ignored, fail_on_unavoidable_obstacles);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -75,6 +75,10 @@ int Raft::getFillerLayerHeight(const SliceDataStorage& storage)
|
||||
int Raft::getTotalExtraLayers(const SliceDataStorage& storage)
|
||||
{
|
||||
const ExtruderTrain& train = *storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("adhesion_extruder_nr"));
|
||||
if (train.getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::RAFT)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 2 + train.getSettingAsCount("raft_surface_layers") + getFillerLayerCount(storage);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ std::string toString(EGCodeFlavor flavor)
|
||||
}
|
||||
|
||||
SettingsBaseVirtual::SettingsBaseVirtual()
|
||||
: parent(NULL)
|
||||
: parent(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ SettingsBaseVirtual::SettingsBaseVirtual(SettingsBaseVirtual* parent)
|
||||
}
|
||||
|
||||
SettingsBase::SettingsBase()
|
||||
: SettingsBaseVirtual(NULL)
|
||||
: SettingsBaseVirtual(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -351,6 +351,8 @@ EFillMethod SettingsBaseVirtual::getSettingAsFillMethod(std::string key) const
|
||||
return EFillMethod::TRIANGLES;
|
||||
if (value == "concentric")
|
||||
return EFillMethod::CONCENTRIC;
|
||||
if (value == "concentric_3d")
|
||||
return EFillMethod::CONCENTRIC_3D;
|
||||
if (value == "zigzag")
|
||||
return EFillMethod::ZIG_ZAG;
|
||||
return EFillMethod::NONE;
|
||||
@@ -363,6 +365,8 @@ EPlatformAdhesion SettingsBaseVirtual::getSettingAsPlatformAdhesion(std::string
|
||||
return EPlatformAdhesion::BRIM;
|
||||
if (value == "raft")
|
||||
return EPlatformAdhesion::RAFT;
|
||||
if (value == "none")
|
||||
return EPlatformAdhesion::NONE;
|
||||
return EPlatformAdhesion::SKIRT;
|
||||
}
|
||||
|
||||
@@ -400,6 +404,20 @@ ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key) const
|
||||
return ESurfaceMode::NORMAL;
|
||||
}
|
||||
|
||||
FillPerimeterGapMode SettingsBaseVirtual::getSettingAsFillPerimeterGapMode(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "nowhere")
|
||||
{
|
||||
return FillPerimeterGapMode::NOWHERE;
|
||||
}
|
||||
if (value == "everywhere")
|
||||
{
|
||||
return FillPerimeterGapMode::EVERYWHERE;
|
||||
}
|
||||
return FillPerimeterGapMode::NOWHERE;
|
||||
}
|
||||
|
||||
CombingMode SettingsBaseVirtual::getSettingAsCombingMode(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
|
||||
@@ -108,6 +108,7 @@ enum class EFillMethod
|
||||
TETRAHEDRAL,
|
||||
TRIANGLES,
|
||||
CONCENTRIC,
|
||||
CONCENTRIC_3D,
|
||||
ZIG_ZAG,
|
||||
NONE
|
||||
};
|
||||
@@ -119,7 +120,8 @@ enum class EPlatformAdhesion
|
||||
{
|
||||
SKIRT,
|
||||
BRIM,
|
||||
RAFT
|
||||
RAFT,
|
||||
NONE
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -146,6 +148,12 @@ enum class ESurfaceMode
|
||||
BOTH
|
||||
};
|
||||
|
||||
enum class FillPerimeterGapMode
|
||||
{
|
||||
NOWHERE,
|
||||
EVERYWHERE
|
||||
};
|
||||
|
||||
enum class CombingMode
|
||||
{
|
||||
OFF,
|
||||
@@ -240,6 +248,7 @@ public:
|
||||
ESupportType getSettingAsSupportType(std::string key) const;
|
||||
EZSeamType getSettingAsZSeamType(std::string key) const;
|
||||
ESurfaceMode getSettingAsSurfaceMode(std::string key) const;
|
||||
FillPerimeterGapMode getSettingAsFillPerimeterGapMode(std::string key) const;
|
||||
CombingMode getSettingAsCombingMode(std::string key);
|
||||
SupportDistPriority getSettingAsSupportDistPriority(std::string key);
|
||||
};
|
||||
|
||||
+21
-3
@@ -143,13 +143,22 @@ void generateInfill(int layerNr, SliceMeshStorage& mesh, const int innermost_wal
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layerNr];
|
||||
|
||||
int extra_offset = 0;
|
||||
EFillMethod fill_pattern = mesh.getSettingAsFillMethod("infill_pattern");
|
||||
if ((fill_pattern == EFillMethod::CONCENTRIC || fill_pattern == EFillMethod::CONCENTRIC_3D)
|
||||
&& layerNr % 2 == 0
|
||||
&& mesh.getSettingInMicrons("infill_line_distance") > mesh.getSettingInMicrons("infill_line_width") * 2)
|
||||
{
|
||||
extra_offset = -innermost_wall_line_width;
|
||||
}
|
||||
|
||||
for(SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
if (int(part.insets.size()) < wall_line_count)
|
||||
{
|
||||
continue; // the last wall is not present, the part should only get inter preimeter gaps, but no infill.
|
||||
}
|
||||
Polygons infill = part.insets.back().offset(-innermost_wall_line_width / 2 - infill_skin_overlap);
|
||||
Polygons infill = part.insets.back().offset(extra_offset - innermost_wall_line_width / 2 - infill_skin_overlap);
|
||||
|
||||
for(SliceLayerPart& part2 : layer.parts)
|
||||
{
|
||||
@@ -162,8 +171,17 @@ void generateInfill(int layerNr, SliceMeshStorage& mesh, const int innermost_wal
|
||||
}
|
||||
}
|
||||
infill.removeSmallAreas(MIN_AREA_SIZE);
|
||||
|
||||
part.infill_area = infill.offset(infill_skin_overlap);
|
||||
|
||||
Polygons final_infill = infill.offset(infill_skin_overlap);
|
||||
|
||||
if (mesh.getSettingBoolean("infill_hollow"))
|
||||
{
|
||||
part.print_outline = part.print_outline.difference(final_infill);
|
||||
}
|
||||
else
|
||||
{
|
||||
part.infill_area = final_infill;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+136
-11
@@ -96,6 +96,7 @@ std::vector<GCodePathConfig> SliceDataStorage::initializeSkirtBrimConfigs()
|
||||
|
||||
SliceDataStorage::SliceDataStorage(MeshGroup* meshgroup) : SettingsMessenger(meshgroup),
|
||||
meshgroup(meshgroup != nullptr ? meshgroup : new MeshGroup(FffProcessor::getInstance())), //If no mesh group is provided, we roll our own.
|
||||
print_layer_count(0),
|
||||
retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
extruder_switch_retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
travel_config_per_extruder(initializeTravelConfigs()),
|
||||
@@ -105,10 +106,37 @@ SliceDataStorage::SliceDataStorage(MeshGroup* meshgroup) : SettingsMessenger(mes
|
||||
raft_surface_config(PrintFeatureType::SupportInterface),
|
||||
support_config(PrintFeatureType::Support),
|
||||
support_skin_config(PrintFeatureType::SupportInterface),
|
||||
max_object_height_second_to_last_extruder(-1)
|
||||
max_print_height_second_to_last_extruder(-1)
|
||||
{
|
||||
}
|
||||
|
||||
SliceLayerPart* SliceDataStorage::getPartInside(int layer_nr, Point location)
|
||||
{
|
||||
if (layer_nr >= 0)
|
||||
{
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
if ((unsigned int)layer_nr >= mesh.layers.size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
if (part.outline.inside(location))
|
||||
{
|
||||
return ∂
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only) const
|
||||
{
|
||||
if (layer_nr < 0 && layer_nr < -Raft::getFillerLayerCount(*this))
|
||||
@@ -209,20 +237,23 @@ Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool inc
|
||||
|
||||
}
|
||||
|
||||
std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
std::vector<bool> SliceDataStorage::getExtrudersUsed() const
|
||||
{
|
||||
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt_brim[extr_nr].size() > 0)
|
||||
if (getSettingAsPlatformAdhesion("adhesion_type") != EPlatformAdhesion::NONE)
|
||||
{
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
if (skirt_brim[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,9 +267,103 @@ std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
ret[getSettingAsIndex("support_interface_extruder_nr")] = true;
|
||||
|
||||
// all meshes are presupposed to actually have content
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
if (!mesh.getSettingBoolean("anti_overhang_mesh")
|
||||
&& !mesh.getSettingBoolean("support_mesh")
|
||||
)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<bool> SliceDataStorage::getExtrudersUsed(int layer_nr) const
|
||||
{
|
||||
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
|
||||
bool include_adhesion = true;
|
||||
bool include_helper_parts = true;
|
||||
bool include_models = true;
|
||||
if (layer_nr < 0)
|
||||
{
|
||||
include_models = false;
|
||||
if (layer_nr < -Raft::getFillerLayerCount(*this))
|
||||
{
|
||||
include_helper_parts = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
layer_nr = 0; // because the helper parts are copied from the initial layer in the filler layer
|
||||
include_adhesion = false;
|
||||
}
|
||||
}
|
||||
else if (layer_nr > 0 || getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT)
|
||||
{ // only include adhesion only for layers where platform adhesion actually occurs
|
||||
// i.e. layers < 0 are for raft, layer 0 is for brim/skirt
|
||||
include_adhesion = false;
|
||||
}
|
||||
if (include_adhesion)
|
||||
{
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt_brim[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ooze shield, draft shield ..?
|
||||
|
||||
if (include_helper_parts)
|
||||
{
|
||||
// support
|
||||
if (layer_nr < int(support.supportLayers.size()))
|
||||
{
|
||||
const SupportLayer& support_layer = support.supportLayers[layer_nr];
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
if (support_layer.supportAreas.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (support_layer.supportAreas.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_infill_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
if (support_layer.skin.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_interface_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (include_models)
|
||||
{
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
if (layer_nr >= int(mesh.layers.size()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
if (layer.parts.size() > 0)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
, insetX_config(PrintFeatureType::InnerWall)
|
||||
, skin_config(PrintFeatureType::Skin)
|
||||
{
|
||||
layers.reserve(slice_layer_count);
|
||||
layers.resize(slice_layer_count);
|
||||
infill_config.reserve(MAX_INFILL_COMBINE);
|
||||
for(int n=0; n<MAX_INFILL_COMBINE; n++)
|
||||
infill_config.emplace_back(PrintFeatureType::Infill);
|
||||
@@ -174,6 +174,8 @@ class SliceDataStorage : public SettingsMessenger, NoCopy
|
||||
public:
|
||||
MeshGroup* meshgroup; // needed to pass on the per extruder settings.. (TODO: put this somewhere else? Put the per object settings here directly, or a pointer only to the per object settings.)
|
||||
|
||||
unsigned int print_layer_count; //!< The total number of layers (except the raft and filler layers)
|
||||
|
||||
Point3 model_size, model_min, model_max;
|
||||
std::vector<SliceMeshStorage> meshes;
|
||||
|
||||
@@ -197,7 +199,10 @@ public:
|
||||
Polygons skirt_brim[MAX_EXTRUDERS]; //!< Skirt and brim polygons per extruder, ordered from inner to outer polygons.
|
||||
Polygons raftOutline; //Storage for the outline of the raft. Will be filled with lines when the GCode is generated.
|
||||
|
||||
int max_object_height_second_to_last_extruder; //!< Used in multi-extrusion: the layer number beyond which all models are printed with the same extruder
|
||||
int max_print_height_second_to_last_extruder; //!< Used in multi-extrusion: the layer number beyond which all models are printed with the same extruder
|
||||
std::vector<int> max_print_height_per_extruder; //!< For each extruder the highest layer number at which it is used.
|
||||
std::vector<size_t> max_print_height_order; //!< Ordered indices into max_print_height_per_extruder: back() will return the extruder number with the highest print height.
|
||||
|
||||
PrimeTower primeTower;
|
||||
|
||||
std::vector<Polygons> oozeShield; //oozeShield per layer
|
||||
@@ -234,6 +239,15 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Check in which part \p location lies, if in any.
|
||||
*
|
||||
* \param layer_nr The layer for which to check
|
||||
* \param location The location to check
|
||||
* \return The part in which \p location lie, or nullptr, if it's outside all parts.
|
||||
*/
|
||||
SliceLayerPart* getPartInside(int layer_nr, Point location);
|
||||
|
||||
/*!
|
||||
* Get all outlines within a given layer.
|
||||
*
|
||||
@@ -258,7 +272,15 @@ public:
|
||||
*
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed();
|
||||
std::vector<bool> getExtrudersUsed() const;
|
||||
|
||||
/*!
|
||||
* Get the extruders used on a particular layer.
|
||||
*
|
||||
* \param layer_nr the layer for which to check
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed(int layer_nr) const;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+25
-15
@@ -74,7 +74,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int l
|
||||
break;
|
||||
}
|
||||
}
|
||||
storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, (int)max_layer_nr_support_mesh_filled);
|
||||
storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, max_layer_nr_support_mesh_filled);
|
||||
for (int layer_nr = 0; layer_nr < max_layer_nr_support_mesh_filled; layer_nr++)
|
||||
{
|
||||
SupportLayer& support_layer = storage.support.supportLayers[max_layer_nr_support_mesh_filled];
|
||||
@@ -230,7 +230,6 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
basic_and_full_overhang_above.push_front(computeBasicAndFullOverhang(storage, mesh, layer_idx, max_dist_from_lower_layer));
|
||||
}
|
||||
|
||||
bool still_in_upper_empty_layers = true;
|
||||
int overhang_points_pos = overhang_points.size() - 1;
|
||||
Polygons supportLayer_last;
|
||||
std::vector<Polygons> towerRoofs;
|
||||
@@ -302,12 +301,6 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
|
||||
supportAreas[layer_idx] = supportLayer_this;
|
||||
|
||||
if (still_in_upper_empty_layers && supportLayer_this.size() > 0)
|
||||
{
|
||||
storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, (int)layer_idx);
|
||||
still_in_upper_empty_layers = false;
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::SUPPORT, storage.meshes.size() * mesh_idx + support_layer_count - layer_idx, support_layer_count * storage.meshes.size());
|
||||
}
|
||||
|
||||
@@ -345,6 +338,17 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (unsigned int layer_idx = supportAreas.size() - 1; layer_idx != (unsigned int) std::max(-1, storage.support.layer_nr_max_filled_layer) ; layer_idx--)
|
||||
{
|
||||
const Polygons& support_here = supportAreas[layer_idx];
|
||||
if (support_here.size() > 0)
|
||||
{
|
||||
storage.support.layer_nr_max_filled_layer = layer_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
storage.support.generated = true;
|
||||
}
|
||||
|
||||
@@ -471,15 +475,21 @@ void AreaSupport::handleTowers(
|
||||
}
|
||||
|
||||
// make tower roofs
|
||||
//for (Polygons& tower_roof : towerRoofs)
|
||||
for (unsigned int r = 0; r < towerRoofs.size(); r++)
|
||||
for (unsigned int roof_idx = 0; roof_idx < towerRoofs.size(); roof_idx++)
|
||||
{
|
||||
supportLayer_this = supportLayer_this.unionPolygons(towerRoofs[r]);
|
||||
|
||||
Polygons& tower_roof = towerRoofs[r];
|
||||
if (tower_roof.size() > 0 && tower_roof[0].area() < supportTowerDiameter * supportTowerDiameter)
|
||||
Polygons& tower_roof = towerRoofs[roof_idx];
|
||||
if (tower_roof.size() > 0)
|
||||
{
|
||||
towerRoofs[r] = tower_roof.offset(towerRoofExpansionDistance);
|
||||
supportLayer_this = supportLayer_this.unionPolygons(tower_roof);
|
||||
|
||||
if (tower_roof[0].area() < supportTowerDiameter * supportTowerDiameter)
|
||||
{
|
||||
tower_roof = tower_roof.offset(towerRoofExpansionDistance);
|
||||
}
|
||||
else
|
||||
{
|
||||
tower_roof.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ AABB3D::AABB3D()
|
||||
|
||||
bool AABB3D::hit(const AABB3D& other) const
|
||||
{
|
||||
if ( max.x < other.min.y
|
||||
|| min.x > other.max.y
|
||||
if ( max.x < other.min.x
|
||||
|| min.x > other.max.x
|
||||
|| max.y < other.min.y
|
||||
|| min.y > other.max.y
|
||||
|| max.z < other.min.z
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_LAZY_INITIALIZATION_H
|
||||
#define UTILS_LAZY_INITIALIZATION_H
|
||||
|
||||
#include <functional> // bind, function
|
||||
|
||||
#include "optional.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for initializing an object only when it's requested
|
||||
*
|
||||
* Credits to Johannes Goller
|
||||
*
|
||||
* \tparam T The type of the object to instantiate lazily
|
||||
* \tparam Args The types of the arguments to the constructor or constructor function object
|
||||
*/
|
||||
template <typename T, typename... Args>
|
||||
class LazyInitialization : public std::optional<T>
|
||||
{
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Delayed constructor call of T class
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the constructor at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[args...]()
|
||||
{
|
||||
return new T(args...);
|
||||
}
|
||||
)
|
||||
{ }
|
||||
|
||||
/*!
|
||||
* Delayed function call for creating a T object
|
||||
*
|
||||
* Performs a copy from the return value of the function on the stack to the heap.
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the function object at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(const std::function<T (Args...)>& f, Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[f, args...]()
|
||||
{
|
||||
return new T(f(args...));
|
||||
}
|
||||
)
|
||||
{ }
|
||||
|
||||
/*!
|
||||
* Delayed function call for creating a T object
|
||||
*
|
||||
* \warning passing references or pointers as parameters means these objects will be given to the function object at evaluation time.
|
||||
* Make sure these references/pointers are not invalidated between construction of the lazy object and the evaluation.
|
||||
*/
|
||||
LazyInitialization(const std::function<T* (Args...)>& f, Args... args)
|
||||
: std::optional<T>()
|
||||
, constructor(
|
||||
[f, args...]()
|
||||
{
|
||||
return f(args...);
|
||||
}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
LazyInitialization(LazyInitialization<T, Args...>& other) //!< copy constructor
|
||||
: std::optional<T>(other)
|
||||
, constructor(other.constructor)
|
||||
{
|
||||
}
|
||||
|
||||
LazyInitialization(LazyInitialization<T, Args...>&& other) //!< move constructor
|
||||
: std::optional<T>(other)
|
||||
{
|
||||
constructor = std::move(other.constructor);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Dereference this lazy object
|
||||
*
|
||||
* Calls constructor if object isn't constructed yet.
|
||||
*/
|
||||
T& operator*()
|
||||
{
|
||||
if (!std::optional<T>::instance)
|
||||
{
|
||||
std::optional<T>::instance = constructor();
|
||||
}
|
||||
return std::optional<T>::operator*();
|
||||
}
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
if (!std::optional<T>::instance)
|
||||
{
|
||||
std::optional<T>::instance = constructor();
|
||||
}
|
||||
return std::optional<T>::operator->();
|
||||
}
|
||||
|
||||
LazyInitialization<T, Args...>& operator=(LazyInitialization<T, Args...>&& other)
|
||||
{
|
||||
std::optional<T>::operator=(other);
|
||||
constructor = other.constructor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(LazyInitialization<T, Args...>& other)
|
||||
{
|
||||
std::optional<T>::swap(other);
|
||||
std::swap(constructor, other.constructor);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<T* ()> constructor;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif // UTILS_LAZY_INITIALIZATION_H
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "linearAlg2D.h"
|
||||
|
||||
#include <cmath> // atan2
|
||||
#include <cassert>
|
||||
#include <algorithm> // swap
|
||||
|
||||
#include "intpoint.h" // dot
|
||||
|
||||
@@ -108,4 +110,38 @@ bool LinearAlg2D::getPointOnLineWithDist(const Point p, const Point a, const Poi
|
||||
}
|
||||
}
|
||||
|
||||
bool LinearAlg2D::lineSegmentsCollide(Point a_from_transformed, Point a_to_transformed, Point b_from_transformed, Point b_to_transformed)
|
||||
{
|
||||
assert(std::abs(a_from_transformed.Y - a_to_transformed.Y) < 2 && "line a is supposed to be transformed to be aligned with the X axis!");
|
||||
assert(a_from_transformed.X - 2 <= a_to_transformed.X && "line a is supposed to be aligned with X axis in positive direction!");
|
||||
if ((b_from_transformed.Y >= a_from_transformed.Y && b_to_transformed.Y <= a_from_transformed.Y) || (b_to_transformed.Y >= a_from_transformed.Y && b_from_transformed.Y <= a_from_transformed.Y))
|
||||
{
|
||||
if(b_to_transformed.Y == b_from_transformed.Y)
|
||||
{
|
||||
if (b_to_transformed.X < b_from_transformed.X)
|
||||
{
|
||||
std::swap(b_to_transformed.X, b_from_transformed.X);
|
||||
}
|
||||
if (b_from_transformed.X > a_to_transformed.X)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (b_to_transformed.X < a_from_transformed.X)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t x = b_from_transformed.X + (b_to_transformed.X - b_from_transformed.X) * (a_from_transformed.Y - b_from_transformed.Y) / (b_to_transformed.Y - b_from_transformed.Y);
|
||||
if (x >= a_from_transformed.X && x <= a_to_transformed.X)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
|
||||
@@ -49,10 +49,11 @@ public:
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
void operator=(const ListPolyIt& other)
|
||||
ListPolyIt& operator=(const ListPolyIt& other)
|
||||
{
|
||||
poly = other.poly;
|
||||
it = other.it;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
ListPolyIt& operator++()
|
||||
|
||||
@@ -112,9 +112,10 @@ void PolygonProximityLinker::findProximatePoints()
|
||||
// o--->o-->o-->o->
|
||||
// 2 4 6 8
|
||||
std::unordered_set<ListPolyIt> nearby_lines;
|
||||
auto process_func = [&nearby_lines](const ListPolyIt& elem)
|
||||
std::function<bool (const ListPolyIt&)> process_func = [&nearby_lines](const ListPolyIt& elem)
|
||||
{
|
||||
nearby_lines.emplace(elem);
|
||||
return true;
|
||||
};
|
||||
line_grid.processNearby(point_it.p(), proximity_distance, process_func);
|
||||
for (const ListPolyIt& nearby_line : nearby_lines)
|
||||
@@ -139,9 +140,10 @@ void PolygonProximityLinker::findProximatePoints()
|
||||
// o--->o-->o-->o->
|
||||
// 2 4 6 8
|
||||
std::unordered_set<ListPolyIt> nearby_vert_its;
|
||||
auto process_func = [&nearby_vert_its](const ListPolyIt& elem)
|
||||
std::function<bool (const ListPolyIt&)> process_func = [&nearby_vert_its](const ListPolyIt& elem)
|
||||
{
|
||||
nearby_vert_its.emplace(elem);
|
||||
return true;
|
||||
};
|
||||
line_grid.processNearby(new_point_it.p(), proximity_distance, process_func);
|
||||
// because we use the same line_grid as before the resulting nearby_points
|
||||
|
||||
@@ -43,6 +43,13 @@ public:
|
||||
}
|
||||
return (*polygons)[poly_idx][point_idx];
|
||||
}
|
||||
/*!
|
||||
* Get the polygon to which this PolygonsPointIndex refers
|
||||
*/
|
||||
const PolygonRef getPolygon() const
|
||||
{
|
||||
return (*polygons)[poly_idx];
|
||||
}
|
||||
/*!
|
||||
* Test whether two iterators refer to the same polygon in the same polygon list.
|
||||
*
|
||||
@@ -57,11 +64,12 @@ public:
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
void operator=(const PolygonsPointIndex& other)
|
||||
PolygonsPointIndex& operator=(const PolygonsPointIndex& other)
|
||||
{
|
||||
polygons = other.polygons;
|
||||
poly_idx = other.poly_idx;
|
||||
point_idx = other.point_idx;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PolygonsPointIndex& operator++()
|
||||
|
||||
+143
-18
@@ -8,6 +8,7 @@
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -75,11 +76,22 @@ public:
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell.
|
||||
* called for each element in the cell. Processing stops if function returns false.
|
||||
*/
|
||||
template<class ProcessFunc>
|
||||
void processNearby(const Point &query_pt, coord_t radius,
|
||||
ProcessFunc &process_func) const;
|
||||
const std::function<bool (const ElemT&)>& process_func) const;
|
||||
|
||||
/*! \brief Process elements from cells that might contain sought after points along a line.
|
||||
*
|
||||
* Processes elements from cells that cross the line \p query_line.
|
||||
* May process elements that are up to sqrt(2) * cell_size from \p query_line.
|
||||
*
|
||||
* \param[in] query_line The line along which to check each cell
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cells. Processing stops if function returns false.
|
||||
*/
|
||||
void processLine(const std::pair<Point, Point> query_line,
|
||||
const std::function<bool (const Elem&)>& process_elem_func) const;
|
||||
|
||||
coord_t getCellSize() const;
|
||||
|
||||
@@ -92,11 +104,29 @@ protected:
|
||||
*
|
||||
* \param[in] grid_pt The grid coordinates of the cell.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell.
|
||||
* called for each element in the cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing a next cell.
|
||||
*/
|
||||
template<class ProcessFunc>
|
||||
void processFromCell(const GridPoint &grid_pt,
|
||||
ProcessFunc &process_func) const;
|
||||
bool processFromCell(const GridPoint &grid_pt,
|
||||
const std::function<bool (const Elem&)>& process_func) const;
|
||||
|
||||
/*! \brief Process cells along a line indicated by \p line.
|
||||
*
|
||||
* \param[in] line The line along which to process cells
|
||||
* \param[in] process_func Processes each cell. process_func(elem) is
|
||||
* called for each cell. Processing stops if function returns false.
|
||||
*/
|
||||
void processLineCells(const std::pair<Point, Point> line,
|
||||
const std::function<bool (GridPoint)>& process_cell_func);
|
||||
|
||||
/*! \brief Process cells along a line indicated by \p line.
|
||||
*
|
||||
* \param[in] line The line along which to process cells
|
||||
* \param[in] process_func Processes each cell. process_func(elem) is
|
||||
* called for each cell. Processing stops if function returns false.
|
||||
*/
|
||||
void processLineCells(const std::pair<Point, Point> line,
|
||||
const std::function<bool (GridPoint)>& process_cell_func) const;
|
||||
|
||||
/*! \brief Compute the grid coordinates of a point.
|
||||
*
|
||||
@@ -132,6 +162,8 @@ protected:
|
||||
GridMap m_grid;
|
||||
/*! \brief The cell (square) size. */
|
||||
coord_t m_cell_size;
|
||||
|
||||
grid_coord_t nonzero_sign(const grid_coord_t z) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -190,23 +222,99 @@ typename cura::coord_t SGI_THIS::toLowerCoord(const grid_coord_t& grid_coord) c
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
template<class ProcessFunc>
|
||||
void SGI_THIS::processFromCell(
|
||||
bool SGI_THIS::processFromCell(
|
||||
const GridPoint &grid_pt,
|
||||
ProcessFunc &process_func) const
|
||||
const std::function<bool (const Elem&)>& process_func) const
|
||||
{
|
||||
auto grid_range = m_grid.equal_range(grid_pt);
|
||||
for (auto iter = grid_range.first; iter != grid_range.second; ++iter)
|
||||
{
|
||||
process_func(iter->second);
|
||||
if (!process_func(iter->second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
void SGI_THIS::processLineCells(
|
||||
const std::pair<Point, Point> line,
|
||||
const std::function<bool (GridPoint)>& process_cell_func)
|
||||
{
|
||||
static_cast<const SGI_THIS*>(this)->processLineCells(line, process_cell_func);
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
void SGI_THIS::processLineCells(
|
||||
const std::pair<Point, Point> line,
|
||||
const std::function<bool (GridPoint)>& process_cell_func) const
|
||||
{
|
||||
|
||||
Point start = line.first;
|
||||
Point end = line.second;
|
||||
if (end.X < start.X)
|
||||
{ // make sure X increases between start and end
|
||||
std::swap(start, end);
|
||||
}
|
||||
|
||||
const GridPoint start_cell = toGridPoint(start);
|
||||
const GridPoint end_cell = toGridPoint(end);
|
||||
const coord_t y_diff = end.Y - start.Y;
|
||||
const grid_coord_t y_dir = nonzero_sign(y_diff);
|
||||
|
||||
grid_coord_t x_cell_start = start_cell.X;
|
||||
for (grid_coord_t cell_y = start_cell.Y; cell_y * y_dir <= end_cell.Y * y_dir; cell_y += y_dir)
|
||||
{ // for all Y from start to end
|
||||
// nearest y coordinate of the cells in the next row
|
||||
coord_t nearest_next_y = toLowerCoord(cell_y + ((nonzero_sign(cell_y) == y_dir || cell_y == 0) ? y_dir : coord_t(0)));
|
||||
grid_coord_t x_cell_end; // the X coord of the last cell to include from this row
|
||||
if (y_diff == 0)
|
||||
{
|
||||
x_cell_end = end_cell.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
coord_t area = (end.X - start.X) * (nearest_next_y - start.Y);
|
||||
// corresponding_x: the x coordinate corresponding to nearest_next_y
|
||||
coord_t corresponding_x = start.X + area / y_diff;
|
||||
x_cell_end = toGridCoord(corresponding_x + ((corresponding_x < 0) && ((area % y_diff) != 0)));
|
||||
if (x_cell_end < start_cell.X)
|
||||
{ // process at least one cell!
|
||||
x_cell_end = x_cell_start;
|
||||
}
|
||||
}
|
||||
|
||||
for (grid_coord_t cell_x = x_cell_start; cell_x <= x_cell_end; ++cell_x)
|
||||
{
|
||||
GridPoint grid_loc(cell_x, cell_y);
|
||||
bool continue_ = process_cell_func(grid_loc);
|
||||
if (!continue_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (grid_loc == end_cell)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO: this causes at least a one cell overlap for each row, which
|
||||
// includes extra cells when crossing precisely on the corners
|
||||
// where positive slope where x > 0 and negative slope where x < 0
|
||||
x_cell_start = x_cell_end;
|
||||
}
|
||||
assert(false && "We should have returned already before here!");
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
typename SGI_THIS::grid_coord_t SGI_THIS::nonzero_sign(const grid_coord_t z) const
|
||||
{
|
||||
return (z >= 0) - (z < 0);
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
template<class ProcessFunc>
|
||||
void SGI_THIS::processNearby(const Point &query_pt, coord_t radius,
|
||||
ProcessFunc &process_func) const
|
||||
const std::function<bool (const Elem&)>& process_func) const
|
||||
{
|
||||
Point min_loc(query_pt.X - radius, query_pt.Y - radius);
|
||||
Point max_loc(query_pt.X + radius, query_pt.Y + radius);
|
||||
@@ -219,19 +327,35 @@ void SGI_THIS::processNearby(const Point &query_pt, coord_t radius,
|
||||
for (coord_t grid_x = min_grid.X; grid_x <= max_grid.X; ++grid_x)
|
||||
{
|
||||
GridPoint grid_pt(grid_x,grid_y);
|
||||
processFromCell(grid_pt, process_func);
|
||||
bool continue_ = processFromCell(grid_pt, process_func);
|
||||
if (!continue_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
void SGI_THIS::processLine(const std::pair<Point, Point> query_line,
|
||||
const std::function<bool (const Elem&)>& process_elem_func) const
|
||||
{
|
||||
const std::function<bool (const GridPoint&)> process_cell_func = [&process_elem_func, this](GridPoint grid_loc)
|
||||
{
|
||||
return processFromCell(grid_loc, process_elem_func);
|
||||
};
|
||||
processLineCells(query_line, process_cell_func);
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
std::vector<typename SGI_THIS::Elem>
|
||||
SGI_THIS::getNearby(const Point &query_pt, coord_t radius) const
|
||||
{
|
||||
std::vector<Elem> ret;
|
||||
auto process_func = [&ret](const Elem &elem)
|
||||
const std::function<bool (const Elem&)> process_func = [&ret](const Elem &elem)
|
||||
{
|
||||
ret.push_back(elem);
|
||||
return true;
|
||||
};
|
||||
processNearby(query_pt, radius, process_func);
|
||||
return ret;
|
||||
@@ -252,12 +376,12 @@ bool SGI_THIS::getNearest(
|
||||
{
|
||||
bool found = false;
|
||||
int64_t best_dist2 = static_cast<int64_t>(radius) * radius;
|
||||
auto process_func =
|
||||
const std::function<bool (const Elem&)> process_func =
|
||||
[&query_pt, &elem_nearest, &found, &best_dist2, &precondition](const Elem &elem)
|
||||
{
|
||||
if (!precondition(elem))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
int64_t dist2 = vSize2(elem.point - query_pt);
|
||||
if (dist2 < best_dist2)
|
||||
@@ -266,6 +390,7 @@ bool SGI_THIS::getNearest(
|
||||
elem_nearest = elem;
|
||||
best_dist2 = dist2;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
processNearby(query_pt, radius, process_func);
|
||||
return found;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "intpoint.h"
|
||||
#include "SparseGrid.h"
|
||||
@@ -50,7 +51,6 @@ protected:
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
grid_coord_t nonzero_sign(grid_coord_t z);
|
||||
};
|
||||
|
||||
|
||||
@@ -68,61 +68,19 @@ SGI_TEMPLATE
|
||||
void SGI_THIS::insert(const Elem &elem)
|
||||
{
|
||||
const std::pair<Point, Point> line = m_locator(elem);
|
||||
Point start = line.first;
|
||||
Point end = line.second;
|
||||
if (end.X < start.X)
|
||||
{ // make sure X increases between start and end
|
||||
std::swap(start, end);
|
||||
}
|
||||
|
||||
const GridPoint start_cell = SparseGrid<ElemT>::toGridPoint(start);
|
||||
const GridPoint end_cell = SparseGrid<ElemT>::toGridPoint(end);
|
||||
const coord_t y_diff = end.Y - start.Y;
|
||||
const grid_coord_t y_dir = nonzero_sign(y_diff);
|
||||
|
||||
grid_coord_t x_cell_start = start_cell.X;
|
||||
for (grid_coord_t cell_y = start_cell.Y; cell_y * y_dir <= end_cell.Y * y_dir; cell_y += y_dir)
|
||||
{ // for all Y from start to end
|
||||
// nearest y coordinate of the cells in the next row
|
||||
coord_t nearest_next_y = SparseGrid<ElemT>::toLowerCoord(cell_y + ((nonzero_sign(cell_y) == y_dir || cell_y == 0) ? y_dir : coord_t(0)));
|
||||
grid_coord_t x_cell_end; // the X coord of the last cell to include from this row
|
||||
if (y_diff == 0)
|
||||
using GridMap = std::unordered_multimap<GridPoint, Elem>;
|
||||
// below is a workaround for the fact that lambda functions cannot access private or protected members
|
||||
// first we define a lambda which works on any GridMap and then we bind it to the actual protected GridMap of the parent class
|
||||
std::function<bool (GridMap*, const GridPoint)> process_cell_func_ = [&elem, this](GridMap* m_grid, const GridPoint grid_loc)
|
||||
{
|
||||
x_cell_end = end_cell.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
coord_t area = (end.X - start.X) * (nearest_next_y - start.Y);
|
||||
// corresponding_x: the x coordinate corresponding to nearest_next_y
|
||||
coord_t corresponding_x = start.X + area / y_diff;
|
||||
x_cell_end = SparseGrid<ElemT>::toGridCoord(corresponding_x + ((corresponding_x < 0) && ((area % y_diff) != 0)));
|
||||
if (x_cell_end < start_cell.X)
|
||||
{ // process at least one cell!
|
||||
x_cell_end = x_cell_start;
|
||||
}
|
||||
}
|
||||
m_grid->emplace(grid_loc, elem);
|
||||
return true;
|
||||
};
|
||||
using namespace std::placeholders; // for _1, _2, _3...
|
||||
GridMap* m_grid = &(this->m_grid);
|
||||
std::function<bool (const GridPoint)> process_cell_func(std::bind(process_cell_func_, m_grid, _1));
|
||||
|
||||
for (grid_coord_t cell_x = x_cell_start; cell_x <= x_cell_end; ++cell_x)
|
||||
{
|
||||
GridPoint grid_loc(cell_x, cell_y);
|
||||
SparseGrid<ElemT>::m_grid.emplace(grid_loc, elem);
|
||||
if (grid_loc == end_cell)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO: this causes at least a one cell overlap for each row, which
|
||||
// includes extra cells when crossing precisely on the corners
|
||||
// where positive slope where x > 0 and negative slope where x < 0
|
||||
x_cell_start = x_cell_end;
|
||||
}
|
||||
assert(false && "We should have returned already before here!");
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
typename SGI_THIS::grid_coord_t SGI_THIS::nonzero_sign(grid_coord_t z)
|
||||
{
|
||||
return (z >= 0) - (z < 0);
|
||||
SparseGrid<ElemT>::processLineCells(line, process_cell_func);
|
||||
}
|
||||
|
||||
SGI_TEMPLATE
|
||||
@@ -132,16 +90,16 @@ void SGI_THIS::debugHTML(std::string filename)
|
||||
for (std::pair<GridPoint, ElemT> cell: SparseGrid<ElemT>::m_grid)
|
||||
{
|
||||
aabb.include(SparseGrid<ElemT>::toLowerCorner(cell.first));
|
||||
aabb.include(SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(nonzero_sign(cell.first.X), nonzero_sign(cell.first.Y))));
|
||||
aabb.include(SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(SparseGrid<ElemT>::nonzero_sign(cell.first.X), SparseGrid<ElemT>::nonzero_sign(cell.first.Y))));
|
||||
}
|
||||
SVG svg(filename.c_str(), aabb);
|
||||
for (std::pair<GridPoint, ElemT> cell: SparseGrid<ElemT>::m_grid)
|
||||
{
|
||||
// doesn't draw cells at x = 0 or y = 0 correctly (should be double size)
|
||||
Point lb = SparseGrid<ElemT>::toLowerCorner(cell.first);
|
||||
Point lt = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(0, nonzero_sign(cell.first.Y)));
|
||||
Point rt = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(nonzero_sign(cell.first.X), nonzero_sign(cell.first.Y)));
|
||||
Point rb = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(nonzero_sign(cell.first.X), 0));
|
||||
Point lt = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(0, SparseGrid<ElemT>::nonzero_sign(cell.first.Y)));
|
||||
Point rt = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(SparseGrid<ElemT>::nonzero_sign(cell.first.X), SparseGrid<ElemT>::nonzero_sign(cell.first.Y)));
|
||||
Point rb = SparseGrid<ElemT>::toLowerCorner(cell.first + GridPoint(SparseGrid<ElemT>::nonzero_sign(cell.first.X), 0));
|
||||
if (lb.X == 0)
|
||||
{
|
||||
lb.X = -SparseGrid<ElemT>::m_cell_size;
|
||||
|
||||
@@ -109,9 +109,10 @@ std::vector<Val>
|
||||
SG_THIS::getNearbyVals(const Point &query_pt, coord_t radius) const
|
||||
{
|
||||
std::vector<Val> ret;
|
||||
auto process_func = [&ret](const typename SG_THIS::Elem &elem)
|
||||
std::function<bool (const SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem<Val>&)> process_func = [&ret](const typename SG_THIS::Elem &elem)
|
||||
{
|
||||
ret.push_back(elem.val);
|
||||
return true;
|
||||
};
|
||||
this->processNearby(query_pt, radius, process_func);
|
||||
return ret;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_ALGORITHM_H
|
||||
#define UTILS_ALGORITHM_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
|
||||
// extensions to algorithm.h from the standard library
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Get the order of a vector: the sorted indices of a vector
|
||||
*
|
||||
* {1.6, 1.8, 1.7} returns {1, 3, 2} meaning {in[1], in[3], in[2]} is a sorted
|
||||
* vector
|
||||
*
|
||||
* Thanks to Lukasz Wiklendt
|
||||
*
|
||||
* \param in The vector for which to get the order
|
||||
* \return An ordered vector of indices into \p in
|
||||
*/
|
||||
template<typename T>
|
||||
std::vector<size_t> order(const std::vector<T> &in)
|
||||
{
|
||||
// initialize original index locations
|
||||
std::vector<size_t> order(in.size());
|
||||
std::iota(order.begin(), order.end(), 0); // fill vector with 1, 2, 3,.. etc
|
||||
|
||||
// sort indexes based on comparing values in v
|
||||
std::sort(order.begin(), order.end(),
|
||||
[&in](size_t i1, size_t i2)
|
||||
{
|
||||
return in[i1] < in[i2];
|
||||
}
|
||||
);
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // UTILS_ALGORITHM_H
|
||||
|
||||
@@ -216,7 +216,23 @@ public:
|
||||
|| getDist2FromLineSegment(c, a, d) <= max_dist2
|
||||
|| getDist2FromLineSegment(c, b, d) <= max_dist2;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Check whether two line segments collide.
|
||||
*
|
||||
* \warning Edge cases (end points of line segments fall on other line segment) register as a collision.
|
||||
*
|
||||
* \note All points are assumed to be transformed by the transformation matrix of the vector from \p a_from to \p a_to.
|
||||
* I.e. a is a vertical line; the Y of \p a_from_transformed is the same as the Y of \p a_to_transformed.
|
||||
*
|
||||
* \param a_from_transformed The transformed from location of line a
|
||||
* \param a_from_transformed The transformed to location of line a
|
||||
* \param b_from_transformed The transformed from location of line b
|
||||
* \param b_from_transformed The transformed to location of line b
|
||||
* \return Whether the two line segments collide
|
||||
*/
|
||||
static bool lineSegmentsCollide(Point a_from_transformed, Point a_to_transformed, Point b_from_transformed, Point b_to_transformed);
|
||||
|
||||
/*!
|
||||
* Compute the angle between two consecutive line segments.
|
||||
*
|
||||
|
||||
+11
-4
@@ -3,6 +3,8 @@
|
||||
#define UTILS_OPTIONAL_H
|
||||
|
||||
#include <algorithm> // swap
|
||||
#include <type_traits> // enable_if is_same
|
||||
#include <cassert> // assert
|
||||
|
||||
namespace std
|
||||
{
|
||||
@@ -19,6 +21,7 @@ namespace std
|
||||
template<typename T>
|
||||
class optional
|
||||
{
|
||||
protected:
|
||||
T* instance;
|
||||
public:
|
||||
optional() //!< create an optional value which is not instantiated
|
||||
@@ -46,7 +49,7 @@ public:
|
||||
: instance(new T(args...))
|
||||
{
|
||||
}
|
||||
~optional() //!< simple destructor
|
||||
virtual ~optional() //!< simple destructor
|
||||
{
|
||||
if (instance)
|
||||
{
|
||||
@@ -72,13 +75,13 @@ public:
|
||||
{
|
||||
if (instance)
|
||||
{
|
||||
delete instance;
|
||||
if (other.instance)
|
||||
{
|
||||
*instance = *other.instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete instance;
|
||||
instance = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -101,12 +104,14 @@ public:
|
||||
other.instance = nullptr;
|
||||
return *this;
|
||||
}
|
||||
template<class U>
|
||||
template<class U = T
|
||||
, typename = typename std::enable_if<std::is_assignable<T&, U>::value>::type // type U is T, T& or T&&
|
||||
>
|
||||
optional& operator=(U&& value)
|
||||
{
|
||||
if (instance)
|
||||
{
|
||||
*instance = value;
|
||||
*instance = std::forward<U>(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -116,10 +121,12 @@ public:
|
||||
}
|
||||
constexpr T* operator->() const
|
||||
{
|
||||
assert(instance && "instance should be instatiated!");
|
||||
return instance;
|
||||
}
|
||||
constexpr T& operator*() const&
|
||||
{
|
||||
assert(instance && "instance should be instatiated!");
|
||||
return *instance;
|
||||
}
|
||||
constexpr explicit operator bool() const
|
||||
|
||||
@@ -83,6 +83,42 @@ unsigned int Polygons::pointCount() const
|
||||
}
|
||||
|
||||
bool Polygons::inside(Point p, bool border_result) const
|
||||
{
|
||||
int poly_count_inside = 0;
|
||||
for (const ClipperLib::Path& poly : *this)
|
||||
{
|
||||
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly);
|
||||
if (is_inside_this_poly == -1)
|
||||
{
|
||||
return border_result;
|
||||
}
|
||||
poly_count_inside += is_inside_this_poly;
|
||||
}
|
||||
return (poly_count_inside % 2) == 1;
|
||||
}
|
||||
|
||||
bool PolygonsPart::inside(Point p, bool border_result) const
|
||||
{
|
||||
if (size() < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(*this)[0].inside(p, border_result))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (unsigned int n = 1; n < paths.size(); n++)
|
||||
{
|
||||
if ((*this)[n].inside(p, !border_result))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Polygons::insideOld(Point p, bool border_result) const
|
||||
{
|
||||
const Polygons& thiss = *this;
|
||||
if (size() < 1)
|
||||
|
||||
+39
-20
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "intpoint.h"
|
||||
|
||||
//#define CHECK_POLY_ACCESS
|
||||
#define CHECK_POLY_ACCESS
|
||||
#ifdef CHECK_POLY_ACCESS
|
||||
#define POLY_ASSERT(e) assert(e)
|
||||
#else
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
|
||||
Point& operator[] (unsigned int index) const
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
POLY_ASSERT(index < size() && index >= 0);
|
||||
return (*path)[index];
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
|
||||
void remove(unsigned int index)
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
POLY_ASSERT(index < size() && index >= 0);
|
||||
path->erase(path->begin() + index);
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ public:
|
||||
|
||||
PolygonRef operator[] (unsigned int index)
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
POLY_ASSERT(index < size() && index >= 0);
|
||||
return PolygonRef(paths[index]);
|
||||
}
|
||||
const PolygonRef operator[] (unsigned int index) const
|
||||
@@ -432,7 +432,7 @@ public:
|
||||
}
|
||||
void remove(unsigned int index)
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
POLY_ASSERT(index < size() && index >= 0);
|
||||
paths.erase(paths.begin() + index);
|
||||
}
|
||||
void erase(ClipperLib::Paths::iterator start, ClipperLib::Paths::iterator end)
|
||||
@@ -476,6 +476,9 @@ public:
|
||||
Polygons() {}
|
||||
|
||||
Polygons(const Polygons& other) { paths = other.paths; }
|
||||
|
||||
virtual ~Polygons() {}
|
||||
|
||||
Polygons& operator=(const Polygons& other) { paths = other.paths; return *this; }
|
||||
|
||||
bool operator==(const Polygons& other) const =delete;
|
||||
@@ -537,6 +540,20 @@ public:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Check if we are inside the polygon.
|
||||
*
|
||||
* We do this by counting the number of polygons inside which this point lies.
|
||||
* An odd number is inside, while an even number is outside.
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
virtual bool inside(Point p, bool border_result = false) const;
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
@@ -549,11 +566,13 @@ public:
|
||||
*
|
||||
* Returns false if outside, true if inside; if the point lies exactly on the border, will return \p border_result.
|
||||
*
|
||||
* \deprecated This function is old and no longer used. instead use \ref Polygons::inside
|
||||
*
|
||||
* \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) const;
|
||||
bool insideOld(Point p, bool border_result = false) const;
|
||||
|
||||
/*!
|
||||
* Find the polygon inside which point \p p resides.
|
||||
@@ -877,20 +896,20 @@ public:
|
||||
Polygons& thiss = *this;
|
||||
return thiss[0];
|
||||
}
|
||||
|
||||
bool inside(Point p)
|
||||
{
|
||||
if (size() < 1)
|
||||
return false;
|
||||
if (!(*this)[0].inside(p))
|
||||
return false;
|
||||
for(unsigned int n=1; n<paths.size(); n++)
|
||||
{
|
||||
if ((*this)[n].inside(p))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Check if we are inside the polygon.
|
||||
*
|
||||
* We do this by counting the number of polygons inside which this point lies.
|
||||
* An odd number is inside, while an even number is outside.
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
virtual bool inside(Point p, bool border_result = false) const;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
+122
-57
@@ -126,15 +126,20 @@ Point PolygonUtils::getBoundaryPointWithOffset(PolygonRef poly, unsigned int poi
|
||||
|
||||
Point PolygonUtils::moveInsideDiagonally(ClosestPolygonPoint point_on_boundary, int64_t inset)
|
||||
{
|
||||
Point p0 = point_on_boundary.poly[point_on_boundary.point_idx];
|
||||
Point p1 = point_on_boundary.poly[(point_on_boundary.point_idx + 1) % point_on_boundary.poly.size()];
|
||||
if (!point_on_boundary.isValid())
|
||||
{
|
||||
return no_point;
|
||||
}
|
||||
PolygonRef poly = *point_on_boundary.poly;
|
||||
Point p0 = poly[point_on_boundary.point_idx];
|
||||
Point p1 = poly[(point_on_boundary.point_idx + 1) % poly.size()];
|
||||
if (vSize2(p0 - point_on_boundary.location) < vSize2(p1 - point_on_boundary.location))
|
||||
{
|
||||
return point_on_boundary.location + normal(getVertexInwardNormal(point_on_boundary.poly, point_on_boundary.point_idx), inset);
|
||||
return point_on_boundary.location + normal(getVertexInwardNormal(poly, point_on_boundary.point_idx), inset);
|
||||
}
|
||||
else
|
||||
{
|
||||
return point_on_boundary.location + normal(getVertexInwardNormal(point_on_boundary.poly, (point_on_boundary.point_idx + 1) % point_on_boundary.poly.size()), inset);
|
||||
return point_on_boundary.location + normal(getVertexInwardNormal(poly, (point_on_boundary.point_idx + 1) % poly.size()), inset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,23 +149,39 @@ unsigned int PolygonUtils::moveOutside(const Polygons& polygons, Point& from, in
|
||||
return moveInside(polygons, from, -distance, maxDist2);
|
||||
}
|
||||
|
||||
ClosestPolygonPoint PolygonUtils::moveInside2(const Polygons& polygons, Point& from, const int distance, const int64_t max_dist2, const std::function<int(Point)>& penalty_function)
|
||||
ClosestPolygonPoint PolygonUtils::moveInside2(const Polygons& polygons, Point& from, const int distance, const int64_t max_dist2, const Polygons* loc_to_line_polygons, const LocToLineGrid* loc_to_line_grid, const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
const ClosestPolygonPoint closest_polygon_point = findClosest(from, polygons, penalty_function);
|
||||
return _moveInside2(closest_polygon_point, distance, from, max_dist2);
|
||||
std::optional<ClosestPolygonPoint> closest_polygon_point;
|
||||
if (loc_to_line_grid)
|
||||
{
|
||||
closest_polygon_point = findClose(from, *loc_to_line_polygons, *loc_to_line_grid, penalty_function);
|
||||
}
|
||||
if (!closest_polygon_point)
|
||||
{
|
||||
closest_polygon_point = findClosest(from, polygons, penalty_function);
|
||||
}
|
||||
return _moveInside2(*closest_polygon_point, distance, from, max_dist2);
|
||||
}
|
||||
|
||||
ClosestPolygonPoint PolygonUtils::moveInside2(const PolygonRef polygon, Point& from, const int distance, const int64_t max_dist2, const std::function<int(Point)>& penalty_function)
|
||||
ClosestPolygonPoint PolygonUtils::moveInside2(const Polygons& loc_to_line_polygons, const PolygonRef polygon, Point& from, const int distance, const int64_t max_dist2, const LocToLineGrid* loc_to_line_grid, const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
const ClosestPolygonPoint closest_polygon_point = findClosest(from, polygon, penalty_function);
|
||||
return _moveInside2(closest_polygon_point, distance, from, max_dist2);
|
||||
std::optional<ClosestPolygonPoint> closest_polygon_point;
|
||||
if (loc_to_line_grid)
|
||||
{
|
||||
closest_polygon_point = findClose(from, loc_to_line_polygons, *loc_to_line_grid, penalty_function);
|
||||
}
|
||||
if (!closest_polygon_point)
|
||||
{
|
||||
closest_polygon_point = findClosest(from, polygon, penalty_function);
|
||||
}
|
||||
return _moveInside2(*closest_polygon_point, distance, from, max_dist2);
|
||||
}
|
||||
|
||||
ClosestPolygonPoint PolygonUtils::_moveInside2(const ClosestPolygonPoint& closest_polygon_point, const int distance, Point& from, const int64_t max_dist2)
|
||||
{
|
||||
if (closest_polygon_point.point_idx == NO_INDEX)
|
||||
if (!closest_polygon_point.isValid())
|
||||
{
|
||||
return ClosestPolygonPoint(closest_polygon_point.poly); // stub with invalid indices to signify we haven't found any
|
||||
return ClosestPolygonPoint(); // stub with invalid indices to signify we haven't found any
|
||||
}
|
||||
const Point v_boundary_from = from - closest_polygon_point.location;
|
||||
Point result = moveInside(closest_polygon_point, distance);
|
||||
@@ -182,7 +203,7 @@ ClosestPolygonPoint PolygonUtils::_moveInside2(const ClosestPolygonPoint& closes
|
||||
{
|
||||
if (vSize2(v_boundary_from) > max_dist2)
|
||||
{
|
||||
return ClosestPolygonPoint(closest_polygon_point.poly); // stub with invalid indices to signify we haven't found any
|
||||
return ClosestPolygonPoint(*closest_polygon_point.poly); // stub with invalid indices to signify we haven't found any
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -315,11 +336,15 @@ Point PolygonUtils::moveOutside(const ClosestPolygonPoint& cpp, const int distan
|
||||
|
||||
Point PolygonUtils::moveInside(const ClosestPolygonPoint& cpp, const int distance)
|
||||
{
|
||||
if (!cpp.isValid())
|
||||
{
|
||||
return no_point;
|
||||
}
|
||||
if (distance == 0)
|
||||
{ // the point which is assumed to be on the boundary doesn't have to be moved
|
||||
return cpp.location;
|
||||
}
|
||||
const PolygonRef poly = cpp.poly;
|
||||
const PolygonRef poly = *cpp.poly;
|
||||
unsigned int point_idx = cpp.point_idx;
|
||||
const Point& on_boundary = cpp.location;
|
||||
|
||||
@@ -355,14 +380,19 @@ Point PolygonUtils::moveInside(const ClosestPolygonPoint& cpp, const int distanc
|
||||
}
|
||||
}
|
||||
|
||||
ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons, Point& from, int preferred_dist_inside, int64_t max_dist2, const std::function<int(Point)>& penalty_function)
|
||||
ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons, Point& from, int preferred_dist_inside, int64_t max_dist2, const Polygons* loc_to_line_polygons, const LocToLineGrid* loc_to_line_grid, const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
ClosestPolygonPoint closest_polygon_point = moveInside2(polygons, from, preferred_dist_inside, max_dist2, penalty_function);
|
||||
if (closest_polygon_point.point_idx == NO_INDEX)
|
||||
ClosestPolygonPoint closest_polygon_point = moveInside2(polygons, from, preferred_dist_inside, max_dist2, loc_to_line_polygons, loc_to_line_grid, penalty_function);
|
||||
return ensureInsideOrOutside(polygons, from, closest_polygon_point, preferred_dist_inside, max_dist2, loc_to_line_polygons, loc_to_line_grid, penalty_function);
|
||||
}
|
||||
|
||||
ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons, Point& from, ClosestPolygonPoint& closest_polygon_point, int preferred_dist_inside, int64_t max_dist2, const Polygons* loc_to_line_polygons, const LocToLineGrid* loc_to_line_grid, const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
if (!closest_polygon_point.isValid())
|
||||
{
|
||||
return ClosestPolygonPoint(polygons[0]); // we couldn't move inside
|
||||
return ClosestPolygonPoint(); // we couldn't move inside
|
||||
}
|
||||
PolygonRef closest_poly = closest_polygon_point.poly;
|
||||
PolygonRef closest_poly = *closest_polygon_point.poly;
|
||||
bool is_outside_boundary = closest_poly.orientation();
|
||||
|
||||
{
|
||||
@@ -376,7 +406,7 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons
|
||||
|
||||
// try once more with half the preferred distance inside
|
||||
int64_t max_dist2_here = std::numeric_limits<int64_t>::max(); // we already concluded we are close enough to the closest_poly
|
||||
moveInside2(closest_poly, from, preferred_dist_inside / 2, max_dist2_here, penalty_function);
|
||||
moveInside2(*loc_to_line_polygons, closest_poly, from, preferred_dist_inside / 2, max_dist2_here, loc_to_line_grid, penalty_function);
|
||||
bool is_inside = closest_poly.inside(from) == is_outside_boundary; // inside a hole is outside the part
|
||||
if (is_inside == (preferred_dist_inside > 0))
|
||||
{ // we ended up on the right side of the polygon
|
||||
@@ -390,10 +420,10 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons
|
||||
Polygons insetted = closest_poly.offset(offset / 2); // perform less inset, because chances are (thin parts of) the polygon will disappear, given that moveInside did an overshoot
|
||||
if (insetted.size() == 0)
|
||||
{
|
||||
return ClosestPolygonPoint(polygons[0]); // we couldn't move inside
|
||||
return ClosestPolygonPoint(); // we couldn't move inside
|
||||
}
|
||||
ClosestPolygonPoint inside = findClosest(from, insetted, penalty_function);
|
||||
if (inside.point_idx != NO_INDEX)
|
||||
if (inside.isValid())
|
||||
{
|
||||
bool is_inside = polygons.inside(inside.location) == is_outside_boundary; // inside a hole is outside the part
|
||||
if (is_inside != (preferred_dist_inside > 0))
|
||||
@@ -427,16 +457,16 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons
|
||||
}
|
||||
}
|
||||
svg.writeComment("From location");
|
||||
svg.writePoint(from, false, 5, SVG::Color::GREEN);
|
||||
svg.writePoint(from, true, 5, SVG::Color::GREEN);
|
||||
svg.writeComment("Location computed to be inside the black polygon");
|
||||
svg.writePoint(inside.location, false, 5, SVG::Color::RED);
|
||||
svg.writePoint(inside.location, true, 5, SVG::Color::RED);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
}
|
||||
logError("Clipper::offset failed. See generated debug.html!\n\tBlack is original\n\tBlue is offsetted polygon\n");
|
||||
#endif
|
||||
return ClosestPolygonPoint(polygons[0]);
|
||||
return ClosestPolygonPoint();
|
||||
}
|
||||
from = inside.location;
|
||||
} // otherwise we just return the closest polygon point without modifying the from location
|
||||
@@ -447,8 +477,12 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons
|
||||
|
||||
void PolygonUtils::findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result, int sample_size)
|
||||
{
|
||||
PolygonRef poly1 = poly1_result.poly;
|
||||
PolygonRef poly2 = poly2_result.poly;
|
||||
if (!poly1_result.isValid() || !poly2_result.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
PolygonRef poly1 = *poly1_result.poly;
|
||||
PolygonRef poly2 = *poly2_result.poly;
|
||||
if (poly1.size() == 0 || poly2.size() == 0)
|
||||
{
|
||||
return;
|
||||
@@ -477,8 +511,12 @@ void PolygonUtils::findSmallestConnection(ClosestPolygonPoint& poly1_result, Clo
|
||||
|
||||
void PolygonUtils::walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result)
|
||||
{
|
||||
PolygonRef poly1 = poly1_result.poly;
|
||||
PolygonRef poly2 = poly2_result.poly;
|
||||
if (!poly1_result.isValid() || !poly2_result.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
PolygonRef poly1 = *poly1_result.poly;
|
||||
PolygonRef poly2 = *poly2_result.poly;
|
||||
if (poly1_result.point_idx < 0 || poly2_result.point_idx < 0)
|
||||
{
|
||||
return;
|
||||
@@ -502,12 +540,12 @@ void PolygonUtils::walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_re
|
||||
ClosestPolygonPoint PolygonUtils::findNearestClosest(Point from, PolygonRef polygon, int start_idx)
|
||||
{
|
||||
ClosestPolygonPoint forth = findNearestClosest(from, polygon, start_idx, 1);
|
||||
if (forth.point_idx == NO_INDEX)
|
||||
if (!forth.isValid())
|
||||
{
|
||||
return forth; // stop computation
|
||||
}
|
||||
ClosestPolygonPoint back = findNearestClosest(from, polygon, start_idx, -1);
|
||||
assert(back.point_idx != NO_INDEX);
|
||||
assert(back.isValid());
|
||||
if (vSize2(forth.location - from) < vSize2(back.location - from))
|
||||
{
|
||||
return forth;
|
||||
@@ -556,7 +594,7 @@ ClosestPolygonPoint PolygonUtils::findNearestClosest(Point from, PolygonRef poly
|
||||
|
||||
ClosestPolygonPoint PolygonUtils::findClosest(Point from, const Polygons& polygons, const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
ClosestPolygonPoint none(from, -1, polygons[0], -1);
|
||||
ClosestPolygonPoint none;
|
||||
|
||||
if (polygons.size() == 0) return none;
|
||||
PolygonRef aPolygon = polygons[0];
|
||||
@@ -572,7 +610,7 @@ ClosestPolygonPoint PolygonUtils::findClosest(Point from, const Polygons& polygo
|
||||
const PolygonRef poly = polygons[ply];
|
||||
if (poly.size() == 0) continue;
|
||||
ClosestPolygonPoint closest_here = findClosest(from, poly, penalty_function);
|
||||
if (closest_here.point_idx == NO_INDEX)
|
||||
if (!closest_here.isValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -658,7 +696,7 @@ unsigned int PolygonUtils::findNearestVert(const Point from, const PolygonRef po
|
||||
}
|
||||
|
||||
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* PolygonUtils::createLocToLineGrid(const Polygons& polygons, int square_size)
|
||||
LocToLineGrid* PolygonUtils::createLocToLineGrid(const Polygons& polygons, int square_size)
|
||||
{
|
||||
unsigned int n_points = 0;
|
||||
for (const auto& poly : polygons)
|
||||
@@ -666,7 +704,7 @@ SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* PolygonUti
|
||||
n_points += poly.size();
|
||||
}
|
||||
|
||||
SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* ret = new SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>(square_size, n_points);
|
||||
LocToLineGrid* ret = new LocToLineGrid(square_size, n_points);
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
@@ -689,7 +727,7 @@ SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* PolygonUti
|
||||
*/
|
||||
std::optional<ClosestPolygonPoint> PolygonUtils::findClose(
|
||||
Point from, const Polygons& polygons,
|
||||
const SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& loc_to_line,
|
||||
const LocToLineGrid& loc_to_line,
|
||||
const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
std::vector<PolygonsPointIndex> near_lines =
|
||||
@@ -728,7 +766,7 @@ std::optional<ClosestPolygonPoint> PolygonUtils::findClose(
|
||||
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> PolygonUtils::findClose(
|
||||
const PolygonRef from, const Polygons& destination,
|
||||
const SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& destination_loc_to_line,
|
||||
const LocToLineGrid& destination_loc_to_line,
|
||||
const std::function<int(Point)>& penalty_function)
|
||||
{
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> ret;
|
||||
@@ -842,34 +880,61 @@ bool PolygonUtils::getNextPointWithDistance(Point from, int64_t dist, const Poly
|
||||
}
|
||||
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithLineSegment(const Point from, const Point to, const LocToLineGrid& loc_to_line, PolygonsPointIndex* collision_result)
|
||||
{
|
||||
bool ret = false;
|
||||
Point diff = to - from;
|
||||
if (vSize2(diff) < 2)
|
||||
{ // transformation matrix would fail
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithlineSegment(const PolygonRef poly, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix)
|
||||
PointMatrix transformation_matrix = PointMatrix(diff);
|
||||
Point transformed_from = transformation_matrix.apply(from);
|
||||
Point transformed_to = transformation_matrix.apply(to);
|
||||
|
||||
PolygonsPointIndex result;
|
||||
|
||||
std::function<bool (const PolygonsPointIndex&)> process_elem_func =
|
||||
[transformed_from, transformed_to, &transformation_matrix, &result, &ret]
|
||||
(const PolygonsPointIndex& line_start)
|
||||
{
|
||||
Point p0 = transformation_matrix.apply(line_start.p());
|
||||
Point p1 = transformation_matrix.apply(line_start.next().p());
|
||||
|
||||
if (LinearAlg2D::lineSegmentsCollide(transformed_from, transformed_to, p0, p1))
|
||||
{
|
||||
result = line_start;
|
||||
ret = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
loc_to_line.processLine(std::make_pair(from, to), process_elem_func);
|
||||
|
||||
if (collision_result)
|
||||
{
|
||||
*collision_result = result;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithLineSegment(const 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))
|
||||
if (LinearAlg2D::lineSegmentsCollide(transformed_startPoint, transformed_endPoint, p0, p1))
|
||||
{
|
||||
int64_t x;
|
||||
if(p1.Y == p0.Y)
|
||||
{
|
||||
x = p0.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithlineSegment(const PolygonRef poly, Point& startPoint, Point& endPoint)
|
||||
bool PolygonUtils::polygonCollidesWithLineSegment(const PolygonRef poly, Point& startPoint, Point& endPoint)
|
||||
{
|
||||
Point diff = endPoint - startPoint;
|
||||
|
||||
@@ -877,15 +942,15 @@ bool PolygonUtils::polygonCollidesWithlineSegment(const PolygonRef poly, Point&
|
||||
Point transformed_startPoint = transformation_matrix.apply(startPoint);
|
||||
Point transformed_endPoint = transformation_matrix.apply(endPoint);
|
||||
|
||||
return PolygonUtils::polygonCollidesWithlineSegment(poly, transformed_startPoint, transformed_endPoint, transformation_matrix);
|
||||
return PolygonUtils::polygonCollidesWithLineSegment(poly, transformed_startPoint, transformed_endPoint, transformation_matrix);
|
||||
}
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithlineSegment(const Polygons& polys, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix)
|
||||
bool PolygonUtils::polygonCollidesWithLineSegment(const Polygons& polys, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix)
|
||||
{
|
||||
for (const PolygonRef poly : const_cast<Polygons&>(polys))
|
||||
{
|
||||
if (poly.size() == 0) { continue; }
|
||||
if (PolygonUtils::polygonCollidesWithlineSegment(poly, transformed_startPoint, transformed_endPoint, transformation_matrix))
|
||||
if (PolygonUtils::polygonCollidesWithLineSegment(poly, transformed_startPoint, transformed_endPoint, transformation_matrix))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -895,7 +960,7 @@ bool PolygonUtils::polygonCollidesWithlineSegment(const Polygons& polys, Point&
|
||||
}
|
||||
|
||||
|
||||
bool PolygonUtils::polygonCollidesWithlineSegment(const Polygons& polys, Point& startPoint, Point& endPoint)
|
||||
bool PolygonUtils::polygonCollidesWithLineSegment(const Polygons& polys, Point& startPoint, Point& endPoint)
|
||||
{
|
||||
Point diff = endPoint - startPoint;
|
||||
|
||||
@@ -903,7 +968,7 @@ bool PolygonUtils::polygonCollidesWithlineSegment(const Polygons& polys, Point&
|
||||
Point transformed_startPoint = transformation_matrix.apply(startPoint);
|
||||
Point transformed_endPoint = transformation_matrix.apply(endPoint);
|
||||
|
||||
return polygonCollidesWithlineSegment(polys, transformed_startPoint, transformed_endPoint, transformation_matrix);
|
||||
return polygonCollidesWithLineSegment(polys, transformed_startPoint, transformed_endPoint, transformation_matrix);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+81
-14
@@ -20,16 +20,21 @@ namespace cura
|
||||
struct ClosestPolygonPoint
|
||||
{
|
||||
Point location; //!< Result location
|
||||
PolygonRef poly; //!< Polygon in which the result was found
|
||||
std::optional<PolygonRef> poly; //!< Polygon in which the result was found (or none if no result was found)
|
||||
unsigned int poly_idx; //!< The index of the polygon in some Polygons where ClosestPolygonPoint::poly can be found
|
||||
unsigned int point_idx; //!< Index to the first point in the polygon of the line segment on which the result was found
|
||||
ClosestPolygonPoint(Point p, int pos, PolygonRef poly) : location(p), poly(poly), poly_idx(NO_INDEX), point_idx(pos) {};
|
||||
ClosestPolygonPoint(Point p, int pos, PolygonRef poly, int poly_idx) : location(p), poly(poly), poly_idx(poly_idx), point_idx(pos) {};
|
||||
ClosestPolygonPoint(PolygonRef poly) : poly(poly), poly_idx(NO_INDEX), point_idx(NO_INDEX) {};
|
||||
ClosestPolygonPoint(Point p, int pos, PolygonRef poly) : location(p), poly(true, poly), poly_idx(NO_INDEX), point_idx(pos) {};
|
||||
ClosestPolygonPoint(Point p, int pos, PolygonRef poly, int poly_idx) : location(p), poly(true, poly), poly_idx(poly_idx), point_idx(pos) {};
|
||||
ClosestPolygonPoint(PolygonRef poly) : poly(true, poly), poly_idx(NO_INDEX), point_idx(NO_INDEX) {};
|
||||
ClosestPolygonPoint() : poly_idx(NO_INDEX), point_idx(NO_INDEX) {};
|
||||
Point p() const
|
||||
{ // conformity with other classes
|
||||
return location;
|
||||
}
|
||||
bool isValid() const
|
||||
{
|
||||
return point_idx != NO_INDEX;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -56,6 +61,8 @@ struct PolygonsPointIndexSegmentLocator
|
||||
}
|
||||
};
|
||||
|
||||
typedef SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator> LocToLineGrid;
|
||||
|
||||
class PolygonUtils
|
||||
{
|
||||
public:
|
||||
@@ -137,13 +144,20 @@ public:
|
||||
* 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.
|
||||
*
|
||||
* \warning If \p loc_to_line_grid is used, it's best to have all and only \p polygons in there.
|
||||
* If \p from is not closest to \p polygons this function may
|
||||
* return a ClosestPolygonPoint on a polygon in \p loc_to_line_grid which is not in \p polygons.
|
||||
*
|
||||
* \param polygons The polygons onto which to move the point
|
||||
* \param from[in,out] The point to move.
|
||||
* \param distance The distance by which to move the point.
|
||||
* \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon.
|
||||
* \param loc_to_line_polygons All polygons with which the \p loc_to_line_grid has been created.
|
||||
* \param loc_to_line_grid A SparseGrid mapping locations to line segments of \p polygons
|
||||
* \param penalty_function A function returning a penalty term on the squared distance score of a candidate point.
|
||||
* \return The point on the polygon closest to \p from
|
||||
*/
|
||||
static ClosestPolygonPoint moveInside2(const Polygons& polygons, Point& from, const int distance = 0, const int64_t max_dist2 = std::numeric_limits<int64_t>::max(), const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
static ClosestPolygonPoint moveInside2(const Polygons& polygons, Point& from, const int distance = 0, const int64_t max_dist2 = std::numeric_limits<int64_t>::max(), const Polygons* loc_to_line_polygons = nullptr, const LocToLineGrid* loc_to_line_grid = nullptr, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* Moves the point \p from onto the nearest segment of \p polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance.
|
||||
@@ -151,14 +165,20 @@ public:
|
||||
* 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.
|
||||
*
|
||||
* \warning When a \p loc_to_line is given this function only considers nearby elements.
|
||||
* Even when the penalty function favours elements farther away.
|
||||
* Also using the \p loc_to_line_grid automatically considers \p all_polygons
|
||||
*
|
||||
* \param loc_to_line_polygons All polygons which are present in the \p loc_to_line_grid of which \p polygon is an element
|
||||
* \param polygon The polygon onto which to move the point
|
||||
* \param from[in,out] The point to move.
|
||||
* \param distance The distance by which to move the point.
|
||||
* \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon.
|
||||
* \param loc_to_line_grid A SparseGrid mapping locations to line segments of \p polygon
|
||||
* \param penalty_function A function returning a penalty term on the squared distance score of a candidate point.
|
||||
* \return The point on the polygon closest to \p from
|
||||
*/
|
||||
static ClosestPolygonPoint moveInside2(const PolygonRef polygon, Point& from, const int distance = 0, const int64_t max_dist2 = std::numeric_limits<int64_t>::max(), const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
static ClosestPolygonPoint moveInside2(const Polygons& loc_to_line_polygons, const PolygonRef polygon, Point& from, const int distance = 0, const int64_t max_dist2 = std::numeric_limits<int64_t>::max(), const LocToLineGrid* loc_to_line_grid = nullptr, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* The opposite of moveInside.
|
||||
@@ -210,14 +230,45 @@ public:
|
||||
* but it might still be the case that we end up outside:
|
||||
* when the closest point on the boundary is very close to another polygon
|
||||
*
|
||||
* \warning When using a \p loc_to_line_grid which contains more polygons than just \p polygons,
|
||||
* the results is only correct if \p from is already closest to \p polygons, rather than other polygons in the \p loc_to_line_grid.
|
||||
*
|
||||
* \param polygons The polygons onto which to move the point
|
||||
* \param from[in,out] The point to move.
|
||||
* \param preferred_dist_inside The preferred distance from the boundary to the point
|
||||
* \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon.
|
||||
* \param loc_to_line_polygons The original polygons with which the \p loc_to_line_grid has been created
|
||||
* \param loc_to_line_grid A SparseGrid mapping locations to line segments of \p polygons
|
||||
* \param penalty_function A function returning a penalty term on the squared distance score of a candidate point.
|
||||
* \return The point on the polygon closest to \p from
|
||||
*/
|
||||
static ClosestPolygonPoint ensureInsideOrOutside(const Polygons& polygons, Point& from, int preferred_dist_inside, int64_t max_dist2 = std::numeric_limits<int64_t>::max(), const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
static ClosestPolygonPoint ensureInsideOrOutside(const Polygons& polygons, Point& from, int preferred_dist_inside, int64_t max_dist2 = std::numeric_limits<int64_t>::max(), const Polygons* loc_to_line_polygons = nullptr, const LocToLineGrid* loc_to_line_grid = nullptr, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
*
|
||||
* \warning May give false positives.
|
||||
* Some checking is done to make sure we end up inside the polygon,
|
||||
* but it might still be the case that we end up outside:
|
||||
* when the closest point on the boundary is very close to another polygon
|
||||
*
|
||||
* \warning When using a \p loc_to_line_grid which contains more polygons than just \p polygons,
|
||||
* the results is only correct if \p from is already closest to \p polygons, rather than other polygons in the \p loc_to_line_grid.
|
||||
*
|
||||
* \param polygons The polygons onto which to move the point
|
||||
* \param from[in,out] The point to move.
|
||||
* \param closest_polygon_point The point on \p polygons closest to \p from
|
||||
* \param preferred_dist_inside The preferred distance from the boundary to the point
|
||||
* \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon.
|
||||
* \param loc_to_line_polygons The original polygons with which the \p loc_to_line_grid has been created
|
||||
* \param loc_to_line_grid A SparseGrid mapping locations to line segments of \p polygons
|
||||
* \param penalty_function A function returning a penalty term on the squared distance score of a candidate point.
|
||||
* \return The point on the polygon closest to \p from
|
||||
*/
|
||||
static ClosestPolygonPoint ensureInsideOrOutside(const Polygons& polygons, Point& from, ClosestPolygonPoint& closest_polygon_point, int preferred_dist_inside, int64_t max_dist2 = std::numeric_limits<int64_t>::max(), const Polygons* loc_to_line_polygons = nullptr, const LocToLineGrid* loc_to_line_grid = nullptr, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* Find the two points in two polygons with the smallest distance.
|
||||
@@ -303,7 +354,7 @@ public:
|
||||
* \param square_size The cell size used to bundle line segments (also used to chop up lines so that multiple cells contain the same long line)
|
||||
* \return A bucket grid mapping spatial locations to poly-point indices into \p polygons
|
||||
*/
|
||||
static SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>* createLocToLineGrid(const Polygons& polygons, int square_size);
|
||||
static LocToLineGrid* createLocToLineGrid(const Polygons& polygons, int square_size);
|
||||
|
||||
/*!
|
||||
* Find the line segment closest to a given point \p from within a cell-block of a size defined in the SparsePointGridInclusive \p loc_to_line
|
||||
@@ -317,7 +368,7 @@ public:
|
||||
* \param penalty_function A function returning a penalty term on the squared distance score of a candidate point.
|
||||
* \return The nearest point on the polygon if the polygon was within a distance equal to the cell_size of the SparsePointGridInclusive
|
||||
*/
|
||||
static std::optional<ClosestPolygonPoint> findClose(Point from, const Polygons& polygons, const SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& loc_to_line, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
static std::optional<ClosestPolygonPoint> findClose(Point from, const Polygons& polygons, const LocToLineGrid& loc_to_line, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* Find the line segment closest to any point on \p from within cell-blocks of a size defined in the SparsePointGridInclusive \p destination_loc_to_line
|
||||
@@ -331,7 +382,23 @@ public:
|
||||
* \param penalty_function A function returning a penalty term on the squared distance score of a candidate point.
|
||||
* \return A collection of near crossing from the \p from polygon to the \p destination polygon. Each element in the sollection is a pair with as first a cpp in the \p from polygon and as second a cpp in the \p destination polygon.
|
||||
*/
|
||||
static std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> findClose(const PolygonRef from, const Polygons& destination, const SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator>& destination_loc_to_line, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
static std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> findClose(const PolygonRef from, const Polygons& destination, const LocToLineGrid& destination_loc_to_line, const std::function<int(Point)>& penalty_function = no_penalty_function);
|
||||
|
||||
/*!
|
||||
* Checks whether a given line segment collides with polygons as given in a loc_to_line grid.
|
||||
*
|
||||
* If the line segment doesn't intersect with any edge of the polygon, but
|
||||
* merely touches it, a collision is also reported. For instance, a
|
||||
* collision is reported when the an endpoint of the line is exactly on the
|
||||
* polygon, and when the line coincides with an edge.
|
||||
*
|
||||
* \param[in] from The start point
|
||||
* \param[in] to The end point
|
||||
* \param[in] loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segmetns of the \p polygons
|
||||
* \param[out] collision_result (optional) The polygons segment intersecting with the line segment
|
||||
* \return whether the line segment collides with the boundary of the polygons
|
||||
*/
|
||||
static bool polygonCollidesWithLineSegment(const Point from, const Point to, const LocToLineGrid& loc_to_line, PolygonsPointIndex* collision_result = nullptr);
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
@@ -366,7 +433,7 @@ public:
|
||||
* \return whether the line segment collides with the boundary of the
|
||||
* polygon(s)
|
||||
*/
|
||||
static bool polygonCollidesWithlineSegment(const PolygonRef poly, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix);
|
||||
static bool polygonCollidesWithLineSegment(const PolygonRef poly, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix);
|
||||
|
||||
/*!
|
||||
* Checks whether a given line segment collides with a given polygon(s).
|
||||
@@ -382,7 +449,7 @@ public:
|
||||
* \return whether the line segment collides with the boundary of the
|
||||
* polygon(s)
|
||||
*/
|
||||
static bool polygonCollidesWithlineSegment(const PolygonRef poly, Point& startPoint, Point& endPoint);
|
||||
static bool polygonCollidesWithLineSegment(const PolygonRef poly, Point& startPoint, Point& endPoint);
|
||||
|
||||
/*!
|
||||
* Checks whether a given line segment collides with a given polygon(s).
|
||||
@@ -404,7 +471,7 @@ public:
|
||||
* \return whether the line segment collides with the boundary of the
|
||||
* polygon(s)
|
||||
*/
|
||||
static bool polygonCollidesWithlineSegment(const Polygons& polys, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix);
|
||||
static bool polygonCollidesWithLineSegment(const Polygons& polys, Point& transformed_startPoint, Point& transformed_endPoint, PointMatrix transformation_matrix);
|
||||
|
||||
/*!
|
||||
* Checks whether a given line segment collides with a given polygon(s).
|
||||
@@ -420,7 +487,7 @@ public:
|
||||
* \return whether the line segment collides with the boundary of the
|
||||
* polygon(s)
|
||||
*/
|
||||
static bool polygonCollidesWithlineSegment(const Polygons& polys, Point& startPoint, Point& endPoint);
|
||||
static bool polygonCollidesWithLineSegment(const Polygons& polys, Point& startPoint, Point& endPoint);
|
||||
|
||||
private:
|
||||
/*!
|
||||
|
||||
+32
-6
@@ -5,6 +5,10 @@
|
||||
#include <cstdio> // sprintf
|
||||
#include <sstream> // ostringstream
|
||||
|
||||
#include <cinttypes> // PRId64
|
||||
|
||||
#include "logoutput.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
@@ -29,8 +33,19 @@ static inline int stringcasecompare(const char* a, const char* b)
|
||||
*/
|
||||
static inline void writeInt2mm(const int64_t coord, std::ostream& ss)
|
||||
{
|
||||
char buffer[24];
|
||||
int char_count = sprintf(buffer, "%ld", coord); // convert int to string
|
||||
constexpr size_t buffer_size = 24;
|
||||
char buffer[buffer_size];
|
||||
int char_count = sprintf(buffer, "%" PRId64, coord); // convert int to string
|
||||
#ifdef DEBUG
|
||||
if (char_count + 1 >= int(buffer_size)) // + 1 for the null character
|
||||
{
|
||||
logError("Cannot write %ld to buffer of size %i", coord, buffer_size);
|
||||
}
|
||||
if (char_count < 0)
|
||||
{
|
||||
logError("Encoding error while writing %ld", coord);
|
||||
}
|
||||
#endif // DEBUG
|
||||
int end_pos = char_count; // the first character not to write any more
|
||||
int trailing_zeros = 1;
|
||||
while (trailing_zeros < 4 && buffer[char_count - trailing_zeros] == '0')
|
||||
@@ -53,7 +68,7 @@ static inline void writeInt2mm(const int64_t coord, std::ostream& ss)
|
||||
ss << '-';
|
||||
start = 1;
|
||||
}
|
||||
ss << '.';
|
||||
ss << "0.";
|
||||
for (int nulls = char_count - start; nulls < 3; nulls++)
|
||||
{ // fill up to 3 decimals with zeros
|
||||
ss << '0';
|
||||
@@ -103,10 +118,21 @@ struct MMtoStream
|
||||
*/
|
||||
static inline void writeDoubleToStream(const unsigned int precision, const double coord, std::ostream& ss)
|
||||
{
|
||||
char format[5] = "%.xf"; // write a float with [x] digits after the dot
|
||||
char format[5] = "%.xF"; // write a float with [x] digits after the dot
|
||||
format[2] = '0' + precision; // set [x]
|
||||
char buffer[24];
|
||||
int char_count = snprintf(buffer, 24, format, coord);
|
||||
constexpr size_t buffer_size = 400;
|
||||
char buffer[buffer_size];
|
||||
int char_count = sprintf(buffer, format, coord);
|
||||
#ifdef DEBUG
|
||||
if (char_count + 1 >= int(buffer_size)) // + 1 for the null character
|
||||
{
|
||||
logError("Cannot write %f to buffer of size %i", coord, buffer_size);
|
||||
}
|
||||
if (char_count < 0)
|
||||
{
|
||||
logError("Encoding error while writing %f", coord);
|
||||
}
|
||||
#endif // DEBUG
|
||||
if (char_count <= 0)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -36,9 +36,9 @@ void GCodePlannerTest::setUp()
|
||||
fan_speed_layer_time_settings.cool_min_speed = 0.5;
|
||||
std::vector<FanSpeedLayerTimeSettings> fan_speed_layer_time_settings_per_extruder;
|
||||
fan_speed_layer_time_settings_per_extruder.push_back(fan_speed_layer_time_settings);
|
||||
// Slice layer z layer last current is inside fan speed and layer combing comb travel travel avoid
|
||||
// storage nr height position extruder mesh time settings mode offset avoid distance
|
||||
gCodePlanner = new GCodePlanner(*storage, 0, 0, 0.1, Point(0,0), 0, false, fan_speed_layer_time_settings_per_extruder, CombingMode::OFF, 100, false, 50 );
|
||||
// Slice layer z layer last current fan speed and layer combing comb travel travel avoid
|
||||
// storage nr height position extruder time settings mode offset avoid distance
|
||||
gCodePlanner = new GCodePlanner(*storage, 0, 0, 0.1, Point(0,0), 0, fan_speed_layer_time_settings_per_extruder, CombingMode::OFF, 100, false, 50 );
|
||||
}
|
||||
|
||||
void GCodePlannerTest::tearDown()
|
||||
|
||||
@@ -91,6 +91,27 @@ void PolygonTest::isOutsideTest()
|
||||
CPPUNIT_ASSERT_MESSAGE("Below point is calculated as inside while it's outside!", !test_triangle.inside(Point(100, -100)));
|
||||
}
|
||||
|
||||
void PolygonTest::isInsideTest()
|
||||
{
|
||||
Polygons test_polys;
|
||||
PolygonRef poly = test_polys.newPoly();
|
||||
poly.add(Point(82124,98235));
|
||||
poly.add(Point(83179,98691));
|
||||
poly.add(Point(83434,98950));
|
||||
poly.add(Point(82751,99026));
|
||||
poly.add(Point(82528,99019));
|
||||
poly.add(Point(81605,98854));
|
||||
poly.add(Point(80401,98686));
|
||||
poly.add(Point(79191,98595));
|
||||
poly.add(Point(78191,98441));
|
||||
poly.add(Point(78998,98299));
|
||||
poly.add(Point(79747,98179));
|
||||
poly.add(Point(80960,98095));
|
||||
|
||||
CPPUNIT_ASSERT_MESSAGE("Inside point is calculated as being outside!", test_polys.inside(Point(78315, 98440)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ class PolygonTest : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(polygonOffsetTest);
|
||||
CPPUNIT_TEST(polygonOffsetBugTest);
|
||||
CPPUNIT_TEST(isOutsideTest);
|
||||
CPPUNIT_TEST(isInsideTest);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -44,6 +45,7 @@ public:
|
||||
void polygonOffsetTest();
|
||||
void polygonOffsetBugTest();
|
||||
void isOutsideTest();
|
||||
void isInsideTest();
|
||||
|
||||
|
||||
private:
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
#include "StringTest.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream> // ostringstream
|
||||
#include <../src/utils/intpoint.h>
|
||||
#include <../src/utils/string.h>
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(StringTest);
|
||||
@@ -69,13 +71,25 @@ void StringTest::writeInt2mmTest123456789()
|
||||
{
|
||||
writeInt2mmAssert(123456789);
|
||||
}
|
||||
void StringTest::writeInt2mmTestMax()
|
||||
{
|
||||
writeInt2mmAssert(std::numeric_limits<int64_t>::max() / 1001); // divide by 1001, because MM2INT first converts to int and then multiplies by 1000, which causes overflow for the highest integer.
|
||||
}
|
||||
|
||||
|
||||
void StringTest::writeInt2mmAssert(int64_t in)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
writeInt2mm(in, ss);
|
||||
|
||||
ss.flush();
|
||||
std::string str = ss.str();
|
||||
if (!ss.good())
|
||||
{
|
||||
char buffer[200];
|
||||
sprintf(buffer, "The integer %ld was printed as '%s' which was a bad string!", in, str.c_str());
|
||||
CPPUNIT_ASSERT_MESSAGE(std::string(buffer), false);
|
||||
}
|
||||
int64_t out = MM2INT(strtod(str.c_str(), nullptr));
|
||||
|
||||
char buffer[200];
|
||||
@@ -84,4 +98,102 @@ void StringTest::writeInt2mmAssert(int64_t in)
|
||||
}
|
||||
|
||||
|
||||
void StringTest::writeDoubleToStreamTest10000Negative()
|
||||
{
|
||||
writeDoubleToStreamAssert(-10.000);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest1000Negative()
|
||||
{
|
||||
writeDoubleToStreamAssert(-1.000);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest100Negative()
|
||||
{
|
||||
writeDoubleToStreamAssert(-.100);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest10Negative()
|
||||
{
|
||||
writeDoubleToStreamAssert(-.010);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest1Negative()
|
||||
{
|
||||
writeDoubleToStreamAssert(-.001);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest0()
|
||||
{
|
||||
writeDoubleToStreamAssert(0.000);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest1()
|
||||
{
|
||||
writeDoubleToStreamAssert(.001);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest10()
|
||||
{
|
||||
writeDoubleToStreamAssert(.010);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest100()
|
||||
{
|
||||
writeDoubleToStreamAssert(.100);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest1000()
|
||||
{
|
||||
writeDoubleToStreamAssert(1.000);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest10000()
|
||||
{
|
||||
writeDoubleToStreamAssert(10.000);
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTest123456789()
|
||||
{
|
||||
writeDoubleToStreamAssert(123456.789);
|
||||
}
|
||||
|
||||
|
||||
void StringTest::writeDoubleToStreamTestMin()
|
||||
{
|
||||
writeDoubleToStreamAssert(std::numeric_limits<double>::min());
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTestMax()
|
||||
{
|
||||
writeDoubleToStreamAssert(std::numeric_limits<double>::max());
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTestLowest()
|
||||
{
|
||||
writeDoubleToStreamAssert(std::numeric_limits<double>::lowest());
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTestLowestNeg()
|
||||
{
|
||||
writeDoubleToStreamAssert(-std::numeric_limits<double>::lowest());
|
||||
}
|
||||
void StringTest::writeDoubleToStreamTestLow()
|
||||
{
|
||||
writeDoubleToStreamAssert(0.00000001d);
|
||||
}
|
||||
|
||||
|
||||
void StringTest::writeDoubleToStreamAssert(double in, unsigned int precision)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
writeDoubleToStream(precision, in, ss);
|
||||
ss.flush();
|
||||
std::string str = ss.str();
|
||||
if (!ss.good())
|
||||
{
|
||||
char buffer[8000];
|
||||
sprintf(buffer, "The double %f was printed as '%s' which was a bad string!", in, str.c_str());
|
||||
CPPUNIT_ASSERT_MESSAGE(std::string(buffer), false);
|
||||
}
|
||||
double out = strtod(str.c_str(), nullptr);
|
||||
|
||||
std::ostringstream in_ss;
|
||||
in_ss << std::fixed << std::setprecision(precision) << in;
|
||||
std::string in_str = in_ss.str();
|
||||
double in_reinterpreted = strtod(in_str.c_str(), nullptr);
|
||||
|
||||
char buffer[8000];
|
||||
sprintf(buffer, "The double %f was printed as '%s' which was interpreted as %f rather than %f!", in, str.c_str(), out, in_reinterpreted);
|
||||
if (in_reinterpreted != out) std::cerr << buffer << "\n";
|
||||
CPPUNIT_ASSERT_MESSAGE(std::string(buffer), in_reinterpreted == out);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -25,6 +25,25 @@ class StringTest : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(writeInt2mmTest1000);
|
||||
CPPUNIT_TEST(writeInt2mmTest10000);
|
||||
CPPUNIT_TEST(writeInt2mmTest123456789);
|
||||
CPPUNIT_TEST(writeInt2mmTestMax);
|
||||
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest10000Negative);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest1000Negative);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest100Negative);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest10Negative);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest1Negative);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest0);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest1);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest10);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest100);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest1000);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest10000);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTest123456789);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTestMin);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTestMax);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTestLowest);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTestLowestNeg);
|
||||
CPPUNIT_TEST(writeDoubleToStreamTestLow);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -57,6 +76,25 @@ public:
|
||||
void writeInt2mmTest1000();
|
||||
void writeInt2mmTest10000();
|
||||
void writeInt2mmTest123456789();
|
||||
void writeInt2mmTestMax();
|
||||
|
||||
void writeDoubleToStreamTest10000Negative();
|
||||
void writeDoubleToStreamTest1000Negative();
|
||||
void writeDoubleToStreamTest100Negative();
|
||||
void writeDoubleToStreamTest10Negative();
|
||||
void writeDoubleToStreamTest1Negative();
|
||||
void writeDoubleToStreamTest0();
|
||||
void writeDoubleToStreamTest1();
|
||||
void writeDoubleToStreamTest10();
|
||||
void writeDoubleToStreamTest100();
|
||||
void writeDoubleToStreamTest1000();
|
||||
void writeDoubleToStreamTest10000();
|
||||
void writeDoubleToStreamTest123456789();
|
||||
void writeDoubleToStreamTestMin();
|
||||
void writeDoubleToStreamTestMax();
|
||||
void writeDoubleToStreamTestLowest();
|
||||
void writeDoubleToStreamTestLowestNeg();
|
||||
void writeDoubleToStreamTestLow();
|
||||
|
||||
private:
|
||||
|
||||
@@ -69,6 +107,17 @@ private:
|
||||
* \param in the integer to check
|
||||
*/
|
||||
void writeInt2mmAssert(int64_t in);
|
||||
|
||||
/*!
|
||||
* \brief Performs the actual assertion for the getDist2FromLineSegmentTest.
|
||||
*
|
||||
* This is essentially a parameterised version of all unit tests pertaining
|
||||
* to the writeInt2mm tests.
|
||||
*
|
||||
* \param in the double to check
|
||||
* \param precision the (maximum) number of digits after the decimal mark to print
|
||||
*/
|
||||
void writeDoubleToStreamAssert(double in, unsigned int precision = 4);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário