Comparar commits

..

76 Commits

Autor SHA1 Mensagem Data
Tim Kuipers 93485cd0df Merge branch '2.1' of https://github.com/Ultimaker/CuraEngine into 2.1 2016-04-06 17:09:16 +02:00
Tim Kuipers 2d3382874a fix: polygon smoothing removed vertices which were connected to a small as well as a large line segment (CURA-1361) 2016-04-06 17:09:05 +02:00
Tim Kuipers 1e78397e18 fix: support max layer was set after each object (CURA-1360) 2016-04-06 17:05:33 +02:00
Ghostkeeper 6bcdd94f7e Copy protocol file from front-end
This synchronises the whitespace and comments from the front-end. The typeids are not necessary any more.

Contributes to issue CURA-1210.
2016-04-05 17:14:09 +02:00
Ghostkeeper accd28db64 Update documentation of sendLayerData
Contributes to issue CURA-1210.
2016-04-05 17:13:57 +02:00
Ghostkeeper 1c0f4c42d9 Remove superfluous set to nullptr
Shared pointers are already set to nullptr by default.

Contributes to issue CURA-1210.
2016-04-05 17:13:47 +02:00
Ghostkeeper 5da1632d9f Remove sliced object lists from protocol
Each layer is now sent individually, instead of grouped by object and grouping those objects in a sliced object list. The list was sometimes too large to send in one message. The objects weren't used by the front-end anyway.

Contributes to issue CURA-1210.
2016-04-05 17:13:37 +02:00
Ghostkeeper cbf1152f56 Remove object ID from layer
Turns out that it is not needed in the front-end either any more. If we need it in the future, we'll add it again.

Contributes to issue CURA-1210.
2016-04-05 17:13:22 +02:00
Ghostkeeper 2273c5aefe Alter protocol to no longer group sliced objects
They will be sent with one message per layer from now on.

Contributes to issue CURA-1210.
2016-04-05 17:13:09 +02:00
Tim Kuipers 46c793e73d fix: lil string vs c_str bug (CURA-1231) 2016-04-04 14:15:14 +02:00
Tim Kuipers fd4969887b refactor: replaced all gcode output \n by new_line (CURA-1231) 2016-04-04 14:13:32 +02:00
Tim Kuipers 6620a050a5 feat: new_line string used for BFB machines (CURA-1231) 2016-04-04 14:13:10 +02:00
Tim Kuipers 6325197fce feat/refactor: moved file header generation to gcodeExport and introduced the NOZZLE_SIZE header comment (CURA-1231) 2016-04-04 14:03:38 +02:00
Tim Kuipers f828d44365 Merge branch 'feature_better_time_estimates' into 2.1 2016-04-04 12:14:19 +02:00
Tim Kuipers 29564a23e0 refactor: introduced mm, mm3, E value conversion functions instead of inline is_volumetric checks (CURA-1293) 2016-03-30 12:35:11 +02:00
Tim Kuipers 1fdda3319f fix: volumetric time estimation was bugged (CURA-1293) 2016-03-30 12:24:27 +02:00
sean041 be113eceb4 Fix typo. downSkinCount -> upSkinCount (CURA-1299)
This typo causes flaky crash when downSkinCount < upSkinCount and ignore small z gaps is disabled.
2016-03-30 10:24:58 +02:00
Tim Kuipers 0c42ff9bfa fix: inner (2nd) wall was refered to even if it wasn't generated (CURA-1294) 2016-03-29 15:18:47 +02:00
Tim Kuipers 168e041c42 Merge branch '2.1' of https://github.com/Ultimaker/CuraEngine into 2.1 2016-03-24 15:58:16 +01:00
Ghostkeeper 670ae6dd8c Spaces around minus operator
Conforming to code style.

Contributes to issue CURA-863.
2016-03-24 15:58:11 +01:00
Tim Kuipers 20adfa751f doc+refactor: fan speed calc more clear (CURA-863) 2016-03-24 15:57:54 +01:00
Tim Kuipers bf8776b112 optimization: removed superluous recalculation of line direction in LineOrderOptimizer (CURA-1170) 2016-03-23 16:25:10 +01:00
Tim Kuipers 1d0f3f519a fix syntax mistake (CURA-1170) 2016-03-23 16:25:01 +01:00
Tim Kuipers 07fef8668c calculate incoming_perpundicular_normal in end stage of line order optimizer (CURA-1170) 2016-03-23 16:24:51 +01:00
Tim Kuipers 1c06fc49fc refactor: factor out getAngleScore from line order optimizer (CURA-1170) 2016-03-23 16:24:39 +01:00
Tim Kuipers beb9422d9b refactor: clear up pathOrderOptimizer for lines (CURA-1170) 2016-03-23 16:24:27 +01:00
Tim Kuipers a8359b9a68 refactor: small optimization of line order optimizer dot score (CURA-1170) 2016-03-23 16:24:17 +01:00
Tim Kuipers 5ccfe2d1aa refactor: clear up pathOrderOptimizer for lines (CURA-1170) 2016-03-23 16:24:07 +01:00
Tim Kuipers 74577759b4 refactor: expand complicated code in pathOrderOptimizer (CURA-1170) 2016-03-23 16:23:54 +01:00
Tim Kuipers 45eb026777 refactor: rewrite line order optimizer dot score stuff (CURA-1170) 2016-03-23 16:23:42 +01:00
Tim Kuipers aabb07fd81 refactor: simple renaming of incoming_perpendicular_normal (CURA-1170)
Conflicts:
	src/pathOrderOptimizer.cpp
2016-03-23 16:23:21 +01:00
Ghostkeeper 6377ec63e1 Add edge-case tests for getAngleLeft
These test what happens when two or more points are equal. There is nothing about this in the function specification, so it allows any output, but at least it shouldn't give like a divide by zero error.

Contributes to issue CURA-1170.
2016-03-23 16:14:11 +01:00
Tim Kuipers eab2d8e667 fix: improved dot-score for preferring the z-seam on inside corners (CURA-1170) 2016-03-23 16:13:58 +01:00
Tim Kuipers 8d41003c67 feat: linearAlg2D::getAngleLeft CMAKE (CURA-1170) 2016-03-23 16:13:48 +01:00
Tim Kuipers ecfae4d75c feat: linearAlg2D::getAngleLeft (CURA-1170) 2016-03-23 16:13:37 +01:00
Tim Kuipers a2208f6b69 fix: pathOrderOptimizer was bugged (CURA-1170)
polyStart indices used wrongly
2016-03-23 16:13:26 +01:00
Tim Kuipers bacacb01dc refactor: more cleanup of pathOrderOptimizer (CURA-1170) 2016-03-23 16:13:15 +01:00
Tim Kuipers 94c9399f2c refactor: intpoint::crossZ ==> turn90CCW (CURA-1170) 2016-03-23 16:13:03 +01:00
Tim Kuipers 168dc3c12b refactor: more cleanup of pathOrderOptimizer (CURA-1170) 2016-03-23 16:12:53 +01:00
Tim Kuipers 235af65b00 refactor: code cleanup helper functions of PathOrderOptimizer (CURA-1170) 2016-03-23 16:12:43 +01:00
Tim Kuipers f3f3be74cc refactor: code cleanup of PathOrderOptimizer - lines (CURA-1170) 2016-03-23 16:12:32 +01:00
Tim Kuipers dca0bc80b5 fix: PathOrderOptimizar::polyStart had wrong indexing (CURA-1170) 2016-03-23 16:12:18 +01:00
Tim Kuipers c0e57622d0 cleanup: more clarification of pathOrderOptimizer for polygons code (CURA-1170) 2016-03-23 16:12:04 +01:00
Tim Kuipers b7a8fbe798 cleanup: pathOrderOptimizer got cleaned up (CURA-1170) 2016-03-23 16:11:49 +01:00
Tim Kuipers 4353980e78 refactor: made code more explicit: after smoothing speed (towards bottom layer speed) the speeds are set to their iconic speeds once and for all (CURA-1248) 2016-03-22 17:20:38 +01:00
Tim Kuipers 18ae9cf41d bugfix: alternate extra wall had skin overlapping with inner wall (CURA-1233)
To see if there was infill above, we looked at the innermost wall instead of the wall with index [wall line count]
2016-03-22 13:42:32 +01:00
Tim Kuipers 04edf35331 bugfix: bottom layer speed influenced all layers (CURA-1248)
smoothSpeed never got called for the final speed
2016-03-22 13:21:03 +01:00
Tim Kuipers 6718a8b2f3 fix: made engine not depend on support_z_distance, which is a parent setting value (CURA-1171) 2016-03-16 09:36:05 +01:00
Ghostkeeper 08a5ec7dee Codestyle: Whitespace around binary operators
Contributes to issue CURA-1097.
2016-03-14 16:45:56 +01:00
Ghostkeeper 3de01763c1 Codestyle: Whitespace around binary operators.
Contributes to issue CURA-1097.
2016-03-14 16:41:34 +01:00
Tim Kuipers bc7ee74d19 made combing depend on inner wall line width instead of nozzle_size (CURA-1097) 2016-03-14 11:02:32 +01:00
Tim Kuipers fc20a6661b made rafts' combing not depend on nozzle_size (CURA-1097) 2016-03-14 10:37:46 +01:00
Tim Kuipers 81d521a58b quickfix: remove outer wall offset based on nozzle size (CURA-1097) 2016-03-10 09:56:18 +01:00
Tim Kuipers c4eb1d9f27 bugfix: uninitialised infill (CURA-1084)
not always did the basic infill_area get initialised, notably when there was no infill, which should be an empty polygon instead of no polygon
2016-03-08 17:52:12 +01:00
Tim Kuipers 32d1bb6d75 bugfix: one-at-a-time printing went to z height of last position planned on the previous object (CURA-988) 2016-03-02 13:55:46 +01:00
Tim Kuipers afdb552f63 bugfix: retraction limitation segfaulted when retraction count == 1, cause it handled retraction count as if it was [retraction count - 1] (CURA-977) 2016-03-01 13:18:53 +01:00
Tim Kuipers a400ba28f2 fix: supportOnByuildplateOnly didn't account for conical support (CURA-914) 2016-03-01 12:06:54 +01:00
Tim Kuipers e0a7818d9e fix: infill wipe distance also on skin (CURA-964) 2016-02-29 13:03:42 +01:00
Tim Kuipers 277b5dce75 fix: get infill sparse combine per mesh (CURA-949) 2016-02-29 12:21:11 +01:00
Tim Kuipers 462a6e8c16 bugfix: skin didn't overlap with walls (CURA-941) 2016-02-29 12:16:02 +01:00
Tim Kuipers c20d35e293 fix: used adress comparison on ListPolygons instead of in depth comparison (CURA-934) 2016-02-23 16:23:09 +01:00
Tim Kuipers 9e56841cd2 fix: explicitly deleted polygon comparison, cause it is inefficient, and you most likely need to compare either the memory adress or need to account for the fact that two different polygons might have a different starting point (CURA-934) 2016-02-23 16:21:30 +01:00
Tim Kuipers c39e43c161 bugfix: combing shortcuts to end of comb move (CURA-893) 2016-02-23 15:38:33 +01:00
Tim Kuipers 421a6d4095 removed line from wrong cherry-pick (CURA-894) 2016-02-23 15:38:23 +01:00
Tim Kuipers 8497e46542 bugfix: moveInside always moved in the positive direction for the corner case (CURA-579) 2016-02-23 15:33:04 +01:00
Tim Kuipers 6510ebbd92 Merge branch '2.1' of https://github.com/Ultimaker/CuraEngine into 2.1 2016-02-22 17:27:06 +01:00
Tim Kuipers c1b4a5398b bugfix: small zgaps no-heuristic bugfixes (CURA-921)
It didn't create the right amount of top and bottom layers and didn't work correctly on the topmost and bottom most layers.
2016-02-22 17:26:57 +01:00
Arjen Hiemstra 11c4b9339a Store layer messages in a hash map to speed up lookups for layers 2016-02-22 17:23:17 +01:00
Tim Kuipers 75efbac68e fix: cone angle sign inverted: more sensible fix (CURA-869) 2016-02-22 17:08:04 +01:00
Tim Kuipers cd199dc43e fix: cone angle sign inverted (CURA-869) 2016-02-22 16:50:27 +01:00
Ghostkeeper 2372a78c9b Fix setting-crash test with function evaluation
The minimum value, maximum value, minimum warning value and maximum warning value of each setting is now evaluated as a function, preventing casting errors. Runtest.py runs without errors or even test failures again now.

Contributes to issue CURA-814.
2016-02-15 12:54:44 +01:00
Tim Kuipers 142f4d519f Merge branch 'bugfix_coasting_prime' into 2.1 2016-02-08 16:28:50 +01:00
Arjen Hiemstra 9ea43e7fc1 Move addListener call to before message registration
This makes it possible to receive debug information about message registration
2016-02-07 18:56:28 +01:00
Arjen Hiemstra 9b92de9b8b Add an Arcus::SocketListener subclass so we can log all errors and debug 2016-02-04 12:16:30 +01:00
Tim Kuipers f0f14b0be3 fix: prime too slow after coasting due to unset speed (CURA-796) 2016-01-27 17:40:04 +01:00
Tim Kuipers a82c00bead fix: prime too much after coasting (CURA-796) 2016-01-27 17:39:37 +01:00
36 arquivos alterados com 836 adições e 1161 exclusões
+1 -1
Ver Arquivo
@@ -77,6 +77,7 @@ set(engine_SRCS # Except main.cpp.
src/infill/ZigzagConnectorProcessorNoEndPieces.cpp
src/utils/gettime.cpp
src/utils/LinearAlg2D.cpp
src/utils/logoutput.cpp
src/utils/polygonUtils.cpp
src/utils/polygon.cpp
@@ -86,7 +87,6 @@ set(engine_SRCS # Except main.cpp.
set(engine_TEST
GCodePlannerTest
LinearAlg2DTest
TravellingSalesmanTest
)
# Generating ProtoBuf protocol.
+3 -26
Ver Arquivo
@@ -2,20 +2,18 @@ syntax = "proto3";
package cura.proto;
message ObjectList
message ObjectList
{
repeated Object objects = 1;
repeated Setting settings = 2;
}
// typeid 1
message Slice
{
repeated ObjectList object_lists = 1;
}
message Object
message Object
{
int64 id = 1;
bytes vertices = 2; //An array of 3 floats.
@@ -24,28 +22,13 @@ message Object
repeated Setting settings = 5; // Setting override per object, overruling the global settings.
}
// typeid 3
message Progress
message Progress
{
float amount = 1;
}
// typeid 2
message SlicedObjectList
{
repeated SlicedObject objects = 1;
}
message SlicedObject
{
int64 id = 1;
repeated Layer layers = 2;
}
message Layer {
int32 id = 1;
float height = 2;
float thickness = 3;
@@ -70,20 +53,16 @@ message Polygon {
float line_width = 3;
}
// typeid 4
message GCodeLayer {
int64 id = 1;
bytes data = 2;
}
// typeid 5
message ObjectPrintTime {
int64 id = 1;
float time = 2;
float material_amount = 3;
}
// typeid 6
message SettingList {
repeated Setting settings = 1;
}
@@ -94,11 +73,9 @@ message Setting {
bytes value = 2;
}
// typeid 7
message GCodePrefix {
bytes data = 2;
}
// typeid 8
message SlicingFinished {
}
+30 -28
Ver Arquivo
@@ -178,15 +178,8 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
{
if (!CommandSocket::isInstantiated())
{
std::ostringstream prefix;
prefix << "FLAVOR:" << toString(gcode.getFlavor());
gcode.writeComment(prefix.str().c_str());
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
{
gcode.writeComment("TIME:666");
gcode.writeComment("MATERIAL:666");
gcode.writeComment("MATERIAL2:-1");
}
std::string prefix = gcode.getFileHeader();
gcode.writeCode(prefix.c_str());
}
if (gcode.getFlavor() != EGCodeFlavor::ULTIGCODE)
{
@@ -238,7 +231,8 @@ void FffGcodeWriter::processNextMeshGroupCode(SliceDataStorage& storage)
gcode.resetExtrusionValue();
gcode.setZ(max_object_height + 5000);
gcode.writeMove(gcode.getPositionXY(), getSettingInMillimetersPerSecond("speed_travel"), 0);
gcode.writeMove(Point(storage.model_min.x, storage.model_min.y), getSettingInMillimetersPerSecond("speed_travel"), 0);
last_position_planned = Point(storage.model_min.x, storage.model_min.y);
gcode.writeMove(last_position_planned, getSettingInMillimetersPerSecond("speed_travel"), 0);
}
void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_layers)
@@ -275,7 +269,8 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
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(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"));
int64_t comb_offset = train->getSettingInMicrons("raft_base_line_spacing");
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
gcode_layer.setIsInside(false);
if (getSettingAsIndex("adhesion_extruder_nr") > 0)
@@ -303,7 +298,8 @@ 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(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"));
int64_t comb_offset = train->getSettingInMicrons("raft_interface_line_spacing");
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
gcode_layer.setIsInside(false);
if (CommandSocket::isInstantiated())
@@ -329,7 +325,8 @@ 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(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"));
int64_t comb_offset = train->getSettingInMicrons("raft_surface_line_spacing");
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
gcode_layer.setIsInside(false);
if (CommandSocket::isInstantiated())
@@ -361,8 +358,10 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_
{
layer_thickness = getSettingInMicrons("layer_height_0");
}
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.
ExtruderTrain* current_extruder_train = storage.meshgroup->getExtruderTrain(current_extruder_planned);
int64_t comb_offset_from_outlines = current_extruder_train->getSettingInMicrons((current_extruder_train->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 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(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"));
@@ -593,12 +592,14 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
if (skin_alternate_rotation && ( layer_nr / 2 ) & 1)
skin_angle -= 45;
int64_t skin_overlap = 0;
int64_t skin_overlap = infill_overlap;
processSkin(gcode_layer, mesh, part, layer_nr, skin_overlap, skin_angle, mesh->skin_config.getLineWidth());
//After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter.
if (!mesh->getSettingBoolean("magic_spiralize") || static_cast<int>(layer_nr) < mesh->getSettingAsCount("bottom_layers"))
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons("machine_nozzle_size") * 1);
{
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons((mesh->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
}
}
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
{
@@ -739,7 +740,15 @@ 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, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES)
{
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, SpaceFillType::Lines, mesh->getSettingInMicrons("infill_wipe_dist"));
}
else
{
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
}
}
// handle gaps between perimeters etc.
@@ -754,7 +763,7 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
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);
gcode_layer.addLinesByOptimizer(perimeter_gap_lines, &mesh->skin_config, SpaceFillType::Lines, mesh->getSettingInMicrons("infill_wipe_dist"));
}
}
@@ -933,15 +942,8 @@ void FffGcodeWriter::finalize()
{
if (CommandSocket::isInstantiated())
{
std::ostringstream prefix;
prefix << ";FLAVOR:" << toString(gcode.getFlavor()) << "\n";
prefix << ";TIME:" << int(gcode.getTotalPrintTime()) << "\n";
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
{
prefix << ";MATERIAL:" << int(gcode.getTotalFilamentUsed(0)) << "\n";
prefix << ";MATERIAL2:" << int(gcode.getTotalFilamentUsed(1)) << "\n";
}
CommandSocket::getInstance()->sendGCodePrefix(prefix.str());
std::string prefix = gcode.getFileHeader(gcode.getTotalPrintTime(), gcode.getTotalFilamentUsed(0), gcode.getTotalFilamentUsed(1));
CommandSocket::getInstance()->sendGCodePrefix(prefix);
}
gcode.finalize(getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
+2 -5
Ver Arquivo
@@ -24,9 +24,6 @@ namespace cura
bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper)
{
if (CommandSocket::isInstantiated())
CommandSocket::getInstance()->beginSendSlicedObject();
if (!sliceModel(meshgroup, timeKeeper, storage))
{
return false;
@@ -199,9 +196,9 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
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.
for(SliceMeshStorage& mesh : storage.meshes)
{
unsigned int combined_infill_layers = mesh.getSettingInMicrons("infill_sparse_thickness") / std::max(mesh.getSettingInMicrons("layer_height"), 1); //How many infill layers to combine to obtain the requested sparse thickness.
combineInfillLayers(mesh,combined_infill_layers);
}
@@ -439,7 +436,7 @@ void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh)
for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + rand() % range_random_point_dist)
{
int r = rand() % (fuzziness * 2) - fuzziness;
Point perp_to_p0p1 = crossZ(p0p1);
Point perp_to_p0p1 = turn90CCW(p0p1);
Point fuzz = normal(perp_to_p0p1, r);
Point pa = *p0 + normal(p0p1, p0pa_dist) + fuzz;
result.add(pa);
+1 -1
Ver Arquivo
@@ -103,7 +103,7 @@ bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
if (CommandSocket::isInstantiated())
{
CommandSocket::getInstance()->flushGcode();
CommandSocket::getInstance()->endSendSlicedObject();
CommandSocket::getInstance()->sendLayerData();
}
log("Total time elapsed %5.2fs.\n", time_keeper_total.restart());
+2 -2
Ver Arquivo
@@ -141,7 +141,7 @@ bool MergeInfillLines::isConvertible(const Point& a, const Point& b, const Point
(a + b) / 2;
second_middle = (c + d) / 2;
Point dir_vector_perp = crossZ(second_middle - first_middle);
Point dir_vector_perp = turn90CCW(second_middle - first_middle);
int64_t dir_vector_perp_length = vSize(dir_vector_perp); // == dir_vector_length
if (dir_vector_perp_length == 0)
{
@@ -167,7 +167,7 @@ bool MergeInfillLines::isConvertible(const Point& a, const Point& b, const Point
// check whether two lines are adjacent (note: not 'line segments' but 'lines')
Point ac = c - first_middle;
Point infill_vector_perp = crossZ(infill_vector);
Point infill_vector_perp = turn90CCW(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
+6 -6
Ver Arquivo
@@ -550,7 +550,7 @@ void Wireframe2gcode::processStartingCode()
{
if (!CommandSocket::isInstantiated())
{
gcode.writeCode(";FLAVOR:UltiGCode\n;TIME:666\n;MATERIAL:666\n;MATERIAL2:-1\n");
gcode.writeCode(gcode.getFileHeader().c_str());
}
}
else
@@ -600,14 +600,14 @@ void Wireframe2gcode::processSkirt()
order.addPolygons(skirt);
order.optimize();
for (unsigned int poly_idx = 0; poly_idx < skirt.size(); poly_idx++)
for (unsigned int poly_order_idx = 0; poly_order_idx < skirt.size(); poly_order_idx++)
{
unsigned int actual_poly_idx = order.polyOrder[poly_idx];
PolygonRef poly = skirt[actual_poly_idx];
gcode.writeMove(poly[order.polyStart[actual_poly_idx]], getSettingInMillimetersPerSecond("speed_travel"), 0);
unsigned int poly_idx = order.polyOrder[poly_order_idx];
PolygonRef poly = skirt[poly_idx];
gcode.writeMove(poly[order.polyStart[poly_idx]], getSettingInMillimetersPerSecond("speed_travel"), 0);
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
{
Point& p = poly[(point_idx + order.polyStart[actual_poly_idx] + 1) % poly.size()];
Point& p = poly[(point_idx + order.polyStart[poly_idx] + 1) % poly.size()];
gcode.writeMove(p, getSettingInMillimetersPerSecond("skirt_speed"), getSettingInMillimetersPerSecond("skirt_line_width"));
}
}
+1
Ver Arquivo
@@ -385,6 +385,7 @@ bool LinePolygonsCrossings::optimizePath(CombPath& comb_path, CombPath& optimize
}
}
}
optimized_comb_path.push_back(comb_path.back());
return true;
}
+52 -56
Ver Arquivo
@@ -7,6 +7,8 @@
#include <cinttypes>
#include <Arcus/Socket.h>
#include <Arcus/SocketListener.h>
#include <Arcus/Error.h>
#include <string> // stoi
@@ -25,31 +27,48 @@ namespace cura {
CommandSocket* CommandSocket::instance = nullptr; // instantiate instance
class Listener : public Arcus::SocketListener
{
public:
void stateChanged(Arcus::SocketState::SocketState newState) override
{
}
void messageReceived() override
{
}
void error(const Arcus::Error & error) override
{
if(error.getErrorCode() == Arcus::ErrorCode::Debug)
{
log("%s\n", error.toString().c_str());
}
else
{
logError("%s\n", error.toString().c_str());
}
}
};
class CommandSocket::Private
{
public:
Private()
: socket(nullptr)
, object_count(0)
, current_sliced_object(nullptr)
, sliced_objects(0)
, current_layer_count(0)
, current_layer_offset(0)
{ }
cura::proto::Layer* getLayerById(int id);
std::shared_ptr<cura::proto::Layer> getLayerById(int id);
Arcus::Socket* socket;
// Number of objects that need to be sliced
int object_count;
// Message that holds a list of sliced objects
std::shared_ptr<cura::proto::SlicedObjectList> sliced_object_list;
// Message that holds the currently sliced object (to be added to sliced_object_list)
cura::proto::SlicedObject* current_sliced_object;
// Number of sliced objects for this sliced object list
int sliced_objects;
@@ -57,15 +76,14 @@ public:
// Used for incrementing the current layer in one at a time mode
int current_layer_count;
int current_layer_offset;
// Ids of the sliced objects
std::vector<int64_t> object_ids;
std::string temp_gcode_file;
std::ostringstream gcode_output_stream;
// Print object that olds one or more meshes that need to be sliced.
std::vector< std::shared_ptr<MeshGroup> > objects_to_slice;
std::unordered_map<int, std::shared_ptr<cura::proto::Layer>> sliced_layers;
};
CommandSocket::CommandSocket()
@@ -92,9 +110,11 @@ bool CommandSocket::isInstantiated()
void CommandSocket::connect(const std::string& ip, int port)
{
private_data->socket = new Arcus::Socket();
private_data->socket->addListener(new Listener());
//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::Layer::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());
@@ -104,14 +124,14 @@ void CommandSocket::connect(const std::string& ip, int port)
private_data->socket->connect(ip, port);
log("Connecting to %s:%i", ip.c_str(), port);
log("Connecting to %s:%i\n", 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);
log("Connected to %s:%i\n", ip.c_str(), port);
bool slice_another_time = true;
@@ -137,7 +157,6 @@ void CommandSocket::connect(const std::string& ip, int port)
{
// Reset object counts
private_data->object_count = 0;
private_data->object_ids.clear();
for(auto object : slice->object_lists())
{
handleObjectList(&object);
@@ -169,15 +188,10 @@ void CommandSocket::connect(const std::string& ip, int port)
//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));
}
log("Closing connection\n");
private_data->socket->close();
}
@@ -255,7 +269,6 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
mesh.setSetting(setting.name(), setting.value());
}
private_data->object_ids.push_back(object.id());
mesh.finish();
}
@@ -273,25 +286,17 @@ void CommandSocket::handleSettingList(cura::proto::SettingList* list)
void CommandSocket::sendLayerInfo(int layer_nr, int32_t z, int32_t height)
{
if(!private_data->current_sliced_object)
{
return;
}
cura::proto::Layer* layer = private_data->getLayerById(layer_nr);
std::shared_ptr<cura::proto::Layer> layer = private_data->getLayerById(layer_nr);
layer->set_height(z);
layer->set_thickness(height);
}
void CommandSocket::sendPolygons(PrintFeatureType type, int layer_nr, Polygons& polygons, int line_width)
{
if(!private_data->current_sliced_object)
return;
if (polygons.size() == 0)
return;
cura::proto::Layer* proto_layer = private_data->getLayerById(layer_nr);
std::shared_ptr<cura::proto::Layer> proto_layer = private_data->getLayerById(layer_nr);
for(unsigned int i = 0; i < polygons.size(); ++i)
{
@@ -335,31 +340,22 @@ void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float
// socket.sendFloat32(print_time);
}
void CommandSocket::beginSendSlicedObject()
{
if(!private_data->sliced_object_list)
{
private_data->sliced_object_list = std::make_shared<cura::proto::SlicedObjectList>();
}
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()
void CommandSocket::sendLayerData()
{
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;
log("End sliced object called. Sending ", private_data->current_layer_count, " layers.");
if(private_data->sliced_objects >= private_data->object_count)
{
private_data->socket->sendMessage(private_data->sliced_object_list);
for (std::pair<const int, std::shared_ptr<cura::proto::Layer>> entry : private_data->sliced_layers) //Note: This is in no particular order!
{
private_data->socket->sendMessage(entry.second); //Send the actual layers.
}
private_data->sliced_objects = 0;
private_data->current_layer_count = 0;
private_data->current_layer_offset = 0;
private_data->sliced_object_list.reset();
private_data->current_sliced_object = nullptr;
private_data->sliced_layers.clear();
auto done_message = std::make_shared<cura::proto::SlicingFinished>();
private_data->socket->sendMessage(done_message);
}
@@ -379,7 +375,6 @@ void CommandSocket::beginGCode()
void CommandSocket::flushGcode()
{
auto message = std::make_shared<cura::proto::GCodeLayer>();
message->set_id(private_data->object_ids[0]);
message->set_data(private_data->gcode_output_stream.str());
private_data->socket->sendMessage(message);
@@ -393,22 +388,23 @@ void CommandSocket::sendGCodePrefix(std::string prefix)
private_data->socket->sendMessage(message);
}
cura::proto::Layer* CommandSocket::Private::getLayerById(int id)
std::shared_ptr<cura::proto::Layer> CommandSocket::Private::getLayerById(int id)
{
id += current_layer_offset;
auto itr = std::find_if(current_sliced_object->mutable_layers()->begin(), current_sliced_object->mutable_layers()->end(), [id](cura::proto::Layer& l) { return l.id() == id; });
auto itr = sliced_layers.find(id);
cura::proto::Layer* layer = nullptr;
if(itr != current_sliced_object->mutable_layers()->end())
std::shared_ptr<cura::proto::Layer> layer;
if(itr != sliced_layers.end())
{
layer = &(*itr);
layer = itr->second;
}
else
{
layer = current_sliced_object->add_layers();
layer = std::make_shared<cura::proto::Layer>();
layer->set_id(id);
current_layer_count++;
sliced_layers[id] = layer;
}
return layer;
+5 -7
Ver Arquivo
@@ -82,16 +82,14 @@ public:
* Does nothing at the moment
*/
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
* Send the sliced layer data to the GUI.
*
* The GUI may use this to visualise the g-code, so that the user can
* inspect the result of slicing.
*/
void endSendSlicedObject();
void sendLayerData();
/*!
* \brief Sends a message to indicate that all the slicing is done.
+113 -68
Ver Arquivo
@@ -29,6 +29,23 @@ GCodeExport::~GCodeExport()
{
}
std::string GCodeExport::getFileHeader(double print_time, int filament_used_0, int filament_used_1)
{
std::ostringstream prefix;
prefix << ";FLAVOR:" << toString(flavor) << new_line;
prefix << ";TIME:" << int(print_time) << new_line;
if (flavor == EGCodeFlavor::ULTIGCODE)
{
prefix << ";MATERIAL:" << int(filament_used_0) << new_line;
prefix << ";MATERIAL2:" << int(filament_used_1) << new_line;
prefix << ";NOZZLE_DIAMETER:" << float(INT2MM(getNozzleSize(0))) << new_line;
// prefix << ";NOZZLE_DIAMETER:" << float(INT2MM(getNozzleSize(1))) << new_line; // TODO: the second nozzle size isn't always initiated!
}
return prefix.str();
}
void GCodeExport::setLayerNr(unsigned int layer_nr_) {
layer_nr = layer_nr_;
}
@@ -39,6 +56,11 @@ void GCodeExport::setOutputStream(std::ostream* stream)
*output_stream << std::fixed;
}
int GCodeExport::getNozzleSize(int extruder_idx)
{
return extruder_attr[extruder_idx].nozzle_size;
}
Point GCodeExport::getExtruderOffset(int id)
{
return extruder_attr[id].nozzle_offset;
@@ -133,6 +155,42 @@ double GCodeExport::getCurrentExtrudedVolume()
}
}
double GCodeExport::eToMm(double e)
{
if (is_volumatric)
{
return e / extruder_attr[current_extruder].filament_area;
}
else
{
return e;
}
}
double GCodeExport::mm3ToE(double mm3)
{
if (is_volumatric)
{
return mm3;
}
else
{
return mm3 / extruder_attr[current_extruder].filament_area;
}
}
double GCodeExport::mmToE(double mm)
{
if (is_volumatric)
{
return mm * extruder_attr[current_extruder].filament_area;
}
else
{
return mm;
}
}
double GCodeExport::getTotalFilamentUsed(int e)
{
@@ -176,12 +234,12 @@ void GCodeExport::writeComment(std::string comment)
*output_stream << comment[i];
}
}
*output_stream << "\n";
*output_stream << new_line;
}
void GCodeExport::writeTypeComment(const char* type)
{
*output_stream << ";TYPE:" << type << "\n";
*output_stream << ";TYPE:" << type << new_line;
}
void GCodeExport::writeTypeComment(PrintFeatureType type)
@@ -189,25 +247,25 @@ void GCodeExport::writeTypeComment(PrintFeatureType type)
switch (type)
{
case PrintFeatureType::OuterWall:
*output_stream << ";TYPE:WALL-OUTER\n";
*output_stream << ";TYPE:WALL-OUTER" << new_line;
break;
case PrintFeatureType::InnerWall:
*output_stream << ";TYPE:WALL-INNER\n";
*output_stream << ";TYPE:WALL-INNER" << new_line;
break;
case PrintFeatureType::Skin:
*output_stream << ";TYPE:SKIN\n";
*output_stream << ";TYPE:SKIN" << new_line;
break;
case PrintFeatureType::Support:
*output_stream << ";TYPE:SUPPORT\n";
*output_stream << ";TYPE:SUPPORT" << new_line;
break;
case PrintFeatureType::Skirt:
*output_stream << ";TYPE:SKIRT\n";
*output_stream << ";TYPE:SKIRT" << new_line;
break;
case PrintFeatureType::Infill:
*output_stream << ";TYPE:FILL\n";
*output_stream << ";TYPE:FILL" << new_line;
break;
case PrintFeatureType::SupportInfill:
*output_stream << ";TYPE:SUPPORT\n";
*output_stream << ";TYPE:SUPPORT" << new_line;
break;
case PrintFeatureType::MoveCombing:
case PrintFeatureType::MoveRetraction:
@@ -220,24 +278,24 @@ void GCodeExport::writeTypeComment(PrintFeatureType type)
void GCodeExport::writeLayerComment(int layer_nr)
{
*output_stream << ";LAYER:" << layer_nr << "\n";
*output_stream << ";LAYER:" << layer_nr << new_line;
}
void GCodeExport::writeLayerCountComment(int layer_count)
{
*output_stream << ";LAYER_COUNT:" << layer_count << "\n";
*output_stream << ";LAYER_COUNT:" << layer_count << new_line;
}
void GCodeExport::writeLine(const char* line)
{
*output_stream << line << "\n";
*output_stream << line << new_line;
}
void GCodeExport::resetExtrusionValue()
{
if (current_e_value != 0.0 && flavor != EGCodeFlavor::MAKERBOT && flavor != EGCodeFlavor::BFB)
{
*output_stream << "G92 " << extruder_attr[current_extruder].extruderCharacter << "0\n";
*output_stream << "G92 " << extruder_attr[current_extruder].extruderCharacter << "0" << new_line;
double current_extruded_volume = getCurrentExtrudedVolume();
extruder_attr[current_extruder].totalFilament += current_extruded_volume;
for (double& extruded_volume_at_retraction : extruder_attr[current_extruder].extruded_volume_at_previous_n_retractions)
@@ -251,7 +309,7 @@ void GCodeExport::resetExtrusionValue()
void GCodeExport::writeDelay(double timeAmount)
{
*output_stream << "G4 P" << int(timeAmount * 1000) << "\n";
*output_stream << "G4 P" << int(timeAmount * 1000) << new_line;
estimateCalculator.addTime(timeAmount);
}
@@ -267,11 +325,7 @@ void GCodeExport::writeMove(Point3 p, double speed, double extrusion_mm3_per_mm)
void GCodeExport::writeMoveBFB(int x, int y, int z, double speed, double extrusion_mm3_per_mm)
{
double extrusion_per_mm = extrusion_mm3_per_mm;
if (!is_volumatric)
{
extrusion_per_mm = extrusion_mm3_per_mm / extruder_attr[current_extruder].filament_area;
}
double extrusion_per_mm = mm3ToE(extrusion_mm3_per_mm);
Point gcode_pos = getGcodePos(x,y, current_extruder);
@@ -288,11 +342,11 @@ void GCodeExport::writeMoveBFB(int x, int y, int z, double speed, double extrusi
{
//fprintf(f, "; %f e-per-mm %d mm-width %d mm/s\n", extrusion_per_mm, lineWidth, speed);
//fprintf(f, "M108 S%0.1f\r\n", rpm);
*output_stream << "M108 S" << std::setprecision(1) << rpm << "\r\n";
*output_stream << "M108 S" << std::setprecision(1) << rpm << new_line;
currentSpeed = double(rpm);
}
//Add M101 or M201 to enable the proper extruder.
*output_stream << "M" << int((current_extruder + 1) * 100 + 1) << "\r\n";
*output_stream << "M" << int((current_extruder + 1) * 100 + 1) << new_line;
extruder_attr[current_extruder].retraction_e_amount_current = 0.0;
}
//Fix the speed by the actual RPM we are asking, because of rounding errors we cannot get all RPM values, but we have a lot more resolution in the feedrate value.
@@ -309,17 +363,17 @@ void GCodeExport::writeMoveBFB(int x, int y, int z, double speed, double extrusi
//If we are not extruding, check if we still need to disable the extruder. This causes a retraction due to auto-retraction.
if (!extruder_attr[current_extruder].retraction_e_amount_current)
{
*output_stream << "M103\r\n";
*output_stream << "M103" << new_line;
extruder_attr[current_extruder].retraction_e_amount_current = 1.0; // 1.0 used as stub; BFB doesn't use the actual retraction amount; it performs retraction on the firmware automatically
}
}
*output_stream << std::setprecision(3) <<
"G1 X" << INT2MM(gcode_pos.X) <<
" Y" << INT2MM(gcode_pos.Y) <<
" Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << "\r\n";
" Z" << INT2MM(z) << std::setprecision(1) << " F" << fspeed << new_line;
currentPosition = Point3(x, y, z);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), speed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), speed);
}
void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_mm3_per_mm)
@@ -342,11 +396,7 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
return;
}
double extrusion_per_mm = extrusion_mm3_per_mm;
if (!is_volumatric)
{
extrusion_per_mm = extrusion_mm3_per_mm / extruder_attr[current_extruder].filament_area;
}
double extrusion_per_mm = mm3ToE(extrusion_mm3_per_mm);
Point gcode_pos = getGcodePos(x,y, current_extruder);
@@ -355,30 +405,30 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
Point3 diff = Point3(x,y,z) - getPosition();
if (isZHopped > 0)
{
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z) << "\n";
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z) << new_line;
isZHopped = 0;
}
double prime_volume = extruder_attr[current_extruder].prime_volume;
current_e_value += (is_volumatric) ? prime_volume : prime_volume / extruder_attr[current_extruder].filament_area;
current_e_value += mm3ToE(prime_volume);
if (extruder_attr[current_extruder].retraction_e_amount_current)
{
if (firmware_retract)
{ // note that BFB is handled differently
*output_stream << "G11\n";
*output_stream << "G11" << new_line;
//Assume default UM2 retraction settings.
if (prime_volume > 0)
{
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << new_line;
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
}
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), 25.0);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), 25.0);
}
else
{
current_e_value += extruder_attr[current_extruder].retraction_e_amount_current;
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << new_line;
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
}
if (getCurrentExtrudedVolume() > 10000.0) //According to https://github.com/Ultimaker/CuraEngine/issues/14 having more then 21m of extrusion causes inaccuracies. So reset it every 10m, just to be sure.
{
@@ -388,10 +438,9 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
}
else if (prime_volume > 0.0)
{
current_e_value += prime_volume;
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
*output_stream << "G1 F" << (extruder_attr[current_extruder].last_retraction_prime_speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << new_line;
currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
}
extruder_attr[current_extruder].prime_volume = 0.0;
current_e_value += extrusion_per_mm * diff.vSizeMM();
@@ -425,10 +474,10 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
*output_stream << " Z" << INT2MM(z + isZHopped);
if (extrusion_mm3_per_mm > 0.000001)
*output_stream << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value;
*output_stream << "\n";
*output_stream << new_line;
currentPosition = Point3(x, y, z);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), speed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), speed);
}
void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
@@ -437,7 +486,7 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
{
return;
}
if (extruder_attr[current_extruder].retraction_e_amount_current == config->distance * ((is_volumatric)? extruder_attr[current_extruder].filament_area : 1.0))
if (extruder_attr[current_extruder].retraction_e_amount_current == mmToE(config->distance))
{
return;
}
@@ -449,7 +498,7 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
{ // handle retraction limitation
double current_extruded_volume = getCurrentExtrudedVolume();
std::deque<double>& extruded_volume_at_previous_n_retractions = extruder_attr[current_extruder].extruded_volume_at_previous_n_retractions;
while (int(extruded_volume_at_previous_n_retractions.size()) >= config->retraction_count_max && !extruded_volume_at_previous_n_retractions.empty())
while (int(extruded_volume_at_previous_n_retractions.size()) > config->retraction_count_max && !extruded_volume_at_previous_n_retractions.empty())
{
// extruder switch could have introduced data which falls outside the retraction window
// also the retraction_count_max could have changed between the last retraction and this
@@ -459,13 +508,13 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
{
return;
}
if (!force && int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max - 1
if (!force && int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max
&& current_extruded_volume < extruded_volume_at_previous_n_retractions.back() + config->retraction_extrusion_window * extruder_attr[current_extruder].filament_area)
{
return;
}
extruded_volume_at_previous_n_retractions.push_front(current_extruded_volume);
if (int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max)
if (int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max + 1)
{
extruded_volume_at_previous_n_retractions.pop_back();
}
@@ -473,19 +522,19 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
extruder_attr[current_extruder].last_retraction_prime_speed = config->primeSpeed;
double retraction_e_amount = config->distance * ((is_volumatric)? extruder_attr[current_extruder].filament_area : 1.0);
double retraction_e_amount = mmToE(config->distance);
if (firmware_retract)
{
*output_stream << "G10\n";
*output_stream << "G10" << new_line;
//Assume default UM2 retraction settings.
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value - retraction_e_amount), 25); // TODO: hardcoded values!
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value - retraction_e_amount)), 25); // TODO: hardcoded values!
}
else
{
current_e_value -= retraction_e_amount;
*output_stream << "G1 F" << (config->speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
*output_stream << "G1 F" << (config->speed * 60) << " " << extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << new_line;
currentSpeed = config->speed;
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed);
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
}
extruder_attr[current_extruder].retraction_e_amount_current = retraction_e_amount ;
@@ -494,7 +543,7 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
if (config->zHop > 0)
{
isZHopped = config->zHop;
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z + isZHopped) << "\n";
*output_stream << std::setprecision(3) << "G1 Z" << INT2MM(currentPosition.z + isZHopped) << new_line;
}
}
@@ -503,13 +552,13 @@ void GCodeExport::writeRetraction_extruderSwitch()
if (flavor == EGCodeFlavor::BFB)
{
if (!extruder_attr[current_extruder].retraction_e_amount_current)
*output_stream << "M103\r\n";
*output_stream << "M103" << new_line;
extruder_attr[current_extruder].retraction_e_amount_current = 1.0; // 1.0 is a stub; BFB doesn't use the actual retracted amount; retraction is performed by firmware
return;
}
double retraction_e_amount = extruder_attr[current_extruder].extruder_switch_retraction_distance * ((is_volumatric)? extruder_attr[current_extruder].filament_area : 1.0);
double retraction_e_amount = mmToE(extruder_attr[current_extruder].extruder_switch_retraction_distance);
if (extruder_attr[current_extruder].retraction_e_amount_current == retraction_e_amount)
{
return;
@@ -525,13 +574,13 @@ void GCodeExport::writeRetraction_extruderSwitch()
{
return;
}
*output_stream << "G10 S1\n";
*output_stream << "G10 S1" << new_line;
}
else
{
current_e_value -= retraction_e_amount;
*output_stream << "G1 F" << (extruder_attr[current_extruder].extruderSwitchRetractionSpeed * 60) << " "
<< extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << "\n";
<< extruder_attr[current_extruder].extruderCharacter << std::setprecision(5) << current_e_value << new_line;
// the E value of the extruder switch retraction 'overwrites' the E value of the normal retraction
currentSpeed = extruder_attr[current_extruder].extruderSwitchRetractionSpeed;
extruder_attr[current_extruder].last_retraction_prime_speed = extruder_attr[current_extruder].extruderSwitchPrimeSpeed;
@@ -559,11 +608,11 @@ void GCodeExport::switchExtruder(int new_extruder)
writeCode(extruder_attr[old_extruder].end_code.c_str());
if (flavor == EGCodeFlavor::MAKERBOT)
{
*output_stream << "M135 T" << current_extruder << "\n";
*output_stream << "M135 T" << current_extruder << new_line;
}
else
{
*output_stream << "T" << current_extruder << "\n";
*output_stream << "T" << current_extruder << new_line;
}
writeCode(extruder_attr[new_extruder].start_code.c_str());
@@ -573,11 +622,7 @@ void GCodeExport::switchExtruder(int new_extruder)
void GCodeExport::writeCode(const char* str)
{
*output_stream << str;
if (flavor == EGCodeFlavor::BFB)
*output_stream << "\r\n";
else
*output_stream << "\n";
*output_stream << str << new_line;
}
void GCodeExport::writeFanCommand(double speed)
@@ -587,16 +632,16 @@ void GCodeExport::writeFanCommand(double speed)
if (speed > 0)
{
if (flavor == EGCodeFlavor::MAKERBOT)
*output_stream << "M126 T0\n"; //value = speed * 255 / 100 // Makerbot cannot set fan speed...;
*output_stream << "M126 T0" << new_line; //value = speed * 255 / 100 // Makerbot cannot set fan speed...;
else
*output_stream << "M106 S" << (speed * 255 / 100) << "\n";
*output_stream << "M106 S" << (speed * 255 / 100) << new_line;
}
else
{
if (flavor == EGCodeFlavor::MAKERBOT)
*output_stream << "M127 T0\n";
*output_stream << "M127 T0" << new_line;
else
*output_stream << "M107\n";
*output_stream << "M107" << new_line;
}
currentFanSpeed = speed;
}
@@ -612,7 +657,7 @@ void GCodeExport::writeTemperatureCommand(int extruder, double temperature, bool
*output_stream << "M104";
if (extruder != current_extruder)
*output_stream << " T" << extruder;
*output_stream << " S" << temperature << "\n";
*output_stream << " S" << temperature << new_line;
extruder_attr[extruder].currentTemperature = temperature;
}
@@ -622,7 +667,7 @@ void GCodeExport::writeBedTemperatureCommand(double temperature, bool wait)
*output_stream << "M190 S";
else
*output_stream << "M140 S";
*output_stream << temperature << "\n";
*output_stream << temperature << new_line;
}
void GCodeExport::finalize(double moveSpeed, const char* endCode)
+74 -5
Ver Arquivo
@@ -90,6 +90,14 @@ public:
speed = (speed_iconic*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
}
/*!
* Set the speed to the iconic speed, i.e. the normal speed of the feature type for which this is a config.
*/
void setSpeedIconic()
{
speed = speed_iconic;
}
/*!
* Can only be called after the layer height has been set (which is done while writing the gcode!)
*/
@@ -135,6 +143,7 @@ class GCodeExport : public NoCopy
private:
struct ExtruderTrainAttributes
{
int nozzle_size; //!< The nozzle size label of the nozzle (e.g. 0.4mm; irrespective of tolerances)
Point nozzle_offset;
char extruderCharacter;
std::string start_code;
@@ -170,13 +179,15 @@ private:
, retraction_e_amount_current(0.0)
, retraction_e_amount_at_e_start(0.0)
, prime_volume(0.0)
, last_retraction_prime_speed(1.0)
, last_retraction_prime_speed(0.0)
{ }
};
ExtruderTrainAttributes extruder_attr[MAX_EXTRUDERS];
bool use_extruder_offset_to_offset_coords;
std::ostream* output_stream;
std::string new_line;
double current_e_value; //!< The last E value written to gcode (in mm or mm^3)
Point3 currentPosition;
double currentSpeed; //!< The current speed (F values / 60) in mm/s
@@ -195,15 +206,61 @@ private:
unsigned int layer_nr; //!< for sending travel data
protected:
/*!
* Convert an E value to a value in mm (if it wasn't already in mm) for the current extruder.
*
* E values are either in mm or in mm^3
* The current extruder is used to determine the filament area to make the conversion.
*
* \param e the value to convert
* \return the value converted to mm
*/
double eToMm(double e);
/*!
* Convert a volume value to an E value (which might be volumetric as well) for the current extruder.
*
* E values are either in mm or in mm^3
* The current extruder is used to determine the filament area to make the conversion.
*
* \param mm3 the value to convert
* \return the value converted to mm or mm3 depending on whether the E axis is volumetric
*/
double mm3ToE(double mm3);
/*!
* Convert a distance value to an E value (which might be linear/distance based as well) for the current extruder.
*
* E values are either in mm or in mm^3
* The current extruder is used to determine the filament area to make the conversion.
*
* \param mm the value to convert
* \return the value converted to mm or mm3 depending on whether the E axis is volumetric
*/
double mmToE(double mm);
public:
GCodeExport();
~GCodeExport();
/*!
* Get the gcode file header (e.g. ";FLAVOR:UltiGCode\n")
*
* \param print_time The total print time of the whole file (if known)
* \param filament_used_0 The total mm^3 filament used for the primary extruder (if known)
* \param filament_used_1 The total mm^3 filament used for the secondary extruder (if used and if known)
* \return The string representing the file header
*/
std::string getFileHeader(double print_time = 666, int filament_used_0 = 666, int filament_used_1 = 0);
void setLayerNr(unsigned int layer_nr);
void setOutputStream(std::ostream* stream);
int getNozzleSize(int extruder_idx);
Point getExtruderOffset(int id);
Point getGcodePos(int64_t x, int64_t y, int extruder_train);
@@ -229,7 +286,7 @@ public:
void setFilamentDiameter(unsigned int n, int diameter);
double getCurrentExtrudedVolume();
double getTotalFilamentUsed(int e);
double getTotalPrintTime();
@@ -283,6 +340,7 @@ public:
ExtruderTrain* train = settings->getExtruderTrain(n);
setFilamentDiameter(n, train->getSettingInMicrons("material_diameter"));
extruder_attr[n].nozzle_size = train->getSettingInMicrons("machine_nozzle_size");
extruder_attr[n].nozzle_offset = Point(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
extruder_attr[n].start_code = train->getSettingString("machine_extruder_start_code");
@@ -291,10 +349,21 @@ public:
extruder_attr[n].extruder_switch_retraction_distance = INT2MM(train->getSettingInMicrons("switch_extruder_retraction_amount"));
extruder_attr[n].extruderSwitchRetractionSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_retraction_speed");
extruder_attr[n].extruderSwitchPrimeSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_prime_speed");
extruder_attr[n].last_retraction_prime_speed = train->getSettingInMillimetersPerSecond("retraction_prime_speed"); // the alternative would be switch_extruder_prime_speed, but dual extrusion might not even be configured...
}
setFlavor(settings->getSettingAsGCodeFlavor("machine_gcode_flavor"));
use_extruder_offset_to_offset_coords = settings->getSettingBoolean("machine_use_extruder_offset_to_offset_coords");
if (flavor == EGCodeFlavor::BFB)
{
new_line = "\r\n";
}
else
{
new_line = "\n";
}
}
void finalize(double moveSpeed, const char* endCode);
+73 -21
Ver Arquivo
@@ -235,7 +235,7 @@ void GCodePlanner::addTravel(Point p)
{ // 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);
moveInsideCombBoundary(extr->getSettingInMicrons((extr->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
}
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
path->retract = true;
@@ -286,28 +286,34 @@ void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig*
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type)
{
PathOrderOptimizer orderOptimizer(lastPosition, z_seam_type);
for(unsigned int i=0;i<polygons.size();i++)
orderOptimizer.addPolygon(polygons[i]);
orderOptimizer.optimize();
for(unsigned int i=0;i<orderOptimizer.polyOrder.size();i++)
if (polygons.size() == 0)
{
int nr = orderOptimizer.polyOrder[i];
addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config, wall_overlap_computation);
return;
}
PathOrderOptimizer orderOptimizer(lastPosition, z_seam_type);
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
{
orderOptimizer.addPolygon(polygons[poly_idx]);
}
orderOptimizer.optimize();
for (int poly_idx : orderOptimizer.polyOrder)
{
addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config, wall_overlap_computation);
}
}
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist)
{
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();
for(unsigned int i=0;i<orderOptimizer.polyOrder.size();i++)
LineOrderOptimizer orderOptimizer(lastPosition);
for (unsigned int line_idx = 0; line_idx < polygons.size(); line_idx++)
{
int nr = orderOptimizer.polyOrder[i];
// addPolygon(polygons[nr], orderOptimizer.polyStart[nr], config);
PolygonRef polygon = polygons[nr];
int start = orderOptimizer.polyStart[nr];
orderOptimizer.addPolygon(polygons[line_idx]);
}
orderOptimizer.optimize();
for (int poly_idx : orderOptimizer.polyOrder)
{
// addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config); // adds line as polygon; old code
PolygonRef polygon = polygons[poly_idx];
int start = orderOptimizer.polyStart[poly_idx];
int end = 1 - start;
Point& p0 = polygon[start];
addTravel(p0);
@@ -445,7 +451,21 @@ void GCodePlanner::processFanSpeedAndMinimalLayerTime()
FanSpeedLayerTimeSettings& fsml = fan_speed_layer_time_settings;
TimeMaterialEstimates estimates = computeNaiveTimeEstimates();
forceMinimalLayerTime(fsml.cool_min_layer_time, fsml.cool_min_speed, estimates.getTravelTime(), estimates.getExtrudeTime());
/*
min layer time
:
: min layer time fan speed min
| : :
^ max..|__: :
| \ :
fan | \ :
speed min..|... \:___________
|________________
layer time >
*/
// interpolate fan speed (for cool_fan_full_layer and for cool_min_layer_time_fan_speed_max)
fan_speed = fsml.cool_fan_speed_min;
double totalLayerTime = estimates.unretracted_travel_time + estimates.extrude_time;
@@ -456,8 +476,25 @@ void GCodePlanner::processFanSpeedAndMinimalLayerTime()
else if (totalLayerTime < fsml.cool_min_layer_time_fan_speed_max)
{
// when forceMinimalLayerTime didn't change the extrusionSpeedFactor, we adjust the fan speed
fan_speed = fsml.cool_fan_speed_max - (fsml.cool_fan_speed_max-fsml.cool_fan_speed_min) * (totalLayerTime - fsml.cool_min_layer_time) / (fsml.cool_min_layer_time_fan_speed_max - fsml.cool_min_layer_time);
double fan_speed_diff = fsml.cool_fan_speed_max - fsml.cool_fan_speed_min;
double layer_time_diff = fsml.cool_min_layer_time_fan_speed_max - fsml.cool_min_layer_time;
double fraction_of_slope = (totalLayerTime - fsml.cool_min_layer_time) / layer_time_diff;
fan_speed = fsml.cool_fan_speed_max - fan_speed_diff * fraction_of_slope;
}
/*
Supposing no influence of minimal layer time; i.e. layer time > min layer time fan speed min:
max.. fan 'full' on layer
| :
| :
^ min..|..:________________
fan | /
speed | /
zero..|/__________________
layer nr >
*/
if (layer_nr < fsml.cool_fan_full_layer)
{
//Slow down the fan on the layers below the [cool_fan_full_layer], where layer 0 is speed 0.
@@ -649,24 +686,39 @@ void GCodePlanner::completeConfigs()
void GCodePlanner::processInitialLayersSpeedup()
{
double initial_speedup_layers = storage.getSettingAsCount("speed_slowdown_layers");
int initial_speedup_layers = storage.getSettingAsCount("speed_slowdown_layers");
if (static_cast<int>(layer_nr) < initial_speedup_layers)
{
double initial_layer_speed = storage.getSettingInMillimetersPerSecond("speed_layer_0");
storage.support_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
storage.support_roof_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
for(SliceMeshStorage& mesh : storage.meshes)
for (SliceMeshStorage& mesh : storage.meshes)
{
initial_layer_speed = mesh.getSettingInMillimetersPerSecond("speed_layer_0");
mesh.inset0_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
mesh.insetX_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
mesh.skin_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
for(unsigned int idx=0; idx<MAX_INFILL_COMBINE; idx++)
for (unsigned int idx = 0; idx < MAX_INFILL_COMBINE; idx++)
{
mesh.infill_config[idx].smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
}
}
}
else if (static_cast<int>(layer_nr) == initial_speedup_layers)
{
storage.support_config.setSpeedIconic();
storage.support_roof_config.setSpeedIconic();
for (SliceMeshStorage& mesh : storage.meshes)
{
mesh.inset0_config.setSpeedIconic();
mesh.insetX_config.setSpeedIconic();
mesh.skin_config.setSpeedIconic();
for (unsigned int idx = 0; idx < MAX_INFILL_COMBINE; idx++)
{
mesh.infill_config[idx].setSpeedIconic();
}
}
}
}
void GCodePlanner::writeRetraction(GCodeExport& gcode, unsigned int extruder_plan_idx, unsigned int path_idx_travel_after)
+1 -4
Ver Arquivo
@@ -208,10 +208,7 @@ void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outli
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.
}
outline = outline.offset(infill_overlap * infill_line_width / 100); // division by 100 cause it's a percentage.
if (outline.size() == 0)
{
+2 -2
Ver Arquivo
@@ -16,7 +16,7 @@ void generateInsets(SliceLayerPart* part, int nozzle_width, int line_width_0, in
part->insets.push_back(Polygons());
if (i == 0)
{
if (line_width_0 < nozzle_width)
if (false) // line_width_0 < nozzle_width) // TODO: this is a quick fix for version 2.1 only; this line should not be in master
{
PolygonUtils::offsetSafe(part->outline, - nozzle_width/2, line_width_0, part->insets[0], avoidOverlappingPerimeters_0);
}
@@ -26,7 +26,7 @@ void generateInsets(SliceLayerPart* part, int nozzle_width, int line_width_0, in
}
} else if (i == 1)
{
if (line_width_0 < nozzle_width)
if (false) // line_width_0 < nozzle_width) // TODO: this is a quick fix for version 2.1 only; this line should not be in master
{
int offset_from_first_boundary_for_edge_of_outer_wall = -nozzle_width/2;
// ideally this /\ should be: nozzle_width/2 - line_width_0; however, factually, the nozzle will fill up part of the perimeter gaps
+132 -180
Ver Arquivo
@@ -2,32 +2,30 @@
#include "pathOrderOptimizer.h"
#include "utils/logoutput.h"
#include "utils/BucketGrid2D.h"
#include "utils/TravellingSalesman.h"
#include "utils/linearAlg2D.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 (PolygonRef poly : polygons) /// 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 point_idx = 0; point_idx < poly.size(); point_idx++) /// get closest point in polygon
{
float dist = vSize2f(poly[i_point] - startPoint);
float dist = vSize2f(poly[point_idx] - startPoint);
if (dist < bestDist)
{
best = i_point;
best = point_idx;
bestDist = dist;
}
}
@@ -39,46 +37,50 @@ void PathOrderOptimizer::optimize()
Point prev_point = startPoint;
for (unsigned int polygon_idx = 0; polygon_idx < polygons.size(); polygon_idx++) /// actual path order optimizer
for (unsigned int poly_order_idx = 0; poly_order_idx < polygons.size(); poly_order_idx++) /// actual path order optimizer
{
int best = -1;
int best_poly_idx = -1;
float bestDist = std::numeric_limits<float>::infinity();
for (unsigned int polygon2_idx = 0; polygon2_idx < polygons.size(); polygon2_idx++)
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
{
if (picked[polygon2_idx] || polygons[polygon2_idx].size() < 1) /// skip single-point-polygons
if (picked[poly_idx] || polygons[poly_idx].size() < 1) /// skip single-point-polygons
{
continue;
}
assert(polygons[polygon2_idx].size() != 2);
assert (polygons[poly_idx].size() != 2);
float dist = vSize2f(polygons[polygon2_idx][polyStart[polygon2_idx]] - prev_point);
float dist = vSize2f(polygons[poly_idx][polyStart[poly_idx]] - prev_point);
if (dist < bestDist)
{
best = polygon2_idx;
best_poly_idx = poly_idx;
bestDist = dist;
}
}
if (best > -1) /// should always be true; we should have been able to identify the best next polygon
if (best_poly_idx > -1) /// should always be true; we should have been able to identify the best next polygon
{
assert(polygons[best].size() != 2);
assert(polygons[best_poly_idx].size() != 2);
prev_point = polygons[best][polyStart[best]];
prev_point = polygons[best_poly_idx][polyStart[best_poly_idx]];
picked[best] = true;
polyOrder.push_back(best);
picked[best_poly_idx] = true;
polyOrder.push_back(best_poly_idx);
}
else
{
logError("Failed to find next closest polygon.\n");
}
}
prev_point = startPoint;
for (unsigned int n = 0; n < polyOrder.size(); n++) /// decide final starting points in each polygon
for (unsigned int order_idx = 0; order_idx < polyOrder.size(); order_idx++) /// decide final starting points in each polygon
{
int poly_idx = polyOrder[n];
int poly_idx = polyOrder[order_idx];
int point_idx = getPolyStart(prev_point, poly_idx);
polyStart[poly_idx] = point_idx;
prev_point = polygons[poly_idx][point_idx];
@@ -90,32 +92,34 @@ 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++)
float best_point_score = std::numeric_limits<float>::infinity();
Point p0 = poly.back();
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
{
float dist = vSize2f(poly[i_point] - prev_point);
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)
dot_score = -dot_score;
if (dist + dot_score < bestDist)
Point& p1 = poly[point_idx];
Point& p2 = poly[(point_idx + 1) % poly.size()];
int64_t dist = vSize2(p1 - prev_point);
float is_on_inside_corner_score = -LinearAlg2D::getAngleLeft(p0, p1, p2) / M_PI * 5000 * 5000; // prefer inside corners
// this score is in the order of 5 mm
if (dist + is_on_inside_corner_score < best_point_score)
{
best_point_idx = i_point;
bestDist = dist;
best_point_idx = point_idx;
best_point_score = dist + is_on_inside_corner_score;
}
p0 = p1;
}
return best_point_idx;
}
@@ -125,12 +129,13 @@ 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,177 +146,124 @@ 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()
{
if (lines.empty()) //Nothing to do. Terminate early.
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 poly_idx = 0; poly_idx < polygons.size(); poly_idx++) /// find closest point to initial starting point within each polygon +initialize picked
{
return;
int best_point_idx = -1;
float best_point_dist = std::numeric_limits<float>::infinity();
PolygonRef poly = polygons[poly_idx];
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++) /// get closest point from polygon
{
float dist = vSize2f(poly[point_idx] - startPoint);
if (dist < best_point_dist)
{
best_point_idx = point_idx;
best_point_dist = dist;
}
}
polyStart.push_back(best_point_idx);
assert(poly.size() == 2);
line_bucket_grid.insert(poly[0], poly_idx);
line_bucket_grid.insert(poly[1], poly_idx);
}
//Since polyOrder must be filled with indices, an index in the polygons vector represents each line.
std::vector<Cluster> line_clusters = cluster();
//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.
Point incoming_perpundicular_normal(0, 0);
Point prev_point = startPoint;
for (unsigned int order_idx = 0; order_idx < polygons.size(); order_idx++) /// actual path order optimizer
{
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.
{
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.
int best_line_idx = -1;
float best_score = std::numeric_limits<float>::infinity(); // distance score for the best next line
//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 close_line_poly_idx : line_bucket_grid.findNearbyObjects(prev_point)) /// check if single-line-polygon is close to last point
{
polyOrder.push_back(static_cast<int>(cluster[0]));
if (orientation >= 1)
if (picked[close_line_poly_idx] || polygons[close_line_poly_idx].size() < 1)
{
polyStart[cluster[0]] = 1 - polyStart[cluster[0]];
continue;
}
}
else //Larger clusters have 4 possible orientations.
{
if ((orientation & 1) == 0) //Not reversed.
{
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];
}
}
}
}
}
std::vector<std::vector<size_t>> LineOrderOptimizer::cluster()
{
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.
{
continue;
updateBestLine(close_line_poly_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal);
}
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.
if (best_line_idx == -1) /// if single-line-polygon hasn't been found yet
{
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]))
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
{
if (picked[neighbour]) //Don't use neighbours that are already in another cluster.
if (picked[poly_idx] || polygons[poly_idx].size() < 1) /// skip single-point-polygons
{
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;
assert(polygons[poly_idx].size() == 2);
updateBestLine(poly_idx, best_line_idx, best_score, prev_point, incoming_perpundicular_normal);
}
}
if (best_line_idx > -1) /// should always be true; we should have been able to identify the best next polygon
{
PolygonRef best_line = polygons[best_line_idx];
assert(best_line.size() == 2);
int line_start_point_idx = polyStart[best_line_idx];
int line_end_point_idx = line_start_point_idx * -1 + 1; /// 1 -> 0 , 0 -> 1
Point& line_start = best_line[line_start_point_idx];
Point& line_end = best_line[line_end_point_idx];
prev_point = line_end;
incoming_perpundicular_normal = turn90CCW(normal(line_end - line_start, 1000));
picked[best_line_idx] = true;
polyOrder.push_back(best_line_idx);
}
else
{
logError("Failed to find next closest line.\n");
}
}
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)
inline void LineOrderOptimizer::updateBestLine(unsigned int poly_idx, int& best, float& best_score, Point prev_point, Point incoming_perpundicular_normal)
{
//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.
Point& p0 = polygons[poly_idx][0];
Point& p1 = polygons[poly_idx][1];
float dot_score = getAngleScore(incoming_perpundicular_normal, p0, p1);
{ /// check distance to first point on line (0)
float score = vSize2f(p0 - prev_point) + dot_score; // prefer 90 degree corners
if (score < best_score)
{
*best_polygon = line;
*best_distance = distance_end_end;
*best_start = lines[line].size() - 1;
best = poly_idx;
best_score = score;
polyStart[poly_idx] = 0;
}
}
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)
{ /// check distance to second point on line (1)
float score = vSize2f(p1 - prev_point) + dot_score; // prefer 90 degree corners
if (score < best_score)
{
*best_polygon = line;
*best_distance = distance_end_start;
*best_start = 0;
best = poly_idx;
best_score = score;
polyStart[poly_idx] = 1;
}
}
}
float LineOrderOptimizer::getAngleScore(Point incoming_perpundicular_normal, Point p0, Point p1)
{
return dot(incoming_perpundicular_normal, normal(p1 - p0, 1000)) * 0.0001f;
}
}//namespace cura
+30 -59
Ver Arquivo
@@ -56,89 +56,60 @@ 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> lines; //!< the parts of the layer (in arbitrary order)
std::vector<PolygonRef> polygons; //!< the parts of the layer (in arbitrary order)
std::vector<int> polyStart; //!< polygons[i][polyStart[i]] = point of polygon i which is to be the starting point in printing the polygon
std::vector<int> polyOrder; //!< the optimized order as indices in #polygons
/*!
* \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);
LineOrderOptimizer(Point startPoint)
{
this->startPoint = startPoint;
}
void addPolygon(PolygonRef polygon)
{
this->lines.push_back(polygon);
this->polygons.push_back(polygon);
}
void addPolygons(Polygons& polygons)
{
for(unsigned int i = 0; i < polygons.size(); i++)
{
this->lines.push_back(polygons[i]);
}
for(unsigned int i=0;i<polygons.size(); i++)
this->polygons.push_back(polygons[i]);
}
void optimize(); //!< sets #polyStart and #polyOrder
private:
/*!
* \brief Clusters the polygons in groups such that the start and end of the
* polygons in each group are close together.
* Update LineOrderOptimizer::polyStart if the current line is better than the current best.
*
* 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.
* Besides looking at the distance from the previous line segment, we also look at the angle we make.
*
* \return Clusters of polygons, where each cluster is represented with a
* vector of indices pointing to positions in \link polygons.
* We prefer 90 degree angles; 180 degree turn arounds are slow on machines where the jerk is limited.
* 0 degree (straight ahead) 'corners' occur only when a single infill line is interrupted,
* in which case the travel move might involve combing, which makes it rather longer.
*
* \param poly_idx[in] The index in LineOrderOptimizer::polygons for the current line to test
* \param best[in, out] The index of current best line
* \param best_score[in, out] The distance score for the current best line
* \param prev_point[in] The previous point from which to find the next best line
* \param incoming_perpundicular_normal[in] The direction of movement when the print head arrived at \p prev_point, turned 90 degrees CCW
*/
std::vector<Cluster> cluster();
void updateBestLine(unsigned int poly_idx, int& best, float& best_score, Point prev_point, Point incoming_perpundicular_normal);
/*!
* \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.
* Get a score to modify the distance score for measuring how good two lines follow each other.
*
* The angle score is symmetric in \p from and \p to; they can be exchanged without altering the result. (Code relies on this property)
*
* \param incoming_perpundicular_normal The direction in which the head was moving while printing the previous line, turned 90 degrees CCW
* \param from The one end of the next line
* \param to The other end of the next line
* \return A score measuring how good the angle is of the line between \p from and \p to when the previous line had a direction given by \p incoming_perpundicular_normal
*
*/
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);
static float getAngleScore(Point incoming_perpundicular_normal, Point from, Point to);
};
}//namespace cura
+10 -6
Ver Arquivo
@@ -42,13 +42,16 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& storage, int innermost_wa
Polygons downskin = (downSkinCount == 0)? Polygons() : upskin;
if (upSkinCount == 0) upskin = Polygons();
auto getInsidePolygons = [&part](SliceLayer& layer2)
auto getInsidePolygons = [&part, wall_line_count](SliceLayer& layer2)
{
Polygons result;
for(SliceLayerPart& part2 : layer2.parts)
{
if (part.boundaryBox.hit(part2.boundaryBox))
result.add(part2.insets.back());
{
unsigned int wall_idx = std::min(wall_line_count, (int) part2.insets.size()) - 1;
result.add(part2.insets[wall_idx]);
}
}
return result;
};
@@ -67,20 +70,20 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& storage, int innermost_wa
}
else
{
if (layer_nr > 0 && downSkinCount > 0)
if (layer_nr >= downSkinCount && downSkinCount > 0)
{
Polygons not_air = getInsidePolygons(storage.layers[layer_nr - 1]);
for (int downskin_layer_nr = std::max(0, layer_nr - downSkinCount); downskin_layer_nr < layer_nr - 1; downskin_layer_nr++)
for (int downskin_layer_nr = layer_nr - downSkinCount; downskin_layer_nr < layer_nr - 1; downskin_layer_nr++)
{
not_air = not_air.intersection(getInsidePolygons(storage.layers[downskin_layer_nr]));
}
downskin = downskin.difference(not_air); // skin overlaps with the walls
}
if (layer_nr < static_cast<int>(storage.layers.size()) - 1 && upSkinCount > 0)
if (layer_nr < static_cast<int>(storage.layers.size()) - upSkinCount && upSkinCount > 0)
{
Polygons not_air = getInsidePolygons(storage.layers[layer_nr + 1]);
for (int upskin_layer_nr = layer_nr + 2; upskin_layer_nr < std::min(static_cast<int>(storage.layers.size()) - 1, layer_nr + upSkinCount); upskin_layer_nr++)
for (int upskin_layer_nr = layer_nr + 2; upskin_layer_nr < layer_nr + upSkinCount + 1; upskin_layer_nr++)
{
not_air = not_air.intersection(getInsidePolygons(storage.layers[upskin_layer_nr]));
}
@@ -142,6 +145,7 @@ void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_e
{
if (int(part.insets.size()) < wall_line_count)
{
part.infill_area.emplace_back(); // put empty polygon as (uncombined) infill
continue; // the last wall is not present, the part should only get inter preimeter gaps, but no infill.
}
Polygons infill = part.insets.back().offset(-innermost_wall_extrusion_width / 2 - infill_skin_overlap);
+2
Ver Arquivo
@@ -65,6 +65,8 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
*
* The walls should already be generated.
*
* After this function has been called on a layer of a mesh, each SliceLayerPart of that layer should have an infill_area consisting of exactly one Polygons : the normal uncombined infill area.
*
* \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 innermost_wall_extrusion_width width of the innermost wall lines
+31 -10
Ver Arquivo
@@ -94,7 +94,6 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
double supportAngle = mesh.getSettingInAngleRadians("support_angle");
bool supportOnBuildplateOnly = support_type == ESupportType::PLATFORM_ONLY;
int supportZDistance = mesh.getSettingInMicrons("support_z_distance");
int supportZDistanceBottom = mesh.getSettingInMicrons("support_bottom_distance");
int supportZDistanceTop = mesh.getSettingInMicrons("support_top_distance");
int join_distance = mesh.getSettingInMicrons("support_join_distance");
@@ -120,15 +119,17 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
double conical_support_angle = mesh.getSettingInAngleRadians("support_conical_angle");
int64_t conical_smallest_breadth = mesh.getSettingInMicrons("support_conical_min_width");
// derived settings:
if (conical_support_angle == 0)
{
conical_support = false;
}
if (supportZDistanceBottom < 0) supportZDistanceBottom = supportZDistance;
if (supportZDistanceTop < 0) supportZDistanceTop = supportZDistance;
// derived settings:
int supportLayerThickness = layerThickness;
int layerZdistanceTop = supportZDistanceTop / supportLayerThickness + 1; // support must always be 1 layer below overhang
int layerZdistanceTop = std::max(0, supportZDistanceTop / supportLayerThickness) + 1; // support must always be 1 layer below overhang
unsigned int layerZdistanceBottom = std::max(0, supportZDistanceBottom / supportLayerThickness);
double tanAngle = tan(supportAngle) - 0.01; // the XY-component of the supportAngle
@@ -136,12 +137,12 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
int64_t conical_support_offset;
if (conical_support_angle > 0)
{
conical_support_offset = (tan(conical_support_angle) - 0.01) * supportLayerThickness;
{ // outward ==> wider base than overhang
conical_support_offset = -(tan(conical_support_angle) - 0.01) * supportLayerThickness;
}
else
{
conical_support_offset = -(tan(-conical_support_angle) - 0.01) * supportLayerThickness;
{ // inward ==> smaller base than overhang
conical_support_offset = (tan(-conical_support_angle) - 0.01) * supportLayerThickness;
}
unsigned int support_layer_count = layer_count;
@@ -248,7 +249,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
if (still_in_upper_empty_layers && supportLayer_this.size() > 0)
{
storage.support.layer_nr_max_filled_layer = layer_idx;
storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, (int)layer_idx);
still_in_upper_empty_layers = false;
}
@@ -263,6 +264,26 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
{
Polygons& supportLayer = supportAreas[layer_idx];
if (conical_support)
{ // with conical support the next layer is allowed to be larger than the previous
touching_buildplate = touching_buildplate.offset(std::abs(conical_support_offset) + 10, ClipperLib::jtMiter, 10);
// + 10 and larger miter limit cause performing an outward offset after an inward offset can disregard sharp corners
//
// conical support can make
// layer above layer below
// v v
// | : |
// | ==> : |__
// |____ :....
//
// a miter limit would result in
// | : : |
// | :.. <== : |__
// .\___ :....
//
}
touching_buildplate = supportLayer.intersection(touching_buildplate); // from bottom to top, support areas can only decrease!
supportAreas[layer_idx] = touching_buildplate;
-37
Ver Arquivo
@@ -30,43 +30,6 @@ 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)
{
+5
Ver Arquivo
@@ -205,6 +205,11 @@ public:
// if (! emplaced.second)
// logError("Error! BucketGrid2D couldn't insert object!");
};
};
}//namespace cura
+34
Ver Arquivo
@@ -0,0 +1,34 @@
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
#include "linearAlg2D.h"
#include <cmath> // atan2
#include "intpoint.h" // dot
namespace cura
{
float LinearAlg2D::getAngleLeft(const Point& a, const Point& b, const Point& c)
{
Point ba = a - b;
Point bc = c - b;
int64_t dott = dot(ba, bc); // dot product
int64_t det = ba.X * bc.Y - ba.Y * bc.X; // determinant
float angle = -atan2(det, dott); // from -pi to pi
if (angle >= 0 )
{
return angle;
}
else
{
return M_PI * 2 + angle;
}
// Point ba = a - b;
// Point bc = c - b;
// int64_t dott = dot(ba, bc); // dot product
// int64_t det = ba.X * bc.Y - ba.Y * bc.X; // determinant
// return -atan2(det, dott); // from -pi to pi
}
} // namespace cura
+1 -39
Ver Arquivo
@@ -140,44 +140,6 @@ 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.
@@ -247,7 +209,7 @@ public:
{
Point fa = transform(a);
Point fb = transform(b);
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());
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());
}
/*!
-405
Ver Arquivo
@@ -1,405 +0,0 @@
//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 */
+1 -1
Ver Arquivo
@@ -202,7 +202,7 @@ INLINE Point normal(const Point& p0, int64_t len)
return p0 * len / _len;
}
INLINE Point crossZ(const Point& p0)
INLINE Point turn90CCW(const Point& p0)
{
return Point(-p0.Y, p0.X);
}
+19
Ver Arquivo
@@ -181,6 +181,25 @@ public:
|| getDist2FromLineSegment(c, a, d) <= max_dist2
|| getDist2FromLineSegment(c, b, d) <= max_dist2;
}
/*!
* Compute the angle between two consecutive line segments.
*
* The angle is computed from the left side of b when looking from a.
*
* c
* \ .
* \ b
* angle|
* |
* a
*
* \param a start of first line segment
* \param b end of first segment and start of second line segment
* \param c end of second line segment
* \return the angle in radians between 0 and 2 * pi of the corner in \p b
*/
static float getAngleLeft(const Point& a, const Point& b, const Point& c);
};
+23 -8
Ver Arquivo
@@ -65,7 +65,9 @@ public:
}
PolygonRef& operator=(const PolygonRef& other) { polygon = other.polygon; return *this; }
bool operator==(const PolygonRef& other) const =delete;
ClipperLib::Path& operator*() { return *polygon; }
template <typename... Args>
@@ -211,7 +213,7 @@ public:
/*!
* Smooth out the polygon and store the result in \p result.
* Smoothing is performed by removing line segments smaller than \p remove_length
* Smoothing is performed by removing vertices for which both connected line segments are smaller than \p remove_length
*
* \param remove_length The length of the largest segment removed
* \param result (output) The result polygon, assumed to be empty
@@ -221,15 +223,26 @@ public:
PolygonRef& thiss = *this;
ClipperLib::Path* poly = result.polygon;
if (size() > 0)
{
poly->push_back(thiss[0]);
}
for (unsigned int poly_idx = 1; poly_idx < size(); poly_idx++)
{
if (shorterThen(thiss[poly_idx-1]-thiss[poly_idx], remove_length))
Point& last = thiss[poly_idx - 1];
Point& now = thiss[poly_idx];
Point& next = thiss[(poly_idx + 1) % size()];
if (shorterThen(last - now, remove_length) && shorterThen(now - next, remove_length))
{
poly_idx++; // skip the next line piece (dont escalate the removal of edges)
if (poly_idx < size())
{
poly->push_back(thiss[poly_idx]);
} else poly->push_back(thiss[poly_idx]);
}
}
else
{
poly->push_back(thiss[poly_idx]);
}
}
}
@@ -348,6 +361,9 @@ public:
Polygons(const Polygons& other) { polygons = other.polygons; }
Polygons& operator=(const Polygons& other) { polygons = other.polygons; return *this; }
bool operator==(const Polygons& other) const =delete;
Polygons difference(const Polygons& other) const
{
Polygons ret;
@@ -391,13 +407,12 @@ public:
clipper.Execute(ClipperLib::ctXor, ret.polygons);
return ret;
}
Polygons offset(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter) const
Polygons offset(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miter_limit = 1.2) const
{
Polygons ret;
double miterLimit = 1.2;
ClipperLib::ClipperOffset clipper(miterLimit, 10.0);
ClipperLib::ClipperOffset clipper(miter_limit, 10.0);
clipper.AddPaths(polygons, joinType, ClipperLib::etClosedPolygon);
clipper.MiterLimit = miterLimit;
clipper.MiterLimit = miter_limit;
clipper.Execute(ret.polygons, distance);
return ret;
}
+11 -11
Ver Arquivo
@@ -70,8 +70,8 @@ Point PolygonUtils::getBoundaryPointWithOffset(PolygonRef poly, unsigned int poi
Point p1 = poly[point_idx];
Point p2 = poly[(point_idx < (poly.size() - 1)) ? (point_idx + 1) : 0];
Point off0 = crossZ(normal(p1 - p0, MM2INT(1.0))); // 1.0 for some precision
Point off1 = crossZ(normal(p2 - p1, MM2INT(1.0))); // 1.0 for some precision
Point off0 = turn90CCW(normal(p1 - p0, MM2INT(1.0))); // 1.0 for some precision
Point off1 = turn90CCW(normal(p2 - p1, MM2INT(1.0))); // 1.0 for some precision
Point n = normal(off0 + off1, -offset);
return p1 + n;
@@ -85,7 +85,7 @@ unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int dista
Point ret = from;
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
unsigned int bestPoly = NO_INDEX;
bool is_inside = false;
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
{
PolygonRef poly = polygons[poly_idx];
@@ -125,9 +125,10 @@ unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int dista
if (distance == 0) { ret = x; }
else
{
Point inward_dir = crossZ(normal(ab,distance * 4) + normal(p1 - p0,distance * 4));
ret = x + normal(inward_dir, distance); // *4 to retain more precision for the eventual normalization
is_inside = dot(inward_dir, p - x) >= 0;
Point inward_dir = turn90CCW(normal(ab, MM2INT(10.0)) + normal(p1 - p0, MM2INT(10.0))); // inward direction irrespective of sign of [distance]
// MM2INT(10.0) to retain precision for the eventual normalization
ret = x + normal(inward_dir, distance);
is_already_on_correct_side_of_boundary = dot(inward_dir, p - x) * distance >= 0;
}
}
}
@@ -147,7 +148,7 @@ unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int dista
continue;
}
else
{
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
projected_p_beyond_prev_segment = false;
Point x = a + ab * ax_length / ab_length;
@@ -159,9 +160,9 @@ unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int dista
if (distance == 0) { ret = x; }
else
{
Point inward_dir = crossZ(normal(ab, distance));
Point inward_dir = turn90CCW(normal(ab, distance)); // inward or outward depending on the sign of [distance]
ret = x + inward_dir;
is_inside = dot(inward_dir, p - x) >= 0;
is_already_on_correct_side_of_boundary = dot(inward_dir, p - x) >= 0;
}
}
}
@@ -171,7 +172,7 @@ unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int dista
p1 = p2;
}
}
if (is_inside)
if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
{
if (bestDist2 < distance * distance)
{
@@ -191,7 +192,6 @@ unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int dista
return NO_INDEX;
}
void PolygonUtils::findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result, int sample_size)
{
PolygonRef poly1 = poly1_result.poly;
+2 -2
Ver Arquivo
@@ -79,7 +79,7 @@ void WallOverlapComputation::findOverlapPoints(ListPolyIt from_it, unsigned int
Point& last_point = *last_it;
Point& point = *it;
if ( from_it.poly == to_list_poly
if (&from_it.poly == &to_list_poly
&& (
(from_it.it == last_it || from_it.it == it) // we currently consider a linesegment directly connected to [from]
|| (from_it.prev().it == it || from_it.next().it == last_it) // line segment from [last_point] to [point] is connected to line segment of which [from] is the other end
@@ -94,7 +94,7 @@ void WallOverlapComputation::findOverlapPoints(ListPolyIt from_it, unsigned int
int64_t dist2 = vSize2(closest - from);
if (dist2 > line_width * line_width
|| ( from_it.poly == to_list_poly
|| (&from_it.poly == &to_list_poly
&& dot(from_it.next().p() - from, point - last_point) > 0
&& dot(from - from_it.prev().p(), point - last_point) > 0 ) // line segments are likely connected, because the winding order is in the same general direction
)
+10 -1
Ver Arquivo
@@ -74,7 +74,16 @@ class WallOverlapComputation
ListPolyIt(ListPolygon& poly, ListPolygon::iterator it)
: poly(poly), it(it) { }
Point& p() const { return *it; }
bool operator==(const ListPolyIt& other) const { return poly == other.poly && it == other.it; }
/*!
* Test whether two iterators refer to the same polygon in the same polygon list.
*
* \param other The ListPolyIt to test for equality
* \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument.
*/
bool operator==(const ListPolyIt& other) const
{
return &poly == &other.poly && it == other.it;
}
void operator=(const ListPolyIt& other) { poly = other.poly; it = other.it; }
//!< move the iterator forward (and wrap around at the end)
ListPolyIt& operator++()
+52
Ver Arquivo
@@ -215,4 +215,56 @@ void LinearAlg2DTest::getDist2FromLineSegmentAssert(Point line_start,Point line_
}
}
void LinearAlg2DTest::getAngleStraightTest()
{
getAngleAssert(Point(-100, 0), Point(0, 0), Point(100, 1), 1.0);
}
void LinearAlg2DTest::getAngle45CcwTest()
{
getAngleAssert(Point(-100, 0), Point(0, 0), Point(-100, -100), 1.75);
}
void LinearAlg2DTest::getAngle90CcwTest()
{
getAngleAssert(Point(-100, 0), Point(0, 0), Point(0, -100), 1.5);
}
void LinearAlg2DTest::getAngle90CwTest()
{
getAngleAssert(Point(-100, 0), Point(0, 0), Point(0, 100), .5);
}
void LinearAlg2DTest::getAngleStraightBackTest()
{
getAngleAssert(Point(-100, 0), Point(0, 0), Point(-100, 1), 0.0);
getAngleAssert(Point(-100, 0), Point(0, 0), Point(-100, -1), 2.0);
}
void LinearAlg2DTest::getAngleLeftAABTest()
{
LinearAlg2D::getAngleLeft(Point(0, 0), Point(0, 0), Point(100, 0)); //Any output is allowed. Just don't crash!
}
void LinearAlg2DTest::getAngleLeftABBTest()
{
LinearAlg2D::getAngleLeft(Point(0, 0), Point(100, 0), Point(100, 100)); //Any output is allowed. Just don't crash!
}
void LinearAlg2DTest::getAngleLeftAAATest()
{
LinearAlg2D::getAngleLeft(Point(0, 0), Point(0, 0), Point(0, 0)); //Any output is allowed. Just don't crash!
}
void LinearAlg2DTest::getAngleAssert(Point a, Point b, Point c, float actual_angle_in_half_rounds)
{
float actual_angle = actual_angle_in_half_rounds * M_PI;
float supposed_angle = LinearAlg2D::getAngleLeft(a, b, c);
std::stringstream ss;
ss << "Corner in " << a << "-" << b << "-" << c << " was computed to have an angle of " << supposed_angle << " instead of " << actual_angle << ".";
CPPUNIT_ASSERT_MESSAGE(ss.str(), std::fabs(actual_angle - supposed_angle) <= maximum_error_angle);
}
}
+33
Ver Arquivo
@@ -42,6 +42,15 @@ class LinearAlg2DTest : public CppUnit::TestFixture
CPPUNIT_TEST(getDist2FromLineSegmentDiagonal2LargeTest);
CPPUNIT_TEST(getDist2FromLineSegmentZeroNearTest);
CPPUNIT_TEST(getDist2FromLineSegmentZeroOnTest);
CPPUNIT_TEST(getAngleStraightTest);
CPPUNIT_TEST(getAngle90CcwTest);
CPPUNIT_TEST(getAngle90CwTest);
CPPUNIT_TEST(getAngle45CcwTest);
CPPUNIT_TEST(getAngleStraightBackTest);
CPPUNIT_TEST(getAngleLeftAABTest);
CPPUNIT_TEST(getAngleLeftABBTest);
CPPUNIT_TEST(getAngleLeftAAATest);
CPPUNIT_TEST_SUITE_END();
public:
@@ -90,6 +99,15 @@ public:
void getDist2FromLineSegmentDiagonal2LargeTest();
void getDist2FromLineSegmentZeroNearTest();
void getDist2FromLineSegmentZeroOnTest();
void getAngleStraightTest();
void getAngle90CcwTest();
void getAngle90CwTest();
void getAngle45CcwTest();
void getAngleStraightBackTest();
void getAngleLeftAABTest();
void getAngleLeftABBTest();
void getAngleLeftAAATest();
private:
/*!
@@ -111,6 +129,21 @@ private:
* \param actual_is_beyond Whether the point is actually beyond the line.
*/
void getDist2FromLineSegmentAssert(Point line_start,Point line_end,Point point,int64_t actual_distance2,char actual_is_beyond);
/*!
* \brief The maximum allowed error in angle measurements.
*/
static constexpr float maximum_error_angle = 1.0;
/*!
* Performs the assertion of the getAngle tests
*
* \param a the a parameter of getAngle
* \param b the b parameter of getAngle
* \param c the c parameter of getAngle
* \param actual_angle_in_half_rounds the actual angle where 0.5 equals ???
*/
void getAngleAssert(Point a, Point b, Point c, float actual_angle_in_half_rounds);
};
}
-91
Ver Arquivo
@@ -1,91 +0,0 @@
//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);
}
}
}
-73
Ver Arquivo
@@ -1,73 +0,0 @@
//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 */
+74 -6
Ver Arquivo
@@ -9,6 +9,7 @@
# * Single random value
# * All settings random
import ast #For safe function evaluation.
import sys
import subprocess
import os
@@ -87,18 +88,34 @@ class TestResults():
class Setting():
def __init__(self, key, data):
## Creates a new setting from a JSON node.
#
# Some parts of the setting may have to be evaluated as functions. For
# these, the default values of all settings are added as local variables.
#
# \param key The name of the setting.
# \param data The JSON node of the setting, containing the default value,
# the setting type, the minimum value, maximum value, the minimum warning
# value, the maximum warning value and (for enum types) the options.
# \param locals The local variables for eventual function evaluation.
def __init__(self, key, data, locals):
self._key = key
self._default = data["default"]
self._type = data["type"]
self._min_value = data.get("min_value", None)
self._max_value = data.get("max_value", None)
self._min_value_warning = data.get("min_value_warning", None)
self._max_value_warning = data.get("max_value_warning", None)
self._min_value = self._evaluateFunction(data.get("min_value", None), locals)
self._max_value = self._evaluateFunction(data.get("max_value", None), locals)
self._min_value_warning = self._evaluateFunction(data.get("min_value_warning", None), locals)
self._max_value_warning = self._evaluateFunction(data.get("max_value_warning", None), locals)
self._options = data.get("options", None)
if self._options is not None:
self._options = list(self._options.keys())
## Gets the default value of this setting, according to the source JSON.
#
# \return The default value.
def getDefault(self):
return self._default
## Return a list of possible values for this setting. This list depends on the setting type.
# For number values it contains the minimal and maximal values.
# For enums and booleans it will contain the exact possible values.
@@ -157,10 +174,42 @@ class Setting():
return random.uniform(float(min), float(max))
return random.choice(self.getSettingValues())
## Evaluates a setting value that is described as a function.
#
# Note that this function should behave EXACTLY the same as it does in
# UM/Settings/Setting.py:_createFunction. The only differences should be
# that this evaluation always uses the default values instead of the
# current profile values, and that this function directly evaluates the
# setting instead of returning a function with which to evaluate the
# setting. Also, this function doesn't need to compile the list of
# settings that this setting depends on.
#
# \param code The string to evaluate as a function of default values of
# other settings.
# \param locals The default values of other settings, as dictionary keyed
# by the setting names.
# \return The evaluated value of the setting, or None if \p code was None.
def _evaluateFunction(self, code, locals):
if not code: #The input was None. This setting value doesn't exist in the JSON.
return None
try:
tree = ast.parse(code, "eval")
compiled = compile(code, self._key, "eval")
except (SyntaxError, TypeError) as e:
print("Parse error in function (" + code + ") for setting", self._key + ":", str(e))
except IllegalMethodError as e:
print("Use of illegal method", str(e), "in function (" + code + ") for setting", self._key)
except Exception as e:
print("Exception in function (" + code + ") for setting", self._key + ":", str(e))
return eval(compiled, globals(), locals)
class EngineTest():
def __init__(self, json_filename, engine_filename, models):
self._json_filename = json_filename
self._json = json.load(open(json_filename, "r"))
self._locals = {}
self._addAllLocals() #Fills the _locals dictionary.
self._engine = engine_filename
self._models = models
self._settings = {}
@@ -174,7 +223,7 @@ class EngineTest():
def _flattenSettings(self, settings):
for key, setting in settings.items():
self._settings[key] = Setting(key, setting)
self._settings[key] = Setting(key, setting, self._locals)
if "children" in setting:
self._flattenSettings(setting["children"])
@@ -248,6 +297,25 @@ class EngineTest():
def getResults(self):
return self._test_results
## Adds all default values for all settings to the locals.
#
# The results are stored in self._locals, keyed by the setting name.
def _addAllLocals(self):
for key, data in self._json["categories"].items():
self._addLocals(data["settings"])
self._addLocals(self._json["machine_settings"])
## Adds the default values in a node of the setting tree to the locals.
#
# The results are stored in self._locals, keyed by the setting name.
#
# \param settings The JSON node of which to add the default values.
def _addLocals(self, settings):
for key, setting in settings.items():
self._locals[key] = setting["default"]
if "children" in setting:
self._addLocals(setting["children"]) #Recursively go down the tree.
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))