Comparar commits
390 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 4a13deedad | |||
| 0e4b64b8f2 | |||
| 8318f290fa | |||
| 191f699309 | |||
| 2dac16b44f | |||
| 9db735e2e5 | |||
| ee087f23de | |||
| 5878916b03 | |||
| 7e7dbf34be | |||
| c3d535d88f | |||
| 2b987a9a73 | |||
| 315f01daef | |||
| fdc756a22b | |||
| 497c969048 | |||
| e22d4d30e7 | |||
| 432761690b | |||
| 39422e6dbc | |||
| 0f9c14e42a | |||
| d9374270cf | |||
| 9f8cdf69f0 | |||
| 492e4f7b29 | |||
| b50bc938df | |||
| aa36ad175e | |||
| 0d50fe50b4 | |||
| 8bf31f771c | |||
| 634fddd908 | |||
| 7f64494dec | |||
| dffb1ef459 | |||
| 9b74c6c0a2 | |||
| b23ede94f3 | |||
| 1837d658dd | |||
| 7086762216 | |||
| bc78163d70 | |||
| 81c2091ddb | |||
| 6e3d71e39b | |||
| 35112600bc | |||
| 0e58fb960f | |||
| a36c90d076 | |||
| b72625b28f | |||
| 8ead13d2be | |||
| d30d2af4f0 | |||
| 2f46c0e473 | |||
| 34f60e6616 | |||
| 303246b39c | |||
| 3b80ac93ea | |||
| 6b7b5a7ea9 | |||
| 231eee1e46 | |||
| 81424528f3 | |||
| c78808b69d | |||
| 1c347be3be | |||
| c79c503621 | |||
| e6fb617f3f | |||
| ca1799efc6 | |||
| 749e85b15b | |||
| 82c7bfaf7a | |||
| d5df34b3cf | |||
| 59774e7f14 | |||
| bf8e73a4ae | |||
| 8b778b82be | |||
| d42b0ac9eb | |||
| cda656d43d | |||
| 8c18b2ca89 | |||
| eeb69776de | |||
| cee01abe16 | |||
| 8df1562d7b | |||
| 3bbc4a1d72 | |||
| 5a9eaa29ea | |||
| 6faeaf8c0b | |||
| f9b15a2f47 | |||
| c1eb1fdd85 | |||
| 324f424e69 | |||
| 3dfb35d73e | |||
| e72789e3cb | |||
| d6ac9e69c1 | |||
| 358d2e26c2 | |||
| e9fb973a05 | |||
| 22b86f81b7 | |||
| cf05c65061 | |||
| 5f8f0110cf | |||
| d3715e8e76 | |||
| 31e2996104 | |||
| 3ff329033a | |||
| f7d72623d2 | |||
| fee8867855 | |||
| 62a4db8632 | |||
| efb20afc76 | |||
| 683c887e53 | |||
| 4d9daccb5b | |||
| c0611904bb | |||
| cecac0fe90 | |||
| f48c858ec4 | |||
| fc4d24fb01 | |||
| 529301f950 | |||
| 9c47644e55 | |||
| 2949f89b29 | |||
| 1793961094 | |||
| 590795921e | |||
| b79c404dc3 | |||
| 0065532d6d | |||
| b89c8fd1fa | |||
| d409c4d245 | |||
| fd64b5ce60 | |||
| 5bc3b86dc4 | |||
| 9ec92fa33f | |||
| 03b654af3e | |||
| e3c03e6c04 | |||
| 33c40f3398 | |||
| c79a7f1819 | |||
| 7843a68d7c | |||
| 8f912835cf | |||
| 36de33b735 | |||
| 01884663c3 | |||
| 4c46dd37a7 | |||
| 9245a4fa41 | |||
| 4b240e8057 | |||
| fff8195d51 | |||
| e01f18c7d4 | |||
| 42891874f4 | |||
| 257d6a6635 | |||
| bec8bef455 | |||
| 4efeaa7083 | |||
| 95fd9d6685 | |||
| 6d54a31bcd | |||
| 18df36ca06 | |||
| 55f47523b2 | |||
| 4130af0ad6 | |||
| c08a0221c8 | |||
| 31f8459a0f | |||
| 4642076fdc | |||
| 13b3c715bf | |||
| 3583d71dcd | |||
| c5ab004ece | |||
| b8fe70ee74 | |||
| 6db88290c2 | |||
| be6fd8cc7c | |||
| baeb736705 | |||
| 05fa05bb5a | |||
| 5a7c2e5ef1 | |||
| c9de58ceba | |||
| 1d60079220 | |||
| 77d40fb0e9 | |||
| 72e9906bb8 | |||
| 2db37c6018 | |||
| 41b0966d26 | |||
| d5bff03a1c | |||
| 0d5cc686c9 | |||
| 3103acb7b0 | |||
| bcce1bd8a4 | |||
| cda16c4429 | |||
| 21a8afb895 | |||
| 0d7074ee8b | |||
| 8bb91cecb9 | |||
| 7aaf3b9bae | |||
| a806a27836 | |||
| d556999aed | |||
| 9da99b67fc | |||
| 4b02912ab6 | |||
| 58234e4125 | |||
| e825637f14 | |||
| 242384bd28 | |||
| b0cb94aeca | |||
| 95fc4695d2 | |||
| 1f6f847b51 | |||
| 3e1b5128bb | |||
| b88ee700fc | |||
| b43b98da25 | |||
| ecdb4f7879 | |||
| 20ef9ce1c0 | |||
| aec58f7e00 | |||
| 7dca18fe6a | |||
| dc761c2f57 | |||
| 059c97b2cd | |||
| d14e05f318 | |||
| a533559918 | |||
| 78ca299380 | |||
| 1d581c0fec | |||
| d0c58acfcf | |||
| 7b90354033 | |||
| 5c4fdfdd0b | |||
| c8051f5b37 | |||
| fa203bd976 | |||
| 4d2e544be0 | |||
| bd27011107 | |||
| fb3c99ebe0 | |||
| 2a8a86aac4 | |||
| 4fa497ee8c | |||
| 76eaeeb196 | |||
| 9cebeb770a | |||
| a0200f1548 | |||
| 83164fe1e7 | |||
| f9f162383b | |||
| 403f7515b8 | |||
| 969ed87600 | |||
| a2dccb118c | |||
| 64e5c5b8bf | |||
| 8bb2a6ba7f | |||
| da38e958ac | |||
| 081a46118c | |||
| bd006e676b | |||
| afc40e1c70 | |||
| cc6583b214 | |||
| 55fbd2ba54 | |||
| 916d26417f | |||
| 258a7e6f37 | |||
| d5f13616c2 | |||
| 54c7f942c4 | |||
| bd565ab000 | |||
| fc24ce974d | |||
| 7a7c824b0d | |||
| a4227db5b1 | |||
| 7fdc77c74c | |||
| 508b1b2933 | |||
| 387ef30ca1 | |||
| 1b6df75591 | |||
| 53ccadbf91 | |||
| 612f6cac3d | |||
| 166601492b | |||
| e0de929c5b | |||
| 7ac4738435 | |||
| 4c547b9a66 | |||
| 7ca184fb78 | |||
| 080663a653 | |||
| cc54c8be08 | |||
| c19f35ce13 | |||
| 983720cfc0 | |||
| cad745f0b5 | |||
| 2a10954df2 | |||
| 7eded0ba3c | |||
| ca963d5da0 | |||
| 7091650876 | |||
| 93485cd0df | |||
| 2d3382874a | |||
| 1e78397e18 | |||
| 6bcdd94f7e | |||
| accd28db64 | |||
| 1c0f4c42d9 | |||
| 5da1632d9f | |||
| cbf1152f56 | |||
| 2273c5aefe | |||
| 991adf19a1 | |||
| 9a4e1b52ed | |||
| 98d2786dd0 | |||
| 46c793e73d | |||
| fd4969887b | |||
| 6620a050a5 | |||
| 6325197fce | |||
| f828d44365 | |||
| 5072995a66 | |||
| a8ab0c12aa | |||
| 09c989a019 | |||
| 29564a23e0 | |||
| 1fdda3319f | |||
| be113eceb4 | |||
| 0b3b8ea33b | |||
| 0c42ff9bfa | |||
| a5bd599ec7 | |||
| 7548c41d7b | |||
| 168e041c42 | |||
| 670ae6dd8c | |||
| 20adfa751f | |||
| bf8776b112 | |||
| 1d0f3f519a | |||
| 07fef8668c | |||
| 1c06fc49fc | |||
| beb9422d9b | |||
| a8359b9a68 | |||
| 5ccfe2d1aa | |||
| 74577759b4 | |||
| 45eb026777 | |||
| aabb07fd81 | |||
| 6377ec63e1 | |||
| eab2d8e667 | |||
| 8d41003c67 | |||
| ecfae4d75c | |||
| a2208f6b69 | |||
| bacacb01dc | |||
| 94c9399f2c | |||
| 168dc3c12b | |||
| 235af65b00 | |||
| f3f3be74cc | |||
| dca0bc80b5 | |||
| c0e57622d0 | |||
| b7a8fbe798 | |||
| 06521eef8b | |||
| 47a6f0dc36 | |||
| 4353980e78 | |||
| 18ae9cf41d | |||
| 04edf35331 | |||
| ce4d34adb2 | |||
| d42be2a22c | |||
| dbcbcae2e3 | |||
| ff9cb24d99 | |||
| b8ff36651e | |||
| fc046d5978 | |||
| 2bd8657050 | |||
| 08c69ca589 | |||
| a3d17d217a | |||
| e47fcea2bf | |||
| ad3903037c | |||
| 1c16c77d56 | |||
| 883f0c7419 | |||
| bc11121a2e | |||
| d18843abe3 | |||
| ea12d310b0 | |||
| b993e4aff1 | |||
| 20701117fb | |||
| a40d48c1be | |||
| 07203c9d91 | |||
| 8d941063c1 | |||
| 471d20ff75 | |||
| b82d6f9aff | |||
| 277e1581d0 | |||
| d4b128e0f3 | |||
| eec65df83e | |||
| 78e1c3114d | |||
| e85eec54ec | |||
| dcc4d956b2 | |||
| 91249dd012 | |||
| 4de1f1abdf | |||
| 59abad0197 | |||
| 0dc7e326c9 | |||
| 3271bde77e | |||
| 7b71426839 | |||
| 27e3df2fbc | |||
| 2e915039e6 | |||
| 8262ff3ac6 | |||
| 5cc0eb24ef | |||
| dad74e1cd6 | |||
| a878a7f091 | |||
| 53a543c548 | |||
| 0366b9df71 | |||
| 9c1f74fff1 | |||
| 1e0d416a5b | |||
| 4612c69ae3 | |||
| 916e0b221d | |||
| a05d31456d | |||
| 8d82b8b943 | |||
| 948c9b7054 | |||
| 28d13b4aa1 | |||
| 0b0f85f9ff | |||
| ef55f2ea11 | |||
| 692180e185 | |||
| e25a681201 | |||
| dca1beeba2 | |||
| d10fea3ee6 | |||
| 1e93ffd83b | |||
| d41b842b39 | |||
| 59c3047543 | |||
| b53f147b14 | |||
| 1768072cec | |||
| b2b4847b85 | |||
| 3a832ef492 | |||
| 03749e98ea | |||
| 9e410e7007 | |||
| beab605308 | |||
| 9de4b7e939 | |||
| 0d7710ecd1 | |||
| fe7364c146 | |||
| 98b1fda1f1 | |||
| 151bef23c9 | |||
| b9a411df10 | |||
| 0f53a7a231 | |||
| 57ac6aa926 | |||
| 2ba03ff1dc | |||
| f59ca9c33e | |||
| 8fc6ee87ee | |||
| 48707d95f7 | |||
| 7a1a900d78 | |||
| 38c8941b59 | |||
| df47cf8b2d | |||
| f28cf53651 | |||
| 9d063d885c | |||
| 3ec63017cc | |||
| 26dfd02b26 | |||
| d3f0a06ee0 | |||
| f94c95dd97 | |||
| 07b7d84df2 | |||
| 97ee04c12b | |||
| 18c7c6bdb9 | |||
| e93770017f | |||
| 60e1f30c60 | |||
| d4844d08f2 | |||
| 30df9853e4 | |||
| 4ff00a8f73 | |||
| f5b7cadcb5 | |||
| 17ca8fce0a | |||
| dbdbec44cc | |||
| 203eb05d7c | |||
| 9e75f8c70c | |||
| 93b3d2e46e |
+16
-12
@@ -54,26 +54,23 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/gcodeExport.cpp
|
||||
src/gcodePlanner.cpp
|
||||
src/infill.cpp
|
||||
src/inset.cpp
|
||||
src/WallsComputation.cpp
|
||||
src/layerPart.cpp
|
||||
src/LayerPlanBuffer.cpp
|
||||
src/Material.cpp
|
||||
src/MaterialBase.cpp
|
||||
src/MergeInfillLines.cpp
|
||||
src/mesh.cpp
|
||||
src/MeshGroup.cpp
|
||||
src/multiVolumes.cpp
|
||||
src/pathOrderOptimizer.cpp
|
||||
src/PrimeTower.cpp
|
||||
src/Progress.cpp
|
||||
src/raft.cpp
|
||||
src/settingRegistry.cpp
|
||||
src/settings.cpp
|
||||
src/skin.cpp
|
||||
src/skirt.cpp
|
||||
src/sliceDataStorage.cpp
|
||||
src/slicer.cpp
|
||||
src/support.cpp
|
||||
src/timeEstimate.cpp
|
||||
src/TexturedMesh.cpp
|
||||
src/TextureProcessor.cpp
|
||||
src/WallsComputation.cpp
|
||||
src/wallOverlap.cpp
|
||||
src/Weaver.cpp
|
||||
src/Wireframe2gcode.cpp
|
||||
@@ -84,11 +81,17 @@ set(engine_SRCS # Except main.cpp.
|
||||
src/infill/ZigzagConnectorProcessorEndPieces.cpp
|
||||
src/infill/ZigzagConnectorProcessorNoEndPieces.cpp
|
||||
|
||||
src/slicer/LayerPart.cpp
|
||||
src/slicer/MultiVolumes.cpp
|
||||
src/slicer/SlicerLayer.cpp
|
||||
src/slicer/Slicer.cpp
|
||||
src/progress/Progress.cpp
|
||||
src/progress/ProgressStageEstimator.cpp
|
||||
|
||||
src/settings/SettingConfig.cpp
|
||||
src/settings/SettingContainer.cpp
|
||||
src/settings/SettingRegistry.cpp
|
||||
src/settings/settings.cpp
|
||||
|
||||
src/utils/AABB.cpp
|
||||
src/utils/AABB3D.cpp
|
||||
src/utils/Date.cpp
|
||||
src/utils/gettime.cpp
|
||||
src/utils/LinearAlg2D.cpp
|
||||
src/utils/logoutput.cpp
|
||||
@@ -105,6 +108,7 @@ set(engine_TEST_INFILL
|
||||
set(engine_TEST_UTILS
|
||||
BucketGrid2DTest
|
||||
LinearAlg2DTest
|
||||
PolygonUtilsTest
|
||||
)
|
||||
|
||||
# Generating ProtoBuf protocol
|
||||
|
||||
+3
-26
@@ -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 {
|
||||
}
|
||||
|
||||
+6
-1
@@ -54,11 +54,16 @@ For that one needs a settings JSON file, which can be found in the Ultimaker/Cur
|
||||
An example run for an UM2 machine looks as follows:
|
||||
* Navigate to the CuraEngine directory and execute the following
|
||||
```
|
||||
./build/CuraEngine slice -v -j ../Cura/resources/machines/dual_extrusion_printer.json -o "output/test.gcode" -e1 -s infill_line_distance=0 -e0 -l "/model_1.stl" -e1 -l "fully_filled_model.stl"
|
||||
./build/CuraEngine slice -v -j ../Cura/resources/definitions/dual_extrusion_printer.def.json -o "output/test.gcode" -e1 -s infill_line_distance=0 -e0 -l "/model_1.stl" -e1 -l "fully_filled_model.stl"
|
||||
```
|
||||
|
||||
Run `CuraEngine help` for a general description of how to use the CuraEngine tool.
|
||||
|
||||
[Set the environment variable](https://help.ubuntu.com/community/EnvironmentVariables) CURA_ENGINE_SEARCH_PATH to the appropriate paths, delimited by a colon e.g.
|
||||
```
|
||||
CURA_ENGINE_SEARCH_PATH=/path/to/Cura/resources/definitions:/user/defined/path
|
||||
```
|
||||
|
||||
Internals
|
||||
=========
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
find engine setting literals
|
||||
|
||||
|
||||
cd ~/Development/CuraEngine/output/reflection/
|
||||
|
||||
~/bin/substitute.pl y 'while(/getSetting\w+\("(\w+)"\)/gsm) { print "$1\n"; }' ../../src/ | sort | uniq > engineSettingLiterals.txt
|
||||
|
||||
|
||||
run setting inheritance reflection
|
||||
|
||||
cd ~/Development/CuraEngine
|
||||
./build/CuraEngine analyse ../Cura/resources/machines/fdmprinter.json meta/refl_ff.gv output/reflection/engineSettingLiterals.txt -piew
|
||||
|
||||
dot meta/refl_ff.gv -Tpng > meta/rafl_ff_dotted.png
|
||||
|
||||
|
||||
green block = used in engine
|
||||
red edge = inherit function only
|
||||
black edge = parent-child relation
|
||||
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 284 KiB |
@@ -1,7 +1,7 @@
|
||||
#ifndef EXTRUDER_TRAIN_H
|
||||
#define EXTRUDER_TRAIN_H
|
||||
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -13,7 +13,7 @@ public:
|
||||
int getExtruderNr() { return extruder_nr; }
|
||||
|
||||
ExtruderTrain(SettingsBaseVirtual* settings, int extruder_nr)
|
||||
: SettingsBase(settings)
|
||||
: SettingsBase(settings, std::string("extruder"))
|
||||
, extruder_nr(extruder_nr)
|
||||
{ }
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef FAN_SPEED_LAYER_TIME_H
|
||||
#define FAN_SPEED_LAYER_TIME_H
|
||||
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
+203
-171
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "FffGcodeWriter.h"
|
||||
#include "FffProcessor.h"
|
||||
#include "Progress.h"
|
||||
#include "progress/Progress.h"
|
||||
#include "wallOverlap.h"
|
||||
|
||||
namespace cura
|
||||
@@ -14,9 +14,10 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
|
||||
{
|
||||
gcode.preSetup(storage.meshgroup);
|
||||
|
||||
if (meshgroup_number == 1)
|
||||
{
|
||||
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
|
||||
{ // first meshgroup
|
||||
gcode.resetTotalPrintTimeAndFilament();
|
||||
gcode.setInitialTemps(*storage.meshgroup);
|
||||
}
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
@@ -40,7 +41,7 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
|
||||
|
||||
layer_plan_buffer.setPreheatConfig(*storage.meshgroup);
|
||||
|
||||
if (meshgroup_number == 1)
|
||||
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
|
||||
{
|
||||
processStartingCode(storage);
|
||||
}
|
||||
@@ -48,7 +49,6 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep
|
||||
{
|
||||
processNextMeshGroupCode(storage);
|
||||
}
|
||||
meshgroup_number++;
|
||||
|
||||
size_t total_layers = 0;
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
@@ -106,50 +106,40 @@ void FffGcodeWriter::setConfigCoasting(SliceDataStorage& storage)
|
||||
|
||||
void FffGcodeWriter::setConfigRetraction(SliceDataStorage& storage)
|
||||
{
|
||||
storage.retraction_config.distance = (storage.getSettingBoolean("retraction_enable"))? INT2MM(getSettingInMicrons("retraction_amount")) : 0;
|
||||
storage.retraction_config.prime_volume = getSettingInCubicMillimeters("retraction_extra_prime_amount");
|
||||
storage.retraction_config.speed = getSettingInMillimetersPerSecond("retraction_retract_speed");
|
||||
storage.retraction_config.primeSpeed = getSettingInMillimetersPerSecond("retraction_prime_speed");
|
||||
storage.retraction_config.zHop = getSettingInMicrons("retraction_hop");
|
||||
storage.retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
|
||||
storage.retraction_config.retraction_extrusion_window = INT2MM(getSettingInMicrons("retraction_extrusion_window"));
|
||||
storage.retraction_config.retraction_count_max = getSettingAsCount("retraction_count_max");
|
||||
|
||||
int extruder_count = storage.meshgroup->getExtruderCount();
|
||||
for (int extruder = 0; extruder < extruder_count; extruder++)
|
||||
{
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[extruder];
|
||||
retraction_config.distance = (train->getSettingBoolean("retraction_enable"))? INT2MM(train->getSettingInMicrons("retraction_amount")) : 0;
|
||||
retraction_config.distance = (train->getSettingBoolean("retraction_enable"))? train->getSettingInMillimeters("retraction_amount") : 0;
|
||||
retraction_config.prime_volume = train->getSettingInCubicMillimeters("retraction_extra_prime_amount");
|
||||
retraction_config.speed = train->getSettingInMillimetersPerSecond("retraction_retract_speed");
|
||||
retraction_config.primeSpeed = train->getSettingInMillimetersPerSecond("retraction_prime_speed");
|
||||
retraction_config.zHop = train->getSettingInMicrons("retraction_hop");
|
||||
retraction_config.retraction_min_travel_distance = train->getSettingInMicrons("retraction_min_travel");
|
||||
retraction_config.retraction_extrusion_window = INT2MM(train->getSettingInMicrons("retraction_extrusion_window"));
|
||||
retraction_config.retraction_extrusion_window = train->getSettingInMillimeters("retraction_extrusion_window");
|
||||
retraction_config.retraction_count_max = train->getSettingAsCount("retraction_count_max");
|
||||
}
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
mesh.retraction_config.distance = (mesh.getSettingBoolean("retraction_enable"))? INT2MM(mesh.getSettingInMicrons("retraction_amount")) : 0;
|
||||
mesh.retraction_config.distance = (mesh.getSettingBoolean("retraction_enable"))? mesh.getSettingInMillimeters("retraction_amount") : 0;
|
||||
mesh.retraction_config.prime_volume = mesh.getSettingInCubicMillimeters("retraction_extra_prime_amount");
|
||||
mesh.retraction_config.speed = mesh.getSettingInMillimetersPerSecond("retraction_retract_speed");
|
||||
mesh.retraction_config.primeSpeed = mesh.getSettingInMillimetersPerSecond("retraction_prime_speed");
|
||||
mesh.retraction_config.zHop = mesh.getSettingInMicrons("retraction_hop");
|
||||
mesh.retraction_config.retraction_min_travel_distance = mesh.getSettingInMicrons("retraction_min_travel");
|
||||
mesh.retraction_config.retraction_extrusion_window = INT2MM(mesh.getSettingInMicrons("retraction_extrusion_window"));
|
||||
mesh.retraction_config.retraction_extrusion_window = mesh.getSettingInMillimeters("retraction_extrusion_window");
|
||||
mesh.retraction_config.retraction_count_max = mesh.getSettingAsCount("retraction_count_max");
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::initConfigs(SliceDataStorage& storage)
|
||||
{
|
||||
storage.travel_config.init(getSettingInMillimetersPerSecond("speed_travel"), 0, 0);
|
||||
|
||||
for (int extruder = 0; extruder < storage.meshgroup->getExtruderCount(); extruder++)
|
||||
{ // skirt
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder);
|
||||
storage.skirt_config[extruder].init(train->getSettingInMillimetersPerSecond("skirt_speed"), train->getSettingInMicrons("skirt_line_width"), train->getSettingInPercentage("material_flow"));
|
||||
storage.travel_config_per_extruder[extruder].init(train->getSettingInMillimetersPerSecond("speed_travel"), 0, 0);
|
||||
}
|
||||
|
||||
{ // support
|
||||
@@ -178,17 +168,13 @@ 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)
|
||||
|
||||
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
|
||||
|
||||
if (gcode.getFlavor() != EGCodeFlavor::ULTIGCODE && gcode.getFlavor() != EGCodeFlavor::GRIFFIN)
|
||||
{
|
||||
if (getSettingBoolean("material_bed_temp_prepend"))
|
||||
{
|
||||
@@ -200,29 +186,24 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
|
||||
if (getSettingBoolean("material_print_temp_prepend"))
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
for (int extruder_nr = 0; extruder_nr < storage.getSettingAsCount("extruder_count"); extruder_nr++)
|
||||
{
|
||||
if (mesh.getSettingInDegreeCelsius("material_print_temperature") > 0)
|
||||
{
|
||||
gcode.writeTemperatureCommand(mesh.getSettingAsIndex("extruder_nr"), mesh.getSettingInDegreeCelsius("material_print_temperature"));
|
||||
}
|
||||
double print_temp = storage.meshgroup->getExtruderTrain(extruder_nr)->getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(extruder_nr, print_temp);
|
||||
}
|
||||
if (getSettingBoolean("material_print_temp_wait"))
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
for (int extruder_nr = 0; extruder_nr < storage.getSettingAsCount("extruder_count"); extruder_nr++)
|
||||
{
|
||||
if (mesh.getSettingInDegreeCelsius("material_print_temperature") > 0)
|
||||
{
|
||||
gcode.writeTemperatureCommand(mesh.getSettingAsIndex("extruder_nr"), mesh.getSettingInDegreeCelsius("material_print_temperature"), true);
|
||||
}
|
||||
double print_temp = storage.meshgroup->getExtruderTrain(extruder_nr)->getSettingInDegreeCelsius("material_print_temperature");
|
||||
gcode.writeTemperatureCommand(extruder_nr, print_temp, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gcode.writeCode(getSettingString("machine_start_gcode").c_str());
|
||||
|
||||
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
|
||||
if (gcode.getFlavor() == EGCodeFlavor::BFB)
|
||||
{
|
||||
gcode.writeComment("enable auto-retraction");
|
||||
@@ -230,6 +211,20 @@ void FffGcodeWriter::processStartingCode(SliceDataStorage& storage)
|
||||
tmp << "M227 S" << (getSettingInMicrons("retraction_amount") * 2560 / 1000) << " P" << (getSettingInMicrons("retraction_amount") * 2560 / 1000);
|
||||
gcode.writeLine(tmp.str().c_str());
|
||||
}
|
||||
else if (gcode.getFlavor() == EGCodeFlavor::GRIFFIN)
|
||||
{ // initialize extruder trains
|
||||
gcode.writeCode("T0"); // Toolhead already assumed to be at T0, but writing it just to be safe...
|
||||
gcode.writeCode("G92 E0"); // E-value already assumed to be at E0, but writing it just to be safe...
|
||||
// G1 X175 Y6 Z20 F9000
|
||||
gcode.writeMove(FPoint3(175, 6, 2).toPoint3(), storage.meshgroup->getExtruderTrain(0)->getSettingInMillimetersPerSecond("speed_travel"), 0.0);
|
||||
gcode.writePrimeTrain();
|
||||
gcode.switchExtruder(1);
|
||||
// G1 X180 Y6 Z20 F9000
|
||||
gcode.writeMove(FPoint3(198, 6, 2).toPoint3(), storage.meshgroup->getExtruderTrain(1)->getSettingInMillimetersPerSecond("speed_travel"), 0.0);
|
||||
gcode.writeTemperatureCommand(1, storage.meshgroup->getExtruderTrain(1)->getSettingInDegreeCelsius("material_print_temperature"), true); // TODO: this is a hack job which should get fixed as soon as we prime the first time we need to
|
||||
gcode.writePrimeTrain();
|
||||
gcode.writeTemperatureCommand(1, storage.meshgroup->getExtruderTrain(1)->getSettingInDegreeCelsius("material_standby_temperature"), false); // TODO: this is a hack job which should get fixed as soon as we prime the first time we need to
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processNextMeshGroupCode(SliceDataStorage& storage)
|
||||
@@ -237,9 +232,9 @@ void FffGcodeWriter::processNextMeshGroupCode(SliceDataStorage& storage)
|
||||
gcode.writeFanCommand(0);
|
||||
gcode.resetExtrusionValue();
|
||||
gcode.setZ(max_object_height + 5000);
|
||||
gcode.writeMove(gcode.getPositionXY(), getSettingInMillimetersPerSecond("speed_travel"), 0);
|
||||
gcode.writeMove(gcode.getPositionXY(), storage.meshgroup->getExtruderTrain(gcode.getExtruderNr())->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);
|
||||
gcode.writeMove(last_position_planned, storage.meshgroup->getExtruderTrain(gcode.getExtruderNr())->getSettingInMillimetersPerSecond("speed_travel"), 0);
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_layers)
|
||||
@@ -247,7 +242,7 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
int extruder_nr = getSettingAsIndex("adhesion_extruder_nr");
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(extruder_nr);
|
||||
|
||||
bool retraction_combing = true;
|
||||
CombingMode combing_mode = storage.getSettingAsCombingMode("retraction_combing");
|
||||
|
||||
int n_raft_surface_layers = train->getSettingAsCount("raft_surface_layers");
|
||||
|
||||
@@ -265,21 +260,18 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
}
|
||||
|
||||
// some infill config for all lines infill generation below
|
||||
Polygons* in_between = nullptr;
|
||||
int offset_from_poly_outline = 0;
|
||||
bool avoidOverlappingPerimeters = false;
|
||||
double fill_overlap = 0; // raft line shouldn't be expanded - there is no boundary polygon printed
|
||||
Polygons raft_polygons; // should remain empty, since we only have the lines pattern for the raft...
|
||||
|
||||
{ // raft base layer
|
||||
|
||||
int layer_nr = -n_raft_surface_layers - 2;
|
||||
int layer_height = getSettingInMicrons("raft_base_thickness");
|
||||
int layer_height = train->getSettingInMicrons("raft_base_thickness");
|
||||
z += layer_height;
|
||||
int64_t comb_offset = train->getSettingInMicrons("raft_base_line_spacing");
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
if (getSettingAsIndex("adhesion_extruder_nr") > 0)
|
||||
{
|
||||
gcode_layer.setExtruder(extruder_nr);
|
||||
@@ -292,12 +284,13 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
|
||||
Polygons raftLines;
|
||||
double fill_angle = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_base_config.getLineWidth(), train->getSettingInMicrons("raft_base_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raftLines, in_between);
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, storage.raft_base_config.getLineWidth(), train->getSettingInMicrons("raft_base_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raftLines);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_base_config, SpaceFillType::Lines);
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
|
||||
|
||||
gcode_layer.setFanSpeed(train->getSettingInPercentage("raft_base_fan_speed"));
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
@@ -308,9 +301,8 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
int layer_height = train->getSettingInMicrons("raft_interface_thickness");
|
||||
z += layer_height;
|
||||
int64_t comb_offset = train->getSettingInMicrons("raft_interface_line_spacing");
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, fan_speed_layer_time_settings, retraction_combing, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
|
||||
@@ -319,12 +311,13 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
Polygons raftLines;
|
||||
int offset_from_poly_outline = 0;
|
||||
double fill_angle = train->getSettingAsCount("raft_surface_layers") > 0 ? 45 : 90;
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_interface_config.getLineWidth(), train->getSettingInMicrons("raft_interface_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raftLines, in_between);
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, storage.raft_interface_config.getLineWidth(), train->getSettingInMicrons("raft_interface_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raftLines);
|
||||
gcode_layer.addLinesByOptimizer(raftLines, &storage.raft_interface_config, SpaceFillType::Lines);
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
|
||||
|
||||
gcode_layer.setFanSpeed(train->getSettingInPercentage("raft_interface_fan_speed"));
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
@@ -337,9 +330,8 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
int layer_nr = -n_raft_surface_layers + raftSurfaceLayer - 1;
|
||||
z += layer_height;
|
||||
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"));
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_height, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings, combing_mode, comb_offset, train->getSettingBoolean("travel_avoid_other_parts"), train->getSettingInMicrons("travel_avoid_distance"));
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, z, layer_height);
|
||||
@@ -348,12 +340,13 @@ void FffGcodeWriter::processRaft(SliceDataStorage& storage, unsigned int total_l
|
||||
Polygons raft_lines;
|
||||
int offset_from_poly_outline = 0;
|
||||
double fill_angle = 90 * raftSurfaceLayer;
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, avoidOverlappingPerimeters, storage.raft_surface_config.getLineWidth(), train->getSettingInMicrons("raft_surface_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raft_lines, in_between);
|
||||
Infill infill_comp(EFillMethod::LINES, storage.raftOutline, offset_from_poly_outline, storage.raft_surface_config.getLineWidth(), train->getSettingInMicrons("raft_surface_line_spacing"), fill_overlap, fill_angle);
|
||||
infill_comp.generate(raft_polygons, raft_lines);
|
||||
gcode_layer.addLinesByOptimizer(raft_lines, &storage.raft_surface_config, SpaceFillType::Lines);
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
|
||||
|
||||
gcode_layer.setFanSpeed(train->getSettingInPercentage("raft_surface_fan_speed"));
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
@@ -370,24 +363,36 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_
|
||||
layer_thickness = getSettingInMicrons("layer_height_0");
|
||||
}
|
||||
|
||||
int max_inner_wall_width = 0;
|
||||
bool avoid_other_parts = false;
|
||||
int avoid_distance = 10; // stub
|
||||
std::vector<bool> extruders_used = storage.getExtrudersUsed(layer_nr);
|
||||
for (int extr_nr = 0; extr_nr < storage.meshgroup->getExtruderCount(); extr_nr++)
|
||||
{
|
||||
if (extruders_used[extr_nr])
|
||||
{
|
||||
ExtruderTrain* extr = storage.meshgroup->getExtruderTrain(extr_nr);
|
||||
max_inner_wall_width = std::max(max_inner_wall_width, extr->getSettingInMicrons((extr->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"));
|
||||
|
||||
if (extr->getSettingBoolean("travel_avoid_other_parts"))
|
||||
{
|
||||
avoid_other_parts = true;
|
||||
avoid_distance = std::max(avoid_distance, extr->getSettingInMicrons("travel_avoid_distance"));
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
|
||||
int max_inner_wall_width = 0;
|
||||
for (SettingsBaseVirtual& mesh_settings : storage.meshes)
|
||||
{
|
||||
max_inner_wall_width = std::max(max_inner_wall_width, mesh_settings.getSettingInMicrons((mesh_settings.getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"));
|
||||
}
|
||||
int64_t comb_offset_from_outlines = max_inner_wall_width;
|
||||
|
||||
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"));
|
||||
|
||||
GCodePlanner& gcode_layer = layer_plan_buffer.emplace_back(storage, layer_nr, z, layer_thickness, last_position_planned, current_extruder_planned, is_inside_mesh_layer_part, fan_speed_layer_time_settings, getSettingAsCombingMode("retraction_combing"), comb_offset_from_outlines, avoid_other_parts, avoid_distance);
|
||||
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
{ // process the skirt of the starting extruder
|
||||
int start_extruder = 0; // TODO: make settable
|
||||
gcode_layer.setExtruder(start_extruder);
|
||||
processSkirt(storage, gcode_layer, start_extruder);
|
||||
@@ -402,25 +407,31 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_
|
||||
|
||||
//Figure out in which order to print the meshes, do this by looking at the current extruder and preferer the meshes that use that extruder.
|
||||
std::vector<unsigned int> mesh_order = calculateMeshOrder(storage, gcode_layer.getExtruder());
|
||||
gcode_layer.setIsInside(true);
|
||||
for(unsigned int mesh_idx : mesh_order)
|
||||
{
|
||||
SliceMeshStorage* mesh = &storage.meshes[mesh_idx];
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
{
|
||||
gcode_layer.setIsInside(false);
|
||||
addMeshLayerToGCode_meshSurfaceMode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.setIsInside(true); // needed when the last mesh was spiralized
|
||||
addMeshLayerToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
}
|
||||
gcode_layer.setIsInside(false);
|
||||
|
||||
addSupportToGCode(storage, gcode_layer, layer_nr, extruder_nr_before, false);
|
||||
|
||||
if (layer_nr == 0)
|
||||
{ // add skirt for all extruders which haven't primed the skirt yet
|
||||
for (int extruder_nr = 0; extruder_nr < storage.meshgroup->getExtruderCount(); extruder_nr++)
|
||||
{
|
||||
if (!skirt_is_processed[extruder_nr])
|
||||
{
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, extruder_nr);
|
||||
}
|
||||
}
|
||||
}
|
||||
{ // add prime tower if it hasn't already been added
|
||||
// print the prime tower if it hasn't been printed yet
|
||||
int prev_extruder = gcode_layer.getExtruder(); // most likely the same extruder as we are extruding with now
|
||||
@@ -429,18 +440,24 @@ void FffGcodeWriter::processLayer(SliceDataStorage& storage, unsigned int layer_
|
||||
|
||||
last_position_planned = gcode_layer.getLastPosition();
|
||||
current_extruder_planned = gcode_layer.getExtruder();
|
||||
is_inside_mesh_layer_part = gcode_layer.getIsInsideMesh();
|
||||
|
||||
gcode_layer.processFanSpeedAndMinimalLayerTime();
|
||||
}
|
||||
|
||||
void FffGcodeWriter::processSkirt(SliceDataStorage& storage, GCodePlanner& gcode_layer, unsigned int extruder_nr)
|
||||
{
|
||||
gcode_layer.setIsInside(false);
|
||||
Polygons& skirt = storage.skirt[extruder_nr];
|
||||
if (skirt.size() > 0)
|
||||
if (skirt_is_processed[extruder_nr])
|
||||
{
|
||||
gcode_layer.addTravel(skirt[skirt.size()-1].closestPointTo(gcode_layer.getLastPosition()));
|
||||
return;
|
||||
}
|
||||
Polygons& skirt = storage.skirt[extruder_nr];
|
||||
skirt_is_processed[extruder_nr] = true;
|
||||
if (skirt.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
gcode_layer.addTravel(skirt[skirt.size()-1].closestPointTo(gcode_layer.getLastPosition()));
|
||||
gcode_layer.addPolygonsByOptimizer(skirt, &storage.skirt_config[extruder_nr]);
|
||||
|
||||
}
|
||||
@@ -449,7 +466,6 @@ void FffGcodeWriter::processOozeShield(SliceDataStorage& storage, GCodePlanner&
|
||||
{
|
||||
if (storage.oozeShield.size() > 0)
|
||||
{
|
||||
gcode_layer.setIsInside(false);
|
||||
gcode_layer.addPolygonsByOptimizer(storage.oozeShield[layer_nr], &storage.skirt_config[0]); // TODO: skirt config idx should correspond to ooze shield extruder nr
|
||||
}
|
||||
}
|
||||
@@ -472,7 +488,6 @@ void FffGcodeWriter::processDraftShield(SliceDataStorage& storage, GCodePlanner&
|
||||
return;
|
||||
}
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, &storage.skirt_config[0]); // TODO: skirt config idx should correspond to draft shield extruder nr
|
||||
}
|
||||
|
||||
@@ -521,11 +536,10 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(SliceDataStorage& stora
|
||||
{
|
||||
polygons.add(layer->parts[partNr].outline);
|
||||
}
|
||||
if (mesh->getSettingBoolean("magic_spiralize"))
|
||||
mesh->inset0_config.spiralize = true;
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(polygons, &mesh->inset0_config);
|
||||
|
||||
EZSeamType z_seam_type = mesh->getSettingAsZSeamType("z_seam_type");
|
||||
gcode_layer.addPolygonsByOptimizer(polygons, &mesh->inset0_config, nullptr, z_seam_type, mesh->getSettingBoolean("magic_spiralize"));
|
||||
|
||||
addMeshOpenPolyLinesToGCode(storage, mesh, gcode_layer, layer_nr);
|
||||
}
|
||||
|
||||
@@ -561,19 +575,26 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int extruder_nr = mesh->getSettingAsIndex("extruder_nr");
|
||||
|
||||
{ // TODO: only do this for dual color texture
|
||||
if (layer_nr % 2 == 0)
|
||||
if (mesh->getSettingAsCount("wall_line_count") > 0)
|
||||
{ // don't switch extruder if there's nothing to print
|
||||
bool empty = true;
|
||||
for (SliceLayerPart& part : layer->parts)
|
||||
{
|
||||
extruder_nr = 1 - extruder_nr;
|
||||
if (part.insets.size() > 0)
|
||||
{
|
||||
empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, extruder_nr);
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, mesh->getSettingAsIndex("extruder_nr"));
|
||||
|
||||
|
||||
EZSeamType z_seam_type = mesh->getSettingAsZSeamType("z_seam_type");
|
||||
PathOrderOptimizer part_order_optimizer(last_position_planned, z_seam_type);
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
@@ -592,16 +613,18 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
int infill_angle = 45;
|
||||
if ((infill_pattern == EFillMethod::LINES || infill_pattern == EFillMethod::ZIG_ZAG))
|
||||
{
|
||||
unsigned int combined_infill_layers = mesh->getSettingInMicrons("infill_sparse_thickness") / std::max(mesh->getSettingInMicrons("layer_height"), 1);
|
||||
if ((combined_infill_layers & 1 && layer_nr & 1) || (!(combined_infill_layers & 1) && (layer_nr / 2) & 1))
|
||||
{ // odd combine count and odd, or even combine count and switch direction every two layers
|
||||
unsigned int combined_infill_layers = std::max(1, mesh->getSettingInMicrons("infill_sparse_thickness") / std::max(getSettingInMicrons("layer_height"), 1));
|
||||
if ((layer_nr / combined_infill_layers) & 1)
|
||||
{ // switch every [combined_infill_layers] layers
|
||||
infill_angle += 90;
|
||||
}
|
||||
}
|
||||
int infill_line_width = mesh->infill_config[0].getLineWidth();
|
||||
|
||||
int infill_line_distance = mesh->getSettingInMicrons("infill_line_distance");
|
||||
int infill_overlap = mesh->getSettingInMicrons("infill_overlap");
|
||||
int infill_overlap = mesh->getSettingInMicrons("infill_overlap_mm");
|
||||
|
||||
gcode_layer.setIsInside(true); // going to print inside stuff below
|
||||
|
||||
if (mesh->getSettingBoolean("infill_before_walls"))
|
||||
{
|
||||
@@ -626,7 +649,7 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
if (skin_alternate_rotation && ( layer_nr / 2 ) & 1)
|
||||
skin_angle -= 45;
|
||||
|
||||
int64_t skin_overlap = infill_overlap;
|
||||
int64_t skin_overlap = mesh->getSettingInMicrons("skin_overlap_mm");
|
||||
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.
|
||||
@@ -634,6 +657,8 @@ void FffGcodeWriter::addMeshLayerToGCode(SliceDataStorage& storage, SliceMeshSto
|
||||
{
|
||||
gcode_layer.moveInsideCombBoundary(mesh->getSettingInMicrons((mesh->getSettingAsCount("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0") * 1);
|
||||
}
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
}
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
{
|
||||
@@ -650,13 +675,13 @@ void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMes
|
||||
if (infill_line_distance > 0)
|
||||
{
|
||||
//Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers)
|
||||
for(unsigned int n=1; n<part.infill_area.size(); n++)
|
||||
for(unsigned int n=1; n<part.infill_area_per_combine.size(); n++)
|
||||
{
|
||||
EFillMethod infill_pattern = mesh->getSettingAsFillMethod("infill_pattern");
|
||||
Infill infill_comp(infill_pattern, part.infill_area[n], 0, false, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
|
||||
Infill infill_comp(infill_pattern, part.infill_area_per_combine[n], 0, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
|
||||
Polygons infill_polygons;
|
||||
Polygons infill_lines;
|
||||
infill_comp.generate(infill_polygons, infill_lines, nullptr);
|
||||
infill_comp.generate(infill_polygons, infill_lines);
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[n]);
|
||||
gcode_layer.addLinesByOptimizer(infill_lines, &mesh->infill_config[n], (infill_pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
@@ -666,7 +691,7 @@ void FffGcodeWriter::processMultiLayerInfill(GCodePlanner& gcode_layer, SliceMes
|
||||
void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_line_distance, int infill_overlap, int infill_angle, int extrusion_width)
|
||||
{
|
||||
|
||||
if (infill_line_distance == 0 || part.infill_area.size() == 0)
|
||||
if (infill_line_distance == 0 || part.infill_area_per_combine.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -676,8 +701,8 @@ void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMe
|
||||
Polygons infill_lines;
|
||||
|
||||
EFillMethod pattern = mesh->getSettingAsFillMethod("infill_pattern");
|
||||
Infill infill_comp(pattern, part.infill_area[0], 0, false, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
|
||||
infill_comp.generate(infill_polygons, infill_lines, nullptr);
|
||||
Infill infill_comp(pattern, part.infill_area_per_combine[0], 0, extrusion_width, infill_line_distance, infill_overlap, infill_angle, false, false);
|
||||
infill_comp.generate(infill_polygons, infill_lines);
|
||||
gcode_layer.addPolygonsByOptimizer(infill_polygons, &mesh->infill_config[0]);
|
||||
if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
@@ -691,41 +716,56 @@ void FffGcodeWriter::processSingleLayerInfill(GCodePlanner& gcode_layer, SliceMe
|
||||
|
||||
void FffGcodeWriter::processInsets(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, EZSeamType z_seam_type)
|
||||
{
|
||||
bool compensate_overlap = mesh->getSettingBoolean("travel_compensate_overlapping_walls_enabled");
|
||||
bool compensate_overlap_0 = mesh->getSettingBoolean("travel_compensate_overlapping_walls_0_enabled");
|
||||
bool compensate_overlap_x = mesh->getSettingBoolean("travel_compensate_overlapping_walls_x_enabled");
|
||||
if (mesh->getSettingAsCount("wall_line_count") > 0)
|
||||
{
|
||||
bool spiralize = false;
|
||||
if (mesh->getSettingBoolean("magic_spiralize"))
|
||||
{
|
||||
if (static_cast<int>(layer_nr) >= mesh->getSettingAsCount("bottom_layers"))
|
||||
mesh->inset0_config.spiralize = true;
|
||||
{
|
||||
spiralize = true;
|
||||
}
|
||||
if (static_cast<int>(layer_nr) == mesh->getSettingAsCount("bottom_layers") && part.insets.size() > 0)
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->insetX_config);
|
||||
{ // on the last normal layer first make the outer wall normally and then start a second outer wall from the same hight, but gradually moving upward
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->insetX_config, nullptr, EZSeamType::SHORTEST, false);
|
||||
}
|
||||
}
|
||||
for(int inset_number=part.insets.size()-1; inset_number>-1; inset_number--)
|
||||
{
|
||||
if (inset_number == 0)
|
||||
{
|
||||
if (!compensate_overlap)
|
||||
if (!compensate_overlap_0)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->inset0_config, nullptr, z_seam_type);
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[0], &mesh->inset0_config, nullptr, z_seam_type, spiralize);
|
||||
}
|
||||
else
|
||||
{
|
||||
Polygons& outer_wall = part.insets[0];
|
||||
WallOverlapComputation wall_overlap_computation(outer_wall, mesh->getSettingInMicrons("wall_line_width_0"));
|
||||
gcode_layer.addPolygonsByOptimizer(outer_wall, &mesh->inset0_config, &wall_overlap_computation, z_seam_type);
|
||||
gcode_layer.addPolygonsByOptimizer(outer_wall, &mesh->inset0_config, &wall_overlap_computation, z_seam_type, spiralize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[inset_number], &mesh->insetX_config);
|
||||
if (!compensate_overlap_x)
|
||||
{
|
||||
gcode_layer.addPolygonsByOptimizer(part.insets[inset_number], &mesh->insetX_config);
|
||||
}
|
||||
else
|
||||
{
|
||||
Polygons& outer_wall = part.insets[inset_number];
|
||||
WallOverlapComputation wall_overlap_computation(outer_wall, mesh->getSettingInMicrons("wall_line_width_x"));
|
||||
gcode_layer.addPolygonsByOptimizer(outer_wall, &mesh->insetX_config, &wall_overlap_computation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int infill_overlap, int infill_angle, int extrusion_width)
|
||||
void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* mesh, SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int skin_angle, int extrusion_width)
|
||||
{
|
||||
for(SkinPart& skin_part : part.skin_parts) // TODO: optimize parts order
|
||||
{
|
||||
@@ -752,17 +792,7 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
{
|
||||
inner_skin_outline = &skin_part.insets.back();
|
||||
offset_from_inner_skin_outline = -extrusion_width/2;
|
||||
if (mesh->getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") != FillPerimeterGapMode::NOWHERE)
|
||||
{
|
||||
Polygons result_polygons; // should remain empty, since we're only allowing for lines infill
|
||||
Polygons* in_between = nullptr;
|
||||
bool avoidOverlappingPerimeters = false;
|
||||
int line_distance = extrusion_width;
|
||||
int outline_offset = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, skin_part.perimeterGaps, outline_offset, avoidOverlappingPerimeters, extrusion_width, line_distance, infill_overlap, infill_angle);
|
||||
infill_comp.generate(result_polygons, skin_lines, in_between);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inner_skin_outline == nullptr)
|
||||
@@ -770,8 +800,8 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
inner_skin_outline = &skin_part.outline;
|
||||
}
|
||||
|
||||
Infill infill_comp(pattern, *inner_skin_outline, offset_from_inner_skin_outline, mesh->getSettingBoolean("remove_overlapping_walls_x_enabled"), extrusion_width, extrusion_width, infill_overlap, infill_angle, false, false);
|
||||
infill_comp.generate(skin_polygons, skin_lines, &part.perimeterGaps);
|
||||
Infill infill_comp(pattern, *inner_skin_outline, offset_from_inner_skin_outline, extrusion_width, extrusion_width, skin_overlap, skin_angle, false, false);
|
||||
infill_comp.generate(skin_polygons, skin_lines);
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(skin_polygons, &mesh->skin_config);
|
||||
|
||||
@@ -784,21 +814,6 @@ void FffGcodeWriter::processSkin(GCodePlanner& gcode_layer, SliceMeshStorage* me
|
||||
gcode_layer.addLinesByOptimizer(skin_lines, &mesh->skin_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
}
|
||||
|
||||
// handle gaps between perimeters etc.
|
||||
if (mesh->getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") != FillPerimeterGapMode::NOWHERE)
|
||||
{
|
||||
Polygons perimeter_gap_lines;
|
||||
Polygons result_polygons; // should remain empty, since we're only allowing for lines infill
|
||||
Polygons* in_between = nullptr;
|
||||
bool avoidOverlappingPerimeters = false;
|
||||
int line_distance = extrusion_width;
|
||||
int outline_offset = 0;
|
||||
Infill infill_comp(EFillMethod::LINES, part.perimeterGaps, outline_offset, avoidOverlappingPerimeters, extrusion_width, line_distance, infill_overlap, infill_angle);
|
||||
infill_comp.generate(result_polygons, perimeter_gap_lines, in_between);
|
||||
|
||||
gcode_layer.addLinesByOptimizer(perimeter_gap_lines, &mesh->skin_config, SpaceFillType::Lines, mesh->getSettingInMicrons("infill_wipe_dist"));
|
||||
}
|
||||
}
|
||||
|
||||
void FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner& gcode_layer, int layer_nr, int extruder_nr_before, bool before_rest)
|
||||
@@ -816,11 +831,15 @@ void FffGcodeWriter::addSupportToGCode(SliceDataStorage& storage, GCodePlanner&
|
||||
if (print_support_before_rest != before_rest)
|
||||
return;
|
||||
|
||||
gcode_layer.setIsInside(false);
|
||||
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
|
||||
if (support_layer.roofs.size() == 0 && support_layer.supportAreas.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int current_extruder_nr = gcode_layer.getExtruder();
|
||||
|
||||
if (storage.support.supportLayers[layer_nr].roofs.size() > 0)
|
||||
if (support_layer.roofs.size() > 0)
|
||||
{
|
||||
if (support_roof_extruder_nr != support_infill_extruder_nr && support_roof_extruder_nr == current_extruder_nr)
|
||||
{
|
||||
@@ -852,14 +871,11 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
int extrusion_width = storage.support_config.getLineWidth();
|
||||
EFillMethod support_pattern = getSettingAsFillMethod("support_pattern");
|
||||
if (layer_nr == 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG)) { support_pattern = EFillMethod::GRID; }
|
||||
|
||||
|
||||
int support_infill_extruder_nr = (layer_nr == 0)? getSettingAsIndex("support_extruder_nr_layer_0") : getSettingAsIndex("support_infill_extruder_nr");
|
||||
|
||||
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_infill_extruder_nr);
|
||||
|
||||
|
||||
Polygons& support = storage.support.supportLayers[layer_nr].supportAreas;
|
||||
|
||||
|
||||
std::vector<PolygonsPart> support_islands = support.splitIntoParts();
|
||||
|
||||
PathOrderOptimizer island_order_optimizer(gcode_layer.getLastPosition());
|
||||
@@ -873,25 +889,30 @@ void FffGcodeWriter::addSupportInfillToGCode(SliceDataStorage& storage, GCodePla
|
||||
{
|
||||
PolygonsPart& island = support_islands[island_order_optimizer.polyOrder[n]];
|
||||
|
||||
int infill_overlap = 0; // support infill should not be expanded outward
|
||||
int support_infill_overlap = 0; // support infill should not be expanded outward
|
||||
|
||||
int offset_from_outline = 0;
|
||||
bool remove_overlapping_perimeters = false;
|
||||
if (support_pattern == EFillMethod::GRID || support_pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
Polygons boundary;
|
||||
PolygonUtils::offsetSafe(island, -extrusion_width / 2, extrusion_width, boundary, remove_overlapping_perimeters);
|
||||
gcode_layer.addPolygonsByOptimizer(boundary, &storage.support_config);
|
||||
Polygons boundary = island.offset(-extrusion_width / 2);
|
||||
if (boundary.size() > 0)
|
||||
{
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_infill_extruder_nr); // only switch extruder if we're sure we're going to switch
|
||||
gcode_layer.addPolygonsByOptimizer(boundary, &storage.support_config);
|
||||
}
|
||||
offset_from_outline = -extrusion_width;
|
||||
infill_overlap = storage.meshgroup->getExtruderTrain(support_infill_extruder_nr)->getSettingInMicrons("infill_overlap"); // support lines area should be expanded outward to overlap with the boundary polygon
|
||||
support_infill_overlap = storage.meshgroup->getExtruderTrain(support_infill_extruder_nr)->getSettingInMicrons("infill_overlap_mm"); // support lines area should be expanded outward to overlap with the boundary polygon
|
||||
}
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, remove_overlapping_perimeters, extrusion_width, support_line_distance, infill_overlap, 0, getSettingBoolean("support_connect_zigzags"), true);
|
||||
Infill infill_comp(support_pattern, island, offset_from_outline, extrusion_width, support_line_distance, support_infill_overlap, 0, getSettingBoolean("support_connect_zigzags"), true);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines, nullptr);
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_config, (support_pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
infill_comp.generate(support_polygons, support_lines);
|
||||
if (support_lines.size() > 0 || support_polygons.size() > 0)
|
||||
{
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, support_infill_extruder_nr); // only switch extruder if we're sure we're going to switch
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_config, (support_pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -910,12 +931,21 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
|
||||
int roof_extruder_nr = getSettingAsIndex("support_roof_extruder_nr");
|
||||
setExtruder_addPrime(storage, gcode_layer, layer_nr, roof_extruder_nr);
|
||||
|
||||
bool all_roofs_are_low = true;
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (mesh.getSettingInMicrons("support_roof_height") >= 2 * getSettingInMicrons("layer_height"))
|
||||
{
|
||||
all_roofs_are_low = false;
|
||||
}
|
||||
}
|
||||
|
||||
double fillAngle;
|
||||
if (pattern == EFillMethod::CONCENTRIC)
|
||||
{
|
||||
fillAngle = 0;
|
||||
}
|
||||
else if (getSettingInMicrons("support_roof_height") < 2 * getSettingInMicrons("layer_height") || pattern == EFillMethod::TRIANGLES)
|
||||
else if (all_roofs_are_low || pattern == EFillMethod::TRIANGLES)
|
||||
{
|
||||
fillAngle = 90; // perpendicular to support lines
|
||||
}
|
||||
@@ -923,13 +953,13 @@ void FffGcodeWriter::addSupportRoofsToGCode(SliceDataStorage& storage, GCodePlan
|
||||
{
|
||||
fillAngle = 45 + (layer_nr % 2) * 90; // alternate between the two kinds of diagonal: / and \ .
|
||||
}
|
||||
int infill_overlap = 0; // the roofs should never be expanded outwards
|
||||
int support_skin_overlap = 0; // the roofs should never be expanded outwards
|
||||
int outline_offset = 0;
|
||||
|
||||
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].roofs, outline_offset, false, storage.support_roof_config.getLineWidth(), support_line_distance, infill_overlap, fillAngle, false, true);
|
||||
Infill infill_comp(pattern, storage.support.supportLayers[layer_nr].roofs, outline_offset, storage.support_roof_config.getLineWidth(), support_line_distance, support_skin_overlap, fillAngle, false, true);
|
||||
Polygons support_polygons;
|
||||
Polygons support_lines;
|
||||
infill_comp.generate(support_polygons, support_lines, nullptr);
|
||||
infill_comp.generate(support_polygons, support_lines);
|
||||
|
||||
gcode_layer.addPolygonsByOptimizer(support_polygons, &storage.support_roof_config);
|
||||
gcode_layer.addLinesByOptimizer(support_lines, &storage.support_roof_config, (pattern == EFillMethod::ZIG_ZAG)? SpaceFillType::PolyLines : SpaceFillType::Lines);
|
||||
@@ -946,7 +976,7 @@ void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanne
|
||||
|
||||
if (extruder_changed)
|
||||
{
|
||||
if (layer_nr == 0)
|
||||
if (layer_nr == 0 && !skirt_is_processed[extruder_nr])
|
||||
{
|
||||
processSkirt(storage, gcode_layer, extruder_nr);
|
||||
}
|
||||
@@ -961,7 +991,7 @@ void FffGcodeWriter::setExtruder_addPrime(SliceDataStorage& storage, GCodePlanne
|
||||
void FffGcodeWriter::addPrimeTower(SliceDataStorage& storage, GCodePlanner& gcodeLayer, int layer_nr, int prev_extruder)
|
||||
{
|
||||
|
||||
if (getSettingInMicrons("prime_tower_size") < 1)
|
||||
if (!getSettingBoolean("prime_tower_enable"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -976,21 +1006,22 @@ 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)
|
||||
double print_time = gcode.getTotalPrintTime();
|
||||
std::vector<double> filament_used;
|
||||
for (int extr_nr = 0; extr_nr < getSettingAsCount("machine_extruder_count"); extr_nr++)
|
||||
{
|
||||
prefix << ";MATERIAL:" << int(gcode.getTotalFilamentUsed(0)) << "\n";
|
||||
prefix << ";MATERIAL2:" << int(gcode.getTotalFilamentUsed(1)) << "\n";
|
||||
filament_used.emplace_back(gcode.getTotalFilamentUsed(extr_nr));
|
||||
}
|
||||
CommandSocket::getInstance()->sendGCodePrefix(prefix.str());
|
||||
std::string prefix = gcode.getFileHeader(&print_time, filament_used);
|
||||
CommandSocket::getInstance()->sendGCodePrefix(prefix);
|
||||
}
|
||||
|
||||
gcode.finalize(getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
|
||||
for(int e=0; e<getSettingAsCount("machine_extruder_count"); e++)
|
||||
|
||||
gcode.finalize(getSettingString("machine_end_gcode").c_str());
|
||||
for (int e = 0; e < getSettingAsCount("machine_extruder_count"); e++)
|
||||
{
|
||||
gcode.writeTemperatureCommand(e, 0, false);
|
||||
|
||||
}
|
||||
|
||||
gcode.writeComment("End of Gcode");
|
||||
/*
|
||||
the profile string below can be executed since the M25 doesn't end the gcode on an UMO and when printing via USB.
|
||||
@@ -1002,3 +1033,4 @@ void FffGcodeWriter::finalize()
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
+10
-19
@@ -39,14 +39,6 @@ class FffGcodeWriter : public SettingsMessenger, NoCopy
|
||||
private:
|
||||
int max_object_height; //!< The maximal height of all previously sliced meshgroups, used to avoid collision when moving to the next meshgroup to print.
|
||||
|
||||
/*!
|
||||
* The number of the current meshgroup being processed.
|
||||
*
|
||||
* Used for sequential printing of objects.
|
||||
* The first meshgroup will get number 1.
|
||||
*/
|
||||
int meshgroup_number;
|
||||
|
||||
/*
|
||||
* Buffer for all layer plans (of type GCodePlanner)
|
||||
*
|
||||
@@ -74,27 +66,26 @@ private:
|
||||
*/
|
||||
int last_prime_tower_poly_printed[MAX_EXTRUDERS];
|
||||
|
||||
bool skirt_is_processed[MAX_EXTRUDERS]; //!< Whether the skirt polygons have been processed into planned paths for each extruder train
|
||||
|
||||
FanSpeedLayerTimeSettings fan_speed_layer_time_settings; //!< The settings used relating to minimal layer time and fan speeds.
|
||||
|
||||
Point last_position_planned; //!< The position of the head before planning the next layer
|
||||
int current_extruder_planned; //!< The extruder train in use before planning the next layer
|
||||
bool is_inside_mesh_layer_part; //!< Whether the last position was inside a layer part (used in combing)
|
||||
public:
|
||||
FffGcodeWriter(SettingsBase* settings_)
|
||||
: SettingsMessenger(settings_)
|
||||
, layer_plan_buffer(this, gcode)
|
||||
, last_position_planned(no_point)
|
||||
, current_extruder_planned(0) // TODO: make configurable
|
||||
, is_inside_mesh_layer_part(false)
|
||||
{
|
||||
meshgroup_number = 1;
|
||||
max_object_height = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Reset the meshgroup number to process the next slicing.
|
||||
*/
|
||||
void resetMeshGroupNumber()
|
||||
{
|
||||
meshgroup_number = 1;
|
||||
for (unsigned int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++)
|
||||
{
|
||||
skirt_is_processed[extruder_nr] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -335,11 +326,11 @@ private:
|
||||
* \param mesh The mesh for which to add to the layer plan \p gcodeLayer.
|
||||
* \param part The part for which to create gcode
|
||||
* \param layer_nr The current layer number.
|
||||
* \param infill_overlap The distance by which the infill overlaps with the wall insets.
|
||||
* \param skin_overlap The distance by which the skinfill overlaps with the wall insets.
|
||||
* \param fillAngle The angle in the XY plane at which the infill is generated.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
*/
|
||||
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int infill_overlap, int infill_angle, int extrusion_width);
|
||||
void processSkin(cura::GCodePlanner& gcode_layer, cura::SliceMeshStorage* mesh, cura::SliceLayerPart& part, unsigned int layer_nr, int skin_overlap, int infill_angle, int extrusion_width);
|
||||
|
||||
/*!
|
||||
* Add the support to the layer plan \p gcodeLayer of the current layer.
|
||||
|
||||
+252
-131
@@ -1,23 +1,27 @@
|
||||
#include "FffPolygonGenerator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map> // multimap (ordered map allowing duplicate keys)
|
||||
|
||||
#include "slicer/Slicer.h"
|
||||
#include "slicer.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "MeshGroup.h"
|
||||
#include "support.h"
|
||||
#include "slicer/MultiVolumes.h"
|
||||
#include "slicer/LayerPart.h"
|
||||
#include "TextureProcessor.h"
|
||||
#include "inset.h"
|
||||
#include "multiVolumes.h"
|
||||
#include "layerPart.h"
|
||||
#include "WallsComputation.h"
|
||||
#include "skirt.h"
|
||||
#include "skin.h"
|
||||
#include "infill.h"
|
||||
#include "raft.h"
|
||||
#include "debug.h"
|
||||
#include "Progress.h"
|
||||
#include "progress/Progress.h"
|
||||
#include "PrintFeature.h"
|
||||
#include "progress/ProgressEstimator.h"
|
||||
#include "progress/ProgressStageEstimator.h"
|
||||
#include "progress/ProgressEstimatorLinear.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -25,9 +29,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;
|
||||
@@ -47,34 +48,29 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
storage.model_size = storage.model_max - storage.model_min;
|
||||
|
||||
log("Slicing model...\n");
|
||||
int initial_layer_thickness = meshgroup->getSettingInMicrons("layer_height_0");
|
||||
int initial_layer_thickness = getSettingInMicrons("layer_height_0");
|
||||
if(initial_layer_thickness <= 0) //Initial layer height of 0 is not allowed. Negative layer height is nonsense.
|
||||
{
|
||||
logError("Initial layer height %i is disallowed.",initial_layer_thickness);
|
||||
logError("Initial layer height %i is disallowed.\n", initial_layer_thickness);
|
||||
return false;
|
||||
}
|
||||
int layer_thickness = meshgroup->getSettingInMicrons("layer_height");
|
||||
int layer_thickness = getSettingInMicrons("layer_height");
|
||||
if(layer_thickness <= 0) //Layer height of 0 is not allowed. Negative layer height is nonsense.
|
||||
{
|
||||
logError("Layer height %i is disallowed.",layer_thickness);
|
||||
logError("Layer height %i is disallowed.\n", layer_thickness);
|
||||
return false;
|
||||
}
|
||||
if (meshgroup->getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT)
|
||||
{
|
||||
initial_layer_thickness = layer_thickness;
|
||||
}
|
||||
int initial_slice_z = initial_layer_thickness - layer_thickness / 2;
|
||||
int layer_count = (storage.model_max.z - initial_slice_z) / layer_thickness + 1;
|
||||
if(layer_count <= 0) //Model is shallower than layer_height_0, so not even the first layer is sliced. Return an empty model then.
|
||||
{
|
||||
Progress::messageProgressStage(Progress::Stage::INSET,&timeKeeper); //Continue directly with the inset stage, which will also immediately stop.
|
||||
return true; //This is NOT an error state!
|
||||
}
|
||||
|
||||
std::vector<Slicer*> slicerList;
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < meshgroup->meshes.size(); mesh_idx++)
|
||||
{
|
||||
Mesh& mesh = *meshgroup->meshes[mesh_idx];
|
||||
Mesh& mesh = meshgroup->meshes[mesh_idx];
|
||||
Slicer* slicer = new Slicer(&mesh, initial_slice_z, layer_thickness, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
|
||||
slicerList.push_back(slicer);
|
||||
/*
|
||||
@@ -94,33 +90,41 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper);
|
||||
//carveMultipleVolumes(storage.meshes);
|
||||
|
||||
generateMultipleVolumesOverlap(slicerList, getSettingInMicrons("multiple_mesh_overlap"));
|
||||
generateMultipleVolumesOverlap(slicerList);
|
||||
// TODO!!! dont generate multi volume overlap with infill meshes!
|
||||
|
||||
storage.meshes.reserve(slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated.
|
||||
for(unsigned int meshIdx=0; meshIdx < slicerList.size(); meshIdx++)
|
||||
{
|
||||
storage.meshes.emplace_back(meshgroup->meshes[meshIdx]); // new mesh in storage had settings from the Mesh
|
||||
storage.meshes.emplace_back(&meshgroup->meshes[meshIdx]); // new mesh in storage had settings from the Mesh
|
||||
SliceMeshStorage& meshStorage = storage.meshes.back();
|
||||
Mesh& mesh = *storage.meshgroup->meshes[meshIdx];
|
||||
Mesh& mesh = storage.meshgroup->meshes[meshIdx];
|
||||
|
||||
|
||||
createLayerParts(meshStorage, slicerList[meshIdx], mesh.getSettingBoolean("meshfix_union_all"), mesh.getSettingBoolean("meshfix_union_all_remove_holes"));
|
||||
delete slicerList[meshIdx];
|
||||
|
||||
bool has_raft = meshStorage.getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT;
|
||||
bool has_raft = getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT;
|
||||
//Add the raft offset to each layer.
|
||||
for(unsigned int layer_nr=0; layer_nr<meshStorage.layers.size(); layer_nr++)
|
||||
{
|
||||
SliceLayer& layer = meshStorage.layers[layer_nr];
|
||||
meshStorage.layers[layer_nr].printZ +=
|
||||
meshStorage.getSettingInMicrons("layer_height_0")
|
||||
getSettingInMicrons("layer_height_0")
|
||||
- initial_slice_z;
|
||||
if (has_raft)
|
||||
{
|
||||
ExtruderTrain* train = storage.meshgroup->getExtruderTrain(getSettingAsIndex("adhesion_extruder_nr"));
|
||||
layer.printZ +=
|
||||
meshStorage.getSettingInMicrons("raft_base_thickness")
|
||||
+ meshStorage.getSettingInMicrons("raft_interface_thickness")
|
||||
+ meshStorage.getSettingAsCount("raft_surface_layers") * getSettingInMicrons("raft_surface_thickness")
|
||||
+ meshStorage.getSettingInMicrons("raft_airgap");
|
||||
train->getSettingInMicrons("raft_base_thickness")
|
||||
+ train->getSettingInMicrons("raft_interface_thickness")
|
||||
+ train->getSettingAsCount("raft_surface_layers") * train->getSettingInMicrons("raft_surface_thickness")
|
||||
+ train->getSettingInMicrons("raft_airgap")
|
||||
- train->getSettingInMicrons("layer_0_z_overlap"); // shift all layers (except 0) down
|
||||
if (layer_nr == 0)
|
||||
{
|
||||
layer.printZ += train->getSettingInMicrons("layer_0_z_overlap"); // undo shifting down of first layer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,44 +135,70 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, layer.printZ, layer_nr == 0? meshStorage.getSettingInMicrons("layer_height_0") : meshStorage.getSettingInMicrons("layer_height"));
|
||||
CommandSocket::getInstance()->sendLayerInfo(layer_nr, layer.printZ, layer_nr == 0? getSettingInMicrons("layer_height_0") : getSettingInMicrons("layer_height"));
|
||||
}
|
||||
}
|
||||
|
||||
Progress::messageProgress(Progress::Stage::PARTS, meshIdx + 1, slicerList.size());
|
||||
}
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::INSET, &timeKeeper);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& time_keeper)
|
||||
{
|
||||
size_t total_layers = 0;
|
||||
// compute layer count and remove first empty layers
|
||||
// there is no separate progress stage for removeEmptyFisrtLayer (TODO)
|
||||
unsigned int total_layers = 0;
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
total_layers = std::max<unsigned int>(total_layers, mesh.layers.size());
|
||||
if (!mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
total_layers = std::max<unsigned int>(total_layers, mesh.layers.size());
|
||||
}
|
||||
}
|
||||
|
||||
// handle meshes
|
||||
std::vector<double> mesh_timings;
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
{
|
||||
mesh_timings.push_back(1.0); // TODO: have a more accurate estimate of the relative time it takes per mesh, based on the height and number of polygons
|
||||
}
|
||||
ProgressStageEstimator inset_skin_progress_estimate(mesh_timings);
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::INSET_SKIN, &time_keeper);
|
||||
std::vector<unsigned int> mesh_order;
|
||||
{ // compute mesh order
|
||||
std::multimap<int, unsigned int> order_to_mesh_indices;
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
|
||||
{
|
||||
order_to_mesh_indices.emplace(storage.meshes[mesh_idx].getSettingAsIndex("infill_mesh_order"), mesh_idx);
|
||||
}
|
||||
for (std::pair<const int, unsigned int>& order_and_mesh_idx : order_to_mesh_indices)
|
||||
{
|
||||
mesh_order.push_back(order_and_mesh_idx.second);
|
||||
}
|
||||
}
|
||||
for (unsigned int mesh_idx : mesh_order)
|
||||
{
|
||||
processBasicWallsSkinInfill(storage, mesh_idx, mesh_order, total_layers, inset_skin_progress_estimate);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, mesh_idx + 1, storage.meshes.size());
|
||||
}
|
||||
|
||||
//layerparts2HTML(storage, "output/output.html");
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
|
||||
// we need to remove empty layers after we have procesed the insets
|
||||
// processInsets might throw away parts if they have no wall at all (cause it doesn't fit)
|
||||
// brim depends on the first layer not being empty
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), total_layers); // changes total_layers!
|
||||
if (total_layers == 0)
|
||||
{
|
||||
processInsets(storage, layer_number);
|
||||
|
||||
Progress::messageProgress(Progress::Stage::INSET, layer_number+1, total_layers);
|
||||
}
|
||||
|
||||
removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), total_layers);
|
||||
|
||||
if (total_layers < 1)
|
||||
{
|
||||
log("Stopping process because there are no layers.\n");
|
||||
log("Stopping process because there are no non-empty layers.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper);
|
||||
|
||||
|
||||
AreaSupport::generateSupportAreas(storage, total_layers);
|
||||
|
||||
/*
|
||||
if (storage.support.generated)
|
||||
{
|
||||
@@ -182,83 +212,181 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper);
|
||||
int mesh_max_bottom_layer_count = 0;
|
||||
if (getSettingBoolean("magic_spiralize"))
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
mesh_max_bottom_layer_count = std::max(mesh_max_bottom_layer_count, mesh.getSettingAsCount("bottom_layers"));
|
||||
}
|
||||
}
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
{
|
||||
if (!getSettingBoolean("magic_spiralize") || static_cast<int>(layer_number) < mesh_max_bottom_layer_count) //Only generate up/downskin and infill for the first X layers when spiralize is choosen.
|
||||
{
|
||||
processSkinsAndInfill(storage, layer_number);
|
||||
}
|
||||
Progress::messageProgress(Progress::Stage::SKIN, layer_number+1, total_layers);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// handle helpers
|
||||
storage.primeTower.computePrimeTowerMax(storage);
|
||||
storage.primeTower.generatePaths(storage, total_layers);
|
||||
|
||||
processOozeShield(storage, total_layers);
|
||||
|
||||
processDraftShield(storage, total_layers);
|
||||
|
||||
processPlatformAdhesion(storage);
|
||||
|
||||
processOozeShield(storage, total_layers);
|
||||
|
||||
processDraftShield(storage, total_layers);
|
||||
|
||||
processPlatformAdhesion(storage);
|
||||
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
// meshes post processing
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
if (mesh.getSettingBoolean("magic_fuzzy_skin_enabled"))
|
||||
{
|
||||
processFuzzyWalls(mesh);
|
||||
}
|
||||
processDerivedWallsSkinInfill(mesh, total_layers);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processInsets(SliceDataStorage& storage, unsigned int layer_nr)
|
||||
void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate)
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
if (mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
SliceLayer* layer = &mesh.layers[layer_nr];
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::SURFACE)
|
||||
processInfillMesh(storage, mesh_idx, mesh_order, total_layers);
|
||||
}
|
||||
|
||||
// TODO: make progress more accurate!!
|
||||
// note: estimated time for insets : skins = 22.953 : 48.858
|
||||
std::vector<double> walls_vs_skin_timing({22.953, 48.858});
|
||||
ProgressStageEstimator* mesh_inset_skin_progress_estimator = new ProgressStageEstimator(walls_vs_skin_timing);
|
||||
|
||||
inset_skin_progress_estimate.nextStage(mesh_inset_skin_progress_estimator); // the stage of this function call
|
||||
|
||||
ProgressEstimatorLinear* inset_estimator = new ProgressEstimatorLinear(total_layers);
|
||||
mesh_inset_skin_progress_estimator->nextStage(inset_estimator);
|
||||
|
||||
|
||||
// walls
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
{
|
||||
processInsets(mesh, layer_number);
|
||||
double progress = inset_skin_progress_estimate.progress(layer_number);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100);
|
||||
}
|
||||
|
||||
ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(total_layers);
|
||||
mesh_inset_skin_progress_estimator->nextStage(skin_estimator);
|
||||
|
||||
// skin & infill
|
||||
// Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper);
|
||||
int mesh_max_bottom_layer_count = 0;
|
||||
if (mesh.getSettingBoolean("magic_spiralize"))
|
||||
{
|
||||
mesh_max_bottom_layer_count = std::max(mesh_max_bottom_layer_count, mesh.getSettingAsCount("bottom_layers"));
|
||||
}
|
||||
for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++)
|
||||
{
|
||||
if (!mesh.getSettingBoolean("magic_spiralize") || static_cast<int>(layer_number) < mesh_max_bottom_layer_count) //Only generate up/downskin and infill for the first X layers when spiralize is choosen.
|
||||
{
|
||||
int inset_count = mesh.getSettingAsCount("wall_line_count");
|
||||
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
|
||||
inset_count += 5;
|
||||
int line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
|
||||
int line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
|
||||
if (mesh.getSettingBoolean("alternate_extra_perimeter"))
|
||||
inset_count += layer_nr % 2;
|
||||
generateInsets(layer, mesh.getSettingInMicrons("wall_0_inset"), line_width_0, line_width_x, inset_count, mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
|
||||
processSkinsAndInfill(mesh, layer_number);
|
||||
}
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
double progress = inset_skin_progress_estimate.progress(layer_number);
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers)
|
||||
{
|
||||
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
|
||||
for (unsigned int layer_idx = 0; layer_idx < mesh.layers.size(); layer_idx++)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_idx];
|
||||
std::vector<PolygonsPart> new_parts;
|
||||
|
||||
for (unsigned int other_mesh_idx : mesh_order)
|
||||
{
|
||||
for (PolygonRef polyline : layer->openPolyLines)
|
||||
if (other_mesh_idx == mesh_idx)
|
||||
{
|
||||
Polygons segments;
|
||||
for (unsigned int point_idx = 1; point_idx < polyline.size(); point_idx++)
|
||||
break; // all previous meshes have been processed
|
||||
}
|
||||
SliceMeshStorage& other_mesh = storage.meshes[other_mesh_idx];
|
||||
if (layer_idx >= other_mesh.layers.size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SliceLayer& other_layer = other_mesh.layers[layer_idx];
|
||||
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
for (SliceLayerPart& other_part : other_layer.parts)
|
||||
{
|
||||
PolygonRef segment = segments.newPoly();
|
||||
segment.add(polyline[point_idx-1]);
|
||||
segment.add(polyline[point_idx]);
|
||||
if (!part.boundaryBox.hit(other_part.boundaryBox))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Polygons& infill = other_part.infill_area;
|
||||
Polygons new_outline = part.outline.intersection(infill);
|
||||
if (new_outline.size() == 1)
|
||||
{
|
||||
PolygonsPart outline_part_here;
|
||||
outline_part_here.add(new_outline[0]);
|
||||
new_parts.push_back(outline_part_here);
|
||||
}
|
||||
else if (new_outline.size() > 1)
|
||||
{
|
||||
std::vector<PolygonsPart> new_parts_here = new_outline.splitIntoParts();
|
||||
for (PolygonsPart& new_part_here : new_parts_here)
|
||||
{
|
||||
new_parts.push_back(new_part_here);
|
||||
}
|
||||
}
|
||||
infill = infill.difference(part.outline);
|
||||
other_part.infill_area_per_combine.back() = infill;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer.parts.clear();
|
||||
for (PolygonsPart& part : new_parts)
|
||||
{
|
||||
layer.parts.emplace_back();
|
||||
layer.parts.back().outline = part;
|
||||
layer.parts.back().boundaryBox.calculate(part);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers)
|
||||
{
|
||||
// combine infill
|
||||
unsigned int combined_infill_layers = mesh.getSettingInMicrons("infill_sparse_thickness") / std::max(getSettingInMicrons("layer_height"), 1); //How many infill layers to combine to obtain the requested sparse thickness.
|
||||
combineInfillLayers(mesh,combined_infill_layers);
|
||||
|
||||
// fuzzy skin
|
||||
if (mesh.getSettingBoolean("magic_fuzzy_skin_enabled"))
|
||||
{
|
||||
processFuzzyWalls(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int layer_nr)
|
||||
{
|
||||
SliceLayer* layer = &mesh.layers[layer_nr];
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::SURFACE)
|
||||
{
|
||||
int inset_count = mesh.getSettingAsCount("wall_line_count");
|
||||
if (mesh.getSettingBoolean("magic_spiralize") && static_cast<int>(layer_nr) < mesh.getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
|
||||
inset_count += 5;
|
||||
int line_width_x = mesh.getSettingInMicrons("wall_line_width_x");
|
||||
int line_width_0 = mesh.getSettingInMicrons("wall_line_width_0");
|
||||
if (mesh.getSettingBoolean("alternate_extra_perimeter"))
|
||||
inset_count += layer_nr % 2;
|
||||
bool recompute_outline_based_on_outer_wall = mesh.getSettingBoolean("support_enable");
|
||||
WallsComputation walls_computation(mesh.getSettingInMicrons("wall_0_inset"), line_width_0, line_width_x, inset_count, recompute_outline_based_on_outer_wall);
|
||||
walls_computation.generateInsets(layer);
|
||||
}
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
{
|
||||
for (PolygonRef polyline : layer->openPolyLines)
|
||||
{
|
||||
Polygons segments;
|
||||
for (unsigned int point_idx = 1; point_idx < polyline.size(); point_idx++)
|
||||
{
|
||||
PolygonRef segment = segments.newPoly();
|
||||
segment.add(polyline[point_idx-1]);
|
||||
segment.add(polyline[point_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, int layer_height, unsigned int total_layers)
|
||||
void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, const int layer_height, unsigned int& total_layers)
|
||||
{
|
||||
int n_empty_first_layers = 0;
|
||||
for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++)
|
||||
@@ -299,34 +427,26 @@ void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, int
|
||||
}
|
||||
}
|
||||
|
||||
void FffPolygonGenerator::processSkinsAndInfill(SliceDataStorage& storage, unsigned int layer_nr)
|
||||
void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr)
|
||||
{
|
||||
for(SliceMeshStorage& mesh : storage.meshes)
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int wall_line_count = mesh.getSettingAsCount("wall_line_count");
|
||||
int skin_extrusion_width = mesh.getSettingInMicrons("skin_line_width");
|
||||
int innermost_wall_extrusion_width = (wall_line_count == 1)? mesh.getSettingInMicrons("wall_line_width_0") : mesh.getSettingInMicrons("wall_line_width_x");
|
||||
generateSkins(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), wall_line_count, innermost_wall_extrusion_width, mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("skin_no_small_gaps_heuristic"));
|
||||
if (mesh.getSettingInMicrons("infill_line_distance") > 0)
|
||||
{
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { continue; }
|
||||
|
||||
int wall_line_count = mesh.getSettingAsCount("wall_line_count");
|
||||
int skin_extrusion_width = mesh.getSettingInMicrons("skin_line_width");
|
||||
int innermost_wall_extrusion_width = (wall_line_count == 1)? mesh.getSettingInMicrons("wall_line_width_0") : mesh.getSettingInMicrons("wall_line_width_x");
|
||||
generateSkins(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), wall_line_count, innermost_wall_extrusion_width, mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("skin_no_small_gaps_heuristic"), mesh.getSettingBoolean("remove_overlapping_walls_0_enabled"), mesh.getSettingBoolean("remove_overlapping_walls_x_enabled"));
|
||||
if (mesh.getSettingInMicrons("infill_line_distance") > 0)
|
||||
int infill_skin_overlap = 0;
|
||||
bool infill_is_dense = mesh.getSettingInMicrons("infill_line_distance") < mesh.getSettingInMicrons("infill_line_width") + 10;
|
||||
if (!infill_is_dense && mesh.getSettingAsFillMethod("infill_pattern") != EFillMethod::CONCENTRIC)
|
||||
{
|
||||
int infill_skin_overlap = 0;
|
||||
bool infill_is_dense = mesh.getSettingInMicrons("infill_line_distance") < mesh.getSettingInMicrons("infill_line_width") + 10;
|
||||
if (!infill_is_dense && mesh.getSettingAsFillMethod("infill_pattern") != EFillMethod::CONCENTRIC)
|
||||
{
|
||||
infill_skin_overlap = skin_extrusion_width / 2;
|
||||
}
|
||||
generateInfill(layer_nr, mesh, innermost_wall_extrusion_width, infill_skin_overlap, wall_line_count);
|
||||
if (mesh.getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") == FillPerimeterGapMode::SKIN)
|
||||
{
|
||||
generatePerimeterGaps(layer_nr, mesh, skin_extrusion_width, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"));
|
||||
}
|
||||
else if (mesh.getSettingAsFillPerimeterGapMode("fill_perimeter_gaps") == FillPerimeterGapMode::EVERYWHERE)
|
||||
{
|
||||
generatePerimeterGaps(layer_nr, mesh, skin_extrusion_width, 0, 0);
|
||||
}
|
||||
infill_skin_overlap = skin_extrusion_width / 2;
|
||||
}
|
||||
generateInfill(layer_nr, mesh, innermost_wall_extrusion_width, infill_skin_overlap, wall_line_count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,19 +507,20 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned
|
||||
|
||||
void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage)
|
||||
{
|
||||
SettingsBaseVirtual* train = storage.meshgroup->getExtruderTrain(getSettingBoolean("adhesion_extruder_nr"));
|
||||
switch(getSettingAsPlatformAdhesion("adhesion_type"))
|
||||
{
|
||||
case EPlatformAdhesion::SKIRT:
|
||||
if (getSettingInMicrons("draft_shield_height") == 0)
|
||||
if (train->getSettingInMicrons("draft_shield_height") == 0)
|
||||
{ // draft screen replaces skirt
|
||||
generateSkirt(storage, getSettingInMicrons("skirt_gap"), getSettingAsCount("skirt_line_count"), getSettingInMicrons("skirt_minimal_length"));
|
||||
generateSkirt(storage, train->getSettingInMicrons("skirt_gap"), train->getSettingAsCount("skirt_line_count"), train->getSettingInMicrons("skirt_minimal_length"));
|
||||
}
|
||||
break;
|
||||
case EPlatformAdhesion::BRIM:
|
||||
generateSkirt(storage, 0, getSettingAsCount("brim_line_count"), getSettingInMicrons("skirt_minimal_length"));
|
||||
generateSkirt(storage, 0, train->getSettingAsCount("brim_line_count"), train->getSettingInMicrons("skirt_minimal_length"));
|
||||
break;
|
||||
case EPlatformAdhesion::RAFT:
|
||||
generateRaft(storage, getSettingInMicrons("raft_margin"));
|
||||
generateRaft(storage, train->getSettingInMicrons("raft_margin"));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include "utils/polygonUtils.h"
|
||||
#include "utils/NoCopy.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "commandSocket.h"
|
||||
#include "PrintFeature.h"
|
||||
#include "progress/ProgressEstimator.h"
|
||||
#include "progress/ProgressStageEstimator.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -64,20 +66,53 @@ private:
|
||||
*/
|
||||
void slices2polygons(SliceDataStorage& storage, TimeKeeper& timeKeeper);
|
||||
|
||||
/*!
|
||||
* Processes the outline information as stored in the \p storage: generates inset perimeter polygons, skin and infill
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh_idx The index of the mesh to process in the vector of meshes in \p storage
|
||||
* \param mesh_order The order in which the meshes are processed (used for infill meshes)
|
||||
* \param total_layers The total number of layers over all objects
|
||||
* \param inset_skin_progress_estimate The progress stage estimate calculator
|
||||
*/
|
||||
void processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate);
|
||||
|
||||
/*!
|
||||
* Process the mesh to be an infill mesh: limit all outlines to within the infill of normal meshes and subtract their volume from the infill of those meshes
|
||||
*
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh_idx The index of the mesh to process in the vector of meshes in \p storage
|
||||
* \param mesh_order The order in which the meshes are processed
|
||||
* \param total_layers The total number of layers over all objects
|
||||
*/
|
||||
void processInfillMesh(SliceDataStorage& storage, unsigned int mesh_idx, std::vector<unsigned int>& mesh_order, size_t total_layers);
|
||||
|
||||
/*!
|
||||
* Process features which are derived from the basic walls, skin, and infill:
|
||||
* fuzzy skin, infill combine
|
||||
*
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param total_layers The total number of layers over all objects
|
||||
*/
|
||||
void processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers);
|
||||
|
||||
/*!
|
||||
* Remove all bottom layers which are empty.
|
||||
*
|
||||
* \warning Changes \p total_layers
|
||||
*
|
||||
* \param storage Input and Ouput parameter: stores all layers
|
||||
* \param layer_height The height of each layer
|
||||
* \param total_layers The total number of layers
|
||||
*/
|
||||
void removeEmptyFirstLayers(SliceDataStorage& storage, int layer_height, unsigned int total_layers);
|
||||
void removeEmptyFirstLayers(SliceDataStorage& storage, const int layer_height, unsigned int& total_layers);
|
||||
|
||||
/*!
|
||||
* Generate the inset polygons which form the walls.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param layer_nr The layer for which to generate the insets.
|
||||
*/
|
||||
void processInsets(SliceDataStorage& storage, unsigned int layer_nr);
|
||||
void processInsets(SliceMeshStorage& mesh, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Generate the outline of the ooze shield.
|
||||
@@ -88,10 +123,10 @@ private:
|
||||
|
||||
/*!
|
||||
* Generate the skin areas.
|
||||
* \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage
|
||||
* \param layer_nr The layer for which to generate the skin areas.
|
||||
*/
|
||||
void processSkinsAndInfill(SliceDataStorage& storage, unsigned int layer_nr);
|
||||
void processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr);
|
||||
|
||||
/*!
|
||||
* Generate the polygons where the draft screen should be.
|
||||
|
||||
+37
-9
@@ -5,6 +5,19 @@ namespace cura
|
||||
|
||||
FffProcessor FffProcessor::instance; // definition must be in cpp
|
||||
|
||||
FffProcessor::FffProcessor()
|
||||
: SettingsBase("global")
|
||||
, polygon_generator(this)
|
||||
, gcode_writer(this)
|
||||
, meshgroup_number(0)
|
||||
{
|
||||
}
|
||||
|
||||
int FffProcessor::getMeshgroupNr()
|
||||
{
|
||||
return meshgroup_number;
|
||||
}
|
||||
|
||||
|
||||
std::string FffProcessor::getAllSettingsString(MeshGroup& meshgroup, bool first_meshgroup)
|
||||
{
|
||||
@@ -26,8 +39,8 @@ std::string FffProcessor::getAllSettingsString(MeshGroup& meshgroup, bool first_
|
||||
}
|
||||
for (unsigned int mesh_idx = 0; mesh_idx < meshgroup.meshes.size(); mesh_idx++)
|
||||
{
|
||||
Mesh& mesh = *meshgroup.meshes[mesh_idx];
|
||||
sstream << " -e" << mesh.getSettingAsCount("extruder_nr") << " -l \"" << mesh_idx << "\"" << mesh.getAllLocalSettingsString();
|
||||
Mesh& mesh = meshgroup.meshes[mesh_idx];
|
||||
sstream << " -e" << mesh.getSettingAsIndex("extruder_nr") << " -l \"" << mesh_idx << "\"" << mesh.getAllLocalSettingsString();
|
||||
}
|
||||
sstream << "\n";
|
||||
return sstream.str();
|
||||
@@ -58,19 +71,30 @@ bool FffProcessor::processFiles(const std::vector< std::string >& files)
|
||||
|
||||
bool FffProcessor::processMeshGroup(MeshGroup* meshgroup)
|
||||
{
|
||||
if (SHOW_ALL_SETTINGS) { logWarning(getAllSettingsString(*meshgroup, first_meshgroup).c_str()); }
|
||||
if (SHOW_ALL_SETTINGS) { logWarning(getAllSettingsString(*meshgroup, meshgroup_number == 0).c_str()); }
|
||||
time_keeper.restart();
|
||||
if (!meshgroup)
|
||||
return false;
|
||||
|
||||
TimeKeeper time_keeper_total;
|
||||
|
||||
if (meshgroup->meshes.empty())
|
||||
|
||||
polygon_generator.setParent(meshgroup);
|
||||
gcode_writer.setParent(meshgroup);
|
||||
|
||||
bool empty = true;
|
||||
for (Mesh& mesh : meshgroup->meshes)
|
||||
{
|
||||
if (!mesh.getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
empty = false;
|
||||
}
|
||||
}
|
||||
if (empty)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::FINISH, 1, 1); // 100% on this meshgroup
|
||||
log("Total time elapsed %5.2fs.\n", time_keeper_total.restart());
|
||||
|
||||
profile_string += getAllSettingsString(*meshgroup, first_meshgroup);
|
||||
profile_string += getAllSettingsString(*meshgroup, meshgroup_number == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -103,12 +127,16 @@ 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());
|
||||
|
||||
profile_string += getAllSettingsString(*meshgroup, first_meshgroup);
|
||||
first_meshgroup = false;
|
||||
profile_string += getAllSettingsString(*meshgroup, meshgroup_number == 0);
|
||||
meshgroup_number++;
|
||||
|
||||
polygon_generator.setParent(this); // otherwise consequent getSetting calls (e.g. for finalize) will refer to non-existent meshgroup
|
||||
gcode_writer.setParent(this); // otherwise consequent getSetting calls (e.g. for finalize) will refer to non-existent meshgroup
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -1,13 +1,13 @@
|
||||
#ifndef FFF_PROCESSOR_H
|
||||
#define FFF_PROCESSOR_H
|
||||
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
#include "FffGcodeWriter.h"
|
||||
#include "FffPolygonGenerator.h"
|
||||
#include "commandSocket.h"
|
||||
#include "Weaver.h"
|
||||
#include "Wireframe2gcode.h"
|
||||
#include "Progress.h"
|
||||
#include "progress/Progress.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/NoCopy.h"
|
||||
|
||||
@@ -24,12 +24,7 @@ private:
|
||||
*/
|
||||
static FffProcessor instance;
|
||||
|
||||
FffProcessor()
|
||||
: polygon_generator(this)
|
||||
, gcode_writer(this)
|
||||
, first_meshgroup(true)
|
||||
{
|
||||
}
|
||||
FffProcessor();
|
||||
public:
|
||||
/*!
|
||||
* Get the instance
|
||||
@@ -39,7 +34,12 @@ public:
|
||||
{
|
||||
return &instance;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Get the index of the meshgroup currently being processed, starting at zero.
|
||||
*/
|
||||
int getMeshgroupNr();
|
||||
|
||||
private:
|
||||
/*!
|
||||
* The polygon generator, which slices the models and generates all polygons to be printed and areas to be filled.
|
||||
@@ -52,9 +52,9 @@ private:
|
||||
FffGcodeWriter gcode_writer;
|
||||
|
||||
/*!
|
||||
* Whether the firs meshgroup is being processed.
|
||||
* The index of the meshgroup currently being processed, starting at zero.
|
||||
*/
|
||||
bool first_meshgroup;
|
||||
int meshgroup_number;
|
||||
|
||||
/*!
|
||||
* A string containing all setting values passed to the engine in the format by which CuraEngine is called via the command line.
|
||||
@@ -93,7 +93,7 @@ public:
|
||||
*/
|
||||
void resetMeshGroupNumber()
|
||||
{
|
||||
gcode_writer.resetMeshGroupNumber();
|
||||
meshgroup_number = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "LayerPlanBuffer.h"
|
||||
#include "gcodeExport.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "FffProcessor.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -16,7 +17,7 @@ void LayerPlanBuffer::flush()
|
||||
}
|
||||
while (!buffer.empty())
|
||||
{
|
||||
buffer.front().writeGCode(gcode, getSettingBoolean("cool_lift_head"), buffer.front().getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
|
||||
buffer.front().writeGCode(gcode);
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->flushGcode();
|
||||
@@ -115,7 +116,7 @@ void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector<GCodePlann
|
||||
assert (extruder_plan_before.extruder != extruder);
|
||||
|
||||
double time_here = extruder_plan_before.estimates.getTotalTime();
|
||||
if (time_here > time_before_extruder_plan_to_insert)
|
||||
if (time_here >= time_before_extruder_plan_to_insert)
|
||||
{
|
||||
insertPreheatCommand(extruder_plan_before, time_here - time_before_extruder_plan_to_insert, extruder, required_temp);
|
||||
return;
|
||||
@@ -142,17 +143,33 @@ void LayerPlanBuffer::insertPreheatCommand(std::vector<GCodePlanner*>& layers, u
|
||||
if (extruder_plan_idx == 0)
|
||||
{
|
||||
if (layer_plan_idx == 0)
|
||||
{ // the very first extruder plan
|
||||
{ // the very first extruder plan of the current meshgroup
|
||||
for (int extruder_idx = 0; extruder_idx < getSettingAsCount("machine_extruder_count"); extruder_idx++)
|
||||
{ // set temperature of the first nozzle, turn other nozzles down
|
||||
if (extruder_idx == extruder)
|
||||
if (FffProcessor::getInstance()->getMeshgroupNr() == 0)
|
||||
{
|
||||
// extruder_plan.insertCommand(0, extruder, required_temp, true);
|
||||
// the first used extruder should already be set to the required temp in the start gcode
|
||||
// override values from GCodeExport::setInitialTemps
|
||||
// the first used extruder should be set to the required temp in the start gcode
|
||||
// see FffGcodeWriter::processStartingCode
|
||||
if (extruder_idx == extruder)
|
||||
{
|
||||
gcode.setInitialTemp(extruder_idx, required_temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode.setInitialTemp(extruder_idx, preheat_config.getStandbyTemp(extruder_idx));
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
extruder_plan.insertCommand(0, extruder_idx, preheat_config.getStandbyTemp(extruder_idx), false);
|
||||
if (extruder_idx == extruder)
|
||||
{
|
||||
extruder_plan.insertCommand(0, extruder, required_temp, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
extruder_plan.insertCommand(0, extruder_idx, preheat_config.getStandbyTemp(extruder_idx), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
#include "commandSocket.h"
|
||||
|
||||
#include "gcodeExport.h"
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
buffer.emplace_back(constructor_args...);
|
||||
if (buffer.size() > buffer_size)
|
||||
{
|
||||
buffer.front().writeGCode(gcode, getSettingBoolean("cool_lift_head"), buffer.front().getLayerNr() > 0 ? getSettingInMicrons("layer_height") : getSettingInMicrons("layer_height_0"));
|
||||
buffer.front().writeGCode(gcode);
|
||||
if (CommandSocket::isInstantiated())
|
||||
{
|
||||
CommandSocket::getInstance()->flushGcode();
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef MAT_COORD_H
|
||||
#define MAT_COORD_H
|
||||
|
||||
#include "utils/FPoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Coordinates in a specific texture bitmap
|
||||
*/
|
||||
struct MatCoord
|
||||
{
|
||||
FPoint coords;
|
||||
int mat_id; //!< Material id
|
||||
MatCoord() //!< non-initializing constructor
|
||||
{}
|
||||
MatCoord(FPoint coords, int mat_id) //!< constructor
|
||||
: coords(coords)
|
||||
, mat_id(mat_id)
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // MAT_COORD_H
|
||||
@@ -1,27 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef MAT_SEGMENT_H
|
||||
#define MAT_SEGMENT_H
|
||||
|
||||
#include "MatCoord.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Coordinates in a specific texture bitmap
|
||||
*/
|
||||
struct MatSegment
|
||||
{
|
||||
MatCoord start;
|
||||
MatCoord end;
|
||||
MatSegment() //!< non-initializing constructor
|
||||
{}
|
||||
MatSegment(MatCoord start, MatCoord end)
|
||||
: start(start)
|
||||
, end(end)
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // MAT_SEGMENT_H
|
||||
@@ -1,33 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include <limits> // numeric limits
|
||||
#include <algorithm> // min max
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "Material.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void Material::setData(unsigned char* data)
|
||||
{
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
void Material::setWidthHeight(int width, int height)
|
||||
{
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
}
|
||||
|
||||
float Material::getColor(float x, float y)
|
||||
{
|
||||
int w_idx = std::max(0, std::min(int (x * width), width - 1));
|
||||
int h_idx = std::max(0, std::min(int (y * height), height - 1));
|
||||
unsigned char r = data[(h_idx * width + w_idx) * 3];
|
||||
return (float) r / std::numeric_limits<unsigned char>::max();
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
@@ -1,30 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef MATERIAL_H
|
||||
#define MATERIAL_H
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class Material
|
||||
{
|
||||
public:
|
||||
void setData(unsigned char* data);
|
||||
void setWidthHeight(int width, int height);
|
||||
/*!
|
||||
* get some value representing the getColor
|
||||
*
|
||||
* red?
|
||||
*
|
||||
* TODO
|
||||
*
|
||||
* \return a value between zero and one
|
||||
*/
|
||||
float getColor(float x, float y);
|
||||
protected:
|
||||
unsigned char* data; //!< pixel data in rgb-row-first (or bgr-row first ?)
|
||||
int width, height; //!< image dimensions
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // MATERIAL_H
|
||||
@@ -1,42 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "MaterialBase.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
Material* MaterialBase::add(std::string name)
|
||||
{
|
||||
name_to_mat_idx[name] = materials.size();
|
||||
materials.emplace_back();
|
||||
return &materials.back();
|
||||
}
|
||||
|
||||
Material* MaterialBase::getMat(unsigned int id)
|
||||
{
|
||||
if (id < materials.size())
|
||||
{
|
||||
return &materials[id];
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int MaterialBase::getMatId(std::string name)
|
||||
{
|
||||
auto it = name_to_mat_idx.find(name);
|
||||
if (it == name_to_mat_idx.end())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cura
|
||||
@@ -1,27 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef MATERIAL_BASE_H
|
||||
#define MATERIAL_BASE_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Material.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class MaterialBase
|
||||
{
|
||||
public:
|
||||
int getMatId(std::string name);
|
||||
Material* add(std::string name);
|
||||
Material* getMat(unsigned int id);
|
||||
protected:
|
||||
std::unordered_map<std::string, int> name_to_mat_idx;
|
||||
std::vector<Material> materials;
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // MATERIAL_BASE_H
|
||||
+115
-181
@@ -22,16 +22,125 @@ void* fgets_(char* ptr, size_t len, FILE* f)
|
||||
*ptr = '\0';
|
||||
return ptr;
|
||||
}
|
||||
else if (*ptr =='\0')
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
ptr++;
|
||||
len--;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MeshGroup::MeshGroup(SettingsBaseVirtual* settings_base)
|
||||
: SettingsBase(settings_base, std::string("meshgroup"))
|
||||
, extruder_count(-1)
|
||||
{}
|
||||
|
||||
MeshGroup::~MeshGroup()
|
||||
{
|
||||
for (unsigned int extruder = 0; extruder < MAX_EXTRUDERS; extruder++)
|
||||
{
|
||||
if (extruders[extruder])
|
||||
{
|
||||
delete extruders[extruder];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int MeshGroup::getExtruderCount()
|
||||
{
|
||||
if (extruder_count == -1)
|
||||
{
|
||||
extruder_count = getSettingAsCount("machine_extruder_count");
|
||||
}
|
||||
return extruder_count;
|
||||
}
|
||||
|
||||
ExtruderTrain* MeshGroup::createExtruderTrain(unsigned int extruder_nr)
|
||||
{
|
||||
if (!extruders[extruder_nr])
|
||||
{
|
||||
extruders[extruder_nr] = new ExtruderTrain(this, extruder_nr);
|
||||
}
|
||||
return extruders[extruder_nr];
|
||||
}
|
||||
|
||||
ExtruderTrain* MeshGroup::getExtruderTrain(unsigned int extruder_nr)
|
||||
{
|
||||
assert(extruders[extruder_nr]);
|
||||
return extruders[extruder_nr];
|
||||
}
|
||||
|
||||
const ExtruderTrain* MeshGroup::getExtruderTrain(unsigned int extruder_nr) const
|
||||
{
|
||||
assert(extruders[extruder_nr]);
|
||||
return extruders[extruder_nr];
|
||||
}
|
||||
|
||||
Point3 MeshGroup::min() const
|
||||
{
|
||||
if (meshes.size() < 1)
|
||||
{
|
||||
return Point3(0, 0, 0);
|
||||
}
|
||||
Point3 ret = meshes[0].min();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
{
|
||||
Point3 v = meshes[i].min();
|
||||
ret.x = std::min(ret.x, v.x);
|
||||
ret.y = std::min(ret.y, v.y);
|
||||
ret.z = std::min(ret.z, v.z);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Point3 MeshGroup::max() const
|
||||
{
|
||||
if (meshes.size() < 1)
|
||||
{
|
||||
return Point3(0, 0, 0);
|
||||
}
|
||||
Point3 ret = meshes[0].max();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
{
|
||||
Point3 v = meshes[i].max();
|
||||
ret.x = std::max(ret.x, v.x);
|
||||
ret.y = std::max(ret.y, v.y);
|
||||
ret.z = std::max(ret.z, v.z);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MeshGroup::clear()
|
||||
{
|
||||
for(Mesh& m : meshes)
|
||||
{
|
||||
m.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshGroup::finalize()
|
||||
{
|
||||
//If the machine settings have been supplied, offset the given position vertices to the center of vertices (0,0,0) is at the bed center.
|
||||
Point3 meshgroup_offset(0, 0, 0);
|
||||
if (!getSettingBoolean("machine_center_is_zero"))
|
||||
{
|
||||
meshgroup_offset.x = getSettingInMicrons("machine_width") / 2;
|
||||
meshgroup_offset.y = getSettingInMicrons("machine_depth") / 2;
|
||||
}
|
||||
|
||||
// If a mesh position was given, put the mesh at this position in 3D space.
|
||||
for(Mesh& mesh : meshes)
|
||||
{
|
||||
Point3 mesh_offset(mesh.getSettingInMicrons("mesh_position_x"), mesh.getSettingInMicrons("mesh_position_y"), mesh.getSettingInMicrons("mesh_position_z"));
|
||||
if (mesh.getSettingBoolean("center_object"))
|
||||
{
|
||||
Point3 object_min = mesh.min();
|
||||
Point3 object_max = mesh.max();
|
||||
Point3 object_size = object_max - object_min;
|
||||
mesh_offset += Point3(-object_min.x - object_size.x / 2, -object_min.y - object_size.y / 2, -object_min.z);
|
||||
}
|
||||
mesh.offset(mesh_offset + meshgroup_offset);
|
||||
}
|
||||
}
|
||||
|
||||
bool loadMeshSTL_ascii(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
|
||||
{
|
||||
FILE* f = fopen(filename, "rt");
|
||||
@@ -172,193 +281,18 @@ bool loadMeshSTL(Mesh* mesh, const char* filename, const FMatrix3x3& matrix)
|
||||
return loadMeshSTL_binary(mesh, filename, matrix);
|
||||
}
|
||||
|
||||
void readBMP(Material* mat, const char* filename)
|
||||
{
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (f == nullptr)
|
||||
{
|
||||
logError("ERROR: couldn't load image file %s.\n", filename);
|
||||
return;
|
||||
}
|
||||
unsigned char info[54];
|
||||
fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header
|
||||
|
||||
// extract image height and width from header
|
||||
int width = *(int*)&info[18];
|
||||
int height = *(int*)&info[22];
|
||||
|
||||
int size = 3 * width * height;
|
||||
unsigned char* data = new unsigned char[size]; // allocate 3 bytes per pixel
|
||||
fread(data, sizeof(unsigned char), size, f); // read the rest of the data at once
|
||||
fclose(f);
|
||||
|
||||
|
||||
// for (int i = 0; i < size; i += 3)
|
||||
// {
|
||||
// unsigned char tmp = data[i];
|
||||
// data[i] = data[i+2];
|
||||
// data[i+2] = tmp;
|
||||
// } // BGR ==> RGB
|
||||
mat->setData(data);
|
||||
mat->setWidthHeight(width, height);
|
||||
}
|
||||
void loadMatImage(Material* mat, const char* filename)
|
||||
{
|
||||
const char* ext = strrchr(filename, '.');
|
||||
if (ext && (strcmp(ext, ".bmp") == 0 || strcmp(ext, ".BMP") == 0))
|
||||
{
|
||||
readBMP(mat, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
logError("ERROR: trying to load unsupported image. File %s has %s extension.\n", filename, ext);
|
||||
}
|
||||
}
|
||||
|
||||
void loadMaterialBase(TexturedMesh* mesh, const char* filename)
|
||||
{
|
||||
FILE* f = fopen(filename, "rt");
|
||||
if (f == nullptr)
|
||||
{
|
||||
logError("ERROR: Couldn't load MTL file %s.\n", filename);
|
||||
return;
|
||||
}
|
||||
char buffer[1024];
|
||||
char mat_name [100];
|
||||
char mat_file [100];
|
||||
char map_type [10];
|
||||
Material* last_mat = nullptr;
|
||||
while(fgets_(buffer, sizeof(buffer), f))
|
||||
{
|
||||
if (buffer[0] == '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (sscanf(buffer, "map_%s %s", map_type, mat_file) == 2 // we don't care what type of map it specifies (currently)
|
||||
|| sscanf(buffer, "bump %s", mat_file) == 1
|
||||
|| sscanf(buffer, "disp %s", mat_file) == 1
|
||||
|| sscanf(buffer, "decal %s", mat_file) == 1
|
||||
|| sscanf(buffer, "refl %s", mat_file) == 1
|
||||
)
|
||||
{
|
||||
std::string parent_dir = std::string(filename).substr(0, std::string(filename).find_last_of("/\\"));
|
||||
std::string mtl_file = parent_dir + "/" + mat_file;
|
||||
if (last_mat)
|
||||
{
|
||||
loadMatImage(last_mat, mtl_file.c_str());
|
||||
}
|
||||
}
|
||||
else if (sscanf(buffer, "newmtl %s", mat_name) == 1)
|
||||
{
|
||||
last_mat = mesh->addMaterial(mat_name);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
bool loadMeshOBJ(TexturedMesh* mesh, const char* filename, const FMatrix3x3& matrix)
|
||||
{
|
||||
FILE* f = fopen(filename, "rt");
|
||||
if (f == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
char buffer[1024];
|
||||
FPoint3 vertex;
|
||||
Point3 vertex_indices;
|
||||
float texture_x;
|
||||
float texture_y;
|
||||
float temp;
|
||||
char face_index_buffer_1 [100];
|
||||
char face_index_buffer_2 [100];
|
||||
char face_index_buffer_3 [100];
|
||||
char str_buffer [100];
|
||||
while(fgets_(buffer, sizeof(buffer), f))
|
||||
{
|
||||
if (buffer[0] == '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (sscanf(buffer, "v %f %f %f", &vertex.x, &vertex.y, &vertex.z) == 3)
|
||||
{
|
||||
Point3 v = matrix.apply(vertex);
|
||||
mesh->addVertex(v);
|
||||
}
|
||||
else if (sscanf(buffer, "vt %f %f", &texture_x, &texture_y) == 2)
|
||||
{
|
||||
mesh->addTextureCoord(texture_x, texture_y);
|
||||
}
|
||||
else if (sscanf(buffer, "f %s %s %s", face_index_buffer_1, face_index_buffer_2, face_index_buffer_3) == 3)
|
||||
{
|
||||
int normal_vector_index; // unused
|
||||
Point3 texture_indices(0, 0, 0); // becomes -1 if no texture data supplied
|
||||
int n_scanned_1 = sscanf(face_index_buffer_1, "%d/%d/%d", &vertex_indices.x, &texture_indices.x, &normal_vector_index);
|
||||
int n_scanned_2 = sscanf(face_index_buffer_2, "%d/%d/%d", &vertex_indices.y, &texture_indices.y, &normal_vector_index);
|
||||
int n_scanned_3 = sscanf(face_index_buffer_3, "%d/%d/%d", &vertex_indices.z, &texture_indices.z, &normal_vector_index);
|
||||
if (n_scanned_1 > 0 && n_scanned_2 > 0 && n_scanned_3 > 0)
|
||||
{
|
||||
mesh->addFace(vertex_indices.x - 1, vertex_indices.y - 1, vertex_indices.z - 1, texture_indices.x - 1, texture_indices.y - 1, texture_indices.z - 1);
|
||||
// obj files count vertex indices starting from 1!
|
||||
}
|
||||
}
|
||||
else if (sscanf(buffer, "mtllib %s", str_buffer) == 1)
|
||||
{
|
||||
std::string parent_dir = std::string(filename).substr(0, std::string(filename).find_last_of("/\\"));
|
||||
std::string mtl_file = parent_dir + "/" + str_buffer;
|
||||
loadMaterialBase(mesh, mtl_file.c_str());
|
||||
}
|
||||
else if (sscanf(buffer, "usemtl %s", str_buffer) == 1)
|
||||
{
|
||||
mesh->setMaterial(str_buffer);
|
||||
}
|
||||
else if (sscanf(buffer, "vn %f %f %f", &temp, &temp, &temp) == 3)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
else if (buffer[0] == '\0')
|
||||
{
|
||||
// empty line, do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
logError("Cannot parse line \"%s\"\n", buffer);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
mesh->finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMatrix3x3& transformation, SettingsBaseVirtual* object_parent_settings)
|
||||
{
|
||||
const char* ext = strrchr(filename, '.');
|
||||
if (ext && (strcmp(ext, ".stl") == 0 || strcmp(ext, ".STL") == 0))
|
||||
{
|
||||
Mesh* mesh = new Mesh(object_parent_settings ? object_parent_settings : meshgroup); //If we have object_parent_settings, use them as parent settings. Otherwise, just use meshgroup.
|
||||
if (loadMeshSTL(mesh,filename,transformation)) //Load it! If successful...
|
||||
Mesh mesh = object_parent_settings ? Mesh(object_parent_settings) : Mesh(meshgroup); //If we have object_parent_settings, use them as parent settings. Otherwise, just use meshgroup.
|
||||
if(loadMeshSTL(&mesh,filename,transformation)) //Load it! If successful...
|
||||
{
|
||||
meshgroup->meshes.push_back(mesh);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
else if (ext && (strcmp(ext, ".obj") == 0 || strcmp(ext, ".OBJ") == 0))
|
||||
{
|
||||
TexturedMesh* mesh = new TexturedMesh(object_parent_settings ? object_parent_settings : meshgroup); //If we have object_parent_settings, use them as parent settings. Otherwise, just use meshgroup.
|
||||
if (loadMeshOBJ(mesh,filename,transformation)) //Load it! If successful...
|
||||
{
|
||||
meshgroup->meshes.push_back(mesh);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+14
-106
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "utils/NoCopy.h"
|
||||
#include "mesh.h"
|
||||
#include "TexturedMesh.h"
|
||||
#include "ExtruderTrain.h"
|
||||
|
||||
namespace cura
|
||||
@@ -21,120 +20,29 @@ class MeshGroup : public SettingsBase, NoCopy
|
||||
ExtruderTrain* extruders[MAX_EXTRUDERS] = {nullptr};
|
||||
int extruder_count;
|
||||
public:
|
||||
int getExtruderCount()
|
||||
{
|
||||
if (extruder_count == -1)
|
||||
{
|
||||
extruder_count = getSettingAsCount("machine_extruder_count");
|
||||
}
|
||||
return extruder_count;
|
||||
}
|
||||
int getExtruderCount();
|
||||
|
||||
MeshGroup(SettingsBaseVirtual* settings_base)
|
||||
: SettingsBase(settings_base)
|
||||
, extruder_count(-1)
|
||||
{}
|
||||
MeshGroup(SettingsBaseVirtual* settings_base);
|
||||
|
||||
~MeshGroup()
|
||||
{
|
||||
for (unsigned int extruder = 0; extruder < MAX_EXTRUDERS; extruder++)
|
||||
{
|
||||
if (extruders[extruder])
|
||||
{
|
||||
delete extruders[extruder];
|
||||
}
|
||||
}
|
||||
for (Mesh* mesh : meshes)
|
||||
{
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
~MeshGroup();
|
||||
|
||||
/*!
|
||||
* Create a new extruder train for the @p extruder_nr, or return the one which already exists.
|
||||
*/
|
||||
ExtruderTrain* createExtruderTrain(unsigned int extruder_nr)
|
||||
{
|
||||
if (!extruders[extruder_nr])
|
||||
{
|
||||
extruders[extruder_nr] = new ExtruderTrain(this, extruder_nr);
|
||||
}
|
||||
return extruders[extruder_nr];
|
||||
}
|
||||
|
||||
ExtruderTrain* getExtruderTrain(unsigned int extruder_nr)
|
||||
{
|
||||
assert(extruders[extruder_nr]);
|
||||
return extruders[extruder_nr];
|
||||
}
|
||||
|
||||
std::vector<Mesh*> meshes;
|
||||
ExtruderTrain* createExtruderTrain(unsigned int extruder_nr);
|
||||
|
||||
Point3 min() const //! minimal corner of bounding box
|
||||
{
|
||||
if (meshes.size() < 1)
|
||||
{
|
||||
return Point3(0, 0, 0);
|
||||
}
|
||||
Point3 ret = meshes[0]->min();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
{
|
||||
Point3 v = meshes[i]->min();
|
||||
ret.x = std::min(ret.x, v.x);
|
||||
ret.y = std::min(ret.y, v.y);
|
||||
ret.z = std::min(ret.z, v.z);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Point3 max() const //! maximal corner of bounding box
|
||||
{
|
||||
if (meshes.size() < 1)
|
||||
{
|
||||
return Point3(0, 0, 0);
|
||||
}
|
||||
Point3 ret = meshes[0]->max();
|
||||
for(unsigned int i=1; i<meshes.size(); i++)
|
||||
{
|
||||
Point3 v = meshes[i]->max();
|
||||
ret.x = std::max(ret.x, v.x);
|
||||
ret.y = std::max(ret.y, v.y);
|
||||
ret.z = std::max(ret.z, v.z);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ExtruderTrain* getExtruderTrain(unsigned int extruder_nr);
|
||||
|
||||
void clear()
|
||||
{
|
||||
for(Mesh* m : meshes)
|
||||
{
|
||||
m->clear();
|
||||
}
|
||||
}
|
||||
const ExtruderTrain* getExtruderTrain(unsigned int extruder_nr) const;
|
||||
|
||||
void finalize()
|
||||
{
|
||||
//If the machine settings have been supplied, offset the given position vertices to the center of vertices (0,0,0) is at the bed center.
|
||||
Point3 meshgroup_offset(0, 0, 0);
|
||||
if (!getSettingBoolean("machine_center_is_zero"))
|
||||
{
|
||||
meshgroup_offset.x = getSettingInMicrons("machine_width") / 2;
|
||||
meshgroup_offset.y = getSettingInMicrons("machine_depth") / 2;
|
||||
}
|
||||
|
||||
// If a mesh position was given, put the mesh at this position in 3D space.
|
||||
for(Mesh* mesh : meshes)
|
||||
{
|
||||
Point3 mesh_offset(mesh->getSettingInMicrons("mesh_position_x"), mesh->getSettingInMicrons("mesh_position_y"), mesh->getSettingInMicrons("mesh_position_z"));
|
||||
if (mesh->getSettingBoolean("center_object"))
|
||||
{
|
||||
Point3 object_min = mesh->min();
|
||||
Point3 object_max = mesh->max();
|
||||
Point3 object_size = object_max - object_min;
|
||||
mesh_offset += Point3(-object_min.x - object_size.x / 2, -object_min.y - object_size.y / 2, -object_min.z);
|
||||
}
|
||||
mesh->offset(mesh_offset + meshgroup_offset);
|
||||
}
|
||||
}
|
||||
std::vector<Mesh> meshes;
|
||||
|
||||
Point3 min() const; //! minimal corner of bounding box
|
||||
Point3 max() const; //! maximal corner of bounding box
|
||||
|
||||
void clear();
|
||||
|
||||
void finalize();
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -117,7 +117,7 @@ void PrimeTower::generateGroundpoly(SliceDataStorage& storage)
|
||||
|
||||
void PrimeTower::generatePaths(SliceDataStorage& storage, unsigned int total_layers)
|
||||
{
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0)
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingBoolean("prime_tower_enable"))
|
||||
{
|
||||
generatePaths3(storage);
|
||||
}
|
||||
@@ -125,7 +125,7 @@ void PrimeTower::generatePaths(SliceDataStorage& storage, unsigned int total_lay
|
||||
void PrimeTower::generatePaths_OLD(SliceDataStorage& storage, unsigned int total_layers)
|
||||
{
|
||||
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingInMicrons("prime_tower_size") > 0)
|
||||
if (storage.max_object_height_second_to_last_extruder >= 0 && storage.getSettingBoolean("prime_tower_enable"))
|
||||
{
|
||||
PolygonRef p = storage.primeTower.ground_poly.newPoly();
|
||||
int tower_size = storage.getSettingInMicrons("prime_tower_size");
|
||||
@@ -180,14 +180,12 @@ void PrimeTower::generatePaths3(SliceDataStorage& storage)
|
||||
for (int pattern_idx = 0; pattern_idx < n_patterns; pattern_idx++)
|
||||
{
|
||||
Polygons result_polygons; // should remain empty, since we generate lines pattern!
|
||||
Polygons* in_between = nullptr;
|
||||
bool avoidOverlappingPerimeters = false;
|
||||
int outline_offset = -line_width/2;
|
||||
int line_distance = line_width;
|
||||
double fill_angle = 45 + pattern_idx * 90;
|
||||
Polygons& result_lines = patterns[pattern_idx];
|
||||
Infill infill_comp(EFillMethod::LINES, ground_poly, outline_offset, avoidOverlappingPerimeters, line_width, line_distance, infill_overlap, fill_angle);
|
||||
infill_comp.generate(result_polygons, result_lines, in_between);
|
||||
Infill infill_comp(EFillMethod::LINES, ground_poly, outline_offset, line_width, line_distance, infill_overlap, fill_angle);
|
||||
infill_comp.generate(result_polygons, result_lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
#include "TextureProcessor.h"
|
||||
|
||||
#include <algorithm> // swap
|
||||
|
||||
#include "slicer/SlicerSegment.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
#define POINT_DIST 400
|
||||
#define AMPLITUDE 500
|
||||
#define EXTRA_OFFSET 500
|
||||
|
||||
/*
|
||||
void TextureProcessor::process(std::vector< Slicer* >& slicer_list)
|
||||
{
|
||||
for (Slicer* slicer : slicer_list)
|
||||
{
|
||||
for (SlicerLayer& layer : slicer->layers)
|
||||
{
|
||||
process(slicer->mesh, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void TextureProcessor::processBumpMap(Mesh* mesh, SlicerLayer& layer)
|
||||
{
|
||||
process(mesh, layer, false);
|
||||
}
|
||||
|
||||
void TextureProcessor::processDualColorTexture(Mesh* mesh, SlicerLayer& layer)
|
||||
{
|
||||
process(mesh, layer, true);
|
||||
}
|
||||
|
||||
void TextureProcessor::process(Mesh* mesh, SlicerLayer& layer, bool dual_color_texture)
|
||||
{
|
||||
bool flipped = false;
|
||||
if (dual_color_texture)
|
||||
{
|
||||
flipped = layer.layer_nr % 2 == 0;
|
||||
}
|
||||
Polygons results;
|
||||
for (PolygonRef poly : layer.polygonList)
|
||||
{
|
||||
// generate points in between p0 and p1
|
||||
PolygonRef result = results.newPoly();
|
||||
|
||||
int64_t dist_left_over = (POINT_DIST / 2); // the distance to be traversed on the line before making the first new point
|
||||
Point* p0 = &poly.back();
|
||||
bool even = false;
|
||||
for (Point& p1 : poly)
|
||||
{
|
||||
SlicerSegment segment(*p0, p1);
|
||||
auto it = layer.segment_to_material_segment.find(segment);
|
||||
if (it != layer.segment_to_material_segment.end())
|
||||
{
|
||||
MatSegment& mat = it->second;
|
||||
MatCoord mat_start = mat.start;
|
||||
MatCoord mat_end = mat.end;
|
||||
if (it->first.start != *p0)
|
||||
{
|
||||
std::swap(mat_start, mat_end);
|
||||
}
|
||||
Point p0p1 = p1 - *p0;
|
||||
Point perp_to_p0p1 = turn90CCW(p0p1);
|
||||
int64_t p0p1_size = vSize(p0p1);
|
||||
int64_t dist_last_point = dist_left_over + p0p1_size * 2; // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
|
||||
// TODO: move start point (which was already moved last iteration
|
||||
for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += POINT_DIST)
|
||||
{
|
||||
Point pa = *p0 + normal(p0p1, p0pa_dist);
|
||||
if (even ^ flipped)
|
||||
{
|
||||
MatCoord mat_coord_now = mat_start;
|
||||
mat_coord_now.coords = mat_start.coords + (mat_end.coords - mat_start.coords) * p0pa_dist / p0p1_size;
|
||||
float val = mesh->getColor(mat_coord_now); // between 0 and 1
|
||||
if (flipped)
|
||||
{
|
||||
val = 1.0f - val;
|
||||
}
|
||||
assert(val > -0.001 && val < 1.001);
|
||||
int r = val * (AMPLITUDE * 2) - AMPLITUDE + EXTRA_OFFSET;
|
||||
Point displacement = normal(perp_to_p0p1, r);
|
||||
pa -= displacement;
|
||||
}
|
||||
result.add(pa);
|
||||
dist_last_point = p0pa_dist;
|
||||
even = !even;
|
||||
}
|
||||
// TODO: move end point as well
|
||||
float val = mesh->getColor(mat_end);
|
||||
int r = val * (AMPLITUDE * 2) - AMPLITUDE + EXTRA_OFFSET;
|
||||
Point displacement = normal(perp_to_p0p1, r);
|
||||
if (dual_color_texture)
|
||||
{
|
||||
result.emplace_back(p1);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.emplace_back(p1 - displacement);
|
||||
}
|
||||
dist_left_over = p0p1_size - dist_last_point;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.emplace_back(p1);
|
||||
}
|
||||
p0 = &p1;
|
||||
}
|
||||
while (result.size() < 3 )
|
||||
{
|
||||
unsigned int point_idx = poly.size() - 2;
|
||||
result.add(poly[point_idx]);
|
||||
if (point_idx == 0) { break; }
|
||||
point_idx--;
|
||||
}
|
||||
if (result.size() < 3)
|
||||
{
|
||||
result.clear();
|
||||
for (Point& p : poly)
|
||||
result.add(p);
|
||||
}
|
||||
}
|
||||
layer.polygonList = results;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,32 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURE_PROCESSOR_H
|
||||
#define TEXTURE_PROCESSOR_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "slicer/Slicer.h"
|
||||
#include "mesh.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class TextureProcessor
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Apply offsets in the xy plane corresponding to pixel intensities
|
||||
*/
|
||||
static void processBumpMap(Mesh* mesh, SlicerLayer& layer);
|
||||
|
||||
/*!
|
||||
* Apply a zigzag pattern with offsets corresponding to pixel intensities
|
||||
*/
|
||||
static void processDualColorTexture(Mesh* mesh, SlicerLayer& layer);
|
||||
protected:
|
||||
static void process(Mesh* mesh, SlicerLayer& layer, bool dual_color_texture);
|
||||
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURE_PROCESSOR_H
|
||||
@@ -1,137 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "TexturedMesh.h"
|
||||
|
||||
#include <cassert>
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
TexturedMesh::TexturedMesh(SettingsBaseVirtual* sb)
|
||||
: Mesh(sb)
|
||||
, current_mat(-1) // not set yet
|
||||
{
|
||||
}
|
||||
|
||||
void TexturedMesh::addTextureCoord(float x, float y)
|
||||
{
|
||||
texture_coords.emplace_back(x, y);
|
||||
}
|
||||
|
||||
void TexturedMesh::addFace(int vi0, int vi1, int vi2, int ti0, int ti1, int ti2)
|
||||
{
|
||||
if (vi0 < -1)
|
||||
{
|
||||
vi0 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (vi1 < -1)
|
||||
{
|
||||
vi1 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (vi2 < -1)
|
||||
{
|
||||
vi2 = Mesh::faces.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (ti0 < -1)
|
||||
{
|
||||
ti0 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (ti1 < -1)
|
||||
{
|
||||
ti1 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
if (ti2 < -1)
|
||||
{
|
||||
ti2 = texture_coords.size() + vi0 + 1; // + 1 because of relative indexing doesn't start counting from 1
|
||||
}
|
||||
bool made_new_face = Mesh::addFace(vi0, vi1, vi2);
|
||||
if (made_new_face)
|
||||
{
|
||||
face_texture_indices.emplace_back(ti0, ti1, ti2, current_mat);
|
||||
assert(Mesh::faces.size() == face_texture_indices.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool TexturedMesh::setMaterial(std::string name)
|
||||
{
|
||||
current_mat = material_base.getMatId(name);
|
||||
return current_mat >= 0;
|
||||
}
|
||||
|
||||
Material* TexturedMesh::addMaterial(std::__cxx11::string name)
|
||||
{
|
||||
return material_base.add(name);
|
||||
}
|
||||
|
||||
|
||||
bool TexturedMesh::getFaceEdgeMatCoord(unsigned int face_idx, int64_t z, unsigned int p0_idx, unsigned int p1_idx, MatCoord& result)
|
||||
{
|
||||
if (face_idx >= face_texture_indices.size() || face_idx >= faces.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FaceTextureCoordIndices texture_idxs = face_texture_indices[face_idx];
|
||||
if (texture_idxs.index[0] < 0 || texture_idxs.index[1] < 0 || texture_idxs.index[2] < 0 || texture_idxs.mat_id < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
MeshFace& face = faces[face_idx];
|
||||
Point3 p0(vertices[face.vertex_index[p0_idx]].p);
|
||||
Point3 p1(vertices[face.vertex_index[p1_idx]].p);
|
||||
|
||||
float dzp0 = z - p0.z;
|
||||
float dp0p1 = p1.z - p0.z;
|
||||
|
||||
if (dzp0 * dp0p1 < 0)
|
||||
{ // z doesn't lie between p0 and p1
|
||||
return false;
|
||||
}
|
||||
if (dzp0 == 0)
|
||||
{ // edge is not cut by horizontal plane!
|
||||
return false;
|
||||
}
|
||||
float ratio = INT2MM(dzp0) / INT2MM(dp0p1);
|
||||
|
||||
FPoint t0 = texture_coords[texture_idxs.index[p0_idx]];
|
||||
FPoint t1 = texture_coords[texture_idxs.index[p1_idx]];
|
||||
|
||||
result.mat_id = texture_idxs.mat_id;
|
||||
result.coords.x = t0.x + (t1.x - t0.x) * ratio;
|
||||
result.coords.y = t0.y + (t1.y - t0.y) * ratio;
|
||||
|
||||
if (result.coords.x > 1.001 || result.coords.x < -0.001 || result.coords.y > 1.001 || result.coords.y < -0.001)
|
||||
{
|
||||
logError("WARNING: wapping material to outside image!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TexturedMesh::registerFaceSlice(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result)
|
||||
{
|
||||
if (!getFaceEdgeMatCoord(face_idx, z, idx_shared, idx_first, result.start))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!getFaceEdgeMatCoord(face_idx, z, idx_shared, idx_second, result.end))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float TexturedMesh::getColor(MatCoord bitmap_coord)
|
||||
{
|
||||
Material* mat = material_base.getMat(bitmap_coord.mat_id);
|
||||
if (mat)
|
||||
{
|
||||
return mat->getColor(bitmap_coord.coords.x, bitmap_coord.coords.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -1,78 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef TEXTURED_MESH_H
|
||||
#define TEXTURED_MESH_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "MaterialBase.h"
|
||||
|
||||
#include "mesh.h"
|
||||
#include "utils/intpoint.h"
|
||||
#include "MatSegment.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A mesh with bitmap textures to it.
|
||||
*
|
||||
* material coordinates are defined separately, and can be reused for different bitmap textures
|
||||
*/
|
||||
class TexturedMesh : public Mesh
|
||||
{
|
||||
public:
|
||||
TexturedMesh(SettingsBaseVirtual* sb);
|
||||
|
||||
|
||||
/*!
|
||||
*
|
||||
*/
|
||||
struct FaceTextureCoordIndices
|
||||
{
|
||||
int index[3]; //!< indices into texture_coords or -1 if no texture data available
|
||||
int mat_id; //!< Material id
|
||||
FaceTextureCoordIndices(int i1, int i2, int i3, int mat_id)
|
||||
: mat_id(mat_id)
|
||||
{
|
||||
index[0] = i1;
|
||||
index[1] = i2;
|
||||
index[2] = i3;
|
||||
}
|
||||
};
|
||||
void addTextureCoord(float x, float y);
|
||||
void addFace(int vi0, int vi1, int vi2, int ti0, int ti1, int ti2);
|
||||
using Mesh::addFace; // otherwise above addFace would shadow the parent addFace
|
||||
|
||||
bool setMaterial(std::string name); //!< set the material to be used in the comming data to be loaded
|
||||
Material* addMaterial(std::string name);
|
||||
|
||||
|
||||
|
||||
virtual bool registerFaceSlice(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result);
|
||||
|
||||
protected:
|
||||
std::vector<FPoint> texture_coords; //!< all texture coordinates by all faces
|
||||
std::vector<FaceTextureCoordIndices> face_texture_indices; //!< for each face the corresponding texture coordinates in TexturedMesh::texture_coords
|
||||
// TODO clean up above lists when super class clear() is called
|
||||
// TODO when to clean up below material base?
|
||||
MaterialBase material_base;
|
||||
/*!
|
||||
* Get the material coordinate corresponding to the point on a plane cutting a given edge of the face.
|
||||
* \param face_idx The face for which to get the material coord
|
||||
* \param z The z of the horizontal plane cutting the face
|
||||
* \param p0_idx The index into the first vert of the edge
|
||||
* \param p1_idx The index into the second vert of the edge
|
||||
* \param result The resulting material Coordinates
|
||||
* \return Whether a Material coordinate is defined at the given location
|
||||
*/
|
||||
bool getFaceEdgeMatCoord(unsigned int face_idx, int64_t z, unsigned int p0_idx, unsigned int p1_idx, MatCoord& result);
|
||||
|
||||
virtual float getColor(MatCoord bitmap_coord);
|
||||
private:
|
||||
int current_mat; //!< material currently used in loading the face material info
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // TEXTURED_MESH_H
|
||||
@@ -0,0 +1,79 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "WallsComputation.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
namespace cura {
|
||||
|
||||
WallsComputation::WallsComputation(int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool recompute_outline_based_on_outer_wall)
|
||||
: wall_0_inset(wall_0_inset)
|
||||
, line_width_0(line_width_0)
|
||||
, line_width_x(line_width_x)
|
||||
, insetCount(insetCount)
|
||||
, recompute_outline_based_on_outer_wall(recompute_outline_based_on_outer_wall)
|
||||
{
|
||||
}
|
||||
|
||||
void WallsComputation::generateInsets(SliceLayerPart* part)
|
||||
{
|
||||
if (insetCount == 0)
|
||||
{
|
||||
part->insets.push_back(part->outline);
|
||||
part->print_outline = part->outline;
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i=0; i<insetCount; i++)
|
||||
{
|
||||
part->insets.push_back(Polygons());
|
||||
if (i == 0)
|
||||
{
|
||||
part->insets[0] = part->outline.offset(-line_width_0 / 2 - wall_0_inset);
|
||||
} else if (i == 1)
|
||||
{
|
||||
part->insets[1] = part->insets[0].offset(-line_width_0 / 2 + wall_0_inset - line_width_x / 2);
|
||||
} else
|
||||
{
|
||||
part->insets[i] = part->insets[i-1].offset(-line_width_x);
|
||||
}
|
||||
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
part->insets[i].simplify();
|
||||
if (i == 0)
|
||||
{
|
||||
if (recompute_outline_based_on_outer_wall)
|
||||
{
|
||||
part->print_outline = part->insets[0].offset(line_width_0 / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
part->print_outline = part->outline;
|
||||
}
|
||||
}
|
||||
if (part->insets[i].size() < 1)
|
||||
{
|
||||
part->insets.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallsComputation::generateInsets(SliceLayer* layer)
|
||||
{
|
||||
for(unsigned int partNr = 0; partNr < layer->parts.size(); partNr++)
|
||||
{
|
||||
generateInsets(&layer->parts[partNr]);
|
||||
}
|
||||
|
||||
//Remove the parts which did not generate an inset. As these parts are too small to print,
|
||||
// and later code can now assume that there is always minimal 1 inset line.
|
||||
for(unsigned int partNr = 0; partNr < layer->parts.size(); partNr++)
|
||||
{
|
||||
if (layer->parts[partNr].insets.size() < 1)
|
||||
{
|
||||
layer->parts.erase(layer->parts.begin() + partNr);
|
||||
partNr -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,69 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef INSET_H
|
||||
#define INSET_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Function container for computing the outer walls / insets / perimeters polygons of a layer
|
||||
*/
|
||||
class WallsComputation
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The offset applied to the outer wall
|
||||
*/
|
||||
int wall_0_inset;
|
||||
/*!
|
||||
* line width of the outer wall
|
||||
*/
|
||||
int line_width_0;
|
||||
/*!
|
||||
* line width of other walls
|
||||
*/
|
||||
int line_width_x;
|
||||
/*!
|
||||
* The number of insets to to generate
|
||||
*/
|
||||
int insetCount;
|
||||
/*!
|
||||
* Whether to compute a more accurate poly representation of the printed outlines, based on the outer wall
|
||||
*/
|
||||
bool recompute_outline_based_on_outer_wall;
|
||||
|
||||
/*!
|
||||
* Basic constructor initializing the parameters with which to perform the walls computation
|
||||
*
|
||||
* \param wall_0_inset The offset applied to the outer wall
|
||||
* \param line_width_0 line width of the outer wall
|
||||
* \param line_width_x line width of other walls
|
||||
* \param insetCount The number of insets to to generate
|
||||
* \param recompute_outline_based_on_outer_wall Whether to compute a more accurate poly representation of the printed outlines, based on the outer wall
|
||||
*/
|
||||
WallsComputation(int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool recompute_outline_based_on_outer_wall);
|
||||
|
||||
/*!
|
||||
* Generates the insets / perimeters for all parts in a layer.
|
||||
*
|
||||
* Note that the second inset gets offsetted by WallsComputation::line_width_0 instead of the first,
|
||||
* which leads to better results for a smaller WallsComputation::line_width_0 than WallsComputation::line_width_x and when printing the outer wall last.
|
||||
*
|
||||
* \param layer The layer for which to generate the insets.
|
||||
*/
|
||||
void generateInsets(SliceLayer* layer);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Generates the insets / perimeters for a single layer part.
|
||||
*
|
||||
* \param part The part for which to generate the insets.
|
||||
*/
|
||||
void generateInsets(SliceLayerPart* part);
|
||||
|
||||
};
|
||||
}//namespace cura
|
||||
|
||||
#endif//INSET_H
|
||||
+10
-10
@@ -4,7 +4,7 @@
|
||||
#include <fstream> // debug IO
|
||||
#include <unistd.h>
|
||||
|
||||
#include "Progress.h"
|
||||
#include "progress/Progress.h"
|
||||
#include "weaveDataStorage.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
@@ -23,9 +23,9 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
|
||||
std::vector<cura::Slicer*> slicerList;
|
||||
|
||||
for (Mesh* mesh : meshgroup->meshes)
|
||||
for(Mesh& mesh : meshgroup->meshes)
|
||||
{
|
||||
cura::Slicer* slicer = new cura::Slicer(mesh, initial_layer_thickness, connectionHeight, layer_count, mesh->getSettingBoolean("meshfix_keep_open_polygons"), mesh->getSettingBoolean("meshfix_extensive_stitching"));
|
||||
cura::Slicer* slicer = new cura::Slicer(&mesh, initial_layer_thickness, connectionHeight, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching"));
|
||||
slicerList.push_back(slicer);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
{
|
||||
Polygons parts;
|
||||
for (cura::Slicer* slicer : slicerList)
|
||||
parts.add(slicer->layers[starting_layer_idx].polygonList);
|
||||
parts.add(slicer->layers[starting_layer_idx].polygons);
|
||||
|
||||
if (parts.size() > 0)
|
||||
break;
|
||||
@@ -51,7 +51,7 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
{
|
||||
int starting_z = -1;
|
||||
for (cura::Slicer* slicer : slicerList)
|
||||
wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygonList);
|
||||
wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygons);
|
||||
|
||||
if (CommandSocket::isInstantiated())
|
||||
CommandSocket::getInstance()->sendPolygons(PrintFeatureType::OuterWall, 0, wireFrame.bottom_outline, 1);
|
||||
@@ -71,14 +71,14 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
else
|
||||
starting_point_in_layer = (Point(0,0) + meshgroup->max() + meshgroup->min()) / 2;
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::INSET, nullptr);
|
||||
Progress::messageProgressStage(Progress::Stage::INSET_SKIN, nullptr);
|
||||
for (int layer_idx = starting_layer_idx + 1; layer_idx < layer_count; layer_idx++)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::INSET, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
|
||||
Progress::messageProgress(Progress::Stage::INSET_SKIN, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
|
||||
|
||||
Polygons parts1;
|
||||
for (cura::Slicer* slicer : slicerList)
|
||||
parts1.add(slicer->layers[layer_idx].polygonList);
|
||||
parts1.add(slicer->layers[layer_idx].polygons);
|
||||
|
||||
|
||||
Polygons chainified;
|
||||
@@ -109,10 +109,10 @@ void Weaver::weave(MeshGroup* meshgroup)
|
||||
{
|
||||
Polygons* lower_top_parts = &wireFrame.bottom_outline;
|
||||
|
||||
Progress::messageProgressStage(Progress::Stage::SKIN, nullptr);
|
||||
Progress::messageProgressStage(Progress::Stage::SUPPORT, nullptr);
|
||||
for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++)
|
||||
{
|
||||
Progress::messageProgress(Progress::Stage::SKIN, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
|
||||
Progress::messageProgress(Progress::Stage::SUPPORT, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
|
||||
|
||||
WeaveLayer& layer = wireFrame.layers[layer_idx];
|
||||
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@
|
||||
|
||||
#include "weaveDataStorage.h"
|
||||
#include "commandSocket.h"
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
|
||||
#include "MeshGroup.h"
|
||||
#include "slicer/Slicer.h"
|
||||
#include "slicer.h"
|
||||
|
||||
#include "utils/NoCopy.h"
|
||||
#include "utils/polygon.h"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <fstream> // debug IO
|
||||
|
||||
#include "weaveDataStorage.h"
|
||||
#include "Progress.h"
|
||||
#include "progress/Progress.h"
|
||||
|
||||
#include "pathOrderOptimizer.h" // for skirt
|
||||
|
||||
@@ -17,6 +17,8 @@ void Wireframe2gcode::writeGCode()
|
||||
|
||||
gcode.preSetup(wireFrame.meshgroup);
|
||||
|
||||
gcode.setInitialTemps(wireFrame.meshgroup);
|
||||
|
||||
if (CommandSocket::getInstance())
|
||||
CommandSocket::getInstance()->beginGCode();
|
||||
|
||||
@@ -239,7 +241,7 @@ void Wireframe2gcode::strategy_retract(WeaveLayer& layer, WeaveConnectionPart& p
|
||||
retraction_config.primeSpeed = 15; // 30;
|
||||
retraction_config.zHop = 0; //getSettingInt("retraction_hop");
|
||||
retraction_config.retraction_count_max = getSettingAsCount("retraction_count_max");
|
||||
retraction_config.retraction_extrusion_window = INT2MM(getSettingInMicrons("retraction_extrusion_window"));
|
||||
retraction_config.retraction_extrusion_window = getSettingInMillimeters("retraction_extrusion_window");
|
||||
retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
|
||||
|
||||
double top_retract_pause = 2.0;
|
||||
@@ -534,24 +536,21 @@ Wireframe2gcode::Wireframe2gcode(Weaver& weaver, GCodeExport& gcode, SettingsBas
|
||||
roof_outer_delay = getSettingInSeconds("wireframe_roof_outer_delay");
|
||||
|
||||
|
||||
standard_retraction_config.distance = INT2MM(getSettingInMicrons("retraction_amount"));
|
||||
standard_retraction_config.distance = getSettingInMillimeters("retraction_amount");
|
||||
standard_retraction_config.prime_volume = getSettingInCubicMillimeters("retraction_extra_prime_amount");
|
||||
standard_retraction_config.speed = getSettingInMillimetersPerSecond("retraction_retract_speed");
|
||||
standard_retraction_config.primeSpeed = getSettingInMillimetersPerSecond("retraction_prime_speed");
|
||||
standard_retraction_config.zHop = getSettingInMicrons("retraction_hop");
|
||||
standard_retraction_config.retraction_count_max = getSettingAsCount("retraction_count_max");
|
||||
standard_retraction_config.retraction_extrusion_window = INT2MM(getSettingInMicrons("retraction_extrusion_window"));
|
||||
standard_retraction_config.retraction_extrusion_window = getSettingInMillimeters("retraction_extrusion_window");
|
||||
standard_retraction_config.retraction_min_travel_distance = getSettingInMicrons("retraction_min_travel");
|
||||
}
|
||||
|
||||
void Wireframe2gcode::processStartingCode()
|
||||
{
|
||||
if (gcode.getFlavor() == EGCodeFlavor::ULTIGCODE)
|
||||
if (!CommandSocket::isInstantiated())
|
||||
{
|
||||
if (!CommandSocket::isInstantiated())
|
||||
{
|
||||
gcode.writeCode(";FLAVOR:UltiGCode\n;TIME:666\n;MATERIAL:666\n;MATERIAL2:-1\n");
|
||||
}
|
||||
gcode.writeCode(gcode.getFileHeader().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -616,7 +615,7 @@ void Wireframe2gcode::processSkirt()
|
||||
|
||||
void Wireframe2gcode::finalize()
|
||||
{
|
||||
gcode.finalize(getSettingInMillimetersPerSecond("speed_travel"), getSettingString("machine_end_gcode").c_str());
|
||||
gcode.finalize(getSettingString("machine_end_gcode").c_str());
|
||||
for(int e=0; e<getSettingAsCount("machine_extruder_count"); e++)
|
||||
gcode.writeTemperatureCommand(e, 0, false);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
#include "weaveDataStorage.h"
|
||||
#include "commandSocket.h"
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
|
||||
#include "MeshGroup.h"
|
||||
#include "slicer/Slicer.h"
|
||||
#include "slicer.h"
|
||||
|
||||
#include "utils/polygon.h"
|
||||
#include "Weaver.h"
|
||||
|
||||
+161
-70
@@ -11,15 +11,26 @@ namespace cura {
|
||||
|
||||
|
||||
// boundary_outside is only computed when it's needed!
|
||||
Polygons* Comb::getBoundaryOutside()
|
||||
Polygons& Comb::getBoundaryOutside()
|
||||
{
|
||||
if (!boundary_outside)
|
||||
{
|
||||
boundary_outside = new Polygons();
|
||||
*boundary_outside = storage.getLayerOutlines(layer_nr, false).offset(offset_from_outlines_outside);
|
||||
*boundary_outside = storage.getLayerOutlines(layer_nr, false).offset(offset_from_outlines_outside);
|
||||
}
|
||||
return boundary_outside;
|
||||
return *boundary_outside;
|
||||
}
|
||||
|
||||
BucketGrid2D<PolygonsPointIndex>& Comb::getOutsideLocToLine()
|
||||
{
|
||||
Polygons& outside = getBoundaryOutside();
|
||||
if (!outside_loc_to_line)
|
||||
{
|
||||
outside_loc_to_line = PolygonUtils::createLocToLineGrid(outside, offset_from_outlines_outside * 3 / 2);
|
||||
}
|
||||
return *outside_loc_to_line;
|
||||
}
|
||||
|
||||
|
||||
Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_inside, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
@@ -27,10 +38,12 @@ Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_insi
|
||||
, offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls
|
||||
, max_moveInside_distance2(offset_from_outlines * 2 * offset_from_outlines * 2)
|
||||
, offset_from_outlines_outside(travel_avoid_distance)
|
||||
, max_crossing_dist2(offset_from_outlines_outside * offset_from_outlines_outside * 3) // so max_crossing_dist = offset_from_outlines_outside * sqrt(3), which is a bit more than sqrt(2) which is necesary for 90* corners
|
||||
, avoid_other_parts(travel_avoid_other_parts)
|
||||
// , boundary_inside( boundary.offset(-offset_from_outlines) ) // TODO: make inside boundary configurable?
|
||||
, boundary_inside( comb_boundary_inside )
|
||||
, boundary_outside(nullptr)
|
||||
, outside_loc_to_line(nullptr)
|
||||
, partsView_inside( boundary_inside.splitIntoPartsView() ) // !! changes the order of boundary_inside !!
|
||||
{
|
||||
}
|
||||
@@ -38,7 +51,13 @@ Comb::Comb(SliceDataStorage& storage, int layer_nr, Polygons& comb_boundary_insi
|
||||
Comb::~Comb()
|
||||
{
|
||||
if (boundary_outside)
|
||||
{
|
||||
delete boundary_outside;
|
||||
}
|
||||
if (outside_loc_to_line)
|
||||
{
|
||||
delete outside_loc_to_line;
|
||||
}
|
||||
}
|
||||
|
||||
bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool startInside, bool endInside, int64_t max_comb_distance_ignored)
|
||||
@@ -47,9 +66,7 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool sta
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Move start and end point inside the comb boundary
|
||||
unsigned int start_inside_poly = NO_INDEX;
|
||||
if (startInside)
|
||||
@@ -99,90 +116,131 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool sta
|
||||
}
|
||||
else
|
||||
{ // comb inside part to edge (if needed) >> move through air avoiding other parts >> comb inside end part upto the endpoint (if needed)
|
||||
Point middle_from;
|
||||
Point middle_to;
|
||||
Point inside_middle_from;
|
||||
Point inside_middle_to;
|
||||
// INSIDE | in_between | OUTSIDE | in_between | INSIDE
|
||||
// ^crossing_1_in ^crossing_1_mid ^crossing_1_out ^crossing_2_out ^crossing_2_mid ^crossing_2_in
|
||||
//
|
||||
// when startPoint is inside crossing_1_in is of interest
|
||||
// when it is in between inside and outside it is equal to crossing_1_mid
|
||||
Point crossing_1_in_or_mid; // the point inside the starting polygon if startPoint is inside or the startPoint itself if it is not inside
|
||||
Point crossing_1_out;
|
||||
Point crossing_2_in_or_mid; // the point inside the ending polygon if endPoint is inside or the endPoint itself if it is not inside
|
||||
Point crossing_2_out;
|
||||
|
||||
if (startInside && endInside)
|
||||
{
|
||||
ClosestPolygonPoint middle_from_cp = PolygonUtils::findClosest(endPoint, boundary_inside[start_part_boundary_poly_idx]);
|
||||
ClosestPolygonPoint middle_to_cp = PolygonUtils::findClosest(middle_from_cp.location, boundary_inside[end_part_boundary_poly_idx]);
|
||||
// walkToNearestSmallestConnection(middle_from_cp, middle_to_cp); // TODO: perform this optimization?
|
||||
middle_from = middle_from_cp.location;
|
||||
inside_middle_from = middle_from_cp.location;
|
||||
middle_to = middle_to_cp.location;
|
||||
inside_middle_to = middle_to_cp.location;
|
||||
PolygonUtils::moveInside(boundary_inside,inside_middle_from,offset_dist_to_get_from_on_the_polygon_to_outside,max_comb_distance_ignored); //Also move the intermediary waypoint inside if it isn't yet.
|
||||
PolygonUtils::moveInside(boundary_inside,inside_middle_to,offset_dist_to_get_from_on_the_polygon_to_outside,max_comb_distance_ignored);
|
||||
}
|
||||
else if(!startInside && !endInside)
|
||||
{
|
||||
middle_from = startPoint;
|
||||
inside_middle_from = startPoint;
|
||||
middle_to = endPoint;
|
||||
inside_middle_to = endPoint;
|
||||
}
|
||||
else if(!startInside && endInside)
|
||||
{
|
||||
middle_from = startPoint;
|
||||
inside_middle_from = startPoint;
|
||||
ClosestPolygonPoint middle_to_cp = PolygonUtils::findClosest(middle_from,boundary_inside[end_part_boundary_poly_idx]);
|
||||
middle_to = middle_to_cp.location;
|
||||
inside_middle_to = middle_to_cp.location;
|
||||
PolygonUtils::moveInside(boundary_inside,inside_middle_to,offset_dist_to_get_from_on_the_polygon_to_outside,max_comb_distance_ignored);
|
||||
}
|
||||
else if(startInside && !endInside)
|
||||
{
|
||||
middle_to = endPoint;
|
||||
inside_middle_to = endPoint;
|
||||
ClosestPolygonPoint middle_from_cp = PolygonUtils::findClosest(middle_to,boundary_inside[start_part_boundary_poly_idx]);
|
||||
middle_from = middle_from_cp.location;
|
||||
inside_middle_from = middle_from_cp.location;
|
||||
PolygonUtils::moveInside(boundary_inside,inside_middle_from,offset_dist_to_get_from_on_the_polygon_to_outside,max_comb_distance_ignored);
|
||||
{ // find crossing over the in-between area between inside and outside
|
||||
if (startInside)
|
||||
{
|
||||
ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::findClosest(endPoint, boundary_inside[start_part_boundary_poly_idx]);
|
||||
crossing_1_in_or_mid = PolygonUtils::moveInside(crossing_1_in_cp, offset_dist_to_get_from_on_the_polygon_to_outside); // in-case
|
||||
}
|
||||
else
|
||||
{
|
||||
crossing_1_in_or_mid = startPoint; // mid-case
|
||||
}
|
||||
|
||||
if (endInside)
|
||||
{
|
||||
ClosestPolygonPoint crossing_2_in_cp = PolygonUtils::findClosest(crossing_1_in_or_mid, boundary_inside[end_part_boundary_poly_idx]);
|
||||
crossing_2_in_or_mid = PolygonUtils::moveInside(crossing_2_in_cp, offset_dist_to_get_from_on_the_polygon_to_outside); // in-case
|
||||
}
|
||||
else
|
||||
{
|
||||
crossing_2_in_or_mid = endPoint; // mid-case
|
||||
}
|
||||
}
|
||||
|
||||
bool avoid_other_parts_now = avoid_other_parts;
|
||||
if (avoid_other_parts_now && vSize2(crossing_1_in_or_mid - crossing_2_in_or_mid) < offset_from_outlines_outside * offset_from_outlines_outside * 4)
|
||||
{ // parts are next to eachother, i.e. the direct crossing will always be smaller than two crossings via outside
|
||||
avoid_other_parts_now = false;
|
||||
}
|
||||
|
||||
if (avoid_other_parts_now)
|
||||
{ // compute the crossing points when moving through air
|
||||
Polygons& outside = getBoundaryOutside(); // comb through all air, since generally the outside consists of a single part
|
||||
|
||||
|
||||
crossing_1_out = crossing_1_in_or_mid;
|
||||
if (startInside || outside.inside(crossing_1_in_or_mid, true)) // start in_between
|
||||
{ // move outside
|
||||
ClosestPolygonPoint* crossing_1_out_cpp = PolygonUtils::findClose(crossing_1_in_or_mid, outside, getOutsideLocToLine());
|
||||
if (crossing_1_out_cpp)
|
||||
{
|
||||
crossing_1_out = PolygonUtils::moveOutside(*crossing_1_out_cpp, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
else
|
||||
{
|
||||
PolygonUtils::moveOutside(outside, crossing_1_out, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
int64_t in_out_dist2_1 = vSize2(crossing_1_out - crossing_1_in_or_mid);
|
||||
if (startInside && in_out_dist2_1 > max_crossing_dist2) // moveInside moved too far
|
||||
{ // if move is to far over in_between
|
||||
// find crossing closer by
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(boundary_inside[start_part_boundary_poly_idx], startPoint, endPoint);
|
||||
if (best)
|
||||
{
|
||||
crossing_1_in_or_mid = PolygonUtils::moveInside(best->first, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
crossing_1_out = PolygonUtils::moveOutside(best->second, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
crossing_2_out = crossing_2_in_or_mid;
|
||||
if (endInside || outside.inside(crossing_2_in_or_mid, true))
|
||||
{ // move outside
|
||||
ClosestPolygonPoint* crossing_2_out_cpp = PolygonUtils::findClose(crossing_2_in_or_mid, outside, getOutsideLocToLine());
|
||||
if (crossing_2_out_cpp)
|
||||
{
|
||||
crossing_2_out = PolygonUtils::moveOutside(*crossing_2_out_cpp, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
else
|
||||
{
|
||||
PolygonUtils::moveOutside(outside, crossing_2_out, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
int64_t in_out_dist2_2 = vSize2(crossing_2_out - crossing_2_in_or_mid);
|
||||
if (endInside && in_out_dist2_2 > max_crossing_dist2) // moveInside moved too far
|
||||
{ // if move is to far over in_between
|
||||
// find crossing closer by
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> best = findBestCrossing(boundary_inside[end_part_boundary_poly_idx], endPoint, crossing_1_out);
|
||||
if (best)
|
||||
{
|
||||
crossing_2_in_or_mid = PolygonUtils::moveInside(best->first, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
crossing_2_out = PolygonUtils::moveOutside(best->second, offset_dist_to_get_from_on_the_polygon_to_outside);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startInside)
|
||||
{
|
||||
// start to boundary
|
||||
PolygonsPart part_begin = partsView_inside.assemblePart(start_part_idx); // comb through the starting part only
|
||||
combPaths.emplace_back();
|
||||
LinePolygonsCrossings::comb(part_begin, startPoint, inside_middle_from, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
LinePolygonsCrossings::comb(part_begin, startPoint, crossing_1_in_or_mid, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
}
|
||||
|
||||
// throught air from boundary to boundary
|
||||
if (avoid_other_parts)
|
||||
if (avoid_other_parts_now)
|
||||
{
|
||||
Polygons& middle = *getBoundaryOutside(); // comb through all air, since generally the outside consists of a single part
|
||||
Point from_outside = middle_from;
|
||||
if (startInside || middle.inside(from_outside, true))
|
||||
{ // move outside
|
||||
PolygonUtils::moveInside(middle, from_outside, -offset_extra_start_end, max_moveInside_distance2);
|
||||
}
|
||||
Point to_outside = middle_to;
|
||||
if (endInside || middle.inside(to_outside, true))
|
||||
{ // move outside
|
||||
PolygonUtils::moveInside(middle, to_outside, -offset_extra_start_end, max_moveInside_distance2);
|
||||
}
|
||||
combPaths.emplace_back();
|
||||
combPaths.back().throughAir = true;
|
||||
if ( vSize(inside_middle_from - inside_middle_to) < vSize(inside_middle_from - from_outside) + vSize(inside_middle_to - to_outside) )
|
||||
{ // via outside is a detour
|
||||
combPaths.back().push_back(inside_middle_from);
|
||||
combPaths.back().push_back(inside_middle_to);
|
||||
combPaths.throughAir = true;
|
||||
if ( vSize(crossing_1_in_or_mid - crossing_2_in_or_mid) < vSize(crossing_1_in_or_mid - crossing_1_out) + vSize(crossing_2_in_or_mid - crossing_2_out) )
|
||||
{ // via outside is moving more over the in-between zone
|
||||
combPaths.back().push_back(crossing_1_in_or_mid);
|
||||
combPaths.back().push_back(crossing_2_in_or_mid);
|
||||
}
|
||||
else
|
||||
{
|
||||
LinePolygonsCrossings::comb(middle, from_outside, to_outside, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
LinePolygonsCrossings::comb(getBoundaryOutside(), crossing_1_out, crossing_2_out, combPaths.back(), offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // directly through air (not avoiding other parts)
|
||||
combPaths.emplace_back();
|
||||
combPaths.back().throughAir = true;
|
||||
combPaths.throughAir = true;
|
||||
combPaths.back().cross_boundary = true; // TODO: calculate whether we cross a boundary!
|
||||
combPaths.back().push_back(inside_middle_from);
|
||||
combPaths.back().push_back(inside_middle_to);
|
||||
combPaths.back().push_back(crossing_1_in_or_mid);
|
||||
combPaths.back().push_back(crossing_2_in_or_mid);
|
||||
}
|
||||
|
||||
if (endInside)
|
||||
@@ -190,13 +248,45 @@ bool Comb::calc(Point startPoint, Point endPoint, CombPaths& combPaths, bool sta
|
||||
// boundary to end
|
||||
PolygonsPart part_end = partsView_inside.assemblePart(end_part_idx); // comb through end part only
|
||||
combPaths.emplace_back();
|
||||
LinePolygonsCrossings::comb(part_end, inside_middle_to, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
LinePolygonsCrossings::comb(part_end, crossing_2_in_or_mid, endPoint, combPaths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> Comb::findBestCrossing(PolygonRef from, Point estimated_start, Point estimated_end)
|
||||
{
|
||||
ClosestPolygonPoint* best_in = nullptr;
|
||||
ClosestPolygonPoint* best_out = nullptr;
|
||||
int64_t best_detour_dist = std::numeric_limits<int64_t>::max();
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> crossing_out_candidates = PolygonUtils::findClose(from, getBoundaryOutside(), getOutsideLocToLine());
|
||||
for (std::pair<ClosestPolygonPoint, ClosestPolygonPoint>& crossing_candidate : crossing_out_candidates)
|
||||
{
|
||||
int64_t crossing_dist2 = vSize2(crossing_candidate.first.location - crossing_candidate.second.location);
|
||||
if (crossing_dist2 > max_crossing_dist2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t dist_to_start = vSize(crossing_candidate.second.location - estimated_start); // use outside location, so that the crossing direction is taken into account
|
||||
int64_t dist_to_end = vSize(crossing_candidate.second.location - estimated_end);
|
||||
int64_t detour_dist = dist_to_start + dist_to_end;
|
||||
if (detour_dist < best_detour_dist)
|
||||
{
|
||||
best_in = &crossing_candidate.first;
|
||||
best_out = &crossing_candidate.second;
|
||||
best_detour_dist = detour_dist;
|
||||
}
|
||||
}
|
||||
if (best_detour_dist == std::numeric_limits<int64_t>::max())
|
||||
{
|
||||
return std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>();
|
||||
}
|
||||
return std::make_shared<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>>(*best_in, *best_out);
|
||||
}
|
||||
|
||||
|
||||
void LinePolygonsCrossings::calcScanlineCrossings()
|
||||
{
|
||||
|
||||
@@ -293,6 +383,7 @@ void LinePolygonsCrossings::getCombingPath(CombPath& combPath, int64_t max_comb_
|
||||
CombPath basicPath;
|
||||
getBasicCombingPath(basicPath);
|
||||
optimizePath(basicPath, combPath);
|
||||
// combPath = basicPath; // uncomment to disable comb path optimization
|
||||
}
|
||||
|
||||
|
||||
@@ -310,7 +401,7 @@ void LinePolygonsCrossings::getBasicCombingPath(CombPath& combPath)
|
||||
void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, CombPath& combPath)
|
||||
{
|
||||
PolygonRef poly = boundary[polyCrossings.poly_idx];
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x + dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.min.x - dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
if ( ( polyCrossings.max.point_idx - polyCrossings.min.point_idx + poly.size() ) % poly.size()
|
||||
< poly.size() / 2 )
|
||||
{ // follow the path in the same direction as the winding order of the boundary polygon
|
||||
@@ -331,7 +422,7 @@ void LinePolygonsCrossings::getBasicCombingPath(PolyCrossings& polyCrossings, Co
|
||||
combPath.push_back(PolygonUtils::getBoundaryPointWithOffset(poly, point_idx, dist_to_move_boundary_point_outside));
|
||||
}
|
||||
}
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x - dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
combPath.push_back(transformation_matrix.unapply(Point(polyCrossings.max.x + dist_to_move_boundary_point_outside, transformed_startPoint.Y)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+31
-8
@@ -2,18 +2,22 @@
|
||||
#ifndef COMB_H
|
||||
#define COMB_H
|
||||
|
||||
#include <memory> // shared_ptr
|
||||
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/BucketGrid2D.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
struct CombPath : public std::vector<Point> //!< A single path either inside or outise the parts
|
||||
{
|
||||
bool throughAir = false; //!< Whether the path is one which moves through air.
|
||||
bool cross_boundary = false; //!< Whether the path crosses a boundary.
|
||||
};
|
||||
struct CombPaths : public std::vector<CombPath> //!< A list of paths alternating between inside a part and outside a part
|
||||
{
|
||||
bool throughAir = false; //!< Whether the path is one which moves through air.
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -205,25 +209,44 @@ class Comb
|
||||
friend class LinePolygonsCrossings;
|
||||
private:
|
||||
SliceDataStorage& storage; //!< The storage from which to compute the outside boundary, when needed.
|
||||
int layer_nr; //!< The layer number for the layer for which to compute the outside boundary, when needed.
|
||||
const int layer_nr; //!< The layer number for the layer for which to compute the outside boundary, when needed.
|
||||
|
||||
int64_t offset_from_outlines; //!< Offset from the boundary of a part to the comb path. (nozzle width / 2)
|
||||
int64_t max_moveInside_distance2; //!< Maximal distance of a point to the Comb::boundary_inside which is still to be considered inside. (very sharp corners not allowed :S)
|
||||
int64_t offset_from_outlines_outside; //!< Offset from the boundary of a part to a travel path which avoids it by this distance.
|
||||
const int64_t offset_from_outlines; //!< Offset from the boundary of a part to the comb path. (nozzle width / 2)
|
||||
const int64_t max_moveInside_distance2; //!< Maximal distance of a point to the Comb::boundary_inside which is still to be considered inside. (very sharp corners not allowed :S)
|
||||
const int64_t offset_from_outlines_outside; //!< Offset from the boundary of a part to a travel path which avoids it by this distance.
|
||||
const int64_t max_crossing_dist2; //!< The maximal distance by which to cross the in_between area between inside and outside
|
||||
static const int64_t max_moveOutside_distance2 = INT64_MAX; //!< Any point which is not inside should be considered outside.
|
||||
static const int64_t offset_dist_to_get_from_on_the_polygon_to_outside = 40; //!< in order to prevent on-boundary vs crossing boundary confusions (precision thing)
|
||||
static const int64_t offset_extra_start_end = 100; //!< Distance to move start point and end point toward eachother to extra avoid collision with the boundaries.
|
||||
|
||||
bool avoid_other_parts; //!< Whether to perform inverse combing a.k.a. avoid parts.
|
||||
|
||||
const bool avoid_other_parts; //!< Whether to perform inverse combing a.k.a. avoid parts.
|
||||
|
||||
Polygons& boundary_inside; //!< The boundary within which to comb.
|
||||
Polygons* boundary_outside; //!< The boundary outside of which to stay to avoid collision with other layer parts. This is a pointer cause we only compute it when we move outside the boundary (so not when there is only a single part in the layer)
|
||||
BucketGrid2D<PolygonsPointIndex>* outside_loc_to_line; //!< The BucketGrid mapping locations to line segments of the outside boundary.
|
||||
PartsView partsView_inside; //!< Structured indices onto boundary_inside which shows which polygons belong to which part.
|
||||
|
||||
/*!
|
||||
* Get the boundary_outside, which is an offset from the outlines of all meshes in the layer. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
Polygons* getBoundaryOutside();
|
||||
Polygons& getBoundaryOutside();
|
||||
|
||||
/*!
|
||||
* Get the BucketGrid mapping locations to line segments of the outside boundary. Calculate it when it hasn't been calculated yet.
|
||||
*/
|
||||
BucketGrid2D<PolygonsPointIndex>& getOutsideLocToLine();
|
||||
|
||||
/*!
|
||||
* Find the best crossing from some inside polygon to the outside boundary.
|
||||
*
|
||||
* The detour from \p estimated_start to \p estimated_end is minimized.
|
||||
*
|
||||
* \param from From which inside boundary the crossing to the outside starts or ends
|
||||
* \param estimated_start The one point to which to stay close when evaluating crossings which cross about the same distance
|
||||
* \param estimated_end The other point to which to stay close when evaluating crossings which cross about the same distance
|
||||
* \return A pair of which the first is the crossing point on the inside boundary and the second the crossing point on the outside boundary
|
||||
*/
|
||||
std::shared_ptr<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> findBestCrossing(PolygonRef from, Point estimated_start, Point estimated_end);
|
||||
|
||||
public:
|
||||
/*!
|
||||
|
||||
+42
-69
@@ -1,7 +1,7 @@
|
||||
#include "utils/logoutput.h"
|
||||
#include "commandSocket.h"
|
||||
#include "FffProcessor.h"
|
||||
#include "Progress.h"
|
||||
#include "progress/Progress.h"
|
||||
|
||||
#include <thread>
|
||||
#include <cinttypes>
|
||||
@@ -18,7 +18,10 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "settings/SettingRegistry.h" // loadExtruderJSONsettings
|
||||
|
||||
#define DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(x)
|
||||
|
||||
// std::cerr << x;
|
||||
|
||||
namespace cura {
|
||||
@@ -43,7 +46,7 @@ public:
|
||||
|
||||
void error(const Arcus::Error & error) override
|
||||
{
|
||||
if(error.getErrorCode() == Arcus::ErrorCode::Debug)
|
||||
if (error.getErrorCode() == Arcus::ErrorCode::Debug)
|
||||
{
|
||||
log("%s\n", error.toString().c_str());
|
||||
}
|
||||
@@ -60,25 +63,18 @@ 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;
|
||||
|
||||
@@ -86,9 +82,6 @@ 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;
|
||||
@@ -96,7 +89,7 @@ public:
|
||||
// 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, cura::proto::Layer*> sliced_layers;
|
||||
std::unordered_map<int, std::shared_ptr<cura::proto::Layer>> sliced_layers;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -133,7 +126,7 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
|
||||
//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());
|
||||
@@ -160,36 +153,35 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
// Actually start handling messages.
|
||||
Arcus::MessagePtr message = private_data->socket->takeNextMessage();
|
||||
cura::proto::SettingList* setting_list = dynamic_cast<cura::proto::SettingList*>(message.get());
|
||||
if(setting_list)
|
||||
if (setting_list)
|
||||
{
|
||||
handleSettingList(setting_list);
|
||||
}
|
||||
|
||||
/*cura::proto::ObjectList* object_list = dynamic_cast<cura::proto::ObjectList*>(message.get());
|
||||
if(object_list)
|
||||
if (object_list)
|
||||
{
|
||||
handleObjectList(object_list);
|
||||
}*/
|
||||
|
||||
cura::proto::Slice* slice = dynamic_cast<cura::proto::Slice*>(message.get());
|
||||
if(slice)
|
||||
if (slice)
|
||||
{
|
||||
// Reset object counts
|
||||
private_data->object_count = 0;
|
||||
private_data->object_ids.clear();
|
||||
for(auto object : slice->object_lists())
|
||||
for (auto object : slice->object_lists())
|
||||
{
|
||||
handleObjectList(&object);
|
||||
}
|
||||
}
|
||||
|
||||
//If there is an object to slice, do so.
|
||||
if(private_data->objects_to_slice.size())
|
||||
if (private_data->objects_to_slice.size())
|
||||
{
|
||||
FffProcessor::getInstance()->resetMeshGroupNumber();
|
||||
for(auto object : private_data->objects_to_slice)
|
||||
for (auto object : private_data->objects_to_slice)
|
||||
{
|
||||
if(!FffProcessor::getInstance()->processMeshGroup(object.get()))
|
||||
if (!FffProcessor::getInstance()->processMeshGroup(object.get()))
|
||||
{
|
||||
logError("Slicing mesh group failed!");
|
||||
}
|
||||
@@ -218,7 +210,7 @@ void CommandSocket::connect(const std::string& ip, int port)
|
||||
#ifdef ARCUS
|
||||
void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
{
|
||||
if(list->objects_size() <= 0)
|
||||
if (list->objects_size() <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -229,29 +221,30 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
private_data->objects_to_slice.push_back(std::make_shared<MeshGroup>(FffProcessor::getInstance()));
|
||||
MeshGroup* meshgroup = private_data->objects_to_slice.back().get();
|
||||
|
||||
for(auto setting : list->settings())
|
||||
for (auto setting : list->settings())
|
||||
{
|
||||
meshgroup->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
|
||||
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
meshgroup->createExtruderTrain(extruder_nr)->setExtruderTrainDefaults(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_nr, train);
|
||||
}
|
||||
|
||||
for(auto object : list->objects())
|
||||
for (auto object : list->objects())
|
||||
{
|
||||
int bytes_per_face = BYTES_PER_FLOAT * FLOATS_PER_VECTOR * VECTORS_PER_FACE;
|
||||
int face_count = object.vertices().size() / bytes_per_face;
|
||||
|
||||
if(face_count <= 0)
|
||||
if (face_count <= 0)
|
||||
{
|
||||
logWarning("Got an empty mesh, ignoring it!");
|
||||
continue;
|
||||
}
|
||||
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR("solid Cura_out\n");
|
||||
int extruder_train_nr = 0; // TODO: make primary extruder configurable!
|
||||
for(auto setting : object.settings())
|
||||
for (auto setting : object.settings())
|
||||
{
|
||||
if (setting.name() == "extruder_nr")
|
||||
{
|
||||
@@ -261,10 +254,10 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
}
|
||||
SettingsBase* extruder_train = meshgroup->getExtruderTrain(extruder_train_nr);
|
||||
|
||||
meshgroup->meshes.push_back(new Mesh(extruder_train)); //Construct a new mesh (with the corresponding extruder train as settings parent object) and put it into MeshGroup's mesh list.
|
||||
Mesh& mesh = *meshgroup->meshes.back();
|
||||
meshgroup->meshes.push_back(extruder_train); //Construct a new mesh (with the corresponding extruder train as settings parent object) and put it into MeshGroup's mesh list.
|
||||
Mesh& mesh = meshgroup->meshes.back();
|
||||
|
||||
for(int i = 0; i < face_count; ++i)
|
||||
for (int i = 0; i < face_count; ++i)
|
||||
{
|
||||
//TODO: Apply matrix
|
||||
std::string data = object.vertices().substr(i * bytes_per_face, bytes_per_face);
|
||||
@@ -285,12 +278,11 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR(" endfacet\n");
|
||||
}
|
||||
DEBUG_OUTPUT_OBJECT_STL_THROUGH_CERR("endsolid Cura_out\n");
|
||||
for(auto setting : object.settings())
|
||||
for (auto setting : object.settings())
|
||||
{
|
||||
mesh.setSetting(setting.name(), setting.value());
|
||||
}
|
||||
|
||||
private_data->object_ids.push_back(object.id());
|
||||
mesh.finish();
|
||||
}
|
||||
|
||||
@@ -300,7 +292,7 @@ void CommandSocket::handleObjectList(cura::proto::ObjectList* list)
|
||||
|
||||
void CommandSocket::handleSettingList(cura::proto::SettingList* list)
|
||||
{
|
||||
for(auto setting : list->settings())
|
||||
for (auto setting : list->settings())
|
||||
{
|
||||
FffProcessor::getInstance()->setSetting(setting.name(), setting.value());
|
||||
}
|
||||
@@ -310,12 +302,7 @@ void CommandSocket::handleSettingList(cura::proto::SettingList* list)
|
||||
void CommandSocket::sendLayerInfo(int layer_nr, int32_t z, int32_t height)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
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);
|
||||
#endif
|
||||
@@ -324,15 +311,12 @@ void CommandSocket::sendLayerInfo(int layer_nr, int32_t z, int32_t height)
|
||||
void CommandSocket::sendPolygons(PrintFeatureType type, int layer_nr, Polygons& polygons, int line_width)
|
||||
{
|
||||
#ifdef ARCUS
|
||||
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)
|
||||
for (unsigned int i = 0; i < polygons.size(); ++i)
|
||||
{
|
||||
cura::proto::Polygon* p = proto_layer->add_polygons();
|
||||
p->set_type(static_cast<cura::proto::Polygon_Type>(type));
|
||||
@@ -379,34 +363,24 @@ void CommandSocket::sendPrintMaterialForObject(int index, int extruder_nr, float
|
||||
// socket.sendFloat32(print_time);
|
||||
}
|
||||
|
||||
void CommandSocket::beginSendSlicedObject()
|
||||
void CommandSocket::sendLayerData()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
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]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandSocket::endSendSlicedObject()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
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)
|
||||
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);
|
||||
@@ -433,7 +407,6 @@ void CommandSocket::flushGcode()
|
||||
{
|
||||
#ifdef ARCUS
|
||||
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);
|
||||
|
||||
@@ -451,20 +424,20 @@ void CommandSocket::sendGCodePrefix(std::string prefix)
|
||||
}
|
||||
|
||||
#ifdef ARCUS
|
||||
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 = sliced_layers.find(id);
|
||||
|
||||
cura::proto::Layer* layer = nullptr;
|
||||
if(itr != sliced_layers.end())
|
||||
std::shared_ptr<cura::proto::Layer> layer;
|
||||
if (itr != sliced_layers.end())
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
#include "utils/socket.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "settings.h"
|
||||
#include "Progress.h"
|
||||
#include "settings/settings.h"
|
||||
#include "progress/Progress.h"
|
||||
#include "PrintFeature.h"
|
||||
|
||||
#include <memory>
|
||||
@@ -86,16 +86,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.
|
||||
|
||||
+271
-123
@@ -6,6 +6,7 @@
|
||||
#include "gcodeExport.h"
|
||||
#include "utils/logoutput.h"
|
||||
#include "PrintFeature.h"
|
||||
#include "utils/Date.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
@@ -17,18 +18,147 @@ GCodeExport::GCodeExport()
|
||||
current_e_value = 0;
|
||||
current_extruder = 0;
|
||||
currentFanSpeed = -1;
|
||||
|
||||
|
||||
totalPrintTime = 0.0;
|
||||
|
||||
|
||||
currentSpeed = 1;
|
||||
isZHopped = 0;
|
||||
setFlavor(EGCodeFlavor::REPRAP);
|
||||
initial_bed_temp = 0;
|
||||
|
||||
extruder_count = 0;
|
||||
}
|
||||
|
||||
GCodeExport::~GCodeExport()
|
||||
{
|
||||
}
|
||||
|
||||
void GCodeExport::preSetup(MeshGroup* settings)
|
||||
{
|
||||
setFlavor(settings->getSettingAsGCodeFlavor("machine_gcode_flavor"));
|
||||
use_extruder_offset_to_offset_coords = settings->getSettingBoolean("machine_use_extruder_offset_to_offset_coords");
|
||||
|
||||
extruder_count = settings->getSettingAsCount("machine_extruder_count");
|
||||
|
||||
for (unsigned int n = 0; n < extruder_count; n++)
|
||||
{
|
||||
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");
|
||||
extruder_attr[n].end_code = train->getSettingString("machine_extruder_end_code");
|
||||
|
||||
extruder_attr[n].extruder_switch_retraction_config.distance = train->getSettingInMillimeters("switch_extruder_retraction_amount");
|
||||
extruder_attr[n].extruder_switch_retraction_config.prime_volume = 0.0;
|
||||
extruder_attr[n].extruder_switch_retraction_config.speed = train->getSettingInMillimetersPerSecond("switch_extruder_retraction_speed");
|
||||
extruder_attr[n].extruder_switch_retraction_config.primeSpeed = train->getSettingInMillimetersPerSecond("switch_extruder_prime_speed");
|
||||
extruder_attr[n].extruder_switch_retraction_config.zHop = train->getSettingInMicrons("switch_extruder_retraction_hop");
|
||||
extruder_attr[n].extruder_switch_retraction_config.retraction_count_max = 9999999; // extruder switch retraction is never limited
|
||||
extruder_attr[n].extruder_switch_retraction_config.retraction_extrusion_window = 99999.9; // so that extruder switch retractions won't affect the retraction buffer (extruded_volume_at_previous_n_retractions)
|
||||
extruder_attr[n].extruder_switch_retraction_config.retraction_min_travel_distance = 0; // no limitation on travel distance for an extruder switch retract
|
||||
|
||||
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...
|
||||
}
|
||||
machine_dimensions.x = settings->getSettingInMicrons("machine_width");
|
||||
machine_dimensions.y = settings->getSettingInMicrons("machine_depth");
|
||||
machine_dimensions.z = settings->getSettingInMicrons("machine_height");
|
||||
|
||||
machine_name = settings->getSettingString("machine_name");
|
||||
|
||||
if (flavor == EGCodeFlavor::BFB)
|
||||
{
|
||||
new_line = "\r\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
new_line = "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeExport::setInitialTemps(const MeshGroup& settings)
|
||||
{
|
||||
for (unsigned int extr_nr = 0; extr_nr < extruder_count; extr_nr++)
|
||||
{
|
||||
const ExtruderTrain* extr_train = settings.getExtruderTrain(extr_nr);
|
||||
assert(extr_train);
|
||||
double temp = extr_train->getSettingInDegreeCelsius((extr_nr == 0)? "material_print_temperature" : "material_standby_temperature");
|
||||
setInitialTemp(extr_nr, temp);
|
||||
}
|
||||
|
||||
initial_bed_temp = settings.getSettingInDegreeCelsius("material_bed_temperature");
|
||||
}
|
||||
|
||||
void GCodeExport::setInitialTemp(int extruder_nr, double temp)
|
||||
{
|
||||
extruder_attr[extruder_nr].initial_temp = temp;
|
||||
if (flavor == EGCodeFlavor::GRIFFIN || flavor == EGCodeFlavor::ULTIGCODE)
|
||||
{
|
||||
extruder_attr[extruder_nr].currentTemperature = temp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string GCodeExport::getFileHeader(const double* print_time, const std::vector<double>& filament_used, const std::vector<int16_t>& mat_ids)
|
||||
{
|
||||
std::ostringstream prefix;
|
||||
switch (flavor)
|
||||
{
|
||||
case EGCodeFlavor::GRIFFIN:
|
||||
prefix << ";START_OF_HEADER" << new_line;
|
||||
prefix << ";HEADER_VERSION:0.1" << new_line;
|
||||
prefix << ";FLAVOR:" << toString(flavor) << new_line;
|
||||
prefix << ";GENERATOR.NAME:Cura_SteamEngine" << new_line;
|
||||
prefix << ";GENERATOR.VERSION:" << VERSION << new_line;
|
||||
prefix << ";GENERATOR.BUILD_DATE:" << Date::getDate().toStringDashed() << new_line;
|
||||
prefix << ";TARGET_MACHINE.NAME:" << machine_name << new_line;
|
||||
|
||||
for (unsigned int extr_nr = 0; extr_nr < extruder_count; extr_nr++)
|
||||
{
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".INITIAL_TEMPERATURE:" << extruder_attr[extr_nr].initial_temp << new_line;
|
||||
if (filament_used.size() == extruder_count)
|
||||
{
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".MATERIAL.VOLUME_USED:" << static_cast<int>(filament_used[extr_nr]) << new_line;
|
||||
}
|
||||
if (mat_ids.size() == extruder_count)
|
||||
{
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".MATERIAL.GUID:" << mat_ids[extr_nr] << new_line; // TODO: convert to hexadecimal format
|
||||
}
|
||||
prefix << ";EXTRUDER_TRAIN." << extr_nr << ".NOZZLE.DIAMETER:" << float(INT2MM(getNozzleSize(extr_nr))) << new_line;
|
||||
}
|
||||
prefix << ";BUILD_PLATE.INITIAL_TEMPERATURE:" << initial_bed_temp << new_line;
|
||||
|
||||
if (print_time)
|
||||
{
|
||||
prefix << ";PRINT.TIME:" << static_cast<int>(*print_time) << new_line;
|
||||
}
|
||||
|
||||
prefix << ";PRINT.SIZE.MIN.X:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Y:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MIN.Z:0" << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.X:" << INT2MM(machine_dimensions.x) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Y:" << INT2MM(machine_dimensions.y) << new_line;
|
||||
prefix << ";PRINT.SIZE.MAX.Z:" << INT2MM(machine_dimensions.z) << new_line;
|
||||
prefix << ";END_OF_HEADER" << new_line;
|
||||
return prefix.str();
|
||||
default:
|
||||
prefix << ";FLAVOR:" << toString(flavor) << new_line;
|
||||
prefix << ";TIME:" << ((print_time)? static_cast<int>(*print_time) : 6666) << new_line;
|
||||
if (flavor == EGCodeFlavor::ULTIGCODE)
|
||||
{
|
||||
prefix << ";MATERIAL:" << ((filament_used.size() >= 1)? static_cast<int>(filament_used[0]) : 6666) << new_line;
|
||||
prefix << ";MATERIAL2:" << ((filament_used.size() >= 2)? static_cast<int>(filament_used[1]) : 0) << new_line;
|
||||
|
||||
prefix << ";NOZZLE_DIAMETER:" << float(INT2MM(getNozzleSize(0))) << new_line;
|
||||
// TODO: the second nozzle size isn't always initiated! ";NOZZLE_DIAMETER2:"
|
||||
}
|
||||
return prefix.str();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GCodeExport::setLayerNr(unsigned int layer_nr_) {
|
||||
layer_nr = layer_nr_;
|
||||
}
|
||||
@@ -39,6 +169,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 +268,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 extruder_nr)
|
||||
{
|
||||
@@ -176,12 +347,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 +360,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 +391,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)
|
||||
if (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 +422,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 +438,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 +455,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 +476,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 +509,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 +518,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,9 +551,9 @@ void GCodeExport::writeMove(int x, int y, int z, double speed, double extrusion_
|
||||
}
|
||||
else if (prime_volume > 0.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), 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();
|
||||
@@ -424,30 +587,39 @@ 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)
|
||||
void GCodeExport::writeRetraction(RetractionConfig* config, bool force, bool extruder_switch)
|
||||
{
|
||||
ExtruderTrainAttributes& extr_attr = extruder_attr[current_extruder];
|
||||
|
||||
if (flavor == EGCodeFlavor::BFB)//BitsFromBytes does automatic retraction.
|
||||
{
|
||||
if (extruder_switch)
|
||||
{
|
||||
if (!extr_attr.retraction_e_amount_current)
|
||||
*output_stream << "M103" << new_line;
|
||||
|
||||
extr_attr.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;
|
||||
}
|
||||
if (extruder_attr[current_extruder].retraction_e_amount_current == config->distance * ((is_volumatric)? extruder_attr[current_extruder].filament_area : 1.0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (config->distance <= 0)
|
||||
|
||||
double old_retraction_e_amount = extr_attr.retraction_e_amount_current;
|
||||
double new_retraction_e_amount = mmToE(config->distance);
|
||||
double retraction_diff_e_amount = old_retraction_e_amount - new_retraction_e_amount;
|
||||
if (std::abs(retraction_diff_e_amount) < 0.000001)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
{ // 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;
|
||||
std::deque<double>& extruded_volume_at_previous_n_retractions = extr_attr.extruded_volume_at_previous_n_retractions;
|
||||
while (int(extruded_volume_at_previous_n_retractions.size()) > config->retraction_count_max && !extruded_volume_at_previous_n_retractions.empty())
|
||||
{
|
||||
// extruder switch could have introduced data which falls outside the retraction window
|
||||
@@ -459,7 +631,7 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
|
||||
return;
|
||||
}
|
||||
if (!force && int(extruded_volume_at_previous_n_retractions.size()) == config->retraction_count_max
|
||||
&& current_extruded_volume < extruded_volume_at_previous_n_retractions.back() + config->retraction_extrusion_window * extruder_attr[current_extruder].filament_area)
|
||||
&& current_extruded_volume < extruded_volume_at_previous_n_retractions.back() + config->retraction_extrusion_window * extr_attr.filament_area)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -469,73 +641,49 @@ void GCodeExport::writeRetraction(RetractionConfig* config, bool force)
|
||||
extruded_volume_at_previous_n_retractions.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (firmware_retract)
|
||||
{
|
||||
*output_stream << "G10\n";
|
||||
if (extruder_switch && extr_attr.retraction_e_amount_current)
|
||||
{
|
||||
return;
|
||||
}
|
||||
*output_stream << "G10";
|
||||
if (extruder_switch)
|
||||
{
|
||||
*output_stream << " S1";
|
||||
}
|
||||
*output_stream << 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_diff_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";
|
||||
currentSpeed = config->speed;
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), current_e_value), currentSpeed);
|
||||
double speed = ((retraction_diff_e_amount < 0.0)? config->speed : extr_attr.last_retraction_prime_speed) * 60;
|
||||
current_e_value += retraction_diff_e_amount;
|
||||
*output_stream << "G1 F" << speed << " "
|
||||
<< extr_attr.extruderCharacter << std::setprecision(5) << current_e_value << new_line;
|
||||
currentSpeed = speed;
|
||||
estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed);
|
||||
extr_attr.last_retraction_prime_speed = config->primeSpeed;
|
||||
}
|
||||
|
||||
extruder_attr[current_extruder].retraction_e_amount_current = retraction_e_amount ;
|
||||
extruder_attr[current_extruder].prime_volume += config->prime_volume;
|
||||
|
||||
extr_attr.retraction_e_amount_current = new_retraction_e_amount; // suppose that for UM2 the retraction amount in the firmware is equal to the provided amount
|
||||
extr_attr.prime_volume += config->prime_volume;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeExport::writeRetraction_extruderSwitch()
|
||||
{
|
||||
if (flavor == EGCodeFlavor::BFB)
|
||||
{
|
||||
if (!extruder_attr[current_extruder].retraction_e_amount_current)
|
||||
*output_stream << "M103\r\n";
|
||||
ExtruderTrainAttributes& extr_attr = extruder_attr[current_extruder];
|
||||
RetractionConfig* config = &extr_attr.extruder_switch_retraction_config;
|
||||
|
||||
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);
|
||||
if (extruder_attr[current_extruder].retraction_e_amount_current == retraction_e_amount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double current_extruded_volume = getCurrentExtrudedVolume();
|
||||
std::deque<double>& extruded_volume_at_previous_n_retractions = extruder_attr[current_extruder].extruded_volume_at_previous_n_retractions;
|
||||
extruded_volume_at_previous_n_retractions.push_front(current_extruded_volume);
|
||||
|
||||
if (firmware_retract)
|
||||
{
|
||||
if (extruder_attr[current_extruder].retraction_e_amount_current)
|
||||
{
|
||||
return;
|
||||
}
|
||||
*output_stream << "G10 S1\n";
|
||||
}
|
||||
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";
|
||||
// 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;
|
||||
}
|
||||
extruder_attr[current_extruder].retraction_e_amount_current = retraction_e_amount; // suppose that for UM2 the retraction amount in the firmware is equal to the provided amount
|
||||
writeRetraction(config, true, true);
|
||||
}
|
||||
|
||||
void GCodeExport::switchExtruder(int new_extruder)
|
||||
@@ -545,40 +693,40 @@ void GCodeExport::switchExtruder(int new_extruder)
|
||||
|
||||
writeRetraction_extruderSwitch();
|
||||
|
||||
resetExtrusionValue(); // should be called on the old extruder
|
||||
resetExtrusionValue(); // zero the E value on the old extruder, so that the current_e_value is registered on the old extruder
|
||||
|
||||
int old_extruder = current_extruder;
|
||||
current_extruder = new_extruder;
|
||||
|
||||
if (flavor == EGCodeFlavor::MACH3)
|
||||
{
|
||||
resetExtrusionValue(); // also zero the E value on the 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;
|
||||
}
|
||||
|
||||
resetExtrusionValue(); // zero the E value on the new extruder, because a firmware bug in Griffin adjusted the E-value when performing a toolswitch (should be fixed as of 9 may 2016)
|
||||
|
||||
writeCode(extruder_attr[new_extruder].start_code.c_str());
|
||||
|
||||
|
||||
//Change the Z position so it gets re-writting again. We do not know if the switch code modified the Z position.
|
||||
currentPosition.z += 1;
|
||||
}
|
||||
|
||||
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::writePrimeTrain()
|
||||
{
|
||||
*output_stream << "G280" << new_line;
|
||||
}
|
||||
|
||||
|
||||
void GCodeExport::writeFanCommand(double speed)
|
||||
{
|
||||
if (currentFanSpeed == speed)
|
||||
@@ -586,16 +734,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;
|
||||
}
|
||||
@@ -611,7 +759,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;
|
||||
}
|
||||
|
||||
@@ -621,14 +769,14 @@ 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)
|
||||
void GCodeExport::finalize(const char* endCode)
|
||||
{
|
||||
writeFanCommand(0);
|
||||
writeCode(endCode);
|
||||
int print_time = getTotalPrintTime();
|
||||
long print_time = getTotalPrintTime();
|
||||
int mat_0 = getTotalFilamentUsed(0);
|
||||
log("Print time: %d\n", print_time);
|
||||
log("Print time (readable): %dh %dm %ds\n", print_time / 60 / 60, (print_time / 60) % 60, print_time % 60);
|
||||
|
||||
+153
-59
@@ -6,7 +6,7 @@
|
||||
#include <deque> // for extrusionAmountAtPreviousRetractions
|
||||
#include <sstream> // for stream.str()
|
||||
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/NoCopy.h"
|
||||
#include "timeEstimate.h"
|
||||
@@ -15,14 +15,23 @@
|
||||
|
||||
namespace cura {
|
||||
|
||||
/*!
|
||||
* Coasting configuration used during printing.
|
||||
* Can differ per extruder.
|
||||
*
|
||||
* Might be used in the future to have different coasting per feature, e.g. outer wall only.
|
||||
*/
|
||||
struct CoastingConfig
|
||||
{
|
||||
bool coasting_enable;
|
||||
double coasting_volume;
|
||||
double coasting_speed;
|
||||
double coasting_min_volume;
|
||||
bool coasting_enable; //!< Whether coasting is enabled on the extruder to which this config is attached
|
||||
double coasting_volume; //!< The volume leeked when printing without feeding
|
||||
double coasting_speed; //!< A modifier (0-1) on the last used travel speed to move slower during coasting
|
||||
double coasting_min_volume; //!< The minimal volume printed to build up enough pressure to leek the coasting_volume
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* The retraction configuration used in the GCodePathConfig of each feature (and the travel config)
|
||||
*/
|
||||
class RetractionConfig
|
||||
{
|
||||
public:
|
||||
@@ -31,38 +40,50 @@ public:
|
||||
double primeSpeed; //!< the speed with which to unretract (in mm/s)
|
||||
double prime_volume; //!< the amount of material primed after unretracting (in mm^3)
|
||||
int zHop; //!< the amount with which to lift the head during a retraction-travel
|
||||
int retraction_min_travel_distance; //!<
|
||||
double retraction_extrusion_window; //!< in mm
|
||||
int retraction_count_max;
|
||||
int retraction_min_travel_distance; //!< Minimal distance traversed to even consider retracting (in micron)
|
||||
double retraction_extrusion_window; //!< Window of mm extruded filament in which to limit the amount of retractions
|
||||
int retraction_count_max; //!< The maximum amount of retractions allowed to occur in the RetractionConfig::retraction_extrusion_window
|
||||
};
|
||||
|
||||
//The GCodePathConfig is the configuration for moves/extrusion actions. This defines at which width the line is printed and at which speed.
|
||||
/*!
|
||||
* The GCodePathConfig is the configuration for moves/extrusion actions. This defines at which width the line is printed and at which speed.
|
||||
*/
|
||||
class GCodePathConfig
|
||||
{
|
||||
private:
|
||||
double speed_base; //!< movement speed (mm/s) specific to this print feature
|
||||
double speed_current; //!< current movement speed (mm/s) (modified by layer_nr etc.)
|
||||
double speed_iconic; //!< movement speed (mm/s) specific to this print feature
|
||||
double speed; //!< current movement speed (mm/s) (modified by layer_nr etc.)
|
||||
int line_width; //!< width of the line extruded
|
||||
double flow; //!< extrusion flow in %
|
||||
int layer_thickness; //!< layer height
|
||||
double extrusion_mm3_per_mm;//!< mm^3 filament moved per mm line extruded
|
||||
double flow; //!< extrusion flow modifier in %
|
||||
int layer_thickness; //!< layer height in micron
|
||||
double extrusion_mm3_per_mm;//!< mm^3 filament moved per mm line traversed
|
||||
public:
|
||||
PrintFeatureType type; //!< name of the feature type
|
||||
bool spiralize;
|
||||
RetractionConfig *const retraction_config;
|
||||
|
||||
// GCodePathConfig() : speed(0), line_width(0), extrusion_mm3_per_mm(0.0), name(nullptr), spiralize(false), retraction_config(nullptr) {}
|
||||
GCodePathConfig(RetractionConfig* retraction_config, PrintFeatureType type) : speed_base(0), speed_current(0), line_width(0), extrusion_mm3_per_mm(0.0), type(type), spiralize(false), retraction_config(retraction_config) {}
|
||||
RetractionConfig *const retraction_config; //!< The retraction configuration to use when retracting after a part of this feature has been printed.
|
||||
|
||||
GCodePathConfig(RetractionConfig* retraction_config, PrintFeatureType type)
|
||||
: speed_iconic(0)
|
||||
, speed(0)
|
||||
, line_width(0)
|
||||
, extrusion_mm3_per_mm(0.0)
|
||||
, type(type)
|
||||
, retraction_config(retraction_config)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Initialize some of the member variables.
|
||||
*
|
||||
* Warning! setLayerHeight still has to be called before this object can be used.
|
||||
* \warning GCodePathConfig::setLayerHeight still has to be called before this object can be used.
|
||||
*
|
||||
* \param speed The regular speed with which to print this feature
|
||||
* \param line_width The line width for this feature
|
||||
* \param flow The flow modifier to apply to the extruded filament when printing this feature
|
||||
*/
|
||||
void init(double speed, int line_width, double flow)
|
||||
{
|
||||
speed_base = speed;
|
||||
this->speed_current = speed;
|
||||
speed_iconic = speed;
|
||||
this->speed = speed;
|
||||
this->line_width = line_width;
|
||||
this->flow = flow;
|
||||
}
|
||||
@@ -87,7 +108,15 @@ public:
|
||||
*/
|
||||
void smoothSpeed(double min_speed, int layer_nr, double max_speed_layer)
|
||||
{
|
||||
speed_current = (speed_base*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
|
||||
speed = (speed_iconic*layer_nr)/max_speed_layer + (min_speed*(max_speed_layer-layer_nr)/max_speed_layer);
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -103,7 +132,7 @@ public:
|
||||
*/
|
||||
double getSpeed()
|
||||
{
|
||||
return speed_current;
|
||||
return speed;
|
||||
}
|
||||
|
||||
int getLineWidth()
|
||||
@@ -135,18 +164,18 @@ 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;
|
||||
std::string end_code;
|
||||
double filament_area; //!< in mm^2 for non-volumetric, cylindrical filament
|
||||
|
||||
double extruder_switch_retraction_distance; //<! extruder switch retraction distance in mm
|
||||
int extruderSwitchRetractionSpeed; //!< extruder switch retraction speed in mm/s
|
||||
int extruderSwitchPrimeSpeed; //!< prime speed of extruder switch in mm/s
|
||||
RetractionConfig extruder_switch_retraction_config; //!< Retraction configuration used when performing extruder switches
|
||||
|
||||
double totalFilament; //!< total filament used per extruder in mm^3
|
||||
int currentTemperature;
|
||||
int initial_temp; //!< Temperature this nozzle needs to be at the start of the print.
|
||||
|
||||
double retraction_e_amount_current; //!< The current retracted amount (in mm or mm^3), or zero(i.e. false) if it is not currently retracted (positive values mean retracted amount, so negative impact on E values)
|
||||
double retraction_e_amount_at_e_start; //!< The ExtruderTrainAttributes::retraction_amount_current value at E0, i.e. the offset (in mm or mm^3) from E0 to the situation where the filament is at the tip of the nozzle.
|
||||
@@ -162,11 +191,9 @@ private:
|
||||
, start_code("")
|
||||
, end_code("")
|
||||
, filament_area(0)
|
||||
, extruder_switch_retraction_distance(0.0)
|
||||
, extruderSwitchRetractionSpeed(0)
|
||||
, extruderSwitchPrimeSpeed(0)
|
||||
, totalFilament(0)
|
||||
, currentTemperature(0)
|
||||
, initial_temp(0)
|
||||
, retraction_e_amount_current(0.0)
|
||||
, retraction_e_amount_at_e_start(0.0)
|
||||
, prime_volume(0.0)
|
||||
@@ -174,9 +201,14 @@ private:
|
||||
{ }
|
||||
};
|
||||
ExtruderTrainAttributes extruder_attr[MAX_EXTRUDERS];
|
||||
unsigned int extruder_count;
|
||||
bool use_extruder_offset_to_offset_coords;
|
||||
|
||||
Point3 machine_dimensions;
|
||||
std::string machine_name;
|
||||
|
||||
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
|
||||
@@ -194,16 +226,63 @@ private:
|
||||
bool firmware_retract; //!< whether retractions are done in the firmware, or hardcoded in E values.
|
||||
|
||||
unsigned int layer_nr; //!< for sending travel data
|
||||
|
||||
|
||||
int initial_bed_temp; //!< bed temperature at the beginning of the print.
|
||||
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 in seconds of the whole gcode (if known)
|
||||
* \param filament_used The total mm^3 filament used for each extruder or a vector of the wrong size of unknown
|
||||
* \param mat_ids The material ids for each material.
|
||||
* \return The string representing the file header
|
||||
*/
|
||||
std::string getFileHeader(const double* print_time = nullptr, const std::vector<double>& filament_used = std::vector<double>(), const std::vector<int16_t>& mat_ids = std::vector<int16_t>());
|
||||
|
||||
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);
|
||||
@@ -276,7 +355,7 @@ private:
|
||||
*/
|
||||
void writeMoveBFB(int x, int y, int z, double speed, double extrusion_per_mm);
|
||||
public:
|
||||
void writeRetraction(RetractionConfig* config, bool force=false);
|
||||
void writeRetraction(RetractionConfig* config, bool force = false, bool extruder_switch = false);
|
||||
|
||||
void writeRetraction_extruderSwitch();
|
||||
|
||||
@@ -284,37 +363,52 @@ public:
|
||||
|
||||
void writeCode(const char* str);
|
||||
|
||||
/*!
|
||||
* Write the gcode for priming the current extruder train so that it can be used.
|
||||
*/
|
||||
void writePrimeTrain();
|
||||
|
||||
void writeFanCommand(double speed);
|
||||
|
||||
void writeTemperatureCommand(int extruder, double temperature, bool wait = false);
|
||||
void writeBedTemperatureCommand(double temperature, bool wait = false);
|
||||
|
||||
void preSetup(MeshGroup* settings)
|
||||
{
|
||||
for(int n=0; n<settings->getSettingAsCount("machine_extruder_count"); n++)
|
||||
{
|
||||
ExtruderTrain* train = settings->getExtruderTrain(n);
|
||||
setFilamentDiameter(n, train->getSettingInMicrons("material_diameter"));
|
||||
|
||||
extruder_attr[n].nozzle_offset = Point(train->getSettingInMicrons("machine_nozzle_offset_x"), train->getSettingInMicrons("machine_nozzle_offset_y"));
|
||||
|
||||
extruder_attr[n].start_code = train->getSettingString("machine_extruder_start_code");
|
||||
extruder_attr[n].end_code = train->getSettingString("machine_extruder_end_code");
|
||||
|
||||
extruder_attr[n].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...
|
||||
}
|
||||
/*!
|
||||
* Set member variables using the settings in \p settings
|
||||
*
|
||||
* \param settings The meshgroup to get the global bed temp from and to get the extruder trains from which to get the nozzle temperatures
|
||||
*/
|
||||
void preSetup(MeshGroup* settings);
|
||||
|
||||
/*!
|
||||
* Handle the initial (bed/nozzle) temperatures before any gcode is processed.
|
||||
* These temperatures are set in the pre-print setup in the firmware.
|
||||
*
|
||||
* See FffGcodeWriter::processStartingCode
|
||||
*
|
||||
* \param settings The meshgroup to get the global bed temp from and to get the extruder trains from which to get the nozzle temperatures
|
||||
*/
|
||||
void setInitialTemps(const MeshGroup& settings);
|
||||
|
||||
/*!
|
||||
* Override or set an initial nozzle temperature as written by GCodeExport::setInitialTemps
|
||||
* This is used primarily during better specification of temperatures in LayerPlanBuffer::insertPreheatCommand
|
||||
*
|
||||
* \param extruder_nr The extruder number for which to better specify the temp
|
||||
* \param temp The temp at which the nozzle should be at startup
|
||||
*/
|
||||
void setInitialTemp(int extruder_nr, double temp);
|
||||
|
||||
/*!
|
||||
* Finish the gcode: turn fans off, write end gcode and flush all gcode left in the buffer.
|
||||
*
|
||||
* \param endCode The end gcode to be appended at the very end.
|
||||
*/
|
||||
void finalize(const char* endCode);
|
||||
|
||||
setFlavor(settings->getSettingAsGCodeFlavor("machine_gcode_flavor"));
|
||||
use_extruder_offset_to_offset_coords = settings->getSettingBoolean("machine_use_extruder_offset_to_offset_coords");
|
||||
}
|
||||
void finalize(double moveSpeed, const char* endCode);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif//GCODEEXPORT_H
|
||||
|
||||
|
||||
+137
-90
@@ -22,10 +22,10 @@ TimeMaterialEstimates& TimeMaterialEstimates::operator-=(const TimeMaterialEstim
|
||||
return *this;
|
||||
}
|
||||
|
||||
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow)
|
||||
GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow, bool spiralize)
|
||||
{
|
||||
std::vector<GCodePath>& paths = extruder_plans.back().paths;
|
||||
if (paths.size() > 0 && paths.back().config == config && !paths.back().done && paths.back().flow == flow)
|
||||
if (paths.size() > 0 && paths.back().config == config && !paths.back().done && paths.back().flow == flow) // spiralize can only change when a travel path is in between
|
||||
return &paths.back();
|
||||
paths.emplace_back();
|
||||
GCodePath* ret = &paths.back();
|
||||
@@ -33,8 +33,9 @@ GCodePath* GCodePlanner::getLatestPathWithConfig(GCodePathConfig* config, SpaceF
|
||||
ret->config = config;
|
||||
ret->done = false;
|
||||
ret->flow = flow;
|
||||
ret->spiralize = spiralize;
|
||||
ret->space_fill_type = space_fill_type;
|
||||
if (config != &storage.travel_config)
|
||||
if (!config->isTravelPath())
|
||||
{
|
||||
last_retraction_config = config->retraction_config;
|
||||
}
|
||||
@@ -48,27 +49,27 @@ void GCodePlanner::forceNewPathStart()
|
||||
paths[paths.size()-1].done = true;
|
||||
}
|
||||
|
||||
GCodePlanner::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, bool retraction_combing, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
GCodePlanner::GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_thickness, Point last_position, int current_extruder, bool is_inside_mesh, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance)
|
||||
: storage(storage)
|
||||
, layer_nr(layer_nr)
|
||||
, z(z)
|
||||
, layer_thickness(layer_thickness)
|
||||
, start_position(last_position)
|
||||
, lastPosition(last_position)
|
||||
, comb_boundary_inside(computeCombBoundaryInside())
|
||||
, comb_boundary_inside(computeCombBoundaryInside(combing_mode))
|
||||
, fan_speed_layer_time_settings(fan_speed_layer_time_settings)
|
||||
{
|
||||
extruder_plans.reserve(storage.meshgroup->getExtruderCount());
|
||||
extruder_plans.emplace_back(current_extruder);
|
||||
comb = nullptr;
|
||||
was_inside = true; // means it will try to get inside the comb boundary first
|
||||
is_inside = true; // means it will try to get inside the comb boundary
|
||||
last_retraction_config = &storage.retraction_config; // start with general config
|
||||
was_inside = is_inside_mesh;
|
||||
is_inside = false; // assumes the next move will not be to inside a layer part (overwritten just before going into a layer part)
|
||||
last_retraction_config = &storage.retraction_config_per_extruder[current_extruder]; // start with general config
|
||||
setExtrudeSpeedFactor(1.0);
|
||||
setTravelSpeedFactor(1.0);
|
||||
extraTime = 0.0;
|
||||
totalPrintTime = 0.0;
|
||||
if (retraction_combing)
|
||||
if (combing_mode != CombingMode::OFF)
|
||||
{
|
||||
comb = new Comb(storage, layer_nr, comb_boundary_inside, comb_boundary_offset, travel_avoid_other_parts, travel_avoid_distance);
|
||||
}
|
||||
@@ -82,11 +83,22 @@ GCodePlanner::~GCodePlanner()
|
||||
delete comb;
|
||||
}
|
||||
|
||||
Polygons GCodePlanner::computeCombBoundaryInside()
|
||||
Polygons GCodePlanner::computeCombBoundaryInside(CombingMode combing_mode)
|
||||
{
|
||||
if (combing_mode == CombingMode::OFF)
|
||||
{
|
||||
return Polygons();
|
||||
}
|
||||
if (layer_nr < 0)
|
||||
{ // when a raft is present
|
||||
return storage.raftOutline.offset(MM2INT(0.1));
|
||||
if (combing_mode == CombingMode::NO_SKIN)
|
||||
{
|
||||
return Polygons();
|
||||
}
|
||||
else
|
||||
{
|
||||
return storage.raftOutline.offset(MM2INT(0.1));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -94,7 +106,17 @@ Polygons GCodePlanner::computeCombBoundaryInside()
|
||||
for (SliceMeshStorage& mesh : storage.meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
layer.getSecondOrInnermostWalls(layer_walls);
|
||||
if (mesh.getSettingAsCombingMode("retraction_combing") == CombingMode::NO_SKIN)
|
||||
{
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
layer_walls.add(part.infill_area);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.getSecondOrInnermostWalls(layer_walls);
|
||||
}
|
||||
}
|
||||
return layer_walls;
|
||||
}
|
||||
@@ -111,6 +133,7 @@ bool GCodePlanner::setExtruder(int extruder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
setIsInside(false);
|
||||
{ // handle end position of the prev extruder
|
||||
SettingsBase* train = storage.meshgroup->getExtruderTrain(extruder_plans.back().extruder);
|
||||
bool end_pos_absolute = train->getSettingBoolean("machine_extruder_end_pos_abs");
|
||||
@@ -176,6 +199,7 @@ void GCodePlanner::moveInsideCombBoundary(int distance)
|
||||
void GCodePlanner::addTravel(Point p)
|
||||
{
|
||||
GCodePath* path = nullptr;
|
||||
GCodePathConfig& travel_config = storage.travel_config_per_extruder[extruder_plans.back().extruder];
|
||||
|
||||
bool combed = false;
|
||||
|
||||
@@ -188,18 +212,25 @@ void GCodePlanner::addTravel(Point p)
|
||||
bool retract = combPaths.size() > 1;
|
||||
if (!retract)
|
||||
{ // check whether we want to retract
|
||||
for (CombPath& combPath : combPaths)
|
||||
{ // retract when path moves through a boundary
|
||||
if (combPath.cross_boundary || combPath.throughAir)
|
||||
{
|
||||
retract = true;
|
||||
break;
|
||||
if (combPaths.throughAir)
|
||||
{
|
||||
retract = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (CombPath& combPath : combPaths)
|
||||
{ // retract when path moves through a boundary
|
||||
if (combPath.cross_boundary)
|
||||
{
|
||||
retract = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (combPaths.size() == 1)
|
||||
{
|
||||
CombPath path = combPaths[0];
|
||||
if (path.throughAir && !path.cross_boundary && path.size() == 2 && path[0] == lastPosition && path[1] == p)
|
||||
if (combPaths.throughAir && !path.cross_boundary && path.size() == 2 && path[0] == lastPosition && path[1] == p)
|
||||
{ // limit the retractions from support to support, which didn't cross anything
|
||||
retract = false;
|
||||
}
|
||||
@@ -208,7 +239,7 @@ void GCodePlanner::addTravel(Point p)
|
||||
|
||||
if (retract && last_retraction_config->zHop > 0)
|
||||
{ // TODO: stop comb calculation early! (as soon as we see we don't end in the same part as we began)
|
||||
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
|
||||
path = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
|
||||
if (!shorterThen(lastPosition - p, last_retraction_config->retraction_min_travel_distance))
|
||||
{
|
||||
path->retract = true;
|
||||
@@ -222,7 +253,7 @@ void GCodePlanner::addTravel(Point p)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
|
||||
path = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
|
||||
path->retract = retract;
|
||||
for (Point& combPoint : combPath)
|
||||
{
|
||||
@@ -244,7 +275,7 @@ void GCodePlanner::addTravel(Point p)
|
||||
assert (extr != nullptr);
|
||||
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 = getLatestPathWithConfig(&travel_config, SpaceFillType::None);
|
||||
path->retract = true;
|
||||
}
|
||||
}
|
||||
@@ -257,33 +288,35 @@ void GCodePlanner::addTravel_simple(Point p, GCodePath* path)
|
||||
{
|
||||
if (path == nullptr)
|
||||
{
|
||||
path = getLatestPathWithConfig(&storage.travel_config, SpaceFillType::None);
|
||||
path = getLatestPathWithConfig(&storage.travel_config_per_extruder[extruder_plans.back().extruder], SpaceFillType::None);
|
||||
}
|
||||
path->points.push_back(p);
|
||||
lastPosition = p;
|
||||
}
|
||||
|
||||
|
||||
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow)
|
||||
void GCodePlanner::addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow, bool spiralize)
|
||||
{
|
||||
getLatestPathWithConfig(config, space_fill_type, flow)->points.push_back(p);
|
||||
getLatestPathWithConfig(config, space_fill_type, flow, spiralize)->points.push_back(p);
|
||||
lastPosition = p;
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation)
|
||||
void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, bool spiralize)
|
||||
{
|
||||
Point p0 = polygon[startIdx];
|
||||
addTravel(p0);
|
||||
for(unsigned int i=1; i<polygon.size(); i++)
|
||||
{
|
||||
Point p1 = polygon[(startIdx + i) % polygon.size()];
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
|
||||
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
|
||||
p0 = p1;
|
||||
}
|
||||
if (polygon.size() > 2)
|
||||
{
|
||||
Point& p1 = polygon[startIdx];
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0);
|
||||
float flow = (wall_overlap_computation)? wall_overlap_computation->getFlow(p0, p1) : 1.0;
|
||||
addExtrusionMove(p1, config, SpaceFillType::Polygons, flow, spiralize);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -291,7 +324,7 @@ void GCodePlanner::addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig*
|
||||
}
|
||||
}
|
||||
|
||||
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type)
|
||||
void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation, EZSeamType z_seam_type, bool spiralize)
|
||||
{
|
||||
if (polygons.size() == 0)
|
||||
{
|
||||
@@ -303,9 +336,9 @@ void GCodePlanner::addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* c
|
||||
orderOptimizer.addPolygon(polygons[poly_idx]);
|
||||
}
|
||||
orderOptimizer.optimize();
|
||||
for (int poly_idx : orderOptimizer.polyOrder)
|
||||
for (unsigned int poly_idx : orderOptimizer.polyOrder)
|
||||
{
|
||||
addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config, wall_overlap_computation);
|
||||
addPolygon(polygons[poly_idx], orderOptimizer.polyStart[poly_idx], config, wall_overlap_computation, spiralize);
|
||||
}
|
||||
}
|
||||
void GCodePlanner::addLinesByOptimizer(Polygons& polygons, GCodePathConfig* config, SpaceFillType space_fill_type, int wipe_dist)
|
||||
@@ -396,6 +429,7 @@ TimeMaterialEstimates GCodePlanner::computeNaiveTimeEstimates()
|
||||
Point p0 = start_position;
|
||||
|
||||
bool was_retracted = false; // wrong assumption; won't matter that much. (TODO)
|
||||
RetractionConfig* last_retraction_config = nullptr;
|
||||
for(ExtruderPlan& extr_plan : extruder_plans)
|
||||
{
|
||||
for (GCodePath& path : extr_plan.paths)
|
||||
@@ -418,10 +452,11 @@ TimeMaterialEstimates GCodePlanner::computeNaiveTimeEstimates()
|
||||
{
|
||||
path_time_estimate = &path.estimates.unretracted_travel_time;
|
||||
}
|
||||
if (path.retract != was_retracted)
|
||||
if (path.retract != was_retracted && last_retraction_config != nullptr)
|
||||
{ // handle retraction times
|
||||
double retract_unretract_time;
|
||||
RetractionConfig& retraction_config = *path.config->retraction_config;
|
||||
assert(last_retraction_config != nullptr);
|
||||
RetractionConfig& retraction_config = *last_retraction_config;
|
||||
if (path.retract)
|
||||
{
|
||||
retract_unretract_time = retraction_config.distance / retraction_config.speed;
|
||||
@@ -446,6 +481,10 @@ TimeMaterialEstimates GCodePlanner::computeNaiveTimeEstimates()
|
||||
p0 = p1;
|
||||
}
|
||||
extr_plan.estimates += path.estimates;
|
||||
if (is_extrusion_path)
|
||||
{
|
||||
last_retraction_config = path.config->retraction_config;
|
||||
}
|
||||
}
|
||||
ret += extr_plan.estimates;
|
||||
}
|
||||
@@ -509,7 +548,7 @@ void GCodePlanner::processFanSpeedAndMinimalLayerTime()
|
||||
}
|
||||
|
||||
|
||||
void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int layerThickness)
|
||||
void GCodePlanner::writeGCode(GCodeExport& gcode)
|
||||
{
|
||||
completeConfigs();
|
||||
|
||||
@@ -547,7 +586,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
{
|
||||
writeRetraction(gcode, extruder_plan_idx, path_idx);
|
||||
}
|
||||
if (path.config != &storage.travel_config && last_extrusion_config != path.config)
|
||||
if (!path.config->isTravelPath() && last_extrusion_config != path.config)
|
||||
{
|
||||
gcode.writeTypeComment(path.config->type);
|
||||
last_extrusion_config = path.config;
|
||||
@@ -561,38 +600,34 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
|
||||
int64_t nozzle_size = 400; // TODO
|
||||
|
||||
if (MergeInfillLines(gcode, layer_nr, paths, extruder_plan, storage.travel_config, nozzle_size).mergeInfillLines(speed, path_idx)) // !! has effect on path_idx !!
|
||||
if (MergeInfillLines(gcode, layer_nr, paths, extruder_plan, storage.travel_config_per_extruder[extruder], nozzle_size).mergeInfillLines(speed, path_idx)) // !! has effect on path_idx !!
|
||||
{ // !! has effect on path_idx !!
|
||||
// works when path_idx is the index of the travel move BEFORE the infill lines to be merged
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.config == &storage.travel_config)
|
||||
if (path.config->isTravelPath())
|
||||
{ // early comp for travel paths, which are handled more simply
|
||||
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
if (point_idx == path.points.size() - 1)
|
||||
{
|
||||
gcode.setZ(z); // go down to extrusion level when we spiralized before on this layer
|
||||
gcode.writeMove(gcode.getPositionXY(), speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
bool spiralize = path.config->spiralize;
|
||||
if (spiralize)
|
||||
{
|
||||
//Check if we are the last spiralize path in the list, if not, do not spiralize.
|
||||
for(unsigned int m=path_idx+1; m<paths.size(); m++)
|
||||
{
|
||||
if (paths[m].config->spiralize)
|
||||
spiralize = false;
|
||||
}
|
||||
}
|
||||
bool spiralize = path.spiralize;
|
||||
if (!spiralize) // normal (extrusion) move (with coasting
|
||||
{
|
||||
{
|
||||
CoastingConfig& coasting_config = storage.coasting_config[extruder];
|
||||
bool coasting = coasting_config.coasting_enable;
|
||||
if (coasting)
|
||||
{
|
||||
coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layerThickness, coasting_config.coasting_volume, coasting_config.coasting_speed, coasting_config.coasting_min_volume);
|
||||
coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layer_thickness, coasting_config.coasting_volume, coasting_config.coasting_speed, coasting_config.coasting_min_volume);
|
||||
}
|
||||
if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above...
|
||||
{ // normal path to gcode algorithm
|
||||
@@ -600,8 +635,8 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
false &&
|
||||
path_idx + 2 < paths.size() // has a next move
|
||||
&& paths[path_idx+1].points.size() == 1 // is single extruded line
|
||||
&& paths[path_idx+1].config != &storage.travel_config // next move is extrusion
|
||||
&& paths[path_idx+2].config == &storage.travel_config // next next move is travel
|
||||
&& paths[path_idx+1].config->isTravelPath() // next move is extrusion
|
||||
&& paths[path_idx+2].config->isTravelPath() // next next move is travel
|
||||
&& shorterThen(path.points.back() - gcode.getPositionXY(), 2 * nozzle_size) // preceding extrusion is close by
|
||||
&& shorterThen(paths[path_idx+1].points.back() - path.points.back(), 2 * nozzle_size) // extrusion move is small
|
||||
&& shorterThen(paths[path_idx+2].points.back() - paths[path_idx+1].points.back(), 2 * nozzle_size) // consecutive extrusion is close by
|
||||
@@ -625,26 +660,34 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
{ // SPIRALIZE
|
||||
//If we need to spiralize then raise the head slowly by 1 layer as this path progresses.
|
||||
float totalLength = 0.0;
|
||||
int z = gcode.getPositionZ();
|
||||
Point p0 = gcode.getPositionXY();
|
||||
for(unsigned int i=0; i<path.points.size(); i++)
|
||||
for (unsigned int _path_idx = path_idx; _path_idx < paths.size() && !paths[_path_idx].isTravelPath(); _path_idx++)
|
||||
{
|
||||
Point p1 = path.points[i];
|
||||
totalLength += vSizeMM(p0 - p1);
|
||||
p0 = p1;
|
||||
GCodePath& _path = paths[_path_idx];
|
||||
for (unsigned int point_idx = 0; point_idx < _path.points.size(); point_idx++)
|
||||
{
|
||||
Point p1 = _path.points[point_idx];
|
||||
totalLength += vSizeMM(p0 - p1);
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
|
||||
float length = 0.0;
|
||||
p0 = gcode.getPositionXY();
|
||||
for(unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
Point p1 = path.points[point_idx];
|
||||
length += vSizeMM(p0 - p1);
|
||||
p0 = p1;
|
||||
gcode.setZ(z + layerThickness * length / totalLength);
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
for (; path_idx < paths.size() && paths[path_idx].spiralize; path_idx++)
|
||||
{ // handle all consecutive spiralized paths > CHANGES path_idx!
|
||||
GCodePath& path = paths[path_idx];
|
||||
for (unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++)
|
||||
{
|
||||
Point p1 = path.points[point_idx];
|
||||
length += vSizeMM(p0 - p1);
|
||||
p0 = p1;
|
||||
gcode.setZ(z + layer_thickness * length / totalLength);
|
||||
sendPolygon(path.config->type, gcode.getPositionXY(), path.points[point_idx], path.getLineWidth());
|
||||
gcode.writeMove(path.points[point_idx], speed, path.getExtrusionMM3perMM());
|
||||
}
|
||||
}
|
||||
path_idx--; // the last path_idx didnt spiralize, so it's not part of the current spiralize path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,7 +695,7 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
}
|
||||
|
||||
gcode.updateTotalPrintTime();
|
||||
if (liftHeadIfNeeded && extraTime > 0.0)
|
||||
if (storage.getSettingBoolean("cool_lift_head") && extraTime > 0.0)
|
||||
{
|
||||
gcode.writeComment("Small layer, adding delay");
|
||||
if (last_extrusion_config)
|
||||
@@ -661,8 +704,8 @@ void GCodePlanner::writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int lay
|
||||
writeRetraction(gcode, extruder_switch_retract, last_extrusion_config->retraction_config);
|
||||
}
|
||||
gcode.setZ(gcode.getPositionZ() + MM2INT(3.0));
|
||||
gcode.writeMove(gcode.getPositionXY(), storage.travel_config.getSpeed(), 0);
|
||||
gcode.writeMove(gcode.getPositionXY() - Point(-MM2INT(20.0), 0), storage.travel_config.getSpeed(), 0); // TODO: is this safe?! wouldn't the head move into the sides then?!
|
||||
gcode.writeMove(gcode.getPositionXY(), storage.travel_config_per_extruder[extruder].getSpeed(), 0);
|
||||
gcode.writeMove(gcode.getPositionXY() - Point(-MM2INT(20.0), 0), storage.travel_config_per_extruder[extruder].getSpeed(), 0); // TODO: is this safe?! wouldn't the head move into the sides then?!
|
||||
gcode.writeDelay(extraTime);
|
||||
}
|
||||
}
|
||||
@@ -692,24 +735,44 @@ 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");
|
||||
double initial_layer_speed;
|
||||
int extruder_nr_support_infill = storage.getSettingAsIndex((layer_nr == 0)? "support_extruder_nr_layer_0" : "support_infill_extruder_nr");
|
||||
initial_layer_speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_infill)->getSettingInMillimetersPerSecond("speed_layer_0");
|
||||
storage.support_config.smoothSpeed(initial_layer_speed, layer_nr, initial_speedup_layers);
|
||||
|
||||
int extruder_nr_support_roof = storage.getSettingAsIndex("support_roof_extruder_nr");
|
||||
initial_layer_speed = storage.meshgroup->getExtruderTrain(extruder_nr_support_roof)->getSettingInMillimetersPerSecond("speed_layer_0");
|
||||
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)
|
||||
@@ -720,35 +783,19 @@ void GCodePlanner::writeRetraction(GCodeExport& gcode, unsigned int extruder_pla
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<GCodePath>& paths = extruder_plans[extruder_plan_idx].paths;
|
||||
RetractionConfig* extrusion_retraction_config = nullptr;
|
||||
for(int extrusion_path_idx = int(path_idx_travel_after) - 1; extrusion_path_idx >= 0; extrusion_path_idx--)
|
||||
{ // backtrack to find the last extrusion path
|
||||
if (paths[extrusion_path_idx].config != &storage.travel_config)
|
||||
{
|
||||
extrusion_retraction_config = paths[extrusion_path_idx].config->retraction_config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
writeRetraction(gcode, false, extrusion_retraction_config);
|
||||
writeRetraction(gcode, false, last_retraction_config);
|
||||
}
|
||||
}
|
||||
void GCodePlanner::writeRetraction(GCodeExport& gcode, bool extruder_switch_retract, RetractionConfig* retraction_config)
|
||||
{
|
||||
assert(retraction_config != nullptr);
|
||||
if (extruder_switch_retract)
|
||||
{
|
||||
gcode.writeRetraction_extruderSwitch();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (retraction_config)
|
||||
{
|
||||
gcode.writeRetraction(retraction_config);
|
||||
}
|
||||
else
|
||||
{
|
||||
gcode.writeRetraction(storage.travel_config.retraction_config);
|
||||
}
|
||||
gcode.writeRetraction(retraction_config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+178
-36
@@ -57,12 +57,19 @@ class TimeMaterialEstimates
|
||||
{
|
||||
friend class GCodePlanner;
|
||||
private:
|
||||
double extrude_time; //!< in seconds
|
||||
double unretracted_travel_time; //!< in seconds
|
||||
double retracted_travel_time; //!< in seconds
|
||||
double material; //!< in mm^3
|
||||
double extrude_time; //!< Time in seconds occupied by extrusion
|
||||
double unretracted_travel_time; //!< Time in seconds occupied by non-retracted travel (non-extrusion)
|
||||
double retracted_travel_time; //!< Time in seconds occupied by retracted travel (non-extrusion)
|
||||
double material; //!< Material used (in mm^3)
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Basic contructor
|
||||
*
|
||||
* \param extrude_time Time in seconds occupied by extrusion
|
||||
* \param unretracted_travel_time Time in seconds occupied by non-retracted travel (non-extrusion)
|
||||
* \param retracted_travel_time Time in seconds occupied by retracted travel (non-extrusion)
|
||||
* \param material Material used (in mm^3)
|
||||
*/
|
||||
TimeMaterialEstimates(double extrude_time, double unretracted_travel_time, double retracted_travel_time, double material)
|
||||
: extrude_time(extrude_time)
|
||||
, unretracted_travel_time(unretracted_travel_time)
|
||||
@@ -70,6 +77,10 @@ public:
|
||||
, material(material)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Basic constructor initializing all estimates to zero.
|
||||
*/
|
||||
TimeMaterialEstimates()
|
||||
: extrude_time(0.0)
|
||||
, unretracted_travel_time(0.0)
|
||||
@@ -77,7 +88,7 @@ public:
|
||||
, material(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Set all estimates to zero.
|
||||
*/
|
||||
@@ -88,12 +99,24 @@ public:
|
||||
retracted_travel_time = 0.0;
|
||||
material = 0.0;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Pointwise addition of estimate stats
|
||||
*
|
||||
* \param other The estimates to add to these estimates.
|
||||
* \return The resulting estimates
|
||||
*/
|
||||
TimeMaterialEstimates operator+(const TimeMaterialEstimates& other)
|
||||
{
|
||||
return TimeMaterialEstimates(extrude_time+other.extrude_time, unretracted_travel_time+other.unretracted_travel_time, retracted_travel_time+other.retracted_travel_time, material+other.material);
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* In place pointwise addition of estimate stats
|
||||
*
|
||||
* \param other The estimates to add to these estimates.
|
||||
* \return These estimates
|
||||
*/
|
||||
TimeMaterialEstimates& operator+=(const TimeMaterialEstimates& other)
|
||||
{
|
||||
extrude_time += other.extrude_time;
|
||||
@@ -102,7 +125,7 @@ public:
|
||||
material += other.material;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Subtracts the specified estimates from these estimates and returns
|
||||
* the result.
|
||||
@@ -113,7 +136,7 @@ public:
|
||||
* \return These estimates with the specified estimates subtracted.
|
||||
*/
|
||||
TimeMaterialEstimates operator-(const TimeMaterialEstimates& other);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Subtracts the specified elements from these estimates.
|
||||
*
|
||||
@@ -124,29 +147,72 @@ public:
|
||||
* \return A reference to this instance.
|
||||
*/
|
||||
TimeMaterialEstimates& operator-=(const TimeMaterialEstimates& other);
|
||||
|
||||
|
||||
/*!
|
||||
* Get total time estimate. The different time estimate member values added together.
|
||||
*
|
||||
* \return the total of all different time estimate values
|
||||
*/
|
||||
double getTotalTime() const
|
||||
{
|
||||
return extrude_time + unretracted_travel_time + retracted_travel_time;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the total time during which the head is not retracted.
|
||||
*
|
||||
* This includes extrusion time and non-retracted travel time
|
||||
*
|
||||
* \return the total time during which the head is not retracted.
|
||||
*/
|
||||
double getTotalUnretractedTime() const
|
||||
{
|
||||
return extrude_time + unretracted_travel_time;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the total travel time.
|
||||
*
|
||||
* This includes the retracted travel time as well as the unretracted travel time.
|
||||
*
|
||||
* \return the total travel time.
|
||||
*/
|
||||
double getTravelTime() const
|
||||
{
|
||||
return retracted_travel_time + unretracted_travel_time;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the extrusion time.
|
||||
*
|
||||
* \return extrusion time.
|
||||
*/
|
||||
double getExtrudeTime() const
|
||||
{
|
||||
return extrude_time;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the amount of material used in mm^3.
|
||||
*
|
||||
* \return amount of material
|
||||
*/
|
||||
double getMaterial() const
|
||||
{
|
||||
return material;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* A class for representing a planned path.
|
||||
*
|
||||
* A path consists of several segments of the same type of movement: retracted travel, infill extrusion, etc.
|
||||
*
|
||||
* This is a compact premature representation in which are line segments have the same config, i.e. the config of this path.
|
||||
*
|
||||
* In the final representation (gcode) each line segment may have different properties,
|
||||
* which are added when the generated GCodePaths are processed.
|
||||
*/
|
||||
class GCodePath
|
||||
{
|
||||
public:
|
||||
@@ -156,16 +222,27 @@ public:
|
||||
bool retract; //!< Whether the path is a move path preceded by a retraction move; whether the path is a retracted move path.
|
||||
std::vector<Point> points; //!< The points constituting this path.
|
||||
bool done;//!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one.
|
||||
|
||||
|
||||
bool spiralize; //!< Whether to gradually increment the z position during the printing of this path. A sequence of spiralized paths should start at the given layer height and end in one layer higher.
|
||||
|
||||
TimeMaterialEstimates estimates; //!< Naive time and material estimates
|
||||
|
||||
|
||||
/*!
|
||||
* Whether this config is the config of a travel path.
|
||||
*
|
||||
* \return Whether this config is the config of a travel path.
|
||||
*/
|
||||
bool isTravelPath()
|
||||
{
|
||||
return config->isTravelPath();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Can only be called after the layer height has been set (which is done while writing the gcode!)
|
||||
* Get the material flow in mm^3 per mm traversed.
|
||||
*
|
||||
* \warning Can only be called after the layer height has been set (which is done while writing the gcode!)
|
||||
*
|
||||
* \return The flow
|
||||
*/
|
||||
double getExtrusionMM3perMM()
|
||||
{
|
||||
@@ -182,46 +259,71 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* An extruder plan contains all planned paths (GCodePath) pertaining to a single extruder train.
|
||||
*
|
||||
* It allows for temperature command inserts which can be inserted in between paths.
|
||||
*/
|
||||
class ExtruderPlan
|
||||
{
|
||||
public:
|
||||
std::vector<GCodePath> paths;
|
||||
std::list<NozzleTempInsert> inserts;
|
||||
|
||||
std::vector<GCodePath> paths; //!< The paths planned for this extruder
|
||||
std::list<NozzleTempInsert> inserts; //!< The nozzle temperature command inserts, to be inserted in between paths
|
||||
|
||||
int extruder; //!< The extruder used for this paths in the current plan.
|
||||
double required_temp;
|
||||
|
||||
TimeMaterialEstimates estimates;
|
||||
|
||||
double required_temp; //!< The required temperature at the start of this extruder plan.
|
||||
|
||||
TimeMaterialEstimates estimates; //!< Accumulated time and material estimates for all planned paths within this extruder plan.
|
||||
|
||||
/*!
|
||||
* Simple contructor.
|
||||
*
|
||||
* \warning Doesn't set the required temperature yet.
|
||||
*
|
||||
* \param extruder The extruder number for which this object is a plan.
|
||||
*/
|
||||
ExtruderPlan(int extruder)
|
||||
: extruder(extruder)
|
||||
, required_temp(-1)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Add a new Insert, constructed with the given arguments
|
||||
*
|
||||
* \see NozzleTempInsert
|
||||
*
|
||||
* \param contructor_args The arguments for the constructor of an insert
|
||||
*/
|
||||
template<typename... Args>
|
||||
void insertCommand(Args&&... contructor_args)
|
||||
{
|
||||
inserts.emplace_back(contructor_args...);
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Insert the inserts into gcode which should be inserted before @p path_idx
|
||||
* Insert the inserts into gcode which should be inserted before \p path_idx
|
||||
*
|
||||
* \param path_idx The index into ExtruderPlan::paths which is currently being consider for temperature command insertion
|
||||
* \param gcode The gcode exporter to which to write the temperature command.
|
||||
*/
|
||||
void handleInserts(unsigned int& path_idx, GCodeExport& gcode)
|
||||
{
|
||||
{
|
||||
while ( ! inserts.empty() && path_idx >= inserts.front().path_idx)
|
||||
{ // handle the Insert to be inserted before this path_idx (and all inserts not handled yet)
|
||||
inserts.front().write(gcode);
|
||||
inserts.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Insert all remaining temp inserts into gcode, to be called at the end of an extruder plan
|
||||
*
|
||||
* Inserts temperature commands which should be inserted _after_ the last path.
|
||||
* Also inserts all temperatures which should have been inserted earlier,
|
||||
* but for which ExtruderPlan::handleInserts hasn't been called correctly.
|
||||
*
|
||||
* \param gcode The gcode exporter to which to write the temperature command.
|
||||
*/
|
||||
void handleAllRemainingInserts(GCodeExport& gcode)
|
||||
{
|
||||
@@ -236,18 +338,24 @@ public:
|
||||
};
|
||||
|
||||
class LayerPlanBuffer; // forward declaration to prevent circular dependency
|
||||
|
||||
/*!
|
||||
* The GCodePlanner class stores multiple moves that are planned.
|
||||
*
|
||||
*
|
||||
* It facilitates the combing to keep the head inside the print.
|
||||
* It also keeps track of the print time estimate for this planning so speed adjustments can be made for the minimal-layer-time.
|
||||
*
|
||||
* A GCodePlanner is also knows as a 'layer plan'.
|
||||
*
|
||||
*/
|
||||
class GCodePlanner : public NoCopy
|
||||
{
|
||||
friend class LayerPlanBuffer;
|
||||
private:
|
||||
SliceDataStorage& storage;
|
||||
SliceDataStorage& storage; //!< The polygon data obtained from FffPolygonProcessor
|
||||
|
||||
int layer_nr;
|
||||
int layer_nr; //!< The layer number of this layer plan
|
||||
|
||||
int z;
|
||||
|
||||
@@ -283,9 +391,10 @@ private:
|
||||
* \param config The config used for the path returned
|
||||
* \param space_fill_type The type of space filling which this path employs
|
||||
* \param flow (optional) A ratio for the extrusion speed
|
||||
* \param spiralize Whether to gradually increase the z while printing. (Note that this path may be part of a sequence of spiralized paths, forming one polygon)
|
||||
* \return A path with the given config which is now the last path in GCodePlanner::paths
|
||||
*/
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0);
|
||||
GCodePath* getLatestPathWithConfig(GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0, bool spiralize = false);
|
||||
|
||||
/*!
|
||||
* Force GCodePlanner::getLatestPathWithConfig to return a new path.
|
||||
@@ -304,16 +413,18 @@ public:
|
||||
* \param travel_avoid_other_parts Whether to avoid other layer parts when travaeling through air.
|
||||
* \param travel_avoid_distance The distance by which to avoid other layer parts when traveling through air.
|
||||
* \param last_position The position of the head at the start of this gcode layer
|
||||
* \param combing_mode Whether combing is enabled and full or within infill only.
|
||||
*/
|
||||
GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_height, Point last_position, int current_extruder, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, bool retraction_combing, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
GCodePlanner(SliceDataStorage& storage, unsigned int layer_nr, int z, int layer_height, Point last_position, int current_extruder, bool is_inside_mesh, FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, CombingMode combing_mode, int64_t comb_boundary_offset, bool travel_avoid_other_parts, int64_t travel_avoid_distance);
|
||||
~GCodePlanner();
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Compute the boundary within which to comb, or to move into when performing a retraction.
|
||||
* \param combing_mode Whether combing is enabled and full or within infill only.
|
||||
* \return the comb_boundary_inside
|
||||
*/
|
||||
Polygons computeCombBoundaryInside();
|
||||
Polygons computeCombBoundaryInside(CombingMode combing_mode);
|
||||
|
||||
public:
|
||||
int getLayerNr()
|
||||
@@ -326,6 +437,13 @@ public:
|
||||
return lastPosition;
|
||||
}
|
||||
|
||||
/*!
|
||||
* return whether the last position planned was inside the mesh (used in combing)
|
||||
*/
|
||||
bool getIsInsideMesh()
|
||||
{
|
||||
return was_inside;
|
||||
}
|
||||
/*!
|
||||
* send a polygon through the command socket from the previous point to the given point
|
||||
*/
|
||||
@@ -408,12 +526,36 @@ public:
|
||||
* \param config The config with which to extrude
|
||||
* \param space_fill_type Of what space filling type this extrusion move is a part
|
||||
* \param flow A modifier of the extrusion width which would follow from the \p config
|
||||
* \param spiralize Whether to gradually increase the z while printing. (Note that this path may be part of a sequence of spiralized paths, forming one polygon)
|
||||
*/
|
||||
void addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0);
|
||||
void addExtrusionMove(Point p, GCodePathConfig* config, SpaceFillType space_fill_type, float flow = 1.0, bool spiralize = false);
|
||||
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr);
|
||||
/*!
|
||||
* Add polygon to the gcode starting at vertex \p startIdx
|
||||
* \param polygon The polygon
|
||||
* \param startIdx The index of the starting vertex of the \p polygon
|
||||
* \param config The config with which to print the polygon lines
|
||||
* \param wall_overlap_computation The wall overlap compensation calculator for each given segment (optionally nullptr)
|
||||
* \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over this polygon
|
||||
*/
|
||||
void addPolygon(PolygonRef polygon, int startIdx, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, bool spiralize = false);
|
||||
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST);
|
||||
/*!
|
||||
* Add polygons to the gcode with optimized order.
|
||||
*
|
||||
* When \p spiralize is true, each polygon will gradually increase from a z corresponding to this layer to the z corresponding to the next layer.
|
||||
* Doing this for each polygon means there is a chance for the print head to crash into already printed parts,
|
||||
* but doing it for the last polygon only would mean you are printing half of the layer in non-spiralize mode,
|
||||
* while each layer starts with a different part.
|
||||
* Two towers would result in alternating spiralize and non-spiralize layers.
|
||||
*
|
||||
* \param polygons The polygons
|
||||
* \param config The config with which to print the polygon lines
|
||||
* \param wall_overlap_computation The wall overlap compensation calculator for each given segment (optionally nullptr)
|
||||
* \param z_seam_type The seam type / poly start optimizer
|
||||
* \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over each polygon printed
|
||||
*/
|
||||
void addPolygonsByOptimizer(Polygons& polygons, GCodePathConfig* config, WallOverlapComputation* wall_overlap_computation = nullptr, EZSeamType z_seam_type = EZSeamType::SHORTEST, bool spiralize = false);
|
||||
|
||||
/*!
|
||||
* Add lines to the gcode with optimized order.
|
||||
@@ -439,7 +581,7 @@ public:
|
||||
*
|
||||
* \param gcode The gcode to write the planned paths to
|
||||
*/
|
||||
void writeGCode(GCodeExport& gcode, bool liftHeadIfNeeded, int layerThickness);
|
||||
void writeGCode(GCodeExport& gcode);
|
||||
|
||||
/*!
|
||||
* Complete all GcodePathConfig s by
|
||||
|
||||
+10
-39
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace cura {
|
||||
|
||||
void Infill::generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between)
|
||||
void Infill::generate(Polygons& result_polygons, Polygons& result_lines)
|
||||
{
|
||||
if (in_outline.size() == 0) return;
|
||||
if (line_distance == 0) return;
|
||||
@@ -24,16 +24,9 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines, Polygon
|
||||
generateTriangleInfill(result_lines);
|
||||
break;
|
||||
case EFillMethod::CONCENTRIC:
|
||||
PolygonUtils::offsetSafe(in_outline, outline_offset - infill_line_width / 2, infill_line_width, outline_offsetted, false); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
outline_offsetted = in_outline.offset(outline_offset - infill_line_width / 2); // - infill_line_width / 2 cause generateConcentricInfill expects [outline] to be the outer most polygon instead of the outer outline
|
||||
outline = &outline_offsetted;
|
||||
if (abs(infill_line_width - line_distance) < 10)
|
||||
{
|
||||
generateConcentricInfillDense(*outline, result_polygons, in_between, remove_overlapping_perimeters);
|
||||
}
|
||||
else
|
||||
{
|
||||
generateConcentricInfill(*outline, result_polygons, line_distance);
|
||||
}
|
||||
generateConcentricInfill(*outline, result_polygons, line_distance);
|
||||
break;
|
||||
case EFillMethod::ZIG_ZAG:
|
||||
generateZigZagInfill(result_lines, line_distance, fill_angle, connected_zigzags, use_endpieces);
|
||||
@@ -44,33 +37,11 @@ void Infill::generate(Polygons& result_polygons, Polygons& result_lines, Polygon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Infill::generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
while(outline.size() > 0)
|
||||
{
|
||||
for (unsigned int polyNr = 0; polyNr < outline.size(); polyNr++)
|
||||
{
|
||||
PolygonRef r = outline[polyNr];
|
||||
result.add(r);
|
||||
}
|
||||
Polygons next_outline;
|
||||
PolygonUtils::offsetExtrusionWidth(outline, true, infill_line_width, next_outline, in_between, avoidOverlappingPerimeters);
|
||||
outline = next_outline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int inset_value)
|
||||
{
|
||||
while(outline.size() > 0)
|
||||
{
|
||||
for (unsigned int polyNr = 0; polyNr < outline.size(); polyNr++)
|
||||
{
|
||||
PolygonRef r = outline[polyNr];
|
||||
result.add(r);
|
||||
}
|
||||
result.add(outline);
|
||||
outline = outline.offset(-inset_value);
|
||||
}
|
||||
}
|
||||
@@ -78,15 +49,15 @@ void Infill::generateConcentricInfill(Polygons outline, Polygons& result, int in
|
||||
|
||||
void Infill::generateGridInfill(Polygons& result)
|
||||
{
|
||||
generateLineInfill(result, line_distance * 2, fill_angle);
|
||||
generateLineInfill(result, line_distance * 2, fill_angle + 90);
|
||||
generateLineInfill(result, line_distance, fill_angle);
|
||||
generateLineInfill(result, line_distance, fill_angle + 90);
|
||||
}
|
||||
|
||||
void Infill::generateTriangleInfill(Polygons& result)
|
||||
{
|
||||
generateLineInfill(result, line_distance * 3, fill_angle);
|
||||
generateLineInfill(result, line_distance * 3, fill_angle + 60);
|
||||
generateLineInfill(result, line_distance * 3, fill_angle + 120);
|
||||
generateLineInfill(result, line_distance, fill_angle);
|
||||
generateLineInfill(result, line_distance, fill_angle + 60);
|
||||
generateLineInfill(result, line_distance, fill_angle + 120);
|
||||
}
|
||||
|
||||
void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector<std::vector<int64_t>>& cut_list)
|
||||
@@ -201,7 +172,7 @@ void Infill::generateLinearBasedInfill(const int outline_offset, bool safe_outli
|
||||
Polygons outline;
|
||||
if (outline_offset != 0)
|
||||
{
|
||||
PolygonUtils::offsetSafe(in_outline, outline_offset, infill_line_width, outline, remove_overlapping_perimeters && safe_outline_offset);
|
||||
outline = in_outline.offset(outline_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+3
-16
@@ -3,7 +3,7 @@
|
||||
#define INFILL_H
|
||||
|
||||
#include "utils/polygon.h"
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
// #include "ZigzagConnectorProcessor.h"
|
||||
#include "infill/ZigzagConnectorProcessor.h"
|
||||
#include "infill/NoZigZagConnectorProcessor.h"
|
||||
@@ -23,7 +23,6 @@ class Infill
|
||||
EFillMethod pattern; //!< the space filling pattern of the infill to generate
|
||||
const Polygons& in_outline; //!< a reference polygon for getting the actual area within which to generate infill (see outline_offset)
|
||||
int outline_offset; //!< Offset from Infill::in_outline to get the actual area within which to generate infill
|
||||
bool remove_overlapping_perimeters; //!< Whether to remove overlapping perimeter parts
|
||||
int infill_line_width; //!< The line width of the infill lines to generate
|
||||
int line_distance; //!< The distance between two infill lines / polygons
|
||||
int infill_overlap; //!< the distance by which to overlap with the actual area within which to generate infill
|
||||
@@ -32,11 +31,10 @@ class Infill
|
||||
bool use_endpieces; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself
|
||||
|
||||
public:
|
||||
Infill(EFillMethod pattern, const Polygons& in_outline, int outline_offset, bool remove_overlapping_perimeters, int infill_line_width, int line_distance, int infill_overlap, double fill_angle, bool connected_zigzags = false, bool use_endpieces = false)
|
||||
Infill(EFillMethod pattern, const Polygons& in_outline, int outline_offset, int infill_line_width, int line_distance, int infill_overlap, double fill_angle, bool connected_zigzags = false, bool use_endpieces = false)
|
||||
: pattern(pattern)
|
||||
, in_outline(in_outline)
|
||||
, outline_offset(outline_offset)
|
||||
, remove_overlapping_perimeters(remove_overlapping_perimeters)
|
||||
, infill_line_width(infill_line_width)
|
||||
, line_distance(line_distance)
|
||||
, infill_overlap(infill_overlap)
|
||||
@@ -50,9 +48,8 @@ public:
|
||||
*
|
||||
* \param result_polygons (output) The resulting polygons (from concentric infill)
|
||||
* \param result_lines (output) The resulting line segments (from linear infill types)
|
||||
* \param in_between (optional output) The areas in between two concecutive concentric infill polygons
|
||||
*/
|
||||
void generate(Polygons& result_polygons, Polygons& result_lines, Polygons* in_between);
|
||||
void generate(Polygons& result_polygons, Polygons& result_lines);
|
||||
|
||||
private:
|
||||
|
||||
@@ -64,16 +61,6 @@ private:
|
||||
*/
|
||||
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value);
|
||||
|
||||
/*!
|
||||
* Generate dense concentric infill (100%)
|
||||
*
|
||||
* \param outline The actual outline of the area within which to generate infill
|
||||
* \param result (output) The resulting polygons
|
||||
* \param in_between (output) The areas in between each two consecutive polygons
|
||||
* \param remove_overlapping_perimeters Whether to remove overlapping perimeter parts
|
||||
*/
|
||||
void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, bool remove_overlapping_perimeters);
|
||||
|
||||
/*!
|
||||
* Generate a rectangular grid of infill lines
|
||||
* \param result (output) The resulting lines
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include "inset.h"
|
||||
#include "utils/polygonUtils.h"
|
||||
namespace cura {
|
||||
|
||||
void generateInsets(SliceLayerPart* part, int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
if (insetCount == 0)
|
||||
{
|
||||
part->insets.push_back(part->outline);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i=0; i<insetCount; i++)
|
||||
{
|
||||
part->insets.push_back(Polygons());
|
||||
if (i == 0)
|
||||
{
|
||||
PolygonUtils::offsetSafe(part->outline, -line_width_0 / 2 - wall_0_inset, line_width_0, part->insets[0], avoidOverlappingPerimeters_0);
|
||||
} else if (i == 1)
|
||||
{
|
||||
int offset_from_first_boundary_for_edge_of_outer_wall = -line_width_0 / 2; // the outer bounds of the perimeter gaps
|
||||
// you might think this /\ should be (1): -line_width_0 / 2; or you might think it should be (2): -nozzle_size / 2
|
||||
// (1): the volume extruded is the right volume; the infill gaps overlap more with the outer wall
|
||||
// (2): the outer wall already fills up extra space due to the fact that the nozzle hole overlaps with a part inside the outer wall
|
||||
PolygonUtils::offsetSafe(part->insets[0], -line_width_0 / 2 + wall_0_inset - line_width_x / 2, offset_from_first_boundary_for_edge_of_outer_wall, line_width_x, part->insets[1], &part->perimeterGaps, avoidOverlappingPerimeters);
|
||||
} else
|
||||
{
|
||||
PolygonUtils::offsetExtrusionWidth(part->insets[i-1], true, line_width_x, part->insets[i], &part->perimeterGaps, avoidOverlappingPerimeters);
|
||||
}
|
||||
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
part->insets[i].simplify();
|
||||
if (part->insets[i].size() < 1)
|
||||
{
|
||||
part->insets.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generateInsets(SliceLayer* layer, int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
|
||||
{
|
||||
for(unsigned int partNr = 0; partNr < layer->parts.size(); partNr++)
|
||||
{
|
||||
generateInsets(&layer->parts[partNr], wall_0_inset, line_width_0, line_width_x, insetCount, avoidOverlappingPerimeters_0, avoidOverlappingPerimeters);
|
||||
}
|
||||
|
||||
//Remove the parts which did not generate an inset. As these parts are too small to print,
|
||||
// and later code can now assume that there is always minimal 1 inset line.
|
||||
for(unsigned int partNr = 0; partNr < layer->parts.size(); partNr++)
|
||||
{
|
||||
if (layer->parts[partNr].insets.size() < 1)
|
||||
{
|
||||
layer->parts.erase(layer->parts.begin() + partNr);
|
||||
partNr -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,41 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef INSET_H
|
||||
#define INSET_H
|
||||
|
||||
#include "sliceDataStorage.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Generates the insets / perimeters for a single layer part.
|
||||
*
|
||||
* \param part The part for which to generate the insets.
|
||||
* \param wall_0_inset The offset applied to the outer wall
|
||||
* \param line_width_0 line width of the outer wall
|
||||
* \param line_width_x line width of other walls
|
||||
* \param insetCount The number of insets to to generate
|
||||
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
|
||||
*/
|
||||
void generateInsets(SliceLayerPart* part, int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
|
||||
/*!
|
||||
* Generates the insets / perimeters for all parts in a layer.
|
||||
*
|
||||
* Note that the second inset gets offsetted by \p line_width_0 instead of the first,
|
||||
* which leads to better results for a smaller \p line_width_0 than \p line_width_x and when printing the outer wall last.
|
||||
*
|
||||
* \param layer The layer for which to generate the insets.
|
||||
* \param wall_0_inset The offset applied to the outer wall
|
||||
* \param line_width_0 line width of the outer wall
|
||||
* \param line_width_x line width of other walls
|
||||
* \param insetCount The number of insets to to generate
|
||||
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p part)
|
||||
*/
|
||||
void generateInsets(SliceLayer* layer, int wall_0_inset, int line_width_0, int line_width_x, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//INSET_H
|
||||
@@ -1,10 +1,10 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "LayerPart.h"
|
||||
#include "../settings.h"
|
||||
#include "../Progress.h"
|
||||
#include "layerPart.h"
|
||||
#include "settings/settings.h"
|
||||
#include "progress/Progress.h"
|
||||
|
||||
#include "../utils/SVG.h" // debug output
|
||||
#include "utils/SVG.h" // debug output
|
||||
|
||||
/*
|
||||
The layer-part creation step is the first step in creating actual useful data for 3D printing.
|
||||
@@ -26,15 +26,15 @@ void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool uni
|
||||
|
||||
if (union_all_remove_holes)
|
||||
{
|
||||
for(unsigned int i=0; i<layer->polygonList.size(); i++)
|
||||
for(unsigned int i=0; i<layer->polygons.size(); i++)
|
||||
{
|
||||
if (layer->polygonList[i].orientation())
|
||||
layer->polygonList[i].reverse();
|
||||
if (layer->polygons[i].orientation())
|
||||
layer->polygons[i].reverse();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PolygonsPart> result;
|
||||
result = layer->polygonList.splitIntoParts(union_layers || union_all_remove_holes);
|
||||
result = layer->polygons.splitIntoParts(union_layers || union_all_remove_holes);
|
||||
for(unsigned int i=0; i<result.size(); i++)
|
||||
{
|
||||
storageLayer.parts.emplace_back();
|
||||
@@ -42,14 +42,14 @@ void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool uni
|
||||
storageLayer.parts[i].boundaryBox.calculate(storageLayer.parts[i].outline);
|
||||
}
|
||||
}
|
||||
void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_layers, bool union_all_remove_holes)
|
||||
void createLayerParts(SliceMeshStorage& mesh, Slicer* slicer, bool union_layers, bool union_all_remove_holes)
|
||||
{
|
||||
for(unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
|
||||
{
|
||||
storage.layers.push_back(SliceLayer());
|
||||
storage.layers[layer_nr].sliceZ = slicer->layers[layer_nr].z;
|
||||
storage.layers[layer_nr].printZ = slicer->layers[layer_nr].z;
|
||||
createLayerWithParts(storage.layers[layer_nr], &slicer->layers[layer_nr], union_layers, union_all_remove_holes);
|
||||
mesh.layers.push_back(SliceLayer());
|
||||
mesh.layers[layer_nr].sliceZ = slicer->layers[layer_nr].z;
|
||||
mesh.layers[layer_nr].printZ = slicer->layers[layer_nr].z;
|
||||
createLayerWithParts(mesh.layers[layer_nr], &slicer->layers[layer_nr], union_layers, union_all_remove_holes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_LAYERPART_H
|
||||
#define SLICER_LAYERPART_H
|
||||
#ifndef LAYERPART_H
|
||||
#define LAYERPART_H
|
||||
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "Slicer.h"
|
||||
#include "../commandSocket.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "slicer.h"
|
||||
#include "commandSocket.h"
|
||||
|
||||
/*
|
||||
The layer-part creation step is the first step in creating actual useful data for 3D printing.
|
||||
@@ -22,10 +22,10 @@ namespace cura {
|
||||
|
||||
void createLayerWithParts(SliceLayer& storageLayer, SlicerLayer* layer, bool union_layers, bool union_all_remove_holes);
|
||||
|
||||
void createLayerParts(SliceMeshStorage& storage, Slicer* slicer, bool union_layers, bool union_all_remove_holes);
|
||||
void createLayerParts(SliceMeshStorage& mesh, Slicer* slicer, bool union_layers, bool union_all_remove_holes);
|
||||
|
||||
void layerparts2HTML(SliceDataStorage& storage, const char* filename, bool all_layers = true, int layer_nr = -1);
|
||||
void layerparts2HTML(SliceDataStorage& mesh, const char* filename, bool all_layers = true, int layer_nr = -1);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//SLICER_LAYERPART_H
|
||||
#endif//LAYERPART_H
|
||||
+90
-14
@@ -16,7 +16,9 @@
|
||||
#include "utils/string.h"
|
||||
|
||||
#include "FffProcessor.h"
|
||||
#include "settingRegistry.h"
|
||||
#include "settings/SettingRegistry.h"
|
||||
|
||||
#include "settings/SettingsToGV.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -28,23 +30,25 @@ void print_usage()
|
||||
cura::logError("CuraEngine help\n");
|
||||
cura::logError("\tShow this help message\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("CuraEngine connect <host>[:<port>] [-j <settings.json>]\n");
|
||||
cura::logError("CuraEngine connect <host>[:<port>] [-j <settings.def.json>]\n");
|
||||
cura::logError(" --connect <host>[:<port>]\n\tConnect to <host> via a command socket, \n\tinstead of passing information via the command line\n");
|
||||
cura::logError(" -j\n\tLoad settings.json file to register all settings and their defaults\n");
|
||||
cura::logError(" -j<settings.def.json>\n\tLoad settings.json file to register all settings and their defaults\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("CuraEngine slice [-v] [-p] [-j <settings.json>] [-s <settingkey>=<value>] [-g] [-e] [-o <output.gcode>] [-l <model.stl>] [--next]\n");
|
||||
cura::logError("CuraEngine slice [-v] [-p] [-j <settings.json>] [-s <settingkey>=<value>] [-g] [-e<extruder_nr>] [-o <output.gcode>] [-l <model.stl>] [--next]\n");
|
||||
cura::logError(" -v\n\tIncrease the verbose level (show log messages).\n");
|
||||
cura::logError(" -p\n\tLog progress information.\n");
|
||||
cura::logError(" -j\n\tLoad settings.json file to register all settings and their defaults.\n");
|
||||
cura::logError(" -j\n\tLoad settings.def.json file to register all settings and their defaults.\n");
|
||||
cura::logError(" -s <setting>=<value>\n\tSet a setting to a value for the last supplied object, \n\textruder train, or general settings.\n");
|
||||
cura::logError(" -l <model_file>\n\tLoad an STL model. \n");
|
||||
cura::logError(" -g\n\tSwitch setting focus to the current mesh group only.\n\tUsed for one-at-a-time printing.\n");
|
||||
cura::logError(" -e\n\tAdd a new extruder train.\n");
|
||||
cura::logError(" -e<extruder_nr>\n\tSwitch setting focus to the extruder train with the given number.\n");
|
||||
cura::logError(" --next\n\tGenerate gcode for the previously supplied mesh group and append that to \n\tthe gcode of further models for one-at-a-time printing.\n");
|
||||
cura::logError(" -o <output_file>\n\tSpecify a file to which to write the generated gcode.\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("The settings are appended to the last supplied object:\n");
|
||||
cura::logError("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e [extruder train settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object settings] \n\t--next [next group settings]\n\t... etc.\n");
|
||||
cura::logError("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e0 [extruder train 0 settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object settings] \n\t--next [next group settings]\n\t... etc.\n");
|
||||
cura::logError("\n");
|
||||
cura::logError("In order to load machine definitions from custom locations, you need to create the environment variable CURA_ENGINE_SEARCH_PATH, which should contain all search paths delimited by a (semi-)colon.\n");
|
||||
cura::logError("\n");
|
||||
}
|
||||
|
||||
@@ -77,7 +81,6 @@ void connect(int argc, char **argv)
|
||||
port = std::stoi(ip_port.substr(ip_port.find(':') + 1).data());
|
||||
}
|
||||
|
||||
|
||||
for(int argn = 3; argn < argc; argn++)
|
||||
{
|
||||
char* str = argv[argn];
|
||||
@@ -92,7 +95,7 @@ void connect(int argc, char **argv)
|
||||
break;
|
||||
case 'j':
|
||||
argn++;
|
||||
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn]))
|
||||
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn], FffProcessor::getInstance()))
|
||||
{
|
||||
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
|
||||
}
|
||||
@@ -120,7 +123,8 @@ void slice(int argc, char **argv)
|
||||
|
||||
int extruder_train_nr = 0;
|
||||
|
||||
SettingsBase* last_extruder_train = meshgroup->createExtruderTrain(0);
|
||||
SettingsBase* last_extruder_train = meshgroup->createExtruderTrain(0);
|
||||
// extruder defaults cannot be loaded yet cause no json has been parsed
|
||||
SettingsBase* last_settings_object = FffProcessor::getInstance();
|
||||
for(int argn = 2; argn < argc; argn++)
|
||||
{
|
||||
@@ -139,7 +143,8 @@ void slice(int argc, char **argv)
|
||||
|
||||
for (int extruder_nr = 0; extruder_nr < FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count"); extruder_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
meshgroup->getExtruderTrain(extruder_nr)->setExtruderTrainDefaults(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_nr); // create new extruder train objects or use already existing ones
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_nr, train);
|
||||
}
|
||||
//start slicing
|
||||
FffProcessor::getInstance()->processMeshGroup(meshgroup);
|
||||
@@ -150,6 +155,7 @@ void slice(int argc, char **argv)
|
||||
meshgroup = new MeshGroup(FffProcessor::getInstance());
|
||||
last_extruder_train = meshgroup->createExtruderTrain(0);
|
||||
last_settings_object = meshgroup;
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(0, last_extruder_train);
|
||||
|
||||
}catch(...){
|
||||
cura::logError("Unknown exception\n");
|
||||
@@ -171,7 +177,7 @@ void slice(int argc, char **argv)
|
||||
break;
|
||||
case 'j':
|
||||
argn++;
|
||||
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn]))
|
||||
if (SettingRegistry::getInstance()->loadJSONsettings(argv[argn], last_settings_object))
|
||||
{
|
||||
cura::logError("ERROR: Failed to load json file: %s\n", argv[argn]);
|
||||
}
|
||||
@@ -181,6 +187,7 @@ void slice(int argc, char **argv)
|
||||
extruder_train_nr = int(*str - '0'); // TODO: parse int instead (now "-e10"="-e:" , "-e11"="-e;" , "-e12"="-e<" .. etc)
|
||||
last_settings_object = meshgroup->createExtruderTrain(extruder_train_nr);
|
||||
last_extruder_train = last_settings_object;
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_train_nr, last_extruder_train);
|
||||
break;
|
||||
case 'l':
|
||||
argn++;
|
||||
@@ -194,7 +201,7 @@ void slice(int argc, char **argv)
|
||||
}
|
||||
else
|
||||
{
|
||||
last_settings_object = meshgroup->meshes.back();
|
||||
last_settings_object = &(meshgroup->meshes.back()); // pointer is valid until a new object is added, so this is OK
|
||||
}
|
||||
break;
|
||||
case 'o':
|
||||
@@ -243,7 +250,8 @@ void slice(int argc, char **argv)
|
||||
int extruder_count = FffProcessor::getInstance()->getSettingAsCount("machine_extruder_count");
|
||||
for (extruder_train_nr = 0; extruder_train_nr < extruder_count; extruder_train_nr++)
|
||||
{ // initialize remaining extruder trains and load the defaults
|
||||
meshgroup->createExtruderTrain(extruder_train_nr)->setExtruderTrainDefaults(extruder_train_nr); // create new extruder train objects or use already existing ones
|
||||
ExtruderTrain* train = meshgroup->createExtruderTrain(extruder_train_nr); // create new extruder train objects or use already existing ones
|
||||
SettingRegistry::getInstance()->loadExtruderJSONsettings(extruder_train_nr, train);
|
||||
}
|
||||
|
||||
|
||||
@@ -326,6 +334,74 @@ int main(int argc, char **argv)
|
||||
print_usage();
|
||||
exit(0);
|
||||
}
|
||||
else if (stringcasecompare(argv[1], "analyse") == 0)
|
||||
{ // CuraEngine analyse [json] [output.gv] [engine_settings] -[p|i|e|w]
|
||||
// p = show parent-child relations
|
||||
// i = show inheritance function
|
||||
// e = show error functions
|
||||
// w = show warning functions
|
||||
// dot refl_ff.gv -Tpng > rafl_ff_dotted.png
|
||||
// see meta/HOWTO.txt
|
||||
|
||||
bool parent_child_viz = false;
|
||||
bool inherit_viz = false;
|
||||
bool warning_viz = false;
|
||||
bool error_viz = false;
|
||||
if (argc >= 6)
|
||||
{
|
||||
char* str = argv[5];
|
||||
if (str[0] == '-')
|
||||
{
|
||||
for(str++; *str; str++)
|
||||
{
|
||||
switch(*str)
|
||||
{
|
||||
case 'p':
|
||||
parent_child_viz = true;
|
||||
break;
|
||||
case 'i':
|
||||
inherit_viz = true;
|
||||
break;
|
||||
case 'e':
|
||||
error_viz = true;
|
||||
break;
|
||||
case 'w':
|
||||
warning_viz = true;
|
||||
break;
|
||||
default:
|
||||
cura::logError("Unknown option: %c\n", *str);
|
||||
print_call(argc, argv);
|
||||
print_usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cura::logError("\n");
|
||||
cura::logError("usage:\n");
|
||||
cura::logError("CuraEngine analyse <fdmPrinter.def.json> <output.gv> <engine_settings_list> -[p|i|e|w]\n");
|
||||
cura::logError("\tGenerate a grpah to visualize the setting inheritance structure.\n");
|
||||
cura::logError("\t<fdmPrinter.def.json>\n\tThe base seting definitions file.\n");
|
||||
cura::logError("\t<output.gv>\n\tThe output file.\n");
|
||||
cura::logError("\t<engine_settings_list>\n\tA text file with all setting keys used in the engine, separated by newlines.\n");
|
||||
cura::logError("\t-[p|i|e|w]\n\tOptions for what to include in the visualization\n");
|
||||
cura::logError("\t\tp\tVisualize the parent-child relationship.\n");
|
||||
cura::logError("\t\ti\tVisualize inheritance function relationships.\n");
|
||||
cura::logError("\t\te\tVisualize (max/min) error function relationships.\n");
|
||||
cura::logError("\t\tw\tVisualize (max/min) warning function relationships.\n");
|
||||
cura::logError("\n");
|
||||
|
||||
}
|
||||
|
||||
SettingsToGv gv_out(argv[3], argv[4], parent_child_viz, inherit_viz, error_viz, warning_viz);
|
||||
if (gv_out.generate(std::string(argv[2])))
|
||||
{
|
||||
cura::logError("ERROR: Failed to analyse json file: %s\n", argv[2]);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
cura::logError("Unknown command: %s\n", argv[1]);
|
||||
|
||||
+10
-32
@@ -5,31 +5,26 @@ namespace cura
|
||||
{
|
||||
|
||||
const int vertex_meld_distance = MM2INT(0.03);
|
||||
/*!
|
||||
* returns a hash for the location, but first divides by the vertex_meld_distance,
|
||||
* so that any point within a box of vertex_meld_distance by vertex_meld_distance would get mapped to the same hash.
|
||||
*/
|
||||
static inline uint32_t pointHash(const Point3& p)
|
||||
{
|
||||
return ((p.x + vertex_meld_distance/2) / vertex_meld_distance) ^ (((p.y + vertex_meld_distance/2) / vertex_meld_distance) << 10) ^ (((p.z + vertex_meld_distance/2) / vertex_meld_distance) << 20);
|
||||
}
|
||||
|
||||
Mesh::Mesh(SettingsBaseVirtual* parent)
|
||||
: SettingsBase(parent)
|
||||
: SettingsBase(parent, std::string("mesh"))
|
||||
{
|
||||
}
|
||||
|
||||
bool Mesh::addFace(Point3& v0, Point3& v1, Point3& v2)
|
||||
void Mesh::addFace(Point3& v0, Point3& v1, Point3& v2)
|
||||
{
|
||||
int vi0 = findIndexOfVertex(v0);
|
||||
int vi1 = findIndexOfVertex(v1);
|
||||
int vi2 = findIndexOfVertex(v2);
|
||||
return addFace(vi0, vi1, vi2);
|
||||
}
|
||||
|
||||
bool Mesh::addFace(int vi0, int vi1, int vi2)
|
||||
{
|
||||
if (vi0 == vi1 || vi1 == vi2 || vi0 == vi2)
|
||||
{
|
||||
// the face has two vertices which get assigned the same location. Don't add the face.
|
||||
return false;
|
||||
}
|
||||
if (vi0 == vi1 || vi1 == vi2 || vi0 == vi2) return; // the face has two vertices which get assigned the same location. Don't add the face.
|
||||
|
||||
int idx = faces.size(); // index of face to be added
|
||||
faces.emplace_back();
|
||||
@@ -40,8 +35,6 @@ bool Mesh::addFace(int vi0, int vi1, int vi2)
|
||||
vertices[face.vertex_index[0]].connected_faces.push_back(idx);
|
||||
vertices[face.vertex_index[1]].connected_faces.push_back(idx);
|
||||
vertices[face.vertex_index[2]].connected_faces.push_back(idx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mesh::clear()
|
||||
@@ -74,15 +67,12 @@ Point3 Mesh::max() const
|
||||
{
|
||||
return aabb.max;
|
||||
}
|
||||
|
||||
void Mesh::addVertex(const Point3& v)
|
||||
AABB3D Mesh::getAABB() const
|
||||
{
|
||||
uint32_t hash = pointHash(v);
|
||||
vertex_hash_map[hash].push_back(vertices.size());
|
||||
vertices.emplace_back(v);
|
||||
aabb.include(v);
|
||||
return aabb;
|
||||
}
|
||||
|
||||
|
||||
int Mesh::findIndexOfVertex(const Point3& v)
|
||||
{
|
||||
uint32_t hash = pointHash(v);
|
||||
@@ -198,16 +188,4 @@ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
bool Mesh::registerFaceSlice(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result)
|
||||
{
|
||||
// do nothing for a non-textured mesh
|
||||
return false;
|
||||
}
|
||||
|
||||
float Mesh::getColor(MatCoord bitmap_coord)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+4
-30
@@ -1,9 +1,8 @@
|
||||
#ifndef MESH_H
|
||||
#define MESH_H
|
||||
|
||||
#include "settings.h"
|
||||
#include "utils/AABB.h"
|
||||
#include "MatSegment.h"
|
||||
#include "settings/settings.h"
|
||||
#include "utils/AABB3D.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -66,31 +65,13 @@ public:
|
||||
|
||||
Mesh(SettingsBaseVirtual* parent); //!< initializes the settings
|
||||
|
||||
virtual ~Mesh() {} //!< Destructor
|
||||
|
||||
/*!
|
||||
* add a face to the mesh without settings it's connected_faces.
|
||||
*
|
||||
* Don't add a face when the surface is zero mm^2
|
||||
*
|
||||
* \return whether a face has been added
|
||||
*/
|
||||
bool addFace(Point3& v0, Point3& v1, Point3& v2);
|
||||
/*!
|
||||
* add a face to the mesh without settings it's connected_faces.
|
||||
*
|
||||
* Don't add a face when the surface is zero mm^2
|
||||
*
|
||||
* \return whether a face has been added
|
||||
*/
|
||||
bool addFace(int vi0, int vi1, int vi2);
|
||||
void addVertex(const Point3& v);
|
||||
|
||||
void addFace(Point3& v0, Point3& v1, Point3& v2); //!< add a face to the mesh without settings it's connected_faces.
|
||||
void clear(); //!< clears all data
|
||||
void finish(); //!< complete the model : set the connected_face_index fields of the faces.
|
||||
|
||||
Point3 min() const; //!< min (in x,y and z) vertex of the bounding box
|
||||
Point3 max() const; //!< max (in x,y and z) vertex of the bounding box
|
||||
AABB3D getAABB() const; //!< Get the axis aligned bounding box
|
||||
|
||||
/*!
|
||||
* Offset the whole mesh (all vertices and the bounding box).
|
||||
@@ -104,13 +85,6 @@ public:
|
||||
aabb.offset(offset);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \return Whether a texture line segment has been created
|
||||
*/
|
||||
virtual bool registerFaceSlice(unsigned int face_idx, unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, Point segment_start, Point segment_end, MatSegment& result);
|
||||
|
||||
virtual float getColor(MatCoord bitmap_coord);
|
||||
|
||||
private:
|
||||
int findIndexOfVertex(const Point3& v); //!< find index of vertex close to the given point, or create a new vertex and return its index.
|
||||
/*!
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "multiVolumes.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
//Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas.
|
||||
for (unsigned int volume_1_idx = 0; volume_1_idx < volumes.size(); volume_1_idx++)
|
||||
{
|
||||
Slicer& volume_1 = *volumes[volume_1_idx];
|
||||
if (volume_1.mesh->getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (unsigned int volume_2_idx = 0; volume_2_idx < volume_1_idx; volume_2_idx++)
|
||||
{
|
||||
Slicer& volume_2 = *volumes[volume_2_idx];
|
||||
if (volume_2.mesh->getSettingBoolean("infill_mesh"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!volume_1.mesh->getAABB().hit(volume_2.mesh->getAABB()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (unsigned int layerNr = 0; layerNr < volume_1.layers.size(); layerNr++)
|
||||
{
|
||||
SlicerLayer& layer1 = volume_1.layers[layerNr];
|
||||
SlicerLayer& layer2 = volume_2.layers[layerNr];
|
||||
layer1.polygons = layer1.polygons.difference(layer2.polygons);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
//This generates some overlap in dual extrusion, for better bonding in touching parts.
|
||||
void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
if (volumes.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int offset_to_merge_other_merged_volumes = 20;
|
||||
for (Slicer* volume : volumes)
|
||||
{
|
||||
int overlap = volume->mesh->getSettingInMicrons("multiple_mesh_overlap");
|
||||
if (volume->mesh->getSettingBoolean("infill_mesh")
|
||||
|| overlap == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (unsigned int layer_nr = 0; layer_nr < volume->layers.size(); layer_nr++)
|
||||
{
|
||||
Polygons all_other_volumes;
|
||||
for (Slicer* other_volume : volumes)
|
||||
{
|
||||
if (other_volume->mesh->getSettingBoolean("infill_mesh")
|
||||
|| !other_volume->mesh->getAABB().hit(volume->mesh->getAABB())
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SlicerLayer& other_volume_layer = other_volume->layers[layer_nr];
|
||||
all_other_volumes = all_other_volumes.unionPolygons(other_volume_layer.polygons.offset(offset_to_merge_other_merged_volumes));
|
||||
}
|
||||
all_other_volumes = all_other_volumes.offset(-offset_to_merge_other_merged_volumes);
|
||||
|
||||
SlicerLayer& volume_layer = volume->layers[layer_nr];
|
||||
volume_layer.polygons.unionPolygons(all_other_volumes.intersection(volume_layer.polygons.offset(overlap / 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef SLICER_MULTIVOLUMES_H
|
||||
#define SLICER_MULTIVOLUMES_H
|
||||
#ifndef MULTIVOLUMES_H
|
||||
#define MULTIVOLUMES_H
|
||||
|
||||
#include "../sliceDataStorage.h"
|
||||
#include "Slicer.h"
|
||||
#include "sliceDataStorage.h"
|
||||
#include "slicer.h"
|
||||
|
||||
/* This file contains code to help fixing up and changing layers that are build from multiple volumes. */
|
||||
namespace cura {
|
||||
@@ -13,8 +13,8 @@ void carveMultipleVolumes(std::vector<Slicer*> &meshes);
|
||||
* Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
* This generates some overlap in dual extrusion, for better bonding in touching parts.
|
||||
*/
|
||||
void generateMultipleVolumesOverlap(std::vector<Slicer*> &meshes, int overlap);
|
||||
void generateMultipleVolumesOverlap(std::vector<Slicer*> &meshes);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//SLICER_MULTIVOLUMES_H
|
||||
#endif//MULTIVOLUMES_H
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include "utils/polygon.h"
|
||||
#include "settings.h"
|
||||
#include "settings/settings.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
/** Copyright (C) 2015 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "Progress.h"
|
||||
|
||||
#include "commandSocket.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "../commandSocket.h"
|
||||
#include "../utils/gettime.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
double Progress::times [] =
|
||||
{
|
||||
0.0,
|
||||
5.269,
|
||||
1.533,
|
||||
22.953,
|
||||
51.009,
|
||||
48.858,
|
||||
154.62,
|
||||
0.1
|
||||
0.0, // START = 0,
|
||||
5.269, // SLICING = 1,
|
||||
1.533, // PARTS = 2,
|
||||
71.811, // INSET_SKIN = 3
|
||||
51.009, // SUPPORT = 4,
|
||||
154.62, // EXPORT = 5,
|
||||
0.1 // FINISH = 6
|
||||
};
|
||||
std::string Progress::names [] =
|
||||
{
|
||||
"start",
|
||||
"slice",
|
||||
"layerparts",
|
||||
"inset",
|
||||
"inset+skin",
|
||||
"support",
|
||||
"skin",
|
||||
"export",
|
||||
"process"
|
||||
};
|
||||
@@ -39,9 +37,8 @@ const Progress::Stage Progress::stages[] =
|
||||
Progress::Stage::START,
|
||||
Progress::Stage::SLICING,
|
||||
Progress::Stage::PARTS,
|
||||
Progress::Stage::INSET,
|
||||
Progress::Stage::INSET_SKIN,
|
||||
Progress::Stage::SUPPORT,
|
||||
Progress::Stage::SKIN,
|
||||
Progress::Stage::EXPORT,
|
||||
Progress::Stage::FINISH
|
||||
};
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "utils/logoutput.h"
|
||||
#include "utils/gettime.h"
|
||||
#include "../utils/logoutput.h"
|
||||
#include "../utils/gettime.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
class CommandSocket;
|
||||
|
||||
#define N_PROGRESS_STAGES 8
|
||||
#define N_PROGRESS_STAGES 7
|
||||
|
||||
/*!
|
||||
* Class for handling the progress bar and the progress logging.
|
||||
@@ -30,11 +30,10 @@ public:
|
||||
START = 0,
|
||||
SLICING = 1,
|
||||
PARTS = 2,
|
||||
INSET = 3,
|
||||
INSET_SKIN = 3,
|
||||
SUPPORT = 4,
|
||||
SKIN = 5,
|
||||
EXPORT = 6,
|
||||
FINISH = 7
|
||||
EXPORT = 5,
|
||||
FINISH = 6
|
||||
};
|
||||
private:
|
||||
static double times [N_PROGRESS_STAGES]; //!< Time estimates per stage
|
||||
@@ -0,0 +1,29 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef PROGRESS_PROGRESS_ESTIMATOR_H
|
||||
#define PROGRESS_PROGRESS_ESTIMATOR_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
/*
|
||||
* ProgressEstimator is a finger-tree with ProgressEstimatorLinear as leaves.
|
||||
*
|
||||
* Each (non-leaf) node consists of a ProgressStageEstimator which consists of several stages.
|
||||
*
|
||||
* The structure of this tree is an oversimplification of the call graph of CuraEngine.
|
||||
*
|
||||
*/
|
||||
|
||||
class ProgressEstimator
|
||||
{
|
||||
public:
|
||||
virtual double progress(int current_step) = 0;
|
||||
virtual ~ProgressEstimator()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // PROGRESS_PROGRESS_ESTIMATOR_H
|
||||
@@ -0,0 +1,29 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef PROGRESS_PROGRESS_ESTIMATOR_LINEAR_H
|
||||
#define PROGRESS_PROGRESS_ESTIMATOR_LINEAR_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ProgressEstimator.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
class ProgressEstimatorLinear : public ProgressEstimator
|
||||
{
|
||||
unsigned int total_steps;
|
||||
public:
|
||||
ProgressEstimatorLinear(unsigned int total_steps)
|
||||
: total_steps(total_steps)
|
||||
{
|
||||
}
|
||||
double progress(int current_step)
|
||||
{
|
||||
return double(current_step) / double(total_steps);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // PROGRESS_PROGRESS_ESTIMATOR_LINEAR_H
|
||||
@@ -0,0 +1,52 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "ProgressStageEstimator.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
ProgressStageEstimator::ProgressStageEstimator(std::vector< double >& relative_time_estimates)
|
||||
: total_estimated_time(0)
|
||||
, accumulated_estimate(0)
|
||||
, current_stage_idx(-1)
|
||||
{
|
||||
stages.reserve(relative_time_estimates.size());
|
||||
for (double relative_estimated_time : relative_time_estimates)
|
||||
{
|
||||
stages.emplace_back(relative_estimated_time);
|
||||
total_estimated_time += relative_estimated_time;
|
||||
}
|
||||
}
|
||||
|
||||
ProgressStageEstimator::~ProgressStageEstimator()
|
||||
{
|
||||
for (ProgressStage& stage : stages)
|
||||
{
|
||||
delete stage.stage;
|
||||
}
|
||||
}
|
||||
|
||||
double ProgressStageEstimator::progress(int current_step)
|
||||
{
|
||||
ProgressStage& current_stage = stages[current_stage_idx];
|
||||
return (accumulated_estimate + current_stage.stage->progress(current_step) * current_stage.relative_estimated_time) / total_estimated_time;
|
||||
}
|
||||
|
||||
void ProgressStageEstimator::nextStage(ProgressEstimator* stage)
|
||||
{
|
||||
if (current_stage_idx >= int(stages.size()) - 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (current_stage_idx >= 0)
|
||||
{
|
||||
ProgressStage& current_stage = stages[current_stage_idx];
|
||||
accumulated_estimate += current_stage.relative_estimated_time;
|
||||
}
|
||||
current_stage_idx++;
|
||||
stages[current_stage_idx].stage = stage;
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,54 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef PROGRESS_PROGRESS_STAGE_ESTIMATOR_H
|
||||
#define PROGRESS_PROGRESS_STAGE_ESTIMATOR_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ProgressEstimator.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* A staged progress estimator which estimates each stage to have different times.
|
||||
*/
|
||||
class ProgressStageEstimator : public ProgressEstimator
|
||||
{
|
||||
struct ProgressStage
|
||||
{
|
||||
double relative_estimated_time;
|
||||
ProgressEstimator* stage;
|
||||
ProgressStage(double relative_estimated_time)
|
||||
: relative_estimated_time(relative_estimated_time)
|
||||
, stage(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
protected:
|
||||
std::vector<ProgressStage> stages;
|
||||
double total_estimated_time;
|
||||
|
||||
private:
|
||||
double accumulated_estimate;
|
||||
int current_stage_idx;
|
||||
|
||||
public:
|
||||
ProgressStageEstimator(std::vector<double>& relative_time_estimates);
|
||||
|
||||
double progress(int current_step);
|
||||
|
||||
/*!
|
||||
*
|
||||
* \warning This class is responsible for deleting the \p stage
|
||||
*
|
||||
*/
|
||||
void nextStage(ProgressEstimator* stage);
|
||||
|
||||
~ProgressStageEstimator();
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // PROGRESS_PROGRESS_STAGE_ESTIMATOR_H
|
||||
@@ -1,369 +0,0 @@
|
||||
#include "settingRegistry.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream> // debug IO
|
||||
#include <libgen.h> // dirname
|
||||
#include <string>
|
||||
#include <algorithm> // find_if
|
||||
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
#include "rapidjson/rapidjson.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/error/en.h"
|
||||
#include "rapidjson/filereadstream.h"
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
SettingRegistry SettingRegistry::instance; // define settingRegistry
|
||||
|
||||
std::string SettingRegistry::toString(rapidjson::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case rapidjson::Type::kNullType: return "null";
|
||||
case rapidjson::Type::kFalseType: return "false";
|
||||
case rapidjson::Type::kTrueType: return "true";
|
||||
case rapidjson::Type::kObjectType: return "object";
|
||||
case rapidjson::Type::kArrayType: return "array";
|
||||
case rapidjson::Type::kStringType: return "string";
|
||||
case rapidjson::Type::kNumberType: return "number";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SettingContainer::SettingContainer(std::string key, std::string label)
|
||||
: key(key)
|
||||
, label(label)
|
||||
{
|
||||
}
|
||||
|
||||
SettingConfig* SettingContainer::addChild(std::string key, std::string label)
|
||||
{
|
||||
children.emplace_back(key, label);
|
||||
return &children.back();
|
||||
}
|
||||
|
||||
SettingConfig& SettingContainer::getOrCreateChild(std::string key, std::string label)
|
||||
{
|
||||
auto child_it = std::find_if(children.begin(), children.end(), [&key](SettingConfig& child) { return child.key == key; } );
|
||||
if (child_it == children.end())
|
||||
{
|
||||
children.emplace_back(key, label);
|
||||
return children.back();
|
||||
}
|
||||
else
|
||||
{
|
||||
return *child_it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SettingConfig::SettingConfig(std::string key, std::string label)
|
||||
: SettingContainer(key, label)
|
||||
{
|
||||
// std::cerr << key << std::endl; // debug output to show all frontend registered settings...
|
||||
}
|
||||
|
||||
void SettingContainer::debugOutputAllSettings() const
|
||||
{
|
||||
std::cerr << "\nCATEGORY: " << key << std::endl;
|
||||
for (const SettingConfig& child : children)
|
||||
{
|
||||
child.debugOutputAllSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SettingRegistry::settingExists(std::string key) const
|
||||
{
|
||||
return settings.find(key) != settings.end();
|
||||
}
|
||||
|
||||
SettingConfig* SettingRegistry::getSettingConfig(std::string key) const
|
||||
{
|
||||
auto it = settings.find(key);
|
||||
if (it == settings.end())
|
||||
return nullptr;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
SettingContainer* SettingRegistry::getCategory(std::string key)
|
||||
{
|
||||
for (SettingContainer& cat : categories)
|
||||
if (cat.getKey().compare(key) == 0)
|
||||
return &cat;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SettingContainer* SettingRegistry::getCategory(std::string key) const
|
||||
{
|
||||
for (const SettingContainer& cat : categories)
|
||||
if (cat.getKey().compare(key) == 0)
|
||||
return &cat;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SettingContainer& SettingRegistry::getOrCreateCategory(std::string cat_name, const rapidjson::Value& category)
|
||||
{
|
||||
std::list<SettingContainer>::iterator category_found = std::find_if(categories.begin(), categories.end(), [&cat_name](SettingContainer& cat) { return cat.getKey().compare(cat_name) == 0; });
|
||||
if (category_found != categories.end())
|
||||
{ // category is already present; add settings to category
|
||||
return *category_found;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string label = cat_name;
|
||||
if (category.IsObject() && category.HasMember("label") && category["label"].IsString())
|
||||
{
|
||||
label = category["label"].GetString();
|
||||
}
|
||||
categories.emplace_back(cat_name, label);
|
||||
return categories.back();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SettingRegistry::SettingRegistry()
|
||||
{
|
||||
}
|
||||
|
||||
bool SettingRegistry::settingsLoaded() const
|
||||
{
|
||||
return settings.size() > 0;
|
||||
}
|
||||
|
||||
int SettingRegistry::loadJSON(std::string filename, rapidjson::Document& json_document)
|
||||
{
|
||||
FILE* f = fopen(filename.c_str(), "rb");
|
||||
if (!f)
|
||||
{
|
||||
cura::logError("Couldn't open JSON file.\n");
|
||||
return 1;
|
||||
}
|
||||
char read_buffer[4096];
|
||||
rapidjson::FileReadStream reader_stream(f, read_buffer, sizeof(read_buffer));
|
||||
json_document.ParseStream(reader_stream);
|
||||
fclose(f);
|
||||
if (json_document.HasParseError())
|
||||
{
|
||||
cura::logError("Error parsing JSON(offset %u): %s\n", (unsigned)json_document.GetErrorOffset(), GetParseError_En(json_document.GetParseError()));
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SettingRegistry::loadJSONsettings(std::string filename)
|
||||
{
|
||||
rapidjson::Document json_document;
|
||||
|
||||
int err = loadJSON(filename, json_document);
|
||||
if (err) { return err; }
|
||||
|
||||
if (json_document.HasMember("inherits"))
|
||||
{
|
||||
std::string filename_copy = std::string(filename.c_str()); // copy the string because dirname(.) changes the input string!!!
|
||||
char* filename_cstr = (char*)filename_copy.c_str();
|
||||
int err = loadJSONsettings(std::string(dirname(filename_cstr)) + std::string("/") + json_document["inherits"].GetString());
|
||||
if (err) { return err; }
|
||||
return loadJSONsettingsFromDoc(json_document, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return loadJSONsettingsFromDoc(json_document, true);
|
||||
}
|
||||
}
|
||||
|
||||
int SettingRegistry::loadJSONsettingsFromDoc(rapidjson::Document& json_document, bool warn_duplicates)
|
||||
{
|
||||
|
||||
if (!json_document.IsObject())
|
||||
{
|
||||
cura::logError("JSON file is not an object.\n");
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (json_document.HasMember("machine_extruder_trains"))
|
||||
{
|
||||
const rapidjson::Value& trains = json_document["machine_extruder_trains"];
|
||||
SettingContainer& category_trains = getOrCreateCategory("machine_extruder_trains", trains);
|
||||
|
||||
if (trains.IsObject())
|
||||
{
|
||||
for (rapidjson::Value::ConstMemberIterator train_iterator = trains.MemberBegin(); train_iterator != trains.MemberEnd(); ++train_iterator)
|
||||
{
|
||||
SettingConfig& child = category_trains.getOrCreateChild(train_iterator->name.GetString(), std::string("Extruder ") + train_iterator->name.GetString());
|
||||
const rapidjson::Value& train = train_iterator->value;
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = train.MemberBegin(); setting_iterator != train.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(&child, setting_iterator, warn_duplicates, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (trains.IsArray())
|
||||
{
|
||||
int train_nr = 0;
|
||||
for (rapidjson::Value::ConstValueIterator train_iterator = trains.Begin(); train_iterator != trains.End(); ++train_iterator)
|
||||
{
|
||||
SettingConfig& child = category_trains.getOrCreateChild(std::to_string(train_nr), std::string("Extruder ") + std::to_string(train_nr));
|
||||
const rapidjson::Value& train = *train_iterator;
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = train.MemberBegin(); setting_iterator != train.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(&child, setting_iterator, warn_duplicates, false);
|
||||
}
|
||||
train_nr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (json_document.HasMember("machine_settings"))
|
||||
{
|
||||
const rapidjson::Value& machine_settings = json_document["machine_settings"];
|
||||
SettingContainer& category = getOrCreateCategory("machine_settings", machine_settings);
|
||||
// _addCategory(std::string("machine_settings"), machine_settings, warn_duplicates); // TODO: make machine_settings a category with a settings field and a label field and throw away rest of the code in this code block
|
||||
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = machine_settings.MemberBegin(); setting_iterator != machine_settings.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(&category, setting_iterator, warn_duplicates);
|
||||
}
|
||||
}
|
||||
|
||||
if (json_document.HasMember("categories"))
|
||||
{
|
||||
for (rapidjson::Value::ConstMemberIterator category_iterator = json_document["categories"].MemberBegin(); category_iterator != json_document["categories"].MemberEnd(); ++category_iterator)
|
||||
{
|
||||
_addCategory(category_iterator->name.GetString(), category_iterator->value, warn_duplicates);
|
||||
}
|
||||
}
|
||||
|
||||
if (json_document.HasMember("overrides"))
|
||||
{
|
||||
const rapidjson::Value& json_object_container = json_document["overrides"];
|
||||
for (rapidjson::Value::ConstMemberIterator override_iterator = json_object_container.MemberBegin(); override_iterator != json_object_container.MemberEnd(); ++override_iterator)
|
||||
{
|
||||
std::string setting = override_iterator->name.GetString();
|
||||
SettingConfig* conf = getSettingConfig(setting);
|
||||
if (!conf) //Setting could not be found.
|
||||
{
|
||||
logWarning("Trying to override unknown setting %s.\n", setting.c_str());
|
||||
continue;
|
||||
}
|
||||
_loadSettingValues(conf, override_iterator, false);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SettingRegistry::_addCategory(std::string cat_name, const rapidjson::Value& fields, bool warn_duplicates)
|
||||
{
|
||||
if (!fields.IsObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!fields.HasMember("settings") || !fields["settings"].IsObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
SettingContainer& category = getOrCreateCategory(cat_name, fields);
|
||||
const rapidjson::Value& json_object_container = fields["settings"];
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(&category, setting_iterator, warn_duplicates);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SettingRegistry::_addSettingToContainer(SettingContainer* parent, const rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings)
|
||||
{
|
||||
const rapidjson::Value& data = json_object_it->value;
|
||||
|
||||
std::string label;
|
||||
if (!json_object_it->value.HasMember("label") || !data["label"].IsString())
|
||||
{
|
||||
label = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
label = data["label"].GetString();
|
||||
}
|
||||
|
||||
/// Create the new setting config object.
|
||||
SettingConfig& config = parent->getOrCreateChild(json_object_it->name.GetString(), label);
|
||||
|
||||
_loadSettingValues(&config, json_object_it, warn_duplicates, add_to_settings);
|
||||
}
|
||||
|
||||
void SettingRegistry::_loadSettingValues(SettingConfig* config, const rapidjson::GenericValue< rapidjson::UTF8< char > >::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings)
|
||||
{
|
||||
const rapidjson::Value& data = json_object_it->value;
|
||||
/// Fill the setting config object with data we have in the json file.
|
||||
if (data.HasMember("type") && data["type"].IsString())
|
||||
{
|
||||
config->setType(data["type"].GetString());
|
||||
}
|
||||
if (data.HasMember("default"))
|
||||
{
|
||||
const rapidjson::Value& dflt = data["default"];
|
||||
if (dflt.IsString())
|
||||
{
|
||||
config->setDefault(dflt.GetString());
|
||||
}
|
||||
else if (dflt.IsTrue())
|
||||
{
|
||||
config->setDefault("true");
|
||||
}
|
||||
else if (dflt.IsFalse())
|
||||
{
|
||||
config->setDefault("false");
|
||||
}
|
||||
else if (dflt.IsNumber())
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << dflt.GetDouble();
|
||||
config->setDefault(ss.str());
|
||||
} // arrays are ignored because machine_extruder_trains needs to be handled separately
|
||||
else
|
||||
{
|
||||
if (data.HasMember("type") && data["type"].IsString() &&
|
||||
(data["type"].GetString() == std::string("polygon") || data["type"].GetString() == std::string("polygons")))
|
||||
{
|
||||
logWarning("WARNING: Loading polygon setting %s not implemented...\n", json_object_it->name.GetString());
|
||||
}
|
||||
else
|
||||
{
|
||||
logWarning("WARNING: Unrecognized data type in JSON: %s has type %s\n", json_object_it->name.GetString(), toString(dflt.GetType()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.HasMember("unit") && data["unit"].IsString())
|
||||
{
|
||||
config->setUnit(data["unit"].GetString());
|
||||
}
|
||||
|
||||
/// Register the setting in the settings map lookup.
|
||||
if (warn_duplicates && settingExists(config->getKey()))
|
||||
{
|
||||
cura::logError("Duplicate definition of setting: %s a.k.a. \"%s\" was already claimed by \"%s\"\n", config->getKey().c_str(), config->getLabel().c_str(), getSettingConfig(config->getKey())->getLabel().c_str());
|
||||
}
|
||||
|
||||
if (add_to_settings)
|
||||
{
|
||||
settings[config->getKey()] = config;
|
||||
}
|
||||
|
||||
/// When this setting has children, add those children to this setting.
|
||||
if (data.HasMember("children") && data["children"].IsObject())
|
||||
{
|
||||
const rapidjson::Value& json_object_container = data["children"];
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
_addSettingToContainer(config, setting_iterator, warn_duplicates, add_to_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,262 +0,0 @@
|
||||
#ifndef SETTING_REGISTRY_H
|
||||
#define SETTING_REGISTRY_H
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <iostream> // debug out
|
||||
|
||||
#include "utils/NoCopy.h"
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
// Forward declaration
|
||||
class SettingConfig;
|
||||
class SettingRegistry;
|
||||
|
||||
/*!
|
||||
* Setting category.
|
||||
* Filled from the fdmprinter.json file. Contains one or more children settings.
|
||||
*/
|
||||
class SettingContainer
|
||||
{
|
||||
friend class SettingConfig;
|
||||
friend class SettingRegistry;
|
||||
private:
|
||||
std::string key;
|
||||
std::string label;
|
||||
std::list<SettingConfig> children; // must be a list cause the pointers to individual children are mapped to in SettingRegistry::settings.
|
||||
public:
|
||||
std::string getKey() const { return key; }
|
||||
std::string getLabel() const { return label; }
|
||||
SettingContainer(std::string key, std::string label);
|
||||
|
||||
SettingConfig* addChild(std::string key, std::string label);
|
||||
|
||||
/*!
|
||||
* Get the \p idx th child.
|
||||
*
|
||||
* This is used to get a specific extruder train in Settingsbase::setExtruderTrainDefaults
|
||||
*
|
||||
* \param idx The index in the list of children
|
||||
* \return The \p idx th child
|
||||
*/
|
||||
const SettingConfig* getChild(unsigned int idx) const
|
||||
{
|
||||
if (idx < children.size())
|
||||
{
|
||||
auto it = children.begin();
|
||||
while (idx > 0) { ++it; idx--; }
|
||||
return &*it;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Get the (direct) child with key \p key, or create one with key \p key and label \p label as well.
|
||||
*
|
||||
* \param key the key
|
||||
* \param label the label for creating a new child
|
||||
* \return The existing or newly created child setting.
|
||||
*/
|
||||
SettingConfig& getOrCreateChild(std::string key, std::string label);
|
||||
public:
|
||||
void debugOutputAllSettings() const;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Single setting data.
|
||||
* Filled from the fdmprinter.json file. Can contain child settings, and is registered in the
|
||||
* setting registry with it's key.
|
||||
*/
|
||||
class SettingConfig : public SettingContainer
|
||||
{
|
||||
private:
|
||||
std::string type;
|
||||
std::string default_value;
|
||||
std::string unit;
|
||||
public:
|
||||
SettingConfig(std::string key, std::string label);
|
||||
|
||||
/*!
|
||||
* Get the SettingConfig::children.
|
||||
*
|
||||
* This is used to get the extruder trains; see Settingsbase::setExtruderTrainDefaults
|
||||
*
|
||||
* \return SettingConfig::children
|
||||
*/
|
||||
const std::list<SettingConfig>& getChildren() const { return children; }
|
||||
|
||||
std::string getKey() const
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
void setType(std::string type)
|
||||
{
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
std::string getType() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
void setDefault(std::string default_value)
|
||||
{
|
||||
this->default_value = default_value;
|
||||
}
|
||||
|
||||
std::string getDefaultValue() const
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
|
||||
void setUnit(std::string unit)
|
||||
{
|
||||
this->unit = unit;
|
||||
}
|
||||
|
||||
std::string getUnit() const
|
||||
{
|
||||
return unit;
|
||||
}
|
||||
|
||||
void debugOutputAllSettings() const
|
||||
{
|
||||
std::cerr << key << "(" << default_value << ")" <<std::endl;
|
||||
for (const SettingConfig& child : children)
|
||||
{
|
||||
child.debugOutputAllSettings();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Setting registry.
|
||||
* There is a single global setting registry.
|
||||
* This registry contains all known setting keys.
|
||||
* The registry also contains the settings categories to build up the setting hiarcy from the json file.
|
||||
* Also the default values are stored and retrieved in case a given setting doesn't get a value from the command line or the frontend.
|
||||
*/
|
||||
class SettingRegistry : NoCopy
|
||||
{
|
||||
private:
|
||||
static SettingRegistry instance;
|
||||
|
||||
SettingRegistry();
|
||||
|
||||
std::unordered_map<std::string, SettingConfig*> settings;
|
||||
std::list<SettingContainer> categories;
|
||||
public:
|
||||
/*!
|
||||
* Get the SettingRegistry.
|
||||
*
|
||||
* This is a singleton class.
|
||||
*
|
||||
* \return The SettingRegistry
|
||||
*/
|
||||
static SettingRegistry* getInstance() { return &instance; }
|
||||
|
||||
bool settingExists(std::string key) const;
|
||||
SettingConfig* getSettingConfig(std::string key) const;
|
||||
|
||||
/*!
|
||||
* Return the first category with the given key as name, or a null pointer.
|
||||
*
|
||||
* \param key the key as it is in the JSON file
|
||||
* \return The first category in the list having the \p key
|
||||
*/
|
||||
SettingContainer* getCategory(std::string key);
|
||||
|
||||
/*!
|
||||
* Return the first category with the given key as name, or a null pointer. const style
|
||||
*
|
||||
* \param key the key as it is in the JSON file
|
||||
* \return The first category in the list having the \p key
|
||||
*/
|
||||
const SettingContainer* getCategory(std::string key) const;
|
||||
private:
|
||||
/*!
|
||||
* Return the first category with the given key as name, or a new one.
|
||||
*
|
||||
* \param cat_name the key as it is in the JSON file
|
||||
* \param category the JSON Value associated with the key
|
||||
* \return The first category in the list having the \p key (or a new one)
|
||||
*/
|
||||
SettingContainer& getOrCreateCategory(std::string cat_name, const rapidjson::Value& category);
|
||||
public:
|
||||
bool settingsLoaded() const;
|
||||
/*!
|
||||
* Load settings from a json file and all the parents it inherits from.
|
||||
*
|
||||
* Uses recursion to load the parent json file.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadJSONsettings(std::string filename);
|
||||
void debugOutputAllSettings() const
|
||||
{
|
||||
for (const SettingContainer& cat : categories)
|
||||
{
|
||||
cat.debugOutputAllSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \param type type to convert to string
|
||||
* \return human readable version of json type
|
||||
*/
|
||||
static std::string toString(rapidjson::Type type);
|
||||
/*!
|
||||
* Load a json document.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \param json_document (output) the document to be loaded
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadJSON(std::string filename, rapidjson::Document& json_document);
|
||||
|
||||
/*!
|
||||
* Load settings from a single json file.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \param warn_duplicates whether to warn for duplicate definitions
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadJSONsettingsFromDoc(rapidjson::Document& json_document, bool warn_duplicates);
|
||||
|
||||
/*!
|
||||
* Get the string from a json value (generally the default value field of a setting)
|
||||
* \param dflt The value to convert to string
|
||||
* \param setting_name The name of the setting (in case we need to display an error message)
|
||||
* \return The string
|
||||
*/
|
||||
static std::string toString(const rapidjson::Value& dflt, std::string setting_name = "?");
|
||||
|
||||
/*!
|
||||
* \param warn_duplicates whether to warn for duplicate definitions
|
||||
*/
|
||||
void _addSettingToContainer(SettingContainer* parent, const rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings = true);
|
||||
|
||||
/*!
|
||||
* Adds a category with a given name to the registry.
|
||||
* \param cat_name the key of the category as it is in the JSON file
|
||||
* \param fields The members of the category
|
||||
* \param warn_duplicates whether to warn for duplicate definitions
|
||||
*/
|
||||
void _addCategory(std::string cat_name, const rapidjson::Value& fields, bool warn_duplicates);
|
||||
|
||||
void _loadSettingValues(SettingConfig* config, const rapidjson::Value::ConstMemberIterator& json_object_it, bool warn_duplicates, bool add_to_settings = true);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//SETTING_REGISTRY_H
|
||||
@@ -0,0 +1,14 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "SettingConfig.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
SettingConfig::SettingConfig(std::string key, std::string label)
|
||||
: SettingContainer(key, label)
|
||||
{
|
||||
// std::cerr << key << std::endl; // debug output to show all frontend registered settings...
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,76 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef SETTINGS_SETTING_CONFIG_H
|
||||
#define SETTINGS_SETTING_CONFIG_H
|
||||
|
||||
#include <string>
|
||||
#include <iostream> // debug out
|
||||
|
||||
#include "SettingContainer.h"
|
||||
|
||||
#include "../utils/NoCopy.h"
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Single setting data.
|
||||
* Filled from the fdmprinter.json file. Can contain child settings, and is registered in the
|
||||
* setting registry with it's key.
|
||||
*/
|
||||
class SettingConfig : public SettingContainer
|
||||
{
|
||||
private:
|
||||
std::string type; //!< The type of the default_value, e.g. str, int, bool
|
||||
std::string default_value; //!< The default value for this setting
|
||||
std::string unit; //!< The unit of the physical quantity in which this setting is measured, e.g. "mm", "mm/s", ""
|
||||
public:
|
||||
SettingConfig(std::string key, std::string label);
|
||||
|
||||
std::string getKey() const
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
void setType(std::string type)
|
||||
{
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
std::string getType() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
void setDefault(std::string default_value)
|
||||
{
|
||||
this->default_value = default_value;
|
||||
}
|
||||
|
||||
std::string getDefaultValue() const
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
|
||||
void setUnit(std::string unit)
|
||||
{
|
||||
this->unit = unit;
|
||||
}
|
||||
|
||||
std::string getUnit() const
|
||||
{
|
||||
return unit;
|
||||
}
|
||||
|
||||
void debugOutputAllSettings() const
|
||||
{
|
||||
std::cerr << key << "(" << default_value << ")" << std::endl;
|
||||
for (const SettingConfig& child : children)
|
||||
{
|
||||
child.debugOutputAllSettings();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//SETTINGS_SETTING_CONFIG_H
|
||||
@@ -0,0 +1,47 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "SettingContainer.h"
|
||||
#include "SettingConfig.h"
|
||||
|
||||
#include <string>
|
||||
#include <algorithm> // find_if
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
SettingContainer::SettingContainer(std::string key, std::string label)
|
||||
: key(key)
|
||||
, label(label)
|
||||
{
|
||||
}
|
||||
|
||||
SettingConfig* SettingContainer::addChild(std::string key, std::string label)
|
||||
{
|
||||
children.emplace_back(key, label);
|
||||
return &children.back();
|
||||
}
|
||||
|
||||
SettingConfig& SettingContainer::getOrCreateChild(std::string key, std::string label)
|
||||
{
|
||||
auto child_it = std::find_if(children.begin(), children.end(), [&key](SettingConfig& child) { return child.key == key; } );
|
||||
if (child_it == children.end())
|
||||
{
|
||||
children.emplace_back(key, label);
|
||||
return children.back();
|
||||
}
|
||||
else
|
||||
{
|
||||
return *child_it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SettingContainer::debugOutputAllSettings() const
|
||||
{
|
||||
std::cerr << "\nSETTINGS BASE: " << key << std::endl;
|
||||
for (const SettingConfig& child : children)
|
||||
{
|
||||
child.debugOutputAllSettings();
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,83 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef SETTINGS_SETTING_CONTAINER_H
|
||||
#define SETTINGS_SETTING_CONTAINER_H
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <iostream> // debug out
|
||||
|
||||
#include "../utils/NoCopy.h"
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
// Forward declaration
|
||||
class SettingConfig;
|
||||
class SettingRegistry;
|
||||
|
||||
/*!
|
||||
* Setting container for a settings base of definitions and default values.
|
||||
* Filled from the .def.json files. Contains one or more children settings.
|
||||
*/
|
||||
class SettingContainer
|
||||
{
|
||||
friend class SettingConfig;
|
||||
friend class SettingRegistry;
|
||||
private:
|
||||
std::string key;
|
||||
std::string label;
|
||||
std::list<SettingConfig> children; // must be a list cause the pointers to individual children are mapped to in SettingRegistry::settings.
|
||||
std::list<std::string> path; //!< The path of parents (internal names) to this container
|
||||
public:
|
||||
std::string getKey() const { return key; }
|
||||
std::string getLabel() const { return label; }
|
||||
SettingContainer(std::string key, std::string label);
|
||||
|
||||
/*!
|
||||
* Get the SettingConfig::children.
|
||||
*
|
||||
* This is used to get the extruder trains; see Settingsbase::setExtruderTrainDefaults
|
||||
*
|
||||
* \return SettingConfig::children
|
||||
*/
|
||||
const std::list<SettingConfig>& getChildren() const { return children; }
|
||||
|
||||
SettingConfig* addChild(std::string key, std::string label);
|
||||
|
||||
/*!
|
||||
* Get the \p idx th child.
|
||||
*
|
||||
* This is used to get a specific extruder train in Settingsbase::setExtruderTrainDefaults
|
||||
*
|
||||
* \param idx The index in the list of children
|
||||
* \return The \p idx th child
|
||||
*/
|
||||
const SettingConfig* getChild(unsigned int idx) const
|
||||
{
|
||||
if (idx < children.size())
|
||||
{
|
||||
auto it = children.begin();
|
||||
while (idx > 0) { ++it; idx--; }
|
||||
return &*it;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
* Get the (direct) child with key \p key, or create one with key \p key and label \p label as well.
|
||||
*
|
||||
* \param key the key
|
||||
* \param label the label for creating a new child
|
||||
* \return The existing or newly created child setting.
|
||||
*/
|
||||
SettingConfig& getOrCreateChild(std::string key, std::string label);
|
||||
public:
|
||||
void debugOutputAllSettings() const;
|
||||
};
|
||||
}//namespace cura
|
||||
#endif//SETTINGS_SETTING_CONTAINER_H
|
||||
@@ -0,0 +1,392 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "SettingRegistry.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream> // debug IO
|
||||
#include <libgen.h> // dirname
|
||||
#include <string>
|
||||
#include <cstring> // strtok (split string using delimiters) strcpy
|
||||
#include <fstream> // ifstream (to see if file exists)
|
||||
|
||||
#include "rapidjson/rapidjson.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/error/en.h"
|
||||
#include "rapidjson/filereadstream.h"
|
||||
#include "../utils/logoutput.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
SettingRegistry SettingRegistry::instance; // define settingRegistry
|
||||
|
||||
std::string SettingRegistry::toString(rapidjson::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case rapidjson::Type::kNullType: return "null";
|
||||
case rapidjson::Type::kFalseType: return "false";
|
||||
case rapidjson::Type::kTrueType: return "true";
|
||||
case rapidjson::Type::kObjectType: return "object";
|
||||
case rapidjson::Type::kArrayType: return "array";
|
||||
case rapidjson::Type::kStringType: return "string";
|
||||
case rapidjson::Type::kNumberType: return "number";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SettingConfig::SettingConfig(std::string key, std::string label)
|
||||
: SettingContainer(key, label)
|
||||
{
|
||||
// std::cerr << key << std::endl; // debug output to show all frontend registered settings...
|
||||
}
|
||||
|
||||
bool SettingRegistry::settingExists(std::string key) const
|
||||
{
|
||||
return setting_key_to_config.find(key) != setting_key_to_config.end();
|
||||
}
|
||||
|
||||
SettingConfig* SettingRegistry::getSettingConfig(std::string key) const
|
||||
{
|
||||
auto it = setting_key_to_config.find(key);
|
||||
if (it == setting_key_to_config.end())
|
||||
return nullptr;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
SettingRegistry::SettingRegistry()
|
||||
: setting_definitions("settings", "Settings")
|
||||
{
|
||||
// load search paths from environment variable CURA_ENGINE_SEARCH_PATH
|
||||
char* paths = getenv("CURA_ENGINE_SEARCH_PATH");
|
||||
if (paths)
|
||||
{
|
||||
#if defined(__linux__) || (defined(__APPLE__) && defined(__MACH__))
|
||||
char delims[] = ":"; // colon
|
||||
#else
|
||||
char delims[] = ";"; // semicolon
|
||||
#endif
|
||||
char* path = strtok(paths, delims); // search for next path delimited by any of the characters in delims
|
||||
while (path != NULL)
|
||||
{
|
||||
search_paths.emplace(path);
|
||||
path = strtok(NULL, ";:,"); // continue searching in last call to strtok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int SettingRegistry::loadJSON(std::string filename, rapidjson::Document& json_document)
|
||||
{
|
||||
FILE* f = fopen(filename.c_str(), "rb");
|
||||
if (!f)
|
||||
{
|
||||
cura::logError("Couldn't open JSON file.\n");
|
||||
return 1;
|
||||
}
|
||||
char read_buffer[4096];
|
||||
rapidjson::FileReadStream reader_stream(f, read_buffer, sizeof(read_buffer));
|
||||
json_document.ParseStream(reader_stream);
|
||||
fclose(f);
|
||||
if (json_document.HasParseError())
|
||||
{
|
||||
cura::logError("Error parsing JSON(offset %u): %s\n", (unsigned)json_document.GetErrorOffset(), GetParseError_En(json_document.GetParseError()));
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Check whether a file exists.
|
||||
* from https://techoverflow.net/blog/2013/01/11/cpp-check-if-file-exists/
|
||||
*
|
||||
* \param filename The path to a filename to check if it exists
|
||||
* \return Whether the file exists.
|
||||
*/
|
||||
bool fexists(const char *filename)
|
||||
{
|
||||
std::ifstream ifile(filename);
|
||||
return (bool)ifile;
|
||||
}
|
||||
|
||||
bool SettingRegistry::getDefinitionFile(const std::string machine_id, std::string& result)
|
||||
{
|
||||
for (const std::string& search_path : search_paths)
|
||||
{
|
||||
result = search_path + std::string("/") + machine_id + std::string(".def.json");
|
||||
if (fexists(result.c_str()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int SettingRegistry::loadExtruderJSONsettings(unsigned int extruder_nr, SettingsBase* settings_base)
|
||||
{
|
||||
if (extruder_nr >= extruder_train_ids.size())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string definition_file;
|
||||
bool found = getDefinitionFile(extruder_train_ids[extruder_nr], definition_file);
|
||||
if (!found)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
bool warn_base_file_duplicates = false;
|
||||
return loadJSONsettings(definition_file, settings_base, warn_base_file_duplicates);
|
||||
}
|
||||
|
||||
int SettingRegistry::loadJSONsettings(std::string filename, SettingsBase* settings_base, bool warn_base_file_duplicates)
|
||||
{
|
||||
rapidjson::Document json_document;
|
||||
|
||||
log("Loading %s...\n", filename.c_str());
|
||||
|
||||
int err = loadJSON(filename, json_document);
|
||||
if (err) { return err; }
|
||||
|
||||
{ // add parent folder to search paths
|
||||
char filename_cstr[500];
|
||||
std::strcpy(filename_cstr, filename.c_str()); // copy the string because dirname(.) changes the input string!!!
|
||||
std::string folder_name = std::string(dirname(filename_cstr));
|
||||
search_paths.emplace(folder_name);
|
||||
}
|
||||
|
||||
if (json_document.HasMember("inherits") && json_document["inherits"].IsString())
|
||||
{
|
||||
std::string child_filename;
|
||||
bool found = getDefinitionFile(json_document["inherits"].GetString(), child_filename);
|
||||
if (!found)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
err = loadJSONsettings(child_filename, settings_base, warn_base_file_duplicates); // load child first
|
||||
if (err)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
err = loadJSONsettingsFromDoc(json_document, settings_base, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
err = loadJSONsettingsFromDoc(json_document, settings_base, warn_base_file_duplicates);
|
||||
}
|
||||
|
||||
if (json_document.HasMember("metadata") && json_document["metadata"].IsObject())
|
||||
{
|
||||
const rapidjson::Value& json_metadata = json_document["metadata"];
|
||||
if (json_metadata.HasMember("machine_extruder_trains") && json_metadata["machine_extruder_trains"].IsObject())
|
||||
{
|
||||
const rapidjson::Value& json_machine_extruder_trains = json_metadata["machine_extruder_trains"];
|
||||
for (rapidjson::Value::ConstMemberIterator extr_train_iterator = json_machine_extruder_trains.MemberBegin(); extr_train_iterator != json_machine_extruder_trains.MemberEnd(); ++extr_train_iterator)
|
||||
{
|
||||
int extruder_train_nr = atoi(extr_train_iterator->name.GetString());
|
||||
if (extruder_train_nr < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const rapidjson::Value& json_id = extr_train_iterator->value;
|
||||
if (!json_id.IsString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const char* id = json_id.GetString();
|
||||
if (extruder_train_nr >= (int) extruder_train_ids.size())
|
||||
{
|
||||
extruder_train_ids.resize(extruder_train_nr + 1);
|
||||
}
|
||||
extruder_train_ids[extruder_train_nr] = std::string(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int SettingRegistry::loadJSONsettingsFromDoc(rapidjson::Document& json_document, SettingsBase* settings_base, bool warn_duplicates)
|
||||
{
|
||||
|
||||
if (!json_document.IsObject())
|
||||
{
|
||||
cura::logError("JSON file is not an object.\n");
|
||||
return 3;
|
||||
}
|
||||
|
||||
{ // handle machine name
|
||||
std::string machine_name = "Unknown";
|
||||
if (json_document.HasMember("name"))
|
||||
{
|
||||
const rapidjson::Value& machine_name_field = json_document["name"];
|
||||
if (machine_name_field.IsString())
|
||||
{
|
||||
machine_name = machine_name_field.GetString();
|
||||
}
|
||||
}
|
||||
SettingConfig& machine_name_setting = addSetting("machine_name", "Machine Name");
|
||||
machine_name_setting.setDefault(machine_name);
|
||||
machine_name_setting.setType("string");
|
||||
settings_base->_setSetting(machine_name_setting.getKey(), machine_name_setting.getDefaultValue());
|
||||
}
|
||||
|
||||
if (json_document.HasMember("settings"))
|
||||
{
|
||||
std::list<std::string> path;
|
||||
handleChildren(json_document["settings"], path, settings_base, warn_duplicates);
|
||||
}
|
||||
|
||||
if (json_document.HasMember("overrides"))
|
||||
{
|
||||
const rapidjson::Value& json_object_container = json_document["overrides"];
|
||||
for (rapidjson::Value::ConstMemberIterator override_iterator = json_object_container.MemberBegin(); override_iterator != json_object_container.MemberEnd(); ++override_iterator)
|
||||
{
|
||||
std::string setting = override_iterator->name.GetString();
|
||||
SettingConfig* conf = getSettingConfig(setting);
|
||||
if (!conf) //Setting could not be found.
|
||||
{
|
||||
logWarning("Trying to override unknown setting %s.\n", setting.c_str());
|
||||
continue;
|
||||
}
|
||||
_loadSettingValues(conf, override_iterator, settings_base);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SettingRegistry::handleChildren(const rapidjson::Value& settings_list, std::list<std::string>& path, SettingsBase* settings_base, bool warn_duplicates)
|
||||
{
|
||||
if (!settings_list.IsObject())
|
||||
{
|
||||
logError("ERROR: json settings list is not an object!\n");
|
||||
return;
|
||||
}
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = settings_list.MemberBegin(); setting_iterator != settings_list.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
handleSetting(setting_iterator, path, settings_base, warn_duplicates);
|
||||
if (setting_iterator->value.HasMember("children"))
|
||||
{
|
||||
std::list<std::string> path_here = path;
|
||||
path_here.push_back(setting_iterator->name.GetString());
|
||||
handleChildren(setting_iterator->value["children"], path_here, settings_base, warn_duplicates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingRegistry::settingIsUsedByEngine(const rapidjson::Value& setting)
|
||||
{
|
||||
if (setting.HasMember("children"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SettingRegistry::handleSetting(const rapidjson::Value::ConstMemberIterator& json_setting_it, std::list<std::string>& path, SettingsBase* settings_base, bool warn_duplicates)
|
||||
{
|
||||
const rapidjson::Value& json_setting = json_setting_it->value;
|
||||
if (!json_setting.IsObject())
|
||||
{
|
||||
logError("ERROR: json setting is not an object!\n");
|
||||
return;
|
||||
}
|
||||
std::string name = json_setting_it->name.GetString();
|
||||
if (json_setting.HasMember("type") && json_setting["type"].IsString() && json_setting["type"].GetString() == std::string("category"))
|
||||
{ // skip category objects
|
||||
return;
|
||||
}
|
||||
if (settingIsUsedByEngine(json_setting))
|
||||
{
|
||||
if (!json_setting.HasMember("label") || !json_setting["label"].IsString())
|
||||
{
|
||||
logError("ERROR: json setting \"%s\" has no label!\n", name.c_str());
|
||||
return;
|
||||
}
|
||||
std::string label = json_setting["label"].GetString();
|
||||
|
||||
SettingConfig* setting = getSettingConfig(name);
|
||||
if (warn_duplicates && setting)
|
||||
{
|
||||
cura::logError("Duplicate definition of setting: %s a.k.a. \"%s\" was already claimed by \"%s\"\n", name.c_str(), label.c_str(), getSettingConfig(name)->getLabel().c_str());
|
||||
}
|
||||
if (!setting)
|
||||
{
|
||||
setting = &addSetting(name, label);
|
||||
}
|
||||
_loadSettingValues(setting, json_setting_it, settings_base);
|
||||
}
|
||||
}
|
||||
|
||||
SettingConfig& SettingRegistry::addSetting(std::string name, std::string label)
|
||||
{
|
||||
SettingConfig* config = setting_definitions.addChild(name, label);
|
||||
|
||||
setting_key_to_config[name] = config;
|
||||
|
||||
return *config;
|
||||
}
|
||||
|
||||
void SettingRegistry::loadDefault(const rapidjson::GenericValue< rapidjson::UTF8< char > >::ConstMemberIterator& json_object_it, SettingConfig* config)
|
||||
{
|
||||
const rapidjson::Value& setting_content = json_object_it->value;
|
||||
if (setting_content.HasMember("default_value"))
|
||||
{
|
||||
const rapidjson::Value& dflt = setting_content["default_value"];
|
||||
if (dflt.IsString())
|
||||
{
|
||||
config->setDefault(dflt.GetString());
|
||||
}
|
||||
else if (dflt.IsTrue())
|
||||
{
|
||||
config->setDefault("true");
|
||||
}
|
||||
else if (dflt.IsFalse())
|
||||
{
|
||||
config->setDefault("false");
|
||||
}
|
||||
else if (dflt.IsNumber())
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << dflt.GetDouble();
|
||||
config->setDefault(ss.str());
|
||||
} // arrays are ignored because machine_extruder_trains needs to be handled separately
|
||||
else
|
||||
{
|
||||
logWarning("WARNING: Unrecognized data type in JSON: %s has type %s\n", json_object_it->name.GetString(), toString(dflt.GetType()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SettingRegistry::_loadSettingValues(SettingConfig* config, const rapidjson::GenericValue< rapidjson::UTF8< char > >::ConstMemberIterator& json_object_it, SettingsBase* settings_base)
|
||||
{
|
||||
const rapidjson::Value& data = json_object_it->value;
|
||||
/// Fill the setting config object with data we have in the json file.
|
||||
if (data.HasMember("type") && data["type"].IsString())
|
||||
{
|
||||
config->setType(data["type"].GetString());
|
||||
}
|
||||
if (config->getType() == std::string("polygon") || config->getType() == std::string("polygons"))
|
||||
{ // skip polygon settings : not implemented yet and not used yet (TODO)
|
||||
// logWarning("WARNING: Loading polygon setting %s not implemented...\n", json_object_it->name.GetString());
|
||||
return;
|
||||
}
|
||||
|
||||
loadDefault(json_object_it, config);
|
||||
|
||||
if (data.HasMember("unit") && data["unit"].IsString())
|
||||
{
|
||||
config->setUnit(data["unit"].GetString());
|
||||
}
|
||||
|
||||
settings_base->_setSetting(config->getKey(), config->getDefaultValue());
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -0,0 +1,191 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef SETTINGS_SETTING_REGISTRY_H
|
||||
#define SETTINGS_SETTING_REGISTRY_H
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <iostream> // debug out
|
||||
|
||||
#include "SettingConfig.h"
|
||||
#include "SettingContainer.h"
|
||||
|
||||
#include "../utils/NoCopy.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Setting registry.
|
||||
* There is a single global setting registry.
|
||||
* This registry contains all known setting keys and (some of) their attributes.
|
||||
* The default values are stored and retrieved in case a given setting doesn't get a value from the command line or the frontend.
|
||||
*/
|
||||
class SettingRegistry : NoCopy
|
||||
{
|
||||
private:
|
||||
static SettingRegistry instance;
|
||||
|
||||
SettingRegistry();
|
||||
|
||||
std::unordered_map<std::string, SettingConfig*> setting_key_to_config; //!< Mapping from setting keys to their configurations
|
||||
|
||||
SettingContainer setting_definitions; //!< All setting configurations (A flat list)
|
||||
|
||||
std::vector<std::string> extruder_train_ids; //!< The internal id's of each extruder (the filename without the extension)
|
||||
|
||||
std::unordered_set<std::string> search_paths; //!< The paths to search for json files.
|
||||
public:
|
||||
/*!
|
||||
* Get the SettingRegistry.
|
||||
*
|
||||
* This is a singleton class.
|
||||
*
|
||||
* \return The SettingRegistry
|
||||
*/
|
||||
static SettingRegistry* getInstance() { return &instance; }
|
||||
|
||||
/*!
|
||||
* Check whether a setting exists, according to the settings json files.
|
||||
*
|
||||
* \param key The internal key for the setting to test
|
||||
* \return Whether a definition of the setting is recorded in this registry.
|
||||
*/
|
||||
bool settingExists(std::string key) const;
|
||||
|
||||
/*!
|
||||
* Get the config of a setting with a given key.
|
||||
*
|
||||
* \param key the (internal) key for a setting
|
||||
* \return the setting definition values
|
||||
*/
|
||||
SettingConfig* getSettingConfig(std::string key) const;
|
||||
protected:
|
||||
/*!
|
||||
* Whether this json settings object is a definition of a CuraEngine setting,
|
||||
* or only a shorthand setting to control other settings.
|
||||
* Only settings used by the engine will be recordedd in the registry.
|
||||
*
|
||||
* \param setting The setting to check whether CuraEngine uses it.
|
||||
* \return Whether CuraEngine uses the setting.
|
||||
*/
|
||||
bool settingIsUsedByEngine(const rapidjson::Value& setting);
|
||||
|
||||
/*!
|
||||
* Get the filename for the machine definition with the given id.
|
||||
* Check the directories in SettingRegistry::search_paths.
|
||||
*
|
||||
* \param machine_id The id and base filename (without extensions) of the machine definition to search for.
|
||||
* \param result The filename of the machine definition
|
||||
* \return Whether we found the file.
|
||||
*/
|
||||
bool getDefinitionFile(const std::string machine_id, std::string& result);
|
||||
|
||||
/*!
|
||||
* Get the default value of a json setting object in the format used internally (c style).
|
||||
*
|
||||
* \param[in] json_object_it An iterator for a given setting json object
|
||||
* \param[out] config Where the default value is stored
|
||||
*/
|
||||
static void loadDefault(const rapidjson::GenericValue< rapidjson::UTF8< char > >::ConstMemberIterator& json_object_it, SettingConfig* config);
|
||||
public:
|
||||
/*!
|
||||
* Load settings from a json file and all the parents it inherits from.
|
||||
*
|
||||
* Uses recursion to load the parent json file.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \param settings_base The settings base where to store the default values.
|
||||
* \param warn_base_file_duplicates Whether to warn if there are duplicate definitions in the base file (the .def.json which has no inherits).
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadJSONsettings(std::string filename, SettingsBase* settings_base, bool warn_base_file_duplicates = true);
|
||||
|
||||
void debugOutputAllSettings() const
|
||||
{
|
||||
setting_definitions.debugOutputAllSettings();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Load settings from the extruder definition json file and all the parents it inherits from.
|
||||
* Use the json file refered to in the machine_extruder_trains attribute of the last loaded machine json file.
|
||||
*
|
||||
* Uses recursion to load the parent json file.
|
||||
*
|
||||
* \param extruder_nr The number of the extruder to load
|
||||
* \param settings_base The settings base where to store the default values. (The extruder settings base)
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadExtruderJSONsettings(unsigned int extruder_nr, SettingsBase* settings_base);
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \param type type to convert to string
|
||||
* \return human readable version of json type
|
||||
*/
|
||||
static std::string toString(rapidjson::Type type);
|
||||
public:
|
||||
/*!
|
||||
* Load a json document.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \param json_document (output) the document to be loaded
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
static int loadJSON(std::string filename, rapidjson::Document& json_document);
|
||||
private:
|
||||
/*!
|
||||
* Load settings from a single json file.
|
||||
*
|
||||
* \param filename The filename of the json file to parse
|
||||
* \param settings_base The settings base where to store the default values.
|
||||
* \param warn_duplicates whether to warn for duplicate definitions
|
||||
* \return an error code or zero of succeeded
|
||||
*/
|
||||
int loadJSONsettingsFromDoc(rapidjson::Document& json_document, SettingsBase* settings_base, bool warn_duplicates);
|
||||
|
||||
/*!
|
||||
* Create a new SettingConfig and add it to the registry.
|
||||
*
|
||||
* \param name The internal key of the setting
|
||||
* \param label The human readable name for the frontend
|
||||
* \return The config created
|
||||
*/
|
||||
SettingConfig& addSetting(std::string name, std::string label);
|
||||
|
||||
/*!
|
||||
* Load inessential data about the setting, like its type and unit.
|
||||
*
|
||||
* \param[out] config Where to store the data
|
||||
* \param[in] json_object_it Iterator to a setting json object
|
||||
* \param[out] settings_base The settings base where to store the default values.
|
||||
*/
|
||||
void _loadSettingValues(SettingConfig* config, const rapidjson::Value::ConstMemberIterator& json_object_it, SettingsBase* settings_base);
|
||||
|
||||
/*!
|
||||
* Handle a json object which contains a list of settings.
|
||||
*
|
||||
* \param settings_list The object containing one or more setting definitions
|
||||
* \param path The path of (internal) setting names traversed to get to this object
|
||||
* \param settings_base The settings base where to store the default values.
|
||||
* \param warn_duplicates whether to warn for duplicate setting definitions
|
||||
*/
|
||||
void handleChildren(const rapidjson::Value& settings_list, std::list<std::string>& path, SettingsBase* settings_base, bool warn_duplicates);
|
||||
|
||||
/*!
|
||||
* Handle a json object for a setting.
|
||||
*
|
||||
* \param json_setting_it Iterator for the setting which contains the key (setting name) and attributes info
|
||||
* \param path The path of (internal) setting names traversed to get to this object
|
||||
* \param settings_base The settings base where to store the default values.
|
||||
* \param warn_duplicates whether to warn for duplicate setting definitions
|
||||
*/
|
||||
void handleSetting(const rapidjson::Value::ConstMemberIterator& json_setting_it, std::list<std::string>& path, SettingsBase* settings_base, bool warn_duplicates);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//SETTINGS_SETTING_REGISTRY_H
|
||||
@@ -0,0 +1,238 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef SETTINGS_TO_GV_H
|
||||
#define SETTINGS_TO_GV_H
|
||||
|
||||
|
||||
#include <stdio.h> // for file output
|
||||
#include <sstream>
|
||||
#include <iostream> // debug IO
|
||||
#include <libgen.h> // dirname
|
||||
#include <string>
|
||||
#include <algorithm> // find_if
|
||||
#include <regex> // regex_search
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
|
||||
#include "rapidjson/rapidjson.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/error/en.h"
|
||||
#include "rapidjson/filereadstream.h"
|
||||
#include "../utils/logoutput.h"
|
||||
#include "SettingRegistry.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class SettingsToGv
|
||||
{
|
||||
enum class RelationType
|
||||
{
|
||||
PARENT_CHILD,
|
||||
INHERIT_FUNCTION,
|
||||
ERROR_FUNCTION,
|
||||
WARNING_FUNCTION
|
||||
};
|
||||
|
||||
FILE* out;
|
||||
std::set<std::string> engine_settings;
|
||||
bool parent_child_viz, inherit_viz, error_viz, warning_viz;
|
||||
public:
|
||||
SettingsToGv(std::string output_filename, std::string engine_settings_filename, bool parent_child_viz, bool inherit_viz, bool error_viz, bool warning_viz)
|
||||
: parent_child_viz(parent_child_viz)
|
||||
, inherit_viz(inherit_viz)
|
||||
, error_viz(error_viz)
|
||||
, warning_viz(warning_viz)
|
||||
{
|
||||
out = fopen(output_filename.c_str(), "w");
|
||||
fprintf(out, "digraph G {\n");
|
||||
|
||||
|
||||
std::ifstream engine_settings_file(engine_settings_filename.c_str());
|
||||
std::string line;
|
||||
while (std::getline(engine_settings_file, line))
|
||||
{
|
||||
engine_settings.insert(line);
|
||||
//fprintf(out, "%s [color=green];\n", line.c_str());
|
||||
}
|
||||
engine_settings_file.close();
|
||||
}
|
||||
private:
|
||||
void generateEdge(const std::string& parent, const std::string& child, RelationType relation_type)
|
||||
{
|
||||
if (engine_settings.find(parent) != engine_settings.end())
|
||||
{
|
||||
fprintf(out, "%s [color=green];\n", parent.c_str());
|
||||
}
|
||||
if (engine_settings.find(child) != engine_settings.end())
|
||||
{
|
||||
fprintf(out, "%s [color=green];\n", child.c_str());
|
||||
}
|
||||
std::string color;
|
||||
switch (relation_type)
|
||||
{
|
||||
case SettingsToGv::RelationType::INHERIT_FUNCTION:
|
||||
if (!inherit_viz)
|
||||
{
|
||||
return;
|
||||
}
|
||||
color = "blue";
|
||||
break;
|
||||
case SettingsToGv::RelationType::PARENT_CHILD:
|
||||
if (!parent_child_viz)
|
||||
{
|
||||
return;
|
||||
}
|
||||
color = "black";
|
||||
break;
|
||||
case SettingsToGv::RelationType::ERROR_FUNCTION:
|
||||
if (!error_viz)
|
||||
{
|
||||
return;
|
||||
}
|
||||
color = "red";
|
||||
break;
|
||||
case SettingsToGv::RelationType::WARNING_FUNCTION:
|
||||
if (!warning_viz)
|
||||
{
|
||||
return;
|
||||
}
|
||||
color = "orange";
|
||||
break;
|
||||
}
|
||||
fprintf(out, "edge [color=%s];\n", color.c_str());
|
||||
fprintf(out, "%s -> %s;\n", parent.c_str(), child.c_str());
|
||||
}
|
||||
|
||||
bool createFunctionEdges(const rapidjson::Value& data, std::string function_key, const std::string& parent, const std::string& name, const RelationType relation_type)
|
||||
{
|
||||
bool generated_edge = false;
|
||||
if (data.HasMember(function_key.c_str()) && data[function_key.c_str()].IsString())
|
||||
{
|
||||
std::string function = data[function_key.c_str()].GetString();
|
||||
|
||||
std::regex setting_name_regex("[a-zA-Z0-9_]+"); // matches mostly with setting names
|
||||
std::smatch regex_match;
|
||||
while (std::regex_search (function, regex_match, setting_name_regex))
|
||||
{
|
||||
std::string inherited_setting_string = regex_match[0];
|
||||
if (inherited_setting_string == "parent_value")
|
||||
{
|
||||
generateEdge(parent, name, RelationType::PARENT_CHILD);
|
||||
generated_edge = true;
|
||||
}
|
||||
else if ( ! std::regex_match(inherited_setting_string, std::regex("[0-9]+")) && // exclude numbers
|
||||
// result != "parent_value" &&
|
||||
inherited_setting_string != "if" && inherited_setting_string != "else" && inherited_setting_string != "and"
|
||||
&& inherited_setting_string != "or" && inherited_setting_string != "math" && inherited_setting_string != "ceil"
|
||||
&& inherited_setting_string != "int" && inherited_setting_string != "round" && inherited_setting_string != "max" // exclude operators and functions
|
||||
&& inherited_setting_string != "grid" && inherited_setting_string != "triangles" // exclude enum values
|
||||
&& function.c_str()[regex_match.position() + regex_match.length()] != '\'') // exclude enum terms
|
||||
{
|
||||
if (inherited_setting_string == parent)
|
||||
{
|
||||
generated_edge = true;
|
||||
generateEdge(inherited_setting_string, name, RelationType::PARENT_CHILD);
|
||||
}
|
||||
else
|
||||
{
|
||||
generateEdge(inherited_setting_string, name, relation_type);
|
||||
}
|
||||
}
|
||||
function = regex_match.suffix().str();
|
||||
}
|
||||
}
|
||||
return generated_edge;
|
||||
}
|
||||
|
||||
void parseSetting(const std::string& parent, rapidjson::Value::ConstMemberIterator json_object_it)
|
||||
{
|
||||
std::string name = json_object_it->name.GetString();
|
||||
|
||||
// std::cerr << "parsed: " << name <<"\n";
|
||||
|
||||
bool generated_edge = false;
|
||||
|
||||
const rapidjson::Value& data = json_object_it->value;
|
||||
|
||||
if (data.HasMember("type") && data["type"].IsString() && data["type"].GetString() != std::string("category"))
|
||||
{
|
||||
|
||||
bool generated_edge_inherit = createFunctionEdges(data, "inherit_function", parent, name, RelationType::INHERIT_FUNCTION);
|
||||
bool generated_edge_max = createFunctionEdges(data, "max_value", parent, name, RelationType::ERROR_FUNCTION);
|
||||
bool generated_edge_min = createFunctionEdges(data, "min_value", parent, name, RelationType::ERROR_FUNCTION);
|
||||
bool generated_edge_max_warn = createFunctionEdges(data, "max_value_warning", parent, name, RelationType::WARNING_FUNCTION);
|
||||
bool generated_edge_min_warn = createFunctionEdges(data, "min_value_warning", parent, name, RelationType::WARNING_FUNCTION);
|
||||
if (generated_edge_inherit || generated_edge_max_warn || generated_edge_min_warn || generated_edge_max || generated_edge_min)
|
||||
{
|
||||
generated_edge = true;
|
||||
}
|
||||
|
||||
if (!generated_edge && parent != "")
|
||||
{
|
||||
generateEdge(parent, name, RelationType::PARENT_CHILD);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
name = "";
|
||||
}
|
||||
|
||||
// recursive part
|
||||
if (data.HasMember("children") && data["children"].IsObject())
|
||||
{
|
||||
const rapidjson::Value& json_object_container = data["children"];
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_object_container.MemberBegin(); setting_iterator != json_object_container.MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
parseSetting(name, setting_iterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parseJson(const rapidjson::Document& json_document)
|
||||
{
|
||||
if (json_document.HasMember("settings"))
|
||||
{
|
||||
for (rapidjson::Value::ConstMemberIterator setting_iterator = json_document["settings"].MemberBegin(); setting_iterator != json_document["settings"].MemberEnd(); ++setting_iterator)
|
||||
{
|
||||
parseSetting("", setting_iterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int generateRecursive(std::string filename)
|
||||
{
|
||||
rapidjson::Document json_document;
|
||||
|
||||
int err = SettingRegistry::loadJSON(filename, json_document);
|
||||
if (err) { return err; }
|
||||
|
||||
if (json_document.HasMember("inherits"))
|
||||
{
|
||||
std::string filename_copy = std::string(filename.c_str()); // copy the string because dirname(.) changes the input string!!!
|
||||
char* filename_cstr = (char*)filename_copy.c_str();
|
||||
int err = generate(std::string(dirname(filename_cstr)) + std::string("/") + json_document["inherits"].GetString());
|
||||
if (err)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
parseJson(json_document);
|
||||
return 0;
|
||||
}
|
||||
public:
|
||||
int generate(std::string json_filename)
|
||||
{
|
||||
int err = generateRecursive(json_filename);
|
||||
fprintf(out, "}\n");
|
||||
fclose(out);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // SETTINGS_TO_GV_H
|
||||
@@ -1,11 +1,12 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <stdio.h>
|
||||
#include <sstream> // ostringstream
|
||||
#include "utils/logoutput.h"
|
||||
#include "../utils/logoutput.h"
|
||||
|
||||
#include "settings.h"
|
||||
#include "settingRegistry.h"
|
||||
#include "SettingRegistry.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -28,6 +29,8 @@ std::string toString(EGCodeFlavor flavor)
|
||||
return "UltiGCode";
|
||||
case EGCodeFlavor::REPRAP_VOLUMATRIC:
|
||||
return "RepRap(Volumetric)";
|
||||
case EGCodeFlavor::GRIFFIN:
|
||||
return "Griffin";
|
||||
case EGCodeFlavor::REPRAP:
|
||||
default:
|
||||
return "RepRap";
|
||||
@@ -44,13 +47,15 @@ SettingsBaseVirtual::SettingsBaseVirtual(SettingsBaseVirtual* parent)
|
||||
{
|
||||
}
|
||||
|
||||
SettingsBase::SettingsBase()
|
||||
SettingsBase::SettingsBase(std::string level_id)
|
||||
: SettingsBaseVirtual(NULL)
|
||||
, level_id(level_id)
|
||||
{
|
||||
}
|
||||
|
||||
SettingsBase::SettingsBase(SettingsBaseVirtual* parent)
|
||||
SettingsBase::SettingsBase(SettingsBaseVirtual* parent, std::string level_id)
|
||||
: SettingsBaseVirtual(parent)
|
||||
, level_id(level_id)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -59,20 +64,36 @@ SettingsMessenger::SettingsMessenger(SettingsBaseVirtual* parent)
|
||||
{
|
||||
}
|
||||
|
||||
void SettingsBase::_setSetting(std::string key, std::string value)
|
||||
{
|
||||
setting_values[key] = value;
|
||||
}
|
||||
|
||||
|
||||
void SettingsBase::setSetting(std::string key, std::string value)
|
||||
{
|
||||
if (SettingRegistry::getInstance()->settingExists(key))
|
||||
{
|
||||
setting_values[key] = value;
|
||||
_setSetting(key, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
cura::logError("Warning: setting an unregistered setting %s\n", key.c_str() );
|
||||
setting_values[key] = value; // Handy when programmers are in the process of introducing a new setting
|
||||
_setSetting(key, value); // Handy when programmers are in the process of introducing a new setting
|
||||
}
|
||||
}
|
||||
|
||||
std::string SettingsBase::getSettingString(std::string key) const
|
||||
{
|
||||
// logError(">>>%s, %s\n", key.c_str(), level_id.c_str());
|
||||
std::cout << key << "\t\t : " << level_id << "\n";
|
||||
// if (level_id == std::string("global"))
|
||||
// {
|
||||
// logError("WARNING: %s retrieved globally!\n", key.c_str());
|
||||
// }
|
||||
return _getSettingString(key);
|
||||
}
|
||||
std::string SettingsBase::_getSettingString(std::string key) const
|
||||
{
|
||||
if (setting_values.find(key) != setting_values.end())
|
||||
{
|
||||
@@ -80,20 +101,12 @@ std::string SettingsBase::getSettingString(std::string key) const
|
||||
}
|
||||
if (parent)
|
||||
{
|
||||
return parent->getSettingString(key);
|
||||
return parent->_getSettingString(key);
|
||||
}
|
||||
|
||||
SettingsBase& nonConstThis = const_cast<SettingsBase&>(*this);
|
||||
if (SettingRegistry::getInstance()->settingExists(key))
|
||||
{
|
||||
nonConstThis.setting_values[key] = SettingRegistry::getInstance()->getSettingConfig(key)->getDefaultValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
nonConstThis.setting_values[key] = "";
|
||||
cura::logError("Unregistered setting %s\n", key.c_str());
|
||||
}
|
||||
return setting_values.at(key);
|
||||
|
||||
const_cast<SettingsBase&>(*this).setting_values[key] = "";
|
||||
cura::logError("Unregistered setting %s\n", key.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
void SettingsMessenger::setSetting(std::string key, std::string value)
|
||||
@@ -101,39 +114,15 @@ void SettingsMessenger::setSetting(std::string key, std::string value)
|
||||
parent->setSetting(key, value);
|
||||
}
|
||||
|
||||
std::string SettingsMessenger::_getSettingString(std::string key) const
|
||||
{
|
||||
return parent->_getSettingString(key);
|
||||
}
|
||||
std::string SettingsMessenger::getSettingString(std::string key) const
|
||||
{
|
||||
return parent->getSettingString(key);
|
||||
}
|
||||
|
||||
|
||||
void SettingsBase::setExtruderTrainDefaults(unsigned int extruder_nr)
|
||||
{
|
||||
const SettingContainer* machine_extruder_trains = SettingRegistry::getInstance()->getCategory(std::string("machine_extruder_trains"));
|
||||
|
||||
if (!machine_extruder_trains)
|
||||
{
|
||||
// no machine_extruder_trains setting present; just use defaults for each train..
|
||||
return;
|
||||
}
|
||||
|
||||
const SettingConfig* train = machine_extruder_trains->getChild(extruder_nr);
|
||||
|
||||
if (!train)
|
||||
{
|
||||
// not enough machine_extruder_trains settings present; just use defaults for this train..
|
||||
return;
|
||||
}
|
||||
|
||||
for (const SettingConfig& setting : train->getChildren())
|
||||
{
|
||||
if (setting_values.find(setting.getKey()) == setting_values.end())
|
||||
{
|
||||
setSetting(setting.getKey(), setting.getDefaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int SettingsBaseVirtual::getSettingAsIndex(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
@@ -146,10 +135,15 @@ int SettingsBaseVirtual::getSettingAsCount(std::string key) const
|
||||
return atoi(value.c_str());
|
||||
}
|
||||
|
||||
int SettingsBaseVirtual::getSettingInMicrons(std::string key) const
|
||||
double SettingsBaseVirtual::getSettingInMillimeters(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
return atof(value.c_str()) * 1000.0;
|
||||
return atof(value.c_str());
|
||||
}
|
||||
|
||||
int SettingsBaseVirtual::getSettingInMicrons(std::string key) const
|
||||
{
|
||||
return getSettingInMillimeters(key) * 1000.0;
|
||||
}
|
||||
|
||||
double SettingsBaseVirtual::getSettingInAngleRadians(std::string key) const
|
||||
@@ -262,8 +256,8 @@ FlowTempGraph SettingsBaseVirtual::getSettingAsFlowTempGraph(std::string key) co
|
||||
EGCodeFlavor SettingsBaseVirtual::getSettingAsGCodeFlavor(std::string key) const
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "RepRap")
|
||||
return EGCodeFlavor::REPRAP;
|
||||
if (value == "Griffin")
|
||||
return EGCodeFlavor::GRIFFIN;
|
||||
else if (value == "UltiGCode")
|
||||
return EGCodeFlavor::ULTIGCODE;
|
||||
else if (value == "Makerbot")
|
||||
@@ -337,22 +331,38 @@ ESurfaceMode SettingsBaseVirtual::getSettingAsSurfaceMode(std::string key) const
|
||||
return ESurfaceMode::NORMAL;
|
||||
}
|
||||
|
||||
FillPerimeterGapMode SettingsBaseVirtual::getSettingAsFillPerimeterGapMode(std::string key) const
|
||||
CombingMode SettingsBaseVirtual::getSettingAsCombingMode(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "nowhere")
|
||||
if (value == "off")
|
||||
{
|
||||
return FillPerimeterGapMode::NOWHERE;
|
||||
return CombingMode::OFF;
|
||||
}
|
||||
if (value == "everywhere")
|
||||
if (value == "all")
|
||||
{
|
||||
return FillPerimeterGapMode::EVERYWHERE;
|
||||
return CombingMode::ALL;
|
||||
}
|
||||
if (value == "skin")
|
||||
if (value == "noskin")
|
||||
{
|
||||
return FillPerimeterGapMode::SKIN;
|
||||
return CombingMode::NO_SKIN;
|
||||
}
|
||||
return FillPerimeterGapMode::NOWHERE;
|
||||
return CombingMode::ALL;
|
||||
}
|
||||
|
||||
SupportDistPriority SettingsBaseVirtual::getSettingAsSupportDistPriority(std::string key)
|
||||
{
|
||||
std::string value = getSettingString(key);
|
||||
if (value == "xy_overrides_z")
|
||||
{
|
||||
return SupportDistPriority::XY_OVERRIDES_Z;
|
||||
}
|
||||
if (value == "z_overrides_xy")
|
||||
{
|
||||
return SupportDistPriority::Z_OVERRIDES_XY;
|
||||
}
|
||||
return SupportDistPriority::XY_OVERRIDES_Z;
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
#ifndef SETTINGS_H
|
||||
#define SETTINGS_H
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef SETTINGS_SETTINGS_H
|
||||
#define SETTINGS_SETTINGS_H
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
|
||||
#include "utils/floatpoint.h"
|
||||
#include "../utils/floatpoint.h"
|
||||
|
||||
#include "FlowTempGraph.h"
|
||||
#include "../FlowTempGraph.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -75,6 +76,16 @@ enum class EGCodeFlavor
|
||||
* M106 Sxxx and M107 are used to turn the fan on/off.
|
||||
**/
|
||||
REPRAP_VOLUMATRIC = 5,
|
||||
/**
|
||||
* Griffin flavored is Marlin based GCode.
|
||||
* This is a type of RepRap used for machines with multiple extruder trains.
|
||||
* G0 for moves, G1 for extrusion.
|
||||
* E values give mm of filament extrusion.
|
||||
* E values are stored separately per extruder train.
|
||||
* Retraction is done on E values with G1. Start/end code is added.
|
||||
* M227 is used to initialize a single extrusion train.
|
||||
**/
|
||||
GRIFFIN = 6,
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -131,11 +142,17 @@ enum class ESurfaceMode
|
||||
BOTH
|
||||
};
|
||||
|
||||
enum class FillPerimeterGapMode
|
||||
enum class CombingMode
|
||||
{
|
||||
NOWHERE,
|
||||
EVERYWHERE,
|
||||
SKIN
|
||||
OFF,
|
||||
ALL,
|
||||
NO_SKIN
|
||||
};
|
||||
|
||||
enum class SupportDistPriority
|
||||
{
|
||||
XY_OVERRIDES_Z,
|
||||
Z_OVERRIDES_XY
|
||||
};
|
||||
|
||||
#define MAX_EXTRUDERS 16
|
||||
@@ -156,6 +173,7 @@ protected:
|
||||
SettingsBaseVirtual* parent;
|
||||
public:
|
||||
virtual std::string getSettingString(std::string key) const = 0;
|
||||
virtual std::string _getSettingString(std::string key) const = 0;
|
||||
|
||||
virtual void setSetting(std::string key, std::string value) = 0;
|
||||
|
||||
@@ -171,6 +189,7 @@ public:
|
||||
int getSettingAsCount(std::string key) const;
|
||||
|
||||
double getSettingInAngleRadians(std::string key) const;
|
||||
double getSettingInMillimeters(std::string key) const;
|
||||
int getSettingInMicrons(std::string key) const;
|
||||
bool getSettingBoolean(std::string key) const;
|
||||
double getSettingInDegreeCelsius(std::string key) const;
|
||||
@@ -181,17 +200,17 @@ public:
|
||||
|
||||
FlowTempGraph getSettingAsFlowTempGraph(std::string key) const;
|
||||
|
||||
std::vector<std::pair<double, double>> getSettingAsPointVector(std::string key) const;
|
||||
|
||||
EGCodeFlavor getSettingAsGCodeFlavor(std::string key) const;
|
||||
EFillMethod getSettingAsFillMethod(std::string key) const;
|
||||
EPlatformAdhesion getSettingAsPlatformAdhesion(std::string key) const;
|
||||
ESupportType getSettingAsSupportType(std::string key) const;
|
||||
EZSeamType getSettingAsZSeamType(std::string key) const;
|
||||
ESurfaceMode getSettingAsSurfaceMode(std::string key) const;
|
||||
FillPerimeterGapMode getSettingAsFillPerimeterGapMode(std::string key) const;
|
||||
CombingMode getSettingAsCombingMode(std::string key);
|
||||
SupportDistPriority getSettingAsSupportDistPriority(std::string key);
|
||||
};
|
||||
|
||||
class SettingRegistry;
|
||||
/*!
|
||||
* Base class for every object that can hold settings.
|
||||
* The SettingBase object can hold multiple key-value pairs that define settings.
|
||||
@@ -201,24 +220,22 @@ public:
|
||||
*/
|
||||
class SettingsBase : public SettingsBaseVirtual
|
||||
{
|
||||
friend class SettingRegistry;
|
||||
private:
|
||||
std::unordered_map<std::string, std::string> setting_values;
|
||||
std::string level_id; //!< The name of the level at which this is retrieved "global", "extruder-1", etc.
|
||||
public:
|
||||
SettingsBase(); //!< SettingsBase without a parent settings object
|
||||
SettingsBase(SettingsBaseVirtual* parent); //!< construct a SettingsBase with a parent settings object
|
||||
|
||||
SettingsBase(std::string level_id); //!< SettingsBase without a parent settings object
|
||||
SettingsBase(SettingsBaseVirtual* parent, std::string level_id); //!< construct a SettingsBase with a parent settings object
|
||||
|
||||
/*!
|
||||
* Retrieve the defaults for each extruder train from the machine_extruder_trains settings
|
||||
* and set the general settings to those defaults if they haven't been set yet.
|
||||
*
|
||||
* Only sets those settings which haven't already been set on that level - not looking at its parent (FffProcessor, meshgroup) or children (meshes).
|
||||
*
|
||||
* \param extruder_nr The index of which extruder train in machine_extruder_trains to get the settings from
|
||||
* Set a setting to a value.
|
||||
* \param key the setting
|
||||
* \param value the value
|
||||
*/
|
||||
void setExtruderTrainDefaults(unsigned int extruder_nr);
|
||||
|
||||
void setSetting(std::string key, std::string value);
|
||||
std::string getSettingString(std::string key) const; //!< Get a setting from this SettingsBase (or any ancestral SettingsBase)
|
||||
std::string _getSettingString(std::string key) const; //!< Get a setting from this SettingsBase (or any ancestral SettingsBase)
|
||||
|
||||
std::string getAllLocalSettingsString() const
|
||||
{
|
||||
@@ -238,6 +255,13 @@ public:
|
||||
for (auto pair : setting_values)
|
||||
std::cerr << pair.first << " : " << pair.second << std::endl;
|
||||
}
|
||||
protected:
|
||||
/*!
|
||||
* Set a setting without checking if it's registered.
|
||||
*
|
||||
* Used in SettingsRegistry
|
||||
*/
|
||||
void _setSetting(std::string key, std::string value);
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -252,8 +276,10 @@ public:
|
||||
|
||||
void setSetting(std::string key, std::string value); //!< Set a setting of the parent SettingsBase to a given value
|
||||
std::string getSettingString(std::string key) const; //!< Get a setting from the parent SettingsBase (or any further ancestral SettingsBase)
|
||||
std::string _getSettingString(std::string key) const; //!< Get a setting from the parent SettingsBase (or any further ancestral SettingsBase)
|
||||
};
|
||||
|
||||
|
||||
}//namespace cura
|
||||
#endif//SETTINGS_H
|
||||
#endif//SETTINGS_SETTINGS_H
|
||||
|
||||
+38
-66
@@ -8,21 +8,21 @@ namespace cura
|
||||
{
|
||||
|
||||
|
||||
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
|
||||
void generateSkins(int layerNr, SliceMeshStorage& mesh, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic)
|
||||
{
|
||||
generateSkinAreas(layerNr, storage, innermost_wall_extrusion_width, downSkinCount, upSkinCount, wall_line_count, no_small_gaps_heuristic);
|
||||
generateSkinAreas(layerNr, mesh, innermost_wall_extrusion_width, downSkinCount, upSkinCount, wall_line_count, no_small_gaps_heuristic);
|
||||
|
||||
SliceLayer* layer = &storage.layers[layerNr];
|
||||
SliceLayer* layer = &mesh.layers[layerNr];
|
||||
for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++)
|
||||
{
|
||||
SliceLayerPart* part = &layer->parts[partNr];
|
||||
generateSkinInsets(part, extrusionWidth, insetCount, avoidOverlappingPerimeters_0, avoidOverlappingPerimeters);
|
||||
generateSkinInsets(part, extrusionWidth, insetCount);
|
||||
}
|
||||
}
|
||||
|
||||
void generateSkinAreas(int layer_nr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic)
|
||||
void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, int innermost_wall_extrusion_width, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic)
|
||||
{
|
||||
SliceLayer& layer = storage.layers[layer_nr];
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
|
||||
if (downSkinCount == 0 && upSkinCount == 0)
|
||||
{
|
||||
@@ -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;
|
||||
};
|
||||
@@ -57,32 +60,32 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& storage, int innermost_wa
|
||||
{
|
||||
if (static_cast<int>(layer_nr - downSkinCount) >= 0)
|
||||
{
|
||||
downskin = downskin.difference(getInsidePolygons(storage.layers[layer_nr - downSkinCount])); // skin overlaps with the walls
|
||||
downskin = downskin.difference(getInsidePolygons(mesh.layers[layer_nr - downSkinCount])); // skin overlaps with the walls
|
||||
}
|
||||
|
||||
if (static_cast<int>(layer_nr + upSkinCount) < static_cast<int>(storage.layers.size()))
|
||||
if (static_cast<int>(layer_nr + upSkinCount) < static_cast<int>(mesh.layers.size()))
|
||||
{
|
||||
upskin = upskin.difference(getInsidePolygons(storage.layers[layer_nr + upSkinCount])); // skin overlaps with the walls
|
||||
upskin = upskin.difference(getInsidePolygons(mesh.layers[layer_nr + upSkinCount])); // skin overlaps with the walls
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (layer_nr >= downSkinCount && downSkinCount > 0)
|
||||
{
|
||||
Polygons not_air = getInsidePolygons(storage.layers[layer_nr - 1]);
|
||||
Polygons not_air = getInsidePolygons(mesh.layers[layer_nr - 1]);
|
||||
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]));
|
||||
not_air = not_air.intersection(getInsidePolygons(mesh.layers[downskin_layer_nr]));
|
||||
}
|
||||
downskin = downskin.difference(not_air); // skin overlaps with the walls
|
||||
}
|
||||
|
||||
if (layer_nr < static_cast<int>(storage.layers.size()) - downSkinCount && upSkinCount > 0)
|
||||
if (layer_nr < static_cast<int>(mesh.layers.size()) - 1 && upSkinCount > 0)
|
||||
{
|
||||
Polygons not_air = getInsidePolygons(storage.layers[layer_nr + 1]);
|
||||
Polygons not_air = getInsidePolygons(mesh.layers[layer_nr + 1]);
|
||||
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]));
|
||||
not_air = not_air.intersection(getInsidePolygons(mesh.layers[upskin_layer_nr]));
|
||||
}
|
||||
upskin = upskin.difference(not_air); // skin overlaps with the walls
|
||||
}
|
||||
@@ -101,7 +104,7 @@ void generateSkinAreas(int layer_nr, SliceMeshStorage& storage, int innermost_wa
|
||||
}
|
||||
|
||||
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters)
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount)
|
||||
{
|
||||
if (insetCount == 0)
|
||||
{
|
||||
@@ -115,12 +118,10 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
skin_part.insets.push_back(Polygons());
|
||||
if (i == 0)
|
||||
{
|
||||
PolygonUtils::offsetSafe(skin_part.outline, - extrusionWidth/2, extrusionWidth, skin_part.insets[0], avoidOverlappingPerimeters_0);
|
||||
Polygons in_between = skin_part.outline.difference(skin_part.insets[0].offset(extrusionWidth/2));
|
||||
skin_part.perimeterGaps.add(in_between);
|
||||
skin_part.insets[0] = skin_part.outline.offset(- extrusionWidth/2);
|
||||
} else
|
||||
{
|
||||
PolygonUtils::offsetExtrusionWidth(skin_part.insets[i-1], true, extrusionWidth, skin_part.insets[i], &skin_part.perimeterGaps, avoidOverlappingPerimeters);
|
||||
skin_part.insets[i] = skin_part.insets[i - 1].offset(-extrusionWidth);
|
||||
}
|
||||
|
||||
// optimize polygons: remove unnnecesary verts
|
||||
@@ -134,15 +135,15 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
}
|
||||
}
|
||||
|
||||
void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count)
|
||||
void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count)
|
||||
{
|
||||
SliceLayer& layer = storage.layers[layerNr];
|
||||
SliceLayer& layer = mesh.layers[layerNr];
|
||||
|
||||
for(SliceLayerPart& part : layer.parts)
|
||||
{
|
||||
if (int(part.insets.size()) < wall_line_count)
|
||||
{
|
||||
part.infill_area.emplace_back(); // put empty polygon as (uncombined) infill
|
||||
part.infill_area_per_combine.emplace_back(); // put empty polygons as initial infill_per_combine
|
||||
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);
|
||||
@@ -159,17 +160,19 @@ void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_e
|
||||
}
|
||||
infill.removeSmallAreas(MIN_AREA_SIZE);
|
||||
|
||||
part.infill_area.push_back(infill.offset(infill_skin_overlap));
|
||||
part.infill_area = infill.offset(infill_skin_overlap);
|
||||
part.infill_area_per_combine.push_back(part.infill_area);
|
||||
}
|
||||
}
|
||||
|
||||
void combineInfillLayers(SliceMeshStorage& storage,unsigned int amount)
|
||||
void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount)
|
||||
{
|
||||
// Note that *all* parts should have an [infill_area_per_combine] with one element in it, which up till now only contains the exact same polygons as [infill].
|
||||
if(amount <= 1) //If we must combine 1 layer, nothing needs to be combined. Combining 0 layers is invalid.
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(storage.layers.empty() || storage.layers.size() - 1 < static_cast<size_t>(storage.getSettingAsCount("top_layers")) || storage.getSettingAsCount("infill_line_distance") <= 0) //No infill is even generated.
|
||||
if (mesh.layers.empty() || mesh.layers.size() - 1 < static_cast<size_t>(mesh.getSettingAsCount("top_layers")) || mesh.getSettingAsCount("infill_line_distance") <= 0) //No infill is even generated.
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -177,13 +180,13 @@ void combineInfillLayers(SliceMeshStorage& storage,unsigned int amount)
|
||||
divisible index. Otherwise we get some parts that have infill at divisible
|
||||
layers and some at non-divisible layers. Those layers would then miss each
|
||||
other. */
|
||||
size_t min_layer = storage.getSettingAsCount("bottom_layers") + amount - 1;
|
||||
size_t min_layer = mesh.getSettingAsCount("bottom_layers") + amount - 1;
|
||||
min_layer -= min_layer % amount; //Round upwards to the nearest layer divisible by infill_sparse_combine.
|
||||
size_t max_layer = storage.layers.size() - 1 - storage.getSettingAsCount("top_layers");
|
||||
size_t max_layer = mesh.layers.size() - 1 - mesh.getSettingAsCount("top_layers");
|
||||
max_layer -= max_layer % amount; //Round downwards to the nearest layer divisible by infill_sparse_combine.
|
||||
for(size_t layer_idx = min_layer;layer_idx <= max_layer;layer_idx += amount) //Skip every few layers, but extrude more.
|
||||
{
|
||||
SliceLayer* layer = &storage.layers[layer_idx];
|
||||
SliceLayer* layer = &mesh.layers[layer_idx];
|
||||
|
||||
for(unsigned int n = 1;n < amount;n++)
|
||||
{
|
||||
@@ -192,7 +195,7 @@ void combineInfillLayers(SliceMeshStorage& storage,unsigned int amount)
|
||||
break;
|
||||
}
|
||||
|
||||
SliceLayer* layer2 = &storage.layers[layer_idx - n];
|
||||
SliceLayer* layer2 = &mesh.layers[layer_idx - n];
|
||||
for(SliceLayerPart& part : layer->parts)
|
||||
{
|
||||
Polygons result;
|
||||
@@ -200,49 +203,18 @@ void combineInfillLayers(SliceMeshStorage& storage,unsigned int amount)
|
||||
{
|
||||
if(part.boundaryBox.hit(part2.boundaryBox))
|
||||
{
|
||||
Polygons intersection = part.infill_area[n - 1].intersection(part2.infill_area[0]).offset(-200).offset(200);
|
||||
Polygons intersection = part.infill_area_per_combine[n - 1].intersection(part2.infill_area_per_combine[0]).offset(-200).offset(200);
|
||||
result.add(intersection);
|
||||
part.infill_area[n - 1] = part.infill_area[n - 1].difference(intersection);
|
||||
part2.infill_area[0] = part2.infill_area[0].difference(intersection);
|
||||
part.infill_area_per_combine[n - 1] = part.infill_area_per_combine[n - 1].difference(intersection);
|
||||
part2.infill_area_per_combine[0] = part2.infill_area_per_combine[0].difference(intersection);
|
||||
}
|
||||
}
|
||||
|
||||
part.infill_area.push_back(result);
|
||||
part.infill_area_per_combine.push_back(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void generatePerimeterGaps(int layer_nr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount)
|
||||
{
|
||||
SliceLayer& layer = storage.layers[layer_nr];
|
||||
|
||||
for (SliceLayerPart& part : layer.parts)
|
||||
{ // handle gaps between perimeters etc.
|
||||
if (downSkinCount > 0 && upSkinCount > 0 && // note: if both are zero or less, then all gaps will be used
|
||||
layer_nr >= downSkinCount && layer_nr < static_cast<int>(storage.layers.size() - upSkinCount)) // remove gaps which appear within print, i.e. not on the bottom most or top most skin
|
||||
{
|
||||
Polygons outlines_above;
|
||||
for (SliceLayerPart& part_above : storage.layers[layer_nr + upSkinCount].parts)
|
||||
{
|
||||
if (part.boundaryBox.hit(part_above.boundaryBox))
|
||||
{
|
||||
outlines_above.add(part_above.outline);
|
||||
}
|
||||
}
|
||||
Polygons outlines_below;
|
||||
for (SliceLayerPart& part_below : storage.layers[layer_nr - downSkinCount].parts)
|
||||
{
|
||||
if (part.boundaryBox.hit(part_below.boundaryBox))
|
||||
{
|
||||
outlines_below.add(part_below.outline);
|
||||
}
|
||||
}
|
||||
part.perimeterGaps = part.perimeterGaps.intersection(outlines_above.xorPolygons(outlines_below));
|
||||
}
|
||||
part.perimeterGaps.removeSmallAreas(MIN_AREA_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+9
-24
@@ -6,23 +6,11 @@
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Generate the gap areas which occur between consecutive insets.
|
||||
*
|
||||
* \param layerNr The index of the layer for which to generate the gaps.
|
||||
* \param storage The storage where the layer outline information (input) is stored and where the gap areas (output) are stored.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param downSkinCount The number of layers of bottom gaps
|
||||
* \param upSkinCount The number of layers of top gaps
|
||||
*/
|
||||
void generatePerimeterGaps(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount);
|
||||
|
||||
/*!
|
||||
* Generate the skin areas and its insets.
|
||||
*
|
||||
* \param layerNr The index of the layer for which to generate the skins.
|
||||
* \param storage The storage where the layer outline information (input) is stored and where the skin insets and fill areas (output) are stored.
|
||||
* \param mesh The storage where the layer outline information (input) is stored and where the skin insets and fill areas (output) are stored.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param downSkinCount The number of layers of bottom skin
|
||||
* \param upSkinCount The number of layers of top skin
|
||||
@@ -30,23 +18,21 @@ void generatePerimeterGaps(int layerNr, SliceMeshStorage& storage, int extrusion
|
||||
* \param innermost_wall_extrusion_width The line width of the inner most wall
|
||||
* \param insetCount The number of perimeters to surround the skin
|
||||
* \param no_small_gaps_heuristic A heuristic which assumes there will be no small gaps between bottom and top skin with a z size smaller than the skin size itself
|
||||
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p storage)
|
||||
*/
|
||||
void generateSkins(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
void generateSkins(int layerNr, SliceMeshStorage& mesh, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, int innermost_wall_extrusion_width, int insetCount, bool no_small_gaps_heuristic);
|
||||
|
||||
/*!
|
||||
* Generate the skin areas (outlines)
|
||||
*
|
||||
* \param layerNr The index of the layer for which to generate the skins.
|
||||
* \param storage The storage where the layer outline information (input) is stored and where the skin outline (output) is stored.
|
||||
* \param mesh The storage where the layer outline information (input) is stored and where the skin outline (output) is stored.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param downSkinCount The number of layers of bottom skin
|
||||
* \param upSkinCount The number of layers of top skin
|
||||
* \param wall_line_count The number of walls, i.e. the number of the wall from which to offset.
|
||||
* \param no_small_gaps_heuristic A heuristic which assumes there will be no small gaps between bottom and top skin with a z size smaller than the skin size itself
|
||||
*/
|
||||
void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic);
|
||||
void generateSkinAreas(int layerNr, SliceMeshStorage& mesh, int extrusionWidth, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic);
|
||||
|
||||
/*!
|
||||
* Generate the skin insets.
|
||||
@@ -55,10 +41,8 @@ void generateSkinAreas(int layerNr, SliceMeshStorage& storage, int extrusionWidt
|
||||
* \param part The part where the skin outline information (input) is stored and where the skin insets (output) are stored.
|
||||
* \param extrusionWidth extrusionWidth
|
||||
* \param insetCount The number of perimeters to surround the skin
|
||||
* \param avoidOverlappingPerimeters_0 Whether to remove the parts of the first perimeters where it have overlap with itself (and store the gaps thus created in the \p storage)
|
||||
* \param avoidOverlappingPerimeters Whether to remove the parts of two consecutive perimeters where they have overlap (and store the gaps thus created in the \p storage)
|
||||
*/
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount, bool avoidOverlappingPerimeters_0, bool avoidOverlappingPerimeters);
|
||||
void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount);
|
||||
|
||||
/*!
|
||||
* Generate Infill by offsetting from the last wall.
|
||||
@@ -68,12 +52,13 @@ void generateSkinInsets(SliceLayerPart* part, int extrusionWidth, int insetCount
|
||||
* 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 mesh The storage where the layer outline information (input) is stored and where the skin outline (output) is stored.
|
||||
* \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
|
||||
* \param infill_skin_overlap overlap distance between infill and skin
|
||||
* \param wall_line_count The number of walls, i.e. the number of the wall from which to offset.
|
||||
*/
|
||||
void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count);
|
||||
void generateInfill(int layerNr, SliceMeshStorage& mesh, int innermost_wall_extrusion_width, int infill_skin_overlap, int wall_line_count);
|
||||
|
||||
/*!
|
||||
* \brief Combines the infill of multiple layers for a specified mesh.
|
||||
@@ -82,10 +67,10 @@ void generateInfill(int layerNr, SliceMeshStorage& storage, int innermost_wall_e
|
||||
* multiplied such that the infill should fill up again to the full height of
|
||||
* all combined layers.
|
||||
*
|
||||
* \param storage The mesh to combine the infill layers of.
|
||||
* \param mesh The mesh to combine the infill layers of.
|
||||
* \param amount The number of layers to combine.
|
||||
*/
|
||||
void combineInfillLayers(SliceMeshStorage& storage,unsigned int amount);
|
||||
void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount);
|
||||
|
||||
}//namespace cura
|
||||
|
||||
|
||||
+52
-26
@@ -5,47 +5,47 @@
|
||||
namespace cura
|
||||
{
|
||||
|
||||
Polygons SliceLayer::getOutlines(bool external_polys_only)
|
||||
Polygons SliceLayer::getOutlines(bool external_polys_only) const
|
||||
{
|
||||
Polygons ret;
|
||||
getOutlines(ret, external_polys_only);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SliceLayer::getOutlines(Polygons& result, bool external_polys_only)
|
||||
void SliceLayer::getOutlines(Polygons& result, bool external_polys_only) const
|
||||
{
|
||||
for (SliceLayerPart& part : parts)
|
||||
for (const SliceLayerPart& part : parts)
|
||||
{
|
||||
if (external_polys_only)
|
||||
{
|
||||
result.add(part.outline.outerPolygon());
|
||||
result.add(const_cast<SliceLayerPart&>(part).outline.outerPolygon()); // TODO: make a const version of outerPolygon()
|
||||
}
|
||||
else
|
||||
{
|
||||
result.add(part.outline);
|
||||
result.add(part.print_outline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Polygons SliceLayer::getSecondOrInnermostWalls()
|
||||
Polygons SliceLayer::getSecondOrInnermostWalls() const
|
||||
{
|
||||
Polygons ret;
|
||||
getSecondOrInnermostWalls(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls)
|
||||
void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls) const
|
||||
{
|
||||
for (SliceLayerPart& part : parts)
|
||||
for (const SliceLayerPart& part : parts)
|
||||
{
|
||||
// we want the 2nd inner walls
|
||||
if (part.insets.size() >= 2) {
|
||||
layer_walls.add(part.insets[1]);
|
||||
layer_walls.add(const_cast<SliceLayerPart&>(part).insets[1]); // TODO const cast!
|
||||
continue;
|
||||
}
|
||||
// but we'll also take the inner wall if the 2nd doesn't exist
|
||||
if (part.insets.size() == 1) {
|
||||
layer_walls.add(part.insets[0]);
|
||||
layer_walls.add(const_cast<SliceLayerPart&>(part).insets[0]); // TODO const cast!
|
||||
continue;
|
||||
}
|
||||
// offset_from_outlines was so large that it completely destroyed our isle,
|
||||
@@ -56,21 +56,47 @@ void SliceLayer::getSecondOrInnermostWalls(Polygons& layer_walls)
|
||||
}
|
||||
|
||||
|
||||
std::vector<RetractionConfig> SliceDataStorage::initializeRetractionConfigs()
|
||||
{
|
||||
std::vector<RetractionConfig> ret;
|
||||
ret.resize(meshgroup->getExtruderCount()); // initializes with constructor RetractionConfig()
|
||||
return ret;
|
||||
}
|
||||
std::vector<GCodePathConfig> SliceDataStorage::initializeTravelConfigs()
|
||||
{
|
||||
std::vector<GCodePathConfig> ret;
|
||||
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
RetractionConfig* retraction_config = nullptr;
|
||||
travel_config_per_extruder.emplace_back(retraction_config, PrintFeatureType::MoveCombing);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
std::vector<GCodePathConfig> SliceDataStorage::initializeSkirtConfigs()
|
||||
{
|
||||
std::vector<GCodePathConfig> ret;
|
||||
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
RetractionConfig* extruder_retraction_config = &retraction_config_per_extruder[extruder];
|
||||
skirt_config.emplace_back(extruder_retraction_config, PrintFeatureType::Skirt);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
SliceDataStorage::SliceDataStorage(MeshGroup* meshgroup) : SettingsMessenger(meshgroup),
|
||||
meshgroup(meshgroup != nullptr ? meshgroup : new MeshGroup(FffProcessor::getInstance())), //If no mesh group is provided, we roll our own.
|
||||
retraction_config_per_extruder(initializeRetractionConfigs()),
|
||||
travel_config(&retraction_config, PrintFeatureType::MoveCombing),
|
||||
travel_config_per_extruder(initializeTravelConfigs()),
|
||||
skirt_config(initializeSkirtConfigs()),
|
||||
raft_base_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
raft_interface_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
raft_surface_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
support_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("support_infill_extruder_nr")], PrintFeatureType::Support),
|
||||
support_roof_config(&retraction_config_per_extruder[this->meshgroup->getSettingAsIndex("support_roof_extruder_nr")], PrintFeatureType::Skin),
|
||||
raft_base_config(&retraction_config_per_extruder[getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
raft_interface_config(&retraction_config_per_extruder[getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
raft_surface_config(&retraction_config_per_extruder[getSettingAsIndex("adhesion_extruder_nr")], PrintFeatureType::Support),
|
||||
support_config(&retraction_config_per_extruder[getSettingAsIndex("support_infill_extruder_nr")], PrintFeatureType::Support),
|
||||
support_roof_config(&retraction_config_per_extruder[getSettingAsIndex("support_roof_extruder_nr")], PrintFeatureType::Skin),
|
||||
max_object_height_second_to_last_extruder(-1)
|
||||
{
|
||||
}
|
||||
|
||||
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only)
|
||||
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only) const
|
||||
{
|
||||
if (layer_nr < 0)
|
||||
{ // when processing raft
|
||||
@@ -99,11 +125,11 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
|
||||
else
|
||||
{
|
||||
Polygons total;
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
layer.getOutlines(total, external_polys_only);
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make all getSetting functions const??
|
||||
{
|
||||
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
|
||||
}
|
||||
@@ -121,7 +147,7 @@ Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_pa
|
||||
}
|
||||
}
|
||||
|
||||
Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts)
|
||||
Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts) const
|
||||
{
|
||||
if (layer_nr < 0)
|
||||
{ // when processing raft
|
||||
@@ -137,11 +163,11 @@ Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool inc
|
||||
else
|
||||
{
|
||||
Polygons total;
|
||||
for (SliceMeshStorage& mesh : meshes)
|
||||
for (const SliceMeshStorage& mesh : meshes)
|
||||
{
|
||||
SliceLayer& layer = mesh.layers[layer_nr];
|
||||
const SliceLayer& layer = mesh.layers[layer_nr];
|
||||
layer.getSecondOrInnermostWalls(total);
|
||||
if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL)
|
||||
if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make getSetting const? make settings.setting_values mapping mutable??
|
||||
{
|
||||
total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100));
|
||||
}
|
||||
@@ -194,7 +220,7 @@ std::vector<bool> SliceDataStorage::getExtrudersUsed(int layer_nr)
|
||||
}
|
||||
else
|
||||
{
|
||||
ret[getSettingAsIndex("support_extruder_nr")] = true;
|
||||
ret[getSettingAsIndex("support_infill_extruder_nr")] = true;
|
||||
}
|
||||
}
|
||||
if (support.supportLayers[layer_nr].roofs.size() > 0)
|
||||
@@ -238,7 +264,7 @@ std::vector< bool > SliceDataStorage::getExtrudersUsed()
|
||||
// support
|
||||
// support is presupposed to be present...
|
||||
ret[getSettingAsIndex("support_extruder_nr_layer_0")] = true;
|
||||
ret[getSettingAsIndex("support_extruder_nr")] = true;
|
||||
ret[getSettingAsIndex("support_infill_extruder_nr")] = true;
|
||||
ret[getSettingAsIndex("support_roof_extruder_nr")] = true;
|
||||
|
||||
// all meshes are presupposed to actually have content
|
||||
|
||||
+26
-29
@@ -5,6 +5,7 @@
|
||||
#include "utils/intpoint.h"
|
||||
#include "utils/polygon.h"
|
||||
#include "utils/NoCopy.h"
|
||||
#include "utils/AABB.h"
|
||||
#include "mesh.h"
|
||||
#include "gcodePlanner.h"
|
||||
#include "MeshGroup.h"
|
||||
@@ -22,7 +23,6 @@ class SkinPart
|
||||
public:
|
||||
PolygonsPart outline; //!< The skinOutline is the area which needs to be 100% filled to generate a proper top&bottom filling. It's filled by the "skin" module.
|
||||
std::vector<Polygons> insets; //!< The skin can have perimeters so that the skin lines always start at a perimeter instead of in the middle of an infill cell.
|
||||
Polygons perimeterGaps; //!< The gaps introduced by avoidOverlappingPerimeters which would otherwise be overlapping perimeters.
|
||||
};
|
||||
/*!
|
||||
The SliceLayerPart is a single enclosed printable area for a single layer. (Also known as islands)
|
||||
@@ -34,10 +34,11 @@ class SliceLayerPart
|
||||
public:
|
||||
AABB boundaryBox; //!< The boundaryBox is an axis-aligned bounardy box which is used to quickly check for possible collision between different parts on different layers. It's an optimalization used during skin calculations.
|
||||
PolygonsPart outline; //!< The outline is the first member that is filled, and it's filled with polygons that match a cross section of the 3D model. The first polygon is the outer boundary polygon and the rest are holes.
|
||||
Polygons print_outline; //!< An approximation to the outline of what's actually printed, based on the outer wall. Too small parts will be omitted compared to the outline.
|
||||
std::vector<Polygons> insets; //!< The insets are generated with: an offset of (index * line_width + line_width/2) compared to the outline. The insets are also known as perimeters, and printed inside out.
|
||||
std::vector<SkinPart> skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets.
|
||||
std::vector<Polygons> infill_area; //!< The infill_area are the areas which need to be filled with sparse (0-99%) infill. The infill_area is an array to support thicker layers of sparse infill. infill_area[n] is infill_area of (n+1) layers thick.
|
||||
Polygons perimeterGaps; //!< The gaps introduced by avoidOverlappingPerimeters which would otherwise be overlapping perimeters.
|
||||
Polygons infill_area; //!< The areas which need to be filled with sparse (0-99%) infill. Like SliceLayerPart::outline, this class member is not used to actually determine the feature area, but is used to compute the infill_area_per_combine and the inside comb boundary.
|
||||
std::vector<Polygons> infill_area_per_combine; //!< The areas which need to be filled with sparse (0-99%) infill for different thicknesses. The infill_area is an array to support thicker layers of sparse infill. infill_area[n] is infill_area of (n+1) layers thick.
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -58,7 +59,7 @@ public:
|
||||
* \param external_polys_only Whether to only include the outermost outline of each layer part
|
||||
* \return A collection of all the outline polygons
|
||||
*/
|
||||
Polygons getOutlines(bool external_polys_only = false);
|
||||
Polygons getOutlines(bool external_polys_only = false) const;
|
||||
|
||||
/*!
|
||||
* Get the all outlines of all layer parts in this layer.
|
||||
@@ -67,20 +68,20 @@ public:
|
||||
* \param external_polys_only Whether to only include the outermost outline of each layer part
|
||||
* \param result The result: a collection of all the outline polygons
|
||||
*/
|
||||
void getOutlines(Polygons& result, bool external_polys_only = false);
|
||||
void getOutlines(Polygons& result, bool external_polys_only = false) const;
|
||||
|
||||
/*!
|
||||
* Collects the second wall of every part, or the outer wall if it has no second, or the outline, if it has no outer wall.
|
||||
* \return The collection of all polygons thus obtained
|
||||
*/
|
||||
Polygons getSecondOrInnermostWalls();
|
||||
Polygons getSecondOrInnermostWalls() const;
|
||||
|
||||
/*!
|
||||
* Collects the second wall of every part, or the outer wall if it has no second, or the outline, if it has no outer wall.
|
||||
* Add those polygons to @p result.
|
||||
* \param result The result: the collection of all polygons thus obtained
|
||||
*/
|
||||
void getSecondOrInnermostWalls(Polygons& result);
|
||||
void getSecondOrInnermostWalls(Polygons& result) const;
|
||||
};
|
||||
|
||||
/******************/
|
||||
@@ -136,9 +137,9 @@ public:
|
||||
std::vector<SliceMeshStorage> meshes;
|
||||
|
||||
std::vector<RetractionConfig> retraction_config_per_extruder; //!< used for support, skirt, etc.
|
||||
RetractionConfig retraction_config; //!< The retraction config used as fallback when getting the per_extruder_config or the mesh config was impossible (for travelConfig)
|
||||
|
||||
GCodePathConfig travel_config; //!< The config used for travel moves (only the speed and retraction config are set!)
|
||||
|
||||
std::vector<GCodePathConfig> travel_config_per_extruder; //!< The config used for travel moves (only speed is set!)
|
||||
|
||||
std::vector<GCodePathConfig> skirt_config; //!< config for skirt per extruder
|
||||
std::vector<CoastingConfig> coasting_config; //!< coasting config per extruder
|
||||
|
||||
@@ -161,23 +162,19 @@ public:
|
||||
Polygons draft_protection_shield; //!< The polygons for a heightened skirt which protects from warping by gusts of wind and acts as a heated chamber.
|
||||
Point wipePoint;
|
||||
|
||||
std::vector<RetractionConfig> initializeRetractionConfigs()
|
||||
{
|
||||
std::vector<RetractionConfig> ret;
|
||||
ret.resize(meshgroup->getExtruderCount()); // initializes with constructor RetractionConfig()
|
||||
return ret;
|
||||
}
|
||||
std::vector<GCodePathConfig> initializeSkirtConfigs()
|
||||
{
|
||||
std::vector<GCodePathConfig> ret;
|
||||
for (int extruder = 0; extruder < meshgroup->getExtruderCount(); extruder++)
|
||||
{
|
||||
RetractionConfig* extruder_retraction_config = &retraction_config_per_extruder[extruder];
|
||||
skirt_config.emplace_back(extruder_retraction_config, PrintFeatureType::Skirt);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct the initial retraction_config_per_extruder
|
||||
*/
|
||||
std::vector<RetractionConfig> initializeRetractionConfigs();
|
||||
/*!
|
||||
* Construct the initial travel_config_per_extruder
|
||||
*/
|
||||
std::vector<GCodePathConfig> initializeTravelConfigs();
|
||||
/*!
|
||||
* Construct the initial skirt_config s for each extruder
|
||||
*/
|
||||
std::vector<GCodePathConfig> initializeSkirtConfigs();
|
||||
|
||||
/*!
|
||||
* \brief Creates a new slice data storage that stores the slice data of the
|
||||
* specified mesh group.
|
||||
@@ -201,7 +198,7 @@ public:
|
||||
* \param include_helper_parts whether to include support and prime tower
|
||||
* \param external_polys_only whether to disregard all hole polygons
|
||||
*/
|
||||
Polygons getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only = false);
|
||||
Polygons getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only = false) const;
|
||||
|
||||
/*!
|
||||
* Collects the second wall of every part, or the outer wall if it has no second, or the outline, if it has no outer wall.
|
||||
@@ -211,7 +208,7 @@ public:
|
||||
* \param layer_nr the index of the layer for which to get the outlines (negative layer numbers indicate the raft)
|
||||
* \param include_helper_parts whether to include support and prime tower
|
||||
*/
|
||||
Polygons getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts);
|
||||
Polygons getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts) const;
|
||||
|
||||
/*!
|
||||
* Get the extruder numbers of all extruders used in a given layer.
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm> // remove_if
|
||||
|
||||
#include "utils/gettime.h"
|
||||
#include "utils/logoutput.h"
|
||||
|
||||
#include "slicer.h"
|
||||
#include "debug.h" // TODO remove
|
||||
|
||||
|
||||
namespace cura {
|
||||
|
||||
int largest_neglected_gap_first_phase = MM2INT(0.01); //!< distance between two line segments regarded as connected
|
||||
int largest_neglected_gap_second_phase = MM2INT(0.02); //!< distance between two line segments regarded as connected
|
||||
int max_stitch1 = MM2INT(10.0); //!< maximal distance stitched between open polylines to form polygons
|
||||
|
||||
void SlicerLayer::makeBasicPolygonLoops(const Mesh* mesh, Polygons& open_polylines)
|
||||
{
|
||||
for(unsigned int start_segment_idx = 0; start_segment_idx < segments.size(); start_segment_idx++)
|
||||
{
|
||||
if (!segments[start_segment_idx].addedToPolygon)
|
||||
{
|
||||
makeBasicPolygonLoop(mesh, open_polylines, start_segment_idx);
|
||||
}
|
||||
}
|
||||
//Clear the segmentList to save memory, it is no longer needed after this point.
|
||||
segments.clear();
|
||||
}
|
||||
|
||||
void SlicerLayer::makeBasicPolygonLoop(const Mesh* mesh, Polygons& open_polylines, unsigned int start_segment_idx)
|
||||
{
|
||||
|
||||
Polygon poly;
|
||||
poly.add(segments[start_segment_idx].start);
|
||||
|
||||
for (int segment_idx = start_segment_idx; segment_idx != -1; )
|
||||
{
|
||||
SlicerSegment& segment = segments[segment_idx];
|
||||
poly.add(segment.end);
|
||||
segment.addedToPolygon = true;
|
||||
segment_idx = getNextSegmentIdx(mesh, segment, start_segment_idx);
|
||||
if (segment_idx == static_cast<int>(start_segment_idx))
|
||||
{ // polyon is closed
|
||||
polygons.add(poly);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// polygon couldn't be closed
|
||||
open_polylines.add(poly);
|
||||
}
|
||||
|
||||
int SlicerLayer::getNextSegmentIdx(const Mesh* mesh, const SlicerSegment& segment, unsigned int start_segment_idx)
|
||||
{
|
||||
int next_segment_idx = -1;
|
||||
const MeshFace& face = mesh->faces[segment.faceIndex];
|
||||
for (unsigned int face_edge_idx = 0; face_edge_idx < 3; face_edge_idx++)
|
||||
{ // check segments in connected faces
|
||||
decltype(face_idx_to_segment_idx.begin()) it;
|
||||
if (face.connected_face_index[face_edge_idx] > -1 && (it = face_idx_to_segment_idx.find(face.connected_face_index[face_edge_idx])) != face_idx_to_segment_idx.end())
|
||||
{
|
||||
int segment_idx = (*it).second;
|
||||
Point p1 = segments[segment_idx].start;
|
||||
Point diff = segment.end - p1;
|
||||
if (shorterThen(diff, largest_neglected_gap_first_phase))
|
||||
{
|
||||
if (segment_idx == static_cast<int>(start_segment_idx))
|
||||
{
|
||||
return start_segment_idx;
|
||||
}
|
||||
if (segments[segment_idx].addedToPolygon)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
next_segment_idx = segment_idx; // not immediately returned since we might still encounter the start_segment_idx
|
||||
}
|
||||
}
|
||||
}
|
||||
return next_segment_idx;
|
||||
}
|
||||
|
||||
void SlicerLayer::connectOpenPolylines(Polygons& open_polylines)
|
||||
{
|
||||
// TODO use some space partitioning data structure to make this run faster than O(n^2)
|
||||
for(unsigned int open_polyline_idx = 0; open_polyline_idx < open_polylines.size(); open_polyline_idx++)
|
||||
{
|
||||
PolygonRef open_polyline = open_polylines[open_polyline_idx];
|
||||
|
||||
if (open_polyline.size() < 1) continue;
|
||||
for(unsigned int open_polyline_other_idx = 0; open_polyline_other_idx < open_polylines.size(); open_polyline_other_idx++)
|
||||
{
|
||||
PolygonRef open_polyline_other = open_polylines[open_polyline_other_idx];
|
||||
|
||||
if (open_polyline_other.size() < 1) continue;
|
||||
|
||||
Point diff = open_polyline.back() - open_polyline_other[0];
|
||||
|
||||
if (shorterThen(diff, largest_neglected_gap_second_phase))
|
||||
{
|
||||
if (open_polyline_idx == open_polyline_other_idx)
|
||||
{
|
||||
polygons.add(open_polyline);
|
||||
open_polyline.clear();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned int line_idx = 0; line_idx < open_polyline_other.size(); line_idx++)
|
||||
{
|
||||
open_polyline.add(open_polyline_other[line_idx]);
|
||||
}
|
||||
open_polyline_other.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SlicerLayer::stitch(Polygons& open_polylines)
|
||||
{ // TODO This is an inefficient implementation which can run in O(n^3) time.
|
||||
// below code closes smallest gaps first
|
||||
while(1)
|
||||
{
|
||||
int64_t best_dist2 = max_stitch1 * max_stitch1;
|
||||
unsigned int best_polyline_1_idx = -1;
|
||||
unsigned int best_polyline_2_idx = -1;
|
||||
bool reversed = false;
|
||||
for(unsigned int polyline_1_idx = 0; polyline_1_idx < open_polylines.size(); polyline_1_idx++)
|
||||
{
|
||||
PolygonRef polyline_1 = open_polylines[polyline_1_idx];
|
||||
|
||||
if (polyline_1.size() < 1) continue;
|
||||
for(unsigned int polyline_2_idx = 0; polyline_2_idx < open_polylines.size(); polyline_2_idx++)
|
||||
{
|
||||
PolygonRef polyline_2 = open_polylines[polyline_2_idx];
|
||||
|
||||
if (polyline_2.size() < 1) continue;
|
||||
|
||||
Point diff = polyline_1.back() - polyline_2[0];
|
||||
int64_t dist2 = vSize2(diff);
|
||||
if (dist2 < best_dist2)
|
||||
{
|
||||
best_dist2 = dist2;
|
||||
best_polyline_1_idx = polyline_1_idx;
|
||||
best_polyline_2_idx = polyline_2_idx;
|
||||
reversed = false;
|
||||
}
|
||||
|
||||
if (polyline_1_idx != polyline_2_idx)
|
||||
{
|
||||
Point diff = polyline_1.back() - polyline_2.back();
|
||||
int64_t dist2 = vSize2(diff);
|
||||
if (dist2 < best_dist2)
|
||||
{
|
||||
best_dist2 = dist2;
|
||||
best_polyline_1_idx = polyline_1_idx;
|
||||
best_polyline_2_idx = polyline_2_idx;
|
||||
reversed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_dist2 >= max_stitch1 * max_stitch1)
|
||||
break; // this code is reached if there was nothing to stitch within the distance limits
|
||||
|
||||
PolygonRef polyline_1 = open_polylines[best_polyline_1_idx];
|
||||
PolygonRef polyline_2 = open_polylines[best_polyline_2_idx];
|
||||
|
||||
if (best_polyline_1_idx == best_polyline_2_idx)
|
||||
{ // connect last piece of 'circle'
|
||||
polygons.add(polyline_1);
|
||||
polyline_1.clear();
|
||||
}
|
||||
else
|
||||
{ // connect two polylines
|
||||
if (reversed)
|
||||
{
|
||||
if (polyline_1.size() > polyline_2.size()) // decide which polygon to copy into the other
|
||||
{
|
||||
for(int poly_idx = polyline_2.size()-1; poly_idx >= 0; poly_idx--)
|
||||
polyline_1.add(polyline_2[poly_idx]);
|
||||
polyline_2.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int poly_idx = polyline_1.size()-1; poly_idx >= 0; poly_idx--)
|
||||
polyline_2.add(polyline_1[poly_idx]);
|
||||
polyline_1.clear();
|
||||
}
|
||||
// note that either way we end up with the end of former polyline_1 next to the start of former polyline_2
|
||||
}
|
||||
else
|
||||
{
|
||||
for(Point& p : polyline_2)
|
||||
polyline_1.add(p);
|
||||
polyline_2.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SlicerLayer::stitch_extensive(Polygons& open_polylines)
|
||||
{
|
||||
//For extensive stitching find 2 open polygons that are touching 2 closed polygons.
|
||||
// Then find the shortest path over this polygon that can be used to connect the open polygons,
|
||||
// And generate a path over this shortest bit to link up the 2 open polygons.
|
||||
// (If these 2 open polygons are the same polygon, then the final result is a closed polyon)
|
||||
|
||||
while(1)
|
||||
{
|
||||
unsigned int best_polyline_1_idx = -1;
|
||||
unsigned int best_polyline_2_idx = -1;
|
||||
GapCloserResult best_result;
|
||||
best_result.len = POINT_MAX;
|
||||
best_result.polygonIdx = -1;
|
||||
best_result.pointIdxA = -1;
|
||||
best_result.pointIdxB = -1;
|
||||
|
||||
for(unsigned int polyline_1_idx = 0; polyline_1_idx < open_polylines.size(); polyline_1_idx++)
|
||||
{
|
||||
PolygonRef polyline_1 = open_polylines[polyline_1_idx];
|
||||
if (polyline_1.size() < 1) continue;
|
||||
|
||||
{
|
||||
GapCloserResult res = findPolygonGapCloser(polyline_1[0], polyline_1.back());
|
||||
if (res.len > 0 && res.len < best_result.len)
|
||||
{
|
||||
best_polyline_1_idx = polyline_1_idx;
|
||||
best_polyline_2_idx = polyline_1_idx;
|
||||
best_result = res;
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned int polyline_2_idx = 0; polyline_2_idx < open_polylines.size(); polyline_2_idx++)
|
||||
{
|
||||
PolygonRef polyline_2 = open_polylines[polyline_2_idx];
|
||||
if (polyline_2.size() < 1 || polyline_1_idx == polyline_2_idx) continue;
|
||||
|
||||
GapCloserResult res = findPolygonGapCloser(polyline_1[0], polyline_2.back());
|
||||
if (res.len > 0 && res.len < best_result.len)
|
||||
{
|
||||
best_polyline_1_idx = polyline_1_idx;
|
||||
best_polyline_2_idx = polyline_2_idx;
|
||||
best_result = res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_result.len < POINT_MAX)
|
||||
{
|
||||
if (best_polyline_1_idx == best_polyline_2_idx)
|
||||
{
|
||||
if (best_result.pointIdxA == best_result.pointIdxB)
|
||||
{
|
||||
polygons.add(open_polylines[best_polyline_1_idx]);
|
||||
open_polylines[best_polyline_1_idx].clear();
|
||||
}
|
||||
else if (best_result.AtoB)
|
||||
{
|
||||
PolygonRef poly = polygons.newPoly();
|
||||
for(unsigned int j = best_result.pointIdxA; j != best_result.pointIdxB; j = (j + 1) % polygons[best_result.polygonIdx].size())
|
||||
poly.add(polygons[best_result.polygonIdx][j]);
|
||||
for(unsigned int j = open_polylines[best_polyline_1_idx].size() - 1; int(j) >= 0; j--)
|
||||
poly.add(open_polylines[best_polyline_1_idx][j]);
|
||||
open_polylines[best_polyline_1_idx].clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned int n = polygons.size();
|
||||
polygons.add(open_polylines[best_polyline_1_idx]);
|
||||
for(unsigned int j = best_result.pointIdxB; j != best_result.pointIdxA; j = (j + 1) % polygons[best_result.polygonIdx].size())
|
||||
polygons[n].add(polygons[best_result.polygonIdx][j]);
|
||||
open_polylines[best_polyline_1_idx].clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (best_result.pointIdxA == best_result.pointIdxB)
|
||||
{
|
||||
for(unsigned int n=0; n<open_polylines[best_polyline_1_idx].size(); n++)
|
||||
open_polylines[best_polyline_2_idx].add(open_polylines[best_polyline_1_idx][n]);
|
||||
open_polylines[best_polyline_1_idx].clear();
|
||||
}
|
||||
else if (best_result.AtoB)
|
||||
{
|
||||
Polygon poly;
|
||||
for(unsigned int n = best_result.pointIdxA; n != best_result.pointIdxB; n = (n + 1) % polygons[best_result.polygonIdx].size())
|
||||
poly.add(polygons[best_result.polygonIdx][n]);
|
||||
for(unsigned int n=poly.size()-1;int(n) >= 0; n--)
|
||||
open_polylines[best_polyline_2_idx].add(poly[n]);
|
||||
for(unsigned int n=0; n<open_polylines[best_polyline_1_idx].size(); n++)
|
||||
open_polylines[best_polyline_2_idx].add(open_polylines[best_polyline_1_idx][n]);
|
||||
open_polylines[best_polyline_1_idx].clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
for(unsigned int n = best_result.pointIdxB; n != best_result.pointIdxA; n = (n + 1) % polygons[best_result.polygonIdx].size())
|
||||
open_polylines[best_polyline_2_idx].add(polygons[best_result.polygonIdx][n]);
|
||||
for(unsigned int n = open_polylines[best_polyline_1_idx].size() - 1; int(n) >= 0; n--)
|
||||
open_polylines[best_polyline_2_idx].add(open_polylines[best_polyline_1_idx][n]);
|
||||
open_polylines[best_polyline_1_idx].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GapCloserResult SlicerLayer::findPolygonGapCloser(Point ip0, Point ip1)
|
||||
{
|
||||
GapCloserResult ret;
|
||||
ClosePolygonResult c1 = findPolygonPointClosestTo(ip0);
|
||||
ClosePolygonResult c2 = findPolygonPointClosestTo(ip1);
|
||||
if (c1.polygonIdx < 0 || c1.polygonIdx != c2.polygonIdx)
|
||||
{
|
||||
ret.len = -1;
|
||||
return ret;
|
||||
}
|
||||
ret.polygonIdx = c1.polygonIdx;
|
||||
ret.pointIdxA = c1.pointIdx;
|
||||
ret.pointIdxB = c2.pointIdx;
|
||||
ret.AtoB = true;
|
||||
|
||||
if (ret.pointIdxA == ret.pointIdxB)
|
||||
{
|
||||
//Connection points are on the same line segment.
|
||||
ret.len = vSize(ip0 - ip1);
|
||||
}else{
|
||||
//Find out if we have should go from A to B or the other way around.
|
||||
Point p0 = polygons[ret.polygonIdx][ret.pointIdxA];
|
||||
int64_t lenA = vSize(p0 - ip0);
|
||||
for(unsigned int i = ret.pointIdxA; i != ret.pointIdxB; i = (i + 1) % polygons[ret.polygonIdx].size())
|
||||
{
|
||||
Point p1 = polygons[ret.polygonIdx][i];
|
||||
lenA += vSize(p0 - p1);
|
||||
p0 = p1;
|
||||
}
|
||||
lenA += vSize(p0 - ip1);
|
||||
|
||||
p0 = polygons[ret.polygonIdx][ret.pointIdxB];
|
||||
int64_t lenB = vSize(p0 - ip1);
|
||||
for(unsigned int i = ret.pointIdxB; i != ret.pointIdxA; i = (i + 1) % polygons[ret.polygonIdx].size())
|
||||
{
|
||||
Point p1 = polygons[ret.polygonIdx][i];
|
||||
lenB += vSize(p0 - p1);
|
||||
p0 = p1;
|
||||
}
|
||||
lenB += vSize(p0 - ip0);
|
||||
|
||||
if (lenA < lenB)
|
||||
{
|
||||
ret.AtoB = true;
|
||||
ret.len = lenA;
|
||||
}else{
|
||||
ret.AtoB = false;
|
||||
ret.len = lenB;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ClosePolygonResult SlicerLayer::findPolygonPointClosestTo(Point input)
|
||||
{
|
||||
ClosePolygonResult ret;
|
||||
for(unsigned int n=0; n<polygons.size(); n++)
|
||||
{
|
||||
Point p0 = polygons[n][polygons[n].size()-1];
|
||||
for(unsigned int i=0; i<polygons[n].size(); i++)
|
||||
{
|
||||
Point p1 = polygons[n][i];
|
||||
|
||||
//Q = A + Normal( B - A ) * ((( B - A ) dot ( P - A )) / VSize( A - B ));
|
||||
Point pDiff = p1 - p0;
|
||||
int64_t lineLength = vSize(pDiff);
|
||||
if (lineLength > 1)
|
||||
{
|
||||
int64_t distOnLine = dot(pDiff, input - p0) / lineLength;
|
||||
if (distOnLine >= 0 && distOnLine <= lineLength)
|
||||
{
|
||||
Point q = p0 + pDiff * distOnLine / lineLength;
|
||||
if (shorterThen(q - input, 100))
|
||||
{
|
||||
ret.intersectionPoint = q;
|
||||
ret.polygonIdx = n;
|
||||
ret.pointIdx = i;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
ret.polygonIdx = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SlicerLayer::makePolygons(const Mesh* mesh, bool keep_none_closed, bool extensive_stitching)
|
||||
{
|
||||
Polygons open_polylines;
|
||||
|
||||
makeBasicPolygonLoops(mesh, open_polylines);
|
||||
|
||||
connectOpenPolylines(open_polylines);
|
||||
|
||||
// TODO: (?) for mesh surface mode: connect open polygons. Maybe the above algorithm can create two open polygons which are actually connected when the starting segment is in the middle between the two open polygons.
|
||||
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::NORMAL)
|
||||
{ // don't stitch when using (any) mesh surface mode, i.e. also don't stitch when using mixed mesh surface and closed polygons, because then polylines which are supposed to be open will be closed
|
||||
stitch(open_polylines);
|
||||
}
|
||||
|
||||
if (extensive_stitching)
|
||||
{
|
||||
stitch_extensive(open_polylines);
|
||||
}
|
||||
|
||||
if (keep_none_closed)
|
||||
{
|
||||
for (PolygonRef polyline : open_polylines)
|
||||
{
|
||||
if (polyline.size() > 0)
|
||||
openPolylines.add(polyline);
|
||||
}
|
||||
}
|
||||
|
||||
for (PolygonRef polyline : open_polylines)
|
||||
{
|
||||
if (polyline.size() > 0)
|
||||
openPolylines.add(polyline);
|
||||
}
|
||||
|
||||
//Remove all the tiny polygons, or polygons that are not closed. As they do not contribute to the actual print.
|
||||
int snapDistance = MM2INT(1.0); // TODO: hardcoded value
|
||||
auto it = std::remove_if(polygons.begin(), polygons.end(), [snapDistance](PolygonRef poly) { return poly.shorterThan(snapDistance); });
|
||||
polygons.erase(it, polygons.end());
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
polygons.simplify();
|
||||
|
||||
polygons.removeDegenerateVerts(); // remove verts connected to overlapping line segments
|
||||
|
||||
int xy_offset = mesh->getSettingInMicrons("xy_offset");
|
||||
if (xy_offset != 0)
|
||||
{
|
||||
polygons = polygons.offset(xy_offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool keep_none_closed, bool extensive_stitching)
|
||||
: mesh(mesh)
|
||||
{
|
||||
assert(layer_count > 0);
|
||||
|
||||
layers.resize(layer_count);
|
||||
|
||||
for(int32_t layer_nr = 0; layer_nr < layer_count; layer_nr++)
|
||||
{
|
||||
layers[layer_nr].z = initial + thickness * layer_nr;
|
||||
}
|
||||
|
||||
for(unsigned int mesh_idx = 0; mesh_idx < mesh->faces.size(); mesh_idx++)
|
||||
{
|
||||
MeshFace& face = mesh->faces[mesh_idx];
|
||||
Point3 p0 = mesh->vertices[face.vertex_index[0]].p;
|
||||
Point3 p1 = mesh->vertices[face.vertex_index[1]].p;
|
||||
Point3 p2 = mesh->vertices[face.vertex_index[2]].p;
|
||||
int32_t minZ = p0.z;
|
||||
int32_t maxZ = p0.z;
|
||||
if (p1.z < minZ) minZ = p1.z;
|
||||
if (p2.z < minZ) minZ = p2.z;
|
||||
if (p1.z > maxZ) maxZ = p1.z;
|
||||
if (p2.z > maxZ) maxZ = p2.z;
|
||||
int32_t layer_max = (maxZ - initial) / thickness;
|
||||
for(int32_t layer_nr = (minZ - initial) / thickness; layer_nr <= layer_max; layer_nr++)
|
||||
{
|
||||
int32_t z = layer_nr * thickness + initial;
|
||||
if (z < minZ) continue;
|
||||
if (layer_nr < 0) continue;
|
||||
|
||||
SlicerSegment s;
|
||||
if (p0.z < z && p1.z >= z && p2.z >= z)
|
||||
s = project2D(p0, p2, p1, z);
|
||||
else if (p0.z > z && p1.z < z && p2.z < z)
|
||||
s = project2D(p0, p1, p2, z);
|
||||
|
||||
else if (p1.z < z && p0.z >= z && p2.z >= z)
|
||||
s = project2D(p1, p0, p2, z);
|
||||
else if (p1.z > z && p0.z < z && p2.z < z)
|
||||
s = project2D(p1, p2, p0, z);
|
||||
|
||||
else if (p2.z < z && p1.z >= z && p0.z >= z)
|
||||
s = project2D(p2, p1, p0, z);
|
||||
else if (p2.z > z && p1.z < z && p0.z < z)
|
||||
s = project2D(p2, p0, p1, z);
|
||||
else
|
||||
{
|
||||
//Not all cases create a segment, because a point of a face could create just a dot, and two touching faces
|
||||
// on the slice would create two segments
|
||||
continue;
|
||||
}
|
||||
layers[layer_nr].face_idx_to_segment_idx.insert(std::make_pair(mesh_idx, layers[layer_nr].segments.size()));
|
||||
s.faceIndex = mesh_idx;
|
||||
s.addedToPolygon = false;
|
||||
layers[layer_nr].segments.push_back(s);
|
||||
}
|
||||
}
|
||||
for(unsigned int layer_nr=0; layer_nr<layers.size(); layer_nr++)
|
||||
{
|
||||
layers[layer_nr].makePolygons(mesh, keep_none_closed, extensive_stitching);
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_H
|
||||
#define SLICER_H
|
||||
|
||||
#include "mesh.h"
|
||||
#include "utils/polygon.h"
|
||||
/*
|
||||
The Slicer creates layers of polygons from an optimized 3D model.
|
||||
The result of the Slicer is a list of polygons without any order or structure.
|
||||
*/
|
||||
namespace cura {
|
||||
|
||||
class SlicerSegment
|
||||
{
|
||||
public:
|
||||
Point start, end;
|
||||
int faceIndex;
|
||||
bool addedToPolygon;
|
||||
};
|
||||
|
||||
class ClosePolygonResult
|
||||
{ //The result of trying to find a point on a closed polygon line. This gives back the point index, the polygon index, and the point of the connection.
|
||||
//The line on which the point lays is between pointIdx-1 and pointIdx
|
||||
public:
|
||||
Point intersectionPoint;
|
||||
int polygonIdx;
|
||||
unsigned int pointIdx;
|
||||
};
|
||||
class GapCloserResult
|
||||
{
|
||||
public:
|
||||
int64_t len;
|
||||
int polygonIdx;
|
||||
unsigned int pointIdxA;
|
||||
unsigned int pointIdxB;
|
||||
bool AtoB;
|
||||
};
|
||||
|
||||
class SlicerLayer
|
||||
{
|
||||
public:
|
||||
std::vector<SlicerSegment> segments;
|
||||
std::unordered_map<int, int> face_idx_to_segment_idx; // topology
|
||||
|
||||
int z;
|
||||
Polygons polygons;
|
||||
Polygons openPolylines;
|
||||
|
||||
/*!
|
||||
* Connect the segments into polygons for this layer of this \p mesh
|
||||
*
|
||||
* \param[in] mesh The mesh data for which we are connecting sliced segments (The face data is used)
|
||||
* \param keepNoneClosed Whether to throw away the data for segments which we couldn't stitch into a polygon
|
||||
* \param extensiveStitching Whether to perform extra work to try and close polylines into polygons when there are large gaps
|
||||
*/
|
||||
void makePolygons(const Mesh* mesh, bool keepNoneClosed, bool extensiveStitching);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Connect the segments into loops which correctly form polygons (don't perform stitching here)
|
||||
*
|
||||
* \param[in] mesh The mesh data for which we are connecting sliced segments (The face data is used)
|
||||
* \param[out] open_polylines The polylines which are stiched, but couldn't be closed into a loop
|
||||
*/
|
||||
void makeBasicPolygonLoops(const Mesh* mesh, Polygons& open_polylines);
|
||||
|
||||
/*!
|
||||
* Connect the segments into a loop, starting from the segment with index \p start_segment_idx
|
||||
*
|
||||
* \param[in] mesh The mesh data for which we are connecting sliced segments (The face data is used)
|
||||
* \param[out] open_polylines The polylines which are stiched, but couldn't be closed into a loop
|
||||
* \param[in] start_segment_idx The index into SlicerLayer::segments for the first segment from which to start the polygon loop
|
||||
*/
|
||||
void makeBasicPolygonLoop(const Mesh* mesh, Polygons& open_polylines, unsigned int start_segment_idx);
|
||||
|
||||
/*!
|
||||
* Get the next segment connected to the end of \p segment.
|
||||
* Used to make closed polygon loops.
|
||||
* Return ASAP if segment is (also) connected to SlicerLayer::segments[\p start_segment_idx]
|
||||
*
|
||||
* \param[in] mesh The mesh data for which we are connecting sliced segments (The face data is used)
|
||||
* \param[in] segment The segment from which to start looking for the next
|
||||
* \param[in] start_segment_idx The index to the segment which when conected to \p segment will immediately stop looking for further candidates.
|
||||
*/
|
||||
int getNextSegmentIdx(const Mesh* mesh, const SlicerSegment& segment, unsigned int start_segment_idx);
|
||||
|
||||
/*!
|
||||
* Connecting polygons that are not closed yet, as models are not always perfect manifold we need to join some stuff up to get proper polygons.
|
||||
* First link up polygon ends that are within 2 microns.
|
||||
*
|
||||
* Clears all open polylines which are used up in the process
|
||||
*
|
||||
* \param[in,out] open_polylines The polylines which are stiched, but couldn't be closed into a loop
|
||||
*/
|
||||
void connectOpenPolylines(Polygons& open_polylines);
|
||||
|
||||
/*!
|
||||
* Link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time.
|
||||
*
|
||||
* Clears all open polylines which are used up in the process
|
||||
*
|
||||
* \param[in,out] open_polylines The polylines which are stiched, but couldn't be closed into a loop yet
|
||||
*/
|
||||
void stitch(Polygons& open_polylines);
|
||||
|
||||
GapCloserResult findPolygonGapCloser(Point ip0, Point ip1);
|
||||
|
||||
ClosePolygonResult findPolygonPointClosestTo(Point input);
|
||||
|
||||
/*!
|
||||
* Try to close up polylines into polygons while they have large gaps in them.
|
||||
*
|
||||
* Clears all open polylines which are used up in the process
|
||||
*
|
||||
* \param[in,out] open_polylines The polylines which are stiched, but couldn't be closed into a loop yet
|
||||
*/
|
||||
void stitch_extensive(Polygons& open_polylines);
|
||||
};
|
||||
|
||||
class Slicer
|
||||
{
|
||||
public:
|
||||
std::vector<SlicerLayer> layers;
|
||||
|
||||
const Mesh* mesh; //!< The sliced mesh
|
||||
|
||||
Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool keepNoneClosed, bool extensiveStitching);
|
||||
|
||||
SlicerSegment project2D(Point3& p0, Point3& p1, Point3& p2, int32_t z) const
|
||||
{
|
||||
SlicerSegment seg;
|
||||
seg.start.X = p0.x + int64_t(p1.x - p0.x) * int64_t(z - p0.z) / int64_t(p1.z - p0.z);
|
||||
seg.start.Y = p0.y + int64_t(p1.y - p0.y) * int64_t(z - p0.z) / int64_t(p1.z - p0.z);
|
||||
seg.end.X = p0.x + int64_t(p2.x - p0.x) * int64_t(z - p0.z) / int64_t(p2.z - p0.z);
|
||||
seg.end.Y = p0.y + int64_t(p2.y - p0.y) * int64_t(z - p0.z) / int64_t(p2.z - p0.z);
|
||||
return seg;
|
||||
}
|
||||
|
||||
void dumpSegmentsToHTML(const char* filename);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//SLICER_H
|
||||
@@ -1,21 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_CLOSE_POLYGON_RESULT_H
|
||||
#define SLICER_CLOSE_POLYGON_RESULT_H
|
||||
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class ClosePolygonResult
|
||||
{ //The result of trying to find a point on a closed polygon line. This gives back the point index, the polygon index, and the point of the connection.
|
||||
//The line on which the point lays is between pointIdx-1 and pointIdx
|
||||
public:
|
||||
Point intersectionPoint;
|
||||
int polygonIdx;
|
||||
unsigned int pointIdx;
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // SLICER_CLOSE_POLYGON_RESULT_H
|
||||
@@ -1,22 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_GAP_CLOSER_RESULT_H
|
||||
#define SLICER_GAP_CLOSER_RESULT_H
|
||||
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class GapCloserResult
|
||||
{
|
||||
public:
|
||||
int64_t len;
|
||||
int polygonIdx;
|
||||
unsigned int pointIdxA;
|
||||
unsigned int pointIdxB;
|
||||
bool AtoB;
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // SLICER_GAP_CLOSER_RESULT_H
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "MultiVolumes.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
void carveMultipleVolumes(std::vector<Slicer*> &volumes)
|
||||
{
|
||||
//Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas.
|
||||
for(unsigned int idx=0; idx < volumes.size(); idx++)
|
||||
{
|
||||
for(unsigned int idx2=0; idx2<idx; idx2++)
|
||||
{
|
||||
for(unsigned int layerNr=0; layerNr < volumes[idx]->layers.size(); layerNr++)
|
||||
{
|
||||
SlicerLayer& layer1 = volumes[idx]->layers[layerNr];
|
||||
SlicerLayer& layer2 = volumes[idx2]->layers[layerNr];
|
||||
layer1.polygonList = layer1.polygonList.difference(layer2.polygonList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes.
|
||||
//This generates some overlap in dual extrusion, for better bonding in touching parts.
|
||||
void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes, int overlap)
|
||||
{
|
||||
if (volumes.size() < 2 || overlap <= 0) return;
|
||||
|
||||
for(unsigned int layerNr=0; layerNr < volumes[0]->layers.size(); layerNr++)
|
||||
{
|
||||
Polygons fullLayer;
|
||||
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
|
||||
{
|
||||
SlicerLayer& layer1 = volumes[volIdx]->layers[layerNr];
|
||||
fullLayer = fullLayer.unionPolygons(layer1.polygonList.offset(20)); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
|
||||
}
|
||||
fullLayer = fullLayer.offset(-20); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?)
|
||||
|
||||
for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++)
|
||||
{
|
||||
SlicerLayer& layer1 = volumes[volIdx]->layers[layerNr];
|
||||
layer1.polygonList = fullLayer.intersection(layer1.polygonList.offset(overlap / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,133 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../utils/gettime.h"
|
||||
#include "../utils/logoutput.h"
|
||||
#include "../MatCoord.h"
|
||||
|
||||
#include "Slicer.h"
|
||||
|
||||
namespace cura {
|
||||
|
||||
|
||||
SlicerSegment Slicer::project2D(unsigned int face_idx, Point3 p[3], unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, int32_t layer_nr)
|
||||
{
|
||||
Point3& p0 = p[idx_shared];
|
||||
Point3& p1 = p[idx_first];
|
||||
Point3& p2 = p[idx_second];
|
||||
SlicerSegment seg;
|
||||
seg.start.X = p0.x + int64_t(p1.x - p0.x) * int64_t(z - p0.z) / int64_t(p1.z - p0.z);
|
||||
seg.start.Y = p0.y + int64_t(p1.y - p0.y) * int64_t(z - p0.z) / int64_t(p1.z - p0.z);
|
||||
seg.end.X = p0.x + int64_t(p2.x - p0.x) * int64_t(z - p0.z) / int64_t(p2.z - p0.z);
|
||||
seg.end.Y = p0.y + int64_t(p2.y - p0.y) * int64_t(z - p0.z) / int64_t(p2.z - p0.z);
|
||||
MatSegment mat_segment;
|
||||
bool got_texture_coords = mesh->registerFaceSlice(face_idx, idx_shared, idx_first, idx_second, z, seg.start, seg.end, mat_segment);
|
||||
if (got_texture_coords)
|
||||
{
|
||||
SlicerLayer& layer = layers[layer_nr];
|
||||
layer.segment_to_material_segment.emplace(seg, mat_segment);
|
||||
}
|
||||
return seg;
|
||||
}
|
||||
|
||||
|
||||
Slicer::Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool keep_none_closed, bool extensive_stitching)
|
||||
: mesh(mesh)
|
||||
, layer_height_0(initial)
|
||||
, layer_height(thickness)
|
||||
{
|
||||
assert(layer_count > 0);
|
||||
|
||||
layers.resize(layer_count);
|
||||
|
||||
for(int32_t layer_nr = 0; layer_nr < layer_count; layer_nr++)
|
||||
{
|
||||
layers[layer_nr].z = initial + thickness * layer_nr;
|
||||
layers[layer_nr].layer_nr = layer_nr;
|
||||
}
|
||||
for (unsigned int face_idx = 0; face_idx < mesh->faces.size(); face_idx++)
|
||||
{
|
||||
MeshFace& face = mesh->faces[face_idx];
|
||||
Point3 p[3] =
|
||||
{ mesh->vertices[face.vertex_index[0]].p
|
||||
, mesh->vertices[face.vertex_index[1]].p
|
||||
, mesh->vertices[face.vertex_index[2]].p };
|
||||
Point3& p0 = p[0];
|
||||
Point3& p1 = p[1];
|
||||
Point3& p2 = p[2];
|
||||
int32_t minZ = p0.z;
|
||||
int32_t maxZ = p0.z;
|
||||
if (p1.z < minZ)
|
||||
{
|
||||
minZ = p1.z;
|
||||
}
|
||||
if (p2.z < minZ)
|
||||
{
|
||||
minZ = p2.z;
|
||||
}
|
||||
if (p1.z > maxZ)
|
||||
{
|
||||
maxZ = p1.z;
|
||||
}
|
||||
if (p2.z > maxZ)
|
||||
{
|
||||
maxZ = p2.z;
|
||||
}
|
||||
int32_t z = 0;
|
||||
for (int32_t layer_nr = (minZ - initial + thickness - 1) / thickness; z <= maxZ; layer_nr++) // + thickness - 1 to get the first layer above or at minZ
|
||||
{
|
||||
SlicerSegment s;
|
||||
|
||||
z = layer_nr * layer_height + layer_height_0;
|
||||
if (layer_nr < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// below code checks the position of the points w.r.t. the layer z
|
||||
// also the direction of the resulting sliced line is determined
|
||||
// p0 is odd one out
|
||||
if (p0.z < z && p1.z >= z && p2.z >= z)
|
||||
{
|
||||
s = project2D(face_idx, p, 0, 2, 1, z, layer_nr);
|
||||
}
|
||||
else if (p0.z > z && p1.z < z && p2.z < z)
|
||||
{
|
||||
s = project2D(face_idx, p, 0, 1, 2, z, layer_nr);
|
||||
}
|
||||
// p1 is odd one out
|
||||
else if (p1.z < z && p0.z >= z && p2.z >= z)
|
||||
{
|
||||
s = project2D(face_idx, p, 1, 0, 2, z, layer_nr);
|
||||
}
|
||||
else if (p1.z > z && p0.z < z && p2.z < z)
|
||||
{
|
||||
s = project2D(face_idx, p, 1, 2, 0, z, layer_nr);
|
||||
}
|
||||
// p2 is odd one out
|
||||
else if (p2.z < z && p1.z >= z && p0.z >= z)
|
||||
{
|
||||
s = project2D(face_idx, p, 2, 1, 0, z, layer_nr);
|
||||
}
|
||||
else if (p2.z > z && p1.z < z && p0.z < z)
|
||||
{
|
||||
s = project2D(face_idx, p, 2, 0, 1, z, layer_nr);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Not all cases create a segment, because a point of a face could create just a dot, and two touching faces
|
||||
// on the slice would create two segments
|
||||
continue;
|
||||
}
|
||||
layers[layer_nr].face_idx_to_segment_index.insert(std::make_pair(face_idx, layers[layer_nr].segmentList.size()));
|
||||
s.faceIndex = face_idx;
|
||||
s.addedToPolygon = false;
|
||||
layers[layer_nr].segmentList.push_back(s);
|
||||
}
|
||||
}
|
||||
for (unsigned int layer_nr = 0; layer_nr < layers.size(); layer_nr++)
|
||||
{
|
||||
layers[layer_nr].makePolygons(mesh, keep_none_closed, extensive_stitching);
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
@@ -1,47 +0,0 @@
|
||||
/** Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_SLICER_H
|
||||
#define SLICER_SLICER_H
|
||||
|
||||
#include "../mesh.h"
|
||||
#include "../utils/polygon.h"
|
||||
|
||||
#include "SlicerSegment.h"
|
||||
#include "ClosePolygonResult.h"
|
||||
#include "SlicerLayer.h"
|
||||
|
||||
#include "../MatSegment.h"
|
||||
|
||||
/*
|
||||
The Slicer creates layers of polygons from an optimized 3D model.
|
||||
The result of the Slicer is a list of polygons without any order or structure.
|
||||
*/
|
||||
namespace cura {
|
||||
|
||||
class Slicer
|
||||
{
|
||||
public:
|
||||
std::vector<SlicerLayer> layers;
|
||||
|
||||
Slicer(Mesh* mesh, int initial, int thickness, int layer_count, bool keepNoneClosed, bool extensiveStitching);
|
||||
|
||||
void dumpSegmentsToHTML(const char* filename);
|
||||
|
||||
protected:
|
||||
Mesh* mesh;
|
||||
|
||||
int layer_height_0;
|
||||
int layer_height;
|
||||
|
||||
/*!
|
||||
* Create a SlicerSegment along the lines going through p0p1 (Start) and p0p2 (End)
|
||||
*
|
||||
* \warning \p p0 may not have the same z as either \p p1 or \p p2
|
||||
*
|
||||
* \param p The face vertice locations in the order the vertices are given in the face
|
||||
*/
|
||||
SlicerSegment project2D(unsigned int face_idx, Point3 p[3], unsigned int idx_shared, unsigned int idx_first, unsigned int idx_second, int32_t z, int32_t layer_nr);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
|
||||
#endif//SLICER_SLICER_H
|
||||
@@ -1,409 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "SlicerLayer.h"
|
||||
#include "../TextureProcessor.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
GapCloserResult SlicerLayer::findPolygonGapCloser(Point ip0, Point ip1)
|
||||
{
|
||||
GapCloserResult ret;
|
||||
ClosePolygonResult c1 = findPolygonPointClosestTo(ip0);
|
||||
ClosePolygonResult c2 = findPolygonPointClosestTo(ip1);
|
||||
if (c1.polygonIdx < 0 || c1.polygonIdx != c2.polygonIdx)
|
||||
{
|
||||
ret.len = -1;
|
||||
return ret;
|
||||
}
|
||||
ret.polygonIdx = c1.polygonIdx;
|
||||
ret.pointIdxA = c1.pointIdx;
|
||||
ret.pointIdxB = c2.pointIdx;
|
||||
ret.AtoB = true;
|
||||
|
||||
if (ret.pointIdxA == ret.pointIdxB)
|
||||
{
|
||||
//Connection points are on the same line segment.
|
||||
ret.len = vSize(ip0 - ip1);
|
||||
}else{
|
||||
//Find out if we have should go from A to B or the other way around.
|
||||
Point p0 = polygonList[ret.polygonIdx][ret.pointIdxA];
|
||||
int64_t lenA = vSize(p0 - ip0);
|
||||
for(unsigned int i = ret.pointIdxA; i != ret.pointIdxB; i = (i + 1) % polygonList[ret.polygonIdx].size())
|
||||
{
|
||||
Point p1 = polygonList[ret.polygonIdx][i];
|
||||
lenA += vSize(p0 - p1);
|
||||
p0 = p1;
|
||||
}
|
||||
lenA += vSize(p0 - ip1);
|
||||
|
||||
p0 = polygonList[ret.polygonIdx][ret.pointIdxB];
|
||||
int64_t lenB = vSize(p0 - ip1);
|
||||
for(unsigned int i = ret.pointIdxB; i != ret.pointIdxA; i = (i + 1) % polygonList[ret.polygonIdx].size())
|
||||
{
|
||||
Point p1 = polygonList[ret.polygonIdx][i];
|
||||
lenB += vSize(p0 - p1);
|
||||
p0 = p1;
|
||||
}
|
||||
lenB += vSize(p0 - ip0);
|
||||
|
||||
if (lenA < lenB)
|
||||
{
|
||||
ret.AtoB = true;
|
||||
ret.len = lenA;
|
||||
}else{
|
||||
ret.AtoB = false;
|
||||
ret.len = lenB;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive_stitching)
|
||||
{
|
||||
Polygons openPolygonList;
|
||||
|
||||
// connect line segments
|
||||
for(unsigned int startSegment=0; startSegment < segmentList.size(); startSegment++)
|
||||
{
|
||||
if (segmentList[startSegment].addedToPolygon)
|
||||
continue;
|
||||
|
||||
Polygon poly;
|
||||
poly.add(segmentList[startSegment].start);
|
||||
|
||||
unsigned int segmentIndex = startSegment;
|
||||
bool canClose;
|
||||
while(true)
|
||||
{
|
||||
canClose = false;
|
||||
segmentList[segmentIndex].addedToPolygon = true;
|
||||
Point p0 = segmentList[segmentIndex].end;
|
||||
poly.add(p0);
|
||||
int nextIndex = -1;
|
||||
const MeshFace& face = mesh->faces[segmentList[segmentIndex].faceIndex];
|
||||
for(unsigned int i=0;i<3;i++)
|
||||
{
|
||||
decltype(face_idx_to_segment_index.begin()) it;
|
||||
if (face.connected_face_index[i] > -1 && (it = face_idx_to_segment_index.find(face.connected_face_index[i])) != face_idx_to_segment_index.end())
|
||||
{
|
||||
int index = (*it).second;
|
||||
Point p1 = segmentList[index].start;
|
||||
Point diff = p0 - p1;
|
||||
if (shorterThen(diff, MM2INT(0.01)))
|
||||
{
|
||||
if (index == static_cast<int>(startSegment))
|
||||
canClose = true;
|
||||
if (segmentList[index].addedToPolygon)
|
||||
continue;
|
||||
nextIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextIndex == -1)
|
||||
break;
|
||||
segmentIndex = nextIndex;
|
||||
}
|
||||
if (canClose)
|
||||
polygonList.add(poly);
|
||||
else
|
||||
openPolygonList.add(poly);
|
||||
}
|
||||
//Clear the segmentList to save memory, it is no longer needed after this point.
|
||||
segmentList.clear();
|
||||
|
||||
// TODO: (?) for mesh surface mode: connect open polygons. Maybe the above algorithm can create two open polygons which are actually connected when the starting segment is in the middle between the two open polygons.
|
||||
|
||||
//Connecting polygons that are not closed yet, as models are not always perfect manifold we need to join some stuff up to get proper polygons
|
||||
//First link up polygon ends that are within 2 microns.
|
||||
for(unsigned int i=0;i<openPolygonList.size();i++)
|
||||
{
|
||||
if (openPolygonList[i].size() < 1) continue;
|
||||
for(unsigned int j=0;j<openPolygonList.size();j++)
|
||||
{
|
||||
if (openPolygonList[j].size() < 1) continue;
|
||||
|
||||
Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0];
|
||||
int64_t distSquared = vSize2(diff);
|
||||
|
||||
if (distSquared < MM2INT(0.02) * MM2INT(0.02))
|
||||
{
|
||||
if (i == j)
|
||||
{
|
||||
polygonList.add(openPolygonList[i]);
|
||||
openPolygonList[i].clear();
|
||||
break;
|
||||
}else{
|
||||
for(unsigned int n=0; n<openPolygonList[j].size(); n++)
|
||||
openPolygonList[i].add(openPolygonList[j][n]);
|
||||
|
||||
openPolygonList[j].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mesh->getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::NORMAL)
|
||||
{
|
||||
//Next link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time.
|
||||
while(1)
|
||||
{
|
||||
int64_t bestScore = MM2INT(10.0) * MM2INT(10.0);
|
||||
unsigned int bestA = -1;
|
||||
unsigned int bestB = -1;
|
||||
bool reversed = false;
|
||||
for(unsigned int i=0;i<openPolygonList.size();i++)
|
||||
{
|
||||
if (openPolygonList[i].size() < 1) continue;
|
||||
for(unsigned int j=0;j<openPolygonList.size();j++)
|
||||
{
|
||||
if (openPolygonList[j].size() < 1) continue;
|
||||
|
||||
Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0];
|
||||
int64_t distSquared = vSize2(diff);
|
||||
if (distSquared < bestScore)
|
||||
{
|
||||
bestScore = distSquared;
|
||||
bestA = i;
|
||||
bestB = j;
|
||||
reversed = false;
|
||||
}
|
||||
|
||||
if (i != j)
|
||||
{
|
||||
Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][openPolygonList[j].size()-1];
|
||||
int64_t distSquared = vSize2(diff);
|
||||
if (distSquared < bestScore)
|
||||
{
|
||||
bestScore = distSquared;
|
||||
bestA = i;
|
||||
bestB = j;
|
||||
reversed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestScore >= MM2INT(10.0) * MM2INT(10.0))
|
||||
break;
|
||||
|
||||
if (bestA == bestB)
|
||||
{
|
||||
polygonList.add(openPolygonList[bestA]);
|
||||
openPolygonList[bestA].clear();
|
||||
}else{
|
||||
if (reversed)
|
||||
{
|
||||
if (openPolygonList[bestA].polygonLength() > openPolygonList[bestB].polygonLength())
|
||||
{
|
||||
for(unsigned int n=openPolygonList[bestB].size()-1; int(n)>=0; n--)
|
||||
openPolygonList[bestA].add(openPolygonList[bestB][n]);
|
||||
openPolygonList[bestB].clear();
|
||||
}else{
|
||||
for(unsigned int n=openPolygonList[bestA].size()-1; int(n)>=0; n--)
|
||||
openPolygonList[bestB].add(openPolygonList[bestA][n]);
|
||||
openPolygonList[bestA].clear();
|
||||
}
|
||||
}else{
|
||||
for(unsigned int n=0; n<openPolygonList[bestB].size(); n++)
|
||||
openPolygonList[bestA].add(openPolygonList[bestB][n]);
|
||||
openPolygonList[bestB].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extensive_stitching)
|
||||
{
|
||||
//For extensive stitching find 2 open polygons that are touching 2 closed polygons.
|
||||
// Then find the sortest path over this polygon that can be used to connect the open polygons,
|
||||
// And generate a path over this shortest bit to link up the 2 open polygons.
|
||||
// (If these 2 open polygons are the same polygon, then the final result is a closed polyon)
|
||||
|
||||
while(1)
|
||||
{
|
||||
unsigned int bestA = -1;
|
||||
unsigned int bestB = -1;
|
||||
GapCloserResult bestResult;
|
||||
bestResult.len = POINT_MAX;
|
||||
bestResult.polygonIdx = -1;
|
||||
bestResult.pointIdxA = -1;
|
||||
bestResult.pointIdxB = -1;
|
||||
|
||||
for(unsigned int i=0; i<openPolygonList.size(); i++)
|
||||
{
|
||||
if (openPolygonList[i].size() < 1) continue;
|
||||
|
||||
{
|
||||
GapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[i][openPolygonList[i].size()-1]);
|
||||
if (res.len > 0 && res.len < bestResult.len)
|
||||
{
|
||||
bestA = i;
|
||||
bestB = i;
|
||||
bestResult = res;
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned int j=0; j<openPolygonList.size(); j++)
|
||||
{
|
||||
if (openPolygonList[j].size() < 1 || i == j) continue;
|
||||
|
||||
GapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[j][openPolygonList[j].size()-1]);
|
||||
if (res.len > 0 && res.len < bestResult.len)
|
||||
{
|
||||
bestA = i;
|
||||
bestB = j;
|
||||
bestResult = res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestResult.len < POINT_MAX)
|
||||
{
|
||||
if (bestA == bestB)
|
||||
{
|
||||
if (bestResult.pointIdxA == bestResult.pointIdxB)
|
||||
{
|
||||
polygonList.add(openPolygonList[bestA]);
|
||||
openPolygonList[bestA].clear();
|
||||
}
|
||||
else if (bestResult.AtoB)
|
||||
{
|
||||
PolygonRef poly = polygonList.newPoly();
|
||||
for(unsigned int j = bestResult.pointIdxA; j != bestResult.pointIdxB; j = (j + 1) % polygonList[bestResult.polygonIdx].size())
|
||||
poly.add(polygonList[bestResult.polygonIdx][j]);
|
||||
for(unsigned int j = openPolygonList[bestA].size() - 1; int(j) >= 0; j--)
|
||||
poly.add(openPolygonList[bestA][j]);
|
||||
openPolygonList[bestA].clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned int n = polygonList.size();
|
||||
polygonList.add(openPolygonList[bestA]);
|
||||
for(unsigned int j = bestResult.pointIdxB; j != bestResult.pointIdxA; j = (j + 1) % polygonList[bestResult.polygonIdx].size())
|
||||
polygonList[n].add(polygonList[bestResult.polygonIdx][j]);
|
||||
openPolygonList[bestA].clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bestResult.pointIdxA == bestResult.pointIdxB)
|
||||
{
|
||||
for(unsigned int n=0; n<openPolygonList[bestA].size(); n++)
|
||||
openPolygonList[bestB].add(openPolygonList[bestA][n]);
|
||||
openPolygonList[bestA].clear();
|
||||
}
|
||||
else if (bestResult.AtoB)
|
||||
{
|
||||
Polygon poly;
|
||||
for(unsigned int n = bestResult.pointIdxA; n != bestResult.pointIdxB; n = (n + 1) % polygonList[bestResult.polygonIdx].size())
|
||||
poly.add(polygonList[bestResult.polygonIdx][n]);
|
||||
for(unsigned int n=poly.size()-1;int(n) >= 0; n--)
|
||||
openPolygonList[bestB].add(poly[n]);
|
||||
for(unsigned int n=0; n<openPolygonList[bestA].size(); n++)
|
||||
openPolygonList[bestB].add(openPolygonList[bestA][n]);
|
||||
openPolygonList[bestA].clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
for(unsigned int n = bestResult.pointIdxB; n != bestResult.pointIdxA; n = (n + 1) % polygonList[bestResult.polygonIdx].size())
|
||||
openPolygonList[bestB].add(polygonList[bestResult.polygonIdx][n]);
|
||||
for(unsigned int n = openPolygonList[bestA].size() - 1; int(n) >= 0; n--)
|
||||
openPolygonList[bestB].add(openPolygonList[bestA][n]);
|
||||
openPolygonList[bestA].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (keep_none_closed)
|
||||
{
|
||||
for(unsigned int n=0; n<openPolygonList.size(); n++)
|
||||
{
|
||||
if (openPolygonList[n].size() > 0)
|
||||
polygonList.add(openPolygonList[n]);
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned int i=0;i<openPolygonList.size();i++)
|
||||
{
|
||||
if (openPolygonList[i].size() > 0)
|
||||
openPolylines.add(openPolygonList[i]);
|
||||
}
|
||||
|
||||
//Remove all the tiny polygons, or polygons that are not closed. As they do not contribute to the actual print.
|
||||
int snapDistance = MM2INT(1.0);
|
||||
for(unsigned int i=0;i<polygonList.size();i++)
|
||||
{
|
||||
int length = 0;
|
||||
|
||||
for(unsigned int n=1; n<polygonList[i].size(); n++)
|
||||
{
|
||||
length += vSize(polygonList[i][n] - polygonList[i][n-1]);
|
||||
if (length > snapDistance)
|
||||
break;
|
||||
}
|
||||
if (length < snapDistance)
|
||||
{
|
||||
polygonList.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// TextureProcessor::processBumpMap(mesh, *this);
|
||||
TextureProcessor::processDualColorTexture(mesh, *this);
|
||||
|
||||
//Finally optimize all the polygons. Every point removed saves time in the long run.
|
||||
polygonList.simplify();
|
||||
|
||||
polygonList.removeDegenerateVerts(); // remove verts connected to overlapping line segments
|
||||
|
||||
int xy_offset = mesh->getSettingInMicrons("xy_offset");
|
||||
if (xy_offset != 0)
|
||||
{
|
||||
polygonList = polygonList.offset(xy_offset);
|
||||
}
|
||||
}
|
||||
|
||||
ClosePolygonResult SlicerLayer::findPolygonPointClosestTo(Point input)
|
||||
{
|
||||
ClosePolygonResult ret;
|
||||
for(unsigned int n=0; n<polygonList.size(); n++)
|
||||
{
|
||||
Point p0 = polygonList[n][polygonList[n].size()-1];
|
||||
for(unsigned int i=0; i<polygonList[n].size(); i++)
|
||||
{
|
||||
Point p1 = polygonList[n][i];
|
||||
|
||||
//Q = A + Normal( B - A ) * ((( B - A ) dot ( P - A )) / VSize( A - B ));
|
||||
Point pDiff = p1 - p0;
|
||||
int64_t lineLength = vSize(pDiff);
|
||||
if (lineLength > 1)
|
||||
{
|
||||
int64_t distOnLine = dot(pDiff, input - p0) / lineLength;
|
||||
if (distOnLine >= 0 && distOnLine <= lineLength)
|
||||
{
|
||||
Point q = p0 + pDiff * distOnLine / lineLength;
|
||||
if (shorterThen(q - input, 100))
|
||||
{
|
||||
ret.intersectionPoint = q;
|
||||
ret.polygonIdx = n;
|
||||
ret.pointIdx = i;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
}
|
||||
}
|
||||
ret.polygonIdx = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -1,45 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_SLICER_LAYER_H
|
||||
#define SLICER_SLICER_LAYER_H
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../mesh.h"
|
||||
#include "../utils/intpoint.h"
|
||||
#include "../utils/polygon.h"
|
||||
|
||||
#include "SlicerSegment.h"
|
||||
#include "GapCloserResult.h"
|
||||
#include "ClosePolygonResult.h"
|
||||
|
||||
#include "../MatSegment.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
class SlicerLayer
|
||||
{
|
||||
public:
|
||||
std::vector<SlicerSegment> segmentList;
|
||||
std::unordered_map<int, int> face_idx_to_segment_index; // topology
|
||||
|
||||
int z;
|
||||
int layer_nr;
|
||||
Polygons polygonList;
|
||||
Polygons openPolylines;
|
||||
|
||||
std::unordered_map<SlicerSegment, MatSegment> segment_to_material_segment;
|
||||
|
||||
void makePolygons(Mesh* mesh, bool keepNoneClosed, bool extensiveStitching);
|
||||
|
||||
private:
|
||||
GapCloserResult findPolygonGapCloser(Point ip0, Point ip1);
|
||||
|
||||
ClosePolygonResult findPolygonPointClosestTo(Point input);
|
||||
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // SLICER_SLICER_LAYER_H
|
||||
@@ -1,52 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef SLICER_SLICER_SEGMENT_H
|
||||
#define SLICER_SLICER_SEGMENT_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "../utils/intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
class SlicerSegment
|
||||
{
|
||||
public:
|
||||
Point start, end;
|
||||
int faceIndex;
|
||||
bool addedToPolygon;
|
||||
|
||||
SlicerSegment() //!< non-initializing constructor
|
||||
{}
|
||||
SlicerSegment(Point start, Point end) //!< partially initializing constructor
|
||||
: start(start)
|
||||
, end(end)
|
||||
{}
|
||||
/*!
|
||||
* equivalence testing irrespective of start/end order
|
||||
*/
|
||||
bool operator==(const SlicerSegment& b) const
|
||||
{
|
||||
return (start == b.start && end == b.end) || (start == b.end && end == b.start);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
namespace std
|
||||
{
|
||||
/*!
|
||||
* hash function irrespective of start/end order
|
||||
*/
|
||||
template<> struct hash<cura::SlicerSegment>
|
||||
{
|
||||
typedef std::size_t result_type;
|
||||
result_type operator()(cura::SlicerSegment const& s) const
|
||||
{
|
||||
return std::hash<cura::Point>()(cura::operator+(s.start, s.end));
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
|
||||
#endif // SLICER_SLICER_SEGMENT_H
|
||||
+74
-39
@@ -3,7 +3,8 @@
|
||||
|
||||
#include <cmath> // sqrt
|
||||
#include <utility> // pair
|
||||
#include "Progress.h"
|
||||
#include <deque>
|
||||
#include "progress/Progress.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
@@ -50,7 +51,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int l
|
||||
|
||||
if (mesh.getSettingBoolean("support_roof_enable"))
|
||||
{
|
||||
generateSupportRoofs(storage, supportAreas, layer_count, mesh.getSettingInMicrons("layer_height"), mesh.getSettingInMicrons("support_roof_height"));
|
||||
generateSupportRoofs(storage, supportAreas, layer_count, storage.getSettingInMicrons("layer_height"), mesh.getSettingInMicrons("support_roof_height"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -111,10 +112,13 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
int min_smoothing_area = 100*100; // minimal area for which to perform smoothing
|
||||
int z_layer_distance_tower = 1; // start tower directly below overhang point
|
||||
|
||||
int layerThickness = mesh.getSettingInMicrons("layer_height");
|
||||
int extrusionWidth = mesh.getSettingInMicrons("support_line_width");
|
||||
int layerThickness = storage.getSettingInMicrons("layer_height");
|
||||
int extrusionWidth = storage.getSettingInMicrons("support_line_width");
|
||||
int supportXYDistance = mesh.getSettingInMicrons("support_xy_distance");
|
||||
|
||||
int support_xy_distance_overhang = mesh.getSettingInMicrons("support_xy_distance_overhang");
|
||||
|
||||
bool use_support_xy_distance_overhang = mesh.getSettingAsSupportDistPriority("support_xy_overrides_z") == SupportDistPriority::Z_OVERRIDES_XY; // whether to use a different xy distance at overhangs
|
||||
|
||||
bool conical_support = mesh.getSettingBoolean("support_conical_enabled");
|
||||
double conical_support_angle = mesh.getSettingInAngleRadians("support_conical_angle");
|
||||
int64_t conical_smallest_breadth = mesh.getSettingInMicrons("support_conical_min_width");
|
||||
@@ -133,7 +137,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
unsigned int layerZdistanceBottom = std::max(0, supportZDistanceBottom / supportLayerThickness);
|
||||
|
||||
double tanAngle = tan(supportAngle) - 0.01; // the XY-component of the supportAngle
|
||||
int maxDistFromLowerLayer = tanAngle * supportLayerThickness; // max dist which can be bridged
|
||||
int max_dist_from_lower_layer = tanAngle * supportLayerThickness; // max dist which can be bridged
|
||||
|
||||
int64_t conical_support_offset;
|
||||
if (conical_support_angle > 0)
|
||||
@@ -165,45 +169,27 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
|
||||
std::vector<std::pair<int, std::vector<Polygons>>> overhang_points; // stores overhang_points along with the layer index at which the overhang point occurs
|
||||
AreaSupport::detectOverhangPoints(storage, mesh, overhang_points, layer_count, supportMinAreaSqrt, extrusionWidth);
|
||||
|
||||
|
||||
|
||||
std::deque<std::pair<Polygons, Polygons>> basic_and_full_overhang_above;
|
||||
for (unsigned int layer_idx = support_layer_count - 1; layer_idx != support_layer_count - 1 - layerZdistanceTop ; layer_idx--)
|
||||
{
|
||||
basic_and_full_overhang_above.push_front(computeBasicAndFullOverhang(storage, mesh, layer_idx, max_dist_from_lower_layer));
|
||||
}
|
||||
|
||||
bool still_in_upper_empty_layers = true;
|
||||
int overhang_points_pos = overhang_points.size() - 1;
|
||||
Polygons supportLayer_last;
|
||||
std::vector<Polygons> towerRoofs;
|
||||
|
||||
for (unsigned int layer_idx = support_layer_count - 1 - layerZdistanceTop; layer_idx != (unsigned int) -1 ; layer_idx--)
|
||||
{
|
||||
basic_and_full_overhang_above.push_front(computeBasicAndFullOverhang(storage, mesh, layer_idx, max_dist_from_lower_layer));
|
||||
|
||||
Polygons overhang;
|
||||
{
|
||||
// compute basic overhang and put in right layer ([layerZdistanceTOp] layers below)
|
||||
Polygons supportLayer_supportee = mesh.layers[layer_idx+layerZdistanceTop].getOutlines();
|
||||
Polygons supportLayer_supporter = storage.getLayerOutlines(layer_idx-1+layerZdistanceTop, false);
|
||||
|
||||
Polygons supportLayer_supported = supportLayer_supporter.offset(maxDistFromLowerLayer);
|
||||
Polygons basic_overhang = supportLayer_supportee.difference(supportLayer_supported);
|
||||
|
||||
// Polygons support_extension = basic_overhang.offset(maxDistFromLowerLayer);
|
||||
// support_extension = support_extension.intersection(supportLayer_supported);
|
||||
// support_extension = support_extension.intersection(supportLayer_supportee);
|
||||
//
|
||||
// Polygons overhang = basic_overhang.unionPolygons(support_extension);
|
||||
// presumably the computation above is slower than the one below
|
||||
|
||||
Polygons overhang_extented = basic_overhang.offset(maxDistFromLowerLayer + 100); // +100 for easier joining with support from layer above
|
||||
overhang = overhang_extented.intersection(supportLayer_supported.unionPolygons(supportLayer_supportee));
|
||||
|
||||
/* layer 2
|
||||
* layer 1 ______________|
|
||||
* _______| ^^^^^ basic overhang
|
||||
*
|
||||
* ^^^^^^^ supporter
|
||||
* ^^^^^^^^^^^^^^^^^ supported
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^ supportee
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^ overhang extended
|
||||
* ^^^^^^^^^ overhang extensions
|
||||
* ^^^^^^^^^^^^^^ overhang
|
||||
*/
|
||||
overhang = basic_and_full_overhang_above.back().second;
|
||||
basic_and_full_overhang_above.pop_back();
|
||||
}
|
||||
|
||||
Polygons& supportLayer_this = overhang;
|
||||
@@ -243,13 +229,29 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
|
||||
// inset using X/Y distance
|
||||
if (supportLayer_this.size() > 0)
|
||||
supportLayer_this = supportLayer_this.difference(storage.getLayerOutlines(layer_idx, false).offset(supportXYDistance));
|
||||
|
||||
{
|
||||
Polygons& basic_overhang = basic_and_full_overhang_above.front().first; // basic overhang on this layer
|
||||
Polygons outlines = storage.getLayerOutlines(layer_idx, false);
|
||||
|
||||
if (use_support_xy_distance_overhang)
|
||||
{
|
||||
Polygons xy_overhang_disallowed = basic_overhang.offset(supportZDistanceTop * tanAngle);
|
||||
Polygons xy_non_overhang_disallowed = outlines.difference(basic_overhang.offset(supportXYDistance)).offset(supportXYDistance);
|
||||
|
||||
Polygons xy_disallowed = xy_overhang_disallowed.unionPolygons(xy_non_overhang_disallowed.unionPolygons(outlines.offset(support_xy_distance_overhang)));
|
||||
supportLayer_this = supportLayer_this.difference(xy_disallowed);
|
||||
}
|
||||
else
|
||||
{
|
||||
supportLayer_this = supportLayer_this.difference(storage.getLayerOutlines(layer_idx, false).offset(supportXYDistance));
|
||||
}
|
||||
}
|
||||
|
||||
supportAreas[layer_idx] = supportLayer_this;
|
||||
|
||||
|
||||
if (still_in_upper_empty_layers && supportLayer_this.size() > 0)
|
||||
{
|
||||
storage.support.layer_nr_max_filled_layer = layer_idx;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -291,6 +293,39 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int m
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* layer 2
|
||||
* layer 1 ______________|
|
||||
* _______| ^^^^^ basic overhang
|
||||
*
|
||||
* ^^^^^^^ supporter
|
||||
* ^^^^^^^^^^^^^^^^^ supported
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^ supportee
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^ overhang extended
|
||||
* ^^^^^^^^^ overhang extensions
|
||||
* ^^^^^^^^^^^^^^ overhang
|
||||
*/
|
||||
std::pair<Polygons, Polygons> AreaSupport::computeBasicAndFullOverhang(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const unsigned int layer_idx, const int64_t max_dist_from_lower_layer)
|
||||
{
|
||||
Polygons supportLayer_supportee = mesh.layers[layer_idx].getOutlines();
|
||||
Polygons supportLayer_supporter = storage.getLayerOutlines(layer_idx-1, false);
|
||||
|
||||
Polygons supportLayer_supported = supportLayer_supporter.offset(max_dist_from_lower_layer);
|
||||
Polygons basic_overhang = supportLayer_supportee.difference(supportLayer_supported);
|
||||
|
||||
// Polygons support_extension = basic_overhang.offset(max_dist_from_lower_layer);
|
||||
// support_extension = support_extension.intersection(supportLayer_supported);
|
||||
// support_extension = support_extension.intersection(supportLayer_supportee);
|
||||
//
|
||||
// Polygons overhang = basic_overhang.unionPolygons(support_extension);
|
||||
// presumably the computation above is slower than the one below
|
||||
|
||||
Polygons overhang_extented = basic_overhang.offset(max_dist_from_lower_layer + 100); // +100 for easier joining with support from layer above
|
||||
Polygons full_overhang = overhang_extented.intersection(supportLayer_supported.unionPolygons(supportLayer_supportee));
|
||||
return std::make_pair(basic_overhang, full_overhang);
|
||||
}
|
||||
|
||||
|
||||
void AreaSupport::detectOverhangPoints(
|
||||
SliceDataStorage& storage,
|
||||
SliceMeshStorage& mesh,
|
||||
|
||||
@@ -75,6 +75,24 @@ private:
|
||||
int extrusionWidth
|
||||
);
|
||||
|
||||
/*!
|
||||
* Compute the basic overhang and full overhang of a layer.
|
||||
* The basic overhang consists of the parts of this layer which are too far away from the layer below to be supported.
|
||||
* The full overhang consists of the basic overhang extended toward the border of the layer below.
|
||||
*
|
||||
* layer 2
|
||||
* layer 1 ______________|
|
||||
* _______| ^^^^^ basic overhang
|
||||
* ^^^^^^^^^^^^^^ full overhang
|
||||
*
|
||||
* \param storage The slice data storage
|
||||
* \param mesh The mesh for which to compute the basic overhangs
|
||||
* \param layer_idx The layer for which to compute the overhang
|
||||
* \param max_dist_from_lower_layer The outward distance from the layer below which can be supported by it
|
||||
* \return a pair of basic overhang and full overhang
|
||||
*/
|
||||
static std::pair<Polygons, Polygons> computeBasicAndFullOverhang(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const unsigned int layer_idx, const int64_t max_dist_from_lower_layer);
|
||||
|
||||
/*!
|
||||
* Adds tower pieces to the current support layer.
|
||||
* From below the roof, the towers are added to the normal support layer and handled as normal support area.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "AABB.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
AABB::AABB()
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
}
|
||||
|
||||
AABB::AABB(Point&min, Point& max)
|
||||
: min(min), max(max)
|
||||
{
|
||||
}
|
||||
|
||||
AABB::AABB(const Polygons& polys)
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
calculate(polys);
|
||||
}
|
||||
|
||||
void AABB::calculate(const Polygons& polys)
|
||||
{
|
||||
min = Point(POINT_MAX, POINT_MAX);
|
||||
max = Point(POINT_MIN, POINT_MIN);
|
||||
for(unsigned int i=0; i<polys.size(); i++)
|
||||
{
|
||||
for(unsigned int j=0; j<polys[i].size(); j++)
|
||||
{
|
||||
include(polys[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AABB::hit(const AABB& other) const
|
||||
{
|
||||
if (max.X < other.min.X) return false;
|
||||
if (min.X > other.max.X) return false;
|
||||
if (max.Y < other.min.Y) return false;
|
||||
if (min.Y > other.max.Y) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AABB::include(Point point)
|
||||
{
|
||||
min.X = std::min(min.X,point.X);
|
||||
min.Y = std::min(min.Y,point.Y);
|
||||
max.X = std::max(max.X,point.X);
|
||||
max.Y = std::max(max.Y,point.Y);
|
||||
}
|
||||
|
||||
void AABB::expand(int dist)
|
||||
{
|
||||
if (min == Point(POINT_MAX, POINT_MAX) || max == Point(POINT_MIN, POINT_MIN))
|
||||
{
|
||||
return;
|
||||
}
|
||||
min.X -= dist;
|
||||
min.Y -= dist;
|
||||
max.X += dist;
|
||||
max.Y += dist;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
+23
-93
@@ -1,8 +1,8 @@
|
||||
#ifndef AABB_H
|
||||
#define AABB_H
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_AABB_H
|
||||
#define UTILS_AABB_H
|
||||
|
||||
|
||||
#include <limits>
|
||||
#include "intpoint.h"
|
||||
#include "polygon.h"
|
||||
|
||||
@@ -17,42 +17,22 @@ class AABB
|
||||
public:
|
||||
Point min, max;
|
||||
|
||||
AABB()
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
}
|
||||
AABB(Point&min, Point& max)
|
||||
: min(min), max(max)
|
||||
{
|
||||
}
|
||||
AABB(Polygons& polys)
|
||||
: min(POINT_MAX, POINT_MAX), max(POINT_MIN, POINT_MIN)
|
||||
{
|
||||
calculate(polys);
|
||||
}
|
||||
AABB(); //!< initializes with invalid min and max
|
||||
AABB(Point& min, Point& max); //!< initializes with given min and max
|
||||
AABB(const Polygons& polys); //!< Computes the boundary box for the given polygons
|
||||
|
||||
void calculate(Polygons& polys)
|
||||
{
|
||||
min = Point(POINT_MAX, POINT_MAX);
|
||||
max = Point(POINT_MIN, POINT_MIN);
|
||||
for(unsigned int i=0; i<polys.size(); i++)
|
||||
{
|
||||
for(unsigned int j=0; j<polys[i].size(); j++)
|
||||
{
|
||||
include(polys[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
void calculate(const Polygons& polys); //!< Calculates the aabb for the given polygons (throws away old min and max data of this aabb)
|
||||
|
||||
/*!
|
||||
* Check whether this aabb overlaps with another.
|
||||
*
|
||||
* In the boundary case false is returned.
|
||||
*
|
||||
* \param other the aabb to check for overlaps with
|
||||
* \return Whether the two aabbs overlap
|
||||
*/
|
||||
bool hit(const AABB& other) const;
|
||||
|
||||
bool hit(const AABB& other) const
|
||||
{
|
||||
if (max.X < other.min.X) return false;
|
||||
if (min.X > other.max.X) return false;
|
||||
if (max.Y < other.min.Y) return false;
|
||||
if (min.Y > other.max.Y) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Includes the specified point in the bounding box.
|
||||
*
|
||||
@@ -60,66 +40,16 @@ public:
|
||||
*
|
||||
* \param point The point to include in the bounding box.
|
||||
*/
|
||||
void include(Point point)
|
||||
{
|
||||
min.X = std::min(min.X,point.X);
|
||||
min.Y = std::min(min.Y,point.Y);
|
||||
max.X = std::max(max.X,point.X);
|
||||
max.Y = std::max(max.Y,point.Y);
|
||||
}
|
||||
};
|
||||
void include(Point point);
|
||||
|
||||
/*!
|
||||
An Axis Aligned Bounding Box. Has a min and max vector, representing minimal and maximal coordinates in the three axes.
|
||||
*/
|
||||
struct AABB3D
|
||||
{
|
||||
Point3 min; //!< The minimal coordinates in x, y and z direction
|
||||
Point3 max; //!< The maximal coordinates in x, y and z direction
|
||||
|
||||
/*!
|
||||
* Create an AABB3D with coordinates at the numeric limits.
|
||||
* Expand the borders of the bounding box in each direction with the given amount
|
||||
*
|
||||
* \param dist The distance by which to expand the borders of the bounding box
|
||||
*/
|
||||
AABB3D()
|
||||
: min(std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max())
|
||||
, max(std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min())
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* Expand the AABB3D to include the point \p p.
|
||||
* \param p The point to include with the bounding box.
|
||||
*/
|
||||
void include(Point3 p)
|
||||
{
|
||||
min.x = std::min(min.x, p.x);
|
||||
min.y = std::min(min.y, p.y);
|
||||
min.z = std::min(min.z, p.z);
|
||||
max.x = std::max(max.x, p.x);
|
||||
max.y = std::max(max.y, p.y);
|
||||
max.z = std::max(max.z, p.z);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Offset the coordinates of the bounding box.
|
||||
* \param offset The offset with which to offset the AABB3D.
|
||||
*/
|
||||
void offset(Point3 offset)
|
||||
{
|
||||
min += offset;
|
||||
max += offset;
|
||||
}
|
||||
/*!
|
||||
* Offset the coordinates of the bounding box.
|
||||
* \param offset The offset with which to offset the AABB3D.
|
||||
*/
|
||||
void offset(Point offset)
|
||||
{
|
||||
min += offset;
|
||||
max += offset;
|
||||
}
|
||||
void expand(int dist);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//AABB_H
|
||||
#endif//UTILS_AABB_H
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
|
||||
#include "AABB3D.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
AABB3D::AABB3D()
|
||||
: min(std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max())
|
||||
, max(std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min())
|
||||
{
|
||||
}
|
||||
|
||||
bool AABB3D::hit(const AABB3D& other) const
|
||||
{
|
||||
if (max.x < other.min.y) return false;
|
||||
if (min.x > other.max.y) return false;
|
||||
if (max.y < other.min.y) return false;
|
||||
if (min.y > other.max.y) return false;
|
||||
if (max.z < other.min.z) return false;
|
||||
if (min.z > other.max.z) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AABB3D::include(Point3 p)
|
||||
{
|
||||
min.x = std::min(min.x, p.x);
|
||||
min.y = std::min(min.y, p.y);
|
||||
min.z = std::min(min.z, p.z);
|
||||
max.x = std::max(max.x, p.x);
|
||||
max.y = std::max(max.y, p.y);
|
||||
max.z = std::max(max.z, p.z);
|
||||
}
|
||||
|
||||
void AABB3D::offset(Point3 offset)
|
||||
{
|
||||
min += offset;
|
||||
max += offset;
|
||||
}
|
||||
|
||||
void AABB3D::offset(Point offset)
|
||||
{
|
||||
min += offset;
|
||||
max += offset;
|
||||
}
|
||||
|
||||
}//namespace cura
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_AABB3D_H
|
||||
#define UTILS_AABB3D_H
|
||||
|
||||
|
||||
#include "intpoint.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
An Axis Aligned Bounding Box. Has a min and max vector, representing minimal and maximal coordinates in the three axes.
|
||||
*/
|
||||
struct AABB3D
|
||||
{
|
||||
Point3 min; //!< The minimal coordinates in x, y and z direction
|
||||
Point3 max; //!< The maximal coordinates in x, y and z direction
|
||||
|
||||
/*!
|
||||
* Create an AABB3D with coordinates at the numeric limits.
|
||||
*/
|
||||
AABB3D();
|
||||
|
||||
/*!
|
||||
* Check whether this aabb overlaps with another.
|
||||
*
|
||||
* In the boundary case false is returned.
|
||||
*
|
||||
* \param other the aabb to check for overlaps with
|
||||
* \return Whether the two aabbs overlap
|
||||
*/
|
||||
bool hit(const AABB3D& other) const;
|
||||
|
||||
/*!
|
||||
* Expand the AABB3D to include the point \p p.
|
||||
* \param p The point to include with the bounding box.
|
||||
*/
|
||||
void include(Point3 p);
|
||||
|
||||
/*!
|
||||
* Offset the coordinates of the bounding box.
|
||||
* \param offset The offset with which to offset the AABB3D.
|
||||
*/
|
||||
void offset(Point3 offset);
|
||||
|
||||
/*!
|
||||
* Offset the coordinates of the bounding box.
|
||||
* \param offset The offset with which to offset the AABB3D.
|
||||
*/
|
||||
void offset(Point offset);
|
||||
};
|
||||
|
||||
}//namespace cura
|
||||
#endif//UTILS_AABB3D_H
|
||||
|
||||
+14
-13
@@ -2,11 +2,12 @@
|
||||
#ifndef UTILS_BUCKET_GRID_2D_H
|
||||
#define UTILS_BUCKET_GRID_2D_H
|
||||
|
||||
#include "logoutput.h"
|
||||
#include "intpoint.h"
|
||||
#include <unordered_map>
|
||||
#include <functional> // std::function
|
||||
|
||||
#include "logoutput.h"
|
||||
#include "intpoint.h"
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
@@ -36,9 +37,9 @@ private:
|
||||
* \param relative_hash The relative position - in grid terms - of the relative point.
|
||||
* \return A point for which the hash is at a grid position of \p relative_hash relative to \p p.
|
||||
*/
|
||||
inline Point getRelativeForHash(const Point& p, const Cellidx& relative_hash)
|
||||
inline Point getRelativeForHash(const Point& p, const Cellidx& relative_hash) const
|
||||
{
|
||||
return p + relative_hash*squareSize;
|
||||
return p + relative_hash * squareSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,13 +66,13 @@ private:
|
||||
*/
|
||||
inline uint32_t pointHash(const Point& point) const
|
||||
{
|
||||
Cellidx p = point/squareSize;
|
||||
Cellidx p = point / squareSize;
|
||||
return pointHash_simple(p);
|
||||
}
|
||||
/*
|
||||
inline uint32_t pointHash(const Point& point, const Point& relativeHash) const
|
||||
{
|
||||
Point p = p/squareSize + relativeHash;
|
||||
Point p = p / squareSize + relativeHash;
|
||||
return pointHash_simple(p);
|
||||
}*/
|
||||
|
||||
@@ -151,7 +152,7 @@ public:
|
||||
* \param p The point for which to find close points.
|
||||
* \param ret Ouput parameter: all objects close to \p p.
|
||||
*/
|
||||
void findNearbyObjects(Point& p, std::vector<T>& ret)
|
||||
void findNearbyObjects(Point& p, std::vector<T>& ret) const
|
||||
{
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
@@ -178,14 +179,14 @@ public:
|
||||
* \param p The point for which to find close points.
|
||||
* \return All objects close to \p p.
|
||||
*/
|
||||
std::vector<T> findNearbyObjects(Point& p)
|
||||
std::vector<T> findNearbyObjects(Point& p) const
|
||||
{
|
||||
std::vector<T> ret;
|
||||
findNearbyObjects(p, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const std::function<bool(Point, T&)> no_precondition;
|
||||
static const std::function<bool(Point, const T&)> no_precondition;
|
||||
|
||||
/*!
|
||||
* Find the nearest object to a given lcoation \p p, if there is any in a neighboring cell in the grid.
|
||||
@@ -195,10 +196,10 @@ public:
|
||||
* \param precondition A precondition which must be satisfied before considering a \p object at a specific \p location as output
|
||||
* \return Whether an object has been found.
|
||||
*/
|
||||
bool findNearestObject(Point& p, T& nearby, std::function<bool(Point location, T& object)> precondition = no_precondition)
|
||||
bool findNearestObject(Point& p, T& nearby, std::function<bool(Point location, const T& object)> precondition = no_precondition) const
|
||||
{
|
||||
bool found = false;
|
||||
int64_t bestDist2 = squareSize*9; // 9 > sqrt(2*2 + 2*2)^2 which is the square of the largest distance of a point to a point in a neighboring cell
|
||||
int64_t bestDist2 = squareSize * 9; // 9 > sqrt(2*2 + 2*2)^2 which is the square of the largest distance of a point to a point in a neighboring cell
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
for (int y = -1; y <= 1; y++)
|
||||
@@ -230,7 +231,7 @@ public:
|
||||
* \param p The location associated with \p t.
|
||||
* \param t The object to insert in the grid cell for position \p p.
|
||||
*/
|
||||
void insert(Point& p, T& t)
|
||||
void insert(Point& p, T t)
|
||||
{
|
||||
// typedef typename Map::iterator iter;
|
||||
// std::pair<iter, bool> emplaced =
|
||||
@@ -245,7 +246,7 @@ public:
|
||||
|
||||
};
|
||||
template<typename T>
|
||||
const std::function<bool(Point, T&)> BucketGrid2D<T>::no_precondition = [](Point loc, T&) { return true; };
|
||||
const std::function<bool(Point, const T&)> BucketGrid2D<T>::no_precondition = [](Point loc, const T&) { return true; };
|
||||
|
||||
}//namespace cura
|
||||
#endif//BUCKET_GRID_2D_H
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#include "Date.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <cstdio> // sscanf
|
||||
#include <cstring> // strstr
|
||||
#include <iomanip> // setw, setfill
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
Date::Date(int year, int month, int day)
|
||||
: year(year)
|
||||
, month(month)
|
||||
, day(day)
|
||||
{
|
||||
}
|
||||
|
||||
std::string Date::toStringDashed()
|
||||
{
|
||||
std::ostringstream str;
|
||||
str << std::setfill('0') << std::setw(4) << year << "-"
|
||||
<< std::setfill('0') << std::setw(2) << month << "-"
|
||||
<< std::setfill('0') << std::setw(2) << day;
|
||||
return str.str();
|
||||
}
|
||||
|
||||
Date::Date()
|
||||
: year(-1)
|
||||
, month(-1)
|
||||
, day(-1)
|
||||
{
|
||||
}
|
||||
|
||||
Date Date::getDate()
|
||||
{
|
||||
Date ret;
|
||||
// code adapted from http://stackoverflow.com/a/1765088/2683223 Jerry Coffin
|
||||
const char* build_date = __DATE__;
|
||||
char s_month[5];
|
||||
static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
||||
|
||||
std::sscanf(build_date, "%s %d %d", s_month, &ret.day, &ret.year);
|
||||
|
||||
ret.month = (strstr(month_names, s_month) - month_names) / 3;
|
||||
|
||||
ret.month++; // humans count Jan as month 1, not zero
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
@@ -0,0 +1,29 @@
|
||||
/** Copyright (C) 2016 Ultimaker - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_DATE_H
|
||||
#define UTILS_DATE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
/*!
|
||||
* Simple class to represent a year, month and day.
|
||||
*/
|
||||
class Date
|
||||
{
|
||||
public:
|
||||
Date(int year, int month, int day); //!< Simple constructor
|
||||
static Date getDate(); //!< Get the current date (compile time)
|
||||
std::string toStringDashed(); //!< Get a formatted string: yyyy-mm-dd
|
||||
protected:
|
||||
int year; //!< Year, e.g. 2016
|
||||
int month; //!< Month, e.g. 12, i.e. starting at 1
|
||||
int day; //!< Day, e.g. 31, i.e. starting at 1
|
||||
private:
|
||||
Date(); //!< Simple constructor initializing all to -1
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // UTILS_DATE_H
|
||||
@@ -1,74 +0,0 @@
|
||||
/** Copyright (C) 2016 Tim Kuipers - Released under terms of the AGPLv3 License */
|
||||
#ifndef UTILS_F_POINT_H
|
||||
#define UTILS_F_POINT_H
|
||||
|
||||
#include <cmath> // sqrt
|
||||
#include <iostream> // auto-serialization / auto-toString() '<<'
|
||||
|
||||
namespace cura
|
||||
{
|
||||
/*!
|
||||
* 2D coordinates represented by floats
|
||||
*/
|
||||
class FPoint
|
||||
{
|
||||
public:
|
||||
float x, y; //!< Coordinates
|
||||
FPoint() //!< non-initializing constructor
|
||||
{}
|
||||
FPoint(float x, float y) //!< constructor
|
||||
: x(x)
|
||||
, y(y)
|
||||
{}
|
||||
|
||||
FPoint operator+(const FPoint& p) const { return FPoint(x+p.x, y+p.y); }
|
||||
FPoint operator-(const FPoint& p) const { return FPoint(x-p.x, y-p.y); }
|
||||
FPoint operator/(const float i) const { return FPoint(x/i, y/i); }
|
||||
FPoint operator*(const float i) const { return FPoint(x*i, y*i); }
|
||||
|
||||
FPoint& operator += (const FPoint& p) { x += p.x; y += p.y; return *this; }
|
||||
FPoint& operator -= (const FPoint& p) { x -= p.x; y -= p.y; return *this; }
|
||||
|
||||
bool operator==(const FPoint& p) const { return x == p.x && y == p.y; }
|
||||
bool operator!=(const FPoint& p) const { return x != p.x || y != p.y; }
|
||||
|
||||
/*!
|
||||
* output to string stream in standard format
|
||||
*/
|
||||
template<class CharT, class TraitsT>
|
||||
friend
|
||||
std::basic_ostream<CharT, TraitsT>&
|
||||
operator <<(std::basic_ostream<CharT, TraitsT>& os, const FPoint& p)
|
||||
{
|
||||
return os << "(" << p.x << ", " << p.y << ")";
|
||||
}
|
||||
|
||||
/*!
|
||||
* squared vector size
|
||||
*/
|
||||
float vSize2() const
|
||||
{
|
||||
return x * x + y * y;
|
||||
}
|
||||
|
||||
/*!
|
||||
* vector size
|
||||
*/
|
||||
float vSize() const
|
||||
{
|
||||
return sqrt(vSize2());
|
||||
}
|
||||
|
||||
/*!
|
||||
* dot product
|
||||
*/
|
||||
float dot(const FPoint& p) const
|
||||
{
|
||||
return x * p.x + y * p.y;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace cura
|
||||
|
||||
#endif // UTILS_F_POINT_H
|
||||
@@ -25,5 +25,4 @@ float LinearAlg2D::getAngleLeft(const Point& a, const Point& b, const Point& c)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace cura
|
||||
}
|
||||
@@ -233,6 +233,11 @@ public:
|
||||
{
|
||||
fprintf(out, txt, args...);
|
||||
}
|
||||
void writeText(Point p, std::string txt)
|
||||
{
|
||||
Point pf = transform(p);
|
||||
fprintf(out, "<text x=\"%lli\" y=\"%lli\" style=\"font-size: 10;\" fill=\"black\">%s</text>\n",pf.X, pf.Y, txt.c_str());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+34
-11
@@ -7,7 +7,24 @@
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
|
||||
bool PolygonRef::shorterThan(int64_t check_length) const
|
||||
{
|
||||
const PolygonRef& polygon = *this;
|
||||
const Point* p0 = &polygon.back();
|
||||
int64_t length = 0;
|
||||
for (const Point& p1 : polygon)
|
||||
{
|
||||
length += vSize(*p0 - p1);
|
||||
if (length >= check_length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
p0 = &p1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PolygonRef::inside(Point p, bool border_result)
|
||||
{
|
||||
PolygonRef thiss = *this;
|
||||
@@ -36,19 +53,19 @@ bool PolygonRef::inside(Point p, bool border_result)
|
||||
return (crossings % 2) == 1;
|
||||
}
|
||||
|
||||
bool Polygons::inside(Point p, bool border_result)
|
||||
bool Polygons::inside(Point p, bool border_result) const
|
||||
{
|
||||
Polygons& thiss = *this;
|
||||
const Polygons& thiss = *this;
|
||||
if (size() < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int crossings = 0;
|
||||
for (PolygonRef poly : thiss)
|
||||
for (const ClipperLib::Path& poly : thiss)
|
||||
{
|
||||
Point p0 = poly.back();
|
||||
for(Point& p1 : poly)
|
||||
for (const Point& p1 : poly)
|
||||
{
|
||||
short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1);
|
||||
if (comp == 1)
|
||||
@@ -160,9 +177,15 @@ void PolygonRef::simplify(int smallest_line_segment_squared, int allowed_error_d
|
||||
last = &here;
|
||||
}
|
||||
}
|
||||
polygon->erase(polygon->begin() + writing_idx , polygon->end());
|
||||
path->erase(path->begin() + writing_idx , path->end());
|
||||
}
|
||||
|
||||
|
||||
if (size() < 3)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
Point* last = &thiss[0];
|
||||
unsigned int writing_idx = 1;
|
||||
for (unsigned int poly_idx = 1; poly_idx < size(); poly_idx++)
|
||||
@@ -185,7 +208,7 @@ void PolygonRef::simplify(int smallest_line_segment_squared, int allowed_error_d
|
||||
last = &here;
|
||||
}
|
||||
}
|
||||
polygon->erase(polygon->begin() + writing_idx , polygon->end());
|
||||
path->erase(path->begin() + writing_idx , path->end());
|
||||
|
||||
|
||||
if (size() < 3)
|
||||
@@ -224,7 +247,7 @@ std::vector<PolygonsPart> Polygons::splitIntoParts(bool unionAll) const
|
||||
std::vector<PolygonsPart> ret;
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
ClipperLib::PolyTree resultPolyTree;
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(paths, ClipperLib::ptSubject, true);
|
||||
if (unionAll)
|
||||
clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
else
|
||||
@@ -298,7 +321,7 @@ PartsView Polygons::splitIntoPartsView(bool unionAll)
|
||||
PartsView partsView(*this);
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
ClipperLib::PolyTree resultPolyTree;
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(paths, ClipperLib::ptSubject, true);
|
||||
if (unionAll)
|
||||
clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
else
|
||||
@@ -310,7 +333,7 @@ PartsView Polygons::splitIntoPartsView(bool unionAll)
|
||||
return partsView;
|
||||
}
|
||||
|
||||
void Polygons::splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node)
|
||||
void Polygons::splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node) const
|
||||
{
|
||||
for(int n=0; n<node->ChildCount(); n++)
|
||||
{
|
||||
|
||||
+113
-89
@@ -29,62 +29,57 @@ const static int clipper_init = (0);
|
||||
|
||||
class PolygonRef
|
||||
{
|
||||
ClipperLib::Path* polygon;
|
||||
ClipperLib::Path* path;
|
||||
PolygonRef()
|
||||
: polygon(nullptr)
|
||||
: path(nullptr)
|
||||
{}
|
||||
public:
|
||||
PolygonRef(ClipperLib::Path& polygon)
|
||||
: polygon(&polygon)
|
||||
: path(&polygon)
|
||||
{}
|
||||
|
||||
PolygonRef(const PolygonRef& other)
|
||||
{
|
||||
polygon = other.polygon;
|
||||
}
|
||||
|
||||
unsigned int size() const
|
||||
{
|
||||
return polygon->size();
|
||||
return path->size();
|
||||
}
|
||||
|
||||
Point& operator[] (unsigned int index) const
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
return (*polygon)[index];
|
||||
return (*path)[index];
|
||||
}
|
||||
|
||||
void* data()
|
||||
{
|
||||
return polygon->data();
|
||||
return path->data();
|
||||
}
|
||||
|
||||
void add(const Point p)
|
||||
{
|
||||
polygon->push_back(p);
|
||||
path->push_back(p);
|
||||
}
|
||||
|
||||
PolygonRef& operator=(const PolygonRef& other) { polygon = other.polygon; return *this; }
|
||||
PolygonRef& operator=(const PolygonRef& other) { path = other.path; return *this; }
|
||||
|
||||
bool operator==(const PolygonRef& other) const =delete;
|
||||
|
||||
ClipperLib::Path& operator*() { return *polygon; }
|
||||
ClipperLib::Path& operator*() { return *path; }
|
||||
|
||||
template <typename... Args>
|
||||
void emplace_back(Args&&... args)
|
||||
{
|
||||
polygon->emplace_back(args...);
|
||||
path->emplace_back(args...);
|
||||
}
|
||||
|
||||
void remove(unsigned int index)
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
polygon->erase(polygon->begin() + index);
|
||||
path->erase(path->begin() + index);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
polygon->clear();
|
||||
path->clear();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -94,31 +89,33 @@ public:
|
||||
*/
|
||||
bool orientation() const
|
||||
{
|
||||
return ClipperLib::Orientation(*polygon);
|
||||
return ClipperLib::Orientation(*path);
|
||||
}
|
||||
|
||||
void reverse()
|
||||
{
|
||||
ClipperLib::ReversePath(*polygon);
|
||||
ClipperLib::ReversePath(*path);
|
||||
}
|
||||
|
||||
int64_t polygonLength() const
|
||||
{
|
||||
int64_t length = 0;
|
||||
Point p0 = (*polygon)[polygon->size()-1];
|
||||
for(unsigned int n=0; n<polygon->size(); n++)
|
||||
Point p0 = (*path)[path->size()-1];
|
||||
for(unsigned int n=0; n<path->size(); n++)
|
||||
{
|
||||
Point p1 = (*polygon)[n];
|
||||
Point p1 = (*path)[n];
|
||||
length += vSize(p0 - p1);
|
||||
p0 = p1;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
bool shorterThan(int64_t check_length) const;
|
||||
|
||||
Point min() const
|
||||
{
|
||||
Point ret = Point(POINT_MAX, POINT_MAX);
|
||||
for(Point p : *polygon)
|
||||
for(Point p : *path)
|
||||
{
|
||||
ret.X = std::min(ret.X, p.X);
|
||||
ret.Y = std::min(ret.Y, p.Y);
|
||||
@@ -129,7 +126,7 @@ public:
|
||||
Point max() const
|
||||
{
|
||||
Point ret = Point(POINT_MIN, POINT_MIN);
|
||||
for(Point p : *polygon)
|
||||
for(Point p : *path)
|
||||
{
|
||||
ret.X = std::max(ret.X, p.X);
|
||||
ret.Y = std::max(ret.Y, p.Y);
|
||||
@@ -139,7 +136,7 @@ public:
|
||||
|
||||
double area() const
|
||||
{
|
||||
return ClipperLib::Area(*polygon);
|
||||
return ClipperLib::Area(*path);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -158,10 +155,10 @@ public:
|
||||
Point centerOfMass() const
|
||||
{
|
||||
double x = 0, y = 0;
|
||||
Point p0 = (*polygon)[polygon->size()-1];
|
||||
for(unsigned int n=0; n<polygon->size(); n++)
|
||||
Point p0 = (*path)[path->size()-1];
|
||||
for(unsigned int n=0; n<path->size(); n++)
|
||||
{
|
||||
Point p1 = (*polygon)[n];
|
||||
Point p1 = (*path)[n];
|
||||
double second_factor = (p0.X * p1.Y) - (p1.X * p0.Y);
|
||||
|
||||
x += double(p0.X + p1.X) * second_factor;
|
||||
@@ -169,7 +166,7 @@ public:
|
||||
p0 = p1;
|
||||
}
|
||||
|
||||
double area = Area(*polygon);
|
||||
double area = Area(*path);
|
||||
|
||||
x = x / 6 / area;
|
||||
y = y / 6 / area;
|
||||
@@ -181,12 +178,12 @@ public:
|
||||
{
|
||||
Point ret = p;
|
||||
float bestDist = FLT_MAX;
|
||||
for(unsigned int n=0; n<polygon->size(); n++)
|
||||
for(unsigned int n=0; n<path->size(); n++)
|
||||
{
|
||||
float dist = vSize2f(p - (*polygon)[n]);
|
||||
float dist = vSize2f(p - (*path)[n]);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
ret = (*polygon)[n];
|
||||
ret = (*path)[n];
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
@@ -213,7 +210,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,17 +218,28 @@ public:
|
||||
void smooth(int remove_length, PolygonRef result)
|
||||
{
|
||||
PolygonRef& thiss = *this;
|
||||
ClipperLib::Path* poly = result.polygon;
|
||||
ClipperLib::Path* poly = result.path;
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,32 +253,32 @@ public:
|
||||
|
||||
void pop_back()
|
||||
{
|
||||
polygon->pop_back();
|
||||
path->pop_back();
|
||||
}
|
||||
|
||||
ClipperLib::Path::reference back() const
|
||||
{
|
||||
return polygon->back();
|
||||
return path->back();
|
||||
}
|
||||
|
||||
ClipperLib::Path::iterator begin()
|
||||
{
|
||||
return polygon->begin();
|
||||
return path->begin();
|
||||
}
|
||||
|
||||
ClipperLib::Path::iterator end()
|
||||
{
|
||||
return polygon->end();
|
||||
return path->end();
|
||||
}
|
||||
|
||||
ClipperLib::Path::const_iterator begin() const
|
||||
{
|
||||
return polygon->begin();
|
||||
return path->begin();
|
||||
}
|
||||
|
||||
ClipperLib::Path::const_iterator end() const
|
||||
{
|
||||
return polygon->end();
|
||||
return path->end();
|
||||
}
|
||||
|
||||
friend class Polygons;
|
||||
@@ -289,7 +297,7 @@ public:
|
||||
Polygon(const PolygonRef& other)
|
||||
: PolygonRef(poly)
|
||||
{
|
||||
poly = *other.polygon;
|
||||
poly = *other.path;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -298,58 +306,74 @@ class PolygonsPart;
|
||||
class Polygons
|
||||
{
|
||||
protected:
|
||||
ClipperLib::Paths polygons;
|
||||
ClipperLib::Paths paths;
|
||||
public:
|
||||
unsigned int size() const
|
||||
{
|
||||
return polygons.size();
|
||||
return paths.size();
|
||||
}
|
||||
|
||||
PolygonRef operator[] (unsigned int index)
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
return PolygonRef(polygons[index]);
|
||||
return PolygonRef(paths[index]);
|
||||
}
|
||||
const PolygonRef operator[] (unsigned int index) const
|
||||
{
|
||||
return const_cast<Polygons*>(this)->operator[](index);
|
||||
}
|
||||
ClipperLib::Paths::iterator begin()
|
||||
{
|
||||
return polygons.begin();
|
||||
return paths.begin();
|
||||
}
|
||||
ClipperLib::Paths::const_iterator begin() const
|
||||
{
|
||||
return paths.begin();
|
||||
}
|
||||
ClipperLib::Paths::iterator end()
|
||||
{
|
||||
return polygons.end();
|
||||
return paths.end();
|
||||
}
|
||||
ClipperLib::Paths::const_iterator end() const
|
||||
{
|
||||
return paths.end();
|
||||
}
|
||||
void remove(unsigned int index)
|
||||
{
|
||||
POLY_ASSERT(index < size());
|
||||
polygons.erase(polygons.begin() + index);
|
||||
paths.erase(paths.begin() + index);
|
||||
}
|
||||
void erase(ClipperLib::Paths::iterator start, ClipperLib::Paths::iterator end)
|
||||
{
|
||||
paths.erase(start, end);
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
polygons.clear();
|
||||
paths.clear();
|
||||
}
|
||||
void add(const PolygonRef& poly)
|
||||
{
|
||||
polygons.push_back(*poly.polygon);
|
||||
paths.push_back(*poly.path);
|
||||
}
|
||||
void add(const Polygons& other)
|
||||
{
|
||||
for(unsigned int n=0; n<other.polygons.size(); n++)
|
||||
polygons.push_back(other.polygons[n]);
|
||||
for(unsigned int n=0; n<other.paths.size(); n++)
|
||||
paths.push_back(other.paths[n]);
|
||||
}
|
||||
PolygonRef newPoly()
|
||||
{
|
||||
polygons.push_back(ClipperLib::Path());
|
||||
return PolygonRef(polygons[polygons.size()-1]);
|
||||
paths.emplace_back();
|
||||
return PolygonRef(paths.back());
|
||||
}
|
||||
PolygonRef back()
|
||||
{
|
||||
return polygons[polygons.size()-1];
|
||||
return PolygonRef(paths.back());
|
||||
}
|
||||
|
||||
Polygons() {}
|
||||
|
||||
Polygons(const Polygons& other) { polygons = other.polygons; }
|
||||
Polygons& operator=(const Polygons& other) { polygons = other.polygons; return *this; }
|
||||
Polygons(const Polygons& other) { paths = other.paths; }
|
||||
Polygons& operator=(const Polygons& other) { paths = other.paths; return *this; }
|
||||
|
||||
bool operator==(const Polygons& other) const =delete;
|
||||
|
||||
@@ -357,18 +381,18 @@ public:
|
||||
{
|
||||
Polygons ret;
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(other.polygons, ClipperLib::ptClip, true);
|
||||
clipper.Execute(ClipperLib::ctDifference, ret.polygons);
|
||||
clipper.AddPaths(paths, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(other.paths, ClipperLib::ptClip, true);
|
||||
clipper.Execute(ClipperLib::ctDifference, ret.paths);
|
||||
return ret;
|
||||
}
|
||||
Polygons unionPolygons(const Polygons& other) const
|
||||
{
|
||||
Polygons ret;
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(other.polygons, ClipperLib::ptSubject, true);
|
||||
clipper.Execute(ClipperLib::ctUnion, ret.polygons, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
clipper.AddPaths(paths, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(other.paths, ClipperLib::ptSubject, true);
|
||||
clipper.Execute(ClipperLib::ctUnion, ret.paths, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
return ret;
|
||||
}
|
||||
/*!
|
||||
@@ -382,27 +406,27 @@ public:
|
||||
{
|
||||
Polygons ret;
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(other.polygons, ClipperLib::ptClip, true);
|
||||
clipper.Execute(ClipperLib::ctIntersection, ret.polygons);
|
||||
clipper.AddPaths(paths, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(other.paths, ClipperLib::ptClip, true);
|
||||
clipper.Execute(ClipperLib::ctIntersection, ret.paths);
|
||||
return ret;
|
||||
}
|
||||
Polygons xorPolygons(const Polygons& other) const
|
||||
{
|
||||
Polygons ret;
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(other.polygons, ClipperLib::ptClip, true);
|
||||
clipper.Execute(ClipperLib::ctXor, ret.polygons);
|
||||
clipper.AddPaths(paths, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(other.paths, ClipperLib::ptClip, true);
|
||||
clipper.Execute(ClipperLib::ctXor, ret.paths);
|
||||
return ret;
|
||||
}
|
||||
Polygons offset(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miter_limit = 1.2) const
|
||||
{
|
||||
Polygons ret;
|
||||
ClipperLib::ClipperOffset clipper(miter_limit, 10.0);
|
||||
clipper.AddPaths(polygons, joinType, ClipperLib::etClosedPolygon);
|
||||
clipper.AddPaths(paths, joinType, ClipperLib::etClosedPolygon);
|
||||
clipper.MiterLimit = miter_limit;
|
||||
clipper.Execute(ret.polygons, distance);
|
||||
clipper.Execute(ret.paths, distance);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -411,9 +435,9 @@ public:
|
||||
Polygons ret;
|
||||
double miterLimit = 1.2;
|
||||
ClipperLib::ClipperOffset clipper(miterLimit, 10.0);
|
||||
clipper.AddPaths(polygons, joinType, ClipperLib::etOpenSquare);
|
||||
clipper.AddPaths(paths, joinType, ClipperLib::etOpenSquare);
|
||||
clipper.MiterLimit = miterLimit;
|
||||
clipper.Execute(ret.polygons, distance);
|
||||
clipper.Execute(ret.paths, distance);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -433,7 +457,7 @@ public:
|
||||
* \param border_result What to return when the point is exactly on the border
|
||||
* \return Whether the point \p p is inside this polygon (or \p border_result when it is on the border)
|
||||
*/
|
||||
bool inside(Point p, bool border_result = false);
|
||||
bool inside(Point p, bool border_result = false) const;
|
||||
|
||||
/*!
|
||||
* Find the polygon inside which point \p p resides.
|
||||
@@ -468,7 +492,7 @@ public:
|
||||
Polygons ret;
|
||||
for (unsigned int p = 0; p < size(); p++)
|
||||
{
|
||||
PolygonRef poly(polygons[p]);
|
||||
PolygonRef poly(paths[p]);
|
||||
if (poly.area() < min_area || poly.size() <= 5) // when optimally removing, a poly with 5 pieces results in a triangle. Smaller polys dont have area!
|
||||
{
|
||||
ret.add(poly);
|
||||
@@ -525,7 +549,7 @@ public:
|
||||
*/
|
||||
PartsView splitIntoPartsView(bool unionAll = false);
|
||||
private:
|
||||
void splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node);
|
||||
void splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node) const;
|
||||
public:
|
||||
/*!
|
||||
* Removes polygons with area smaller than \p minAreaSize (note that minAreaSize is in mm^2, not in micron^2).
|
||||
@@ -655,20 +679,20 @@ public:
|
||||
{
|
||||
Polygons ret;
|
||||
ClipperLib::Clipper clipper(clipper_init);
|
||||
clipper.AddPaths(polygons, ClipperLib::ptSubject, true);
|
||||
clipper.Execute(ClipperLib::ctUnion, ret.polygons);
|
||||
clipper.AddPaths(paths, ClipperLib::ptSubject, true);
|
||||
clipper.Execute(ClipperLib::ctUnion, ret.paths);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t polygonLength() const
|
||||
{
|
||||
int64_t length = 0;
|
||||
for(unsigned int i=0; i<polygons.size(); i++)
|
||||
for(unsigned int i=0; i<paths.size(); i++)
|
||||
{
|
||||
Point p0 = polygons[i][polygons[i].size()-1];
|
||||
for(unsigned int n=0; n<polygons[i].size(); n++)
|
||||
Point p0 = paths[i][paths[i].size()-1];
|
||||
for(unsigned int n=0; n<paths[i].size(); n++)
|
||||
{
|
||||
Point p1 = polygons[i][n];
|
||||
Point p1 = paths[i][n];
|
||||
length += vSize(p0 - p1);
|
||||
p0 = p1;
|
||||
}
|
||||
@@ -679,7 +703,7 @@ public:
|
||||
Point min() const
|
||||
{
|
||||
Point ret = Point(POINT_MAX, POINT_MAX);
|
||||
for(const ClipperLib::Path& polygon : polygons)
|
||||
for(const ClipperLib::Path& polygon : paths)
|
||||
{
|
||||
for(Point p : polygon)
|
||||
{
|
||||
@@ -693,7 +717,7 @@ public:
|
||||
Point max() const
|
||||
{
|
||||
Point ret = Point(POINT_MIN, POINT_MIN);
|
||||
for(const ClipperLib::Path& polygon : polygons)
|
||||
for(const ClipperLib::Path& polygon : paths)
|
||||
{
|
||||
for(Point p : polygon)
|
||||
{
|
||||
@@ -706,11 +730,11 @@ public:
|
||||
|
||||
void applyMatrix(const PointMatrix& matrix)
|
||||
{
|
||||
for(unsigned int i=0; i<polygons.size(); i++)
|
||||
for(unsigned int i=0; i<paths.size(); i++)
|
||||
{
|
||||
for(unsigned int j=0; j<polygons[i].size(); j++)
|
||||
for(unsigned int j=0; j<paths[i].size(); j++)
|
||||
{
|
||||
polygons[i][j] = matrix.apply(polygons[i][j]);
|
||||
paths[i][j] = matrix.apply(paths[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -736,7 +760,7 @@ public:
|
||||
return false;
|
||||
if (!(*this)[0].inside(p))
|
||||
return false;
|
||||
for(unsigned int n=1; n<polygons.size(); n++)
|
||||
for(unsigned int n=1; n<paths.size(); n++)
|
||||
{
|
||||
if ((*this)[n].inside(p))
|
||||
return false;
|
||||
|
||||
+195
-80
@@ -4,83 +4,60 @@
|
||||
#include <list>
|
||||
|
||||
#include "linearAlg2D.h"
|
||||
#include "BucketGrid2D.h"
|
||||
#include "../debug.h"
|
||||
|
||||
|
||||
namespace cura
|
||||
{
|
||||
|
||||
int64_t offset_safe_allowance = 20; // make all offset safe operations a bit less safe to allow for small variations in walls which are supposed to be exactly x perimeters thick
|
||||
int64_t in_between_min_dist_half = 10;
|
||||
|
||||
void PolygonUtils::offsetExtrusionWidth(const Polygons& poly, bool inward, int extrusionWidth, Polygons& result, Polygons* in_between, bool removeOverlappingPerimeters)
|
||||
{
|
||||
int direction = (inward)? -1 : 1;
|
||||
int distance = (inward)? -extrusionWidth : extrusionWidth;
|
||||
if (!removeOverlappingPerimeters)
|
||||
{
|
||||
result = poly.offset(distance);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = poly.offset(distance*3/2 - direction*offset_safe_allowance).offset(-distance/2 + direction*offset_safe_allowance); // overshoot by half the extrusionWidth
|
||||
if (in_between) // if a pointer for in_between is given
|
||||
in_between->add(poly.offset(distance/2 + direction*in_between_min_dist_half).difference(result.offset(-distance/2 - direction*in_between_min_dist_half)));
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonUtils::offsetSafe(const Polygons& poly, int distance, int offset_first_boundary, int extrusion_width, Polygons& result, Polygons* in_between, bool removeOverlappingPerimeters)
|
||||
{
|
||||
int direction = (distance > 0)? 1 : -1;
|
||||
if (!removeOverlappingPerimeters)
|
||||
{
|
||||
result = poly.offset(distance);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = poly.offset(distance + direction*extrusion_width / 2 - direction*offset_safe_allowance).offset(-direction*extrusion_width/2 + direction*offset_safe_allowance); // overshoot by half the extrusionWidth
|
||||
if (in_between) // if a pointer for in_between is given
|
||||
in_between->add(poly.offset(offset_first_boundary + direction*in_between_min_dist_half).difference(result.offset(-direction * extrusion_width/2 - direction*in_between_min_dist_half)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PolygonUtils::offsetSafe(const Polygons& poly, int distance, int extrusionWidth, Polygons& result, bool removeOverlappingPerimeters)
|
||||
{
|
||||
int direction = (distance > 0)? 1 : -1;
|
||||
if (!removeOverlappingPerimeters)
|
||||
{
|
||||
result = poly.offset(distance);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = poly.offset(distance + direction*extrusionWidth/2 - direction*offset_safe_allowance).offset(-direction * extrusionWidth/2 + direction*offset_safe_allowance);
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonUtils::removeOverlapping(const Polygons& poly, int extrusionWidth, Polygons& result)
|
||||
{
|
||||
result = poly.offset(extrusionWidth/2).offset(-extrusionWidth).offset(extrusionWidth/2);
|
||||
}
|
||||
|
||||
Point PolygonUtils::getBoundaryPointWithOffset(PolygonRef poly, unsigned int point_idx, int64_t offset)
|
||||
{
|
||||
Point p0 = poly[(point_idx > 0) ? (point_idx - 1) : (poly.size() - 1)];
|
||||
Point p1 = poly[point_idx];
|
||||
Point p2 = poly[(point_idx < (poly.size() - 1)) ? (point_idx + 1) : 0];
|
||||
|
||||
Point off0 = 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
|
||||
|
||||
int p0_idx;
|
||||
for (p0_idx = int(point_idx) - 1; (unsigned int)p0_idx != point_idx; p0_idx = p0_idx - 1)
|
||||
{ // find the last point different from p1
|
||||
if (p0_idx == -1)
|
||||
{
|
||||
p0_idx = poly.size() - 1;
|
||||
}
|
||||
if (poly[p0_idx] != p1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
Point p0 = poly[p0_idx];
|
||||
|
||||
unsigned int p2_idx;
|
||||
for (p2_idx = point_idx + 1; p2_idx != point_idx; p2_idx = p2_idx + 1)
|
||||
{ // find the next point different from p1
|
||||
if (p2_idx == poly.size())
|
||||
{
|
||||
p2_idx = 0;
|
||||
}
|
||||
if (poly[p2_idx] != p1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
Point& p2 = poly[p2_idx];
|
||||
|
||||
Point off0 = turn90CCW(normal(p1 - p0, MM2INT(10.0))); // 10.0 for some precision
|
||||
Point off1 = turn90CCW(normal(p2 - p1, MM2INT(10.0))); // 10.0 for some precision
|
||||
Point n = normal(off0 + off1, -offset);
|
||||
|
||||
|
||||
return p1 + n;
|
||||
}
|
||||
|
||||
unsigned int PolygonUtils::moveOutside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||
{
|
||||
return moveInside(polygons, from, -distance, maxDist2);
|
||||
}
|
||||
/*
|
||||
* Implementation assumes moving inside, but moving outside should just as well be possible.
|
||||
*/
|
||||
unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||
unsigned int PolygonUtils::moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||
{
|
||||
Point ret = from;
|
||||
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
|
||||
@@ -88,19 +65,19 @@ unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int dista
|
||||
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];
|
||||
const PolygonRef poly = polygons[poly_idx];
|
||||
if (poly.size() < 2)
|
||||
continue;
|
||||
Point p0 = poly[poly.size()-2];
|
||||
Point p1 = poly.back();
|
||||
bool projected_p_beyond_prev_segment = dot(p1 - p0, from - p0) > vSize2(p1 - p0);
|
||||
for(Point& p2 : poly)
|
||||
for(const Point& p2 : poly)
|
||||
{
|
||||
// X = A + Normal( B - A ) * ((( B - A ) dot ( P - A )) / VSize( A - B ));
|
||||
// X = P projected on AB
|
||||
Point& a = p1;
|
||||
Point& b = p2;
|
||||
Point& p = from;
|
||||
const Point& a = p1;
|
||||
const Point& b = p2;
|
||||
const Point& p = from;
|
||||
Point ab = b - a;
|
||||
Point ap = p - a;
|
||||
int64_t ab_length = vSize(ab);
|
||||
@@ -192,6 +169,54 @@ unsigned int PolygonUtils::moveInside(Polygons& polygons, Point& from, int dista
|
||||
return NO_INDEX;
|
||||
}
|
||||
|
||||
Point PolygonUtils::moveOutside(const ClosestPolygonPoint& cpp, const int distance)
|
||||
{
|
||||
return moveInside(cpp, -distance);
|
||||
}
|
||||
|
||||
Point PolygonUtils::moveInside(const ClosestPolygonPoint& cpp, const int distance)
|
||||
{
|
||||
if (distance == 0)
|
||||
{ // the point which is assumed to be on the boundary doesn't have to be moved
|
||||
return cpp.location;
|
||||
}
|
||||
const PolygonRef poly = cpp.poly;
|
||||
unsigned int point_idx = cpp.pos;
|
||||
const Point& on_boundary = cpp.location;
|
||||
|
||||
Point& p1 = poly[point_idx];
|
||||
unsigned int p2_idx;
|
||||
for (p2_idx = point_idx + 1; p2_idx != point_idx; p2_idx = p2_idx + 1)
|
||||
{ // find the next point different from p1
|
||||
if (p2_idx == poly.size())
|
||||
{
|
||||
p2_idx = 0;
|
||||
}
|
||||
if (poly[p2_idx] != p1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
Point& p2 = poly[p2_idx];
|
||||
|
||||
if (on_boundary == p1)
|
||||
{
|
||||
return getBoundaryPointWithOffset(poly, point_idx, -distance);
|
||||
}
|
||||
else if (on_boundary == p2)
|
||||
{
|
||||
return getBoundaryPointWithOffset(poly, p2_idx, -distance);
|
||||
}
|
||||
else
|
||||
{
|
||||
const Point& x = on_boundary; // on_boundary is already projected on p1-p2
|
||||
|
||||
Point inward_dir = turn90CCW(normal(p2 - p1, distance));
|
||||
return x + inward_dir;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PolygonUtils::findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result, int sample_size)
|
||||
{
|
||||
PolygonRef poly1 = poly1_result.poly;
|
||||
@@ -279,11 +304,11 @@ ClosestPolygonPoint PolygonUtils::findNearestClosest(Point from, PolygonRef poly
|
||||
Point& p1 = polygon[p1_idx];
|
||||
Point& p2 = polygon[p2_idx];
|
||||
|
||||
Point closestHere = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
|
||||
int64_t dist = vSize2(from - closestHere);
|
||||
Point closest_here = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
|
||||
int64_t dist = vSize2(from - closest_here);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
best = closestHere;
|
||||
best = closest_here;
|
||||
closestDist = dist;
|
||||
bestPos = p1_idx;
|
||||
}
|
||||
@@ -315,11 +340,11 @@ ClosestPolygonPoint PolygonUtils::findClosest(Point from, Polygons& polygons)
|
||||
{
|
||||
PolygonRef poly = polygons[ply];
|
||||
if (poly.size() == 0) continue;
|
||||
ClosestPolygonPoint closestHere = findClosest(from, poly);
|
||||
int64_t dist = vSize2(from - closestHere.location);
|
||||
ClosestPolygonPoint closest_here = findClosest(from, poly);
|
||||
int64_t dist = vSize2(from - closest_here.location);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
best = closestHere;
|
||||
best = closest_here;
|
||||
closestDist = dist;
|
||||
}
|
||||
|
||||
@@ -348,11 +373,11 @@ ClosestPolygonPoint PolygonUtils::findClosest(Point from, PolygonRef polygon)
|
||||
if (p2_idx >= polygon.size()) p2_idx = 0;
|
||||
Point& p2 = polygon[p2_idx];
|
||||
|
||||
Point closestHere = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
|
||||
int64_t dist = vSize2(from - closestHere);
|
||||
Point closest_here = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
|
||||
int64_t dist = vSize2(from - closest_here);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
best = closestHere;
|
||||
best = closest_here;
|
||||
closestDist = dist;
|
||||
bestPos = p;
|
||||
}
|
||||
@@ -361,11 +386,101 @@ ClosestPolygonPoint PolygonUtils::findClosest(Point from, PolygonRef polygon)
|
||||
return ClosestPolygonPoint(best, bestPos, polygon);
|
||||
}
|
||||
|
||||
BucketGrid2D<PolygonsPointIndex>* PolygonUtils::createLocToLineGrid(const Polygons& polygons, int square_size)
|
||||
{
|
||||
unsigned int n_points = 0;
|
||||
for (const auto& poly : polygons)
|
||||
{
|
||||
n_points += poly.size();
|
||||
}
|
||||
|
||||
BucketGrid2D<PolygonsPointIndex>* ret = new BucketGrid2D<PolygonsPointIndex>(square_size, n_points);
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
const PolygonRef poly = polygons[poly_idx];
|
||||
for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
|
||||
{
|
||||
Point& p1 = poly[point_idx];
|
||||
Point& p2 = poly[(point_idx + 1) % poly.size()];
|
||||
|
||||
ret->insert(p1, PolygonsPointIndex(poly_idx, point_idx));
|
||||
Point vec = p2 - p1;
|
||||
int64_t vec_length = vSize(vec);
|
||||
for (int64_t dist_along_line = square_size; dist_along_line < vec_length; dist_along_line += square_size)
|
||||
{
|
||||
Point point_along_line = p1 + vec * dist_along_line / vec_length;
|
||||
|
||||
ret->insert(point_along_line, PolygonsPointIndex(poly_idx, point_idx));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The current implemetnation can check the same line segment multiple times,
|
||||
* since the same line segment can occur in multiple cells if it it longer than the cell size of the BucketGrid.
|
||||
*
|
||||
* We could skip the duplication by keeping a vector of vectors of bools.
|
||||
*
|
||||
*/
|
||||
ClosestPolygonPoint* PolygonUtils::findClose(Point from, const Polygons& polygons, const BucketGrid2D<PolygonsPointIndex> loc_to_line)
|
||||
{
|
||||
std::vector<PolygonsPointIndex> near_lines;
|
||||
loc_to_line.findNearbyObjects(from, near_lines);
|
||||
|
||||
const Point arbitrary_point = polygons[0][0];
|
||||
Point best = arbitrary_point;
|
||||
|
||||
int64_t closest_dist2 = vSize2(from - best);
|
||||
PolygonsPointIndex best_point_poly_idx(NO_INDEX, NO_INDEX);
|
||||
for (PolygonsPointIndex& point_poly_index : near_lines)
|
||||
{
|
||||
const PolygonRef poly = polygons[point_poly_index.poly_idx];
|
||||
Point& p1 = poly[point_poly_index.point_idx];
|
||||
Point& p2 = poly[(point_poly_index.point_idx + 1) % poly.size()];
|
||||
|
||||
Point closest_here = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
|
||||
int64_t dist = vSize2(from - closest_here);
|
||||
if (dist < closest_dist2)
|
||||
{
|
||||
best = closest_here;
|
||||
closest_dist2 = dist;
|
||||
best_point_poly_idx = point_poly_index;
|
||||
}
|
||||
}
|
||||
if (best_point_poly_idx.poly_idx == NO_INDEX)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ClosestPolygonPoint(best, best_point_poly_idx.point_idx, polygons[best_point_poly_idx.poly_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> PolygonUtils::findClose(const PolygonRef from, const Polygons& destination, const BucketGrid2D< PolygonsPointIndex > destination_loc_to_line)
|
||||
{
|
||||
std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> ret;
|
||||
for (unsigned int point_idx = 0; point_idx < from.size(); point_idx++)
|
||||
{
|
||||
const Point& point = from[point_idx];
|
||||
ClosestPolygonPoint* best_here = findClose(point, destination, destination_loc_to_line);
|
||||
if (best_here)
|
||||
{
|
||||
ret.push_back(std::make_pair(ClosestPolygonPoint(point, point_idx, from), *best_here));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário