Comparar commits
207 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 6c931e2935 | |||
| 295a5a9fc4 | |||
| cc8dadf571 | |||
| 118e71c7af | |||
| b5d29a66b2 | |||
| e937e98b27 | |||
| c3705604cd | |||
| e1993a8ccd | |||
| b819f696eb | |||
| b1b37cf19b | |||
| 9808be9dfa | |||
| 06e8848ee2 | |||
| c43d5ccbf3 | |||
| e3e4ccdba8 | |||
| e0eacf0675 | |||
| ad1489e0ee | |||
| 3efe717154 | |||
| 17bd906318 | |||
| b8cdf0da24 | |||
| 801641823c | |||
| ac25e429b8 | |||
| 54e4b2d44e | |||
| 573699fd07 | |||
| 304a84a25c | |||
| 0e6412289c | |||
| e5901df8e1 | |||
| e2f96fb1ad | |||
| 190908ab27 | |||
| ff775d12f2 | |||
| a2fd4c0bf3 | |||
| 404edc5cdc | |||
| de4edc36bb | |||
| 1623e58063 | |||
| bb1c00f255 | |||
| 773e152454 | |||
| 9c59eff999 | |||
| b21b7e3115 | |||
| 36b09753af | |||
| 71b466a933 | |||
| f0141230b2 | |||
| d4631e3f69 | |||
| 64aa1bcd97 | |||
| d34e168562 | |||
| 8f7afd75da | |||
| 0380fd183d | |||
| c114904828 | |||
| 9c08d3dd82 | |||
| f3dbde8e7f | |||
| 7b0f4d204b | |||
| 79a2dc0a72 | |||
| c97bcdf005 | |||
| 03677b8ac7 | |||
| 9db6352281 | |||
| 3da8607afd | |||
| 4f248088ce | |||
| d08580f969 | |||
| a3652a4fc4 | |||
| 49426a0417 | |||
| 01163bf581 | |||
| b96bdf41b5 | |||
| 943c41ed7b | |||
| a6cd192156 | |||
| 06c82f3426 | |||
| eb06f19f11 | |||
| 4fc471ecf9 | |||
| ff018d40c0 | |||
| cc9199ee50 | |||
| 7bcef0ad97 | |||
| f5f9595627 | |||
| d2f2417012 | |||
| 5299c76521 | |||
| 80f976d1f3 | |||
| b117e64288 | |||
| ca41665c3e | |||
| b94c95149a | |||
| 5b95b7a557 | |||
| bdc31425ba | |||
| f001f31e78 | |||
| 6d56e2c622 | |||
| c983a7e83b | |||
| 86d0ce4e6b | |||
| f79933b140 | |||
| a47716161a | |||
| 831f5ea16e | |||
| 219d400e62 | |||
| 2b8c92cba9 | |||
| 44c5c11160 | |||
| 1f5cdf5d6b | |||
| 7607992eb6 | |||
| 2461073d64 | |||
| 3a43fe2885 | |||
| 9e2d5217d0 | |||
| 353e151c6b | |||
| 6826581497 | |||
| 4fead4612b | |||
| f763edfb05 | |||
| 0e662d7d67 | |||
| 94d8c3ff32 | |||
| 8e91753afc | |||
| 21e59cc1e2 | |||
| 14b1c5333a | |||
| bbe809dabc | |||
| ed581a92b2 | |||
| 9b68305851 | |||
| 940d3a86bd | |||
| 55047120d8 | |||
| e50b00fd73 | |||
| 7377f30fd0 | |||
| c9245e0926 | |||
| 7149b7dbf1 | |||
| daa3927a99 | |||
| 074d1125e6 | |||
| a92274678d | |||
| 081393d7f0 | |||
| 30da3097d0 | |||
| d830fc515b | |||
| 5f2d924771 | |||
| b1ab1cae5a | |||
| 1fb6b20d90 | |||
| dfb4c98e35 | |||
| e455d63ad2 | |||
| cb024c73b1 | |||
| d2d25058cb | |||
| e85362370b | |||
| 00c6f5c092 | |||
| 818c7da951 | |||
| 41723c8b38 | |||
| c8d75dd913 | |||
| faa60c408f | |||
| 7e322bc57c | |||
| 888fc54660 | |||
| fd660fcc11 | |||
| e8080422a4 | |||
| 5fecf2cd17 | |||
| b4bf17c6be | |||
| fabd658b53 | |||
| a2050de513 | |||
| 9bfb5b17e9 | |||
| e93d39d6d5 | |||
| 175a65415a | |||
| 74986dd95c | |||
| 55dd35ae5e | |||
| 8caf810642 | |||
| 76efc41407 | |||
| b0a0fe8f30 | |||
| 653b631ffd | |||
| b9a01ed031 | |||
| de9d3bb447 | |||
| 8f5a46f77d | |||
| c22793b5d7 | |||
| 1e684f23a7 | |||
| 7d0025975b | |||
| 5c892b564b | |||
| d5327ec3f1 | |||
| c48104bc86 | |||
| f58f1daec3 | |||
| 669bb523a3 | |||
| fa74f672ac | |||
| e224d9c853 | |||
| 338d80a7a9 | |||
| 87123dac31 | |||
| 73de288f44 | |||
| 9f5b5f405d | |||
| 2850b34227 | |||
| 072d320f1d | |||
| 8e4b61d2a5 | |||
| ba4e26f801 | |||
| cea0a0a98f | |||
| 2b51a11739 | |||
| d32443ca9b | |||
| 792aa9e8e8 | |||
| a63abb4e88 | |||
| 9f82f58f03 | |||
| df04467ab1 | |||
| bbd9412b5f | |||
| c476acd522 | |||
| 278efdeb39 | |||
| bb8a9dacba | |||
| 42420edeed | |||
| be2e96f3fd | |||
| 18adc0bbc9 | |||
| e7824faefe | |||
| 7199be1b4f | |||
| 727c863f1a | |||
| d732c49dd7 | |||
| 781fc5ed7b | |||
| 7243cf6da4 | |||
| 1cdcce4205 | |||
| acf381c008 | |||
| 4b7df9ddc0 | |||
| 7788a4a234 | |||
| 6224713998 | |||
| db8b30d77a | |||
| 050b9c88f2 | |||
| 8ac63fca6e | |||
| 1383882bc5 | |||
| 3a773d3c0f | |||
| 734ddce3c8 | |||
| 0b19936299 | |||
| 691d5de591 | |||
| 490cef1a5c | |||
| b777b55935 | |||
| 0428c08152 | |||
| 20c74dd22d | |||
| 188b190d21 | |||
| 1cd128decd | |||
| 925247a54d |
+15
-17
@@ -2,14 +2,7 @@ project(CuraEngine)
|
||||
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
|
||||
option (ENABLE_ARCUS
|
||||
"Enable support for ARCUS" ON)
|
||||
|
||||
if (ENABLE_ARCUS)
|
||||
message(STATUS "Building with Arcus")
|
||||
find_package(Arcus REQUIRED)
|
||||
add_definitions(-DARCUS)
|
||||
endif ()
|
||||
find_package(Arcus REQUIRED)
|
||||
|
||||
if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
@@ -17,6 +10,10 @@ else()
|
||||
set(CMAKE_CXX_FLAGS "-std=c++11")
|
||||
endif()
|
||||
|
||||
if(APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++")
|
||||
endif()
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
set(CURA_ENGINE_VERSION "master" CACHE STRING "Version name of Cura")
|
||||
@@ -73,6 +70,12 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/Weaver.cpp
|
||||
src/Wireframe2gcode.cpp
|
||||
|
||||
src/infill/NoZigZagConnectorProcessor.cpp
|
||||
src/infill/ZigzagConnectorProcessorConnectedEndPieces.cpp
|
||||
src/infill/ZigzagConnectorProcessorDisconnectedEndPieces.cpp
|
||||
src/infill/ZigzagConnectorProcessorEndPieces.cpp
|
||||
src/infill/ZigzagConnectorProcessorNoEndPieces.cpp
|
||||
|
||||
src/utils/gettime.cpp
|
||||
src/utils/logoutput.cpp
|
||||
src/utils/polygonUtils.cpp
|
||||
@@ -83,19 +86,15 @@ set(engine_SRCS # Except main.cpp.
|
||||
set(engine_TEST
|
||||
GCodePlannerTest
|
||||
LinearAlg2DTest
|
||||
TravellingSalesmanTest
|
||||
)
|
||||
|
||||
# Generating ProtoBuf protocol
|
||||
if (ENABLE_ARCUS)
|
||||
# Generating ProtoBuf protocol.
|
||||
protobuf_generate_cpp(engine_PB_SRCS engine_PB_HEADERS Cura.proto)
|
||||
endif ()
|
||||
|
||||
# Compiling CuraEngine itself.
|
||||
add_library(_CuraEngine ${engine_SRCS} ${engine_PB_SRCS}) #First compile all of CuraEngine as library, allowing this to be re-used for tests.
|
||||
target_link_libraries(_CuraEngine clipper)
|
||||
if (ENABLE_ARCUS)
|
||||
target_link_libraries(_CuraEngine Arcus)
|
||||
endif ()
|
||||
target_link_libraries(_CuraEngine clipper Arcus)
|
||||
|
||||
set_target_properties(_CuraEngine PROPERTIES COMPILE_DEFINITIONS "VERSION=\"${CURA_ENGINE_VERSION}\"")
|
||||
|
||||
@@ -119,5 +118,4 @@ endif()
|
||||
# Installing CuraEngine.
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS CuraEngine DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
include(CPackConfig.cmake)
|
||||
|
||||
include(CPackConfig.cmake)
|
||||
@@ -98,3 +98,7 @@ message Setting {
|
||||
message GCodePrefix {
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
// typeid 8
|
||||
message SlicingFinished {
|
||||
}
|
||||
|
||||
+103
-164
@@ -21,8 +21,8 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
|
||||
gcode.resetTotalPrintTimeAndFilament();
|
||||
}
|
||||
|
||||
if (command_socket)
|
||||
command_socket->beginGCode();
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->beginGCode();
|
||||
|
||||
setConfigFanSpeedLayerTime();
|
||||
|
||||
@@ -72,17 +72,11 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
|
||||
processLayer(storage, layer_nr, total_layers, has_raft);
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::FINISH, &time_keeper, command_socket);
|
||||
Progress::messageProgressStage(Progress::Stage::FINISH, &time_keeper);
|
||||
|
||||
//Store the object height for when we are printing multiple objects, as we need to clear every one of them when moving to the next position.
|
||||
max_object_height = std::max(max_object_height, storage.model_max.z);
|
||||
|
||||
if (command_socket)
|
||||
{
|
||||
command_socket->sendGCodeLayer();
|
||||
command_socket->endSendSlicedObject();
|
||||
}
|
||||
|
||||
|
||||
layer_plan_buffer.flush();
|
||||
}
|
||||
|
||||
@@ -104,12 +98,9 @@ void FffGcodeWriter::setConfigCoasting(SliceDataStorage& storage)
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extr);
|
||||
CoastingConfig& coasting_config = storage.coasting_config.back();
|
||||
coasting_config.coasting_enable = train->getSettingBoolean("coasting_enable");
|
||||
coasting_config.coasting_volume_move = train->getSettingInCubicMillimeters("coasting_volume_move");
|
||||
coasting_config.coasting_min_volume_move = train->getSettingInCubicMillimeters("coasting_min_volume_move");
|
||||
coasting_config.coasting_speed_move = train->getSettingInPercentage("coasting_speed_move") / 100.0;
|
||||
coasting_config.coasting_volume_retract = train->getSettingInCubicMillimeters("coasting_volume_retract");
|
||||
coasting_config.coasting_min_volume_retract = train->getSettingInCubicMillimeters("coasting_min_volume_retract");
|
||||
coasting_config.coasting_speed_retract = train->getSettingInPercentage("coasting_speed_retract") / 100.0;
|
||||
coasting_config.coasting_volume = train->getSettingInCubicMillimeters("coasting_volume");
|
||||
coasting_config.coasting_min_volume = train->getSettingInCubicMillimeters("coasting_min_volume");
|
||||
coasting_config.coasting_speed = train->getSettingInPercentage("coasting_speed") / 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,8 +153,8 @@ void FffGcodeWriter::initConfigs(SliceDataStorage& storage)
|
||||
}
|
||||
|
||||
{ // support
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_infill_extruder_nr"));
|
||||
storage.support_config.init(getSettingInMillimetersPerSecond("speed_support_infill"), getSettingInMicrons("support_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("support_extruder_nr"));
|
||||
storage.support_config.init(getSettingInMillimetersPerSecond("speed_support_lines"), getSettingInMicrons("support_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
|
||||
storage.support_roof_config.init(getSettingInMillimetersPerSecond("speed_support_roof"), getSettingInMicrons("support_roof_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
}
|
||||
@@ -173,20 +164,19 @@ void FffGcodeWriter::initConfigs(SliceDataStorage& storage)
|
||||
mesh.inset0_config.init(mesh.getSettingInMillimetersPerSecond("speed_wall_0"), mesh.getSettingInMicrons("wall_line_width_0"), mesh.getSettingInPercentage("material_flow"));
|
||||
mesh.insetX_config.init(mesh.getSettingInMillimetersPerSecond("speed_wall_x"), mesh.getSettingInMicrons("wall_line_width_x"), mesh.getSettingInPercentage("material_flow"));
|
||||
mesh.skin_config.init(mesh.getSettingInMillimetersPerSecond("speed_topbottom"), mesh.getSettingInMicrons("skin_line_width"), mesh.getSettingInPercentage("material_flow"));
|
||||
mesh.wall_reinforcement_config.init(mesh.getSettingInMillimetersPerSecond("speed_wall_reinforcement"), mesh.getSettingInMicrons("wall_reinforcement_line_width"), mesh.getSettingInPercentage("material_flow"));
|
||||
|
||||
|
||||
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
|
||||
{
|
||||
mesh.infill_config[idx].init(mesh.getSettingInMillimetersPerSecond("speed_infill"), mesh.getSettingInMicrons("infill_line_width") * (idx + 1), mesh.getSettingInPercentage("material_flow"));
|
||||
}
|
||||
mesh.wall_reinforcement_config.init(mesh.getSettingInMillimetersPerSecond("speed_wall_reinforcement"), mesh.getSettingInMicrons("wall_reinforcement_line_width"), mesh.getSettingInPercentage("material_flow"));
|
||||
}
|
||||
|
||||
storage.primeTower.initConfigs(storage.meshgroup, storage.retraction_config_per_extruder);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
{
|
||||
if (!command_socket)
|
||||
if (!CommandSocket::isInstantiated())
|
||||
{
|
||||
std::ostringstream prefix;
|
||||
prefix << "FLAVOR:" << toString(gcode.getFlavor());
|
||||
@@ -273,25 +263,34 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
storage.raft_surface_config.setLayerHeight(train->getSettingInMicrons("raft_surface_thickness"));
|
||||
}
|
||||
|
||||
// some infill config for all lines infill generation below
|
||||
Polygons* in_between = nullptr;
|
||||
int offset_from_poly_outline = 0;
|
||||
bool avoidOverlappingPerimeters = false;
|
||||
double fill_overlap = 0; // raft line shouldn't be expanded - there is no boundary polygon printed
|
||||
Polygons raft_polygons; // should remain empty, since we only have the lines pattern for the raft...
|
||||
|
||||
{ // raft base layer
|
||||
|
||||
int layer_nr = -n_raft_surface_layers - 2;
|
||||
int layer_height = getSettingInMicrons("raft_base_thickness");
|
||||
z += layer_height;
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(command_socket, storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
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, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") > 0)
|
||||
gcode_layer.setExtruder(extruder_nr);
|
||||
if (command_socket)
|
||||
command_socket->sendLayerInfo(layer_nr, z, layer_height);
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
|
||||
}
|
||||
gcode_layer.addPolygonsByOptimizer(storage.raftOutline, &storage.raft_base_config);
|
||||
|
||||
Polygons raftLines;
|
||||
int offset_from_poly_outline = 0;
|
||||
generateLineInfill(storage.raftOutline, offset_from_poly_outline, raftLines, storage.raft_base_config.getLineWidth(), train->getSettingInMicrons("raft_base_line_spacing"), train->getSettingInPercentage("infill_overlap"), 0);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_base_config);
|
||||
sendPolygons(SupportType, layer_nr, raftLines, storage.raft_base_config.getLineWidth());
|
||||
double fill_angle = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_base_config.getLineWidth(), train->getSettingInMicrons("raft_base_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raftLines, in_between);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_base_config, SpaceFillType::Lines);
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
@@ -304,17 +303,18 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
int layer_nr = -n_raft_surface_layers - 1;
|
||||
int layer_height = train->getSettingInMicrons("raft_interface_thickness");
|
||||
z += layer_height;
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(command_socket, storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
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, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
if (command_socket)
|
||||
command_socket->sendLayerInfo(layer_nr, z, layer_height);
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
|
||||
|
||||
Polygons raftLines;
|
||||
int offset_from_poly_outline = 0;
|
||||
generateLineInfill(storage.raftOutline, offset_from_poly_outline, raftLines, storage.raft_interface_config.getLineWidth(), train->getSettingInMicrons("raft_interface_line_spacing"), train->getSettingInPercentage("infill_overlap"), train->getSettingAsCount("raft_surface_layers") > 0 ? 45 : 90);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_interface_config);
|
||||
sendPolygons(SupportType, layer_nr, raftLines, storage.raft_interface_config.getLineWidth());
|
||||
double fill_angle = train->getSettingAsCount("raft_surface_layers") > 0 ? 45 : 90;
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_interface_config.getLineWidth(), train->getSettingInMicrons("raft_interface_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raftLines, in_between);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_interface_config, SpaceFillType::Lines);
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
@@ -329,17 +329,20 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
{ // raft surface layers
|
||||
int layer_nr = -n_raft_surface_layers + raftSurfaceLayer - 1;
|
||||
z += layer_height;
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(command_socket, storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
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, retraction_combing, train->getSettingInMicrons("machine_nozzle_size"), train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
if (command_socket)
|
||||
command_socket->sendLayerInfo(layer_nr, z, layer_height);
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
|
||||
}
|
||||
|
||||
Polygons raft_lines;
|
||||
int offset_from_poly_outline = 0;
|
||||
generateLineInfill(storage.raftOutline, offset_from_poly_outline, raft_lines, storage.raft_surface_config.getLineWidth(), train->getSettingInMicrons("raft_surface_line_spacing"), train->getSettingInPercentage("infill_overlap"), 90 * raftSurfaceLayer);
|
||||
gcode_layer.addLinesByOptimizer(raft_lines, &storage.raft_surface_config);
|
||||
sendPolygons(SupportType, layer_nr, raft_lines, storage.raft_surface_config.getLineWidth());
|
||||
double fill_angle = 90 * raftSurfaceLayer;
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_surface_config.getLineWidth(), train->getSettingInMicrons("raft_surface_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raft_lines, in_between);
|
||||
gcode_layer.addLinesByOptimizer(raft_lines, &storage.raft_surface_config, SpaceFillType::Lines);
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
@@ -351,7 +354,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
|
||||
void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_nr, unsigned int total_layers, bool has_raft)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::EXPORT, layer_nr+1, total_layers, command_socket);
|
||||
Progress::messageProgress(Progress::Stage::EXPORT, layer_nr+1, total_layers);
|
||||
|
||||
int layer_thickness = getSettingInMicrons("layer_height");
|
||||
if (layer_nr == 0)
|
||||
@@ -359,18 +362,9 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_
|
||||
layer_thickness = getSettingInMicrons("layer_height_0");
|
||||
}
|
||||
|
||||
int max_nozzle_size = 0;
|
||||
std::vector<bool> extruders_used = storage.getExtrudersUsed(layer_nr);
|
||||
for (int extr_nr = 0; extr_nr < storage.meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (extruders_used[extr_nr])
|
||||
{
|
||||
max_nozzle_size = std::max(max_nozzle_size, storage.meshgroup->getExtruderTrain(extr_nr)->getSettingInMicrons("machine_nozzle_size"));
|
||||
}
|
||||
}
|
||||
int64_t comb_offset_from_outlines = max_nozzle_size * 2;// TODO: only used when there is no second wall.
|
||||
int64_t comb_offset_from_outlines = storage.meshgroup->getExtruderTrain(current_extruder_planned)->getSettingInMicrons("machine_nozzle_size") * 2; // TODO: only used when there is no second wall.
|
||||
int64_t z = storage.meshes[0].layers[layer_nr].printZ;
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(command_socket, storage, layer_nr, z, layer_thickness, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, getSettingBoolean("retraction_combing"), comb_offset_from_outlines, getSettingBoolean("travel_avoid_other_parts"), getSettingInMicrons("travel_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, getSettingBoolean("retraction_combing"), comb_offset_from_outlines, getSettingBoolean("travel_avoid_other_parts"), getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
@@ -446,21 +440,20 @@ void FffGcodeWriter::processDraftShield(SliceDataStorage& storage, GCodePlanner&
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int draft_shield_height = getSettingInMicrons("draft_shield_height");
|
||||
int layer_height_0 = getSettingInMicrons("layer_height_0");
|
||||
int layer_height = getSettingInMicrons("layer_height");
|
||||
|
||||
|
||||
int max_screen_layer = (draft_shield_height - layer_height_0) / layer_height + 1;
|
||||
|
||||
|
||||
if (int(layer_nr) > max_screen_layer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, &storage.skirt_config[0]); // TODO: skirt config idx should correspond to draft shield extruder nr
|
||||
|
||||
}
|
||||
|
||||
std::vector<unsigned int> FffGcodeWriter::calculateMeshOrder(SliceDataStorage& storage, int current_extruder)
|
||||
@@ -531,7 +524,7 @@ void FffGcodeWriter::addMeshOpenPolyLinesToGCode(SliceDataStorage& storage, Slic
|
||||
lines.add(p);
|
||||
}
|
||||
}
|
||||
gcode_layer.addLinesByOptimizer(lines, &mesh->inset0_config);
|
||||
gcode_layer.addLinesByOptimizer(lines, &mesh->inset0_config, SpaceFillType::PolyLines);
|
||||
|
||||
}
|
||||
|
||||
@@ -577,28 +570,16 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
int infill_line_distance = mesh->getSettingInMicrons("infill_line_distance");
|
||||
double infill_overlap = mesh->getSettingInPercentage("infill_overlap");
|
||||
|
||||
int wall_reinforcement_line_distance = mesh->getSettingInMicrons("wall_reinforcement_line_distance");
|
||||
int wall_reinforcement_line_width = mesh->wall_reinforcement_config.getLineWidth();
|
||||
|
||||
if (mesh->getSettingBoolean("infill_before_walls"))
|
||||
{
|
||||
processMultiLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, infill_line_width);
|
||||
processSingleLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, infill_line_width);
|
||||
for (unsigned int wall_idx = part.reinforcement_walls.size() - 1; int(wall_idx) >= 0; wall_idx--)
|
||||
{
|
||||
ReinforcementWall& reinforcement_wall = part.reinforcement_walls[wall_idx];
|
||||
processWallReinforcement(gcode_layer, mesh, reinforcement_wall, layer_nr, wall_reinforcement_line_distance, infill_overlap, infill_angle, wall_reinforcement_line_width, true);
|
||||
}
|
||||
}
|
||||
|
||||
processInsets(gcode_layer, mesh, part, layer_nr, z_seam_type);
|
||||
|
||||
if (!mesh->getSettingBoolean("infill_before_walls"))
|
||||
{
|
||||
for (ReinforcementWall& reinforcement_wall : part.reinforcement_walls)
|
||||
{
|
||||
processWallReinforcement(gcode_layer, mesh, reinforcement_wall, layer_nr, wall_reinforcement_line_distance, infill_overlap, infill_angle, wall_reinforcement_line_width, false);
|
||||
}
|
||||
processMultiLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, infill_line_width);
|
||||
processSingleLayerInfill(gcode_layer, mesh, part, layer_nr, infill_line_distance, infill_overlap, infill_angle, infill_line_width);
|
||||
}
|
||||
@@ -636,13 +617,13 @@ void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMes
|
||||
//Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers)
|
||||
for(unsigned int n=1; n<part.infill_area.size(); n++)
|
||||
{
|
||||
Infill infill_comp(mesh->getSettingAsFillMethod("infill_pattern"), part.infill_area[n], 0, false, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
|
||||
EFillMethod infill_pattern = mesh->getSettingAsFillMethod("infill_pattern");
|
||||
Infill infill_comp(infill_pattern, part.infill_area[n], 0, false, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
|
||||
Polygons infill_polygons;
|
||||
Polygons infill_lines;
|
||||
infill_comp.generate(infill_polygons, infill_lines, nullptr);
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[n]);
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[n]);
|
||||
sendPolygons(InfillType, layer_nr, infill_lines, extrusion_width);
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[n], (infill_pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -665,61 +646,12 @@ void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMe
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[0]);
|
||||
if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[0], mesh->getSettingInMicrons("infill_wipe_dist"));
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[0], SpaceFillType::Lines, mesh->getSettingInMicrons("infill_wipe_dist"));
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[0]);
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[0], (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
sendPolygons(InfillType, layer_nr, infill_lines, extrusion_width);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processWallReinforcement(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, ReinforcementWall& reinforcement_wall, unsigned int layer_nr, int wall_reinforcement_line_distance, double infill_overlap, int infill_angle, int wall_reinforcement_line_width, bool inside_out)
|
||||
{
|
||||
if (wall_reinforcement_line_distance == 0 || (reinforcement_wall.wall_reinforcement_area.size() == 0 && reinforcement_wall.wall_reinforcement_axtra_walls.size() == 0) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (inside_out)
|
||||
{
|
||||
processWallReinforcement_extraWalls(gcode_layer, mesh, reinforcement_wall, layer_nr, wall_reinforcement_line_width, inside_out);
|
||||
}
|
||||
|
||||
processWallReinforcement_infill(gcode_layer, mesh, reinforcement_wall, layer_nr, wall_reinforcement_line_distance, infill_overlap, infill_angle, wall_reinforcement_line_width);
|
||||
|
||||
if (!inside_out)
|
||||
{
|
||||
processWallReinforcement_extraWalls(gcode_layer, mesh, reinforcement_wall, layer_nr, wall_reinforcement_line_width, inside_out);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processWallReinforcement_extraWalls(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, ReinforcementWall& reinforcement_wall, unsigned int layer_nr, int wall_reinforcement_line_width, bool inside_out)
|
||||
{
|
||||
if (reinforcement_wall.wall_reinforcement_axtra_walls.size() > 0)
|
||||
{
|
||||
for(int inset_number=reinforcement_wall.wall_reinforcement_axtra_walls.size()-1; inset_number>-1; inset_number--)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(reinforcement_wall.wall_reinforcement_axtra_walls[inset_number], &mesh->insetX_config);
|
||||
}
|
||||
}
|
||||
}
|
||||
void FffGcodeWriter::processWallReinforcement_infill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, ReinforcementWall& reinforcement_wall, unsigned int layer_nr, int wall_reinforcement_line_distance, double infill_overlap, int infill_angle, int wall_reinforcement_line_width)
|
||||
{
|
||||
if (reinforcement_wall.wall_reinforcement_area.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Polygons infill_polygons;
|
||||
Polygons infill_lines;
|
||||
|
||||
EFillMethod pattern = mesh->getSettingAsFillMethod("wall_reinforcement_pattern");
|
||||
Infill infill_comp(pattern, reinforcement_wall.wall_reinforcement_area, 0, false, wall_reinforcement_line_width, wall_reinforcement_line_distance, infill_overlap, infill_angle, false, false);
|
||||
infill_comp.generate(infill_polygons, infill_lines, nullptr);
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->wall_reinforcement_config);
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->wall_reinforcement_config);
|
||||
sendPolygons(SupportInfillType, layer_nr, infill_lines, wall_reinforcement_line_width);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processInsets(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, EZSeamType z_seam_type)
|
||||
@@ -775,15 +707,10 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
}
|
||||
Polygons* inner_skin_outline = nullptr;
|
||||
int offset_from_inner_skin_outline = 0;
|
||||
if (pattern == EFillMethod::CONCENTRIC)
|
||||
{
|
||||
offset_from_inner_skin_outline = -extrusion_width/2;
|
||||
}
|
||||
else
|
||||
if (pattern != EFillMethod::CONCENTRIC)
|
||||
{
|
||||
for (Polygons& skin_perimeter : skin_part.insets)
|
||||
{
|
||||
sendPolygons(SkinType, layer_nr, skin_perimeter, mesh->skin_config.getLineWidth());
|
||||
gcode_layer.addPolygonsByOptimizer(skin_perimeter, &mesh->skin_config); // add polygons to gcode in inward order
|
||||
}
|
||||
if (skin_part.insets.size() > 0)
|
||||
@@ -792,7 +719,13 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
offset_from_inner_skin_outline = -extrusion_width/2;
|
||||
if (mesh->getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") != FillPerimeterGapMode::NOWHERE)
|
||||
{
|
||||
generateLineInfill(skin_part.perimeterGaps, 0, skin_lines, extrusion_width, extrusion_width, 0, infill_angle);
|
||||
Polygons result_polygons; // should remain empty, since we're only allowing for lines infill
|
||||
Polygons* in_between = nullptr;
|
||||
bool avoidOverlappingPerimeters = false;
|
||||
int line_distance = extrusion_width;
|
||||
int outline_offset = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, skin_part.perimeterGaps, outline_offset, avoidOverlappingPerimeters, extrusion_width, line_distance, infill_overlap, infill_angle);
|
||||
infill_comp.generate(result_polygons, skin_lines, in_between);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -806,17 +739,22 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
infill_comp.generate(skin_polygons, skin_lines, &part.perimeterGaps);
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(skin_polygons, &mesh->skin_config);
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config);
|
||||
sendPolygons(SkinType, layer_nr, skin_polygons, mesh->skin_config.getLineWidth());
|
||||
sendPolygons(SkinType, layer_nr, skin_lines, mesh->skin_config.getLineWidth());
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
|
||||
// handle gaps between perimeters etc.
|
||||
if (mesh->getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") != FillPerimeterGapMode::NOWHERE)
|
||||
{
|
||||
Polygons perimeter_gap_lines;
|
||||
generateLineInfill(part.perimeterGaps, 0, perimeter_gap_lines, extrusion_width, extrusion_width, 0, infill_angle);
|
||||
gcode_layer.addLinesByOptimizer(perimeter_gap_lines, &mesh->skin_config);
|
||||
Polygons result_polygons; // should remain empty, since we're only allowing for lines infill
|
||||
Polygons* in_between = nullptr;
|
||||
bool avoidOverlappingPerimeters = false;
|
||||
int line_distance = extrusion_width;
|
||||
int outline_offset = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, part.perimeterGaps, outline_offset, avoidOverlappingPerimeters, extrusion_width, line_distance, infill_overlap, infill_angle);
|
||||
infill_comp.generate(result_polygons, perimeter_gap_lines, in_between);
|
||||
|
||||
gcode_layer.addLinesByOptimizer(perimeter_gap_lines, &mesh->skin_config, SpaceFillType::Lines);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,9 +764,9 @@ void FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner&
|
||||
return;
|
||||
|
||||
int support_roof_extruder_nr = getSettingAsIndex("support_roof_extruder_nr");
|
||||
int support_infill_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
int support_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_extruder_nr");
|
||||
|
||||
bool print_support_before_rest = support_infill_extruder_nr == extruder_nr_before
|
||||
bool print_support_before_rest = support_extruder_nr == extruder_nr_before
|
||||
|| support_roof_extruder_nr == extruder_nr_before;
|
||||
// TODO: always print support after rest when only one nozzle is used for the whole meshgroup
|
||||
|
||||
@@ -841,24 +779,24 @@ void FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner&
|
||||
|
||||
if (storage.support.supportLayers[layer_nr].roofs.size() > 0)
|
||||
{
|
||||
if (support_roof_extruder_nr != support_infill_extruder_nr && support_roof_extruder_nr == current_extruder_nr)
|
||||
if (support_roof_extruder_nr != support_extruder_nr && support_roof_extruder_nr == current_extruder_nr)
|
||||
{
|
||||
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportRoofsToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addSupportInfillToGCode(storage, gcode_layer, layer_nr);
|
||||
addSupportLinesToGCode(storage, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
|
||||
void FffGcodeWriter::addSupportLinesToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr)
|
||||
{
|
||||
if (!storage.support.generated
|
||||
|| layer_nr > storage.support.layer_nr_max_filled_layer
|
||||
@@ -866,17 +804,16 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int support_line_distance = getSettingInMicrons("support_line_distance");
|
||||
int extrusion_width = storage.support_config.getLineWidth();
|
||||
EFillMethod support_pattern = getSettingAsFillMethod("support_pattern");
|
||||
if (layer_nr == 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG)) { support_pattern = EFillMethod::GRID; }
|
||||
|
||||
int support_infill_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
int support_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_extruder_nr");
|
||||
|
||||
double infill_overlap = storage.meshgroup->getExtruderTrain(support_infill_extruder_nr)->getSettingInPercentage("infill_overlap");
|
||||
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_infill_extruder_nr);
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_extruder_nr);
|
||||
|
||||
Polygons& support = storage.support.supportLayers[layer_nr].supportAreas;
|
||||
|
||||
@@ -893,21 +830,25 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
{
|
||||
PolygonsPart& island = support_islands[island_order_optimizer.polyOrder[n]];
|
||||
|
||||
double infill_overlap = 0; // support infill should not be expanded outward
|
||||
|
||||
int offset_from_outline = 0;
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, false, extrusion_width, support_line_distance, infill_overlap, 0, getSettingBoolean("support_connect_zigzags"), true);
|
||||
bool remove_overlapping_perimeters = false;
|
||||
if (support_pattern == EFillMethod::GRID || support_pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
Polygons boundary;
|
||||
PolygonUtils::offsetSafe(island, -extrusion_width / 2, extrusion_width, boundary, remove_overlapping_perimeters);
|
||||
gcode_layer.addPolygonsByOptimizer(boundary, &storage.support_config);
|
||||
offset_from_outline = -extrusion_width;
|
||||
infill_overlap = storage.meshgroup->getExtruderTrain(support_extruder_nr)->getSettingInPercentage("infill_overlap"); // support lines area should be expanded outward to overlap with the boundary polygon
|
||||
}
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, remove_overlapping_perimeters, extrusion_width, support_line_distance, infill_overlap, 0, getSettingBoolean("support_connect_zigzags"), true);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines, nullptr);
|
||||
|
||||
if (support_pattern == EFillMethod::GRID || support_pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(island, &storage.support_config);
|
||||
sendPolygons(SupportType, layer_nr, island, storage.support_config.getLineWidth());
|
||||
}
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_config);
|
||||
sendPolygons(SupportInfillType, layer_nr, support_polygons, storage.support_config.getLineWidth());
|
||||
sendPolygons(SupportInfillType, layer_nr, support_lines, storage.support_config.getLineWidth());
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_config, (support_pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,7 +880,7 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
|
||||
{
|
||||
fillAngle = 45 + (layer_nr % 2) * 90; // alternate between the two kinds of diagonal: / and \ .
|
||||
}
|
||||
double infill_overlap = 0;
|
||||
double infill_overlap = 0; // the roofs should never be expanded outwards
|
||||
int outline_offset = 0;
|
||||
|
||||
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].roofs, outline_offset, false, storage.support_roof_config.getLineWidth(), support_line_distance, infill_overlap, fillAngle, false, true);
|
||||
@@ -948,9 +889,7 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
|
||||
infill_comp.generate(support_polygons, support_lines, nullptr);
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_roof_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_roof_config);
|
||||
sendPolygons(SupportType, layer_nr, support_polygons, storage.support_roof_config.getLineWidth());
|
||||
sendPolygons(SupportType, layer_nr, support_lines, storage.support_roof_config.getLineWidth());
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_roof_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr)
|
||||
@@ -987,12 +926,12 @@ void FffGcodeWriter::addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcod
|
||||
bool prime_tower_dir_outward = getSettingBoolean("prime_tower_dir_outward");
|
||||
bool wipe = getSettingBoolean("prime_tower_wipe_enabled");
|
||||
|
||||
storage.primeTower.addToGcode(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed, command_socket);
|
||||
storage.primeTower.addToGcode(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::finalize()
|
||||
{
|
||||
if (command_socket)
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
std::ostringstream prefix;
|
||||
prefix << ";FLAVOR:" << toString(gcode.getFlavor()) << "\n";
|
||||
@@ -1002,7 +941,7 @@ void FffGcodeWriter::finalize()
|
||||
prefix << ";MATERIAL:" << int(gcode.getTotalFilamentUsed(0)) << "\n";
|
||||
prefix << ";MATERIAL2:" << int(gcode.getTotalFilamentUsed(1)) << "\n";
|
||||
}
|
||||
command_socket->sendGCodePrefix(prefix.str());
|
||||
CommandSocket::getInstance()->sendGCodePrefix(prefix.str());
|
||||
}
|
||||
|
||||
gcode.finalize(getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
|
||||
|
||||
+3
-54
@@ -17,6 +17,7 @@
|
||||
#include "commandSocket.h"
|
||||
#include "PrimeTower.h"
|
||||
#include "FanSpeedLayerTime.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
|
||||
#include "LayerPlanBuffer.h"
|
||||
@@ -42,7 +43,6 @@ private:
|
||||
LayerPlanBuffer layer_plan_buffer;
|
||||
|
||||
GCodeExport gcode;
|
||||
CommandSocket* command_socket;
|
||||
std::ofstream output_file;
|
||||
|
||||
/*!
|
||||
@@ -59,30 +59,18 @@ private:
|
||||
public:
|
||||
FffGcodeWriter(SettingsBase* settings_)
|
||||
: SettingsMessenger(settings_)
|
||||
, layer_plan_buffer(this, command_socket, gcode)
|
||||
, layer_plan_buffer(this, gcode)
|
||||
, last_position_planned(no_point)
|
||||
, current_extruder_planned(0) // TODO: make configurable
|
||||
{
|
||||
meshgroup_number = 1;
|
||||
max_object_height = 0;
|
||||
command_socket = NULL;
|
||||
}
|
||||
void resetFileNumber()
|
||||
{
|
||||
meshgroup_number = 1;
|
||||
}
|
||||
|
||||
void setCommandSocket(CommandSocket* socket)
|
||||
{
|
||||
command_socket = socket;
|
||||
}
|
||||
|
||||
void sendPolygons(PolygonType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
{
|
||||
if (command_socket)
|
||||
command_socket->sendPolygons(type, layer_nr, polygons, line_width);
|
||||
}
|
||||
|
||||
bool setTargetFile(const char* filename)
|
||||
{
|
||||
output_file.open(filename);
|
||||
@@ -124,8 +112,6 @@ private:
|
||||
*/
|
||||
void initConfigs(SliceDataStorage& storage);
|
||||
|
||||
void setConfigWallReinforcement(SliceMeshStorage& mesh, int layer_thickness);
|
||||
|
||||
/*!
|
||||
* Set temperatures and perform initial priming.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
@@ -242,43 +228,6 @@ private:
|
||||
*/
|
||||
void processSingleLayerInfill(GCodePlanner& gcodeLayer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, double infill_overlap, int fillAngle, int extrusionWidth);
|
||||
|
||||
/*!
|
||||
* Add wall reinforcement for a given part in a layer.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh for which to add to the gcode.
|
||||
* \param reinforcement_wall The reinforcement wall for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param wall_reinforcement_line_distance The distance between the infill lines
|
||||
* \param infill_overlap The fraction of the extrusion width by which the infill overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param wall_reinforcement_line_width extrusionWidth
|
||||
* \param inside_out Whether to print from inside outward or other way around
|
||||
*/
|
||||
void processWallReinforcement(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, ReinforcementWall& reinforcement_wall, unsigned int layer_nr, int wall_reinforcement_line_distance, double infill_overlap, int infill_angle, int wall_reinforcement_line_width, bool inside_out);
|
||||
|
||||
/*!
|
||||
* Add the inner extra walls of the wall reinforcement for a given part in a layer.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh for which to add to the gcode.
|
||||
* \param reinforcement_wall The reinforcement wall for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param wall_reinforcement_line_width extrusionWidth
|
||||
*/
|
||||
void processWallReinforcement_extraWalls(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, ReinforcementWall& reinforcement_wall, unsigned int layer_nr, int wall_reinforcement_line_width, bool inside_out);
|
||||
|
||||
/*!
|
||||
* Add the infill of the wall reinforcement for a given part in a layer.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param mesh The mesh for which to add to the gcode.
|
||||
* \param reinforcement_wall The reinforcement wall for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param wall_reinforcement_line_distance The distance between the infill lines
|
||||
* \param infill_overlap The fraction of the extrusion width by which the infill overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param wall_reinforcement_line_width extrusionWidth
|
||||
*/
|
||||
void processWallReinforcement_infill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, ReinforcementWall& reinforcement_wall, unsigned int layer_nr, int wall_reinforcement_line_distance, double infill_overlap, int infill_angle, int wall_reinforcement_line_width);
|
||||
|
||||
/*!
|
||||
* Generate the insets for the walls of a given layer part.
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
@@ -317,7 +266,7 @@ private:
|
||||
* \param gcodeLayer The initial planning of the gcode of the layer.
|
||||
* \param layer_nr The index of the layer to write the gcode of.
|
||||
*/
|
||||
void addSupportInfillToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
void addSupportLinesToGCode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr);
|
||||
/*!
|
||||
* Add the support roofs to the gcode of the current layer.
|
||||
* \param storage Input: where the slice data is stored.
|
||||
|
||||
+26
-130
@@ -16,6 +16,7 @@
|
||||
#include "raft.h"
|
||||
#include "debug.h"
|
||||
#include "Progress.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -23,8 +24,8 @@ namespace cura
|
||||
|
||||
bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper)
|
||||
{
|
||||
if (commandSocket)
|
||||
commandSocket->beginSendSlicedObject();
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->beginSendSlicedObject();
|
||||
|
||||
if (!sliceModel(meshgroup, timeKeeper, storage))
|
||||
{
|
||||
@@ -38,7 +39,7 @@ bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* me
|
||||
|
||||
bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeeper, SliceDataStorage& storage) /// slices the model
|
||||
{
|
||||
Progress::messageProgressStage(Progress::Stage::SLICING, &timeKeeper, commandSocket);
|
||||
Progress::messageProgressStage(Progress::Stage::SLICING, &timeKeeper);
|
||||
|
||||
storage.model_min = meshgroup->min();
|
||||
storage.model_max = meshgroup->max();
|
||||
@@ -65,7 +66,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
int layer_count = (storage.model_max.z - initial_slice_z) / layer_thickness + 1;
|
||||
if(layer_count <= 0) //Model is shallower than layer_height_0, so not even the first layer is sliced. Return an empty model then.
|
||||
{
|
||||
Progress::messageProgressStage(Progress::Stage::INSET,&timeKeeper,commandSocket); //Continue directly with the inset stage, which will also immediately stop.
|
||||
Progress::messageProgressStage(Progress::Stage::INSET,&timeKeeper); //Continue directly with the inset stage, which will also immediately stop.
|
||||
return true; //This is NOT an error state!
|
||||
}
|
||||
|
||||
@@ -79,18 +80,18 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
for(SlicerLayer& layer : slicer->layers)
|
||||
{
|
||||
//Reporting the outline here slows down the engine quite a bit, so only do so when debugging.
|
||||
//sendPolygons("outline", layer_nr, layer.z, layer.polygonList);
|
||||
//sendPolygons("openoutline", layer_nr, layer.openPolygonList);
|
||||
sendPolygons("outline", layer_nr, layer.z, layer.polygonList);
|
||||
sendPolygons("openoutline", layer_nr, layer.openPolygonList);
|
||||
}
|
||||
*/
|
||||
Progress::messageProgress(Progress::Stage::SLICING, mesh_idx + 1, meshgroup->meshes.size(), commandSocket);
|
||||
Progress::messageProgress(Progress::Stage::SLICING, mesh_idx + 1, meshgroup->meshes.size());
|
||||
}
|
||||
|
||||
log("Layer count: %i\n", layer_count);
|
||||
|
||||
meshgroup->clear();///Clear the mesh face and vertex data, it is no longer needed after this point, and it saves a lot of memory.
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper, commandSocket);
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
|
||||
//carveMultipleVolumes(storage.meshes);
|
||||
generateMultipleVolumesOverlap(slicerList, getSettingInMicrons("multiple_mesh_overlap"));
|
||||
|
||||
@@ -126,16 +127,16 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
meshStorage.layer_nr_max_filled_layer = layer_nr; // last set by the highest non-empty layer
|
||||
}
|
||||
|
||||
if (commandSocket)
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
commandSocket->sendLayerInfo(layer_nr, layer.printZ, layer_nr == 0? meshStorage.getSettingInMicrons("layer_height_0") : meshStorage.getSettingInMicrons("layer_height"));
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, layer.printZ, layer_nr == 0? meshStorage.getSettingInMicrons("layer_height_0") : meshStorage.getSettingInMicrons("layer_height"));
|
||||
}
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::PARTS, meshIdx + 1, slicerList.size(), commandSocket);
|
||||
Progress::messageProgress(Progress::Stage::PARTS, meshIdx + 1, slicerList.size());
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::INSET, &timeKeeper, commandSocket);
|
||||
Progress::messageProgressStage(Progress::Stage::INSET, &timeKeeper);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -152,7 +153,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
{
|
||||
processInsets(storage, layer_number);
|
||||
|
||||
Progress::messageProgress(Progress::Stage::INSET, layer_number+1, total_layers, commandSocket);
|
||||
Progress::messageProgress(Progress::Stage::INSET, layer_number+1, total_layers);
|
||||
}
|
||||
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), total_layers);
|
||||
@@ -163,21 +164,24 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
return;
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper, commandSocket);
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
|
||||
|
||||
AreaSupport::generateSupportAreas(storage, total_layers, commandSocket);
|
||||
AreaSupport::generateSupportAreas(storage, total_layers);
|
||||
/*
|
||||
if (storage.support.generated)
|
||||
{
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
{
|
||||
Polygons& support = storage.support.supportLayers[layer_idx].supportAreas;
|
||||
sendPolygons(SupportType, layer_idx, support, getSettingInMicrons("support_line_width"));
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::Infill, layer_idx, support, 100); //getSettingInMicrons("support_line_width"));
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper, commandSocket);
|
||||
Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper);
|
||||
int mesh_max_bottom_layer_count = 0;
|
||||
if (getSettingBoolean("magic_spiralize"))
|
||||
{
|
||||
@@ -192,12 +196,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
{
|
||||
processSkinsAndInfill(storage, layer_number);
|
||||
}
|
||||
Progress::messageProgress(Progress::Stage::SKIN, layer_number+1, total_layers, commandSocket);
|
||||
}
|
||||
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
{
|
||||
processWallReinforcement(storage, layer_number);
|
||||
Progress::messageProgress(Progress::Stage::SKIN, layer_number+1, total_layers);
|
||||
}
|
||||
|
||||
unsigned int combined_infill_layers = storage.getSettingInMicrons("infill_sparse_thickness") / std::max(storage.getSettingInMicrons("layer_height"),1); //How many infill layers to combine to obtain the requested sparse thickness.
|
||||
@@ -222,17 +221,6 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
{
|
||||
processFuzzyWalls(mesh);
|
||||
}
|
||||
else if (mesh.getSettingAsCount("wall_line_count") > 0)
|
||||
{ // only send polygon data
|
||||
for (unsigned int layer_nr = 0; layer_nr < total_layers; layer_nr++)
|
||||
{
|
||||
SliceLayer* layer = &mesh.layers[layer_nr];
|
||||
for(SliceLayerPart& part : layer->parts)
|
||||
{
|
||||
sendPolygons(Inset0Type, layer_nr, (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)? part.outline : part.insets[0], mesh.getSettingInMicrons("wall_line_width_0"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,16 +239,6 @@ void FffPolygonGenerator::processInsets(SliceDataStorage& storage, unsigned int
|
||||
if (mesh.getSettingBoolean("alternate_extra_perimeter"))
|
||||
inset_count += layer_nr % 2;
|
||||
generateInsets(layer, mesh.getSettingInMicrons("machine_nozzle_size"), line_width_0, line_width_x, inset_count, mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
|
||||
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
{
|
||||
if (layer->parts[partNr].insets.size() > 0)
|
||||
{
|
||||
// sendPolygons(Inset0Type, layer_nr, layer->parts[partNr].insets[0], line_width_0); // done after processing fuzzy skin
|
||||
for(unsigned int inset=1; inset<layer->parts[partNr].insets.size(); inset++)
|
||||
sendPolygons(InsetXType, layer_nr, layer->parts[partNr].insets[inset], line_width_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
{
|
||||
@@ -273,72 +251,6 @@ void FffPolygonGenerator::processInsets(SliceDataStorage& storage, unsigned int
|
||||
segment.add(polyline[point_idx-1]);
|
||||
segment.add(polyline[point_idx]);
|
||||
}
|
||||
sendPolygons(Inset0Type, layer_nr, segments, mesh.getSettingInMicrons("wall_line_width_0"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FffPolygonGenerator::processWallReinforcement(SliceDataStorage& storage, unsigned int layer_nr)
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{ // generate infill area
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (mesh.getSettingInMicrons("wall_reinforcement_thickness") == 0.0 && mesh.getSettingAsCount("wall_reinforcement_line_count") == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int inset_count = mesh.getSettingAsCount("wall_reinforcement_line_count");
|
||||
int wall_line_width = mesh.getSettingInMicrons("wall_line_width_x");
|
||||
|
||||
SliceLayer* layer = &mesh.layers[layer_nr];
|
||||
for (SliceLayerPart& part : layer->parts)
|
||||
{
|
||||
if (part.infill_area.size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int wall_reinforcement_count = mesh.getSettingAsCount("wall_reinforcement_count");
|
||||
part.reinforcement_walls.reserve(wall_reinforcement_count);
|
||||
|
||||
|
||||
for (unsigned int wall_idx = 0; int(wall_idx) < wall_reinforcement_count; wall_idx++)
|
||||
{
|
||||
part.reinforcement_walls.emplace_back();
|
||||
ReinforcementWall& reinforcement_wall = part.reinforcement_walls.back();
|
||||
|
||||
Polygons outer_wall_reinforcement_edge = part.infill_area[0].offset(-mesh.getSettingInMicrons("wall_reinforcement_thickness"));
|
||||
reinforcement_wall.wall_reinforcement_area = part.infill_area[0].difference(outer_wall_reinforcement_edge);
|
||||
if (mesh.getSettingAsCount("wall_reinforcement_line_count") > 0)
|
||||
{
|
||||
reinforcement_wall.wall_reinforcement_axtra_walls.push_back(outer_wall_reinforcement_edge.offset(-wall_line_width/2));
|
||||
}
|
||||
else
|
||||
{
|
||||
part.infill_area[0] = outer_wall_reinforcement_edge.offset(-wall_line_width/2);
|
||||
}
|
||||
|
||||
// generate reinforcement wall extra walls
|
||||
|
||||
if (reinforcement_wall.wall_reinforcement_axtra_walls.size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
generateWallReinforcementWallExtraWalls(&part, reinforcement_wall, wall_line_width, inset_count, mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
|
||||
|
||||
if (reinforcement_wall.wall_reinforcement_axtra_walls.size() > 0)
|
||||
{
|
||||
part.infill_area[0] = reinforcement_wall.wall_reinforcement_axtra_walls.back().offset(-wall_line_width/2); // update the infill area to one reinforcement wall insetted (updated each time a reinforcement wall is generated)
|
||||
}
|
||||
if (part.insets.size() > 0)
|
||||
{
|
||||
for(Polygons& polys : reinforcement_wall.wall_reinforcement_axtra_walls)
|
||||
sendPolygons(SupportType, layer_nr, polys, wall_line_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,17 +305,17 @@ void FffPolygonGenerator::processSkinsAndInfill(SliceDataStorage& storage, unsig
|
||||
|
||||
int wall_line_count = mesh.getSettingAsCount("wall_line_count");
|
||||
int skin_extrusion_width = mesh.getSettingInMicrons("skin_line_width");
|
||||
int innermost_wall_extrusion_width = mesh.getSettingInMicrons("wall_line_width_x");
|
||||
int extrusionWidth_infill = mesh.getSettingInMicrons("infill_line_width");
|
||||
int innermost_wall_extrusion_width = (wall_line_count == 1)? mesh.getSettingInMicrons("wall_line_width_0") : mesh.getSettingInMicrons("wall_line_width_x");
|
||||
generateSkins(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), wall_line_count, innermost_wall_extrusion_width, mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("skin_no_small_gaps_heuristic"), mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
|
||||
if (mesh.getSettingInMicrons("infill_line_distance") > 0)
|
||||
{
|
||||
int infill_skin_overlap = 0;
|
||||
if (mesh.getSettingInMicrons("infill_line_distance") > mesh.getSettingInMicrons("infill_line_width") + 10)
|
||||
bool infill_is_dense = mesh.getSettingInMicrons("infill_line_distance") < mesh.getSettingInMicrons("infill_line_width") + 10;
|
||||
if (!infill_is_dense && mesh.getSettingAsFillMethod("infill_pattern") != EFillMethod::CONCENTRIC)
|
||||
{
|
||||
infill_skin_overlap = skin_extrusion_width / 2;
|
||||
}
|
||||
generateInfill(layer_nr, mesh, extrusionWidth_infill, infill_skin_overlap, wall_line_count);
|
||||
generateInfill(layer_nr, mesh, innermost_wall_extrusion_width, infill_skin_overlap, wall_line_count);
|
||||
if (mesh.getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") == FillPerimeterGapMode::SKIN)
|
||||
{
|
||||
generatePerimeterGaps(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"));
|
||||
@@ -413,20 +325,6 @@ void FffPolygonGenerator::processSkinsAndInfill(SliceDataStorage& storage, unsig
|
||||
generatePerimeterGaps(layer_nr, mesh, skin_extrusion_width, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool frontend_can_show_polygon_as_filled_polygon = false;
|
||||
if (frontend_can_show_polygon_as_filled_polygon)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
for(SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
// sendPolygons(InfillType, layer_nr, part.infill_area[0], extrusionWidth_infill); // sends the outline, not the actual infill
|
||||
for (SkinPart& skin_part : part.skin_parts)
|
||||
{
|
||||
sendPolygons(SkinType, layer_nr, skin_part.outline, innermost_wall_extrusion_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +404,6 @@ void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
|
||||
Polygons skirt_sent = storage.skirt[0];
|
||||
for (int extruder = 1; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
skirt_sent.add(storage.skirt[extruder]);
|
||||
sendPolygons(SkirtType, 0, skirt_sent, getSettingInMicrons("skirt_line_width"));
|
||||
}
|
||||
|
||||
|
||||
@@ -567,7 +464,6 @@ void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh)
|
||||
}
|
||||
}
|
||||
skin = results;
|
||||
sendPolygons(Inset0Type, layer_nr, skin, mesh.getSettingInMicrons("wall_line_width_0"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "settings.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "commandSocket.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -24,26 +25,14 @@ namespace cura
|
||||
*/
|
||||
class FffPolygonGenerator : public SettingsMessenger, NoCopy
|
||||
{
|
||||
private:
|
||||
CommandSocket* commandSocket;
|
||||
public:
|
||||
/*!
|
||||
* Basic constructor; doesn't set the FffAreaGenerator::commandSocket .
|
||||
* Basic constructor
|
||||
*/
|
||||
FffPolygonGenerator(SettingsBase* settings_)
|
||||
: SettingsMessenger(settings_)
|
||||
, commandSocket(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the FffAreaGenerator::commandSocket
|
||||
*/
|
||||
void setCommandSocket(CommandSocket* socket)
|
||||
{
|
||||
commandSocket = socket;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Slice the \p object, process the outline information into inset perimeter polygons, support area polygons, etc.
|
||||
@@ -55,17 +44,6 @@ public:
|
||||
bool generateAreas(SliceDataStorage& storage, MeshGroup* object, TimeKeeper& timeKeeper);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Send polygons over the command socket, if there is one.
|
||||
* \param type The type of polygon to send
|
||||
* \param layer_nr The layer number at which the polygons occur
|
||||
* \param polygons The polygons to be sent
|
||||
*/
|
||||
void sendPolygons(PolygonType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
{
|
||||
if (commandSocket)
|
||||
commandSocket->sendPolygons(type, layer_nr, polygons, line_width);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Slice the \p object and store the outlines in the \p storage.
|
||||
@@ -101,15 +79,6 @@ private:
|
||||
*/
|
||||
void processInsets(SliceDataStorage& storage, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Generate the wall reinforcement extra wall polygons and its infill area which form the reinforcement.
|
||||
*
|
||||
* Also redefines the infill area;
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param layer_nr The layer for which to generate the insets.
|
||||
*/
|
||||
void processWallReinforcement(SliceDataStorage& storage, unsigned int layer_nr);
|
||||
/*!
|
||||
* Generate the outline of the ooze shield.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
|
||||
+11
-6
@@ -11,6 +11,7 @@ std::string FffProcessor::getAllSettingsString(MeshGroup& meshgroup, bool first_
|
||||
std::stringstream sstream;
|
||||
if (first_meshgroup)
|
||||
{
|
||||
sstream << getAllLocalSettingsString(); // global settings
|
||||
sstream << " -g";
|
||||
}
|
||||
else
|
||||
@@ -66,7 +67,7 @@ bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
|
||||
if (meshgroup->meshes.empty())
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::FINISH, 1, 1, command_socket); //Report the GUI that a file has been fully processed.
|
||||
Progress::messageProgress(Progress::Stage::FINISH, 1, 1); // 100% on this meshgroup
|
||||
log("Total time elapsed %5.2fs.\n", time_keeper_total.restart());
|
||||
|
||||
profile_string += getAllSettingsString(*meshgroup, first_meshgroup);
|
||||
@@ -78,11 +79,11 @@ bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
log("starting Neith Weaver...\n");
|
||||
|
||||
Weaver w(this);
|
||||
w.weave(meshgroup, command_socket);
|
||||
w.weave(meshgroup);
|
||||
|
||||
log("starting Neith Gcode generation...\n");
|
||||
Wireframe2gcode gcoder(w, gcode_writer.gcode, this);
|
||||
gcoder.writeGCode(command_socket);
|
||||
gcoder.writeGCode();
|
||||
log("finished Neith Gcode generation...\n");
|
||||
|
||||
} else
|
||||
@@ -93,13 +94,17 @@ bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
gcode_writer.setCommandSocket(command_socket);
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::EXPORT, &time_keeper, command_socket);
|
||||
Progress::messageProgressStage(Progress::Stage::EXPORT, &time_keeper);
|
||||
gcode_writer.writeGCode(storage, time_keeper);
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::FINISH, 1, 1, command_socket); //Report the GUI that a file has been fully processed.
|
||||
Progress::messageProgress(Progress::Stage::FINISH, 1, 1); // 100% on this meshgroup
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->flushGcode();
|
||||
CommandSocket::getInstance()->endSendSlicedObject();
|
||||
}
|
||||
log("Total time elapsed %5.2fs.\n", time_keeper_total.restart());
|
||||
|
||||
profile_string += getAllSettingsString(*meshgroup, first_meshgroup);
|
||||
|
||||
@@ -26,7 +26,6 @@ private:
|
||||
, gcode_writer(this)
|
||||
, first_meshgroup(true)
|
||||
{
|
||||
command_socket = NULL;
|
||||
}
|
||||
public:
|
||||
static FffProcessor* getInstance()
|
||||
@@ -37,7 +36,6 @@ public:
|
||||
private:
|
||||
FffPolygonGenerator polygon_generator;
|
||||
FffGcodeWriter gcode_writer;
|
||||
CommandSocket* command_socket; // TODO: replace all refs to command_socket by CommandSocket::getInstance()
|
||||
|
||||
bool first_meshgroup;
|
||||
|
||||
@@ -55,13 +53,6 @@ public:
|
||||
gcode_writer.resetFileNumber();
|
||||
}
|
||||
|
||||
void setCommandSocket(CommandSocket* socket)
|
||||
{
|
||||
command_socket = socket;
|
||||
gcode_writer.setCommandSocket(socket);
|
||||
polygon_generator.setCommandSocket(socket);
|
||||
}
|
||||
|
||||
bool setTargetFile(const char* filename)
|
||||
{
|
||||
return gcode_writer.setTargetFile(filename);
|
||||
|
||||
@@ -14,12 +14,14 @@ void LayerPlanBuffer::flush()
|
||||
{
|
||||
insertPreheatCommands(); // insert preheat commands of the very last layer
|
||||
}
|
||||
for (GCodePlanner& layer_plan : buffer)
|
||||
while (!buffer.empty())
|
||||
{
|
||||
layer_plan.writeGCode(gcode, getSettingBoolean("cool_lift_head"), layer_plan.getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
|
||||
if (command_socket)
|
||||
command_socket->sendGCodeLayer();
|
||||
buffer.front().writeGCode(gcode, getSettingBoolean("cool_lift_head"), buffer.front().getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->flushGcode();
|
||||
buffer.pop_front();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LayerPlanBuffer::insertPreheatCommand(ExtruderPlan& extruder_plan_before, double time_after_extruder_plan_start, int extruder, double temp)
|
||||
@@ -218,10 +220,4 @@ void LayerPlanBuffer::insertPreheatCommands()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -17,8 +17,6 @@ namespace cura
|
||||
|
||||
class LayerPlanBuffer : SettingsMessenger
|
||||
{
|
||||
CommandSocket* command_socket;
|
||||
|
||||
GCodeExport& gcode;
|
||||
|
||||
Preheat preheat_config; //!< the nozzle and material temperature settings for each extruder train.
|
||||
@@ -29,9 +27,8 @@ class LayerPlanBuffer : SettingsMessenger
|
||||
public:
|
||||
std::list<GCodePlanner> buffer; //!< The buffer containing several layer plans (GCodePlanner) before writing them to gcode.
|
||||
|
||||
LayerPlanBuffer(SettingsBaseVirtual* settings, CommandSocket* command_socket, GCodeExport& gcode)
|
||||
LayerPlanBuffer(SettingsBaseVirtual* settings, GCodeExport& gcode)
|
||||
: SettingsMessenger(settings)
|
||||
, command_socket(command_socket)
|
||||
, gcode(gcode)
|
||||
{ }
|
||||
|
||||
@@ -55,8 +52,8 @@ public:
|
||||
if (buffer.size() > buffer_size)
|
||||
{
|
||||
buffer.front().writeGCode(gcode, getSettingBoolean("cool_lift_head"), buffer.front().getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
|
||||
if (command_socket)
|
||||
command_socket->sendGCodeLayer();
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->flushGcode();
|
||||
buffer.pop_front();
|
||||
}
|
||||
return buffer.back();
|
||||
|
||||
+112
-37
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <algorithm> // min
|
||||
|
||||
#include "utils/linearAlg2D.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
@@ -12,6 +14,7 @@ void MergeInfillLines::writeCompensatedMove(Point& to, double speed, GCodePath&
|
||||
double speed_mod = old_line_width / new_line_width_mm;
|
||||
double extrusion_mod = new_line_width_mm / old_line_width;
|
||||
double new_speed = std::min(speed * speed_mod, 150.0); // TODO: hardcoded value: max extrusion speed is 150 mm/s = 9000 mm/min
|
||||
sendPolygon(last_path.config->type, gcode.getPositionXY(), to, last_path.getLineWidth());
|
||||
gcode.writeMove(to, new_speed, last_path.getExtrusionMM3perMM() * extrusion_mod);
|
||||
}
|
||||
|
||||
@@ -51,38 +54,88 @@ bool MergeInfillLines::mergeInfillLines(double speed, unsigned int& path_idx)
|
||||
return false;
|
||||
};
|
||||
|
||||
bool MergeInfillLines::isConvertible(unsigned int path_idx_first_move, Point& first_middle, Point& second_middle, int64_t& line_width, bool use_second_middle_as_first)
|
||||
bool MergeInfillLines::isConvertible(unsigned int path_idx_first_move, Point& first_middle, Point& second_middle, int64_t& resulting_line_width, bool use_second_middle_as_first)
|
||||
{
|
||||
int64_t max_line_width = nozzle_size * 3 / 2;
|
||||
|
||||
|
||||
unsigned int idx = path_idx_first_move;
|
||||
if (idx + 3 > paths.size()-1) return false;
|
||||
if (paths[idx+0].config != &travelConfig) return false;
|
||||
if (paths[idx+1].points.size() > 1) return false;
|
||||
if (paths[idx+1].config == &travelConfig) return false;
|
||||
// if (paths[idx+2].points.size() > 1) return false;
|
||||
if (paths[idx+2].config != &travelConfig) return false;
|
||||
if (paths[idx+3].points.size() > 1) return false;
|
||||
if (paths[idx+3].config == &travelConfig) return false;
|
||||
if (idx + 3 > paths.size()-1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ( paths[idx+0].config != &travelConfig // must be travel
|
||||
|| paths[idx+1].points.size() > 1 // extrusion path is single line
|
||||
|| paths[idx+1].config == &travelConfig // must be extrusion
|
||||
// || paths[idx+2].points.size() > 1 // travel must be direct
|
||||
|| paths[idx+2].config != &travelConfig // must be travel
|
||||
|| paths[idx+3].points.size() > 1 // extrusion path is single line
|
||||
|| paths[idx+3].config == &travelConfig // must be extrusion
|
||||
|| paths[idx+1].config != paths[idx+3].config // both extrusion moves should have the same config
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(paths[idx+1].config->type == PrintFeatureType::Infill || paths[idx+1].config->type == PrintFeatureType::Skin))
|
||||
{ // only (skin) infill lines can be merged (note that the second extrusion line config is already checked to be the same as the first in code above)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (paths[idx+1].space_fill_type != SpaceFillType::Lines || paths[idx+3].space_fill_type != SpaceFillType::Lines)
|
||||
{ // both extrusion moves must be of lines space filling type!
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t line_width = paths[idx+1].config->getLineWidth();
|
||||
|
||||
Point& a = paths[idx+0].points.back(); // first extruded line from
|
||||
Point& b = paths[idx+1].points.back(); // first extruded line to
|
||||
Point& c = paths[idx+2].points.back(); // second extruded line from
|
||||
Point& d = paths[idx+3].points.back(); // second extruded line to
|
||||
|
||||
return isConvertible(a, b, c, d, line_width, first_middle, second_middle, resulting_line_width, use_second_middle_as_first);
|
||||
}
|
||||
|
||||
bool MergeInfillLines::isConvertible(const Point& a, const Point& b, const Point& c, const Point& d, int64_t line_width, Point& first_middle, Point& second_middle, int64_t& resulting_line_width, bool use_second_middle_as_first)
|
||||
{
|
||||
use_second_middle_as_first = false;
|
||||
int64_t max_line_width = nozzle_size * 3 / 2;
|
||||
|
||||
Point ab = b - a;
|
||||
Point cd = d - c;
|
||||
|
||||
|
||||
if (b == c)
|
||||
{
|
||||
return false; // the line segments are connected!
|
||||
}
|
||||
|
||||
int64_t ab_size = vSize(ab);
|
||||
int64_t cd_size = vSize(cd);
|
||||
|
||||
if (ab_size > nozzle_size * 5 || cd_size > nozzle_size * 5)
|
||||
{
|
||||
return false; // infill lines are too long; otherwise infill lines might be merged when the next infill line is coincidentally shorter like |, would become \ ...
|
||||
}
|
||||
|
||||
// if the lines are in the same direction then abs( dot(ab,cd) / |ab| / |cd| ) == 1
|
||||
int64_t prod = dot(ab,cd);
|
||||
if (std::abs(prod) + 400 < vSize(ab) * vSize(cd)) // 400 = 20*20, where 20 micron is the allowed inaccuracy in the dot product, introduced by the inaccurate point locations of a,b,c,d
|
||||
if (std::abs(prod) + 400 < ab_size * cd_size) // 400 = 20*20, where 20 micron is the allowed inaccuracy in the dot product, introduced by the inaccurate point locations of a,b,c,d
|
||||
{
|
||||
return false; // extrusion moves not in the same or opposite diraction
|
||||
if (prod < 0) { ab = ab * -1; }
|
||||
}
|
||||
|
||||
|
||||
Point infill_vector = (cd + ab) / 2;
|
||||
|
||||
if (!shorterThen(infill_vector, 5 * nozzle_size)) return false; // infill lines too far apart
|
||||
|
||||
// make lines in the same direction by flipping one
|
||||
if (prod < 0)
|
||||
{
|
||||
ab = ab * -1;
|
||||
}
|
||||
else if (prod == 0)
|
||||
{
|
||||
return false; // lines are orthogonal!
|
||||
}
|
||||
else if (b == d || a == c)
|
||||
{
|
||||
return false; // the line segments are connected!
|
||||
}
|
||||
|
||||
first_middle = (use_second_middle_as_first)?
|
||||
second_middle :
|
||||
(a + b) / 2;
|
||||
@@ -90,25 +143,47 @@ bool MergeInfillLines::isConvertible(unsigned int path_idx_first_move, Point& fi
|
||||
|
||||
Point dir_vector_perp = crossZ(second_middle - first_middle);
|
||||
int64_t dir_vector_perp_length = vSize(dir_vector_perp); // == dir_vector_length
|
||||
if (dir_vector_perp_length == 0) return false;
|
||||
if (dir_vector_perp_length > 5 * nozzle_size) return false; // infill lines too far apart
|
||||
|
||||
|
||||
line_width = std::abs( dot(dir_vector_perp, infill_vector) / dir_vector_perp_length );
|
||||
if (line_width > max_line_width) return false; // combined lines would be too wide
|
||||
if (line_width == 0) return false; // dot is zero, so lines are in each others extension, not next to eachother
|
||||
|
||||
{ // check whether the two lines are adjacent
|
||||
Point ca = first_middle - c;
|
||||
double ca_size = vSizeMM(ca);
|
||||
double cd_size = vSizeMM(cd);
|
||||
double prod = INT2MM(dot(ca, cd));
|
||||
double fraction = prod / ( ca_size * cd_size );
|
||||
int64_t line2line_dist = MM2INT(cd_size * std::sqrt(1.0 - fraction * fraction));
|
||||
|
||||
if (line2line_dist + 20 > paths[idx+1].config->getLineWidth()) return false; // there is a gap between the two lines
|
||||
if (dir_vector_perp_length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (dir_vector_perp_length > 5 * nozzle_size)
|
||||
{
|
||||
return false; // infill lines too far apart
|
||||
}
|
||||
|
||||
Point infill_vector = (cd + ab) / 2; // (similar to) average line / direction of the infill
|
||||
|
||||
// compute the resulting line width
|
||||
resulting_line_width = std::abs( dot(dir_vector_perp, infill_vector) / dir_vector_perp_length );
|
||||
if (resulting_line_width > max_line_width)
|
||||
{
|
||||
return false; // combined lines would be too wide
|
||||
}
|
||||
if (resulting_line_width == 0)
|
||||
{
|
||||
return false; // dot is zero, so lines are in each others extension, not next to eachother
|
||||
}
|
||||
|
||||
// check whether two lines are adjacent (note: not 'line segments' but 'lines')
|
||||
Point ac = c - first_middle;
|
||||
Point infill_vector_perp = crossZ(infill_vector);
|
||||
int64_t perp_proj = dot(ac, infill_vector_perp);
|
||||
int64_t infill_vector_perp_length = vSize(infill_vector_perp);
|
||||
if (std::abs(std::abs(perp_proj) / infill_vector_perp_length - line_width) > 20) // it should be the case that dot(ac, infill_vector_perp) / |infill_vector_perp| == line_width
|
||||
{
|
||||
return false; // lines are too far apart or too close together
|
||||
}
|
||||
|
||||
// check whether the two line segments are adjacent.
|
||||
// full infill in a narrow area might result in line segments with arbitrary distance between them
|
||||
// the more the narrow passage in the area gets aligned with the infill direction, the further apart the line segments will be
|
||||
// however, distant line segments might also be due to different narrow passages, so we limit the distance between merged line segments.
|
||||
if (!LinearAlg2D::lineSegmentsAreCloserThan(a, b, c, d, line_width * 2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,24 +12,42 @@ class MergeInfillLines
|
||||
{
|
||||
// void merge(Point& from, Point& p0, Point& p1);
|
||||
GCodeExport& gcode; //!< Where to write the combined line to
|
||||
int layer_nr; //!< The current layer number
|
||||
std::vector<GCodePath>& paths; //!< The paths currently under consideration
|
||||
ExtruderPlan& extruder_plan; //!< The extruder plan of the paths currently under consideration
|
||||
|
||||
GCodePathConfig& travelConfig; //!< The travel settings used to see whether a path is a travel path or an extrusion path
|
||||
int64_t nozzle_size; //!< The diameter of the hole in the nozzle
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
|
||||
* \param path_idx_first_move Index into MergeInfillLines::paths to the travel before the two extrusion moves udner consideration
|
||||
* \param first_middle Output parameter: the middle of the first extrusion move
|
||||
* \param second_middle Input/Output parameter: outputs the middle of the second extrusion move; inputs \p first_middle so we don't have to compute it
|
||||
* \param line_width Output parameter: The width of the resulting combined line (the average length of the lines combined)
|
||||
* \param resulting_line_width Output parameter: The width of the resulting combined line (the average length of the lines combined)
|
||||
* \param use_second_middle_as_first Whether to use \p second_middle as input parameter for \p first_middle
|
||||
* \return Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
|
||||
*/
|
||||
bool isConvertible(unsigned int path_idx_first_move, Point& first_middle, Point& second_middle, int64_t& line_width, bool use_second_middle_as_first);
|
||||
|
||||
bool isConvertible(unsigned int path_idx_first_move, Point& first_middle, Point& second_middle, int64_t& resulting_line_width, bool use_second_middle_as_first = false);
|
||||
|
||||
/*!
|
||||
* Whether the two consecutive extrusion paths (ab and cd) are convitrible to a single line segment.
|
||||
*
|
||||
* Note: as an optimization the \p second_middle from the previous call to isConvertible can be used for \p first_middle, instead of recomputing it.
|
||||
*
|
||||
* \param a first from
|
||||
* \param b first to
|
||||
* \param c second from
|
||||
* \param d second to
|
||||
* \param line_width The line width of the moves
|
||||
* \param first_middle Output parameter: the middle of the first extrusion move
|
||||
* \param second_middle Input/Output parameter: outputs the middle of the second extrusion move; inputs \p first_middle so we don't have to compute it
|
||||
* \param resulting_line_width Output parameter: The width of the resulting combined line (the average length of the lines combined)
|
||||
* \param use_second_middle_as_first Whether to use \p second_middle as input parameter for \p first_middle
|
||||
* \return Whether the next two extrusion paths are convertible to a single line segment, starting from the end point the of the last travel move at \p path_idx_first_move
|
||||
*/
|
||||
bool isConvertible(const Point& a, const Point& b, const Point& c, const Point& d, int64_t line_width, Point& first_middle, Point& second_middle, int64_t& resulting_line_width, bool use_second_middle_as_first = false);
|
||||
|
||||
/*!
|
||||
* Write an extrusion move with compensated width and compensated speed so that the material flow will be the same.
|
||||
*
|
||||
@@ -43,8 +61,8 @@ public:
|
||||
/*!
|
||||
* Simple constructor only used by MergeInfillLines::isConvertible to easily convey the environment
|
||||
*/
|
||||
MergeInfillLines(GCodeExport& gcode, std::vector<GCodePath>& paths, ExtruderPlan& extruder_plan, GCodePathConfig& travelConfig, int64_t nozzle_size)
|
||||
: gcode(gcode), paths(paths), extruder_plan(extruder_plan), travelConfig(travelConfig), nozzle_size(nozzle_size) { }
|
||||
MergeInfillLines(GCodeExport& gcode, int layer_nr, std::vector<GCodePath>& paths, ExtruderPlan& extruder_plan, GCodePathConfig& travelConfig, int64_t nozzle_size)
|
||||
: gcode(gcode), layer_nr(layer_nr), paths(paths), extruder_plan(extruder_plan), travelConfig(travelConfig), nozzle_size(nozzle_size) { }
|
||||
|
||||
/*!
|
||||
* Check for lots of small moves and combine them into one large line.
|
||||
@@ -60,6 +78,21 @@ public:
|
||||
*/
|
||||
bool mergeInfillLines(double speed, unsigned int& path_idx);
|
||||
|
||||
/*!
|
||||
* send a polygon through the command socket from the previous point to the given point
|
||||
*/
|
||||
void sendPolygon(PrintFeatureType print_feature_type, Point from, Point to, int line_width)
|
||||
{
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
// we should send this travel as a non-retraction move
|
||||
cura::Polygons pathPoly;
|
||||
PolygonRef path = pathPoly.newPoly();
|
||||
path.add(from);
|
||||
path.add(to);
|
||||
CommandSocket::getInstance()->sendPolygons(print_feature_type, layer_nr, pathPoly, line_width);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+40
-7
@@ -64,26 +64,38 @@ bool loadMeshSTL_ascii(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
bool loadMeshSTL_binary(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
{
|
||||
FILE* f = fopen(filename, "rb");
|
||||
|
||||
fseek(f, 0L, SEEK_END);
|
||||
long long file_size = ftell(f); //The file size is the position of the cursor after seeking to the end.
|
||||
rewind(f); //Seek back to start.
|
||||
size_t face_count = (file_size - 80 - sizeof(uint32_t)) / 50; //Subtract the size of the header. Every face uses exactly 50 bytes.
|
||||
|
||||
char buffer[80];
|
||||
uint32_t faceCount;
|
||||
//Skip the header
|
||||
if (fread(buffer, 80, 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
//Read the face count
|
||||
if (fread(&faceCount, sizeof(uint32_t), 1, f) != 1)
|
||||
|
||||
uint32_t reported_face_count;
|
||||
//Read the face count. We'll use it as a sort of redundancy code to check for file corruption.
|
||||
if (fread(&reported_face_count, sizeof(uint32_t), 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
if (reported_face_count != face_count)
|
||||
{
|
||||
logWarning("Face count reported by file (%s) is not equal to actual face count (%s). File could be corrupt!\n", std::to_string(reported_face_count).c_str(), std::to_string(face_count).c_str());
|
||||
}
|
||||
|
||||
//For each face read:
|
||||
//float(x,y,z) = normal, float(X,Y,Z)*3 = vertexes, uint16_t = flags
|
||||
// Every Face is 50 Bytes: Normal(3*float), Vertices(9*float), 2 Bytes Spacer
|
||||
mesh->faces.reserve(faceCount);
|
||||
mesh->vertices.reserve(faceCount);
|
||||
for(unsigned int i=0;i<faceCount;i++)
|
||||
mesh->faces.reserve(face_count);
|
||||
mesh->vertices.reserve(face_count);
|
||||
for (unsigned int i = 0; i < face_count; i++)
|
||||
{
|
||||
if (fread(buffer, 50, 1, f) != 1)
|
||||
{
|
||||
@@ -105,10 +117,31 @@ bool loadMeshSTL_binary(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
bool loadMeshSTL(Mesh* mesh, const char* filename, FMatrix3x3& matrix)
|
||||
{
|
||||
FILE* f = fopen(filename, "r");
|
||||
char buffer[6];
|
||||
if (f == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//Skip any whitespace at the beginning of the file.
|
||||
unsigned long long num_whitespace = 0; //Number of whitespace characters.
|
||||
unsigned char whitespace;
|
||||
if (fread(&whitespace, 1, 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
while(isspace(whitespace))
|
||||
{
|
||||
num_whitespace++;
|
||||
if (fread(&whitespace, 1, 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fseek(f, num_whitespace, SEEK_SET); //Seek to the place after all whitespace (we may have just read too far).
|
||||
|
||||
char buffer[6];
|
||||
if (fread(buffer, 5, 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
|
||||
+27
-15
@@ -5,6 +5,7 @@
|
||||
#include "gcodeExport.h"
|
||||
#include "gcodePlanner.h"
|
||||
#include "infill.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -22,7 +23,7 @@ void PrimeTower::initConfigs(MeshGroup* meshgroup, std::vector<RetractionConfig>
|
||||
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
{
|
||||
config_per_extruder.emplace_back(&retraction_config_per_extruder[extr], "SUPPORT");// so that visualization in the old Cura still works (TODO)
|
||||
config_per_extruder.emplace_back(&retraction_config_per_extruder[extr], PrintFeatureType::Support);// so that visualization in the old Cura still works (TODO)
|
||||
}
|
||||
for (int extr = 0; extr < extruder_count; extr++)
|
||||
{
|
||||
@@ -55,13 +56,14 @@ void PrimeTower::computePrimeTowerMax(SliceDataStorage& storage)
|
||||
{ // compute max_object_height_per_extruder
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
max_object_height_per_extruder[mesh.getSettingAsIndex("extruder_nr")] =
|
||||
std::max( max_object_height_per_extruder[mesh.getSettingAsIndex("extruder_nr")]
|
||||
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]
|
||||
int support_extruder_nr = storage.getSettingAsIndex("support_extruder_nr"); // TODO: support extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_extruder_nr] =
|
||||
std::max( max_object_height_per_extruder[support_extruder_nr]
|
||||
, storage.support.layer_nr_max_filled_layer );
|
||||
int support_roof_extruder_nr = storage.getSettingAsIndex("support_roof_extruder_nr"); // TODO: support roof extruder should be configurable per object
|
||||
max_object_height_per_extruder[support_roof_extruder_nr] =
|
||||
@@ -168,7 +170,7 @@ void PrimeTower::generatePaths3(SliceDataStorage& storage)
|
||||
{
|
||||
|
||||
int n_patterns = 2; // alternating patterns between layers
|
||||
double infill_overlap = 15; // so that it can't be zero
|
||||
double infill_overlap = 15; // so that it can't be zero; EDIT: wtf?
|
||||
|
||||
generateGroundpoly(storage);
|
||||
|
||||
@@ -179,14 +181,22 @@ void PrimeTower::generatePaths3(SliceDataStorage& storage)
|
||||
std::vector<Polygons>& patterns = patterns_per_extruder.back();
|
||||
for (int pattern_idx = 0; pattern_idx < n_patterns; pattern_idx++)
|
||||
{
|
||||
generateLineInfill(ground_poly, -line_width/2, patterns[pattern_idx], line_width, line_width, infill_overlap, 45 + pattern_idx*90);
|
||||
Polygons result_polygons; // should remain empty, since we generate lines pattern!
|
||||
Polygons* in_between = nullptr;
|
||||
bool avoidOverlappingPerimeters = false;
|
||||
int outline_offset = -line_width/2;
|
||||
int line_distance = line_width;
|
||||
double fill_angle = 45 + pattern_idx * 90;
|
||||
Polygons& result_lines = patterns[pattern_idx];
|
||||
Infill infill_comp(EFillMethod::LINES, ground_poly, outline_offset, avoidOverlappingPerimeters, line_width, line_distance, infill_overlap, fill_angle);
|
||||
infill_comp.generate(result_polygons, result_lines, in_between);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PrimeTower::addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed, CommandSocket* command_socket)
|
||||
void PrimeTower::addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
{
|
||||
if (!( storage.max_object_height_second_to_last_extruder >= 0
|
||||
// && storage.getSettingInMicrons("prime_tower_distance") > 0
|
||||
@@ -208,10 +218,10 @@ void PrimeTower::addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer,
|
||||
{
|
||||
wipe = false;
|
||||
}
|
||||
addToGcode3(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed, command_socket);
|
||||
addToGcode3(storage, gcodeLayer, gcode, layer_nr, prev_extruder, prime_tower_dir_outward, wipe, last_prime_tower_poly_printed);
|
||||
}
|
||||
|
||||
void PrimeTower::addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed, CommandSocket* command_socket)
|
||||
void PrimeTower::addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
{
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
{
|
||||
@@ -227,12 +237,14 @@ void PrimeTower::addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer
|
||||
GCodePathConfig& config = config_per_extruder[new_extruder];
|
||||
int start_idx = 0; // TODO: figure out which idx is closest to the far right corner
|
||||
gcodeLayer.addPolygon(ground_poly.back(), start_idx, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern, &config);
|
||||
gcodeLayer.addLinesByOptimizer(pattern, &config, SpaceFillType::Lines);
|
||||
|
||||
last_prime_tower_poly_printed[new_extruder] = layer_nr;
|
||||
|
||||
if (command_socket)
|
||||
command_socket->sendPolygons(SupportType, layer_nr, pattern, config.getLineWidth());
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::Support, layer_nr, pattern, config.getLineWidth());
|
||||
}
|
||||
|
||||
if (wipe)
|
||||
{ //Make sure we wipe the old extruder on the prime tower.
|
||||
@@ -240,7 +252,7 @@ void PrimeTower::addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer
|
||||
}
|
||||
}
|
||||
|
||||
void PrimeTower::addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed, CommandSocket* command_socket)
|
||||
void PrimeTower::addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed)
|
||||
{
|
||||
if (layer_nr > storage.max_object_height_second_to_last_extruder + 1)
|
||||
{
|
||||
|
||||
+3
-3
@@ -54,9 +54,9 @@ public:
|
||||
|
||||
PrimeTower();
|
||||
|
||||
void addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed, CommandSocket* command_socket);
|
||||
void addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed, CommandSocket* command_socket);
|
||||
void addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed, CommandSocket* command_socket);
|
||||
void addToGcode(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
void addToGcode_OLD(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
void addToGcode3(SliceDataStorage& storage, GCodePlanner& gcodeLayer, GCodeExport& gcode, int layer_nr, int prev_extruder, bool prime_tower_dir_outward, bool wipe, int* last_prime_tower_poly_printed);
|
||||
|
||||
};
|
||||
|
||||
|
||||
+12
-13
@@ -4,24 +4,23 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
enum class EPrintFeature : unsigned int // unused!!
|
||||
{ // TODO: use in gcodePathConfigs ?
|
||||
OUTER_WALL,
|
||||
INNER_WALLS,
|
||||
INFILL,
|
||||
SKIN,
|
||||
HELPERS,
|
||||
UNCLASSIFIED,
|
||||
ENUM_COUNT
|
||||
enum class PrintFeatureType
|
||||
{
|
||||
NoneType, // unused, but libArcus depends on it
|
||||
OuterWall,
|
||||
InnerWall,
|
||||
Skin,
|
||||
Support,
|
||||
Skirt,
|
||||
Infill,
|
||||
SupportInfill,
|
||||
MoveCombing,
|
||||
MoveRetraction
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // PRINT_FEATURE
|
||||
+6
-6
@@ -64,22 +64,22 @@ void Progress::init()
|
||||
total_timing = accumulated_time;
|
||||
}
|
||||
|
||||
void Progress::messageProgress(Progress::Stage stage, int progress_in_stage, int progress_in_stage_max, CommandSocket* command_socket)
|
||||
void Progress::messageProgress(Progress::Stage stage, int progress_in_stage, int progress_in_stage_max)
|
||||
{
|
||||
float percentage = calcOverallProgress(stage, float(progress_in_stage) / float(progress_in_stage_max));
|
||||
if (command_socket)
|
||||
if (CommandSocket::getInstance())
|
||||
{
|
||||
command_socket->sendProgress(percentage);
|
||||
CommandSocket::getInstance()->sendProgress(percentage);
|
||||
}
|
||||
|
||||
logProgress(names[(int)stage].c_str(), progress_in_stage, progress_in_stage_max, percentage);
|
||||
}
|
||||
|
||||
void Progress::messageProgressStage(Progress::Stage stage, TimeKeeper* time_keeper, CommandSocket* command_socket)
|
||||
void Progress::messageProgressStage(Progress::Stage stage, TimeKeeper* time_keeper)
|
||||
{
|
||||
if (command_socket)
|
||||
if (CommandSocket::getInstance())
|
||||
{
|
||||
command_socket->sendProgressStage(stage);
|
||||
CommandSocket::getInstance()->sendProgressStage(stage);
|
||||
}
|
||||
|
||||
if (time_keeper)
|
||||
|
||||
+3
-5
@@ -52,22 +52,20 @@ private:
|
||||
public:
|
||||
static void init(); //!< Initialize some values needed in a fast computation of the progress
|
||||
/*!
|
||||
* Message progress over the \p commandSocket and to the terminal (if the command line arg '-p' is provided).
|
||||
* Message progress over the CommandSocket and to the terminal (if the command line arg '-p' is provided).
|
||||
*
|
||||
* \param stage The current stage of processing
|
||||
* \param progress_in_stage Any number giving the progress within the stage
|
||||
* \param progress_in_stage_max The maximal value of \p progress_in_stage
|
||||
* \param commandSocket The command socket over which to communicate the progress.
|
||||
*/
|
||||
static void messageProgress(Stage stage, int progress_in_stage, int progress_in_stage_max, CommandSocket* commandSocket);
|
||||
static void messageProgress(Stage stage, int progress_in_stage, int progress_in_stage_max);
|
||||
/*!
|
||||
* Message the progress stage over the command socket.
|
||||
*
|
||||
* \param stage The current stage
|
||||
* \param timeKeeper The stapwatch keeping track of the timings for each stage (optional)
|
||||
* \param commandSocket The command socket over which to communicate (optional)
|
||||
*/
|
||||
static void messageProgressStage(Stage stage, TimeKeeper* timeKeeper, CommandSocket* commandSocket);
|
||||
static void messageProgressStage(Stage stage, TimeKeeper* timeKeeper);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef SPACE_FILL_TYPE
|
||||
#define SPACE_FILL_TYPE
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Enum class enumerating the strategies with which an area can be occupied with filament
|
||||
*
|
||||
* The walls/perimeters are Polygons
|
||||
* ZigZag infill is PolyLines, and so is following mesh surface mode for non-polygon surfaces
|
||||
* Grid, Triangles and lines infill is Lines
|
||||
*/
|
||||
enum class SpaceFillType
|
||||
{
|
||||
None,
|
||||
Polygons,
|
||||
PolyLines,
|
||||
Lines
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // SPACE_FILL_TYPE
|
||||
+10
-9
@@ -6,11 +6,12 @@
|
||||
|
||||
#include "Progress.h"
|
||||
#include "weaveDataStorage.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void Weaver::weave(MeshGroup* meshgroup, CommandSocket* commandSocket)
|
||||
void Weaver::weave(MeshGroup* meshgroup)
|
||||
{
|
||||
wireFrame.meshgroup = meshgroup;
|
||||
|
||||
@@ -52,8 +53,8 @@ void Weaver::weave(MeshGroup* meshgroup, CommandSocket* commandSocket)
|
||||
for (cura::Slicer* slicer : slicerList)
|
||||
wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygonList);
|
||||
|
||||
if (commandSocket)
|
||||
commandSocket->sendPolygons(Inset0Type, 0, wireFrame.bottom_outline, 1);
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::OuterWall, 0, wireFrame.bottom_outline, 1);
|
||||
|
||||
if (slicerList.empty()) //Wait, there is nothing to slice.
|
||||
{
|
||||
@@ -70,10 +71,10 @@ void Weaver::weave(MeshGroup* meshgroup, CommandSocket* commandSocket)
|
||||
else
|
||||
starting_point_in_layer = (Point(0,0) + meshgroup->max() + meshgroup->min()) / 2;
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::INSET, nullptr, commandSocket);
|
||||
Progress::messageProgressStage(Progress::Stage::INSET, nullptr);
|
||||
for (int layer_idx = starting_layer_idx + 1; layer_idx < layer_count; layer_idx++)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::INSET, layer_idx+1, layer_count, commandSocket); // abuse the progress system of the normal mode of CuraEngine
|
||||
Progress::messageProgress(Progress::Stage::INSET, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
|
||||
|
||||
Polygons parts1;
|
||||
for (cura::Slicer* slicer : slicerList)
|
||||
@@ -84,8 +85,8 @@ void Weaver::weave(MeshGroup* meshgroup, CommandSocket* commandSocket)
|
||||
|
||||
chainify_polygons(parts1, starting_point_in_layer, chainified, false);
|
||||
|
||||
if (commandSocket)
|
||||
commandSocket->sendPolygons(Inset0Type, layer_idx - starting_layer_idx, chainified, 1);
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::OuterWall, layer_idx - starting_layer_idx, chainified, 1);
|
||||
|
||||
if (chainified.size() > 0)
|
||||
{
|
||||
@@ -107,10 +108,10 @@ void Weaver::weave(MeshGroup* meshgroup, CommandSocket* commandSocket)
|
||||
{
|
||||
Polygons* lower_top_parts = &wireFrame.bottom_outline;
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SKIN, nullptr, commandSocket);
|
||||
Progress::messageProgressStage(Progress::Stage::SKIN, nullptr);
|
||||
for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::SKIN, layer_idx+1, wireFrame.layers.size(), commandSocket); // abuse the progress system of the normal mode of CuraEngine
|
||||
Progress::messageProgress(Progress::Stage::SKIN, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
|
||||
|
||||
WeaveLayer& layer = wireFrame.layers[layer_idx];
|
||||
|
||||
|
||||
+1
-2
@@ -61,9 +61,8 @@ public:
|
||||
* Creates a wireframe for the model consisting of horizontal 'flat' parts and connections between consecutive flat parts consisting of UP moves and diagonally DOWN moves.
|
||||
*
|
||||
* \param objects The objects for which to create a wireframe print
|
||||
* \param commandSocket the commandSocket
|
||||
*/
|
||||
void weave(MeshGroup* objects, CommandSocket* commandSocket);
|
||||
void weave(MeshGroup* objects);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
+10
-16
@@ -12,15 +12,15 @@ namespace cura
|
||||
{
|
||||
|
||||
|
||||
void Wireframe2gcode::writeGCode(CommandSocket* commandSocket)
|
||||
void Wireframe2gcode::writeGCode()
|
||||
{
|
||||
|
||||
gcode.preSetup(wireFrame.meshgroup);
|
||||
|
||||
if (commandSocket)
|
||||
commandSocket->beginGCode();
|
||||
if (CommandSocket::getInstance())
|
||||
CommandSocket::getInstance()->beginGCode();
|
||||
|
||||
processStartingCode(commandSocket);
|
||||
processStartingCode();
|
||||
|
||||
int maxObjectHeight;
|
||||
if (wireFrame.layers.empty())
|
||||
@@ -32,7 +32,7 @@ void Wireframe2gcode::writeGCode(CommandSocket* commandSocket)
|
||||
maxObjectHeight = wireFrame.layers.back().z1;
|
||||
}
|
||||
|
||||
processSkirt(commandSocket);
|
||||
processSkirt();
|
||||
|
||||
|
||||
unsigned int total_layers = wireFrame.layers.size();
|
||||
@@ -76,10 +76,10 @@ void Wireframe2gcode::writeGCode(CommandSocket* commandSocket)
|
||||
gcode.writeMove(segment.to, speedBottom, extrusion_per_mm_flat);
|
||||
}
|
||||
);
|
||||
Progress::messageProgressStage(Progress::Stage::EXPORT, nullptr, commandSocket);
|
||||
Progress::messageProgressStage(Progress::Stage::EXPORT, nullptr);
|
||||
for (unsigned int layer_nr = 0; layer_nr < wireFrame.layers.size(); layer_nr++)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::EXPORT, layer_nr+1, total_layers, commandSocket); // abuse the progress system of the normal mode of CuraEngine
|
||||
Progress::messageProgress(Progress::Stage::EXPORT, layer_nr+1, total_layers); // abuse the progress system of the normal mode of CuraEngine
|
||||
|
||||
WeaveLayer& layer = wireFrame.layers[layer_nr];
|
||||
|
||||
@@ -166,12 +166,6 @@ void Wireframe2gcode::writeGCode(CommandSocket* commandSocket)
|
||||
gcode.writeFanCommand(0);
|
||||
|
||||
finalize();
|
||||
|
||||
if (commandSocket)
|
||||
{
|
||||
commandSocket->sendGCodeLayer();
|
||||
commandSocket->endSendSlicedObject();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -550,11 +544,11 @@ Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBas
|
||||
standard_retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
|
||||
}
|
||||
|
||||
void Wireframe2gcode::processStartingCode(CommandSocket* command_socket)
|
||||
void Wireframe2gcode::processStartingCode()
|
||||
{
|
||||
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
|
||||
{
|
||||
if (!command_socket)
|
||||
if (!CommandSocket::isInstantiated())
|
||||
{
|
||||
gcode.writeCode(";FLAVOR:UltiGCode\n;TIME:666\n;MATERIAL:666\n;MATERIAL2:-1\n");
|
||||
}
|
||||
@@ -595,7 +589,7 @@ void Wireframe2gcode::processStartingCode(CommandSocket* command_socket)
|
||||
}
|
||||
|
||||
|
||||
void Wireframe2gcode::processSkirt(CommandSocket* commandSocket)
|
||||
void Wireframe2gcode::processSkirt()
|
||||
{
|
||||
if (wireFrame.bottom_outline.size() == 0) //If we have no layers, don't create a skirt either.
|
||||
{
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
|
||||
Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBase* settings_base);
|
||||
|
||||
void writeGCode(CommandSocket* commandSocket);
|
||||
void writeGCode();
|
||||
|
||||
|
||||
private:
|
||||
@@ -80,12 +80,12 @@ private:
|
||||
/*!
|
||||
* Startup gcode: nozzle temp up, retraction settings, bed temp
|
||||
*/
|
||||
void processStartingCode(CommandSocket* command_socket);
|
||||
void processStartingCode();
|
||||
|
||||
/*!
|
||||
* Lay down a skirt
|
||||
*/
|
||||
void processSkirt(CommandSocket* commandSocket);
|
||||
void processSkirt();
|
||||
|
||||
/*!
|
||||
* End gcode: nozzle temp down
|
||||
|
||||
+133
-119
@@ -6,9 +6,7 @@
|
||||
#include <thread>
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef ARCUS
|
||||
#include <Arcus/Socket.h>
|
||||
#endif
|
||||
|
||||
#include <string> // stoi
|
||||
|
||||
@@ -25,7 +23,8 @@ namespace cura {
|
||||
#define FLOATS_PER_VECTOR 3
|
||||
#define VECTORS_PER_FACE 3
|
||||
|
||||
#ifdef ARCUS
|
||||
CommandSocket* CommandSocket::instance = nullptr; // instantiate instance
|
||||
|
||||
class CommandSocket::Private
|
||||
{
|
||||
public:
|
||||
@@ -68,64 +67,59 @@ public:
|
||||
// Print object that olds one or more meshes that need to be sliced.
|
||||
std::vector< std::shared_ptr<MeshGroup> > objects_to_slice;
|
||||
};
|
||||
#endif
|
||||
|
||||
CommandSocket::CommandSocket()
|
||||
#ifdef ARCUS
|
||||
: d(new Private)
|
||||
#endif
|
||||
: private_data(new Private)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
FffProcessor::getInstance()->setCommandSocket(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
CommandSocket* CommandSocket::getInstance()
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
void CommandSocket::instantiate()
|
||||
{
|
||||
instance = new CommandSocket();
|
||||
}
|
||||
|
||||
bool CommandSocket::isInstantiated()
|
||||
{
|
||||
return instance != nullptr;
|
||||
}
|
||||
|
||||
|
||||
void CommandSocket::connect(const std::string& ip, int port)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
d->socket = new Arcus::Socket();
|
||||
//d->socket->registerMessageType(1, &Cura::ObjectList::default_instance());
|
||||
d->socket->registerMessageType(1, &cura::proto::Slice::default_instance());
|
||||
d->socket->registerMessageType(2, &cura::proto::SlicedObjectList::default_instance());
|
||||
d->socket->registerMessageType(3, &cura::proto::Progress::default_instance());
|
||||
d->socket->registerMessageType(4, &cura::proto::GCodeLayer::default_instance());
|
||||
d->socket->registerMessageType(5, &cura::proto::ObjectPrintTime::default_instance());
|
||||
d->socket->registerMessageType(6, &cura::proto::SettingList::default_instance());
|
||||
d->socket->registerMessageType(7, &cura::proto::GCodePrefix::default_instance());
|
||||
private_data->socket = new Arcus::Socket();
|
||||
//private_data->socket->registerMessageType(1, &Cura::ObjectList::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::Slice::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::SlicedObjectList::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::Progress::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::GCodeLayer::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::ObjectPrintTime::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::SettingList::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::GCodePrefix::default_instance());
|
||||
private_data->socket->registerMessageType(&cura::proto::SlicingFinished::default_instance());
|
||||
|
||||
d->socket->connect(ip, port);
|
||||
private_data->socket->connect(ip, port);
|
||||
|
||||
log("Connecting to %s:%i", ip.c_str(), port);
|
||||
|
||||
while(private_data->socket->getState() != Arcus::SocketState::Connected && private_data->socket->getState() != Arcus::SocketState::Error)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
log("Connected to %s:%i", ip.c_str(), port);
|
||||
|
||||
bool slice_another_time = true;
|
||||
|
||||
// Start & continue listening as long as socket is not closed and there is no error.
|
||||
while(d->socket->state() != Arcus::SocketState::Closed && d->socket->state() != Arcus::SocketState::Error && slice_another_time)
|
||||
while(private_data->socket->getState() != Arcus::SocketState::Closed && private_data->socket->getState() != Arcus::SocketState::Error && slice_another_time)
|
||||
{
|
||||
//If there is an object to slice, do so.
|
||||
if(d->objects_to_slice.size())
|
||||
{
|
||||
FffProcessor::getInstance()->resetFileNumber();
|
||||
for(auto object : d->objects_to_slice)
|
||||
{
|
||||
if(!FffProcessor::getInstance()->processMeshGroup(object.get()))
|
||||
{
|
||||
logError("Slicing mesh group failed!");
|
||||
}
|
||||
}
|
||||
d->objects_to_slice.clear();
|
||||
FffProcessor::getInstance()->finalize();
|
||||
sendGCodeLayer();
|
||||
sendPrintTime();
|
||||
slice_another_time = false; // TODO: remove this when multiple slicing with CuraEngine is safe
|
||||
//TODO: Support all-at-once/one-at-a-time printing
|
||||
//d->processor->processModel(d->object_to_slice.get());
|
||||
//d->object_to_slice.reset();
|
||||
//d->processor->resetFileNumber();
|
||||
|
||||
//sendPrintTime();
|
||||
}
|
||||
|
||||
// Actually start handling messages.
|
||||
Arcus::MessagePtr message = d->socket->takeNextMessage();
|
||||
Arcus::MessagePtr message = private_data->socket->takeNextMessage();
|
||||
cura::proto::SettingList* setting_list = dynamic_cast<cura::proto::SettingList*>(message.get());
|
||||
if(setting_list)
|
||||
{
|
||||
@@ -142,33 +136,63 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
if(slice)
|
||||
{
|
||||
// Reset object counts
|
||||
d->object_count = 0;
|
||||
d->object_ids.clear();
|
||||
private_data->object_count = 0;
|
||||
private_data->object_ids.clear();
|
||||
for(auto object : slice->object_lists())
|
||||
{
|
||||
handleObjectList(&object);
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
|
||||
if(!d->socket->errorString().empty())
|
||||
//If there is an object to slice, do so.
|
||||
if(private_data->objects_to_slice.size())
|
||||
{
|
||||
logError("%s\n", d->socket->errorString().data());
|
||||
d->socket->clearError();
|
||||
FffProcessor::getInstance()->resetFileNumber();
|
||||
for(auto object : private_data->objects_to_slice)
|
||||
{
|
||||
if(!FffProcessor::getInstance()->processMeshGroup(object.get()))
|
||||
{
|
||||
logError("Slicing mesh group failed!");
|
||||
}
|
||||
}
|
||||
private_data->objects_to_slice.clear();
|
||||
FffProcessor::getInstance()->finalize();
|
||||
flushGcode();
|
||||
sendPrintTime();
|
||||
sendFinishedSlicing();
|
||||
slice_another_time = false; // TODO: remove this when multiple slicing with CuraEngine is safe
|
||||
//TODO: Support all-at-once/one-at-a-time printing
|
||||
//private_data->processor->processModel(private_data->object_to_slice.get());
|
||||
//private_data->object_to_slice.reset();
|
||||
//private_data->processor->resetFileNumber();
|
||||
|
||||
//sendPrintTime();
|
||||
}
|
||||
|
||||
if(private_data->socket->getLastError().isValid())
|
||||
{
|
||||
logError("%s\n", private_data->socket->getLastError().toString().c_str());
|
||||
private_data->socket->clearError();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
#endif
|
||||
|
||||
private_data->socket->close();
|
||||
}
|
||||
|
||||
#ifdef ARCUS
|
||||
void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
{
|
||||
if(list->objects_size() <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FMatrix3x3 matrix;
|
||||
//d->object_count = 0;
|
||||
//d->object_ids.clear();
|
||||
d->objects_to_slice.push_back(std::make_shared<MeshGroup>(FffProcessor::getInstance()));
|
||||
MeshGroup* meshgroup = d->objects_to_slice.back().get();
|
||||
//private_data->object_count = 0;
|
||||
//private_data->object_ids.clear();
|
||||
private_data->objects_to_slice.push_back(std::make_shared<MeshGroup>(FffProcessor::getInstance()));
|
||||
MeshGroup* meshgroup = private_data->objects_to_slice.back().get();
|
||||
|
||||
for(auto setting : list->settings())
|
||||
{
|
||||
@@ -182,6 +206,14 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
|
||||
for(auto object : list->objects())
|
||||
{
|
||||
int bytes_per_face = BYTES_PER_FLOAT * FLOATS_PER_VECTOR * VECTORS_PER_FACE;
|
||||
int face_count = object.vertices().size() / bytes_per_face;
|
||||
|
||||
if(face_count <= 0)
|
||||
{
|
||||
logWarning("Got an empty mesh, ignoring it!");
|
||||
continue;
|
||||
}
|
||||
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR("solid Cura_out\n");
|
||||
int extruder_train_nr = 0; // TODO: make primary extruder configurable!
|
||||
for(auto setting : object.settings())
|
||||
@@ -197,8 +229,6 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
meshgroup->meshes.push_back(extruder_train); //Construct a new mesh (with the corresponding extruder train as settings parent object) and put it into MeshGroup's mesh list.
|
||||
Mesh& mesh = meshgroup->meshes.back();
|
||||
|
||||
int bytes_per_face = BYTES_PER_FLOAT * FLOATS_PER_VECTOR * VECTORS_PER_FACE;
|
||||
int face_count = object.vertices().size() / bytes_per_face;
|
||||
for(int i = 0; i < face_count; ++i)
|
||||
{
|
||||
//TODO: Apply matrix
|
||||
@@ -225,11 +255,11 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
mesh.setSetting(setting.name(), setting.value());
|
||||
}
|
||||
|
||||
d->object_ids.push_back(object.id());
|
||||
private_data->object_ids.push_back(object.id());
|
||||
mesh.finish();
|
||||
}
|
||||
|
||||
d->object_count++;
|
||||
private_data->object_count++;
|
||||
meshgroup->finalize();
|
||||
}
|
||||
|
||||
@@ -240,54 +270,47 @@ void CommandSocket::handleSettingList(cura::proto::SettingList* list)
|
||||
FffProcessor::getInstance()->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void CommandSocket::sendLayerInfo(int layer_nr, int32_t z, int32_t height)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if(!d->current_sliced_object)
|
||||
if(!private_data->current_sliced_object)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cura::proto::Layer* layer = d->getLayerById(layer_nr);
|
||||
cura::proto::Layer* layer = private_data->getLayerById(layer_nr);
|
||||
layer->set_height(z);
|
||||
layer->set_thickness(height);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::sendPolygons(PolygonType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
void CommandSocket::sendPolygons(PrintFeatureType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if(!d->current_sliced_object)
|
||||
if(!private_data->current_sliced_object)
|
||||
return;
|
||||
|
||||
if (polygons.size() == 0)
|
||||
return;
|
||||
|
||||
cura::proto::Layer* layer = d->getLayerById(layer_nr);
|
||||
cura::proto::Layer* proto_layer = private_data->getLayerById(layer_nr);
|
||||
|
||||
for(unsigned int i = 0; i < polygons.size(); ++i)
|
||||
{
|
||||
cura::proto::Polygon* p = layer->add_polygons();
|
||||
cura::proto::Polygon* p = proto_layer->add_polygons();
|
||||
p->set_type(static_cast<cura::proto::Polygon_Type>(type));
|
||||
std::string polydata;
|
||||
polydata.append(reinterpret_cast<const char*>(polygons[i].data()), polygons[i].size() * sizeof(Point));
|
||||
p->set_points(polydata);
|
||||
p->set_line_width(line_width);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::sendProgress(float amount)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
auto message = std::make_shared<cura::proto::Progress>();
|
||||
amount /= d->object_count;
|
||||
amount += d->sliced_objects * (1. / d->object_count);
|
||||
amount /= private_data->object_count;
|
||||
amount += private_data->sliced_objects * (1. / private_data->object_count);
|
||||
message->set_amount(amount);
|
||||
d->socket->sendMessage(message);
|
||||
#endif
|
||||
private_data->socket->sendMessage(message);
|
||||
}
|
||||
|
||||
void CommandSocket::sendProgressStage(Progress::Stage stage)
|
||||
@@ -297,12 +320,10 @@ void CommandSocket::sendProgressStage(Progress::Stage stage)
|
||||
|
||||
void CommandSocket::sendPrintTime()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
auto message = std::make_shared<cura::proto::ObjectPrintTime>();
|
||||
message->set_time(FffProcessor::getInstance()->getTotalPrintTime());
|
||||
message->set_material_amount(FffProcessor::getInstance()->getTotalFilamentUsed(0));
|
||||
d->socket->sendMessage(message);
|
||||
#endif
|
||||
private_data->socket->sendMessage(message);
|
||||
}
|
||||
|
||||
void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float print_time)
|
||||
@@ -316,68 +337,62 @@ void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float
|
||||
|
||||
void CommandSocket::beginSendSlicedObject()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
if(!d->sliced_object_list)
|
||||
if(!private_data->sliced_object_list)
|
||||
{
|
||||
d->sliced_object_list = std::make_shared<cura::proto::SlicedObjectList>();
|
||||
private_data->sliced_object_list = std::make_shared<cura::proto::SlicedObjectList>();
|
||||
}
|
||||
|
||||
d->current_sliced_object = d->sliced_object_list->add_objects();
|
||||
d->current_sliced_object->set_id(d->object_ids[d->sliced_objects]);
|
||||
#endif
|
||||
private_data->current_sliced_object = private_data->sliced_object_list->add_objects();
|
||||
private_data->current_sliced_object->set_id(private_data->object_ids[private_data->sliced_objects]);
|
||||
}
|
||||
|
||||
void CommandSocket::endSendSlicedObject()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
d->sliced_objects++;
|
||||
d->current_layer_offset = d->current_layer_count;
|
||||
std::cout << "End sliced object called. sliced objects " << d->sliced_objects << " object count: " << d->object_count << std::endl;
|
||||
private_data->sliced_objects++;
|
||||
private_data->current_layer_offset = private_data->current_layer_count;
|
||||
std::cout << "End sliced object called. Sliced objects " << private_data->sliced_objects << " object count: " << private_data->object_count << std::endl;
|
||||
|
||||
std::cout << "current layer count" << d->current_layer_count << std::endl;
|
||||
std::cout << "current layer offset" << d->current_layer_offset << std::endl;
|
||||
|
||||
if(d->sliced_objects >= d->object_count)
|
||||
if(private_data->sliced_objects >= private_data->object_count)
|
||||
{
|
||||
d->socket->sendMessage(d->sliced_object_list);
|
||||
d->sliced_objects = 0;
|
||||
d->current_layer_count = 0;
|
||||
d->current_layer_offset = 0;
|
||||
d->sliced_object_list.reset();
|
||||
d->current_sliced_object = nullptr;
|
||||
private_data->socket->sendMessage(private_data->sliced_object_list);
|
||||
private_data->sliced_objects = 0;
|
||||
private_data->current_layer_count = 0;
|
||||
private_data->current_layer_offset = 0;
|
||||
private_data->sliced_object_list.reset();
|
||||
private_data->current_sliced_object = nullptr;
|
||||
auto done_message = std::make_shared<cura::proto::SlicingFinished>();
|
||||
private_data->socket->sendMessage(done_message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::sendFinishedSlicing()
|
||||
{
|
||||
std::shared_ptr<cura::proto::SlicingFinished> done_message = std::make_shared<cura::proto::SlicingFinished>();
|
||||
private_data->socket->sendMessage(done_message);
|
||||
}
|
||||
|
||||
void CommandSocket::beginGCode()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
FffProcessor::getInstance()->setTargetStream(&d->gcode_output_stream);
|
||||
#endif
|
||||
FffProcessor::getInstance()->setTargetStream(&private_data->gcode_output_stream);
|
||||
}
|
||||
|
||||
void CommandSocket::sendGCodeLayer()
|
||||
void CommandSocket::flushGcode()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
auto message = std::make_shared<cura::proto::GCodeLayer>();
|
||||
message->set_id(d->object_ids[0]);
|
||||
message->set_data(d->gcode_output_stream.str());
|
||||
d->socket->sendMessage(message);
|
||||
message->set_id(private_data->object_ids[0]);
|
||||
message->set_data(private_data->gcode_output_stream.str());
|
||||
private_data->socket->sendMessage(message);
|
||||
|
||||
d->gcode_output_stream.str("");
|
||||
#endif
|
||||
private_data->gcode_output_stream.str("");
|
||||
}
|
||||
|
||||
void CommandSocket::sendGCodePrefix(std::string prefix)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
auto message = std::make_shared<cura::proto::GCodePrefix>();
|
||||
message->set_data(prefix);
|
||||
d->socket->sendMessage(message);
|
||||
#endif
|
||||
private_data->socket->sendMessage(message);
|
||||
}
|
||||
|
||||
#ifdef ARCUS
|
||||
cura::proto::Layer* CommandSocket::Private::getLayerById(int id)
|
||||
{
|
||||
id += current_layer_offset;
|
||||
@@ -398,6 +413,5 @@ cura::proto::Layer* CommandSocket::Private::getLayerById(int id)
|
||||
|
||||
return layer;
|
||||
}
|
||||
#endif
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+44
-15
@@ -5,20 +5,29 @@
|
||||
#include "utils/polygon.h"
|
||||
#include "settings.h"
|
||||
#include "Progress.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#ifdef ARCUS
|
||||
#include "Cura.pb.h"
|
||||
#endif
|
||||
|
||||
namespace cura
|
||||
{
|
||||
namespace cura {
|
||||
|
||||
|
||||
class CommandSocket
|
||||
{
|
||||
private:
|
||||
static CommandSocket* instance; //!< May be a nullptr in case it hasn't been instantiated.
|
||||
|
||||
CommandSocket(); //!< The single constructor is known only privately, since this class is similar to a singleton class (except the single object doesn't need to be instantiated)
|
||||
|
||||
public:
|
||||
CommandSocket();
|
||||
static CommandSocket* getInstance(); //!< Get the CommandSocket instance, or nullptr if it hasn't been instantiated.
|
||||
|
||||
static void instantiate(); //!< Instantiate the CommandSocket.
|
||||
|
||||
static bool isInstantiated(); //!< Check whether the singleton is instantiated
|
||||
|
||||
/*!
|
||||
* Connect with the GUI
|
||||
* This creates and initialises the arcus socket and then continues listening for messages.
|
||||
@@ -26,8 +35,7 @@ public:
|
||||
* \param port int of the port to connect with.
|
||||
*/
|
||||
void connect(const std::string& ip, int port);
|
||||
|
||||
#ifdef ARCUS
|
||||
|
||||
/*!
|
||||
* Handler for ObjectList message.
|
||||
* Loads all objects from the message and starts the slicing process
|
||||
@@ -39,18 +47,22 @@ public:
|
||||
* This simply sets all the settings by using key value pair
|
||||
*/
|
||||
void handleSettingList(cura::proto::SettingList* list);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* Does nothing at the moment
|
||||
* Send info on a layer to be displayed by the forntend: set the z and the thickness of the layer.
|
||||
*/
|
||||
void sendLayerInfo(int layer_nr, int32_t z, int32_t height);
|
||||
|
||||
/*!
|
||||
* Send a polygon to the engine. This is used for the layerview in the GUI
|
||||
*/
|
||||
void sendPolygons(cura::PolygonType type, int layer_nr, cura::Polygons& polygons, int line_width);
|
||||
|
||||
void sendPolygons(cura::PrintFeatureType type, int layer_nr, cura::Polygons& polygons, int line_width);
|
||||
|
||||
/*!
|
||||
* Send a polygon to the engine if the command socket is instantiated. This is used for the layerview in the GUI
|
||||
*/
|
||||
static void sendPolygonsToCommandSocket(cura::PrintFeatureType type, int layer_nr, cura::Polygons& polygons, int line_width);
|
||||
|
||||
/*!
|
||||
* Send progress to GUI
|
||||
*/
|
||||
@@ -71,18 +83,35 @@ public:
|
||||
*/
|
||||
void sendPrintMaterialForObject(int index, int extruder_nr, float material_amount);
|
||||
|
||||
/*!
|
||||
* Start the slicing of a new meshgroup
|
||||
*/
|
||||
void beginSendSlicedObject();
|
||||
|
||||
/*!
|
||||
* Conclude the slicing of the current meshgroup, so that we can start the next
|
||||
*/
|
||||
void endSendSlicedObject();
|
||||
|
||||
/*!
|
||||
* \brief Sends a message to indicate that all the slicing is done.
|
||||
*
|
||||
* This should indicate that no more data (g-code, prefix/postfix, metadata
|
||||
* or otherwise) should be sent any more regarding the latest slice job.
|
||||
*/
|
||||
void sendFinishedSlicing();
|
||||
|
||||
void beginGCode();
|
||||
void sendGCodeLayer();
|
||||
|
||||
/*!
|
||||
* Flush the gcode in gcode_output_stream into a message queued in the socket.
|
||||
*/
|
||||
void flushGcode();
|
||||
void sendGCodePrefix(std::string prefix);
|
||||
|
||||
#ifdef ARCUS
|
||||
private:
|
||||
class Private;
|
||||
const std::unique_ptr<Private> d;
|
||||
#endif
|
||||
const std::unique_ptr<Private> private_data;
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+41
-7
@@ -5,13 +5,13 @@
|
||||
|
||||
#include "gcodeExport.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
GCodeExport::GCodeExport()
|
||||
: output_stream(&std::cout)
|
||||
, currentPosition(0,0,MM2INT(20))
|
||||
, commandSocket(nullptr)
|
||||
, layer_nr(0)
|
||||
{
|
||||
current_e_value = 0;
|
||||
@@ -29,8 +29,7 @@ GCodeExport::~GCodeExport()
|
||||
{
|
||||
}
|
||||
|
||||
void GCodeExport::setCommandSocketAndLayerNr(CommandSocket* commandSocket_, unsigned int layer_nr_) {
|
||||
commandSocket = commandSocket_;
|
||||
void GCodeExport::setLayerNr(unsigned int layer_nr_) {
|
||||
layer_nr = layer_nr_;
|
||||
}
|
||||
|
||||
@@ -184,6 +183,41 @@ void GCodeExport::writeTypeComment(const char* type)
|
||||
{
|
||||
*output_stream << ";TYPE:" << type << "\n";
|
||||
}
|
||||
|
||||
void GCodeExport::writeTypeComment(PrintFeatureType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PrintFeatureType::OuterWall:
|
||||
*output_stream << ";TYPE:WALL-OUTER\n";
|
||||
break;
|
||||
case PrintFeatureType::InnerWall:
|
||||
*output_stream << ";TYPE:WALL-INNER\n";
|
||||
break;
|
||||
case PrintFeatureType::Skin:
|
||||
*output_stream << ";TYPE:SKIN\n";
|
||||
break;
|
||||
case PrintFeatureType::Support:
|
||||
*output_stream << ";TYPE:SUPPORT\n";
|
||||
break;
|
||||
case PrintFeatureType::Skirt:
|
||||
*output_stream << ";TYPE:SKIRT\n";
|
||||
break;
|
||||
case PrintFeatureType::Infill:
|
||||
*output_stream << ";TYPE:FILL\n";
|
||||
break;
|
||||
case PrintFeatureType::SupportInfill:
|
||||
*output_stream << ";TYPE:SUPPORT\n";
|
||||
break;
|
||||
case PrintFeatureType::MoveCombing:
|
||||
case PrintFeatureType::MoveRetraction:
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GCodeExport::writeLayerComment(int layer_nr)
|
||||
{
|
||||
*output_stream << ";LAYER:" << layer_nr << "\n";
|
||||
@@ -366,16 +400,16 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
else
|
||||
{
|
||||
*output_stream << "G0";
|
||||
|
||||
if (commandSocket)
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
// we should send this travel as a non-retraction move
|
||||
cura::Polygons travelPoly;
|
||||
PolygonRef travel = travelPoly.newPoly();
|
||||
travel.add(Point(currentPosition.x, currentPosition.y));
|
||||
travel.add(Point(x, y));
|
||||
commandSocket->sendPolygons(extruder_attr[current_extruder].retraction_e_amount_current ? MoveRetractionType : MoveCombingType, layer_nr, travelPoly, extruder_attr[current_extruder].retraction_e_amount_current ? MM2INT(0.2) : MM2INT(0.1));
|
||||
}
|
||||
CommandSocket::getInstance()->sendPolygons(extruder_attr[current_extruder].retraction_e_amount_current ? PrintFeatureType::MoveRetraction : PrintFeatureType::MoveCombing, layer_nr, travelPoly, extruder_attr[current_extruder].retraction_e_amount_current ? MM2INT(0.2) : MM2INT(0.1));
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSpeed != speed)
|
||||
|
||||
+19
-18
@@ -18,13 +18,9 @@ namespace cura {
|
||||
struct CoastingConfig
|
||||
{
|
||||
bool coasting_enable;
|
||||
double coasting_volume_move;
|
||||
double coasting_speed_move;
|
||||
double coasting_min_volume_move;
|
||||
|
||||
double coasting_volume_retract;
|
||||
double coasting_speed_retract;
|
||||
double coasting_min_volume_retract;
|
||||
double coasting_volume;
|
||||
double coasting_speed;
|
||||
double coasting_min_volume;
|
||||
};
|
||||
|
||||
class RetractionConfig
|
||||
@@ -44,19 +40,19 @@ public:
|
||||
class GCodePathConfig
|
||||
{
|
||||
private:
|
||||
double speed_base; //!< movement speed (mm/s) specific to this print feature
|
||||
double speed_current; //!< current movement speed (mm/s) (modified by layer_nr etc.)
|
||||
double speed_iconic; //!< movement speed (mm/s) specific to this print feature
|
||||
double speed; //!< current movement speed (mm/s) (modified by layer_nr etc.)
|
||||
int line_width; //!< width of the line extruded
|
||||
double flow; //!< extrusion flow in %
|
||||
int layer_thickness; //!< layer height
|
||||
double extrusion_mm3_per_mm;//!< mm^3 filament moved per mm line extruded
|
||||
public:
|
||||
const char* name; //!< name of the feature type
|
||||
PrintFeatureType type; //!< name of the feature type
|
||||
bool spiralize;
|
||||
RetractionConfig *const retraction_config;
|
||||
|
||||
// GCodePathConfig() : speed(0), line_width(0), extrusion_mm3_per_mm(0.0), name(nullptr), spiralize(false), retraction_config(nullptr) {}
|
||||
GCodePathConfig(RetractionConfig* retraction_config, const char* name) : speed_base(0), speed_current(0), line_width(0), extrusion_mm3_per_mm(0.0), name(name), spiralize(false), retraction_config(retraction_config) {}
|
||||
GCodePathConfig(RetractionConfig* retraction_config, PrintFeatureType type) : speed_iconic(0), speed(0), line_width(0), extrusion_mm3_per_mm(0.0), type(type), spiralize(false), retraction_config(retraction_config) {}
|
||||
|
||||
/*!
|
||||
* Initialize some of the member variables.
|
||||
@@ -65,8 +61,8 @@ public:
|
||||
*/
|
||||
void init(double speed, int line_width, double flow)
|
||||
{
|
||||
speed_base = speed;
|
||||
this->speed_current = speed;
|
||||
speed_iconic = speed;
|
||||
this->speed = speed;
|
||||
this->line_width = line_width;
|
||||
this->flow = flow;
|
||||
}
|
||||
@@ -91,7 +87,7 @@ public:
|
||||
*/
|
||||
void smoothSpeed(double min_speed, int layer_nr, double max_speed_layer)
|
||||
{
|
||||
speed_current = (speed_base*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
|
||||
speed = (speed_iconic*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -107,7 +103,7 @@ public:
|
||||
*/
|
||||
double getSpeed()
|
||||
{
|
||||
return speed_current;
|
||||
return speed;
|
||||
}
|
||||
|
||||
int getLineWidth()
|
||||
@@ -120,6 +116,11 @@ public:
|
||||
return line_width == 0;
|
||||
}
|
||||
|
||||
double getFlowPercentage()
|
||||
{
|
||||
return flow;
|
||||
}
|
||||
|
||||
private:
|
||||
void calculateExtrusion()
|
||||
{
|
||||
@@ -191,8 +192,7 @@ private:
|
||||
|
||||
bool is_volumatric;
|
||||
bool firmware_retract; //!< whether retractions are done in the firmware, or hardcoded in E values.
|
||||
|
||||
CommandSocket* commandSocket; //!< for sending travel data
|
||||
|
||||
unsigned int layer_nr; //!< for sending travel data
|
||||
|
||||
public:
|
||||
@@ -200,7 +200,7 @@ public:
|
||||
GCodeExport();
|
||||
~GCodeExport();
|
||||
|
||||
void setCommandSocketAndLayerNr(CommandSocket* commandSocket, unsigned int layer_nr);
|
||||
void setLayerNr(unsigned int layer_nr);
|
||||
|
||||
void setOutputStream(std::ostream* stream);
|
||||
|
||||
@@ -238,6 +238,7 @@ public:
|
||||
|
||||
void writeComment(std::string comment);
|
||||
void writeTypeComment(const char* type);
|
||||
void writeTypeComment(PrintFeatureType type);
|
||||
void writeLayerComment(int layer_nr);
|
||||
void writeLayerCountComment(int layer_count);
|
||||
|
||||
|
||||
+74
-88
@@ -22,17 +22,18 @@ TimeMaterialEstimates& TimeMaterialEstimates::operator-=(const TimeMaterialEstim
|
||||
return *this;
|
||||
}
|
||||
|
||||
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, float flow)
|
||||
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow)
|
||||
{
|
||||
std::vector<GCodePath>& paths = extruder_plans.back().paths;
|
||||
if (paths.size() > 0 && paths[paths.size()-1].config == config && !paths[paths.size()-1].done && paths[paths.size()-1].flow == flow)
|
||||
return &paths[paths.size()-1];
|
||||
paths.push_back(GCodePath());
|
||||
GCodePath* ret = &paths[paths.size()-1];
|
||||
if (paths.size() > 0 && paths.back().config == config && !paths.back().done && paths.back().flow == flow)
|
||||
return &paths.back();
|
||||
paths.emplace_back();
|
||||
GCodePath* ret = &paths.back();
|
||||
ret->retract = false;
|
||||
ret->config = config;
|
||||
ret->done = false;
|
||||
ret->flow = flow;
|
||||
ret->space_fill_type = space_fill_type;
|
||||
if (config != &storage.travel_config)
|
||||
{
|
||||
last_retraction_config = config->retraction_config;
|
||||
@@ -47,9 +48,8 @@ void GCodePlanner::forceNewPathStart()
|
||||
paths[paths.size()-1].done = true;
|
||||
}
|
||||
|
||||
GCodePlanner::GCodePlanner(CommandSocket* commandSocket, SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, bool retraction_combing, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
GCodePlanner::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, bool retraction_combing, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
, commandSocket(commandSocket)
|
||||
, layer_nr(layer_nr)
|
||||
, z(z)
|
||||
, layer_thickness(layer_thickness)
|
||||
@@ -105,7 +105,6 @@ void GCodePlanner::setIsInside(bool _is_inside)
|
||||
is_inside = _is_inside;
|
||||
}
|
||||
|
||||
|
||||
bool GCodePlanner::setExtruder(int extruder)
|
||||
{
|
||||
if (extruder == extruder_plans.back().extruder)
|
||||
@@ -115,6 +114,7 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
{ // handle end position of the prev extruder
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder_plans.back().extruder);
|
||||
bool end_pos_absolute = train->getSettingBoolean("machine_extruder_end_pos_abs");
|
||||
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
||||
Point end_pos(train->getSettingInMicrons("machine_extruder_end_pos_x"), train->getSettingInMicrons("machine_extruder_end_pos_y"));
|
||||
if (!end_pos_absolute)
|
||||
{
|
||||
@@ -122,25 +122,18 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
}
|
||||
else
|
||||
{
|
||||
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
||||
end_pos += extruder_offset; // absolute end pos is given as a head position
|
||||
}
|
||||
addTravel(end_pos); // + extruder_offset cause it
|
||||
}
|
||||
if (extruder_plans.back().paths.empty() && extruder_plans.back().inserts.empty())
|
||||
{ // first extruder plan in a layer might be empty, cause it is made with the last extruder planned in the previous layer
|
||||
extruder_plans.back().extruder = extruder;
|
||||
}
|
||||
else
|
||||
{
|
||||
extruder_plans.emplace_back(extruder);
|
||||
}
|
||||
extruder_plans.emplace_back(extruder);
|
||||
|
||||
// forceNewPathStart(); // automatic by the fact that we start a new ExtruderPlan
|
||||
|
||||
{ // handle starting pos of the new extruder
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
bool start_pos_absolute = train->getSettingBoolean("machine_extruder_start_pos_abs");
|
||||
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
||||
Point start_pos(train->getSettingInMicrons("machine_extruder_start_pos_x"), train->getSettingInMicrons("machine_extruder_start_pos_y"));
|
||||
if (!start_pos_absolute)
|
||||
{
|
||||
@@ -148,7 +141,6 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
}
|
||||
else
|
||||
{
|
||||
Point extruder_offset(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
||||
start_pos += extruder_offset; // absolute start pos is given as a head position
|
||||
}
|
||||
lastPosition = start_pos;
|
||||
@@ -158,13 +150,13 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
|
||||
void GCodePlanner::moveInsideCombBoundary(int distance)
|
||||
{
|
||||
int max_dist = MM2INT(2.0); // if we are further than this distance, we conclude we are not inside even though we thought we were.
|
||||
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
|
||||
Point p = lastPosition; // copy, since we are going to move p
|
||||
if (PolygonUtils::moveInside(comb_boundary_inside, p, distance, max_dist) != NO_INDEX)
|
||||
if (PolygonUtils::moveInside(comb_boundary_inside, p, distance, max_dist2) != NO_INDEX)
|
||||
{
|
||||
//Move inside again, so we move out of tight 90deg corners
|
||||
PolygonUtils::moveInside(comb_boundary_inside, p, distance, max_dist);
|
||||
PolygonUtils::moveInside(comb_boundary_inside, p, distance, max_dist2);
|
||||
if (comb_boundary_inside.inside(p))
|
||||
{
|
||||
addTravel_simple(p);
|
||||
@@ -187,21 +179,29 @@ void GCodePlanner::addTravel(Point p)
|
||||
if (combed)
|
||||
{
|
||||
bool retract = combPaths.size() > 1;
|
||||
if (!retract)
|
||||
{ // check whether we want to retract
|
||||
if (!retract && combPaths.size() == 1 && combPaths[0].throughAir && combPaths[0].size() > 2)
|
||||
{ // retract when avoiding obstacles through air
|
||||
retract = true;
|
||||
}
|
||||
|
||||
for (unsigned int path_idx = 0; path_idx < combPaths.size() && !retract; path_idx++)
|
||||
for (CombPath& combPath : combPaths)
|
||||
{ // retract when path moves through a boundary
|
||||
if (combPaths[path_idx].cross_boundary) { retract = true; }
|
||||
if (combPath.cross_boundary || combPath.throughAir)
|
||||
{
|
||||
retract = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (combPaths.size() == 1)
|
||||
{
|
||||
CombPath path = combPaths[0];
|
||||
if (path.throughAir && !path.cross_boundary && path.size() == 2 && path[0] == lastPosition && path[1] == p)
|
||||
{ // limit the retractions from support to support, which didn't cross anything
|
||||
retract = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (retract && last_retraction_config->zHop > 0)
|
||||
{ // TODO: stop comb calculation early! (as soon as we see we don't end in the same part as we began)
|
||||
path = getLatestPathWithConfig(&storage.travel_config);
|
||||
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
|
||||
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
|
||||
{
|
||||
path->retract = true;
|
||||
@@ -215,7 +215,7 @@ void GCodePlanner::addTravel(Point p)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
path = getLatestPathWithConfig(&storage.travel_config);
|
||||
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
|
||||
path->retract = retract;
|
||||
for (Point& combPoint : combPath)
|
||||
{
|
||||
@@ -231,13 +231,13 @@ void GCodePlanner::addTravel(Point p)
|
||||
// no combing? always retract!
|
||||
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
|
||||
{
|
||||
if (was_inside)
|
||||
{
|
||||
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.
|
||||
ExtruderTrain* extr = storage.meshgroup->getExtruderTrain(getExtruder());
|
||||
assert (extr != nullptr);
|
||||
moveInsideCombBoundary(extr->getSettingInMicrons("machine_nozzle_size") * 1);
|
||||
}
|
||||
path = getLatestPathWithConfig(&storage.travel_config);
|
||||
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
|
||||
path->retract = true;
|
||||
}
|
||||
}
|
||||
@@ -250,16 +250,16 @@ void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
{
|
||||
if (path == nullptr)
|
||||
{
|
||||
path = getLatestPathWithConfig(&storage.travel_config);
|
||||
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
|
||||
}
|
||||
path->points.push_back(p);
|
||||
lastPosition = p;
|
||||
}
|
||||
|
||||
|
||||
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, float flow)
|
||||
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow)
|
||||
{
|
||||
getLatestPathWithConfig(config, flow)->points.push_back(p);
|
||||
getLatestPathWithConfig(config, space_fill_type, flow)->points.push_back(p);
|
||||
lastPosition = p;
|
||||
}
|
||||
|
||||
@@ -270,13 +270,17 @@ void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig*
|
||||
for(unsigned int i=1; i<polygon.size(); i++)
|
||||
{
|
||||
Point p1 = polygon[(startIdx + i) % polygon.size()];
|
||||
addExtrusionMove(p1, config, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
|
||||
p0 = p1;
|
||||
}
|
||||
if (polygon.size() > 2)
|
||||
{
|
||||
Point& p1 = polygon[startIdx];
|
||||
addExtrusionMove(p1, config, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
logWarning("WARNING: line added as polygon! (gcodePlanner)\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,9 +296,9 @@ void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* c
|
||||
addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config, wall_overlap_computation);
|
||||
}
|
||||
}
|
||||
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, int wipe_dist)
|
||||
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist)
|
||||
{
|
||||
LineOrderOptimizer orderOptimizer(lastPosition);
|
||||
LineOrderOptimizer orderOptimizer(lastPosition, storage.getSettingInMicrons("infill_line_distance")); //Use infill line distance to make adjacent infill lines fall in the same cluster.
|
||||
for(unsigned int i=0;i<polygons.size();i++)
|
||||
orderOptimizer.addPolygon(polygons[i]);
|
||||
orderOptimizer.optimize();
|
||||
@@ -308,13 +312,13 @@ void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* conf
|
||||
Point& p0 = polygon[start];
|
||||
addTravel(p0);
|
||||
Point& p1 = polygon[end];
|
||||
addExtrusionMove(p1, config);
|
||||
addExtrusionMove(p1, config, space_fill_type);
|
||||
if (wipe_dist != 0)
|
||||
{
|
||||
int line_width = config->getLineWidth();
|
||||
if (vSize2(p1-p0) > line_width * line_width * 4)
|
||||
{ // otherwise line will get optimized by combining multiple into a single extrusion move
|
||||
addExtrusionMove(p1 + normal(p1-p0, wipe_dist), config, 0.0);
|
||||
addExtrusionMove(p1 + normal(p1-p0, wipe_dist), config, space_fill_type, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,7 +470,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
{
|
||||
completeConfigs();
|
||||
|
||||
gcode.setCommandSocketAndLayerNr(commandSocket, layer_nr);
|
||||
gcode.setLayerNr(layer_nr);
|
||||
|
||||
gcode.writeLayerComment(layer_nr);
|
||||
|
||||
@@ -502,7 +506,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
}
|
||||
if (path.config != &storage.travel_config && last_extrusion_config != path.config)
|
||||
{
|
||||
gcode.writeTypeComment(path.config->name);
|
||||
gcode.writeTypeComment(path.config->type);
|
||||
last_extrusion_config = path.config;
|
||||
}
|
||||
double speed = path.config->getSpeed();
|
||||
@@ -511,10 +515,10 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
speed *= getTravelSpeedFactor();
|
||||
else
|
||||
speed *= getExtrudeSpeedFactor();
|
||||
|
||||
int64_t nozzle_size = 400; // TODO allow the machine settings to be passed on everywhere :: depends on which nozzle!
|
||||
|
||||
if (MergeInfillLines(gcode, paths, extruder_plan, storage.travel_config, nozzle_size).mergeInfillLines(speed, path_idx)) // !! has effect on path_idx !!
|
||||
int64_t nozzle_size = 400; // TODO
|
||||
|
||||
if (MergeInfillLines(gcode, layer_nr, paths, extruder_plan, storage.travel_config, nozzle_size).mergeInfillLines(speed, path_idx)) // !! has effect on path_idx !!
|
||||
{ // !! has effect on path_idx !!
|
||||
// works when path_idx is the index of the travel move BEFORE the infill lines to be merged
|
||||
continue;
|
||||
@@ -545,13 +549,11 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
bool coasting = coasting_config.coasting_enable;
|
||||
if (coasting)
|
||||
{
|
||||
coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layerThickness
|
||||
, coasting_config.coasting_volume_move, coasting_config.coasting_speed_move, coasting_config.coasting_min_volume_move
|
||||
, coasting_config.coasting_volume_retract, coasting_config.coasting_speed_retract, coasting_config.coasting_min_volume_retract);
|
||||
coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layerThickness, coasting_config.coasting_volume, coasting_config.coasting_speed, coasting_config.coasting_min_volume);
|
||||
}
|
||||
if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above...
|
||||
{ // normal path to gcode algorithm
|
||||
if ( // change |||||| to /\/\/\/\/ ...
|
||||
if ( // change infill |||||| to /\/\/\/\/ ...
|
||||
false &&
|
||||
path_idx + 2 < paths.size() // has a next move
|
||||
&& paths[path_idx+1].points.size() == 1 // is single extruded line
|
||||
@@ -562,6 +564,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
&& shorterThen(paths[path_idx+2].points.back() - paths[path_idx+1].points.back(), 2 * nozzle_size) // consecutive extrusion is close by
|
||||
)
|
||||
{
|
||||
sendPolygon(paths[path_idx+2].config->type, gcode.getPositionXY(), paths[path_idx+2].points.back(), paths[path_idx+2].getLineWidth());
|
||||
gcode.writeMove(paths[path_idx+2].points.back(), speed, paths[path_idx+1].getExtrusionMM3perMM());
|
||||
path_idx += 2;
|
||||
}
|
||||
@@ -569,6 +572,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
{
|
||||
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
@@ -595,6 +599,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
length += vSizeMM(p0 - p1);
|
||||
p0 = p1;
|
||||
gcode.setZ(z + layerThickness * length / totalLength);
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
@@ -627,10 +632,9 @@ void GCodePlanner::completeConfigs()
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
mesh.inset0_config.setLayerHeight(layer_thickness);
|
||||
|
||||
mesh.insetX_config.setLayerHeight(layer_thickness);
|
||||
mesh.wall_reinforcement_config.setLayerHeight(layer_thickness);
|
||||
mesh.skin_config.setLayerHeight(layer_thickness);
|
||||
mesh.wall_reinforcement_config.setLayerHeight(layer_thickness);
|
||||
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
|
||||
{
|
||||
mesh.infill_config[idx].setLayerHeight(layer_thickness);
|
||||
@@ -656,9 +660,7 @@ void GCodePlanner::processInitialLayersSpeedup()
|
||||
initial_layer_speed = mesh.getSettingInMillimetersPerSecond("speed_layer_0");
|
||||
mesh.inset0_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
mesh.insetX_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
mesh.wall_reinforcement_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
mesh.skin_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
mesh.wall_reinforcement_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
|
||||
{
|
||||
mesh.infill_config[idx].smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
@@ -735,8 +737,12 @@ bool GCodePlanner::makeRetractSwitchRetract(GCodeExport& gcode, unsigned int ext
|
||||
}
|
||||
}
|
||||
|
||||
bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx, int64_t layerThickness, double coasting_volume_move, double coasting_speed_move, double coasting_min_volume_move, double coasting_volume_retract, double coasting_speed_retract, double coasting_min_volume_retract)
|
||||
bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume)
|
||||
{
|
||||
if (coasting_volume <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::vector<GCodePath>& paths = extruder_plans[extruder_plan_idx].paths;
|
||||
GCodePath& path = paths[path_idx];
|
||||
if (path_idx + 1 >= paths.size()
|
||||
@@ -748,21 +754,6 @@ bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, unsigned int extrud
|
||||
{
|
||||
return false;
|
||||
}
|
||||
GCodePath& path_next = paths[path_idx + 1];
|
||||
|
||||
if (path_next.retract)
|
||||
{
|
||||
if (coasting_volume_retract <= 0) { return false; }
|
||||
return writePathWithCoasting(gcode, path, path_next, layerThickness, coasting_volume_retract, coasting_speed_retract, coasting_min_volume_retract, makeRetractSwitchRetract(gcode, extruder_plan_idx, path_idx));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (coasting_volume_move <= 0) { return false; }
|
||||
return writePathWithCoasting(gcode, path, path_next, layerThickness, coasting_volume_move, coasting_speed_move, coasting_min_volume_move);
|
||||
}
|
||||
}
|
||||
bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, GCodePath& path, GCodePath& path_next, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume, bool extruder_switch_retract)
|
||||
{
|
||||
|
||||
int64_t coasting_min_dist_considered = 100; // hardcoded setting for when to not perform coasting
|
||||
|
||||
@@ -770,7 +761,8 @@ bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, GCodePath& path, GC
|
||||
double extrude_speed = path.config->getSpeed() * getExtrudeSpeedFactor(); // travel speed
|
||||
|
||||
int64_t coasting_dist = MM2INT(MM2_2INT(coasting_volume) / layerThickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues
|
||||
int64_t coasting_min_dist = MM2INT(MM2_2INT(coasting_min_volume) / layerThickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues
|
||||
int64_t coasting_min_dist = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layerThickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues
|
||||
// /\ the minimal distance when coasting will coast the full coasting volume instead of linearly less with linearly smaller paths
|
||||
|
||||
|
||||
std::vector<int64_t> accumulated_dist_per_point; // the first accumulated dist is that of the last point! (that of the last point is always zero...)
|
||||
@@ -823,14 +815,11 @@ bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, GCodePath& path, GC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (acc_dist_idx_gt_coast_dist == NO_INDEX)
|
||||
{ // something has gone wrong; coasting_min_dist < coasting_dist ?
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
assert (acc_dist_idx_gt_coast_dist < accumulated_dist_per_point.size()); // something has gone wrong; coasting_min_dist < coasting_dist ?
|
||||
|
||||
unsigned int point_idx_before_start = path.points.size() - 1 - acc_dist_idx_gt_coast_dist;
|
||||
|
||||
|
||||
Point start;
|
||||
{ // computation of begin point of coasting
|
||||
int64_t residual_dist = actual_coasting_dist - accumulated_dist_per_point[acc_dist_idx_gt_coast_dist - 1];
|
||||
@@ -838,27 +827,24 @@ bool GCodePlanner::writePathWithCoasting(GCodeExport& gcode, GCodePath& path, GC
|
||||
Point& b = path.points[point_idx_before_start + 1];
|
||||
start = b + normal(a-b, residual_dist);
|
||||
}
|
||||
|
||||
|
||||
{ // write normal extrude path:
|
||||
for(unsigned int point_idx = 0; point_idx <= point_idx_before_start; point_idx++)
|
||||
{
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
||||
gcode.writeMove(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), start, path.getLineWidth());
|
||||
gcode.writeMove(start, extrude_speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
|
||||
if (path_next.retract)
|
||||
{
|
||||
writeRetraction(gcode, extruder_switch_retract, path.config->retraction_config);
|
||||
}
|
||||
|
||||
|
||||
// write coasting path
|
||||
for (unsigned int point_idx = point_idx_before_start + 1; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
gcode.writeMove(path.points[point_idx], coasting_speed * path.config->getSpeed(), 0);
|
||||
}
|
||||
|
||||
|
||||
gcode.addLastCoastedVolume(path.getExtrusionMM3perMM() * INT2MM(actual_coasting_dist));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+46
-33
@@ -10,6 +10,7 @@
|
||||
#include "wallOverlap.h"
|
||||
#include "commandSocket.h"
|
||||
#include "FanSpeedLayerTime.h"
|
||||
#include "SpaceFillType.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
@@ -150,6 +151,7 @@ class GCodePath
|
||||
{
|
||||
public:
|
||||
GCodePathConfig* config; //!< The configuration settings of the path.
|
||||
SpaceFillType space_fill_type; //!< The type of space filling of which this path is a part
|
||||
float flow; //!< A type-independent flow configuration (used for wall overlap compensation)
|
||||
bool retract; //!< Whether the path is a move path preceded by a retraction move; whether the path is a retracted move path.
|
||||
std::vector<Point> points; //!< The points constituting this path.
|
||||
@@ -169,6 +171,15 @@ public:
|
||||
{
|
||||
return flow * config->getExtrusionMM3perMM();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the actual line width (modulated by the flow)
|
||||
* \return the actual line width as shown in layer view
|
||||
*/
|
||||
int getLineWidth()
|
||||
{
|
||||
return flow * config->getLineWidth() * config->getFlowPercentage() / 100.0;
|
||||
}
|
||||
};
|
||||
|
||||
class ExtruderPlan
|
||||
@@ -236,8 +247,6 @@ class GCodePlanner : public NoCopy
|
||||
private:
|
||||
SliceDataStorage& storage;
|
||||
|
||||
CommandSocket* commandSocket;
|
||||
|
||||
int layer_nr;
|
||||
|
||||
int z;
|
||||
@@ -272,10 +281,11 @@ private:
|
||||
* If GCodePlanner::forceNewPathStart has been called a new path will always be returned.
|
||||
*
|
||||
* \param config The config used for the path returned
|
||||
* \param space_fill_type The type of space filling which this path employs
|
||||
* \param flow (optional) A ratio for the extrusion speed
|
||||
* \return A path with the given config which is now the last path in GCodePlanner::paths
|
||||
*/
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, float flow = 1.0);
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0);
|
||||
|
||||
/*!
|
||||
* Force GCodePlanner::getLatestPathWithConfig to return a new path.
|
||||
@@ -295,7 +305,7 @@ public:
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
* \param last_position The position of the head at the start of this gcode layer
|
||||
*/
|
||||
GCodePlanner(CommandSocket* commandSocket, SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_height, Point last_position, int current_extruder, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, bool retraction_combing, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_height, Point last_position, int current_extruder, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, bool retraction_combing, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
~GCodePlanner();
|
||||
|
||||
private:
|
||||
@@ -316,6 +326,22 @@ public:
|
||||
return lastPosition;
|
||||
}
|
||||
|
||||
/*!
|
||||
* send a polygon through the command socket from the previous point to the given point
|
||||
*/
|
||||
void sendPolygon(PrintFeatureType print_feature_type, Point from, Point to, int line_width)
|
||||
{
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
// we should send this travel as a non-retraction move
|
||||
cura::Polygons pathPoly;
|
||||
PolygonRef path = pathPoly.newPoly();
|
||||
path.add(from);
|
||||
path.add(to);
|
||||
CommandSocket::getInstance()->sendPolygons(print_feature_type, layer_nr, pathPoly, line_width);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set whether the next destination is inside a layer part or not.
|
||||
*
|
||||
@@ -374,8 +400,16 @@ public:
|
||||
* \param path (optional) The travel path to which to add the point \p p
|
||||
*/
|
||||
void addTravel_simple(Point p, GCodePath* path = nullptr);
|
||||
|
||||
void addExtrusionMove(Point p, GCodePathConfig* config, float flow = 1.0);
|
||||
|
||||
/*!
|
||||
* Add an extrusion move to a certain point, optionally with a different flow than the one in the \p config.
|
||||
*
|
||||
* \param p The point to extrude to
|
||||
* \param config The config with which to extrude
|
||||
* \param space_fill_type Of what space filling type this extrusion move is a part
|
||||
* \param flow A modifier of the extrusion width which would follow from the \p config
|
||||
*/
|
||||
void addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0);
|
||||
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr);
|
||||
|
||||
@@ -385,9 +419,10 @@ public:
|
||||
* Add lines to the gcode with optimized order.
|
||||
* \param polygons The lines
|
||||
* \param config The config of the lines
|
||||
* \param space_fill_type The type of space filling used to generate the line segments (should be either Lines or PolyLines!)
|
||||
* \param wipe_dist (optional) the distance wiped without extruding after laying down a line.
|
||||
*/
|
||||
void addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, int wipe_dist = 0);
|
||||
void addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist = 0);
|
||||
|
||||
/*!
|
||||
* Compute naive time estimates (without accountign for slow down at corners etc.) and naive material estimates (without accounting for MergeInfillLines)
|
||||
@@ -438,35 +473,13 @@ public:
|
||||
* \param extruder_plan_idx The index of the current extruder plan
|
||||
* \param path_idx The index into GCodePlanner::paths for the next path to be written to GCode.
|
||||
* \param layerThickness The height of the current layer.
|
||||
* \param coasting_volume_move The volume otherwise leaked during a normal move.
|
||||
* \param coasting_speed_move The speed at which to move during move-coasting.
|
||||
* \param coasting_min_volume_move The minimal volume a path should have which builds up enough pressure to ooze as much as \p coasting_volume_move.
|
||||
* \param coasting_volume_retract The volume otherwise leaked during a retract move.
|
||||
* \param coasting_speed_retract The speed at which to move during retract-coasting.
|
||||
* \param coasting_min_volume_retract The minimal volume a path should have which builds up enough pressure to ooze as much as \p coasting_volume_retract.
|
||||
* \param coasting_volume The volume otherwise leaked during a normal move.
|
||||
* \param coasting_speed The speed at which to move during move-coasting.
|
||||
* \param coasting_min_volume The minimal volume a path should have (before starting to coast) which builds up enough pressure to ooze as much as \p coasting_volume.
|
||||
* \return Whether any GCode has been written for the path.
|
||||
*/
|
||||
bool writePathWithCoasting(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx, int64_t layerThickness, double coasting_volume_move, double coasting_speed_move, double coasting_min_volume_move, double coasting_volume_retract, double coasting_speed_retract, double coasting_min_volume_retract);
|
||||
bool writePathWithCoasting(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume);
|
||||
|
||||
/*!
|
||||
* Writes a path to GCode and performs coasting, or returns false if it did nothing.
|
||||
*
|
||||
* Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines.
|
||||
*
|
||||
* Paths shorter than \p coasting_min_volume will use less \p coasting_volume linearly.
|
||||
*
|
||||
* \param gcode The gcode to write the planned paths to
|
||||
* \param path The extrusion path to be written to GCode.
|
||||
* \param path_next The next travel path to be written to GCode.
|
||||
* \param layerThickness The height of the current layer.
|
||||
* \param coasting_volume The volume otherwise leaked.
|
||||
* \param coasting_speed The speed at which to move during coasting.
|
||||
* \param coasting_min_volume The minimal volume a path should have which builds up enough pressure to ooze as much as \p coasting_volume.
|
||||
* \param extruder_switch_retract (optional) For a coasted path followed by a retraction: whether to retract normally, or do an extruder switch retraction.
|
||||
* \return Whether any GCode has been written for the path.
|
||||
*/
|
||||
bool writePathWithCoasting(GCodeExport& gcode, GCodePath& path, GCodePath& path_next, int64_t layerThickness, double coasting_volume, double coasting_speed, double coasting_min_volume, bool extruder_switch_retract = false);
|
||||
|
||||
/*!
|
||||
* Write a retraction: either an extruder switch retraction or a normal retraction based on the last extrusion paths retraction config.
|
||||
* \param gcode The gcode to write the planned paths to
|
||||
|
||||
+180
-345
@@ -2,7 +2,6 @@
|
||||
#include "infill.h"
|
||||
#include "functional"
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "utils/AABB.h"
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
namespace cura {
|
||||
@@ -16,36 +15,28 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines, Polygon
|
||||
switch(pattern)
|
||||
{
|
||||
case EFillMethod::GRID:
|
||||
generateGridInfill(in_outline, outlineOffset, result_lines, extrusion_width, line_distance * 2, infill_overlap, fill_angle);
|
||||
generateGridInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::LINES:
|
||||
generateLineInfill(in_outline, outlineOffset, result_lines, extrusion_width, line_distance, infill_overlap, fill_angle);
|
||||
generateLineInfill(result_lines, line_distance, fill_angle);
|
||||
break;
|
||||
case EFillMethod::TRIANGLES:
|
||||
generateTriangleInfill(in_outline, outlineOffset, result_lines, extrusion_width, line_distance * 3, infill_overlap, fill_angle);
|
||||
generateTriangleInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC:
|
||||
if (outlineOffset != 0)
|
||||
PolygonUtils::offsetSafe(in_outline, outline_offset - infill_line_width / 2, infill_line_width, outline_offsetted, false); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
outline = &outline_offsetted;
|
||||
if (abs(infill_line_width - line_distance) < 10)
|
||||
{
|
||||
PolygonUtils::offsetSafe(in_outline, outlineOffset, extrusion_width, outline_offsetted, avoidOverlappingPerimeters);
|
||||
outline = &outline_offsetted;
|
||||
generateConcentricInfillDense(*outline, result_polygons, in_between, remove_overlapping_perimeters);
|
||||
}
|
||||
if (abs(extrusion_width - line_distance) < 10)
|
||||
{
|
||||
generateConcentricInfillDense(*outline, result_polygons, in_between, extrusion_width, avoidOverlappingPerimeters);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
generateConcentricInfill(*outline, result_polygons, line_distance);
|
||||
}
|
||||
break;
|
||||
case EFillMethod::ZIG_ZAG:
|
||||
if (outlineOffset != 0)
|
||||
{
|
||||
PolygonUtils::offsetSafe(in_outline, outlineOffset, extrusion_width, outline_offsetted, avoidOverlappingPerimeters);
|
||||
outline = &outline_offsetted;
|
||||
}
|
||||
generateZigZagInfill(*outline, result_lines, extrusion_width, line_distance, infill_overlap, fill_angle, connect_zigzags, use_endPieces);
|
||||
generateZigZagInfill(result_lines, line_distance, fill_angle, connected_zigzags, use_endpieces);
|
||||
break;
|
||||
default:
|
||||
logError("Fill pattern has unknown value.\n");
|
||||
@@ -53,9 +44,9 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines, Polygon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, int extrusionWidth, bool avoidOverlappingPerimeters)
|
||||
|
||||
|
||||
void Infill::generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
while(outline.size() > 0)
|
||||
{
|
||||
@@ -65,13 +56,13 @@ void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons*
|
||||
result.add(r);
|
||||
}
|
||||
Polygons next_outline;
|
||||
PolygonUtils::offsetExtrusionWidth(outline, true, extrusionWidth, next_outline, in_between, avoidOverlappingPerimeters);
|
||||
PolygonUtils::offsetExtrusionWidth(outline, true, infill_line_width, next_outline, in_between, avoidOverlappingPerimeters);
|
||||
outline = next_outline;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value)
|
||||
void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int inset_value)
|
||||
{
|
||||
while(outline.size() > 0)
|
||||
{
|
||||
@@ -85,375 +76,219 @@ void generateConcentricInfill(Polygons outline, Polygons& result, int inset_valu
|
||||
}
|
||||
|
||||
|
||||
void generateGridInfill(const Polygons& in_outline, int outlineOffset, Polygons& result,
|
||||
int extrusionWidth, int lineSpacing, double infillOverlap,
|
||||
double rotation)
|
||||
void Infill::generateGridInfill(Polygons& result)
|
||||
{
|
||||
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
|
||||
infillOverlap, rotation);
|
||||
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
|
||||
infillOverlap, rotation + 90);
|
||||
generateLineInfill(result, line_distance * 2, fill_angle);
|
||||
generateLineInfill(result, line_distance * 2, fill_angle + 90);
|
||||
}
|
||||
|
||||
void generateTriangleInfill(const Polygons& in_outline, int outlineOffset, Polygons& result,
|
||||
int extrusionWidth, int lineSpacing, double infillOverlap,
|
||||
double rotation)
|
||||
void Infill::generateTriangleInfill(Polygons& result)
|
||||
{
|
||||
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
|
||||
infillOverlap, rotation);
|
||||
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
|
||||
infillOverlap, rotation + 60);
|
||||
generateLineInfill(in_outline, outlineOffset, result, extrusionWidth, lineSpacing,
|
||||
infillOverlap, rotation + 120);
|
||||
generateLineInfill(result, line_distance * 3, fill_angle);
|
||||
generateLineInfill(result, line_distance * 3, fill_angle + 60);
|
||||
generateLineInfill(result, line_distance * 3, fill_angle + 120);
|
||||
}
|
||||
|
||||
void addLineInfill(Polygons& result, PointMatrix matrix, int scanline_min_idx, int lineSpacing, AABB boundary, std::vector<std::vector<int64_t> > cutList, int extrusionWidth)
|
||||
void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list)
|
||||
{
|
||||
auto addLine = [&](Point from, Point to)
|
||||
{
|
||||
{
|
||||
PolygonRef p = result.newPoly();
|
||||
p.add(matrix.unapply(from));
|
||||
p.add(matrix.unapply(to));
|
||||
p.add(rotation_matrix.unapply(from));
|
||||
p.add(rotation_matrix.unapply(to));
|
||||
};
|
||||
|
||||
|
||||
auto compare_int64_t = [](const void* a, const void* b)
|
||||
{
|
||||
int64_t n = (*(int64_t*)a) - (*(int64_t*)b);
|
||||
if (n < 0) return -1;
|
||||
if (n > 0) return 1;
|
||||
if (n < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
int scanline_idx = 0;
|
||||
for(int64_t x = scanline_min_idx * lineSpacing; x < boundary.max.X; x += lineSpacing)
|
||||
for(int64_t x = scanline_min_idx * line_distance; x < boundary.max.X; x += line_distance)
|
||||
{
|
||||
qsort(cutList[scanline_idx].data(), cutList[scanline_idx].size(), sizeof(int64_t), compare_int64_t);
|
||||
for(unsigned int i = 0; i + 1 < cutList[scanline_idx].size(); i+=2)
|
||||
std::vector<int64_t>& crossings = cut_list[scanline_idx];
|
||||
qsort(crossings.data(), crossings.size(), sizeof(int64_t), compare_int64_t);
|
||||
for(unsigned int crossing_idx = 0; crossing_idx + 1 < crossings.size(); crossing_idx += 2)
|
||||
{
|
||||
if (cutList[scanline_idx][i+1] - cutList[scanline_idx][i] < extrusionWidth / 5)
|
||||
if (crossings[crossing_idx + 1] - crossings[crossing_idx] < infill_line_width / 5)
|
||||
{ // segment is too short to create infill
|
||||
continue;
|
||||
addLine(Point(x, cutList[scanline_idx][i]), Point(x, cutList[scanline_idx][i+1]));
|
||||
}
|
||||
addLine(Point(x, crossings[crossing_idx]), Point(x, crossings[crossing_idx + 1]));
|
||||
}
|
||||
scanline_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation)
|
||||
void Infill::generateLineInfill(Polygons& result, int line_distance, const double& fill_angle)
|
||||
{
|
||||
if (lineSpacing == 0) return;
|
||||
if (in_outline.size() == 0) return;
|
||||
Polygons outline = ((outlineOffset)? in_outline.offset(outlineOffset) : in_outline).offset(extrusionWidth * infillOverlap / 100);
|
||||
if (outline.size() == 0) return;
|
||||
|
||||
PointMatrix matrix(rotation);
|
||||
|
||||
outline.applyMatrix(matrix);
|
||||
PointMatrix rotation_matrix(fill_angle);
|
||||
NoZigZagConnectorProcessor lines_processor(rotation_matrix, result);
|
||||
bool connected_zigzags = false;
|
||||
bool safe_outline_offset = false;
|
||||
generateLinearBasedInfill(outline_offset, safe_outline_offset, result, line_distance, rotation_matrix, lines_processor, connected_zigzags);
|
||||
}
|
||||
|
||||
|
||||
AABB boundary(outline);
|
||||
|
||||
int scanline_min_idx = boundary.min.X / lineSpacing;
|
||||
int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx;
|
||||
|
||||
std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments
|
||||
|
||||
for(int n=0; n<lineCount; n++)
|
||||
cutList.push_back(std::vector<int64_t>());
|
||||
|
||||
for(unsigned int poly_idx=0; poly_idx < outline.size(); poly_idx++)
|
||||
|
||||
void Infill::generateZigZagInfill(Polygons& result, const int line_distance, const double& fill_angle, const bool connected_zigzags, const bool use_endpieces)
|
||||
{
|
||||
bool safe_outline_offset = true;
|
||||
|
||||
PointMatrix rotation_matrix(fill_angle);
|
||||
if (use_endpieces)
|
||||
{
|
||||
Point p0 = outline[poly_idx][outline[poly_idx].size()-1];
|
||||
for(unsigned int i=0; i < outline[poly_idx].size(); i++)
|
||||
if (connected_zigzags)
|
||||
{
|
||||
Point p1 = outline[poly_idx][i];
|
||||
int64_t xMin = p1.X, xMax = p0.X;
|
||||
if (xMin == xMax) {
|
||||
p0 = p1;
|
||||
continue;
|
||||
}
|
||||
if (xMin > xMax) { xMin = p0.X; xMax = p1.X; }
|
||||
|
||||
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
|
||||
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
|
||||
int direction = 1;
|
||||
if (p0.X > p1.X)
|
||||
{
|
||||
direction = -1;
|
||||
scanline_idx1 += 1; // only consider the scanlines in between the scansegments
|
||||
} else scanline_idx0 += 1; // only consider the scanlines in between the scansegments
|
||||
|
||||
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction)
|
||||
{
|
||||
int x = scanline_idx * lineSpacing;
|
||||
int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X);
|
||||
cutList[scanline_idx - scanline_min_idx].push_back(y);
|
||||
}
|
||||
p0 = p1;
|
||||
ZigzagConnectorProcessorConnectedEndPieces zigzag_processor(rotation_matrix, result);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
|
||||
}
|
||||
else
|
||||
{
|
||||
ZigzagConnectorProcessorDisconnectedEndPieces zigzag_processor(rotation_matrix, result);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
|
||||
}
|
||||
}
|
||||
|
||||
addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth);
|
||||
}
|
||||
|
||||
|
||||
void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags, bool use_endPieces)
|
||||
{
|
||||
if (use_endPieces) return generateZigZagIninfill_endPieces(in_outline, result, extrusionWidth, lineSpacing, infillOverlap, rotation, connect_zigzags);
|
||||
else return generateZigZagIninfill_noEndPieces(in_outline, result, extrusionWidth, lineSpacing, infillOverlap, rotation);
|
||||
}
|
||||
|
||||
void generateZigZagIninfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags)
|
||||
{
|
||||
// if (in_outline.size() == 0) return;
|
||||
// Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2);
|
||||
Polygons empty;
|
||||
Polygons outline = in_outline.difference(empty); // copy
|
||||
if (outline.size() == 0) return;
|
||||
|
||||
PointMatrix matrix(rotation);
|
||||
|
||||
outline.applyMatrix(matrix);
|
||||
|
||||
auto addLine = [&](Point from, Point to)
|
||||
{
|
||||
PolygonRef p = result.newPoly();
|
||||
p.add(matrix.unapply(from));
|
||||
p.add(matrix.unapply(to));
|
||||
};
|
||||
|
||||
AABB boundary(outline);
|
||||
|
||||
int scanline_min_idx = boundary.min.X / lineSpacing;
|
||||
int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx;
|
||||
|
||||
std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments
|
||||
|
||||
for(int n=0; n<lineCount; n++)
|
||||
cutList.push_back(std::vector<int64_t>());
|
||||
for(unsigned int polyNr=0; polyNr < outline.size(); polyNr++)
|
||||
else
|
||||
{
|
||||
std::vector<Point> firstBoundarySegment;
|
||||
std::vector<Point> unevenBoundarySegment; // stored cause for connected_zigzags a boundary segment which ends in an uneven scanline needs to be included
|
||||
|
||||
bool isFirstBoundarySegment = true;
|
||||
bool firstBoundarySegmentEndsInEven = false;
|
||||
|
||||
bool isEvenScanSegment = false;
|
||||
|
||||
|
||||
Point p0 = outline[polyNr][outline[polyNr].size()-1];
|
||||
Point lastPoint = p0;
|
||||
for(unsigned int i=0; i < outline[polyNr].size(); i++)
|
||||
ZigzagConnectorProcessorNoEndPieces zigzag_processor(rotation_matrix, result);
|
||||
generateLinearBasedInfill(outline_offset - infill_line_width / 2, safe_outline_offset, result, line_distance, rotation_matrix, zigzag_processor, connected_zigzags);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* algorithm:
|
||||
* 1. for each line segment of each polygon:
|
||||
* store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections
|
||||
* (zigzag): add boundary segments to result
|
||||
* 2. for each scanline:
|
||||
* sort the associated intersections
|
||||
* and connect them using the even-odd rule
|
||||
*
|
||||
* rough explanation of the zigzag algorithm:
|
||||
* while walking around (each) polygon (1.)
|
||||
* if polygon intersects with even scanline
|
||||
* start boundary segment (add each following segment to the [result])
|
||||
* when polygon intersects with a scanline again
|
||||
* stop boundary segment (stop adding segments to the [result])
|
||||
* (see infill/ZigzagConnectorProcessor.h for actual implementation details)
|
||||
*
|
||||
*
|
||||
* we call the areas between two consecutive scanlines a 'scansegment'.
|
||||
* Scansegment x is the area between scanline x and scanline x+1
|
||||
* Edit: the term scansegment is wrong, since I call a boundary segment leaving from an even scanline to the left as belonging to an even scansegment,
|
||||
* while I also call a boundary segment leaving from an even scanline toward the right as belonging to an even scansegment.
|
||||
*/
|
||||
void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags)
|
||||
{
|
||||
if (line_distance == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (in_outline.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Polygons outline;
|
||||
if (outline_offset != 0)
|
||||
{
|
||||
PolygonUtils::offsetSafe(in_outline, outline_offset, infill_line_width, outline, remove_overlapping_perimeters && safe_outline_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
outline = in_outline;
|
||||
}
|
||||
|
||||
if (line_distance > infill_line_width * 3 / 2)
|
||||
{ // infill is not too dense to have overlap with surrounding polygon
|
||||
outline = outline.offset(infill_overlap * infill_line_width / 100); // division by 100 cause it's a percentage.
|
||||
}
|
||||
|
||||
if (outline.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
outline.applyMatrix(rotation_matrix);
|
||||
|
||||
AABB boundary(outline);
|
||||
|
||||
int scanline_min_idx = boundary.min.X / line_distance;
|
||||
int line_count = (boundary.max.X + (line_distance - 1)) / line_distance - scanline_min_idx;
|
||||
|
||||
std::vector<std::vector<int64_t> > cut_list; // mapping from scanline to all intersections with polygon segments
|
||||
|
||||
for(int scanline_idx = 0; scanline_idx < line_count; scanline_idx++)
|
||||
{
|
||||
cut_list.push_back(std::vector<int64_t>());
|
||||
}
|
||||
|
||||
for(unsigned int poly_idx = 0; poly_idx < outline.size(); poly_idx++)
|
||||
{
|
||||
PolygonRef poly = outline[poly_idx];
|
||||
Point p0 = poly.back();
|
||||
zigzag_connector_processor.registerVertex(p0); // always adds the first point to ZigzagConnectorProcessorEndPieces::first_zigzag_connector when using a zigzag infill type
|
||||
for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
Point p1 = outline[polyNr][i];
|
||||
int64_t xMin = p1.X, xMax = p0.X;
|
||||
if (xMin == xMax) {
|
||||
lastPoint = p1;
|
||||
Point p1 = poly[point_idx];
|
||||
if (p1.X == p0.X)
|
||||
{
|
||||
zigzag_connector_processor.registerVertex(p1);
|
||||
// TODO: how to make sure it always adds the shortest line? (in order to prevent overlap with the zigzag connectors)
|
||||
// note: this is already a problem for normal infill, but hasn't really cothered anyone so far.
|
||||
p0 = p1;
|
||||
continue;
|
||||
}
|
||||
if (xMin > xMax) { xMin = p0.X; xMax = p1.X; }
|
||||
|
||||
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
|
||||
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
|
||||
|
||||
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -line_distance)) / line_distance; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
|
||||
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -line_distance)) / line_distance; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
|
||||
// this way of handling the indices takes care of the case where a boundary line segment ends exactly on a scanline:
|
||||
// in case the next segment moves back from that scanline either 2 or 0 scanline-boundary intersections are created
|
||||
// otherwise only 1 will be created, counting as an actual intersection
|
||||
int direction = 1;
|
||||
if (p0.X > p1.X)
|
||||
{
|
||||
direction = -1;
|
||||
scanline_idx1 += 1; // only consider the scanlines in between the scansegments
|
||||
} else scanline_idx0 += 1; // only consider the scanlines in between the scansegments
|
||||
|
||||
|
||||
if (isFirstBoundarySegment) firstBoundarySegment.push_back(p0);
|
||||
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction)
|
||||
}
|
||||
else
|
||||
{
|
||||
int x = scanline_idx * lineSpacing;
|
||||
scanline_idx0 += 1; // only consider the scanlines in between the scansegments
|
||||
}
|
||||
|
||||
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1 + direction; scanline_idx += direction)
|
||||
{
|
||||
int x = scanline_idx * line_distance;
|
||||
int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X);
|
||||
cutList[scanline_idx - scanline_min_idx].push_back(y);
|
||||
|
||||
|
||||
bool last_isEvenScanSegment = isEvenScanSegment;
|
||||
if (scanline_idx % 2 == 0) isEvenScanSegment = true;
|
||||
else isEvenScanSegment = false;
|
||||
|
||||
if (!isFirstBoundarySegment)
|
||||
{
|
||||
if (last_isEvenScanSegment && (connect_zigzags || !isEvenScanSegment))
|
||||
addLine(lastPoint, Point(x,y));
|
||||
else if (connect_zigzags && !last_isEvenScanSegment && !isEvenScanSegment) // if we end an uneven boundary in an uneven segment
|
||||
{ // add whole unevenBoundarySegment (including the just obtained point)
|
||||
for (unsigned int p = 1; p < unevenBoundarySegment.size(); p++)
|
||||
{
|
||||
addLine(unevenBoundarySegment[p-1], unevenBoundarySegment[p]);
|
||||
}
|
||||
addLine(unevenBoundarySegment[unevenBoundarySegment.size()-1], Point(x,y));
|
||||
unevenBoundarySegment.clear();
|
||||
}
|
||||
if (connect_zigzags && last_isEvenScanSegment && !isEvenScanSegment)
|
||||
unevenBoundarySegment.push_back(Point(x,y));
|
||||
else
|
||||
unevenBoundarySegment.clear();
|
||||
|
||||
}
|
||||
lastPoint = Point(x,y);
|
||||
|
||||
if (isFirstBoundarySegment)
|
||||
{
|
||||
firstBoundarySegment.emplace_back(x,y);
|
||||
firstBoundarySegmentEndsInEven = isEvenScanSegment;
|
||||
isFirstBoundarySegment = false;
|
||||
}
|
||||
|
||||
cut_list[scanline_idx - scanline_min_idx].push_back(y);
|
||||
Point scanline_linesegment_intersection(x, y);
|
||||
zigzag_connector_processor.registerScanlineSegmentIntersection(scanline_linesegment_intersection, scanline_idx % 2 == 0);
|
||||
}
|
||||
if (!isFirstBoundarySegment)
|
||||
{
|
||||
if (isEvenScanSegment)
|
||||
addLine(lastPoint, p1);
|
||||
else if (connect_zigzags)
|
||||
unevenBoundarySegment.push_back(p1);
|
||||
}
|
||||
|
||||
lastPoint = p1;
|
||||
zigzag_connector_processor.registerVertex(p1);
|
||||
p0 = p1;
|
||||
}
|
||||
|
||||
if (isEvenScanSegment || isFirstBoundarySegment || connect_zigzags)
|
||||
{
|
||||
for (unsigned int i = 1; i < firstBoundarySegment.size() ; i++)
|
||||
{
|
||||
if (i < firstBoundarySegment.size() - 1 || !firstBoundarySegmentEndsInEven || connect_zigzags) // only add last element if connect_zigzags or boundary segment ends in uneven scanline
|
||||
addLine(firstBoundarySegment[i-1], firstBoundarySegment[i]);
|
||||
}
|
||||
}
|
||||
else if (!firstBoundarySegmentEndsInEven)
|
||||
addLine(firstBoundarySegment[firstBoundarySegment.size()-2], firstBoundarySegment[firstBoundarySegment.size()-1]);
|
||||
}
|
||||
|
||||
if (cutList.size() == 0) return;
|
||||
if (connect_zigzags && cutList.size() == 1 && cutList[0].size() <= 2) return; // don't add connection if boundary already contains whole outline!
|
||||
|
||||
addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth);
|
||||
}
|
||||
zigzag_connector_processor.registerPolyFinished();
|
||||
}
|
||||
|
||||
|
||||
void generateZigZagIninfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation)
|
||||
{
|
||||
if (in_outline.size() == 0) return;
|
||||
Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2);
|
||||
if (outline.size() == 0) return;
|
||||
|
||||
PointMatrix matrix(rotation);
|
||||
|
||||
outline.applyMatrix(matrix);
|
||||
|
||||
auto addLine = [&](Point from, Point to)
|
||||
{
|
||||
PolygonRef p = result.newPoly();
|
||||
p.add(matrix.unapply(from));
|
||||
p.add(matrix.unapply(to));
|
||||
};
|
||||
|
||||
AABB boundary(outline);
|
||||
|
||||
int scanline_min_idx = boundary.min.X / lineSpacing;
|
||||
int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx;
|
||||
|
||||
std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments
|
||||
|
||||
for(int n=0; n<lineCount; n++)
|
||||
cutList.push_back(std::vector<int64_t>());
|
||||
for(unsigned int polyNr=0; polyNr < outline.size(); polyNr++)
|
||||
if (cut_list.size() == 0)
|
||||
{
|
||||
std::vector<Point> firstBoundarySegment;
|
||||
std::vector<Point> boundarySegment;
|
||||
|
||||
bool isFirstBoundarySegment = true;
|
||||
bool firstBoundarySegmentEndsInEven = true;
|
||||
|
||||
bool isEvenScanSegment = false;
|
||||
|
||||
|
||||
Point p0 = outline[polyNr][outline[polyNr].size()-1];
|
||||
for(unsigned int i=0; i < outline[polyNr].size(); i++)
|
||||
{
|
||||
Point p1 = outline[polyNr][i];
|
||||
int64_t xMin = p1.X, xMax = p0.X;
|
||||
if (xMin == xMax) {
|
||||
p0 = p1;
|
||||
continue;
|
||||
}
|
||||
if (xMin > xMax) { xMin = p0.X; xMax = p1.X; }
|
||||
|
||||
int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ...
|
||||
int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number)
|
||||
int direction = 1;
|
||||
if (p0.X > p1.X)
|
||||
{
|
||||
direction = -1;
|
||||
scanline_idx1 += 1; // only consider the scanlines in between the scansegments
|
||||
} else scanline_idx0 += 1; // only consider the scanlines in between the scansegments
|
||||
|
||||
|
||||
if (isFirstBoundarySegment) firstBoundarySegment.push_back(p0);
|
||||
else boundarySegment.push_back(p0);
|
||||
for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction)
|
||||
{
|
||||
int x = scanline_idx * lineSpacing;
|
||||
int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X);
|
||||
cutList[scanline_idx - scanline_min_idx].push_back(y);
|
||||
|
||||
|
||||
bool last_isEvenScanSegment = isEvenScanSegment;
|
||||
if (scanline_idx % 2 == 0) isEvenScanSegment = true;
|
||||
else isEvenScanSegment = false;
|
||||
|
||||
if (!isFirstBoundarySegment)
|
||||
{
|
||||
if (last_isEvenScanSegment && !isEvenScanSegment)
|
||||
{ // add whole boundarySegment (including the just obtained point)
|
||||
for (unsigned int p = 1; p < boundarySegment.size(); p++)
|
||||
{
|
||||
addLine(boundarySegment[p-1], boundarySegment[p]);
|
||||
}
|
||||
addLine(boundarySegment[boundarySegment.size()-1], Point(x,y));
|
||||
boundarySegment.clear();
|
||||
}
|
||||
else if (isEvenScanSegment) // we are either in an end piece or an uneven boundary segment
|
||||
{
|
||||
boundarySegment.clear();
|
||||
boundarySegment.emplace_back(x,y);
|
||||
} else
|
||||
boundarySegment.clear();
|
||||
|
||||
}
|
||||
|
||||
if (isFirstBoundarySegment)
|
||||
{
|
||||
firstBoundarySegment.emplace_back(x,y);
|
||||
firstBoundarySegmentEndsInEven = isEvenScanSegment;
|
||||
isFirstBoundarySegment = false;
|
||||
boundarySegment.emplace_back(x,y);
|
||||
}
|
||||
|
||||
}
|
||||
if (!isFirstBoundarySegment && isEvenScanSegment)
|
||||
boundarySegment.push_back(p1);
|
||||
|
||||
|
||||
p0 = p1;
|
||||
}
|
||||
|
||||
if (!isFirstBoundarySegment && isEvenScanSegment && !firstBoundarySegmentEndsInEven)
|
||||
{
|
||||
for (unsigned int i = 1; i < firstBoundarySegment.size() ; i++)
|
||||
addLine(firstBoundarySegment[i-1], firstBoundarySegment[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth);
|
||||
return;
|
||||
}
|
||||
if (connected_zigzags && cut_list.size() == 1 && cut_list[0].size() <= 2)
|
||||
{
|
||||
return; // don't add connection if boundary already contains whole outline!
|
||||
}
|
||||
|
||||
addLineInfill(result, rotation_matrix, scanline_min_idx, line_distance, boundary, cut_list);
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+137
-79
@@ -4,99 +4,141 @@
|
||||
|
||||
#include "utils/polygon.h"
|
||||
#include "settings.h"
|
||||
// #include "ZigzagConnectorProcessor.h"
|
||||
#include "infill/ZigzagConnectorProcessor.h"
|
||||
#include "infill/NoZigZagConnectorProcessor.h"
|
||||
#include "infill/ActualZigzagConnectorProcessor.h"
|
||||
#include "infill/ZigzagConnectorProcessorNoEndPieces.h"
|
||||
#include "infill/ZigzagConnectorProcessorEndPieces.h"
|
||||
#include "infill/ZigzagConnectorProcessorConnectedEndPieces.h"
|
||||
#include "infill/ZigzagConnectorProcessorDisconnectedEndPieces.h"
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/AABB.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
class Infill
|
||||
|
||||
class Infill
|
||||
{
|
||||
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
|
||||
bool remove_overlapping_perimeters; //!< Whether to remove overlapping perimeter parts
|
||||
int infill_line_width; //!< The line width of the infill lines to generate
|
||||
int line_distance; //!< The distance between two infill lines / polygons
|
||||
double infill_overlap; //!< the percentage (of infill_line_width) to overlap with the actual area within which to generate infill
|
||||
double fill_angle; //!< for linear infill types: the angle of the infill lines (or the angle of the grid)
|
||||
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
|
||||
|
||||
public:
|
||||
Infill(EFillMethod pattern, const Polygons& in_outline, int outline_offset, bool remove_overlapping_perimeters, int infill_line_width, int line_distance, double infill_overlap, double fill_angle, bool connected_zigzags = false, bool use_endpieces = false)
|
||||
: pattern(pattern)
|
||||
, in_outline(in_outline)
|
||||
, outline_offset(outline_offset)
|
||||
, remove_overlapping_perimeters(remove_overlapping_perimeters)
|
||||
, infill_line_width(infill_line_width)
|
||||
, line_distance(line_distance)
|
||||
, infill_overlap(infill_overlap)
|
||||
, fill_angle(fill_angle)
|
||||
, connected_zigzags(connected_zigzags)
|
||||
, use_endpieces(use_endpieces)
|
||||
{
|
||||
EFillMethod pattern;
|
||||
const Polygons& in_outline;
|
||||
int outlineOffset;
|
||||
bool avoidOverlappingPerimeters;
|
||||
int extrusion_width;
|
||||
int line_distance;
|
||||
double infill_overlap;
|
||||
double fill_angle;
|
||||
bool connect_zigzags;
|
||||
bool use_endPieces;
|
||||
}
|
||||
/*!
|
||||
* Generate the infill.
|
||||
*
|
||||
* \param result_polygons (output) The resulting polygons (from concentric infill)
|
||||
* \param result_lines (output) The resulting line segments (from linear infill types)
|
||||
* \param in_between (optional output) The areas in between two concecutive concentric infill polygons
|
||||
*/
|
||||
void generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between);
|
||||
|
||||
public:
|
||||
Infill(EFillMethod pattern, const Polygons& in_outline, int outlineOffset, bool avoidOverlappingPerimeters, int extrusion_width, int line_distance, double infill_overlap, double fill_angle, bool connect_zigzags, bool use_endPieces)
|
||||
: pattern(pattern)
|
||||
, in_outline(in_outline)
|
||||
, outlineOffset(outlineOffset)
|
||||
, avoidOverlappingPerimeters(avoidOverlappingPerimeters)
|
||||
, extrusion_width(extrusion_width)
|
||||
, line_distance(line_distance)
|
||||
, infill_overlap(infill_overlap)
|
||||
, fill_angle(fill_angle)
|
||||
, connect_zigzags(connect_zigzags)
|
||||
, use_endPieces(use_endPieces)
|
||||
{
|
||||
}
|
||||
void generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between);
|
||||
};
|
||||
|
||||
void generateInfill(EFillMethod pattern, const Polygons& in_outline, int outlineOffset, Polygons& result_polygons, Polygons& result_lines, Polygons* in_between, bool avoidOverlappingPerimeters, int extrusion_width, int line_distance, double infill_overlap, double fill_angle, bool connect_zigzags, bool use_endPieces);
|
||||
|
||||
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value);
|
||||
|
||||
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, int extrusionWidth, bool avoidOverlappingPerimeters);
|
||||
|
||||
void generateGridInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation);
|
||||
|
||||
void generateTriangleInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation);
|
||||
private:
|
||||
|
||||
/*!
|
||||
* generate lines within the area of \p in_outline, at regular intervals of \p lineSpacing
|
||||
* Generate sparse concentric infill
|
||||
* \param outline The actual outline of the area within which to generate infill
|
||||
* \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);
|
||||
|
||||
/*!
|
||||
* Generate dense concentric infill (100%)
|
||||
*
|
||||
* \param outline The actual outline of the area within which to generate infill
|
||||
* \param result (output) The resulting polygons
|
||||
* \param in_between (output) The areas in between each two consecutive polygons
|
||||
* \param remove_overlapping_perimeters Whether to remove overlapping perimeter parts
|
||||
*/
|
||||
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, bool remove_overlapping_perimeters);
|
||||
|
||||
/*!
|
||||
* Generate a rectangular grid of infill lines
|
||||
* \param result (output) The resulting lines
|
||||
*/
|
||||
void generateGridInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Generate a triangular grid of infill lines
|
||||
* \param result (output) The resulting lines
|
||||
*/
|
||||
void generateTriangleInfill(Polygons& result);
|
||||
|
||||
/*!
|
||||
* Convert a mapping from scanline to line_segment-scanline-intersections (\p cut_list) into line segments, using the even-odd rule
|
||||
* \param result (output) The resulting lines
|
||||
* \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill
|
||||
* \param scanline_min_idx The lowest index of all scanlines crossing the polygon
|
||||
* \param line_distance The distance between two lines which are in the same direction
|
||||
* \param boundary The axis aligned boundary box within which the polygon is
|
||||
* \param cut_list A mapping of each scanline to all y-coordinates (in the space transformed by rotation_matrix) where the polygons are crossing the scanline
|
||||
*/
|
||||
void addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list);
|
||||
|
||||
/*!
|
||||
* generate lines within the area of \p in_outline, at regular intervals of \p line_distance
|
||||
*
|
||||
* idea:
|
||||
* intersect a regular grid of 'scanlines' with the area inside \p in_outline
|
||||
*
|
||||
* we call the areas between two consecutive scanlines a 'scansegment'.
|
||||
* Scansegment x is the area between scanline x and scanline x+1
|
||||
*
|
||||
* algorithm:
|
||||
* 1) for each line segment of each polygon:
|
||||
* store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections
|
||||
* (zigzag): add boundary segments to result
|
||||
* 2) for each scanline:
|
||||
* sort the associated intersections
|
||||
* and connect them using the even-odd rule
|
||||
*
|
||||
* \param result (output) The resulting lines
|
||||
* \param line_distance The distance between two lines which are in the same direction
|
||||
* \param fill_angle The angle of the generated lines
|
||||
*/
|
||||
void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation);
|
||||
|
||||
void generateZigZagInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags, bool use_endPieces);
|
||||
void generateLineInfill(Polygons& result, int line_distance, const double& fill_angle);
|
||||
|
||||
/*!
|
||||
* Function for creating linear based infill types (Lines, ZigZag).
|
||||
*
|
||||
* This function implements the basic functionality of Infill::generateLineInfill (see doc of that function),
|
||||
* but makes calls to a ZigzagConnectorProcessor which handles what to do with each line segment - scanline intersection.
|
||||
*
|
||||
* It is called only from Infill::generateLineinfill and Infill::generateZigZagInfill.
|
||||
*
|
||||
* \param outline_offset An offset from the reference polygon (Infill::in_outline) to get the actual outline within which to generate infill
|
||||
* \param safe_outline_offset Whether to consider removing overlapping wall parts (not so for normal line infill)
|
||||
* \param result (output) The resulting lines
|
||||
* \param line_distance The distance between two lines which are in the same direction
|
||||
* \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill
|
||||
* \param zigzag_connector_processor The processor used to generate zigzag connectors
|
||||
* \param connected_zigzags Whether to connect the endpiece zigzag segments on both sides to the same infill line
|
||||
*/
|
||||
void generateLinearBasedInfill(const int outline_offset, bool safe_outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags);
|
||||
|
||||
/*!
|
||||
* adapted from generateLineInfill(.)
|
||||
*
|
||||
* generate lines within the area of [in_outline], at regular intervals of [lineSpacing]
|
||||
* generate lines within the area of [in_outline], at regular intervals of [line_distance]
|
||||
* idea:
|
||||
* intersect a regular grid of 'scanlines' with the area inside [in_outline]
|
||||
* sigzag:
|
||||
* intersect a regular grid of 'scanlines' with the area inside [in_outline] (see generateLineInfill)
|
||||
* zigzag:
|
||||
* include pieces of boundary, connecting the lines, forming an accordion like zigzag instead of separate lines |_|^|_|
|
||||
*
|
||||
* we call the areas between two consecutive scanlines a 'scansegment'
|
||||
*
|
||||
* algorithm:
|
||||
* 1. for each line segment of each polygon:
|
||||
* store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections
|
||||
* (zigzag): add boundary segments to result
|
||||
* 2. for each scanline:
|
||||
* sort the associated intersections
|
||||
* and connect them using the even-odd rule
|
||||
*
|
||||
* zigzag algorithm:
|
||||
* while walking around (each) polygon (1.)
|
||||
* if polygon intersects with even scanline
|
||||
* start boundary segment (add each following segment to the [result])
|
||||
* when polygon intersects with a scanline again
|
||||
* stop boundary segment (stop adding segments to the [result])
|
||||
* if polygon intersects with even scanline again (instead of odd)
|
||||
* dont add the last line segment to the boundary (unless [connect_zigzags])
|
||||
*
|
||||
* Note that ZigZag consists of 3 types:
|
||||
* - without endpieces
|
||||
* - with disconnected endpieces
|
||||
* - with connected endpieces
|
||||
*
|
||||
* <--
|
||||
* ___
|
||||
@@ -106,21 +148,37 @@ namespace cura
|
||||
* -->
|
||||
*
|
||||
* ^ = even scanline
|
||||
* ^ ^ no endpieces
|
||||
*
|
||||
* start boundary from even scanline! :D
|
||||
*
|
||||
*
|
||||
* v disconnected end piece: leave out last line segment
|
||||
* _____
|
||||
* | | | ,
|
||||
* | | | \ .
|
||||
* | | | |
|
||||
* |_____| |__/
|
||||
*
|
||||
* ^ ^ ^ scanlines
|
||||
* ^ disconnected end piece
|
||||
*
|
||||
*
|
||||
* v connected end piece
|
||||
* ________
|
||||
* | | | \ .
|
||||
* | | | |
|
||||
* |_____| |__/ .
|
||||
*
|
||||
* ^ ^ ^ scanlines
|
||||
*
|
||||
* \param result (output) The resulting lines
|
||||
* \param line_distance The distance between two lines which are in the same direction
|
||||
* \param fill_angle The angle of the generated lines
|
||||
* \param connected_zigzags Whether to connect the endpiece zigzag segments on both sides to the same infill line
|
||||
* \param use_endpieces Whether to include zigzag segments connecting a scanline to itself
|
||||
*/
|
||||
void generateZigZagIninfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags);
|
||||
void generateZigZagInfill(Polygons& result, const int line_distance, const double& fill_angle, const bool connected_zigzags, const bool use_endpieces);
|
||||
};
|
||||
|
||||
void generateZigZagIninfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation);
|
||||
}//namespace cura
|
||||
|
||||
#endif//INFILL_H
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef INFILL_ACTUAL_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
#define INFILL_ACTUAL_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "ZigzagConnectorProcessor.h"
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* In contrast to NoZigZagConnectorProcessor
|
||||
*/
|
||||
class ActualZigzagConnectorProcessor : public ZigzagConnectorProcessor
|
||||
{
|
||||
protected:
|
||||
/*!
|
||||
* The line segments belonging the zigzag connector to which the very first vertex belongs.
|
||||
* This will be combined with the last handled zigzag_connector, which combine to a whole zigzag connector.
|
||||
*
|
||||
* Because the boundary polygon may start in in the middle of a zigzag connector,
|
||||
*/
|
||||
std::vector<Point> first_zigzag_connector;
|
||||
/*!
|
||||
* The currently built up zigzag connector (not the first/last) or end piece or discarded boundary segment
|
||||
*/
|
||||
std::vector<Point> zigzag_connector;
|
||||
|
||||
bool is_first_zigzag_connector; //!< Whether we're still in the first zigzag connector
|
||||
bool first_zigzag_connector_ends_in_even_scanline; //!< Whether the first zigzag connector ends in an even scanline
|
||||
bool last_scanline_is_even; //!< Whether the last seen scanline-boundary intersection was with an even scanline
|
||||
|
||||
ActualZigzagConnectorProcessor(const PointMatrix& rotation_matrix, Polygons& result)
|
||||
: ZigzagConnectorProcessor(rotation_matrix, result)
|
||||
, is_first_zigzag_connector(true)
|
||||
, first_zigzag_connector_ends_in_even_scanline(true)
|
||||
, last_scanline_is_even(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
|
||||
#endif // INFILL_ACTUAL_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
@@ -0,0 +1,25 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "NoZigZagConnectorProcessor.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void NoZigZagConnectorProcessor::registerVertex(const Point& vertex)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void NoZigZagConnectorProcessor::registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void NoZigZagConnectorProcessor::registerPolyFinished()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,28 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef INFILL_NO_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
#define INFILL_NO_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "ZigzagConnectorProcessor.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class NoZigZagConnectorProcessor : public ZigzagConnectorProcessor
|
||||
{
|
||||
public:
|
||||
NoZigZagConnectorProcessor(const PointMatrix& rotation_matrix, Polygons& result)
|
||||
: ZigzagConnectorProcessor(rotation_matrix, result)
|
||||
{
|
||||
}
|
||||
|
||||
void registerVertex(const Point& vertex);
|
||||
void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even);
|
||||
void registerPolyFinished();
|
||||
};
|
||||
|
||||
|
||||
} // namespace cura
|
||||
|
||||
|
||||
#endif // INFILL_NO_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
@@ -0,0 +1,154 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Processor class for processing the connections between lines which makes the infill a zigzag pattern.
|
||||
*
|
||||
* During the creation of the infill lines, calls are made to a ZigzagConnectorProcessor so that the zigzag connector segments are created
|
||||
* at the same time as the lines are created.
|
||||
*
|
||||
* generate lines within the area of [in_outline], at regular intervals of [line_distance]
|
||||
* idea:
|
||||
* intersect a regular grid of 'scanlines' with the area inside [in_outline] (see generateLineInfill)
|
||||
* zigzag:
|
||||
* include pieces of boundary, connecting the lines, forming an accordion like zigzag instead of separate lines |_|^|_|
|
||||
*
|
||||
* we call the areas between two consecutive scanlines a 'scansegment'
|
||||
*
|
||||
* algorithm:
|
||||
* 1. for each line segment of each polygon:
|
||||
* store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections
|
||||
* (zigzag): add boundary segments to result
|
||||
* 2. for each scanline:
|
||||
* sort the associated intersections
|
||||
* and connect them using the even-odd rule
|
||||
*
|
||||
* zigzag algorithm:
|
||||
* while walking around (each) polygon (1.)
|
||||
* if polygon intersects with even scanline
|
||||
* start boundary segment (add each following segment to the [result])
|
||||
* when polygon intersects with a scanline again
|
||||
* stop boundary segment (stop adding segments to the [result])
|
||||
* if polygon intersects with even scanline again (instead of odd)
|
||||
* dont add the last line segment to the boundary (unless [connected_zigzags])
|
||||
*
|
||||
* Note that ZigZag consists of 3 types:
|
||||
* - without endpieces
|
||||
* - with disconnected endpieces
|
||||
* - with connected endpieces
|
||||
*
|
||||
* Each of these has a base class for which ZigzagConnectorProcessor is an ancestor.
|
||||
* The inheritance structure is as such:
|
||||
* ZigzagConnectorProcessor
|
||||
* / \ .
|
||||
* / \ .
|
||||
* ActualZigzagConnectorProcessor NoZigZagConnectorProcessor
|
||||
* / \ for lines infill .
|
||||
* / \ .
|
||||
* ZigzagConnectorProcessorEndPieces ZigzagConnectorProcessorNoEndPieces
|
||||
* / \ for zigzag infill (without end pieces) .
|
||||
* / \ .
|
||||
* ZigzagConnectorProcessorConnectedEndPieces ZigzagConnectorProcessorDisconnectedEndPieces
|
||||
* for zigzag support with normal endpieces for zigzag support with disconnected endpieces for more easy removability
|
||||
*
|
||||
* v v zigzag connectors
|
||||
* <--
|
||||
* :___: : < scanlines
|
||||
* | | |
|
||||
* | | | < infill lines along scanlines
|
||||
* | |___|
|
||||
* : : :
|
||||
* --> winding order of polygon
|
||||
*
|
||||
* ^ = even scanline
|
||||
* ^ ^ no endpieces
|
||||
*
|
||||
* start boundary from even scanline! :D
|
||||
* include only a boundary segment if it starts in an even scanline and ends in an odd scanline
|
||||
*
|
||||
* ________
|
||||
* | | | \ .
|
||||
* | | | |
|
||||
* |_____| |__/ .
|
||||
*
|
||||
* ^ ^ ^ scanlines
|
||||
* ^ connected end piece
|
||||
* include a boundary segment also if it starts in an odd scanline and ends odd,
|
||||
* or starts in an even scanline and ends in an even scanline,
|
||||
* but not when it starts in an odd and ends in an even scanline (see top left or bottom middle).
|
||||
*
|
||||
* _____
|
||||
* | | | \ .
|
||||
* | | | |
|
||||
* |_____| |__/
|
||||
*
|
||||
* ^ ^ ^ scanlines
|
||||
* ^ disconnected end piece
|
||||
* Leave out the last line segment of the boundary polygon: from a vertex to the linesegment-scanline intersection.
|
||||
*/
|
||||
class ZigzagConnectorProcessor
|
||||
{
|
||||
protected:
|
||||
const PointMatrix& rotation_matrix; //!< The rotation matrix used to enforce the infill angle
|
||||
Polygons& result; //!< The result of the computation
|
||||
|
||||
virtual ~ZigzagConnectorProcessor()
|
||||
{}
|
||||
|
||||
/*!
|
||||
* Add a line to the result bu unapplying the rotation rotation_matrix.
|
||||
*
|
||||
* \param from The one end of the line segment
|
||||
* \param to The other end of the line segment
|
||||
*/
|
||||
void addLine(Point from, Point to)
|
||||
{
|
||||
PolygonRef line_poly = result.newPoly();
|
||||
line_poly.add(rotation_matrix.unapply(from));
|
||||
line_poly.add(rotation_matrix.unapply(to));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Basic constructor. Inheriting children should call this constructor.
|
||||
*
|
||||
* \param rotation_matrix The rotation matrix used to enforce the infill angle
|
||||
* \param result The resulting line segments (Each line segment is a Polygon with 2 points)
|
||||
*/
|
||||
ZigzagConnectorProcessor(const PointMatrix& rotation_matrix, Polygons& result)
|
||||
: rotation_matrix(rotation_matrix)
|
||||
, result(result)
|
||||
{}
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Handle the next vertex on the outer boundary.
|
||||
* \param vertex The vertex
|
||||
*/
|
||||
virtual void registerVertex(const Point& vertex) = 0;
|
||||
|
||||
/*!
|
||||
* Handle the next intersection between a scanline and the outer boundary.
|
||||
*
|
||||
* \param intersection The intersection
|
||||
* \param scanline_is_even Whether the scanline was even
|
||||
*/
|
||||
virtual void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even) = 0;
|
||||
|
||||
/*!
|
||||
* Handle the end of a polygon and prepare for the next.
|
||||
* This function should reset all member variables.
|
||||
*/
|
||||
virtual void registerPolyFinished() = 0;
|
||||
};
|
||||
|
||||
|
||||
} // namespace cura
|
||||
|
||||
|
||||
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_H
|
||||
@@ -0,0 +1,75 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "ZigzagConnectorProcessorConnectedEndPieces.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
void ZigzagConnectorProcessorConnectedEndPieces::registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even)
|
||||
{
|
||||
bool previous_scanline_is_even = last_scanline_is_even;
|
||||
last_scanline_is_even = scanline_is_even;
|
||||
bool this_scanline_is_even = last_scanline_is_even;
|
||||
|
||||
if (is_first_zigzag_connector)
|
||||
{
|
||||
first_zigzag_connector.push_back(intersection);
|
||||
first_zigzag_connector_ends_in_even_scanline = this_scanline_is_even;
|
||||
is_first_zigzag_connector = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (previous_scanline_is_even)
|
||||
{ // when a boundary segment starts in an even scanline it is either a normal zigzag connector or an endpiece, so it should be included anyway
|
||||
addLine(last_connector_point, intersection);
|
||||
}
|
||||
else if (!previous_scanline_is_even && !this_scanline_is_even) // if we end an odd boundary in an odd segment
|
||||
{ // add whole zigzag_connector (including the just obtained point)
|
||||
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
|
||||
{
|
||||
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
|
||||
}
|
||||
addLine(zigzag_connector.back(), intersection);
|
||||
zigzag_connector.clear();
|
||||
}
|
||||
|
||||
}
|
||||
zigzag_connector.clear(); // we're starting a new (odd) zigzag connector, so clear the old one
|
||||
if (!this_scanline_is_even) // we are either in an end piece or an boundary segment starting in an odd scanline
|
||||
{ // only when a boundary segment starts in an odd scanline it depends on whether it ends in an odd scanline for whether this segment should be included or not
|
||||
zigzag_connector.push_back(intersection);
|
||||
}
|
||||
|
||||
last_connector_point = intersection;
|
||||
}
|
||||
|
||||
|
||||
void ZigzagConnectorProcessorConnectedEndPieces::registerPolyFinished()
|
||||
{
|
||||
// write end segment if needed (first half of start/end-crossing segment)
|
||||
if (!last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
|
||||
{
|
||||
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
|
||||
{
|
||||
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
|
||||
}
|
||||
}
|
||||
// write begin segment if needed (second half of start/end-crossing segment)
|
||||
if (last_scanline_is_even || (!last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
|
||||
|| is_first_zigzag_connector)
|
||||
{
|
||||
for (unsigned int point_idx = 1; point_idx < first_zigzag_connector.size(); point_idx++)
|
||||
{
|
||||
addLine(first_zigzag_connector[point_idx - 1], first_zigzag_connector[point_idx]);
|
||||
}
|
||||
}
|
||||
// reset member variables
|
||||
is_first_zigzag_connector = true;
|
||||
first_zigzag_connector_ends_in_even_scanline = true;
|
||||
last_scanline_is_even = false;
|
||||
first_zigzag_connector.clear();
|
||||
zigzag_connector.clear();
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,27 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_CONNECTED_END_PIECES_H
|
||||
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_CONNECTED_END_PIECES_H
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "ZigzagConnectorProcessorEndPieces.h"
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
class ZigzagConnectorProcessorConnectedEndPieces : public ZigzagConnectorProcessorEndPieces
|
||||
{
|
||||
public:
|
||||
ZigzagConnectorProcessorConnectedEndPieces(const PointMatrix& rotation_matrix, Polygons& result)
|
||||
: ZigzagConnectorProcessorEndPieces(rotation_matrix, result)
|
||||
{
|
||||
}
|
||||
void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even);
|
||||
void registerPolyFinished();
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
|
||||
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_CONNECTED_END_PIECES_H
|
||||
@@ -0,0 +1,79 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "ZigzagConnectorProcessorDisconnectedEndPieces.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void ZigzagConnectorProcessorDisconnectedEndPieces::registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even)
|
||||
{
|
||||
bool previous_scanline_is_even = last_scanline_is_even;
|
||||
last_scanline_is_even = scanline_is_even;
|
||||
bool this_scanline_is_even = last_scanline_is_even;
|
||||
|
||||
if (is_first_zigzag_connector)
|
||||
{
|
||||
first_zigzag_connector.push_back(intersection);
|
||||
first_zigzag_connector_ends_in_even_scanline = this_scanline_is_even;
|
||||
is_first_zigzag_connector = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (previous_scanline_is_even && !this_scanline_is_even)
|
||||
{ // if we left from an even scanline, but not if this is the line segment connecting that zigzag_connector to an even scanline
|
||||
addLine(last_connector_point, intersection);
|
||||
}
|
||||
else if (!previous_scanline_is_even && !this_scanline_is_even) // if we end an odd boundary in an odd segment
|
||||
{ // add whole oddBoundarySegment (including the just obtained point)
|
||||
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
|
||||
{
|
||||
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
|
||||
}
|
||||
// skip the last segment to the [intersection]
|
||||
zigzag_connector.clear();
|
||||
}
|
||||
|
||||
}
|
||||
zigzag_connector.clear(); // we're starting a new (odd) zigzag connector, so clear the old one
|
||||
if (!this_scanline_is_even) // we are either in an end piece or an boundary segment starting in an odd scanline
|
||||
{ // only when a boundary segment starts in an odd scanline it depends on whether it ends in an odd scanline for whether this segment should be included or not
|
||||
zigzag_connector.push_back(intersection);
|
||||
}
|
||||
|
||||
last_connector_point = intersection;
|
||||
}
|
||||
|
||||
|
||||
void ZigzagConnectorProcessorDisconnectedEndPieces::registerPolyFinished()
|
||||
{
|
||||
// write end segment if needed (first half of start/end-crossing segment)
|
||||
if (!last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
|
||||
{
|
||||
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
|
||||
{
|
||||
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
|
||||
}
|
||||
}
|
||||
// write begin segment if needed (second half of start/end-crossing segment)
|
||||
if (last_scanline_is_even || is_first_zigzag_connector)
|
||||
{
|
||||
for (unsigned int point_idx = 1; point_idx < first_zigzag_connector.size() - 1; point_idx++) // -1 cause skipping very last line segment!
|
||||
{
|
||||
addLine(first_zigzag_connector[point_idx - 1], first_zigzag_connector[point_idx]);
|
||||
}
|
||||
}
|
||||
// write very last line segment if needed
|
||||
if (last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
|
||||
{ // only add last element if boundary segment ends in odd scanline
|
||||
addLine(first_zigzag_connector[first_zigzag_connector.size() - 2], first_zigzag_connector[first_zigzag_connector.size() - 1]);
|
||||
}
|
||||
// reset member variables
|
||||
is_first_zigzag_connector = true;
|
||||
first_zigzag_connector_ends_in_even_scanline = true;
|
||||
last_scanline_is_even = false;
|
||||
first_zigzag_connector.clear();
|
||||
zigzag_connector.clear();
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,26 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_DISCONNECTED_END_PIECES_H
|
||||
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_DISCONNECTED_END_PIECES_H
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "ZigzagConnectorProcessorEndPieces.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class ZigzagConnectorProcessorDisconnectedEndPieces : public ZigzagConnectorProcessorEndPieces
|
||||
{
|
||||
|
||||
public:
|
||||
ZigzagConnectorProcessorDisconnectedEndPieces(const PointMatrix& rotation_matrix, Polygons& result)
|
||||
: ZigzagConnectorProcessorEndPieces(rotation_matrix, result)
|
||||
{
|
||||
}
|
||||
void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even);
|
||||
void registerPolyFinished();
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
|
||||
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_DISCONNECTED_END_PIECES_H
|
||||
@@ -0,0 +1,27 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "ZigzagConnectorProcessorEndPieces.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void ZigzagConnectorProcessorEndPieces::registerVertex(const Point& vertex)
|
||||
{
|
||||
if (is_first_zigzag_connector)
|
||||
{
|
||||
first_zigzag_connector.push_back(vertex);
|
||||
}
|
||||
else if (last_scanline_is_even)
|
||||
{ // when a boundary segments starts in an even scanline it's either a normal zigzag connector or an endpiece to be included
|
||||
// note that for ZigzagConnectorProcessorDisconnectedEndPieces only the last line segment from a boundary vertex to a scanline-boundary intersection is omitted
|
||||
addLine(last_connector_point, vertex);
|
||||
}
|
||||
else
|
||||
{ // it's yet unclear whether the line segment should be included, so we store it until we know
|
||||
zigzag_connector.push_back(vertex);
|
||||
}
|
||||
last_connector_point = vertex;
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,32 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_END_PIECES_H
|
||||
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_END_PIECES_H
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "ActualZigzagConnectorProcessor.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
class ZigzagConnectorProcessorEndPieces : public ActualZigzagConnectorProcessor
|
||||
{
|
||||
protected:
|
||||
Point last_connector_point; //!< last registered boundary vertex or scanline-coundary intersection
|
||||
|
||||
ZigzagConnectorProcessorEndPieces(const PointMatrix& rotation_matrix, Polygons& result)
|
||||
: ActualZigzagConnectorProcessor(rotation_matrix, result)
|
||||
, last_connector_point(0,0)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
void registerVertex(const Point& vertex);
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace cura
|
||||
|
||||
|
||||
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_END_PIECES_H
|
||||
@@ -0,0 +1,72 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "ZigzagConnectorProcessorNoEndPieces.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void ZigzagConnectorProcessorNoEndPieces::registerVertex(const Point& vertex)
|
||||
{
|
||||
if (is_first_zigzag_connector)
|
||||
{
|
||||
first_zigzag_connector.push_back(vertex);
|
||||
}
|
||||
else if (last_scanline_is_even)
|
||||
{
|
||||
zigzag_connector.push_back(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ZigzagConnectorProcessorNoEndPieces::registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even)
|
||||
{
|
||||
bool previous_scanline_is_even = last_scanline_is_even;
|
||||
last_scanline_is_even = scanline_is_even;
|
||||
bool this_scanline_is_even = last_scanline_is_even; // for conceptual clarity
|
||||
|
||||
if (is_first_zigzag_connector)
|
||||
{
|
||||
first_zigzag_connector.push_back(intersection);
|
||||
first_zigzag_connector_ends_in_even_scanline = this_scanline_is_even;
|
||||
is_first_zigzag_connector = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (previous_scanline_is_even && !this_scanline_is_even)
|
||||
{ // add whole zigzag_connector (including the just obtained point)
|
||||
for (unsigned int point_idx = 1; point_idx < zigzag_connector.size(); point_idx++)
|
||||
{
|
||||
addLine(zigzag_connector[point_idx - 1], zigzag_connector[point_idx]);
|
||||
}
|
||||
addLine(zigzag_connector.back(), intersection);
|
||||
zigzag_connector.clear();
|
||||
}
|
||||
}
|
||||
zigzag_connector.clear(); // we're starting a new zigzag connector, so clear the old one
|
||||
if (this_scanline_is_even) // only boundary segments starting in an even segment are considered
|
||||
{
|
||||
zigzag_connector.push_back(intersection);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ZigzagConnectorProcessorNoEndPieces::registerPolyFinished()
|
||||
{
|
||||
if (!is_first_zigzag_connector && last_scanline_is_even && !first_zigzag_connector_ends_in_even_scanline)
|
||||
{ // only if it's a normal zigzag connector; not when the whole boundary didn't cross any scanlines
|
||||
for (unsigned int point_idx = 1; point_idx < first_zigzag_connector.size() ; point_idx++)
|
||||
{
|
||||
addLine(first_zigzag_connector[point_idx - 1], first_zigzag_connector[point_idx]);
|
||||
}
|
||||
}
|
||||
// reset member variables
|
||||
is_first_zigzag_connector = true;
|
||||
first_zigzag_connector_ends_in_even_scanline = true;
|
||||
last_scanline_is_even = false;
|
||||
first_zigzag_connector.clear();
|
||||
zigzag_connector.clear();
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,29 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef INFILL_ZIGZAG_CONNECTOR_PROCESSOR_NO_ENDPIECES_H
|
||||
#define INFILL_ZIGZAG_CONNECTOR_PROCESSOR_NO_ENDPIECES_H
|
||||
|
||||
#include "../utils/polygon.h"
|
||||
#include "ActualZigzagConnectorProcessor.h"
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class ZigzagConnectorProcessorNoEndPieces : public ActualZigzagConnectorProcessor
|
||||
{
|
||||
public:
|
||||
ZigzagConnectorProcessorNoEndPieces(const PointMatrix& rotation_matrix, Polygons& result)
|
||||
: ActualZigzagConnectorProcessor(rotation_matrix, result)
|
||||
{
|
||||
}
|
||||
|
||||
void registerVertex(const Point& vertex);
|
||||
void registerScanlineSegmentIntersection(const Point& intersection, bool scanline_is_even);
|
||||
void registerPolyFinished();
|
||||
};
|
||||
|
||||
|
||||
} // namespace cura
|
||||
|
||||
|
||||
#endif // INFILL_ZIGZAG_CONNECTOR_PROCESSOR_NO_ENDPIECES_H
|
||||
@@ -71,33 +71,4 @@ void generateInsets(SliceLayer* layer, int nozzle_width, int line_width_0, int l
|
||||
}
|
||||
}
|
||||
|
||||
void generateWallReinforcementWallExtraWalls(SliceLayerPart* part, ReinforcementWall& reinforcement_wall, int line_width_x, int insetCount, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
// optimize all the polygons. Every point removed saves time in the long run.
|
||||
reinforcement_wall.wall_reinforcement_axtra_walls[0].simplify();
|
||||
if (reinforcement_wall.wall_reinforcement_axtra_walls[0].size() < 1)
|
||||
{
|
||||
reinforcement_wall.wall_reinforcement_axtra_walls.pop_back();
|
||||
}
|
||||
|
||||
if (reinforcement_wall.wall_reinforcement_axtra_walls[0].size() > 0)
|
||||
{
|
||||
for(int i=1; i<insetCount; i++)
|
||||
{
|
||||
reinforcement_wall.wall_reinforcement_axtra_walls.push_back(Polygons());
|
||||
PolygonUtils::offsetExtrusionWidth(reinforcement_wall.wall_reinforcement_axtra_walls[i-1], true, line_width_x, reinforcement_wall.wall_reinforcement_axtra_walls[i], &part->perimeterGaps, avoidOverlappingPerimeters);
|
||||
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
reinforcement_wall.wall_reinforcement_axtra_walls[i].simplify();
|
||||
if (reinforcement_wall.wall_reinforcement_axtra_walls[i].size() < 1)
|
||||
{
|
||||
reinforcement_wall.wall_reinforcement_axtra_walls.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -36,16 +36,6 @@ void generateInsets(SliceLayerPart* part, int nozzle_width, int line_width_0, in
|
||||
*/
|
||||
void generateInsets(SliceLayer* layer, int nozzle_width, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
|
||||
/*!
|
||||
* Generates the wall reinforcement extra walls for a single layer part.
|
||||
*
|
||||
* \param part The part for which to generate the extra walls.
|
||||
* \param line_width_x line width of the walls
|
||||
* \param insetCount The number of insets to to generate
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
|
||||
*/
|
||||
void generateWallReinforcementWallExtraWalls(SliceLayerPart* part, ReinforcementWall& reinforcement_wall, int line_width_x, int insetCount, bool avoidOverlappingPerimeters);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//INSET_H
|
||||
|
||||
+4
-3
@@ -66,7 +66,7 @@ void print_call(int argc, char **argv)
|
||||
|
||||
void connect(int argc, char **argv)
|
||||
{
|
||||
CommandSocket* commandSocket = new CommandSocket();
|
||||
CommandSocket::instantiate();
|
||||
std::string ip;
|
||||
int port = 49674;
|
||||
|
||||
@@ -107,7 +107,7 @@ void connect(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
commandSocket->connect(ip, port);
|
||||
CommandSocket::getInstance()->connect(ip, port);
|
||||
}
|
||||
|
||||
void slice(int argc, char **argv)
|
||||
@@ -148,12 +148,13 @@ void slice(int argc, char **argv)
|
||||
FffProcessor::getInstance()->time_keeper.restart();
|
||||
delete meshgroup;
|
||||
meshgroup = new MeshGroup(FffProcessor::getInstance());
|
||||
last_extruder_train = meshgroup->createExtruderTrain(0);
|
||||
last_settings_object = meshgroup;
|
||||
|
||||
}catch(...){
|
||||
cura::logError("Unknown exception\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
}else{
|
||||
cura::logError("Unknown option: %s\n", str);
|
||||
}
|
||||
|
||||
+162
-123
@@ -2,25 +2,27 @@
|
||||
#include "pathOrderOptimizer.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/BucketGrid2D.h"
|
||||
#include "utils/TravellingSalesman.h"
|
||||
|
||||
#define INLINE static inline
|
||||
|
||||
namespace cura {
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
*
|
||||
*/
|
||||
void PathOrderOptimizer::optimize()
|
||||
{
|
||||
bool picked[polygons.size()];
|
||||
memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses
|
||||
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked
|
||||
memset(picked, false, sizeof (bool) * polygons.size()); /// initialized as falses
|
||||
|
||||
for (unsigned int i_polygon = 0; i_polygon < polygons.size(); i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked
|
||||
{
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
PolygonRef poly = polygons[i_polygon];
|
||||
for(unsigned int i_point=0; i_point<poly.size(); i_point++) /// get closest point in polygon
|
||||
for (unsigned int i_point = 0; i_point < poly.size(); i_point++) /// get closest point in polygon
|
||||
{
|
||||
float dist = vSize2f(poly[i_point] - startPoint);
|
||||
if (dist < bestDist)
|
||||
@@ -37,23 +39,23 @@ void PathOrderOptimizer::optimize()
|
||||
|
||||
|
||||
Point prev_point = startPoint;
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// actual path order optimizer
|
||||
for (unsigned int polygon_idx = 0; polygon_idx < polygons.size(); polygon_idx++) /// actual path order optimizer
|
||||
{
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
|
||||
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++)
|
||||
for (unsigned int polygon2_idx = 0; polygon2_idx < polygons.size(); polygon2_idx++)
|
||||
{
|
||||
if (picked[i_polygon] || polygons[i_polygon].size() < 1) /// skip single-point-polygons
|
||||
if (picked[polygon2_idx] || polygons[polygon2_idx].size() < 1) /// skip single-point-polygons
|
||||
continue;
|
||||
|
||||
assert (polygons[i_polygon].size() != 2);
|
||||
assert(polygons[polygon2_idx].size() != 2);
|
||||
|
||||
float dist = vSize2f(polygons[i_polygon][polyStart[i_polygon]] - prev_point);
|
||||
float dist = vSize2f(polygons[polygon2_idx][polyStart[polygon2_idx]] - prev_point);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
best = i_polygon;
|
||||
best = polygon2_idx;
|
||||
bestDist = dist;
|
||||
}
|
||||
|
||||
@@ -74,7 +76,7 @@ void PathOrderOptimizer::optimize()
|
||||
}
|
||||
|
||||
prev_point = startPoint;
|
||||
for(unsigned int n=0; n<polyOrder.size(); n++) /// decide final starting points in each polygon
|
||||
for (unsigned int n = 0; n < polyOrder.size(); n++) /// decide final starting points in each polygon
|
||||
{
|
||||
int poly_idx = polyOrder[n];
|
||||
int point_idx = getPolyStart(prev_point, poly_idx);
|
||||
@@ -88,24 +90,23 @@ int PathOrderOptimizer::getPolyStart(Point prev_point, int poly_idx)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EZSeamType::BACK: return getFarthestPointInPolygon(poly_idx);
|
||||
case EZSeamType::RANDOM: return getRandomPointInPolygon(poly_idx);
|
||||
case EZSeamType::BACK: return getFarthestPointInPolygon(poly_idx);
|
||||
case EZSeamType::RANDOM: return getRandomPointInPolygon(poly_idx);
|
||||
case EZSeamType::SHORTEST: return getClosestPointInPolygon(prev_point, poly_idx);
|
||||
default: return getClosestPointInPolygon(prev_point, poly_idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int PathOrderOptimizer::getClosestPointInPolygon(Point prev_point, int poly_idx)
|
||||
{
|
||||
PolygonRef poly = polygons[poly_idx];
|
||||
int best_point_idx = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
bool orientation = poly.orientation();
|
||||
for(unsigned int i_point=0 ; i_point<poly.size() ; i_point++)
|
||||
for (unsigned int i_point = 0; i_point < poly.size(); i_point++)
|
||||
{
|
||||
float dist = vSize2f(poly[i_point] - prev_point);
|
||||
Point n0 = normal(poly[(i_point-1+poly.size())%poly.size()] - poly[i_point], 2000);
|
||||
Point n0 = normal(poly[(i_point - 1 + poly.size()) % poly.size()] - poly[i_point], 2000);
|
||||
Point n1 = normal(poly[i_point] - poly[(i_point + 1) % poly.size()], 2000);
|
||||
float dot_score = dot(n0, n1) - dot(crossZ(n0), n1); /// prefer binnenbocht
|
||||
if (orientation)
|
||||
@@ -124,13 +125,12 @@ int PathOrderOptimizer::getRandomPointInPolygon(int poly_idx)
|
||||
return rand() % polygons[poly_idx].size();
|
||||
}
|
||||
|
||||
|
||||
int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
|
||||
{
|
||||
PolygonRef poly = polygons[poly_idx];
|
||||
int best_point_idx = -1;
|
||||
float best_y = std::numeric_limits<float>::min();
|
||||
for(unsigned int point_idx=0 ; point_idx<poly.size() ; point_idx++)
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
if (poly[point_idx].Y > best_y)
|
||||
{
|
||||
@@ -141,136 +141,175 @@ int PathOrderOptimizer::getFarthestPointInPolygon(int poly_idx)
|
||||
return best_point_idx;
|
||||
}
|
||||
|
||||
LineOrderOptimizer::LineOrderOptimizer(const Point& start_point, unsigned long long cluster_grid_size)
|
||||
: cluster_grid_size(cluster_grid_size == 0 ? 2000 : cluster_grid_size) //Initialise cluster_grid_size to 2000 if the input grid size is invalid (e.g. no infill).
|
||||
{
|
||||
this->startPoint = start_point;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
*
|
||||
*/
|
||||
void LineOrderOptimizer::optimize()
|
||||
{
|
||||
int gridSize = 5000; // the size of the cells in the hash grid.
|
||||
BucketGrid2D<unsigned int> line_bucket_grid(gridSize);
|
||||
bool picked[polygons.size()];
|
||||
memset(picked, false, sizeof(bool) * polygons.size());/// initialized as falses
|
||||
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// find closest point to initial starting point within each polygon +initialize picked
|
||||
if (lines.empty()) //Nothing to do. Terminate early.
|
||||
{
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
PolygonRef poly = polygons[i_polygon];
|
||||
for(unsigned int i_point=0; i_point<poly.size(); i_point++) /// get closest point from polygon
|
||||
{
|
||||
float dist = vSize2f(poly[i_point] - startPoint);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
best = i_point;
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
polyStart.push_back(best);
|
||||
|
||||
assert(poly.size() == 2);
|
||||
|
||||
line_bucket_grid.insert(poly[0], i_polygon);
|
||||
line_bucket_grid.insert(poly[1], i_polygon);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//Since polyOrder must be filled with indices, an index in the polygons vector represents each line.
|
||||
std::vector<Cluster> line_clusters = cluster();
|
||||
|
||||
Point incommingPerpundicularNormal(0, 0);
|
||||
Point prev_point = startPoint;
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++) /// actual path order optimizer
|
||||
//Define how the TSP solver should use its elements.
|
||||
std::function<std::vector<std::pair<Point, Point>> (size_t)> get_orientations = [&](size_t cluster_index)->std::vector<std::pair<Point, Point>> //How to get the possible orientations of a cluster.
|
||||
{
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
|
||||
for(unsigned int i_close_line_polygon : line_bucket_grid.findNearbyObjects(prev_point)) /// check if single-line-polygon is close to last point
|
||||
std::vector<std::pair<Point, Point>> result;
|
||||
const size_t first_line_index = line_clusters[cluster_index][0]; //The first line in the current cluster.
|
||||
const size_t last_line_index = line_clusters[cluster_index].back(); //The last line in the current cluster.
|
||||
const PolygonRef first_line = lines[first_line_index];
|
||||
const PolygonRef last_line = lines[last_line_index];
|
||||
const Point start_normal = first_line[polyStart[first_line_index]]; //Start of the path, not mirrored.
|
||||
const Point end_normal = last_line[(polyStart[last_line_index] + last_line.size() - 1) % last_line.size()]; //End of the path, not mirrored.
|
||||
result.push_back(std::pair<Point, Point>(start_normal, end_normal));
|
||||
result.push_back(std::pair<Point, Point>(end_normal, start_normal)); //Can also insert in reverse!
|
||||
if (line_clusters[cluster_index].size() > 1u) //If the cluster has one line, mirroring the line is equal to reversing the path. Otherwise, we must also include mirrored options.
|
||||
{
|
||||
if (picked[i_close_line_polygon] || polygons[i_close_line_polygon].size() < 1)
|
||||
continue;
|
||||
|
||||
|
||||
checkIfLineIsBest(i_close_line_polygon, best, bestDist, prev_point, incommingPerpundicularNormal);
|
||||
|
||||
const Point start_mirrored = first_line[(polyStart[first_line_index] + first_line.size() - 1) % first_line.size()]; //Start of the path, mirrored.
|
||||
const Point end_mirrored = first_line[(polyStart[first_line_index] + first_line.size() - 1) % first_line.size()]; //End of the path, mirrored.
|
||||
result.push_back(std::pair<Point, Point>(start_mirrored, end_mirrored));
|
||||
result.push_back(std::pair<Point, Point>(end_mirrored, start_mirrored));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
TravellingSalesman<size_t> tspsolver(get_orientations); //Solves the macro TSP problem of ordering the clusters.
|
||||
std::vector<size_t> cluster_orientations;
|
||||
std::vector<size_t> unoptimised(line_clusters.size());
|
||||
std::iota(unoptimised.begin(), unoptimised.end(), 0);
|
||||
std::vector<size_t> optimised = tspsolver.findPath(unoptimised, cluster_orientations, &startPoint); //Approximate the shortest path with the TSP solver.
|
||||
|
||||
if (best == -1) /// if single-line-polygon hasn't been found yet
|
||||
//Actually put the paths in their correct order for the output.
|
||||
polyOrder.reserve(lines.size());
|
||||
for (size_t cluster_index = 0; cluster_index < optimised.size(); cluster_index++)
|
||||
{
|
||||
Cluster cluster = line_clusters[optimised[cluster_index]];
|
||||
//Determine in what orientation we should place the cluster depending on cluster_orientations[cluster_index].
|
||||
size_t orientation = cluster_orientations[cluster_index];
|
||||
if (cluster.size() == 1) //Singleton clusters have only 2 possible orientations.
|
||||
{
|
||||
for(unsigned int i_polygon=0 ; i_polygon<polygons.size() ; i_polygon++)
|
||||
polyOrder.push_back(static_cast<int>(cluster[0]));
|
||||
if (orientation >= 1)
|
||||
{
|
||||
if (picked[i_polygon] || polygons[i_polygon].size() < 1) /// skip single-point-polygons
|
||||
continue;
|
||||
assert(polygons[i_polygon].size() == 2);
|
||||
|
||||
checkIfLineIsBest(i_polygon, best, bestDist, prev_point, incommingPerpundicularNormal);
|
||||
|
||||
polyStart[cluster[0]] = 1 - polyStart[cluster[0]];
|
||||
}
|
||||
}
|
||||
|
||||
if (best > -1) /// should always be true; we should have been able to identify the best next polygon
|
||||
else //Larger clusters have 4 possible orientations.
|
||||
{
|
||||
assert(polygons[best].size() == 2);
|
||||
|
||||
int endIdx = polyStart[best] * -1 + 1; /// 1 -> 0 , 0 -> 1
|
||||
prev_point = polygons[best][endIdx];
|
||||
incommingPerpundicularNormal = crossZ(normal(polygons[best][endIdx] - polygons[best][polyStart[best]], 1000));
|
||||
|
||||
picked[best] = true;
|
||||
polyOrder.push_back(best);
|
||||
}
|
||||
else
|
||||
logError("Failed to find next closest line.\n");
|
||||
}
|
||||
|
||||
prev_point = startPoint;
|
||||
for(unsigned int n=0; n<polyOrder.size(); n++) /// decide final starting points in each polygon
|
||||
{
|
||||
int nr = polyOrder[n];
|
||||
PolygonRef poly = polygons[nr];
|
||||
int best = -1;
|
||||
float bestDist = std::numeric_limits<float>::infinity();
|
||||
bool orientation = poly.orientation();
|
||||
for(unsigned int i=0;i<poly.size(); i++)
|
||||
{
|
||||
float dist = vSize2f(polygons[nr][i] - prev_point);
|
||||
Point n0 = normal(poly[(i+poly.size()-1)%poly.size()] - poly[i], 2000);
|
||||
Point n1 = normal(poly[i] - poly[(i + 1) % poly.size()], 2000);
|
||||
float dot_score = dot(n0, n1) - dot(crossZ(n0), n1);
|
||||
if (orientation)
|
||||
dot_score = -dot_score;
|
||||
if (dist + dot_score < bestDist)
|
||||
if ((orientation & 1) == 0) //Not reversed.
|
||||
{
|
||||
best = i;
|
||||
bestDist = dist + dot_score;
|
||||
for (size_t polygon_index = 0; polygon_index < cluster.size(); polygon_index++)
|
||||
{
|
||||
polyOrder.push_back(static_cast<int>(cluster[polygon_index]));
|
||||
}
|
||||
}
|
||||
else //Reversed.
|
||||
{
|
||||
for (size_t polygon_index = cluster.size(); polygon_index-- > 0; ) //Insert the lines in backward direction.
|
||||
{
|
||||
polyOrder.push_back(static_cast<int>(cluster[polygon_index]));
|
||||
}
|
||||
}
|
||||
if (orientation >= 2u) //Mirrored.
|
||||
{
|
||||
for (size_t polygon_index : cluster) //Mirror each line in the cluster.
|
||||
{
|
||||
polyStart[polygon_index] = 1 - polyStart[polygon_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
polyStart[nr] = best;
|
||||
assert(poly.size() == 2);
|
||||
prev_point = poly[best *-1 + 1]; /// 1 -> 0 , 0 -> 1
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inline void LineOrderOptimizer::checkIfLineIsBest(unsigned int i_line_polygon, int& best, float& bestDist, Point& prev_point, Point& incommingPerpundicularNormal)
|
||||
std::vector<std::vector<size_t>> LineOrderOptimizer::cluster()
|
||||
{
|
||||
{ /// check distance to first point on line (0)
|
||||
float dist = vSize2f(polygons[i_line_polygon][0] - prev_point);
|
||||
dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i_line_polygon][1] - polygons[i_line_polygon][0], 1000))) * 0.0001f; /// penalize sharp corners
|
||||
if (dist < bestDist)
|
||||
polyStart.resize(lines.size()); //Polystart should always contain an entry for all polygons.
|
||||
BucketGrid2D<size_t> grid(cluster_grid_size);
|
||||
for (size_t polygon_index = 0; polygon_index < lines.size(); polygon_index++) //First put every endpoint of all lines in the grid.
|
||||
{
|
||||
grid.insert(lines[polygon_index][0], polygon_index);
|
||||
grid.insert(lines[polygon_index].back(), polygon_index);
|
||||
}
|
||||
|
||||
std::vector<Cluster> clusters;
|
||||
bool picked[lines.size()]; //For each polygon, whether it is already in a cluster.
|
||||
memset(picked, 0, lines.size()); //Initialise to false.
|
||||
for (size_t polygon_index = 0; polygon_index < lines.size(); polygon_index++) //Find clusters with nearest neighbour-ish search.
|
||||
{
|
||||
if (picked[polygon_index]) //Already in a cluster.
|
||||
{
|
||||
best = i_line_polygon;
|
||||
bestDist = dist;
|
||||
polyStart[i_line_polygon] = 0;
|
||||
continue;
|
||||
}
|
||||
clusters.push_back(Cluster()); //Make a new cluster for this line.
|
||||
clusters.back().push_back(polygon_index);
|
||||
polyStart[polygon_index] = 0; //Choose one possible mirroring of the lines. This determines the start point of each line. The mirror of a cluster is also checked by the TSP solver (but the combination of directions of each individual line is determined here below).
|
||||
picked[polygon_index] = true;
|
||||
size_t current_polygon = polygon_index; //We'll do a walk to the nearest valid neighbour. A neighbour is valid if it is not picked yet and if both its endpoints are near.
|
||||
size_t best_polygon = current_polygon;
|
||||
while (best_polygon != static_cast<size_t>(-1)) //Keep going until there is no valid neighbour.
|
||||
{
|
||||
const PolygonRef current_line = lines[current_polygon];
|
||||
Point current_start = current_line[polyStart[current_polygon]]; //Start and end point of the current polygon. These are used to find the distance to the next polygon.
|
||||
Point current_end = current_line[(polyStart[current_polygon] + current_line.size() - 1) % current_line.size()];
|
||||
best_polygon = static_cast<size_t>(-1);
|
||||
unsigned long long best_distance = cluster_grid_size * cluster_grid_size + 1; //grid_size squared since vSize2 gives squared distance.
|
||||
size_t best_start;
|
||||
for (size_t neighbour : grid.findNearbyObjects(current_line[0]))
|
||||
{
|
||||
if (picked[neighbour]) //Don't use neighbours that are already in another cluster.
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tryCluster(neighbour, current_start, current_end, &best_polygon, &best_distance, &best_start);
|
||||
}
|
||||
if (best_polygon != static_cast<size_t>(-1)) //We found one.
|
||||
{
|
||||
current_polygon = best_polygon;
|
||||
clusters.back().push_back(best_polygon);
|
||||
polyStart[best_polygon] = best_start;
|
||||
picked[best_polygon] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
{ /// check distance to second point on line (1)
|
||||
float dist = vSize2f(polygons[i_line_polygon][1] - prev_point);
|
||||
dist += abs(dot(incommingPerpundicularNormal, normal(polygons[i_line_polygon][0] - polygons[i_line_polygon][1], 1000) )) * 0.0001f; /// penalize sharp corners
|
||||
if (dist < bestDist)
|
||||
return clusters;
|
||||
}
|
||||
|
||||
void LineOrderOptimizer::tryCluster(size_t line, const Point current_start, const Point current_end, size_t* best_polygon, unsigned long long* best_distance, size_t* best_start)
|
||||
{
|
||||
//Input checking.
|
||||
if (!best_polygon || !best_distance || !best_start) //Output parameter missing.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned long long distance_start_start = vSize2(current_start - lines[line][0]);
|
||||
const unsigned long long distance_start_end = vSize2(current_start - lines[line].back());
|
||||
const unsigned long long distance_end_start = vSize2(current_end - lines[line][0]);
|
||||
const unsigned long long distance_end_end = vSize2(current_end - lines[line].back());
|
||||
if (distance_start_start < cluster_grid_size * cluster_grid_size && distance_end_end < cluster_grid_size * cluster_grid_size) //Two lines are alongside each other.
|
||||
{
|
||||
if (distance_end_end < *best_distance) //Best neighbour and orientation so far.
|
||||
{
|
||||
best = i_line_polygon;
|
||||
bestDist = dist;
|
||||
polyStart[i_line_polygon] = 1;
|
||||
*best_polygon = line;
|
||||
*best_distance = distance_end_end;
|
||||
*best_start = lines[line].size() - 1;
|
||||
}
|
||||
}
|
||||
if (distance_start_end < cluster_grid_size * cluster_grid_size && distance_end_start < cluster_grid_size * cluster_grid_size) //Two lines are alongside each other, but in reverse direction.
|
||||
{
|
||||
if (distance_end_start < *best_distance)
|
||||
{
|
||||
*best_polygon = line;
|
||||
*best_distance = distance_end_start;
|
||||
*best_start = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,33 +56,89 @@ private:
|
||||
*/
|
||||
class LineOrderOptimizer
|
||||
{
|
||||
typedef typename std::vector<size_t> Cluster; //To make it more clear what a cluster is.
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief The size of the grid cells used to cluster lines.
|
||||
*
|
||||
* Increase this value to make the optimisation algorithm fall back to
|
||||
* nearest neighbour more often. Reduce this value to make the optimisation
|
||||
* algorithm use random insertion on smaller pieces of the input.
|
||||
*/
|
||||
const unsigned long long cluster_grid_size;
|
||||
|
||||
Point startPoint; //!< The location of the nozzle before starting to print the current layer
|
||||
std::vector<PolygonRef> polygons; //!< the parts of the layer (in arbitrary order)
|
||||
std::vector<PolygonRef> lines; //!< 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
|
||||
|
||||
LineOrderOptimizer(Point startPoint)
|
||||
{
|
||||
this->startPoint = startPoint;
|
||||
}
|
||||
/*!
|
||||
* \brief Constructs the line order optimiser with the specified settings.
|
||||
*
|
||||
* \param start_point The starting point from where the paths generated by
|
||||
* this optimiser must start.
|
||||
* \param cluster_grid_size The size of the grid cells used to cluster
|
||||
* lines. Make this bigger and the optimiser will fall back to nearest
|
||||
* neighbour search more often. Make this smaller and the optimiser will
|
||||
* use random insertion more often.
|
||||
*/
|
||||
LineOrderOptimizer(const Point& start_point, unsigned long long cluster_grid_size);
|
||||
|
||||
void addPolygon(PolygonRef polygon)
|
||||
{
|
||||
this->polygons.push_back(polygon);
|
||||
this->lines.push_back(polygon);
|
||||
}
|
||||
|
||||
void addPolygons(Polygons& polygons)
|
||||
{
|
||||
for(unsigned int i=0;i<polygons.size(); i++)
|
||||
this->polygons.push_back(polygons[i]);
|
||||
for(unsigned int i = 0; i < polygons.size(); i++)
|
||||
{
|
||||
this->lines.push_back(polygons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void optimize(); //!< sets #polyStart and #polyOrder
|
||||
|
||||
private:
|
||||
void checkIfLineIsBest(unsigned int i_line_polygon, int& best, float& bestDist, Point& prev_point, Point& incommingPerpundicularNormal);
|
||||
/*!
|
||||
* \brief Clusters the polygons in groups such that the start and end of the
|
||||
* polygons in each group are close together.
|
||||
*
|
||||
* This performs a simple nearest-neighbour traversal through all lines. An
|
||||
* arbitrary line is chosen as starting point for a cluster, and iteratively
|
||||
* the nearest neighbouring line will get added to that cluster. A line is
|
||||
* only neighbouring if both of its endpoints are nearby the endpoints of
|
||||
* the previous line. This way you get logical groups of lines that should
|
||||
* always be in sequence, with fairly low computational cost.
|
||||
*
|
||||
* \return Clusters of polygons, where each cluster is represented with a
|
||||
* vector of indices pointing to positions in \link polygons.
|
||||
*/
|
||||
std::vector<Cluster> cluster();
|
||||
|
||||
/*!
|
||||
* \brief Tries to insert the specified line in the currently processed
|
||||
* cluster.
|
||||
*
|
||||
* The distance of the line to the current line (represented by
|
||||
* \p current_start and \p current_end) is measured, and compared to the
|
||||
* distance of the best candidate to insert so far. If it is shorter, then
|
||||
* the new line is stored via \p best_polygon as the best line to add to the
|
||||
* cluster.
|
||||
*
|
||||
* \param line The index of the new line to try to add to the cluster.
|
||||
* \param current_start The start point of the previous line that was added.
|
||||
* \param current_end The end point of the previous line that was added.
|
||||
* \param best_polygon The line that is the best candidate to insert so far.
|
||||
* This parameter may be changed by this function if the new line is better.
|
||||
* \param best_distance The distance of the best candidate to insert so far.
|
||||
* This parameter may be changed by this function if the new line is better.
|
||||
* \param best_start The starting vertex index of the best candidate to
|
||||
* insert so far. This parameter may be changed by this function if the new
|
||||
* line is better.
|
||||
*/
|
||||
void tryCluster(size_t line, const Point current_start, const Point current_end, size_t* best_polygon, unsigned long long* best_distance, size_t* best_start);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -236,6 +236,11 @@ int SettingRegistry::loadJSONsettingsFromDoc(rapidjson::Document& json_document,
|
||||
{
|
||||
std::string setting = override_iterator->name.GetString();
|
||||
SettingConfig* conf = getSettingConfig(setting);
|
||||
if (!conf) //Setting could not be found.
|
||||
{
|
||||
logWarning("Trying to override unknown setting %s.", setting.c_str());
|
||||
continue;
|
||||
}
|
||||
_loadSettingValues(conf, override_iterator, false);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -134,7 +134,7 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
}
|
||||
}
|
||||
|
||||
void generateInfill(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int infill_skin_overlap, int wall_line_count)
|
||||
void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count)
|
||||
{
|
||||
SliceLayer& layer = storage.layers[layerNr];
|
||||
|
||||
@@ -144,7 +144,7 @@ void generateInfill(int layerNr, SliceMeshStorage& storage, int extrusionWidth,
|
||||
{
|
||||
continue; // the last wall is not present, the part should only get inter preimeter gaps, but no infill.
|
||||
}
|
||||
Polygons infill = part.insets.back().offset(-extrusionWidth / 2 - infill_skin_overlap);
|
||||
Polygons infill = part.insets.back().offset(-innermost_wall_extrusion_width / 2 - infill_skin_overlap);
|
||||
|
||||
for(SliceLayerPart& part2 : layer.parts)
|
||||
{
|
||||
|
||||
+2
-2
@@ -67,11 +67,11 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
*
|
||||
* \param layerNr The index of the layer for which to generate the infill
|
||||
* \param part The part where the insets (input) are stored and where the infill (output) is stored.
|
||||
* \param extrusionWidth width of the wall lines
|
||||
* \param innermost_wall_extrusion_width width of the innermost wall lines
|
||||
* \param infill_skin_overlap overlap distance between infill and skin
|
||||
* \param wall_line_count The number of walls, i.e. the number of the wall from which to offset.
|
||||
*/
|
||||
void generateInfill(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int infill_skin_overlap, int wall_line_count);
|
||||
void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count);
|
||||
|
||||
/*!
|
||||
* \brief Combines the infill of multiple layers for a specified mesh.
|
||||
|
||||
@@ -59,13 +59,13 @@ void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls)
|
||||
SliceDataStorage::SliceDataStorage(MeshGroup* meshgroup) : SettingsMessenger(meshgroup),
|
||||
meshgroup(meshgroup != nullptr ? meshgroup : new MeshGroup(FffProcessor::getInstance())), //If no mesh group is provided, we roll our own.
|
||||
retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
travel_config(&retraction_config, "MOVE"),
|
||||
travel_config(&retraction_config, PrintFeatureType::MoveCombing),
|
||||
skirt_config(initializeSkirtConfigs()),
|
||||
raft_base_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], "SUPPORT"),
|
||||
raft_interface_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], "SUPPORT"),
|
||||
raft_surface_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], "SUPPORT"),
|
||||
support_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("support_infill_extruder_nr")], "SUPPORT"),
|
||||
support_roof_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("support_roof_extruder_nr")], "SKIN"),
|
||||
raft_base_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
raft_interface_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
raft_surface_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
support_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("support_extruder_nr")], PrintFeatureType::Support),
|
||||
support_roof_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("support_roof_extruder_nr")], PrintFeatureType::Skin),
|
||||
max_object_height_second_to_last_extruder(-1)
|
||||
{
|
||||
}
|
||||
@@ -161,93 +161,6 @@ Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool inc
|
||||
}
|
||||
|
||||
|
||||
std::vector<bool> SliceDataStorage::getExtrudersUsed(int layer_nr)
|
||||
{
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
if (layer_nr < 0)
|
||||
{
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true; // raft
|
||||
}
|
||||
else
|
||||
{
|
||||
if (layer_nr == 0)
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ooze shield, draft shield
|
||||
|
||||
// support
|
||||
if (support.supportLayers[layer_nr].supportAreas.size() > 0)
|
||||
{
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret[getSettingAsIndex("support_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
if (support.supportLayers[layer_nr].roofs.size() > 0)
|
||||
{
|
||||
ret[getSettingAsIndex("support_roof_extruder_nr")] = true;
|
||||
}
|
||||
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
int extr_nr = mesh.getSettingAsIndex("extruder_nr");
|
||||
if (layer.parts.size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
{
|
||||
|
||||
std::vector<bool> ret;
|
||||
ret.resize(meshgroup->getExtruderCount(), false);
|
||||
|
||||
ret[getSettingAsIndex("adhesion_extruder_nr")] = true;
|
||||
{ // process brim/skirt
|
||||
for (int extr_nr = 0; extr_nr < meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (skirt[extr_nr].size() > 0)
|
||||
{
|
||||
ret[extr_nr] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ooze shield, draft shield ..?
|
||||
|
||||
// support
|
||||
// support is presupposed to be present...
|
||||
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
|
||||
ret[getSettingAsIndex("support_extruder_nr")] = true;
|
||||
ret[getSettingAsIndex("support_roof_extruder_nr")] = true;
|
||||
|
||||
// all meshes are presupposed to actually have content
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
ret[mesh.getSettingAsIndex("extruder_nr")] = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -24,19 +24,6 @@ public:
|
||||
std::vector<Polygons> insets; //!< The skin can have perimeters so that the skin lines always start at a perimeter instead of in the middle of an infill cell.
|
||||
Polygons perimeterGaps; //!< The gaps introduced by avoidOverlappingPerimeters which would otherwise be overlapping perimeters.
|
||||
};
|
||||
|
||||
/*!
|
||||
* A ReinforcementWall is like an insulated wall behind the outer walls.
|
||||
* It consists of an area with (generally more dense) infill and perimeters on the inside.
|
||||
* On the outside it has the outer walls, or the inner walls of another ReinforcementWall.
|
||||
*/
|
||||
class ReinforcementWall
|
||||
{
|
||||
public:
|
||||
Polygons wall_reinforcement_area; //!< The infill of the reinforced wall
|
||||
std::vector<Polygons> wall_reinforcement_axtra_walls; //!< The extra walls on the inside of the reinforcement infill
|
||||
};
|
||||
|
||||
/*!
|
||||
The SliceLayerPart is a single enclosed printable area for a single layer. (Also known as islands)
|
||||
It's filled during the FffProcessor.processSliceData(.), where each step uses data from the previous steps.
|
||||
@@ -50,7 +37,6 @@ public:
|
||||
std::vector<Polygons> insets; //!< The insets are generated with: an offset of (index * line_width + line_width/2) compared to the outline. The insets are also known as perimeters, and printed inside out.
|
||||
std::vector<SkinPart> skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets.
|
||||
std::vector<Polygons> infill_area; //!< The infill_area are the areas which need to be filled with sparse (0-99%) infill. The infill_area is an array to support thicker layers of sparse infill. infill_area[n] is infill_area of (n+1) layers thick.
|
||||
std::vector<ReinforcementWall> reinforcement_walls; //!< The reinforcement walls for this part. Order: from outter to inner reinforcement wall.
|
||||
Polygons perimeterGaps; //!< The gaps introduced by avoidOverlappingPerimeters which would otherwise be overlapping perimeters.
|
||||
};
|
||||
|
||||
@@ -131,14 +117,13 @@ public:
|
||||
GCodePathConfig insetX_config;
|
||||
GCodePathConfig skin_config;
|
||||
std::vector<GCodePathConfig> infill_config;
|
||||
GCodePathConfig wall_reinforcement_config;
|
||||
|
||||
SliceMeshStorage(SettingsBaseVirtual* settings)
|
||||
: SettingsMessenger(settings), layer_nr_max_filled_layer(0), inset0_config(&retraction_config, "WALL-OUTER"), insetX_config(&retraction_config, "WALL-INNER"), skin_config(&retraction_config, "SKIN"), wall_reinforcement_config(&retraction_config, "SUPPORT")
|
||||
: SettingsMessenger(settings), layer_nr_max_filled_layer(0), inset0_config(&retraction_config, PrintFeatureType::OuterWall), insetX_config(&retraction_config, PrintFeatureType::InnerWall), skin_config(&retraction_config, PrintFeatureType::Skin)
|
||||
{
|
||||
infill_config.reserve(MAX_INFILL_COMBINE);
|
||||
for(int n=0; n<MAX_INFILL_COMBINE; n++)
|
||||
infill_config.emplace_back(&retraction_config, "FILL");
|
||||
infill_config.emplace_back(&retraction_config, PrintFeatureType::Infill);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -188,7 +173,7 @@ public:
|
||||
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
RetractionConfig* extruder_retraction_config = &retraction_config_per_extruder[extruder];
|
||||
skirt_config.emplace_back(extruder_retraction_config, "SKIRT");
|
||||
skirt_config.emplace_back(extruder_retraction_config, PrintFeatureType::Skirt);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -227,21 +212,6 @@ public:
|
||||
* \param include_helper_parts whether to include support and prime tower
|
||||
*/
|
||||
Polygons getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts);
|
||||
|
||||
/*!
|
||||
* Get the extruder numbers of all extruders used in a given layer.
|
||||
*
|
||||
* \param layer_nr the index of the layer for which to get the extruders used (negative layer numbers indicate the raft)
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed(int layer_nr);
|
||||
|
||||
/*!
|
||||
* Get the extruders used.
|
||||
*
|
||||
* \return a vector of bools indicating whether the extruder with corresponding index is used in this layer.
|
||||
*/
|
||||
std::vector<bool> getExtrudersUsed();
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+7
-7
@@ -35,7 +35,7 @@ Polygons AreaSupport::join(Polygons& supportLayer_up, Polygons& supportLayer_thi
|
||||
return joined;
|
||||
}
|
||||
|
||||
void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count, CommandSocket* commandSocket)
|
||||
void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count)
|
||||
{
|
||||
// initialization of supportAreasPerLayer
|
||||
for (unsigned int layer_idx = 0; layer_idx < layer_count ; layer_idx++)
|
||||
@@ -46,11 +46,11 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int l
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
std::vector<Polygons> supportAreas;
|
||||
supportAreas.resize(layer_count, Polygons());
|
||||
generateSupportAreas(storage, mesh_idx, layer_count, supportAreas, commandSocket);
|
||||
generateSupportAreas(storage, mesh_idx, layer_count, supportAreas);
|
||||
|
||||
if (mesh.getSettingBoolean("support_roof_enable"))
|
||||
{
|
||||
generateSupportRoofs(storage, supportAreas, layer_count, mesh.getSettingInMicrons("layer_height"), mesh.getSettingInMicrons("support_roof_height"), commandSocket);
|
||||
generateSupportRoofs(storage, supportAreas, layer_count, mesh.getSettingInMicrons("layer_height"), mesh.getSettingInMicrons("support_roof_height"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -80,7 +80,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int l
|
||||
*
|
||||
* for support buildplate only: purge all support not connected to buildplate
|
||||
*/
|
||||
void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int mesh_idx, unsigned int layer_count, std::vector<Polygons>& supportAreas, CommandSocket* commandSocket)
|
||||
void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int mesh_idx, unsigned int layer_count, std::vector<Polygons>& supportAreas)
|
||||
{
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
|
||||
@@ -114,7 +114,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
|
||||
int layerThickness = mesh.getSettingInMicrons("layer_height");
|
||||
int extrusionWidth = mesh.getSettingInMicrons("support_line_width");
|
||||
int supportXYDistance = mesh.getSettingInMicrons("support_xy_distance") + extrusionWidth / 2;
|
||||
int supportXYDistance = mesh.getSettingInMicrons("support_xy_distance");
|
||||
|
||||
bool conical_support = mesh.getSettingBoolean("support_conical_enabled");
|
||||
double conical_support_angle = mesh.getSettingInAngleRadians("support_conical_angle");
|
||||
@@ -252,7 +252,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
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(), commandSocket);
|
||||
Progress::messageProgress(Progress::Stage::SUPPORT, storage.meshes.size() * mesh_idx + support_layer_count - layer_idx, support_layer_count * storage.meshes.size());
|
||||
}
|
||||
|
||||
// do stuff for when support on buildplate only
|
||||
@@ -409,7 +409,7 @@ void AreaSupport::handleWallStruts(
|
||||
}
|
||||
|
||||
|
||||
void AreaSupport::generateSupportRoofs(SliceDataStorage& storage, std::vector<Polygons>& supportAreas, unsigned int layer_count, int layerThickness, int support_roof_height, CommandSocket* commandSocket)
|
||||
void AreaSupport::generateSupportRoofs(SliceDataStorage& storage, std::vector<Polygons>& supportAreas, unsigned int layer_count, int layerThickness, int support_roof_height)
|
||||
{
|
||||
int roof_layer_count = support_roof_height / layerThickness;
|
||||
|
||||
|
||||
+3
-6
@@ -15,9 +15,8 @@ public:
|
||||
* Generate the support areas and support roof areas for all models.
|
||||
* \param storage data storage containing the input layer outline data and containing the output support storage per layer
|
||||
* \param layer_count total number of layers
|
||||
* \param commandSocket Socket over which to report the progress
|
||||
*/
|
||||
static void generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count, CommandSocket* commandSocket);
|
||||
static void generateSupportAreas(SliceDataStorage& storage, unsigned int layer_count);
|
||||
|
||||
private:
|
||||
/*!
|
||||
@@ -28,9 +27,8 @@ private:
|
||||
* \param storage data storage containing the input layer outline data
|
||||
* \param mesh_idx The index of the object for which to generate support areas
|
||||
* \param layer_count total number of layers
|
||||
* \param commandSocket Socket over which to report the progress
|
||||
*/
|
||||
static void generateSupportAreas(SliceDataStorage& storage, unsigned int mesh_idx, unsigned int layer_count, std::vector<Polygons>& supportAreas, CommandSocket* commandSocket);
|
||||
static void generateSupportAreas(SliceDataStorage& storage, unsigned int mesh_idx, unsigned int layer_count, std::vector<Polygons>& supportAreas);
|
||||
|
||||
|
||||
|
||||
@@ -39,11 +37,10 @@ private:
|
||||
*
|
||||
* \param storage Output storage: support area + support roof area output
|
||||
* \param supportAreas The basic support areas for the current mesh
|
||||
* \param commandSocket Socket over which to report the progress
|
||||
* \param layerThickness The layer height
|
||||
* \param support_roof_height The thickness of the hammock in z directiontt
|
||||
*/
|
||||
static void generateSupportRoofs(SliceDataStorage& storage, std::vector<Polygons>& supportAreas, unsigned int layer_count, int layerThickness, int support_roof_height, CommandSocket* commandSocket);
|
||||
static void generateSupportRoofs(SliceDataStorage& storage, std::vector<Polygons>& supportAreas, unsigned int layer_count, int layerThickness, int support_roof_height);
|
||||
|
||||
/*!
|
||||
* Join current support layer with the support of the layer above, (make support conical) and perform smoothing etc operations.
|
||||
|
||||
@@ -30,6 +30,43 @@ public:
|
||||
{
|
||||
calculate(polys);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Creates an axis aligned bounding box around the specified polygon.
|
||||
*
|
||||
* The bounding box will fit snugly around the polygon.
|
||||
*
|
||||
* \param polygon The polygon to create a bounding box for.
|
||||
*/
|
||||
AABB(PolygonRef& polygon)
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
for(Point point : polygon)
|
||||
{
|
||||
include(point);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Creates an axis aligned bounding box around the specified set of
|
||||
* polygons.
|
||||
*
|
||||
* The bounding box will fit around all polygons.
|
||||
*
|
||||
* \param polygons A vector of polygons that should be included in the
|
||||
* bounding box.
|
||||
*/
|
||||
AABB(std::vector<PolygonRef>& polygons)
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
for(PolygonRef polygon : polygons)
|
||||
{
|
||||
for(Point point : polygon)
|
||||
{
|
||||
include(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void calculate(Polygons& polys)
|
||||
{
|
||||
|
||||
@@ -205,11 +205,6 @@ public:
|
||||
// if (! emplaced.second)
|
||||
// logError("Error! BucketGrid2D couldn't insert object!");
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+39
-1
@@ -140,6 +140,44 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Draws the specified list of polygons on the canvas.
|
||||
*
|
||||
* Each polygon is drawn in sequence.
|
||||
*
|
||||
* \param polygons A vector of polygons to draw.
|
||||
* \param colour The colour of the fill of the polygons.
|
||||
* \param outline_colour The colour of the outline of the polygons.
|
||||
*/
|
||||
void writeAreas(const std::vector<PolygonRef>& polygons, Color colour = Color::GRAY, Color outline_colour = Color::BLACK)
|
||||
{
|
||||
for(PolygonRef polygon : polygons)
|
||||
{
|
||||
writeAreas(polygon,colour,outline_colour);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Draws the specified polygon on the canvas.
|
||||
*
|
||||
* The polygon is always closed and has an outline and fill colour that you
|
||||
* can specify.
|
||||
*
|
||||
* \param polygon The polygon to draw on the canvas.
|
||||
* \param colour The colour of the polygon.
|
||||
* \param outline_colour The colour of the outline of the polygon.
|
||||
*/
|
||||
void writeAreas(PolygonRef polygon, Color colour = Color::GRAY, Color outline_colour = Color::BLACK)
|
||||
{
|
||||
fprintf(out, "<polygon points=\"");
|
||||
for(Point point : polygon)
|
||||
{
|
||||
Point transformed = transform(point);
|
||||
fprintf(out, "%lli,%lli ", transformed.X, transformed.Y);
|
||||
}
|
||||
fprintf(out, "\" fill=\"%s\" stroke=\"%s\" stroke-width=\"4\" />\n", toString(colour).c_str(),toString(outline_colour).c_str());
|
||||
}
|
||||
|
||||
void writeAreas(std::vector<Point> polygon,Color color = Color::GRAY,Color outline_color = Color::BLACK)
|
||||
{
|
||||
fprintf(out,"<polygon fill=\"%s\" stroke=\"%s\" stroke-width=\"1\" points=\"",toString(color).c_str(),toString(outline_color).c_str()); //The beginning of the polygon tag.
|
||||
@@ -209,7 +247,7 @@ public:
|
||||
{
|
||||
Point fa = transform(a);
|
||||
Point fb = transform(b);
|
||||
fprintf(out, "<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" style=\"stroke:%s;stroke-width:1\" />\n", fa.X, fa.Y, fb.X, fb.Y, toString(color).c_str());
|
||||
fprintf(out, "<line x1=\"%lli\" y1=\"%lli\" x2=\"%lli\" y2=\"%lli\" stroke=\"%s\" />\n", fa.X, fa.Y, fb.X, fb.Y, toString(color).c_str());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
//Copyright (c) 2015 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef TRAVELLINGSALESMAN_H
|
||||
#define TRAVELLINGSALESMAN_H
|
||||
|
||||
#include "intpoint.h" //For the Point type.
|
||||
|
||||
#include <algorithm> //For std::shuffle to randomly shuffle the array.
|
||||
#include <list> //For std::list, which is used to insert stuff in the result in constant time.
|
||||
#include <random> //For the RNG. Note: CuraEngine should be deterministic! Always use a FIXED SEED!
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* \brief Struct that holds all information of one element of the path.
|
||||
*
|
||||
* It needs to know the actual element in the path, but also where the element's
|
||||
* own path starts and ends.
|
||||
*
|
||||
* \tparam E The type of element data stored in this waypoint. Note that these
|
||||
* are copied into the waypoint on construction and out of the waypoint right
|
||||
* before deletion.
|
||||
*/
|
||||
template<class E> struct Waypoint
|
||||
{
|
||||
/*!
|
||||
* \brief Constructs a new waypoint with the specified possible start and
|
||||
* end points and the specified element.
|
||||
*
|
||||
* \param orientations The possible start and end points of the waypoints
|
||||
* for each orientation the element could be placed in.
|
||||
* \param element The element that's to be bound to this waypoint.
|
||||
*/
|
||||
Waypoint(std::vector<std::pair<Point, Point>> orientations, E element) : orientation_indices(orientations), element(element)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief The possible orientations in which the waypoint could be placed in
|
||||
* the path.
|
||||
*
|
||||
* This defines in what direction or way the element in this waypoint should
|
||||
* be traversed in the final path. The Travelling Salesman solution only
|
||||
* requires the start and end point of this traversal in order to piece the
|
||||
* waypoint into the path.
|
||||
*/
|
||||
std::vector<std::pair<Point, Point>> orientation_indices;
|
||||
|
||||
/*!
|
||||
* \brief The actual element this waypoint holds.
|
||||
*/
|
||||
E element;
|
||||
|
||||
/*!
|
||||
* \brief The optimal orientation of this waypoint in the final path.
|
||||
*
|
||||
* This is computed during the <em>TravellingSalesman::findPath</em>
|
||||
* function. It indicates an index in \link orientations that provides the
|
||||
* shortest path.
|
||||
*/
|
||||
size_t best_orientation_index;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief A class of functions implementing solutions of Travelling Salesman.
|
||||
*
|
||||
* Various variants can be implemented here, such as the shortest path past a
|
||||
* set of points or of lines.
|
||||
*
|
||||
* \tparam E The type of elements that must be ordered by this instance of
|
||||
* <em>TravellingSalesman</em>. Note that each element is copied twice in a run
|
||||
* of \link findPath, so if this type is difficult to copy, provide pointers to
|
||||
* the elements instead.
|
||||
*/
|
||||
template<class E> class TravellingSalesman
|
||||
{
|
||||
typedef typename std::list<Waypoint<E>*>::iterator WaypointListIterator; //To help the compiler with templates in templates.
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief Constructs an instance of Travelling Salesman.
|
||||
*
|
||||
* \param get_orientations A function to get possible orientations for
|
||||
* elements in the path. Each orientation defines a possible way that the
|
||||
* element could be inserted in the path. To do that it must provide a
|
||||
* start point and an end point for each orientation.
|
||||
*/
|
||||
TravellingSalesman(std::function<std::vector<std::pair<Point, Point>>(E)> get_orientations) : get_orientations(get_orientations)
|
||||
{
|
||||
//Do nothing. All parameters are already copied to fields.
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Destroys the instance, releasing all memory used by it.
|
||||
*/
|
||||
virtual ~TravellingSalesman();
|
||||
|
||||
/*!
|
||||
* \brief Computes a short path along all specified elements.
|
||||
*
|
||||
* A short path is computed that includes all specified elements, but not
|
||||
* always the shortest path. Finding the shortest path is known as the
|
||||
* Travelling Salesman Problem, and this is an NP-complete problem. The
|
||||
* solution returned by this function is just a heuristic approximation.
|
||||
*
|
||||
* The approximation will try to insert random elements at the best location
|
||||
* in the current path, thereby incrementally constructing a good path. Each
|
||||
* element can be inserted in multiple possible orientations, defined by the
|
||||
* <em>get_orientations</em> function.
|
||||
*
|
||||
* \param elements The elements past which the path must run.
|
||||
* \param element_orientations Output parameter to indicate for each element
|
||||
* in which orientation it must be placed to minimise the travel time. The
|
||||
* resulting integers correspond to the index of the options given by the
|
||||
* <em>get_orientations</em> constructor parameter.
|
||||
* \param starting_point A fixed starting point of the path, if any. If this
|
||||
* is <em>nullptr</em>, the path may start at the start or end point of any
|
||||
* element, depending on which the heuristic deems shortest.
|
||||
* \return A vector of elements, in an order that would make a short path.
|
||||
*/
|
||||
std::vector<E> findPath(std::vector<E> elements, std::vector<size_t>& element_orientations, Point* starting_point = nullptr);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* \brief Function to use to get the possible orientations of an element.
|
||||
*
|
||||
* Each orientation has a start point and an end point, in that order.
|
||||
*/
|
||||
std::function<std::vector<std::pair<Point, Point>>(E)> get_orientations;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Puts all elements in waypoints, caching their endpoints.
|
||||
*
|
||||
* The <em>get_start</em> and <em>get_end</em> functions are called on each
|
||||
* element. The results are stored along with the element in a waypoint and
|
||||
* a pointer to the waypoint is added to the resulting vector.
|
||||
*
|
||||
* Note that this creates a waypoint for each element on the heap. These
|
||||
* waypoints need to be deleted when the algorithm is done. This is why this
|
||||
* function must always stay private.
|
||||
*
|
||||
* \param elements The elements to put into waypoints.
|
||||
* \return A vector of waypoints with the specified elements in them.
|
||||
*/
|
||||
std::vector<Waypoint<E>*> fillWaypoints(std::vector<E> elements);
|
||||
|
||||
/*!
|
||||
* \brief Tries to insert a waypoint at the first position in the list.
|
||||
*
|
||||
* It will try to insert the waypoint at all possible orientations. If it
|
||||
* finds an orientation with an insertion distance that is less than the
|
||||
* best distance, it will update the \p best_distance, \p best_orientation
|
||||
* and \p best_insert variables with the parameters of this insertion.
|
||||
*
|
||||
* \param waypoint The waypoint to insert in the list.
|
||||
* \param starting_point The starting point to insert the waypoint after, if
|
||||
* any.
|
||||
* \param first_element The first element of the list to insert the waypoint
|
||||
* before.
|
||||
* \param best_distance The current best distance of inserting the waypoint.
|
||||
* This parameter is changed if a better insertion is found.
|
||||
* \param best_orientation The current best orientation of inserting the
|
||||
* waypoint. This parameter is changed if a better insertion is found.
|
||||
* \param best_insert The current best place to insert the waypoint. This
|
||||
* parameter is changed if a better insertion is found.
|
||||
*/
|
||||
inline void tryInsertFirst(Waypoint<E>* waypoint, Point* starting_point, WaypointListIterator first_element, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert);
|
||||
|
||||
/*!
|
||||
* \brief Tries to insert a waypoint at the last position in the list.
|
||||
*
|
||||
* It will try to insert the waypoint at all possible orientations. If it
|
||||
* finds an orientation with an insertion distance that is less than the
|
||||
* best distance, it will update the \p best_distance, \p best_orientation
|
||||
* and \p best_insert variables with the parameters of this insertion.
|
||||
*
|
||||
* \param waypoint The waypoint to insert in the list.
|
||||
* \param last_element The last element of the list to insert the waypoint
|
||||
* after.
|
||||
* \param The WaypointListIterator to use to indicate that it should insert
|
||||
* the new waypoint after the last element of the list, if it should find
|
||||
* that as a new best position.
|
||||
* \param best_distance The current best distance of inserting the waypoint.
|
||||
* This parameter is changed if a better insertion is found.
|
||||
* \param best_orientation The current best orientation of inserting the
|
||||
* waypoint. This parameter is changed if a better insertion is found.
|
||||
* \param best_insert The current best place to insert the waypoint. This
|
||||
* parameter is changed if a better insertion is found.
|
||||
*/
|
||||
inline void tryInsertLast(Waypoint<E>* waypoint, WaypointListIterator last_element, WaypointListIterator after_insert, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert);
|
||||
|
||||
/*!
|
||||
* \brief Tries to insert a waypoint at a position in the centre of the
|
||||
* list.
|
||||
*
|
||||
* It will try to insert the waypoint at all possible orientations. If it
|
||||
* finds an orientation with an insertion distance that is less than the
|
||||
* best distance, it will update the \p best_distance, \p best_orientation
|
||||
* and \p best_insert variables with the parameters of this insertion.
|
||||
*
|
||||
* \param waypoint The waypoint to insert in the list.
|
||||
* \param before_insert The element after which to insert the new waypoint.
|
||||
* \param after_insert The element before which to insert the new waypoint.
|
||||
* \param best_distance The current best distance of inserting the waypoint.
|
||||
* This parameter is changed if a better insertion is found.
|
||||
* \param best_orientation The current best orientation of inserting the
|
||||
* waypoint. This parameter is changed if a better insertion is found.
|
||||
* \param best_insert The current best place to insert the waypoint. This
|
||||
* parameter is changed if a better insertion is found.
|
||||
*/
|
||||
inline void tryInsertMiddle(Waypoint<E>* waypoint, WaypointListIterator before_insert, WaypointListIterator after_insert, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert);
|
||||
};
|
||||
|
||||
////BELOW FOLLOWS THE IMPLEMENTATION.////
|
||||
|
||||
template<class E> TravellingSalesman<E>::~TravellingSalesman()
|
||||
{
|
||||
//Do nothing.
|
||||
}
|
||||
|
||||
template<class E> std::vector<E> TravellingSalesman<E>::findPath(std::vector<E> elements, std::vector<size_t>& element_orientations, Point* starting_point)
|
||||
{
|
||||
/* This approximation algorithm of TSP implements the random insertion
|
||||
* heuristic. Random insertion has in tests proven to be almost as good as
|
||||
* Christofides (111% of the optimal path length rather than 110% on random
|
||||
* graphs) but is much faster to compute. */
|
||||
if (elements.empty())
|
||||
{
|
||||
return std::vector<E>();
|
||||
}
|
||||
|
||||
auto rng = std::default_random_engine(0xDECAFF); //Always use a fixed seed! Wouldn't want it to be nondeterministic.
|
||||
std::vector<Waypoint<E>*> shuffled = fillWaypoints(elements);
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), rng); //"Randomly" shuffles the waypoints.
|
||||
|
||||
std::list<Waypoint<E>*> result;
|
||||
|
||||
if (!starting_point) //If there is no starting point, just insert the initial element.
|
||||
{
|
||||
shuffled[0]->best_orientation_index = 0; //Choose an arbitrary orientation for the first element.
|
||||
result.push_back(shuffled[0]); //Due to the check at the start, we know that shuffled always contains at least 1 element.
|
||||
}
|
||||
else //If there is a starting point, insert the initial element after it.
|
||||
{
|
||||
int64_t best_distance = std::numeric_limits<int64_t>::max(); //Change in travel distance to insert the waypoint. Minimise this distance by varying the orientation.
|
||||
size_t best_orientation_index; //In what orientation to insert the element.
|
||||
for (size_t orientation_index = 0; orientation_index < shuffled[0]->orientation_indices.size(); orientation_index++)
|
||||
{
|
||||
int64_t distance = vSize(*starting_point - shuffled[0]->orientation_indices[orientation_index].first); //Distance from the starting point to the start point of this element.
|
||||
if (distance < best_distance)
|
||||
{
|
||||
best_distance = distance;
|
||||
best_orientation_index = orientation_index;
|
||||
}
|
||||
}
|
||||
shuffled[0]->best_orientation_index = best_orientation_index;
|
||||
result.push_back(shuffled[0]);
|
||||
}
|
||||
|
||||
//Now randomly insert the rest of the points.
|
||||
for (size_t next_to_insert = 1; next_to_insert < shuffled.size(); next_to_insert++)
|
||||
{
|
||||
Waypoint<E>* waypoint = shuffled[next_to_insert];
|
||||
int64_t best_distance = std::numeric_limits<int64_t>::max(); //Change in travel distance to insert the waypoint. Minimise this distance by varying the insertion point and orientation.
|
||||
WaypointListIterator best_insert; //Where to insert the element. It will be inserted before this element. If it's nullptr, insert at the very front.
|
||||
size_t best_orientation_index; //In what orientation to insert the element.
|
||||
|
||||
//First try inserting before the first element.
|
||||
tryInsertFirst(waypoint, starting_point, result.begin(), &best_distance, &best_orientation_index, &best_insert);
|
||||
|
||||
//Try inserting at the other positions.
|
||||
for (WaypointListIterator before_insert = result.begin(); before_insert != result.end(); before_insert++)
|
||||
{
|
||||
WaypointListIterator after_insert = before_insert;
|
||||
after_insert++; //Get the element after the current element.
|
||||
if (after_insert == result.end()) //There is no next element. We're inserting at the end of the path.
|
||||
{
|
||||
tryInsertLast(waypoint, before_insert, after_insert, &best_distance, &best_orientation_index, &best_insert);
|
||||
}
|
||||
else //There is a next element. We're inserting somewhere in the middle.
|
||||
{
|
||||
tryInsertMiddle(waypoint, before_insert, after_insert, &best_distance, &best_orientation_index, &best_insert);
|
||||
}
|
||||
}
|
||||
|
||||
//Actually insert the waypoint at the best position we found.
|
||||
waypoint->best_orientation_index = best_orientation_index;
|
||||
if (best_insert == result.end()) //We must insert at the very end.
|
||||
{
|
||||
result.push_back(waypoint);
|
||||
}
|
||||
else //We must insert before best_insert.
|
||||
{
|
||||
result.insert(best_insert, waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
//Now that we've inserted all points, linearise them into one vector.
|
||||
std::vector<E> result_vector;
|
||||
result_vector.reserve(elements.size());
|
||||
element_orientations.clear(); //Prepare the element_orientations vector for storing in which orientation each element should be placed.
|
||||
element_orientations.reserve(elements.size());
|
||||
for (Waypoint<E>* waypoint : result)
|
||||
{
|
||||
result_vector.push_back(waypoint->element);
|
||||
element_orientations.push_back(waypoint->best_orientation_index);
|
||||
delete waypoint; //Free the waypoint from memory. It is no longer needed from here on, since we copied the element in it to the output.
|
||||
}
|
||||
return result_vector;
|
||||
}
|
||||
|
||||
template<class E> std::vector<Waypoint<E>*> TravellingSalesman<E>::fillWaypoints(std::vector<E> elements)
|
||||
{
|
||||
std::vector<Waypoint<E>*> result;
|
||||
result.reserve(elements.size());
|
||||
|
||||
for (E element : elements) //Put every element in a waypoint.
|
||||
{
|
||||
Waypoint<E>* waypoint = new Waypoint<E>(get_orientations(element), element); //Yes, this must be deleted when the algorithm is done!
|
||||
result.push_back(waypoint);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class E> inline void TravellingSalesman<E>::tryInsertFirst(Waypoint<E>* waypoint, Point* starting_point, WaypointListIterator first_element, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert)
|
||||
{
|
||||
if (!waypoint or !best_distance or !best_orientation_index or !best_insert) //Input checking.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t orientation = 0; orientation < waypoint->orientation_indices.size(); orientation++)
|
||||
{
|
||||
int64_t before_distance = 0;
|
||||
if (starting_point) //If there is a starting point, we're inserting between the first point and the starting point.
|
||||
{
|
||||
before_distance = vSize(*starting_point - waypoint->orientation_indices[orientation].first);
|
||||
}
|
||||
const Point end_of_this = waypoint->orientation_indices[orientation].second;
|
||||
const Point start_of_first = (*first_element)->orientation_indices[(*first_element)->best_orientation_index].first;
|
||||
const int64_t after_distance = vSize(end_of_this - start_of_first); //From the end of this element to the start of the first element.
|
||||
const int64_t distance = before_distance + after_distance;
|
||||
if (distance < *best_distance)
|
||||
{
|
||||
*best_distance = distance;
|
||||
*best_insert = first_element;
|
||||
*best_orientation_index = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class E> inline void TravellingSalesman<E>::tryInsertLast(Waypoint<E>* waypoint, WaypointListIterator last_element, WaypointListIterator after_insert, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert)
|
||||
{
|
||||
if (!waypoint or !best_distance or !best_orientation_index or !best_insert) //Input checking.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t orientation = 0; orientation < waypoint->orientation_indices.size(); orientation++)
|
||||
{
|
||||
const Point end_of_last = (*last_element)->orientation_indices[(*last_element)->best_orientation_index].second;
|
||||
const Point start_of_this = waypoint->orientation_indices[orientation].first;
|
||||
const int64_t distance = vSize(end_of_last - start_of_this); //From the end of the last element to the start of this element.
|
||||
if (distance < *best_distance)
|
||||
{
|
||||
*best_distance = distance;
|
||||
*best_insert = after_insert;
|
||||
*best_orientation_index = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class E> inline void TravellingSalesman<E>::tryInsertMiddle(Waypoint<E>* waypoint, WaypointListIterator before_insert, WaypointListIterator after_insert, int64_t* best_distance, size_t* best_orientation_index, WaypointListIterator* best_insert)
|
||||
{
|
||||
if (!waypoint or !best_distance or !best_orientation_index or !best_insert) //Input checking.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t orientation = 0; orientation < waypoint->orientation_indices.size(); orientation++)
|
||||
{
|
||||
const Point end_of_before = (*before_insert)->orientation_indices[(*before_insert)->best_orientation_index].second;
|
||||
const Point start_of_after = (*after_insert)->orientation_indices[(*after_insert)->best_orientation_index].first;
|
||||
const Point start_of_this = waypoint->orientation_indices[orientation].first;
|
||||
const Point end_of_this = waypoint->orientation_indices[orientation].second;
|
||||
const int64_t removed_distance = vSize(end_of_before - start_of_after); //Distance of the original move that we'll remove.
|
||||
const int64_t before_distance = vSize(end_of_before - start_of_this); //From end of previous element to start of this element.
|
||||
const int64_t after_distance = vSize(end_of_this - start_of_after); //From end of this element to start of next element.
|
||||
const int64_t distance = before_distance + after_distance - removed_distance;
|
||||
if (distance < *best_distance)
|
||||
{
|
||||
*best_distance = distance;
|
||||
*best_insert = after_insert;
|
||||
*best_orientation_index = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* TRAVELLINGSALESMAN_H */
|
||||
|
||||
+21
-12
@@ -146,32 +146,41 @@ public:
|
||||
|
||||
if (ax_size < 0)
|
||||
{// b is 'before' segment ac
|
||||
if (b_is_beyond_ac)
|
||||
{
|
||||
*b_is_beyond_ac = -1;
|
||||
}
|
||||
if (b_is_beyond_ac) { *b_is_beyond_ac = -1; }
|
||||
return vSize2(ab);
|
||||
}
|
||||
if (ax_size > ac_size)
|
||||
{// b is 'after' segment ac
|
||||
if (b_is_beyond_ac)
|
||||
{
|
||||
*b_is_beyond_ac = 1;
|
||||
}
|
||||
if (b_is_beyond_ac) { *b_is_beyond_ac = 1; }
|
||||
return vSize2(b - c);
|
||||
}
|
||||
|
||||
if (b_is_beyond_ac)
|
||||
{
|
||||
*b_is_beyond_ac = 0;
|
||||
}
|
||||
if (b_is_beyond_ac) { *b_is_beyond_ac = 0; }
|
||||
Point ax = ac * ax_size / ac_size;
|
||||
Point bx = ab - ax;
|
||||
return vSize2(bx);
|
||||
// return vSize2(ab) - ax_size*ax_size; // less accurate
|
||||
}
|
||||
|
||||
/*!
|
||||
* Checks whether the minimal distance between two line segments is at most \p max_dist
|
||||
* The first line semgent is given by end points \p a and \p b, the second by \p c and \p d.
|
||||
*
|
||||
* \param a One end point of the first line segment
|
||||
* \param b Another end point of the first line segment
|
||||
* \param c One end point of the second line segment
|
||||
* \param d Another end point of the second line segment
|
||||
* \param max_dist The maximal distance between the two line segments for which this function will return true.
|
||||
*/
|
||||
static bool lineSegmentsAreCloserThan(const Point& a, const Point& b, const Point& c, const Point& d, int64_t max_dist)
|
||||
{
|
||||
int64_t max_dist2 = max_dist * max_dist;
|
||||
|
||||
return getDist2FromLineSegment(a, c, b) <= max_dist2
|
||||
|| getDist2FromLineSegment(a, d, b) <= max_dist2
|
||||
|| getDist2FromLineSegment(c, a, d) <= max_dist2
|
||||
|| getDist2FromLineSegment(c, b, d) <= max_dist2;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -21,20 +21,6 @@
|
||||
|
||||
namespace cura {
|
||||
|
||||
enum PolygonType
|
||||
{
|
||||
NoneType,
|
||||
Inset0Type,
|
||||
InsetXType,
|
||||
SkinType,
|
||||
SupportType,
|
||||
SkirtType,
|
||||
InfillType,
|
||||
SupportInfillType,
|
||||
MoveCombingType,
|
||||
MoveRetractionType
|
||||
};
|
||||
|
||||
|
||||
class PartsView;
|
||||
|
||||
|
||||
@@ -66,10 +66,10 @@ public:
|
||||
* \param polygons The polygons onto which to move the point
|
||||
* \param from The point to move.
|
||||
* \param distance The distance by which to move the point.
|
||||
* \param maxDist2 The squared maximal allowed distance from the point to the nearest polygon.
|
||||
* \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon.
|
||||
* \return The index to the polygon onto which we have moved the point.
|
||||
*/
|
||||
static unsigned int moveInside(Polygons& polygons, Point& from, int distance = 0, int64_t maxDist2 = std::numeric_limits<int64_t>::max());
|
||||
static unsigned int moveInside(Polygons& polygons, Point& from, int distance = 0, int64_t max_dist2 = std::numeric_limits<int64_t>::max());
|
||||
|
||||
/*!
|
||||
* Find the two points in two polygons with the smallest distance.
|
||||
|
||||
@@ -25,9 +25,9 @@ void GCodePlannerTest::setUp()
|
||||
fan_speed_layer_time_settings.cool_fan_speed_min = 0;
|
||||
fan_speed_layer_time_settings.cool_fan_speed_max = 1;
|
||||
fan_speed_layer_time_settings.cool_min_speed = 0.5;
|
||||
// Command Slice layer z layer last current fan speed and layer retraction comb travel travel avoid
|
||||
// socket storage nr height position extruder time settings combing offset avoid distance
|
||||
gCodePlanner = new GCodePlanner(nullptr, *storage, 0, 0, 0.1, Point(0,0), 0, fan_speed_layer_time_settings, false, 100, false, 50 );
|
||||
// Slice layer z layer last current fan speed and layer retraction comb travel travel avoid
|
||||
// storage nr height position extruder time settings combing offset avoid distance
|
||||
gCodePlanner = new GCodePlanner(*storage, 0, 0, 0.1, Point(0,0), 0, fan_speed_layer_time_settings, false, 100, false, 50 );
|
||||
}
|
||||
|
||||
void GCodePlannerTest::tearDown()
|
||||
@@ -46,7 +46,7 @@ void GCodePlannerTest::computeNaiveTimeEstimatesRetractionTest()
|
||||
|
||||
GCodeExport gcode;
|
||||
GCodePathConfig configuration = storage->travel_config;
|
||||
gCodePlanner->addExtrusionMove(Point(0,0),&configuration,1.0f); //Need to have at least one path to have a configuration.
|
||||
gCodePlanner->addExtrusionMove(Point(0, 0), &configuration, SpaceFillType::Lines, 1.0f); //Need to have at least one path to have a configuration.
|
||||
TimeMaterialEstimates before_retract = gCodePlanner->computeNaiveTimeEstimates();
|
||||
gCodePlanner->writeRetraction(gcode,(unsigned int)0,(unsigned int)0); //Make a retract.
|
||||
TimeMaterialEstimates after_retract = gCodePlanner->computeNaiveTimeEstimates();
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//Copyright (c) 2015 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "TravellingSalesmanTest.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TravellingSalesmanTest);
|
||||
|
||||
void TravellingSalesmanTest::setUp()
|
||||
{
|
||||
tsp_points = new TravellingSalesman<Point>([&](Point point) -> std::vector<std::pair<Point, Point>> { return { std::make_pair(point, point) }; }); //For points, just return the point itself as both start and end points.
|
||||
tsp_paths = new TravellingSalesman<std::vector<Point>>( //For paths, return the first and last element of the path.
|
||||
[&](std::vector<Point> path) -> std::vector<std::pair<Point, Point>>
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
return { std::make_pair(Point(0, 0), Point(0, 0)) };
|
||||
}
|
||||
std::vector<std::pair<Point, Point>> result;
|
||||
result.push_back(std::make_pair(path[0], path.back()));
|
||||
result.push_back(std::make_pair(path.back(), path[0]));
|
||||
return result;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void TravellingSalesmanTest::tearDown()
|
||||
{
|
||||
delete tsp_points;
|
||||
delete tsp_paths;
|
||||
}
|
||||
|
||||
void TravellingSalesmanTest::twoPointsTest()
|
||||
{
|
||||
std::vector<Point> points;
|
||||
points.push_back(Point(0,0));
|
||||
points.push_back(Point(100,0));
|
||||
std :: vector<size_t> orientations;
|
||||
std :: vector<Point> result = tsp_points -> findPath(points, orientations);
|
||||
|
||||
bijective(points,result); //Assert that all points in the two vectors are equal.
|
||||
}
|
||||
|
||||
void TravellingSalesmanTest::fivePointsTest()
|
||||
{
|
||||
std::vector<Point> points;
|
||||
points.push_back(Point(-100,0));
|
||||
points.push_back(Point(0,-100));
|
||||
points.push_back(Point(0,100));
|
||||
points.push_back(Point(100,0));
|
||||
points.push_back(Point(0,0));
|
||||
|
||||
std :: vector<size_t> orientations;
|
||||
std :: vector<Point> result = tsp_points -> findPath(points, orientations);
|
||||
|
||||
bijective(points,result); //Assert that all points in the two vectors are equal.
|
||||
}
|
||||
|
||||
void TravellingSalesmanTest::bijective(std::vector<Point> first,std::vector<Point> second)
|
||||
{
|
||||
CPPUNIT_ASSERT_MESSAGE("The resulting path does not have the same length as the provided point set.",first.size() == second.size());
|
||||
|
||||
std::vector<bool> matched; //Indicates which of the elements of the second vector are already matched.
|
||||
matched.reserve(second.size());
|
||||
for(size_t i = 0;i < matched.size();i++) //Initialise all matched to false.
|
||||
{
|
||||
matched.push_back(false);
|
||||
}
|
||||
for(Point first_point : first)
|
||||
{
|
||||
for(size_t second_point_index = 0;second_point_index < second.size();second_point_index++)
|
||||
{
|
||||
if(first_point == second[second_point_index])
|
||||
{
|
||||
matched[second_point_index] = true;
|
||||
goto CONTINUE_FIRST;
|
||||
}
|
||||
}
|
||||
CPPUNIT_FAIL("A point in the original path was not included in the result.");
|
||||
|
||||
CONTINUE_FIRST: ; //Continue with the outer loop.
|
||||
}
|
||||
for(bool match : matched) //Check if there are any unmatched points left.
|
||||
{
|
||||
CPPUNIT_ASSERT_MESSAGE("A point in the result did not come from the original path.",match);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//Copyright (c) 2015 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef TRAVELLINGSALESMANTEST_H
|
||||
#define TRAVELLINGSALESMANTEST_H
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <../src/utils/TravellingSalesman.h>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class TravellingSalesmanTest : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TravellingSalesmanTest);
|
||||
CPPUNIT_TEST(twoPointsTest);
|
||||
CPPUNIT_TEST(fivePointsTest);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief Sets up the test suite to prepare for testing.
|
||||
*
|
||||
* In this case, an instance of <em>TravellingSalesman</em> is produced with
|
||||
* pre-defined functions for getting the start and end points of a point and
|
||||
* another instance with paths.
|
||||
*/
|
||||
void setUp();
|
||||
|
||||
/*!
|
||||
* \brief Tears down the test suite when testing is done.
|
||||
*
|
||||
* The two instances of TravellingSalesman are deleted.
|
||||
*/
|
||||
void tearDown();
|
||||
|
||||
//The test cases.
|
||||
void twoPointsTest();
|
||||
void fivePointsTest();
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief A TSP solver that approximates the shortest path between a set of
|
||||
* points.
|
||||
*/
|
||||
TravellingSalesman<Point>* tsp_points;
|
||||
|
||||
/*!
|
||||
* \brief A TSP solver that approximates the shortest path that includes a
|
||||
* set of paths.
|
||||
*/
|
||||
TravellingSalesman<std::vector<Point>>* tsp_paths;
|
||||
|
||||
/*!
|
||||
* \brief Asserts that the two sets implied by the provided vectors are
|
||||
* bijective.
|
||||
*
|
||||
* They are bijective if and only if all points in the first vector are also
|
||||
* in the second vector and vice versa.
|
||||
*
|
||||
* \param first The first vector to check whether it is bijective with the
|
||||
* second vector.
|
||||
* \param second The second vector to check whether it is bijective with the
|
||||
* first vector.
|
||||
*/
|
||||
void bijective(std::vector<Point> first,std::vector<Point> second);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TRAVELLINGSALESMANTEST_H */
|
||||
|
||||
+21
-16
@@ -64,12 +64,6 @@ class TestResults():
|
||||
self._testsuites.append(suite)
|
||||
return suite
|
||||
|
||||
def getFailureCount(self):
|
||||
result = 0
|
||||
for testsuite in self._testsuites:
|
||||
result += testsuite.getFailureCount()
|
||||
return result
|
||||
|
||||
## Save the test results to the file given in the filename.
|
||||
def saveXML(self, filename):
|
||||
xml = ElementTree.Element("testsuites")
|
||||
@@ -254,9 +248,23 @@ class EngineTest():
|
||||
def getResults(self):
|
||||
return self._test_results
|
||||
|
||||
def main(engine, model_path):
|
||||
filenames = sorted(os.listdir(model_path), key=lambda filename: os.stat(os.path.join(model_path, filename)).st_size)
|
||||
filenames = list(filter(lambda filename: filename.lower().endswith(".stl"), filenames))
|
||||
for filename in filenames:
|
||||
print("Slicing: %s (%d/%d)" % (filename, filenames.index(filename), len(filenames)))
|
||||
t = time.time()
|
||||
p = subprocess.Popen([engine, "-vv", os.path.join(model_path, filename)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.wait() != 0:
|
||||
print ("Engine failed to report success on test object: %s" % (filename))
|
||||
print(stderr.decode("utf-8", "replace").split("\n")[-5:])
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Slicing took: %f" % (time.time() - t))
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="CuraEngine testing script")
|
||||
parser.add_argument("--simple", action="store_true", help="Only run the single test, exit")
|
||||
parser.add_argument("json", type=str, help="Machine JSON file to use")
|
||||
parser.add_argument("engine", type=str, help="Engine executable")
|
||||
parser.add_argument("models", type=str, nargs="+", help="List of models to use for testing")
|
||||
@@ -264,13 +272,10 @@ if __name__ == "__main__":
|
||||
|
||||
et = EngineTest(args.json, args.engine, args.models)
|
||||
if et.testDefaults() == 0:
|
||||
if not args.simple:
|
||||
et.testSingleChanges()
|
||||
if et.testSingleRandom() == 0:
|
||||
et.testDualRandom()
|
||||
if et.testAllRandom(10) == 0:
|
||||
et.testAllRandom(100)
|
||||
et.testSingleChanges()
|
||||
if et.testSingleRandom() == 0:
|
||||
et.testDualRandom()
|
||||
if et.testAllRandom(10) == 0:
|
||||
et.testAllRandom(100)
|
||||
et.getResults().saveXML("output.xml")
|
||||
if args.simple:
|
||||
if et.getResults().getFailureCount() > 0:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário